From c4965cb5807937b4491cbf024e4815d635438f63 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 23 Apr 2026 13:47:37 -0500 Subject: [PATCH] test: add linux coverage for default protocol client APIs (#51288) Add Linux-only app tests to check the default protocol handler. This includes adding reusable XDG mock fixtures. Manual backport of 2c46abe from `main`. --- spec/api-app-spec.ts | 106 +++++++++++++++++- 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, 166 insertions(+), 1 deletion(-) 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 094b9a9ba9..28213b6912 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, listen, waitUntil } from './lib/spec-helpers'; +import { defer, ifdescribe, ifit, 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'; @@ -1435,6 +1451,94 @@ 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}=`)); + + 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('getApplicationNameForProtocol()', () => { // TODO: Linux CI doesn't have registered http & https handlers ifit(!(process.env.CI && process.platform === 'linux'))('returns application names for common protocols', function () { 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..28d4b782c0 --- /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 \ No newline at end of file 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..162133c94e --- /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 \ No newline at end of file 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..a2ef231e04 --- /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; \ No newline at end of file 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..ed935c3429 --- /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; \ No newline at end of file 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..e7df503df7 --- /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; \ No newline at end of file