From 7d6227ad863e026677953a118499e56f83e416c3 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 23 Apr 2026 15:09:43 -0500 Subject: [PATCH] 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. --- shell/browser/browser_linux.cc | 14 ++++++++--- spec/api-app-spec.ts | 46 +++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/shell/browser/browser_linux.cc b/shell/browser/browser_linux.cc index 8e9a2eb488..80f9b527f8 100644 --- a/shell/browser/browser_linux.cc +++ b/shell/browser/browser_linux.cc @@ -8,6 +8,7 @@ #include #if BUILDFLAG(IS_LINUX) +#include #include #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 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 count) { diff --git a/spec/api-app-spec.ts b/spec/api-app-spec.ts index 68f89feaf4..76912a1573 100644 --- a/spec/api-app-spec.ts +++ b/spec/api-app-spec.ts @@ -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 { + function spawnWithXdgMock(url: string, xdgDataHome: string, xdgConfigHome: string): Promise { 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(''); }); });