perf: use GIO instead of xdg-mime for app.getApplicationNameForProtocol() (#51251)

perf: use GIO instead of xdg-mime for app.getApplicationNameForProtocol()

The Linux impl of app.getApplicationNameForProtocol() now uses
`g_app_info_get_default_for_uri_scheme()` + `g_app_info_get_display_name()`
instead of spawning a call to the `xdg-mime` shell command.

Clean up the related tests: remove the xdg-mime mock.
This commit is contained in:
Charles Kerr
2026-04-23 15:09:43 -05:00
committed by GitHub
parent f99a3980e5
commit 7d6227ad86
2 changed files with 41 additions and 19 deletions

View File

@@ -8,6 +8,7 @@
#include <stdlib.h>
#if BUILDFLAG(IS_LINUX)
#include <gio/gio.h>
#include <gtk/gtk.h>
#endif
@@ -15,6 +16,7 @@
#include "base/environment.h"
#include "base/process/launch.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "electron/electron_version.h"
#include "shell/browser/javascript_environment.h"
#include "shell/browser/native_window.h"
@@ -135,11 +137,15 @@ bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol,
}
std::u16string Browser::GetApplicationNameForProtocol(const GURL& url) {
const std::vector<std::string> argv = {
"xdg-mime", "query", "default",
base::StrCat({"x-scheme-handler/", url.scheme()})};
const auto scheme = std::string{url.scheme()}; // gio can't use string_view
auto* app_info = g_app_info_get_default_for_uri_scheme(scheme.c_str());
if (!app_info)
return {};
return base::ASCIIToUTF16(GetXdgAppOutput(argv).value_or(std::string()));
const char* const name = g_app_info_get_display_name(app_info);
const std::u16string u16name = base::UTF8ToUTF16(name);
g_object_unref(app_info);
return u16name;
}
bool Browser::SetBadgeCount(std::optional<int> count) {

View File

@@ -1530,21 +1530,17 @@ describe('app module', () => {
ifdescribe(process.platform === 'linux')('on Linux with mocked XDG dirs', () => {
const fixtureApp = path.join(fixturesPath, 'api', 'protocol-name');
const desktopFileId = 'mock-browser.desktop';
const mockDisplayName = 'Mock Browser';
const mockScheme = 'mockproto';
const mockMimeType = `x-scheme-handler/${mockScheme}`;
function spawnWithXdgMock(
url: string,
xdgDataHome: string,
xdgConfigHome: string,
xdgBinDir: string
): Promise<string> {
function spawnWithXdgMock(url: string, xdgDataHome: string, xdgConfigHome: string): Promise<string> {
return new Promise((resolve, reject) => {
const child = cp.spawn(process.execPath, [fixtureApp, url], {
env: {
...process.env,
PATH: [xdgBinDir, process.env.PATH].filter(Boolean).join(':'),
XDG_DATA_HOME: xdgDataHome,
XDG_DATA_DIRS: [xdgDataHome, process.env.XDG_DATA_DIRS].filter(Boolean).join(':'),
XDG_DATA_DIRS: xdgDataHome,
XDG_CONFIG_HOME: xdgConfigHome
},
stdio: ['ignore', 'pipe', 'pipe']
@@ -1565,7 +1561,7 @@ describe('app module', () => {
try {
const parsed = JSON.parse(stdout);
resolve(parsed.name.trimEnd());
resolve(parsed.name);
} catch {
reject(new Error(`Failed to parse output: ${stdout}\nstderr: ${stderr}`));
}
@@ -1577,22 +1573,42 @@ describe('app module', () => {
let xdgDir: string;
let xdgDataHome: string;
let xdgConfigHome: string;
let xdgBinDir: string;
before(() => {
({ xdgDir, xdgDataHome, xdgConfigHome, xdgBinDir } = makeXdgMockDirectories('electron-xdg-'));
xdgDir = fs.mkdtempSync(path.join(os.tmpdir(), 'electron-xdg-'));
xdgDataHome = path.join(xdgDir, 'data');
xdgConfigHome = path.join(xdgDir, 'config');
const appsDir = path.join(xdgDataHome, 'applications');
fs.mkdirSync(appsDir, { recursive: true });
fs.mkdirSync(xdgConfigHome, { recursive: true });
fs.writeFileSync(
path.join(appsDir, desktopFileId),
[
'[Desktop Entry]',
`Name=${mockDisplayName}`,
'Exec=/usr/bin/true %u',
'Type=Application',
`MimeType=${mockMimeType};`
].join('\n')
);
fs.writeFileSync(
path.join(xdgConfigHome, 'mimeapps.list'),
['[Default Applications]', `${mockMimeType}=${desktopFileId}`].join('\n')
);
});
after(() => {
fs.rmSync(xdgDir, { recursive: true, force: true });
});
it('returns the desktop file ID for a registered protocol', async () => {
const name = await spawnWithXdgMock(`${mockScheme}://`, xdgDataHome, xdgConfigHome, xdgBinDir);
expect(name).to.equal(desktopFileId);
it('returns the display name for a registered protocol', async () => {
const name = await spawnWithXdgMock(`${mockScheme}://`, xdgDataHome, xdgConfigHome);
expect(name).to.equal(mockDisplayName);
});
it('returns an empty string for an unregistered protocol', async () => {
const name = await spawnWithXdgMock('unregistered-proto://', xdgDataHome, xdgConfigHome, xdgBinDir);
const name = await spawnWithXdgMock('unregistered-proto://', xdgDataHome, xdgConfigHome);
expect(name).to.equal('');
});
});