mirror of
https://github.com/zen-browser/desktop.git
synced 2026-07-30 20:07:48 +00:00
gh-14765: Sync upstream Firefox to version 153.0.1 (gh-14764)
This commit is contained in:
@@ -34,7 +34,7 @@ Zen is a firefox-based browser with the aim of pushing your productivity to a ne
|
||||
|
||||
### Firefox Versions
|
||||
|
||||
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `153.0`! 🚀
|
||||
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `153.0.1`! 🚀
|
||||
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 153.0`!
|
||||
|
||||
### Contributing
|
||||
|
||||
@@ -1 +1 @@
|
||||
b1d3fe8f65751e6d695fa9ef94b11ad81e70dac1
|
||||
30959a45eaaf0bc499f3054793c76c6d7f0c8bc2
|
||||
@@ -118,7 +118,19 @@ run-if = [
|
||||
]
|
||||
tags = "os_integration"
|
||||
|
||||
["browser_setDefaultProtocolHandler.js"]
|
||||
run-if = [
|
||||
"os == 'win'",
|
||||
]
|
||||
tags = "os_integration"
|
||||
|
||||
["browser_setDesktopBackgroundPreview.js"]
|
||||
disabled="Disabled by import_external_tests.py"
|
||||
support-files = ["large.png"]
|
||||
support-files = ["large.png", "canvas.html"]
|
||||
tags = "os_integration"
|
||||
|
||||
["browser_windowsSetDefaultAppCmdHandler.js"]
|
||||
run-if = [
|
||||
"os == 'win'",
|
||||
]
|
||||
tags = "os_integration"
|
||||
|
||||
@@ -71,4 +71,10 @@ add_task(async function () {
|
||||
],
|
||||
screenshotPath
|
||||
);
|
||||
|
||||
// Regression test for bug 2003265, likely to become flaky if it regresses.
|
||||
await testFileCreationPositive(
|
||||
["-url", "about:blank", "-screenshot", screenshotPath],
|
||||
screenshotPath
|
||||
);
|
||||
});
|
||||
|
||||
@@ -8,6 +8,9 @@ ChromeUtils.defineESModuleGetters(this, {
|
||||
sinon: "resource://testing-common/Sinon.sys.mjs",
|
||||
});
|
||||
|
||||
const confusedFoxPath = ShellService.getBundledPdfFile("confused_fox.pdf").path;
|
||||
const SET_HANDLER_WIN11 = Ci.nsIWindowsShellService.OPEN_WITH_SET_HANDLER;
|
||||
|
||||
const setDefaultBrowserUserChoiceStub = sinon.stub();
|
||||
const setDefaultExtensionHandlersUserChoiceStub = sinon
|
||||
.stub()
|
||||
@@ -34,17 +37,15 @@ const _userChoiceImpossibleTelemetryResultStub = sinon
|
||||
const setDefaultStub = sinon.stub();
|
||||
// We'll dynamically update this as needed during the tests.
|
||||
const queryCurrentDefaultHandlerForStub = sinon.stub();
|
||||
const launchOpenWithDefaultPickerForFileTypeStub = sinon.stub();
|
||||
const launchSetDefaultAppPickerStub = sinon.stub();
|
||||
const launchModernSettingsDialogDefaultAppsStub = sinon.stub();
|
||||
const shellStub = sinon.stub(ShellService, "shellService").value({
|
||||
setDefaultBrowser: setDefaultStub,
|
||||
queryCurrentDefaultHandlerFor: queryCurrentDefaultHandlerForStub,
|
||||
QueryInterface: () => ({
|
||||
launchOpenWithDefaultPickerForFileType:
|
||||
launchOpenWithDefaultPickerForFileTypeStub,
|
||||
launchModernSettingsDialogDefaultApps:
|
||||
launchModernSettingsDialogDefaultAppsStub,
|
||||
}),
|
||||
launchSetDefaultAppPicker: launchSetDefaultAppPickerStub,
|
||||
launchModernSettingsDialogDefaultApps:
|
||||
launchModernSettingsDialogDefaultAppsStub,
|
||||
QueryInterface: ChromeUtils.generateQI([]),
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
@@ -228,7 +229,7 @@ add_task(async function test_setAsDefaultPDFHandler_knownBrowser() {
|
||||
const expectedArguments = [aumi, [".pdf", "FirefoxPDF"]];
|
||||
const resetStubs = () => {
|
||||
setDefaultExtensionHandlersUserChoiceStub.resetHistory();
|
||||
launchOpenWithDefaultPickerForFileTypeStub.resetHistory();
|
||||
launchSetDefaultAppPickerStub.resetHistory();
|
||||
launchModernSettingsDialogDefaultAppsStub.resetHistory();
|
||||
};
|
||||
|
||||
@@ -250,7 +251,7 @@ add_task(async function test_setAsDefaultPDFHandler_knownBrowser() {
|
||||
"Called default browser agent with expected arguments"
|
||||
);
|
||||
Assert.ok(
|
||||
launchOpenWithDefaultPickerForFileTypeStub.notCalled,
|
||||
launchSetDefaultAppPickerStub.notCalled,
|
||||
"Did not fall back to open-with picker"
|
||||
);
|
||||
Assert.ok(
|
||||
@@ -271,7 +272,7 @@ add_task(async function test_setAsDefaultPDFHandler_knownBrowser() {
|
||||
"Called default browser agent with expected arguments"
|
||||
);
|
||||
Assert.ok(
|
||||
launchOpenWithDefaultPickerForFileTypeStub.notCalled,
|
||||
launchSetDefaultAppPickerStub.notCalled,
|
||||
"Did not fall back to open-with picker"
|
||||
);
|
||||
Assert.ok(
|
||||
@@ -289,7 +290,7 @@ add_task(async function test_setAsDefaultPDFHandler_knownBrowser() {
|
||||
"Did not use userChoice"
|
||||
);
|
||||
Assert.ok(
|
||||
launchOpenWithDefaultPickerForFileTypeStub.notCalled,
|
||||
launchSetDefaultAppPickerStub.notCalled,
|
||||
"Did not fall back to open-with picker"
|
||||
);
|
||||
Assert.ok(
|
||||
@@ -310,7 +311,7 @@ add_task(async function test_setAsDefaultPDFHandler_knownBrowser() {
|
||||
"Called default browser agent with expected arguments"
|
||||
);
|
||||
Assert.ok(
|
||||
launchOpenWithDefaultPickerForFileTypeStub.notCalled,
|
||||
launchSetDefaultAppPickerStub.notCalled,
|
||||
"Did not fall back to open-with picker"
|
||||
);
|
||||
Assert.ok(
|
||||
@@ -365,8 +366,11 @@ add_task(async function test_setAsDefaultPDFHandler_fallback() {
|
||||
|
||||
Assert.ok(userChoiceStub.called, "Attempted userChoice");
|
||||
Assert.ok(
|
||||
launchOpenWithDefaultPickerForFileTypeStub.calledWith(".pdf"),
|
||||
"Fell back to open-with picker for .pdf"
|
||||
launchSetDefaultAppPickerStub.calledWith(
|
||||
confusedFoxPath,
|
||||
SET_HANDLER_WIN11
|
||||
),
|
||||
"Fell back to open-with picker with bundled PDF path and Win11 flag"
|
||||
);
|
||||
Assert.ok(
|
||||
launchModernSettingsDialogDefaultAppsStub.notCalled,
|
||||
@@ -392,7 +396,7 @@ add_task(async function test_setAsDefaultPDFHandler_fallback() {
|
||||
);
|
||||
userChoiceStub.resetHistory();
|
||||
isDefaultHandlerForStub.resetHistory();
|
||||
launchOpenWithDefaultPickerForFileTypeStub.resetHistory();
|
||||
launchSetDefaultAppPickerStub.resetHistory();
|
||||
launchModernSettingsDialogDefaultAppsStub.resetHistory();
|
||||
|
||||
info(
|
||||
@@ -413,22 +417,25 @@ add_task(async function test_setAsDefaultPDFHandler_fallback() {
|
||||
isDefaultHandlerForStub.returns(true);
|
||||
userChoiceStub.resetHistory();
|
||||
isDefaultHandlerForStub.resetHistory();
|
||||
launchOpenWithDefaultPickerForFileTypeStub.resetHistory();
|
||||
launchSetDefaultAppPickerStub.resetHistory();
|
||||
launchModernSettingsDialogDefaultAppsStub.resetHistory();
|
||||
|
||||
info(
|
||||
"When userChoice fails and open-with picker fails, should fall back to settings dialog"
|
||||
);
|
||||
Services.fog.testResetFOG();
|
||||
launchOpenWithDefaultPickerForFileTypeStub.throws(
|
||||
launchSetDefaultAppPickerStub.throws(
|
||||
new Error("mock IOpenWithLauncher failure")
|
||||
);
|
||||
await ShellService.setAsDefaultPDFHandler(false);
|
||||
|
||||
Assert.ok(userChoiceStub.called, "Attempted userChoice");
|
||||
Assert.ok(
|
||||
launchOpenWithDefaultPickerForFileTypeStub.calledWith(".pdf"),
|
||||
"Attempted open-with picker for .pdf"
|
||||
launchSetDefaultAppPickerStub.calledWith(
|
||||
confusedFoxPath,
|
||||
SET_HANDLER_WIN11
|
||||
),
|
||||
"Attempted open-with picker with bundled PDF path and Win11 flag"
|
||||
);
|
||||
Assert.ok(
|
||||
launchModernSettingsDialogDefaultAppsStub.called,
|
||||
@@ -463,7 +470,7 @@ add_task(async function test_setAsDefaultPDFHandler_fallback() {
|
||||
);
|
||||
userChoiceStub.resetHistory();
|
||||
isDefaultHandlerForStub.resetHistory();
|
||||
launchOpenWithDefaultPickerForFileTypeStub.resetHistory();
|
||||
launchSetDefaultAppPickerStub.resetHistory();
|
||||
launchModernSettingsDialogDefaultAppsStub.resetHistory();
|
||||
|
||||
info(
|
||||
@@ -499,7 +506,7 @@ add_task(async function test_setAsDefaultPDFHandler_fallback() {
|
||||
"Event result_is_default is false when no method set the default"
|
||||
);
|
||||
} finally {
|
||||
launchOpenWithDefaultPickerForFileTypeStub.reset();
|
||||
launchSetDefaultAppPickerStub.reset();
|
||||
launchModernSettingsDialogDefaultAppsStub.reset();
|
||||
sandbox.restore();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
@@ -529,7 +536,7 @@ add_task(async function test_setAsDefaultPDFHandler_useOpenWithDisabled() {
|
||||
await ShellService.setAsDefaultPDFHandler(false);
|
||||
|
||||
Assert.ok(
|
||||
launchOpenWithDefaultPickerForFileTypeStub.notCalled,
|
||||
launchSetDefaultAppPickerStub.notCalled,
|
||||
"Did not invoke open-with picker when pref is disabled"
|
||||
);
|
||||
Assert.ok(
|
||||
@@ -550,7 +557,7 @@ add_task(async function test_setAsDefaultPDFHandler_useOpenWithDisabled() {
|
||||
"Event result_is_default reflects isDefaultHandlerFor"
|
||||
);
|
||||
} finally {
|
||||
launchOpenWithDefaultPickerForFileTypeStub.reset();
|
||||
launchSetDefaultAppPickerStub.reset();
|
||||
launchModernSettingsDialogDefaultAppsStub.reset();
|
||||
sandbox.restore();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
ChromeUtils.defineESModuleGetters(this, {
|
||||
DEFAULT_PROTOCOL_URLS:
|
||||
"moz-src:///browser/components/shell/ShellService.sys.mjs",
|
||||
SET_DEFAULT_REDIRECT_PREF:
|
||||
"moz-src:///browser/components/shell/WindowsSetDefaultRedirect.sys.mjs",
|
||||
WindowsSetDefaultRedirect:
|
||||
"moz-src:///browser/components/shell/WindowsSetDefaultRedirect.sys.mjs",
|
||||
sinon: "resource://testing-common/Sinon.sys.mjs",
|
||||
});
|
||||
|
||||
// Everything here is Windows.
|
||||
Assert.equal(AppConstants.platform, "win", "Platform is Windows");
|
||||
|
||||
// Protocol defaults carry OPEN_WITH_PROTOCOL_MESSAGING on top of the
|
||||
// set-handler flag, unlike the file-type flags in browser_setDefaultPDFHandler.
|
||||
const SET_PROTOCOL_WIN11 =
|
||||
Ci.nsIWindowsShellService.OPEN_WITH_SET_HANDLER |
|
||||
Ci.nsIWindowsShellService.OPEN_WITH_PROTOCOL_MESSAGING;
|
||||
const SET_PROTOCOL_WIN10 =
|
||||
Ci.nsIWindowsShellService.OPEN_WITH_SET_HANDLER_WIN10 |
|
||||
Ci.nsIWindowsShellService.OPEN_WITH_PROTOCOL_MESSAGING;
|
||||
|
||||
const launchSetDefaultAppPickerStub = sinon.stub();
|
||||
const launchModernSettingsDialogDefaultAppsStub = sinon.stub();
|
||||
const shellStub = sinon.stub(ShellService, "shellService").value({
|
||||
launchSetDefaultAppPicker: launchSetDefaultAppPickerStub,
|
||||
launchModernSettingsDialogDefaultApps:
|
||||
launchModernSettingsDialogDefaultAppsStub,
|
||||
QueryInterface: ChromeUtils.generateQI([]),
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
shellStub.restore();
|
||||
Services.prefs.clearUserPref(SET_DEFAULT_REDIRECT_PREF);
|
||||
});
|
||||
|
||||
function resetState() {
|
||||
launchSetDefaultAppPickerStub.reset();
|
||||
launchModernSettingsDialogDefaultAppsStub.reset();
|
||||
Services.prefs.clearUserPref(SET_DEFAULT_REDIRECT_PREF);
|
||||
}
|
||||
|
||||
function readPrefObject() {
|
||||
return JSON.parse(
|
||||
Services.prefs.getStringPref(SET_DEFAULT_REDIRECT_PREF, "")
|
||||
);
|
||||
}
|
||||
|
||||
add_task(async function test_default_https_uses_url_and_win11_flag() {
|
||||
resetState();
|
||||
const sandbox = sinon.createSandbox();
|
||||
sandbox.stub(ShellService, "_isWindows11").returns(true);
|
||||
|
||||
try {
|
||||
await ShellService.setAsDefaultProtocolHandler("https");
|
||||
|
||||
Assert.ok(
|
||||
launchSetDefaultAppPickerStub.calledOnce,
|
||||
"Picker invoked once for the https default"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
launchSetDefaultAppPickerStub.firstCall.args,
|
||||
[DEFAULT_PROTOCOL_URLS.https, SET_PROTOCOL_WIN11],
|
||||
"Picker called with https URL and SET_HANDLER | PROTOCOL_MESSAGING"
|
||||
);
|
||||
Assert.ok(
|
||||
launchModernSettingsDialogDefaultAppsStub.notCalled,
|
||||
"Modern settings not invoked when launcher succeeds"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
readPrefObject(),
|
||||
{
|
||||
openWithArg: DEFAULT_PROTOCOL_URLS.https,
|
||||
overrideUri: null,
|
||||
type: WindowsSetDefaultRedirect.TYPE.PROTOCOL,
|
||||
},
|
||||
"Pref records the URL; overrideUri null so the round-trip is suppressed"
|
||||
);
|
||||
} finally {
|
||||
sandbox.restore();
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_custom_url_overrides_protocol_default() {
|
||||
resetState();
|
||||
const sandbox = sinon.createSandbox();
|
||||
sandbox.stub(ShellService, "_isWindows11").returns(true);
|
||||
|
||||
try {
|
||||
const customUrl = "https://custom.example.com/handler-check";
|
||||
await ShellService.setAsDefaultProtocolHandler("https", customUrl, true);
|
||||
|
||||
Assert.deepEqual(
|
||||
launchSetDefaultAppPickerStub.firstCall.args,
|
||||
[customUrl, SET_PROTOCOL_WIN11],
|
||||
"Picker called with caller-provided URL, not the protocol default"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
readPrefObject(),
|
||||
{
|
||||
openWithArg: customUrl,
|
||||
overrideUri: DEFAULT_PROTOCOL_URLS.https,
|
||||
type: WindowsSetDefaultRedirect.TYPE.PROTOCOL,
|
||||
},
|
||||
"Pref stashes the custom URL + protocol-default overrideUri for the round-trip"
|
||||
);
|
||||
} finally {
|
||||
sandbox.restore();
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_win10_uses_set_handler_win10_flag() {
|
||||
resetState();
|
||||
const sandbox = sinon.createSandbox();
|
||||
sandbox.stub(ShellService, "_isWindows11").returns(false);
|
||||
|
||||
try {
|
||||
await ShellService.setAsDefaultProtocolHandler("https");
|
||||
Assert.deepEqual(
|
||||
launchSetDefaultAppPickerStub.firstCall.args,
|
||||
[DEFAULT_PROTOCOL_URLS.https, SET_PROTOCOL_WIN10],
|
||||
"Picker called with SET_HANDLER_WIN10 | PROTOCOL_MESSAGING when not on Win11"
|
||||
);
|
||||
} finally {
|
||||
sandbox.restore();
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_falls_back_to_settings_when_picker_throws() {
|
||||
resetState();
|
||||
const sandbox = sinon.createSandbox();
|
||||
sandbox.stub(ShellService, "_isWindows11").returns(true);
|
||||
launchSetDefaultAppPickerStub.throws(new Error("mock launcher failure"));
|
||||
|
||||
try {
|
||||
await ShellService.setAsDefaultProtocolHandler("https", undefined, true);
|
||||
|
||||
Assert.ok(launchSetDefaultAppPickerStub.called, "Tried picker first");
|
||||
Assert.ok(
|
||||
launchModernSettingsDialogDefaultAppsStub.called,
|
||||
"Fell through to modern settings when launcher threw"
|
||||
);
|
||||
Assert.ok(
|
||||
!Services.prefs.prefHasUserValue(SET_DEFAULT_REDIRECT_PREF),
|
||||
"Pending redirect cleared on launcher failure so a later round-trip can't pick up stale intent"
|
||||
);
|
||||
} finally {
|
||||
sandbox.restore();
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_unknown_protocol_with_no_url_throws() {
|
||||
resetState();
|
||||
|
||||
await Assert.rejects(
|
||||
ShellService.setAsDefaultProtocolHandler("notaprotocol"),
|
||||
/No URL provided and no DEFAULT_PROTOCOL_URLS fallback/,
|
||||
"Throws when neither URL nor protocol fallback resolves to a URL"
|
||||
);
|
||||
Assert.ok(
|
||||
launchSetDefaultAppPickerStub.notCalled,
|
||||
"Picker not invoked when args don't resolve to a URL"
|
||||
);
|
||||
Assert.ok(
|
||||
!Services.prefs.prefHasUserValue(SET_DEFAULT_REDIRECT_PREF),
|
||||
"Pref not touched when validation fails before any side effects"
|
||||
);
|
||||
});
|
||||
|
||||
// Wait for the deferred set_default_protocol_handler_attempt event to be
|
||||
// recorded, then return the single event emitted by the most recent call.
|
||||
async function awaitAttemptEvent() {
|
||||
await TestUtils.waitForCondition(() => {
|
||||
const events =
|
||||
Glean.browser.setDefaultProtocolHandlerAttempt.testGetValue();
|
||||
return events && events.length;
|
||||
}, "Recorded set_default_protocol_handler_attempt event");
|
||||
const events = Glean.browser.setDefaultProtocolHandlerAttempt.testGetValue();
|
||||
Assert.equal(events.length, 1, "Recorded exactly one attempt event");
|
||||
return events[0];
|
||||
}
|
||||
|
||||
add_task(async function test_telemetry_records_open_with_success() {
|
||||
resetState();
|
||||
const sandbox = sinon.createSandbox();
|
||||
sandbox.stub(ShellService, "_isWindows11").returns(true);
|
||||
const isDefaultHandlerForStub = sandbox
|
||||
.stub(ShellService, "isDefaultHandlerFor")
|
||||
.returns(true);
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.shell.setDefaultProtocolHandler.attemptWaitTimeMs", 0]],
|
||||
});
|
||||
|
||||
try {
|
||||
Services.fog.testResetFOG();
|
||||
await ShellService.setAsDefaultProtocolHandler("https");
|
||||
|
||||
const event = await awaitAttemptEvent();
|
||||
Assert.equal(event.extra.method, "open_with", "Event method is open_with");
|
||||
Assert.equal(event.extra.success, "true", "Event success is true");
|
||||
Assert.equal(event.extra.protocol, "https", "Event protocol is https");
|
||||
Assert.equal(
|
||||
event.extra.result_is_default,
|
||||
"true",
|
||||
"Event result_is_default reflects isDefaultHandlerFor"
|
||||
);
|
||||
Assert.ok(
|
||||
isDefaultHandlerForStub.calledWith("https"),
|
||||
"Sampled isDefaultHandlerFor with the protocol"
|
||||
);
|
||||
} finally {
|
||||
sandbox.restore();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_telemetry_records_settings_fallback() {
|
||||
resetState();
|
||||
const sandbox = sinon.createSandbox();
|
||||
sandbox.stub(ShellService, "_isWindows11").returns(true);
|
||||
sandbox.stub(ShellService, "isDefaultHandlerFor").returns(true);
|
||||
launchSetDefaultAppPickerStub.throws(new Error("mock launcher failure"));
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.shell.setDefaultProtocolHandler.attemptWaitTimeMs", 0]],
|
||||
});
|
||||
|
||||
try {
|
||||
Services.fog.testResetFOG();
|
||||
await ShellService.setAsDefaultProtocolHandler("mailto");
|
||||
|
||||
Assert.equal(
|
||||
Glean.browser.setDefaultProtocolHandlerModernSettingsResult.Success.testGetValue(),
|
||||
1,
|
||||
"Recorded modern settings success"
|
||||
);
|
||||
|
||||
const event = await awaitAttemptEvent();
|
||||
Assert.equal(
|
||||
event.extra.method,
|
||||
"settings",
|
||||
"Event method is settings (last attempted)"
|
||||
);
|
||||
Assert.equal(
|
||||
event.extra.success,
|
||||
"true",
|
||||
"Event success reflects modern settings launch"
|
||||
);
|
||||
Assert.equal(event.extra.protocol, "mailto", "Event protocol is mailto");
|
||||
} finally {
|
||||
sandbox.restore();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_telemetry_records_complete_failure() {
|
||||
resetState();
|
||||
const sandbox = sinon.createSandbox();
|
||||
sandbox.stub(ShellService, "_isWindows11").returns(true);
|
||||
sandbox.stub(ShellService, "isDefaultHandlerFor").returns(false);
|
||||
launchSetDefaultAppPickerStub.throws(new Error("mock launcher failure"));
|
||||
launchModernSettingsDialogDefaultAppsStub.throws(
|
||||
new Error("mock modern settings failure")
|
||||
);
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.shell.setDefaultProtocolHandler.attemptWaitTimeMs", 0]],
|
||||
});
|
||||
|
||||
try {
|
||||
Services.fog.testResetFOG();
|
||||
await ShellService.setAsDefaultProtocolHandler("https");
|
||||
|
||||
Assert.equal(
|
||||
Glean.browser.setDefaultProtocolHandlerModernSettingsResult.Failure.testGetValue(),
|
||||
1,
|
||||
"Recorded modern settings failure"
|
||||
);
|
||||
|
||||
const event = await awaitAttemptEvent();
|
||||
Assert.equal(event.extra.method, "settings", "Event method is settings");
|
||||
Assert.equal(
|
||||
event.extra.success,
|
||||
"false",
|
||||
"Event success is false when every method failed"
|
||||
);
|
||||
Assert.equal(
|
||||
event.extra.result_is_default,
|
||||
"false",
|
||||
"Event result_is_default is false when no method set the default"
|
||||
);
|
||||
} finally {
|
||||
sandbox.restore();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_overwrites_existing_pending_redirect() {
|
||||
resetState();
|
||||
const sandbox = sinon.createSandbox();
|
||||
sandbox.stub(ShellService, "_isWindows11").returns(true);
|
||||
|
||||
try {
|
||||
// A differently-typed leftover value on the pref must not trip the
|
||||
// setStringPref in WindowsSetDefaultRedirect.arm (it clears first).
|
||||
Services.prefs.setBoolPref(SET_DEFAULT_REDIRECT_PREF, true);
|
||||
await ShellService.setAsDefaultProtocolHandler("https", undefined, false);
|
||||
Assert.deepEqual(
|
||||
readPrefObject(),
|
||||
{
|
||||
openWithArg: DEFAULT_PROTOCOL_URLS.https,
|
||||
overrideUri: null,
|
||||
type: WindowsSetDefaultRedirect.TYPE.PROTOCOL,
|
||||
},
|
||||
"Stale value replaced with the structured redirect on the next call"
|
||||
);
|
||||
} finally {
|
||||
sandbox.restore();
|
||||
}
|
||||
});
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
/**
|
||||
* Check whether the preview image for setDesktopBackground is rendered
|
||||
* correctly, without stretching
|
||||
* correctly, without stretching, for both <img> and <canvas> targets.
|
||||
*/
|
||||
|
||||
add_setup(async function () {
|
||||
@@ -12,82 +12,85 @@ add_setup(async function () {
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function () {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: getRootDirectory(gTestPath) + "large.png",
|
||||
},
|
||||
async () => {
|
||||
const dialogLoad = BrowserTestUtils.domWindowOpened(null, async win => {
|
||||
await BrowserTestUtils.waitForEvent(win, "load");
|
||||
Assert.equal(
|
||||
win.document.documentElement.getAttribute("windowtype"),
|
||||
"Shell:SetDesktopBackground",
|
||||
"Opened correct window"
|
||||
);
|
||||
return true;
|
||||
});
|
||||
async function testPreview(url, targetSelector) {
|
||||
await BrowserTestUtils.withNewTab({ gBrowser, url }, async browser => {
|
||||
const dialogLoad = BrowserTestUtils.domWindowOpened(null, async win => {
|
||||
await BrowserTestUtils.waitForEvent(win, "load");
|
||||
Assert.equal(
|
||||
win.document.documentElement.getAttribute("windowtype"),
|
||||
"Shell:SetDesktopBackground",
|
||||
"Opened correct window"
|
||||
);
|
||||
return true;
|
||||
});
|
||||
|
||||
const image = content.document.images[0];
|
||||
EventUtils.synthesizeMouseAtCenter(image, { type: "contextmenu" });
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter(
|
||||
targetSelector,
|
||||
{ type: "contextmenu" },
|
||||
browser
|
||||
);
|
||||
|
||||
const menu = document.getElementById("contentAreaContextMenu");
|
||||
await BrowserTestUtils.waitForPopupEvent(menu, "shown");
|
||||
const menuClosed = BrowserTestUtils.waitForPopupEvent(menu, "hidden");
|
||||
const menu = document.getElementById("contentAreaContextMenu");
|
||||
await BrowserTestUtils.waitForPopupEvent(menu, "shown");
|
||||
const menuClosed = BrowserTestUtils.waitForPopupEvent(menu, "hidden");
|
||||
|
||||
const menuItem = document.getElementById("context-setDesktopBackground");
|
||||
try {
|
||||
menu.activateItem(menuItem);
|
||||
} catch (ex) {
|
||||
ok(
|
||||
menuItem.hidden,
|
||||
"should only fail to activate when menu item is hidden"
|
||||
);
|
||||
ok(
|
||||
!ShellService.canSetDesktopBackground,
|
||||
"Should only hide when not able to set the desktop background"
|
||||
);
|
||||
is(
|
||||
AppConstants.platform,
|
||||
"linux",
|
||||
"Should always be able to set desktop background on non-linux platforms"
|
||||
);
|
||||
todo(false, "Skipping test on this configuration");
|
||||
|
||||
menu.hidePopup();
|
||||
await menuClosed;
|
||||
return;
|
||||
}
|
||||
const menuItem = document.getElementById("context-setDesktopBackground");
|
||||
try {
|
||||
menu.activateItem(menuItem);
|
||||
} catch (ex) {
|
||||
ok(
|
||||
menuItem.hidden,
|
||||
"should only fail to activate when menu item is hidden"
|
||||
);
|
||||
ok(
|
||||
!ShellService.canSetDesktopBackground,
|
||||
"Should only hide when not able to set the desktop background"
|
||||
);
|
||||
is(
|
||||
AppConstants.platform,
|
||||
"linux",
|
||||
"Should always be able to set desktop background on non-linux platforms"
|
||||
);
|
||||
todo(false, "Skipping test on this configuration");
|
||||
|
||||
menu.hidePopup();
|
||||
await menuClosed;
|
||||
return;
|
||||
}
|
||||
|
||||
const win = await dialogLoad;
|
||||
await menuClosed;
|
||||
|
||||
/* setDesktopBackground.js does a setTimeout to wait for correct
|
||||
const win = await dialogLoad;
|
||||
|
||||
/* setDesktopBackground.js does a setTimeout to wait for correct
|
||||
dimensions. If we don't wait here we could read the preview dimensions
|
||||
before they're changed to match the screen */
|
||||
await TestUtils.waitForTick();
|
||||
await TestUtils.waitForTick();
|
||||
|
||||
const canvas = win.document.getElementById("screen");
|
||||
const screenRatio = screen.width / screen.height;
|
||||
const previewRatio = canvas.clientWidth / canvas.clientHeight;
|
||||
const canvas = win.document.getElementById("screen");
|
||||
const screenRatio = screen.width / screen.height;
|
||||
const previewRatio = canvas.clientWidth / canvas.clientHeight;
|
||||
|
||||
info(`Screen dimensions are ${screen.width}x${screen.height}`);
|
||||
info(`Screen's raw ratio is ${screenRatio}`);
|
||||
info(
|
||||
`Preview dimensions are ${canvas.clientWidth}x${canvas.clientHeight}`
|
||||
);
|
||||
info(`Preview's raw ratio is ${previewRatio}`);
|
||||
info(`Screen dimensions are ${screen.width}x${screen.height}`);
|
||||
info(`Screen's raw ratio is ${screenRatio}`);
|
||||
info(`Preview dimensions are ${canvas.clientWidth}x${canvas.clientHeight}`);
|
||||
info(`Preview's raw ratio is ${previewRatio}`);
|
||||
|
||||
Assert.ok(
|
||||
previewRatio < screenRatio + 0.01 && previewRatio > screenRatio - 0.01,
|
||||
"Preview's aspect ratio is within ±.01 of screen's"
|
||||
);
|
||||
Assert.ok(
|
||||
previewRatio < screenRatio + 0.01 && previewRatio > screenRatio - 0.01,
|
||||
"Preview's aspect ratio is within ±.01 of screen's"
|
||||
);
|
||||
|
||||
win.close();
|
||||
win.close();
|
||||
|
||||
await menuClosed;
|
||||
}
|
||||
);
|
||||
await menuClosed;
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_image() {
|
||||
await testPreview(getRootDirectory(gTestPath) + "large.png", "img");
|
||||
});
|
||||
|
||||
add_task(async function test_canvas() {
|
||||
await testPreview(getRootDirectory(gTestPath) + "canvas.html", "canvas");
|
||||
});
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const { CommandLineHandler } = ChromeUtils.importESModule(
|
||||
"moz-src:///browser/components/shell/WindowsSetDefaultAppCmdHandler.sys.mjs"
|
||||
);
|
||||
|
||||
ChromeUtils.defineESModuleGetters(this, {
|
||||
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
|
||||
SET_DEFAULT_REDIRECT_PREF:
|
||||
"moz-src:///browser/components/shell/WindowsSetDefaultRedirect.sys.mjs",
|
||||
ShellService: "moz-src:///browser/components/shell/ShellService.sys.mjs",
|
||||
WindowsSetDefaultRedirect:
|
||||
"moz-src:///browser/components/shell/WindowsSetDefaultRedirect.sys.mjs",
|
||||
sinon: "resource://testing-common/Sinon.sys.mjs",
|
||||
});
|
||||
|
||||
Assert.equal(AppConstants.platform, "win", "Platform is Windows");
|
||||
|
||||
const confusedFoxPath = ShellService.getBundledPdfFile("confused_fox.pdf").path;
|
||||
const blankURISpec = Services.io.newFileURI(
|
||||
ShellService.getBundledPdfFile("blank.pdf")
|
||||
).spec;
|
||||
|
||||
const workingDir = Services.dirsvc.get("GreD", Ci.nsIFile);
|
||||
|
||||
// Build a real nsICommandLine from an args array, so we exercise the same
|
||||
// flag-parsing the OS-initiated launch goes through
|
||||
function makeCmdLine(args, state = Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
|
||||
return Cu.createCommandLine(args, workingDir, state);
|
||||
}
|
||||
|
||||
// Arm the one-shot redirect via the real ShellService helper, the way
|
||||
// setAsDefault{*}Handler does right before launching the OS picker. These
|
||||
// tests all use file openWithArgs, so default the type.
|
||||
function armRedirect(
|
||||
openWithArg,
|
||||
overrideUri,
|
||||
type = WindowsSetDefaultRedirect.TYPE.FILE
|
||||
) {
|
||||
WindowsSetDefaultRedirect.arm(openWithArg, overrideUri, type);
|
||||
}
|
||||
|
||||
let fakeWin;
|
||||
let getTopWindowStub;
|
||||
let openWindowStub;
|
||||
|
||||
add_setup(function () {
|
||||
fakeWin = { openTrustedLinkIn: sinon.stub() };
|
||||
// Stubbed rather than spied: getTopWindow must return our fake window (and
|
||||
// null on demand), and openWindow must not actually open a window in the
|
||||
// test harness. We only inspect the calls.
|
||||
getTopWindowStub = sinon.stub(BrowserWindowTracker, "getTopWindow");
|
||||
openWindowStub = sinon.stub(BrowserWindowTracker, "openWindow");
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
sinon.restore();
|
||||
Services.prefs.clearUserPref(SET_DEFAULT_REDIRECT_PREF);
|
||||
});
|
||||
|
||||
function resetState() {
|
||||
fakeWin.openTrustedLinkIn.resetHistory();
|
||||
getTopWindowStub.reset();
|
||||
getTopWindowStub.returns(fakeWin);
|
||||
openWindowStub.reset();
|
||||
Services.prefs.clearUserPref(SET_DEFAULT_REDIRECT_PREF);
|
||||
}
|
||||
|
||||
add_task(async function test_no_osint_returns_early() {
|
||||
resetState();
|
||||
armRedirect(confusedFoxPath, blankURISpec);
|
||||
const cmdLine = makeCmdLine(["-url", confusedFoxPath]);
|
||||
new CommandLineHandler().handle(cmdLine);
|
||||
|
||||
Assert.equal(
|
||||
cmdLine.preventDefault,
|
||||
false,
|
||||
"preventDefault left untouched without -osint"
|
||||
);
|
||||
Assert.greaterOrEqual(
|
||||
cmdLine.findFlag("url", false),
|
||||
0,
|
||||
"-url remains for the next handler"
|
||||
);
|
||||
Assert.ok(
|
||||
fakeWin.openTrustedLinkIn.notCalled,
|
||||
"No tab opened without -osint"
|
||||
);
|
||||
Assert.ok(openWindowStub.notCalled, "No window opened without -osint");
|
||||
});
|
||||
|
||||
add_task(async function test_osint_without_url_returns_early() {
|
||||
resetState();
|
||||
armRedirect(confusedFoxPath, blankURISpec);
|
||||
const cmdLine = makeCmdLine(["-osint"]);
|
||||
new CommandLineHandler().handle(cmdLine);
|
||||
|
||||
Assert.equal(
|
||||
cmdLine.preventDefault,
|
||||
false,
|
||||
"preventDefault left untouched without -url"
|
||||
);
|
||||
Assert.ok(fakeWin.openTrustedLinkIn.notCalled, "No tab opened without -url");
|
||||
Assert.ok(openWindowStub.notCalled, "No window opened without -url");
|
||||
});
|
||||
|
||||
add_task(async function test_no_pending_redirect_leaves_arg() {
|
||||
// No armed redirect: even a -url that looks like our stub PDF is a real,
|
||||
// user-initiated open (e.g. they double-clicked the file), so we must leave
|
||||
// it for BrowserContentHandler.
|
||||
resetState();
|
||||
|
||||
const cmdLine = makeCmdLine(["-osint", "-url", confusedFoxPath]);
|
||||
new CommandLineHandler().handle(cmdLine);
|
||||
|
||||
Assert.equal(
|
||||
cmdLine.preventDefault,
|
||||
false,
|
||||
"preventDefault not set when no redirect is pending"
|
||||
);
|
||||
Assert.greaterOrEqual(
|
||||
cmdLine.findFlag("url", false),
|
||||
0,
|
||||
"-url preserved for BrowserContentHandler"
|
||||
);
|
||||
Assert.ok(
|
||||
fakeWin.openTrustedLinkIn.notCalled,
|
||||
"No tab opened when no redirect is pending"
|
||||
);
|
||||
Assert.ok(
|
||||
openWindowStub.notCalled,
|
||||
"No window opened when no redirect is pending"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_unrelated_url_arg_is_ignored() {
|
||||
resetState();
|
||||
armRedirect(confusedFoxPath, blankURISpec);
|
||||
|
||||
const cmdLine = makeCmdLine([
|
||||
"-osint",
|
||||
"-url",
|
||||
"https://example.com/some-page",
|
||||
]);
|
||||
new CommandLineHandler().handle(cmdLine);
|
||||
|
||||
Assert.equal(
|
||||
cmdLine.preventDefault,
|
||||
false,
|
||||
"preventDefault not set for a -url that isn't the pending openWithArg"
|
||||
);
|
||||
Assert.greaterOrEqual(
|
||||
cmdLine.findFlag("url", false),
|
||||
0,
|
||||
"-url preserved for subsequent handler"
|
||||
);
|
||||
Assert.ok(
|
||||
fakeWin.openTrustedLinkIn.notCalled,
|
||||
"No tab opened for an unrelated -url"
|
||||
);
|
||||
Assert.ok(
|
||||
Services.prefs.prefHasUserValue(SET_DEFAULT_REDIRECT_PREF),
|
||||
"Pending redirect untouched when the openWithArg doesn't match"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_suppress_only_when_target_null() {
|
||||
resetState();
|
||||
armRedirect(confusedFoxPath, null);
|
||||
|
||||
const cmdLine = makeCmdLine(["-osint", "-url", confusedFoxPath]);
|
||||
new CommandLineHandler().handle(cmdLine);
|
||||
|
||||
Assert.equal(
|
||||
cmdLine.preventDefault,
|
||||
true,
|
||||
"openWithArg suppressed so BrowserContentHandler skips it"
|
||||
);
|
||||
Assert.equal(
|
||||
cmdLine.findFlag("url", false),
|
||||
-1,
|
||||
"-url consumed even with no redirect target"
|
||||
);
|
||||
Assert.ok(
|
||||
fakeWin.openTrustedLinkIn.notCalled,
|
||||
"No redirect when target is null"
|
||||
);
|
||||
Assert.ok(openWindowStub.notCalled, "No fallback window when target is null");
|
||||
Assert.ok(
|
||||
!Services.prefs.prefHasUserValue(SET_DEFAULT_REDIRECT_PREF),
|
||||
"Redirect intent consumed"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_redirects_to_top_window() {
|
||||
resetState();
|
||||
armRedirect(confusedFoxPath, blankURISpec);
|
||||
|
||||
const cmdLine = makeCmdLine(["-osint", "-url", confusedFoxPath]);
|
||||
new CommandLineHandler().handle(cmdLine);
|
||||
|
||||
Assert.equal(
|
||||
cmdLine.preventDefault,
|
||||
true,
|
||||
"Default open suppressed for the pending openWithArg"
|
||||
);
|
||||
Assert.equal(cmdLine.findFlag("url", false), -1, "-url consumed");
|
||||
Assert.ok(
|
||||
fakeWin.openTrustedLinkIn.calledOnce,
|
||||
"Redirected into the top window"
|
||||
);
|
||||
Assert.deepEqual(fakeWin.openTrustedLinkIn.firstCall.args, [
|
||||
blankURISpec,
|
||||
"tab",
|
||||
]);
|
||||
Assert.ok(openWindowStub.notCalled, "No new window when one exists");
|
||||
Assert.ok(
|
||||
!Services.prefs.prefHasUserValue(SET_DEFAULT_REDIRECT_PREF),
|
||||
"Redirect intent is one-shot and cleared after use"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_opens_new_window_when_no_top() {
|
||||
resetState();
|
||||
getTopWindowStub.returns(null);
|
||||
armRedirect(confusedFoxPath, blankURISpec);
|
||||
|
||||
const cmdLine = makeCmdLine(["-osint", "-url", confusedFoxPath]);
|
||||
new CommandLineHandler().handle(cmdLine);
|
||||
|
||||
Assert.equal(
|
||||
cmdLine.preventDefault,
|
||||
true,
|
||||
"Default open suppressed even when no top window exists"
|
||||
);
|
||||
Assert.ok(
|
||||
fakeWin.openTrustedLinkIn.notCalled,
|
||||
"Top-window path skipped when getTopWindow returns null"
|
||||
);
|
||||
Assert.ok(openWindowStub.calledOnce, "Falls back to openWindow");
|
||||
|
||||
const opts = openWindowStub.firstCall.args[0];
|
||||
Assert.ok(
|
||||
opts && opts.args,
|
||||
"openWindow called with a {args} options object"
|
||||
);
|
||||
Assert.ok(
|
||||
opts.args instanceof Ci.nsISupportsString,
|
||||
"args is an nsISupportsString"
|
||||
);
|
||||
Assert.equal(
|
||||
opts.args.data,
|
||||
blankURISpec,
|
||||
"nsISupportsString carries the redirect URI"
|
||||
);
|
||||
Assert.ok(
|
||||
!Services.prefs.prefHasUserValue(SET_DEFAULT_REDIRECT_PREF),
|
||||
"Redirect intent is one-shot and cleared after use"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_intent_is_one_shot() {
|
||||
resetState();
|
||||
armRedirect(confusedFoxPath, blankURISpec);
|
||||
|
||||
new CommandLineHandler().handle(
|
||||
makeCmdLine(["-osint", "-url", confusedFoxPath])
|
||||
);
|
||||
Assert.ok(
|
||||
fakeWin.openTrustedLinkIn.calledOnce,
|
||||
"First call honors the pending redirect"
|
||||
);
|
||||
|
||||
fakeWin.openTrustedLinkIn.resetHistory();
|
||||
const second = makeCmdLine(["-osint", "-url", confusedFoxPath]);
|
||||
new CommandLineHandler().handle(second);
|
||||
|
||||
Assert.equal(
|
||||
second.preventDefault,
|
||||
false,
|
||||
"Second call leaves the openWithArg alone because the intent was consumed"
|
||||
);
|
||||
Assert.greaterOrEqual(
|
||||
second.findFlag("url", false),
|
||||
0,
|
||||
"-url preserved on the second call (no pending redirect)"
|
||||
);
|
||||
Assert.ok(
|
||||
fakeWin.openTrustedLinkIn.notCalled,
|
||||
"Second call does not redirect"
|
||||
);
|
||||
});
|
||||
11
src/zen/tests/mochitests/shell/canvas.html
Normal file
11
src/zen/tests/mochitests/shell/canvas.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="c" width="200" height="100"></canvas>
|
||||
<script>
|
||||
const ctx = document.getElementById("c").getContext("2d");
|
||||
ctx.fillStyle = "red";
|
||||
ctx.fillRect(0, 0, 200, 100);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
657
src/zen/tests/mochitests/shell/unit/test_dynamicLauncher.js
Normal file
657
src/zen/tests/mochitests/shell/unit/test_dynamicLauncher.js
Normal file
@@ -0,0 +1,657 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.defineESModuleGetters(this, {
|
||||
ShellService: "moz-src:///browser/components/shell/ShellService.sys.mjs",
|
||||
Subprocess: "resource://gre/modules/Subprocess.sys.mjs",
|
||||
TestUtils: "resource://testing-common/TestUtils.sys.mjs",
|
||||
XPCOMUtils: "resource://gre/modules/XPCOMUtils.sys.mjs",
|
||||
});
|
||||
|
||||
// This tests the browser's interaction with the Dynamic Launcher portal, which
|
||||
// is documented at
|
||||
//
|
||||
// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.DynamicLauncher
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
"IniParserFactory",
|
||||
"@mozilla.org/xpcom/ini-parser-factory;1",
|
||||
Ci.nsIINIParserFactory
|
||||
);
|
||||
|
||||
function desktopEntryFromObject(object) {
|
||||
let ini = IniParserFactory.createINIParser().QueryInterface(
|
||||
Ci.nsIINIParserWriter
|
||||
);
|
||||
|
||||
for (const property of Object.getOwnPropertyNames(object)) {
|
||||
ini.setString("Desktop Entry", property, "" + object[property]);
|
||||
}
|
||||
|
||||
return ini;
|
||||
}
|
||||
|
||||
function objectFromDesktopEntry(aINI) {
|
||||
let ini = IniParserFactory.createINIParser();
|
||||
ini.initFromString(aINI);
|
||||
|
||||
let sections = ini.getSections();
|
||||
ok(sections.hasMore(), "There is at least one section");
|
||||
Assert.equal(sections.getNext(), "Desktop Entry", "First key is correct");
|
||||
Assert.equal(sections.hasMore(), false, "...and there aren't others");
|
||||
|
||||
let keys = ini.getKeys("Desktop Entry");
|
||||
let result = Object.create(null);
|
||||
while (keys.hasMore()) {
|
||||
let key = keys.getNext();
|
||||
result[key] = ini.getString("Desktop Entry", key);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const portalBusName = "org.freedesktop.portal.Desktop";
|
||||
const portalObjectPath = "/org/freedesktop/portal/desktop";
|
||||
const portalInterfaceName = "org.freedesktop.portal.DynamicLauncher";
|
||||
const dbusMockInterface = "org.freedesktop.DBus.Mock";
|
||||
const addObjectMethod = `${dbusMockInterface}.AddObject`;
|
||||
const addMethodMethod = `${dbusMockInterface}.AddMethod`;
|
||||
const emitSignalDetailedMethod = `${dbusMockInterface}.EmitSignalDetailed`;
|
||||
const getCallsMethod = `${dbusMockInterface}.GetCalls`;
|
||||
const clearCallsMethod = `${dbusMockInterface}.ClearCalls`;
|
||||
const resetMethod = `${dbusMockInterface}.Reset`;
|
||||
const mockRequestObjectPath = "/org/freedesktop/portal/desktop/request";
|
||||
|
||||
var DBUS_SESSION_BUS_ADDRESS = "";
|
||||
var DBUS_SESSION_BUS_PID = 0; // eslint-disable-line no-unused-vars
|
||||
var DBUS_MOCK = null;
|
||||
var gdbusCmd = null;
|
||||
|
||||
async function callDbusMethod(objectPath, methodName, args) {
|
||||
let mockProcess = await Subprocess.call({
|
||||
command: gdbusCmd,
|
||||
arguments: [
|
||||
"call",
|
||||
"--session",
|
||||
"-d",
|
||||
portalBusName,
|
||||
"-o",
|
||||
objectPath,
|
||||
"-m",
|
||||
methodName,
|
||||
...args,
|
||||
],
|
||||
});
|
||||
return mockProcess.wait();
|
||||
}
|
||||
|
||||
// Launches a D-Bus daemon that the tests will run under, and sets it (via
|
||||
// environment variables) to be used as the D-Bus connection. Additionally, sets
|
||||
// up dbusmock to allow the test to create fake D-Bus objects; this avoids
|
||||
// needing to display UI to the user, or depending on the details of the
|
||||
// specific portal they have installed.
|
||||
add_setup(async function setup() {
|
||||
// Start and use a separate message bus for the tests, to not interfere with
|
||||
// the current's session message bus.
|
||||
let dbus = await Subprocess.call({
|
||||
command: await Subprocess.pathSearch("dbus-launch"),
|
||||
});
|
||||
await dbus.wait();
|
||||
let stdout = await dbus.stdout.readString();
|
||||
let lines = stdout.split("\n");
|
||||
for (let i in lines) {
|
||||
let tokens = lines[i].split("=");
|
||||
switch (tokens.shift()) {
|
||||
case "DBUS_SESSION_BUS_ADDRESS":
|
||||
DBUS_SESSION_BUS_ADDRESS = tokens.join("=");
|
||||
break;
|
||||
case "DBUS_SESSION_BUS_PID":
|
||||
DBUS_SESSION_BUS_PID = tokens.join();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
gdbusCmd = await Subprocess.pathSearch("gdbus");
|
||||
|
||||
Services.env.set("DBUS_SESSION_BUS_ADDRESS", DBUS_SESSION_BUS_ADDRESS);
|
||||
Services.env.set("GTK_USE_PORTAL", "1");
|
||||
|
||||
// dbusmock is used to mock the native messaging portal's D-Bus API.
|
||||
DBUS_MOCK = await Subprocess.call({
|
||||
command: await Subprocess.pathSearch("python3"),
|
||||
arguments: [
|
||||
"-m",
|
||||
"dbusmock",
|
||||
portalBusName,
|
||||
portalObjectPath,
|
||||
portalInterfaceName,
|
||||
// vvvvvvvvvvvvv why is this needed?!
|
||||
"-l",
|
||||
"/dev/null",
|
||||
],
|
||||
});
|
||||
|
||||
// Wait until dbusmock is ready
|
||||
await TestUtils.waitForCondition(async () => {
|
||||
let res = await callDbusMethod(
|
||||
portalObjectPath,
|
||||
"org.freedesktop.DBus.Mock.GetCalls",
|
||||
[]
|
||||
);
|
||||
return res.exitCode == 0;
|
||||
}, "waiting for dbusmock");
|
||||
|
||||
registerCleanupFunction(async function () {
|
||||
await DBUS_MOCK.kill();
|
||||
// XXX: While this works locally, it consistently fails when tests are run
|
||||
// in CI, with "xpcshell return code: -15". This needs to be investigated
|
||||
// further. This leaves a stray dbus-daemon process behind,
|
||||
// which isn't ideal, but is harmless.
|
||||
/*await lazy.Subprocess.call({
|
||||
command: await lazy.Subprocess.pathSearch("kill"),
|
||||
arguments: ["-SIGQUIT", DBUS_SESSION_BUS_PID],
|
||||
});*/
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Determines who called the corresponding D-Bus method. dbusmock doesn't
|
||||
* provide this information, but we need it to create a portal Request path, so
|
||||
* this uses a one-off dbus-monitor instead.
|
||||
*
|
||||
* This function has two phases, determined by the promises in its return value:
|
||||
*
|
||||
* - monitorReady will resolve when dbus-monitor appears to be awake. At this
|
||||
* point, the method call should happen.
|
||||
*
|
||||
* - senderPromise will resolve when the call is detected. This will resolve
|
||||
* to the sender of the method call.
|
||||
*
|
||||
* @param {string} method
|
||||
* The method call to look for. (Currently you can't key based on the object
|
||||
* path.)
|
||||
* @returns {{monitorReady:Promise<undefined>,senderPromise:Promise<string>}}
|
||||
* A set of promises corresponding to the current 'phase' of the check.
|
||||
*/
|
||||
function sniffDbusMethodCaller(method) {
|
||||
let readyResolvers = Promise.withResolvers();
|
||||
let doneResolvers = Promise.withResolvers();
|
||||
|
||||
// Use .then here so this can be a synchronous function, so you don't
|
||||
// additionally have to worry about the promise returned.
|
||||
Subprocess.pathSearch("dbus-monitor")
|
||||
.then(command =>
|
||||
Subprocess.call({
|
||||
command,
|
||||
arguments: ["--session"],
|
||||
})
|
||||
)
|
||||
.then(async process => {
|
||||
while (true) {
|
||||
for (const line of (await process.stdout.readString()).split("\n")) {
|
||||
readyResolvers.resolve();
|
||||
|
||||
// Extract the sender name from the dbus-monitor process's output.
|
||||
if (
|
||||
line.startsWith("method call") &&
|
||||
line.includes(`member=${method}`)
|
||||
) {
|
||||
let match = line.match(/sender=(\S*)/);
|
||||
ok(match, "dbus-monitor output informs us of the sender");
|
||||
doneResolvers.resolve(match[1]);
|
||||
await process.kill();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(err => {
|
||||
readyResolvers.reject(err);
|
||||
doneResolvers.reject(err);
|
||||
});
|
||||
|
||||
return {
|
||||
monitorReady: readyResolvers.promise,
|
||||
senderPromise: doneResolvers.promise,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a D-Bus method call with the given object path, method, and
|
||||
* position in time (in case multiple calls happened).
|
||||
*
|
||||
* @param {string} objectPath
|
||||
* The object path the call should be for. This must be a dbusmock-based mock
|
||||
* object.
|
||||
* @param {string} method
|
||||
* The method to get information for.
|
||||
* @param {number} offset
|
||||
* Indicates that information should be returned for the nth (zero-based)
|
||||
* call.
|
||||
* @returns {{offset:number,params:string}}
|
||||
* The offset after this call as a number (zero if it wasn't called), and the
|
||||
* string representation of the arguments; e.g. "'abc', 'def'".
|
||||
*/
|
||||
async function expectDbusMockCall(objectPath, method, offset) {
|
||||
let getCalls = await Subprocess.call({
|
||||
command: gdbusCmd,
|
||||
arguments: [
|
||||
"call",
|
||||
"--session",
|
||||
"-d",
|
||||
portalBusName,
|
||||
"-o",
|
||||
objectPath,
|
||||
"-m",
|
||||
getCallsMethod,
|
||||
],
|
||||
});
|
||||
let out = "";
|
||||
while (!out.endsWith("\n")) {
|
||||
out += await getCalls.stdout.readString();
|
||||
}
|
||||
// The other regexes are fragile, so remove the byte arrays that break them.
|
||||
out = out.replaceAll(/<\('bytes', <\[byte 0x[0-9a-fx, ]+\]>\)>/g, "<bytes>");
|
||||
out = out.match(/\((@a\(tsav\) )?\[(.*)\],\)/)[2];
|
||||
let calls = out.matchAll(/\(.*?\),?/g);
|
||||
let methodCalled = false;
|
||||
let params = {};
|
||||
let i = 0;
|
||||
for (let call of calls) {
|
||||
if (i++ < offset) {
|
||||
continue;
|
||||
}
|
||||
let matches = call[0].match(
|
||||
/\((uint64 )?(?<timestamp>\d+), '(?<method>\w+)', (@av )?\[(?<params>.*)\]\),?/
|
||||
);
|
||||
ok(parseFloat(matches.groups.timestamp), "timestamp is valid");
|
||||
if (matches.groups.method == method) {
|
||||
methodCalled = true;
|
||||
params = matches.groups.params;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (method) {
|
||||
ok(methodCalled, `The ${method} mock was called`);
|
||||
} else {
|
||||
equal(i, 0, "No method mock was called");
|
||||
}
|
||||
|
||||
await getCalls.wait();
|
||||
return { offset: i, params };
|
||||
}
|
||||
|
||||
add_task(async function test_successful_creation() {
|
||||
await callDbusMethod(portalObjectPath, resetMethod, []);
|
||||
await callDbusMethod(portalObjectPath, clearCallsMethod, []);
|
||||
|
||||
await callDbusMethod(portalObjectPath, addMethodMethod, [
|
||||
portalInterfaceName,
|
||||
"PrepareInstall",
|
||||
"ssva{sv}",
|
||||
"o",
|
||||
`ret = "/should/not/be/used"`,
|
||||
]);
|
||||
|
||||
let object = {
|
||||
Version: "1.5",
|
||||
Type: "Application",
|
||||
Name: "Example Launcher Name",
|
||||
Icon:
|
||||
Services.dirsvc.get("CurWorkD", Ci.nsIFile).path +
|
||||
"/favicon-normal16.png",
|
||||
Exec: "browser command",
|
||||
};
|
||||
let ini = desktopEntryFromObject(object);
|
||||
|
||||
let { monitorReady, senderPromise } = sniffDbusMethodCaller("PrepareInstall");
|
||||
await monitorReady;
|
||||
|
||||
let promise = ShellService.requestInstallDynamicLauncher("a.b.c", ini, null);
|
||||
|
||||
let senderName = await senderPromise;
|
||||
let result = await expectDbusMockCall(portalObjectPath, "PrepareInstall", 0);
|
||||
let [parentWindow, name, icon] = result.params.split(", ");
|
||||
Assert.equal(parentWindow, "<''>", "No parent window was provided");
|
||||
Assert.equal(name, "<'Example Launcher Name'>", "Launcher name was provided");
|
||||
Assert.equal(icon, "<bytes>", "Icon was some sequence of bytes");
|
||||
|
||||
let match = result.params.match(/'handle_token': <'(?<token>.*)'>/);
|
||||
ok(match, "Start arguments contain a handle token");
|
||||
let handleToken = match.groups.token;
|
||||
|
||||
await callDbusMethod(portalObjectPath, addMethodMethod, [
|
||||
portalInterfaceName,
|
||||
"Install",
|
||||
"sssa{sv}",
|
||||
"",
|
||||
"",
|
||||
]);
|
||||
|
||||
// Mock the Request object that is expected to be created in response to
|
||||
// calling the Start method on the native messaging portal, wait for it to be
|
||||
// available, and emit its Response signal.
|
||||
let requestPath = `${mockRequestObjectPath}/${senderName
|
||||
.slice(1)
|
||||
.replace(".", "_")}/${handleToken}`;
|
||||
await callDbusMethod(portalObjectPath, addObjectMethod, [
|
||||
requestPath,
|
||||
"org.freedesktop.portal.Request",
|
||||
"@a{sv} {}",
|
||||
"@a(ssss) []",
|
||||
]);
|
||||
await callDbusMethod(requestPath, emitSignalDetailedMethod, [
|
||||
"org.freedesktop.portal.Request",
|
||||
"Response",
|
||||
"ua{sv}",
|
||||
`[<uint32 0>, <@a{sv} {'token': <'qwerty'>}>]`,
|
||||
`{'destination': <'${senderName}'>}`,
|
||||
]);
|
||||
|
||||
// Verify that the Install method was called as expected after the Start
|
||||
// request completed.
|
||||
result = await expectDbusMockCall(portalObjectPath, "Install", result.offset);
|
||||
let [token, desktopFileId, desktopEntry] = result.params.split(", ");
|
||||
Assert.equal(token, "<'qwerty'>", "Correct token is provided");
|
||||
Assert.equal(
|
||||
desktopFileId,
|
||||
"<'a.b.c.desktop'>",
|
||||
"Correct desktop file ID is provided"
|
||||
);
|
||||
|
||||
desktopEntry = desktopEntry
|
||||
.replace(/^<'/, "")
|
||||
.replace(/'>$/, "")
|
||||
.replaceAll(/\\n/g, "\n");
|
||||
let entry = objectFromDesktopEntry(desktopEntry);
|
||||
Assert.deepEqual(entry, object, "Desktop entry contains expected values");
|
||||
|
||||
// Check for exceptions etc.
|
||||
await promise;
|
||||
});
|
||||
|
||||
add_task(async function test_prepareinstall_error() {
|
||||
await callDbusMethod(portalObjectPath, resetMethod, []);
|
||||
await callDbusMethod(portalObjectPath, clearCallsMethod, []);
|
||||
|
||||
await callDbusMethod(portalObjectPath, addMethodMethod, [
|
||||
portalInterfaceName,
|
||||
"PrepareInstall",
|
||||
"ssva{sv}",
|
||||
"o",
|
||||
`raise dbus.exceptions.DBusException("prepareinstall failed", name="org.mozilla.Error.Mocked")`,
|
||||
]);
|
||||
|
||||
let object = {
|
||||
Version: "1.5",
|
||||
Type: "Application",
|
||||
Name: "Example Launcher Name",
|
||||
Icon:
|
||||
Services.dirsvc.get("CurWorkD", Ci.nsIFile).path +
|
||||
"/favicon-normal16.png",
|
||||
Exec: "browser command",
|
||||
};
|
||||
let ini = desktopEntryFromObject(object);
|
||||
|
||||
await Assert.rejects(
|
||||
(async () =>
|
||||
ShellService.requestInstallDynamicLauncher("a.b.c", ini, null))(),
|
||||
/.*prepareinstall failed.*/,
|
||||
"Failure was propagated from D-Bus"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_install_error() {
|
||||
await callDbusMethod(portalObjectPath, resetMethod, []);
|
||||
await callDbusMethod(portalObjectPath, clearCallsMethod, []);
|
||||
|
||||
await callDbusMethod(portalObjectPath, addMethodMethod, [
|
||||
portalInterfaceName,
|
||||
"PrepareInstall",
|
||||
"ssva{sv}",
|
||||
"o",
|
||||
`ret = "/should/not/be/used"`,
|
||||
]);
|
||||
|
||||
let object = {
|
||||
Version: "1.5",
|
||||
Type: "Application",
|
||||
Name: "Example Launcher Name",
|
||||
Icon:
|
||||
Services.dirsvc.get("CurWorkD", Ci.nsIFile).path +
|
||||
"/favicon-normal16.png",
|
||||
Exec: "browser command",
|
||||
};
|
||||
let ini = desktopEntryFromObject(object);
|
||||
|
||||
let { monitorReady, senderPromise } = sniffDbusMethodCaller("PrepareInstall");
|
||||
await monitorReady;
|
||||
|
||||
let promise = ShellService.requestInstallDynamicLauncher("a.b.c", ini, null);
|
||||
|
||||
let senderName = await senderPromise;
|
||||
let result = await expectDbusMockCall(portalObjectPath, "PrepareInstall", 0);
|
||||
|
||||
let match = result.params.match(/'handle_token': <'(?<token>.*)'>/);
|
||||
ok(match, "Start arguments contain a handle token");
|
||||
let handleToken = match.groups.token;
|
||||
|
||||
await callDbusMethod(portalObjectPath, addMethodMethod, [
|
||||
portalInterfaceName,
|
||||
"Install",
|
||||
"sssa{sv}",
|
||||
"",
|
||||
`raise dbus.exceptions.DBusException("plain install failed", name="org.mozilla.Error.Mocked")`,
|
||||
]);
|
||||
|
||||
// Mock the Request object that is expected to be created in response to
|
||||
// calling the Start method on the native messaging portal, wait for it to be
|
||||
// available, and emit its Response signal.
|
||||
let requestPath = `${mockRequestObjectPath}/${senderName
|
||||
.slice(1)
|
||||
.replace(".", "_")}/${handleToken}`;
|
||||
await callDbusMethod(portalObjectPath, addObjectMethod, [
|
||||
requestPath,
|
||||
"org.freedesktop.portal.Request",
|
||||
"@a{sv} {}",
|
||||
"@a(ssss) []",
|
||||
]);
|
||||
await callDbusMethod(requestPath, emitSignalDetailedMethod, [
|
||||
"org.freedesktop.portal.Request",
|
||||
"Response",
|
||||
"ua{sv}",
|
||||
`[<uint32 0>, <@a{sv} {'token': <'qwerty'>}>]`,
|
||||
`{'destination': <'${senderName}'>}`,
|
||||
]);
|
||||
|
||||
result = await expectDbusMockCall(portalObjectPath, "Install", result.offset);
|
||||
await Assert.rejects(
|
||||
promise,
|
||||
/.*plain install failed.*/,
|
||||
"Promise rejected with expected content."
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_negativeResponseFromPrepareInstall() {
|
||||
await callDbusMethod(portalObjectPath, resetMethod, []);
|
||||
await callDbusMethod(portalObjectPath, clearCallsMethod, []);
|
||||
|
||||
await callDbusMethod(portalObjectPath, addMethodMethod, [
|
||||
portalInterfaceName,
|
||||
"PrepareInstall",
|
||||
"ssva{sv}",
|
||||
"o",
|
||||
`ret = "/should/not/be/used"`,
|
||||
]);
|
||||
|
||||
let object = {
|
||||
Version: "1.5",
|
||||
Type: "Application",
|
||||
Name: "Example Launcher Name",
|
||||
Icon:
|
||||
Services.dirsvc.get("CurWorkD", Ci.nsIFile).path +
|
||||
"/favicon-normal16.png",
|
||||
Exec: "browser command",
|
||||
};
|
||||
let ini = desktopEntryFromObject(object);
|
||||
|
||||
let { monitorReady, senderPromise } = sniffDbusMethodCaller("PrepareInstall");
|
||||
await monitorReady;
|
||||
|
||||
let promise = ShellService.requestInstallDynamicLauncher("a.b.c", ini, null);
|
||||
|
||||
let senderName = await senderPromise;
|
||||
let result = await expectDbusMockCall(portalObjectPath, "PrepareInstall", 0);
|
||||
|
||||
let match = result.params.match(/'handle_token': <'(?<token>.*)'>/);
|
||||
ok(match, "Start arguments contain a handle token");
|
||||
let handleToken = match.groups.token;
|
||||
|
||||
let requestPath = `${mockRequestObjectPath}/${senderName
|
||||
.slice(1)
|
||||
.replace(".", "_")}/${handleToken}`;
|
||||
await callDbusMethod(portalObjectPath, addObjectMethod, [
|
||||
requestPath,
|
||||
"org.freedesktop.portal.Request",
|
||||
"@a{sv} {}",
|
||||
"@a(ssss) []",
|
||||
]);
|
||||
await callDbusMethod(requestPath, emitSignalDetailedMethod, [
|
||||
"org.freedesktop.portal.Request",
|
||||
"Response",
|
||||
"ua{sv}",
|
||||
`[<uint32 2>, <@a{sv} {}>]`,
|
||||
`{'destination': <'${senderName}'>}`,
|
||||
]);
|
||||
|
||||
await Assert.rejects(
|
||||
promise,
|
||||
/.*Response was non-zero.*/,
|
||||
"Promise rejected with expected content."
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_missingTokenFromPrepareInstall() {
|
||||
await callDbusMethod(portalObjectPath, resetMethod, []);
|
||||
await callDbusMethod(portalObjectPath, clearCallsMethod, []);
|
||||
|
||||
await callDbusMethod(portalObjectPath, addMethodMethod, [
|
||||
portalInterfaceName,
|
||||
"PrepareInstall",
|
||||
"ssva{sv}",
|
||||
"o",
|
||||
`ret = "/should/not/be/used"`,
|
||||
]);
|
||||
|
||||
let object = {
|
||||
Version: "1.5",
|
||||
Type: "Application",
|
||||
Name: "Example Launcher Name",
|
||||
Icon:
|
||||
Services.dirsvc.get("CurWorkD", Ci.nsIFile).path +
|
||||
"/favicon-normal16.png",
|
||||
Exec: "browser command",
|
||||
};
|
||||
let ini = desktopEntryFromObject(object);
|
||||
|
||||
let { monitorReady, senderPromise } = sniffDbusMethodCaller("PrepareInstall");
|
||||
await monitorReady;
|
||||
|
||||
let promise = ShellService.requestInstallDynamicLauncher("a.b.c", ini, null);
|
||||
|
||||
let senderName = await senderPromise;
|
||||
let result = await expectDbusMockCall(portalObjectPath, "PrepareInstall", 0);
|
||||
|
||||
let match = result.params.match(/'handle_token': <'(?<token>.*)'>/);
|
||||
ok(match, "Start arguments contain a handle token");
|
||||
let handleToken = match.groups.token;
|
||||
|
||||
let requestPath = `${mockRequestObjectPath}/${senderName
|
||||
.slice(1)
|
||||
.replace(".", "_")}/${handleToken}`;
|
||||
await callDbusMethod(portalObjectPath, addObjectMethod, [
|
||||
requestPath,
|
||||
"org.freedesktop.portal.Request",
|
||||
"@a{sv} {}",
|
||||
"@a(ssss) []",
|
||||
]);
|
||||
await callDbusMethod(requestPath, emitSignalDetailedMethod, [
|
||||
"org.freedesktop.portal.Request",
|
||||
"Response",
|
||||
"ua{sv}",
|
||||
`[<uint32 0>, <@a{sv} {}>]`,
|
||||
`{'destination': <'${senderName}'>}`,
|
||||
]);
|
||||
|
||||
await Assert.rejects(
|
||||
promise,
|
||||
/.*No token was provided from the portal.*/,
|
||||
"Promise rejected with expected content."
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_bad_icon() {
|
||||
await callDbusMethod(portalObjectPath, resetMethod, []);
|
||||
await callDbusMethod(portalObjectPath, clearCallsMethod, []);
|
||||
|
||||
let object = {
|
||||
Version: "1.5",
|
||||
Type: "Application",
|
||||
Name: "Example Launcher Name",
|
||||
Icon:
|
||||
Services.dirsvc.get("CurWorkD", Ci.nsIFile).path + "/does-not-exist.png",
|
||||
Exec: "browser command",
|
||||
};
|
||||
let ini = desktopEntryFromObject(object);
|
||||
|
||||
await Assert.rejects(
|
||||
(async () =>
|
||||
ShellService.requestInstallDynamicLauncher("a.b.c", ini, null))(),
|
||||
/.*Failed to open .*png.*/,
|
||||
"The resulting error should indicate that the file wasn't found."
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_uninstall_success() {
|
||||
await callDbusMethod(portalObjectPath, resetMethod, []);
|
||||
await callDbusMethod(portalObjectPath, clearCallsMethod, []);
|
||||
|
||||
await callDbusMethod(portalObjectPath, addMethodMethod, [
|
||||
portalInterfaceName,
|
||||
"Uninstall",
|
||||
"sa{sv}",
|
||||
"",
|
||||
``,
|
||||
]);
|
||||
|
||||
await ShellService.requestUninstallDynamicLauncher("a.b.c");
|
||||
let result = await expectDbusMockCall(portalObjectPath, "Uninstall", 0);
|
||||
|
||||
Assert.equal(
|
||||
result.params,
|
||||
"<'a.b.c.desktop'>, <@a{sv} {}>",
|
||||
"Correct desktop entry ID was provided"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_uninstall_failure() {
|
||||
await callDbusMethod(portalObjectPath, resetMethod, []);
|
||||
await callDbusMethod(portalObjectPath, clearCallsMethod, []);
|
||||
|
||||
await callDbusMethod(portalObjectPath, addMethodMethod, [
|
||||
portalInterfaceName,
|
||||
"Uninstall",
|
||||
"sa{sv}",
|
||||
"",
|
||||
`raise dbus.exceptions.DBusException("uninstall dbus failed", name="org.mozilla.Error.Mocked")`,
|
||||
]);
|
||||
|
||||
await Assert.rejects(
|
||||
(async () => ShellService.requestUninstallDynamicLauncher("a.b.c"))(),
|
||||
/.*uninstall dbus failed.*/,
|
||||
"The uninstall request should fail."
|
||||
);
|
||||
});
|
||||
@@ -1,109 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.defineESModuleGetters(this, {
|
||||
StartupOSIntegration:
|
||||
"moz-src:///browser/components/shell/StartupOSIntegration.sys.mjs",
|
||||
WindowsLaunchOnLogin: "resource://gre/modules/WindowsLaunchOnLogin.sys.mjs",
|
||||
sinon: "resource://testing-common/Sinon.sys.mjs",
|
||||
});
|
||||
|
||||
const PREF = "browser.startup.windowsLaunchOnLogin.defaultEnabled";
|
||||
|
||||
async function runWith({ isFirstRun, prefValue, approved }) {
|
||||
let sandbox = sinon.createSandbox();
|
||||
let approvedStub = sandbox
|
||||
.stub(WindowsLaunchOnLogin, "getLaunchOnLoginApproved")
|
||||
.resolves(approved);
|
||||
let createStub = sandbox
|
||||
.stub(WindowsLaunchOnLogin, "createLaunchOnLogin")
|
||||
.resolves();
|
||||
|
||||
if (prefValue === null) {
|
||||
Services.prefs.clearUserPref(PREF);
|
||||
} else {
|
||||
Services.prefs.setBoolPref(PREF, prefValue);
|
||||
}
|
||||
|
||||
try {
|
||||
await StartupOSIntegration.maybeCreateLaunchOnLoginOnFirstRun(isFirstRun);
|
||||
return { approvedStub, createStub };
|
||||
} finally {
|
||||
sandbox.restore();
|
||||
Services.prefs.clearUserPref(PREF);
|
||||
}
|
||||
}
|
||||
|
||||
add_task(async function test_creates_when_all_conditions_true() {
|
||||
let { createStub } = await runWith({
|
||||
isFirstRun: true,
|
||||
prefValue: true,
|
||||
approved: true,
|
||||
});
|
||||
Assert.ok(
|
||||
createStub.calledOnce,
|
||||
"createLaunchOnLogin should be called when isFirstRun, pref, and approval are all true"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_skips_when_not_first_run() {
|
||||
let { createStub, approvedStub } = await runWith({
|
||||
isFirstRun: false,
|
||||
prefValue: true,
|
||||
approved: true,
|
||||
});
|
||||
Assert.ok(
|
||||
!createStub.called,
|
||||
"createLaunchOnLogin should not be called when isFirstRun is false"
|
||||
);
|
||||
Assert.ok(
|
||||
!approvedStub.called,
|
||||
"getLaunchOnLoginApproved should be short-circuited when isFirstRun is false"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_skips_when_pref_disabled() {
|
||||
let { createStub, approvedStub } = await runWith({
|
||||
isFirstRun: true,
|
||||
prefValue: false,
|
||||
approved: true,
|
||||
});
|
||||
Assert.ok(
|
||||
!createStub.called,
|
||||
"createLaunchOnLogin should not be called when pref is false"
|
||||
);
|
||||
Assert.ok(
|
||||
!approvedStub.called,
|
||||
"getLaunchOnLoginApproved should be short-circuited when pref is false"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_skips_when_windows_policy_denies() {
|
||||
let { createStub, approvedStub } = await runWith({
|
||||
isFirstRun: true,
|
||||
prefValue: true,
|
||||
approved: false,
|
||||
});
|
||||
Assert.ok(
|
||||
approvedStub.calledOnce,
|
||||
"getLaunchOnLoginApproved should be consulted when pref and isFirstRun are true"
|
||||
);
|
||||
Assert.ok(
|
||||
!createStub.called,
|
||||
"createLaunchOnLogin should not be called when Windows policy denies"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_uses_pref_default_when_unset() {
|
||||
let { createStub } = await runWith({
|
||||
isFirstRun: true,
|
||||
prefValue: null,
|
||||
approved: true,
|
||||
});
|
||||
Assert.ok(
|
||||
createStub.calledOnce,
|
||||
"createLaunchOnLogin should be called when pref is at its built-in default of true"
|
||||
);
|
||||
});
|
||||
@@ -10,6 +10,21 @@ run-if = [
|
||||
"os == 'linux'",
|
||||
]
|
||||
|
||||
["test_dynamicLauncher.js"]
|
||||
environment = [
|
||||
"XPCSHELL_TEST=1",
|
||||
# The following prevents gsettings from initializing dbus before our mock is
|
||||
# ready.
|
||||
"GSETTINGS_BACKEND=memory",
|
||||
]
|
||||
run-if = [
|
||||
"os == 'linux'",
|
||||
]
|
||||
support-files = [
|
||||
"../../../places/tests/browser/favicon-normal16.png",
|
||||
]
|
||||
tags = ["portal"]
|
||||
|
||||
["test_linuxDesktopEntry.js"]
|
||||
run-if = [
|
||||
"os == 'linux'",
|
||||
@@ -20,11 +35,6 @@ run-if = [
|
||||
"os == 'mac'",
|
||||
]
|
||||
|
||||
["test_maybeCreateLaunchOnLoginOnFirstRun.js"]
|
||||
run-if = [
|
||||
"os == 'win'"
|
||||
]
|
||||
|
||||
["test_secondaryTileJs.js"]
|
||||
run-if = [
|
||||
"os == 'win'"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"binaryName": "zen",
|
||||
"version": {
|
||||
"product": "firefox",
|
||||
"version": "153.0",
|
||||
"version": "153.0.1",
|
||||
"candidate": "153.0",
|
||||
"candidateBuild": 1
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user