From 26ff715d36fe2935a59166e93b8ec057984942ea Mon Sep 17 00:00:00 2001 From: "trop[bot]" <37223003+trop[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 16:28:45 -0500 Subject: [PATCH] test: add linux coverage for default protocol client APIs (#51285) Add Linux-only app tests to check the default protocol handler. This includes adding reusable XDG mock fixtures. Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Charles Kerr --- spec/api-app-spec.ts | 165 ++++++++++++------ spec/fixtures/api/xdg-mock/bin/xdg-mime | 15 ++ spec/fixtures/api/xdg-mock/bin/xdg-settings | 31 ++++ .../api/xdg-mock/config/mimeapps.list | 5 + .../data/applications/electron-test.desktop | 5 + .../data/applications/mock-browser.desktop | 5 + 6 files changed, 168 insertions(+), 58 deletions(-) create mode 100644 spec/fixtures/api/xdg-mock/bin/xdg-mime create mode 100644 spec/fixtures/api/xdg-mock/bin/xdg-settings create mode 100644 spec/fixtures/api/xdg-mock/config/mimeapps.list create mode 100644 spec/fixtures/api/xdg-mock/data/applications/electron-test.desktop create mode 100644 spec/fixtures/api/xdg-mock/data/applications/mock-browser.desktop diff --git a/spec/api-app-spec.ts b/spec/api-app-spec.ts index 1617fee238..244ab575ea 100644 --- a/spec/api-app-spec.ts +++ b/spec/api-app-spec.ts @@ -9,16 +9,32 @@ import * as fs from 'node:fs'; import * as http from 'node:http'; import * as https from 'node:https'; import * as net from 'node:net'; +import * as os from 'node:os'; import * as path from 'node:path'; import * as readline from 'node:readline'; import { setTimeout } from 'node:timers/promises'; import { promisify } from 'node:util'; import { collectStreamBody, getResponse } from './lib/net-helpers'; -import { ifdescribe, ifit, isWayland, listen, waitUntil } from './lib/spec-helpers'; +import { defer, ifdescribe, ifit, isWayland, listen, waitUntil } from './lib/spec-helpers'; import { closeWindow, closeAllWindows } from './lib/window-helpers'; const fixturesPath = path.resolve(__dirname, 'fixtures'); +const xdgMockFixturePath = path.join(fixturesPath, 'api', 'xdg-mock'); + +function makeXdgMockDirectories (prefix: string) { + const xdgDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); + fs.cpSync(xdgMockFixturePath, xdgDir, { recursive: true }); + + const xdgDataHome = path.join(xdgDir, 'data'); + const xdgConfigHome = path.join(xdgDir, 'config'); + const xdgBinDir = path.join(xdgDir, 'bin'); + + fs.chmodSync(path.join(xdgBinDir, 'xdg-mime'), 0o755); + fs.chmodSync(path.join(xdgBinDir, 'xdg-settings'), 0o755); + + return { xdgDir, xdgDataHome, xdgConfigHome, xdgBinDir }; +} const isMacOSx64 = process.platform === 'darwin' && process.arch === 'x64'; @@ -1458,7 +1474,6 @@ describe('app module', () => { const fixtureApp = path.join(fixturesPath, 'api', 'protocol-name'); const desktopFileId = 'mock-browser.desktop'; const mockScheme = 'mockproto'; - const mockMimeType = `x-scheme-handler/${mockScheme}`; function spawnWithXdgMock ( url: string, @@ -1507,62 +1522,7 @@ describe('app module', () => { let xdgConfigHome: string; let xdgBinDir: string; before(() => { - xdgDir = fs.mkdtempSync(path.join(require('node:os').tmpdir(), 'electron-xdg-')); - xdgDataHome = path.join(xdgDir, 'data'); - xdgConfigHome = path.join(xdgDir, 'config'); - xdgBinDir = path.join(xdgDir, 'bin'); - const appsDir = path.join(xdgDataHome, 'applications'); - fs.mkdirSync(appsDir, { recursive: true }); - fs.mkdirSync(xdgConfigHome, { recursive: true }); - fs.mkdirSync(xdgBinDir, { recursive: true }); - - fs.writeFileSync( - path.join(appsDir, desktopFileId), - [ - '[Desktop Entry]', - 'Name=Mock Browser', - 'Exec=/usr/bin/true %u', - 'Type=Application', - `MimeType=${mockMimeType};` - ].join('\n') - ); - - const mimeAppsContents = [ - '[Default Applications]', - `${mockMimeType}=${desktopFileId}`, - '', - '[Added Associations]', - `${mockMimeType}=${desktopFileId};` - ].join('\n'); - - fs.writeFileSync(path.join(xdgConfigHome, 'mimeapps.list'), mimeAppsContents); - fs.writeFileSync(path.join(appsDir, 'mimeapps.list'), mimeAppsContents); - fs.writeFileSync(path.join(appsDir, 'defaults.list'), mimeAppsContents); - - // Different xdg-utils versions resolve custom XDG dirs differently, so - // prepend a deterministic xdg-mime shim for this test. - const xdgMimePath = path.join(xdgBinDir, 'xdg-mime'); - fs.writeFileSync( - xdgMimePath, - [ - '#!/bin/sh', - 'if [ "$1" != "query" ] || [ "$2" != "default" ]; then', - ' exit 1', - 'fi', - 'mime="$3"', - 'for list in "$XDG_CONFIG_HOME/mimeapps.list" "$XDG_DATA_HOME/applications/mimeapps.list" "$XDG_DATA_HOME/applications/defaults.list"; do', - ' if [ -f "$list" ]; then', - ' result=$(grep "^$mime=" "$list" | head -n 1 | cut -d= -f2 | cut -d";" -f1)', - ' if [ -n "$result" ]; then', - ' printf "%s\\n" "$result"', - ' exit 0', - ' fi', - ' fi', - 'done', - 'exit 0' - ].join('\n') - ); - fs.chmodSync(xdgMimePath, 0o755); + ({ xdgDir, xdgDataHome, xdgConfigHome, xdgBinDir } = makeXdgMockDirectories('electron-xdg-')); }); after(() => { @@ -1604,6 +1564,95 @@ describe('app module', () => { }); }); + ifdescribe(process.platform === 'linux')('default protocol client APIs with mocked XDG settings', () => { + const protocol = 'electron-test-linux'; + const desktopFileId = 'electron-test.desktop'; + const protocolMimeType = `x-scheme-handler/${protocol}`; + + let xdgDir: string; + let xdgDataHome: string; + let xdgConfigHome: string; + let xdgBinDir: string; + let oldEnv: Record; + + const getRegisteredHandler = () => { + for (const list of [ + path.join(xdgConfigHome, 'mimeapps.list'), + path.join(xdgDataHome, 'applications', 'mimeapps.list'), + path.join(xdgDataHome, 'applications', 'defaults.list') + ]) { + if (!fs.existsSync(list)) continue; + + const match = fs + .readFileSync(list, 'utf8') + .split('\n') + .find((line) => line.startsWith(`${protocolMimeType}=`)); + + // foo=bar.desktop; --> bar.desktop + if (match) return match.split('=', 2)[1].split(';', 1)[0]; + } + + return ''; + }; + + beforeEach(() => { + ({ xdgDir, xdgDataHome, xdgConfigHome, xdgBinDir } = makeXdgMockDirectories('electron-xdg-default-client-')); + + oldEnv = { + PATH: process.env.PATH, + CHROME_DESKTOP: process.env.CHROME_DESKTOP, + XDG_DATA_HOME: process.env.XDG_DATA_HOME, + XDG_DATA_DIRS: process.env.XDG_DATA_DIRS, + XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME + }; + + defer(() => { + for (const [key, value] of Object.entries(oldEnv)) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + + fs.rmSync(xdgDir, { recursive: true, force: true }); + }); + + process.env.PATH = [xdgBinDir, oldEnv.PATH].filter(Boolean).join(':'); + process.env.XDG_DATA_HOME = xdgDataHome; + process.env.XDG_DATA_DIRS = [xdgDataHome, oldEnv.XDG_DATA_DIRS].filter(Boolean).join(':'); + process.env.XDG_CONFIG_HOME = xdgConfigHome; + app.setDesktopName(desktopFileId); + }); + + it('writes the default handler to the XDG association files', async () => { + expect(getRegisteredHandler()).to.equal(''); + + expect(app.setAsDefaultProtocolClient(protocol)).to.equal(true); + + await waitUntil(() => getRegisteredHandler() === desktopFileId); + expect(getRegisteredHandler()).to.equal(desktopFileId); + }); + + it('detects whether the app is the default protocol client', async () => { + expect(app.isDefaultProtocolClient(protocol)).to.equal(false); + + fs.writeFileSync( + path.join(xdgConfigHome, 'mimeapps.list'), + ['[Default Applications]', `${protocolMimeType}=other.desktop`].join('\n') + ); + expect(app.isDefaultProtocolClient(protocol)).to.equal(false); + + fs.writeFileSync( + path.join(xdgConfigHome, 'mimeapps.list'), + ['[Default Applications]', `${protocolMimeType}=${desktopFileId}`].join('\n') + ); + + await waitUntil(() => app.isDefaultProtocolClient(protocol)); + expect(app.isDefaultProtocolClient(protocol)).to.equal(true); + }); + }); + describe('protocol scheme validation', () => { it('rejects empty protocol names', () => { expect(app.setAsDefaultProtocolClient('')).to.equal(false); diff --git a/spec/fixtures/api/xdg-mock/bin/xdg-mime b/spec/fixtures/api/xdg-mock/bin/xdg-mime new file mode 100644 index 0000000000..b43389a8d6 --- /dev/null +++ b/spec/fixtures/api/xdg-mock/bin/xdg-mime @@ -0,0 +1,15 @@ +#!/bin/sh +if [ "$1" != "query" ] || [ "$2" != "default" ]; then + exit 1 +fi +mime="$3" +for list in "$XDG_CONFIG_HOME/mimeapps.list" "$XDG_DATA_HOME/applications/mimeapps.list" "$XDG_DATA_HOME/applications/defaults.list"; do + if [ -f "$list" ]; then + result=$(grep "^$mime=" "$list" | head -n 1 | cut -d= -f2 | cut -d";" -f1) + if [ -n "$result" ]; then + printf "%s\n" "$result" + exit 0 + fi + fi +done +exit 0 diff --git a/spec/fixtures/api/xdg-mock/bin/xdg-settings b/spec/fixtures/api/xdg-mock/bin/xdg-settings new file mode 100644 index 0000000000..63df91f6c5 --- /dev/null +++ b/spec/fixtures/api/xdg-mock/bin/xdg-settings @@ -0,0 +1,31 @@ +#!/bin/sh +set -eu +get_handler() { + for list in "$XDG_CONFIG_HOME/mimeapps.list" "$XDG_DATA_HOME/applications/mimeapps.list" "$XDG_DATA_HOME/applications/defaults.list"; do + if [ -f "$list" ]; then + result=$(grep "^x-scheme-handler/$1=" "$list" | head -n 1 | cut -d= -f2 | cut -d";" -f1) + if [ -n "$result" ]; then + printf "%s\n" "$result" + return 0 + fi + fi + done + return 1 +} +if [ "$1" = "set" ] && [ "$2" = "default-url-scheme-handler" ]; then + mkdir -p "$XDG_CONFIG_HOME" + { + printf "[Default Applications]\n" + printf "x-scheme-handler/%s=%s\n" "$3" "$4" + } > "$XDG_CONFIG_HOME/mimeapps.list" + exit 0 +fi +if [ "$1" = "check" ] && [ "$2" = "default-url-scheme-handler" ]; then + if [ "$(get_handler "$3" 2>/dev/null || true)" = "$4" ]; then + printf "yes\n" + else + printf "no\n" + fi + exit 0 +fi +exit 1 diff --git a/spec/fixtures/api/xdg-mock/config/mimeapps.list b/spec/fixtures/api/xdg-mock/config/mimeapps.list new file mode 100644 index 0000000000..bafb38a38b --- /dev/null +++ b/spec/fixtures/api/xdg-mock/config/mimeapps.list @@ -0,0 +1,5 @@ +[Default Applications] +x-scheme-handler/mockproto=mock-browser.desktop + +[Added Associations] +x-scheme-handler/mockproto=mock-browser.desktop; diff --git a/spec/fixtures/api/xdg-mock/data/applications/electron-test.desktop b/spec/fixtures/api/xdg-mock/data/applications/electron-test.desktop new file mode 100644 index 0000000000..e4112163e8 --- /dev/null +++ b/spec/fixtures/api/xdg-mock/data/applications/electron-test.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Name=Electron Test +Exec=/usr/bin/true %u +Type=Application +MimeType=x-scheme-handler/electron-test-linux; diff --git a/spec/fixtures/api/xdg-mock/data/applications/mock-browser.desktop b/spec/fixtures/api/xdg-mock/data/applications/mock-browser.desktop new file mode 100644 index 0000000000..a2b73be071 --- /dev/null +++ b/spec/fixtures/api/xdg-mock/data/applications/mock-browser.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Name=Mock Browser +Exec=/usr/bin/true %u +Type=Application +MimeType=x-scheme-handler/mockproto;