Bug 2039411 - Set the default PDF handler through the OS "Open with" picker and intercept the round-trip. r=nrishel,omc-reviewers,sachung
Differential Revision: https://phabricator.services.mozilla.com/D300462
This commit is contained in:
committed by
hsohaney@mozilla.com
parent
425b0d93e3
commit
d0fc8fa5c2
@@ -12,6 +12,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ASRouter: "resource:///modules/asrouter/ASRouter.sys.mjs",
|
||||
ScheduledTask: "resource://gre/modules/ScheduledTask.sys.mjs",
|
||||
Subprocess: "resource://gre/modules/Subprocess.sys.mjs",
|
||||
WindowsSetDefaultRedirect:
|
||||
"moz-src:///browser/components/shell/WindowsSetDefaultRedirect.sys.mjs",
|
||||
WindowsVersionInfo:
|
||||
"resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs",
|
||||
});
|
||||
@@ -422,7 +424,36 @@ let ShellServiceInternal = {
|
||||
);
|
||||
},
|
||||
|
||||
async setAsDefaultPDFHandler(onlyIfKnownBrowser = false) {
|
||||
/**
|
||||
* Returns the on-disk nsIFile for a PDF bundled under the browser directory
|
||||
* in NS_GRE_DIR.
|
||||
*
|
||||
* @param {string} aLeafName - The bundled PDF's file name, e.g.
|
||||
* "confused_fox.pdf".
|
||||
* @returns {nsIFile} The bundled file (which may not exist on disk).
|
||||
*/
|
||||
getBundledPdfFile(aLeafName) {
|
||||
const file = Services.dirsvc.get("GreD", Ci.nsIFile);
|
||||
file.append("browser");
|
||||
file.append(aLeafName);
|
||||
return file;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Firefox as the Windows default PDF handler.
|
||||
*
|
||||
* @param {boolean} [onlyIfKnownBrowser] - When true, only proceed if the
|
||||
* current default PDF handler is a known browser.
|
||||
* @param {boolean} [openInFirefox] - Only meaningful on the "Open with"
|
||||
* picker code path. After the user picks Firefox, the OS relaunches Firefox
|
||||
* with the bundled stub PDF; this flag decides whether we then open a PDF in
|
||||
* a new tab (true), to land the user in Firefox, or silently absorb that
|
||||
* relaunch (false).
|
||||
*/
|
||||
async setAsDefaultPDFHandler(
|
||||
onlyIfKnownBrowser = false,
|
||||
openInFirefox = false
|
||||
) {
|
||||
if (AppConstants.platform != "win") {
|
||||
throw new Error("Windows-only");
|
||||
}
|
||||
@@ -453,10 +484,6 @@ let ShellServiceInternal = {
|
||||
);
|
||||
}
|
||||
|
||||
const winShell = this.shellService.QueryInterface(
|
||||
Ci.nsIWindowsShellService
|
||||
);
|
||||
|
||||
// Optional second attempt via the undocumented IOpenWithLauncher API,
|
||||
// which surfaces the OS "Open with" picker so the user can pick Firefox
|
||||
// themselves. Gated by a pref so it can be remotely disabled if it
|
||||
@@ -469,10 +496,27 @@ let ShellServiceInternal = {
|
||||
)
|
||||
) {
|
||||
method = "open_with";
|
||||
const openWithArg = this.getBundledPdfFile("confused_fox.pdf").path;
|
||||
// Arm the round-trip: the OS hands `openWithArg` back to Firefox if the user
|
||||
// selects us. We redirect that launch to the bundled PDF); otherwise overrideUri
|
||||
// is null and the launch is suppressed.
|
||||
const overrideUri = openInFirefox
|
||||
? Services.io.newFileURI(this.getBundledPdfFile("blank.pdf")).spec
|
||||
: null;
|
||||
lazy.WindowsSetDefaultRedirect.arm(
|
||||
openWithArg,
|
||||
overrideUri,
|
||||
lazy.WindowsSetDefaultRedirect.TYPE.FILE
|
||||
);
|
||||
|
||||
try {
|
||||
winShell.launchOpenWithDefaultPickerForFileType(".pdf");
|
||||
const flags = this._isWindows11()
|
||||
? Ci.nsIWindowsShellService.OPEN_WITH_SET_HANDLER
|
||||
: Ci.nsIWindowsShellService.OPEN_WITH_SET_HANDLER_WIN10;
|
||||
this.shellService.launchSetDefaultAppPicker(openWithArg, flags);
|
||||
success = true;
|
||||
} catch (e) {
|
||||
lazy.WindowsSetDefaultRedirect.clear();
|
||||
// The picker API itself failed (e.g. COM error). Fall through to the
|
||||
// modern settings dialog rather than leaving the user without any
|
||||
// default-handler UI.
|
||||
@@ -487,7 +531,7 @@ let ShellServiceInternal = {
|
||||
if (!success && this._isWindows11()) {
|
||||
method = "settings";
|
||||
try {
|
||||
winShell.launchModernSettingsDialogDefaultApps();
|
||||
this.shellService.launchModernSettingsDialogDefaultApps();
|
||||
Glean.browser.setDefaultPdfHandlerModernSettingsResult.Success.add(1);
|
||||
success = true;
|
||||
} catch (e) {
|
||||
@@ -524,9 +568,7 @@ let ShellServiceInternal = {
|
||||
*/
|
||||
isDefaultHandlerFor(aFileExtensionOrProtocol) {
|
||||
if (AppConstants.platform == "win") {
|
||||
return this.shellService
|
||||
.QueryInterface(Ci.nsIWindowsShellService)
|
||||
.isDefaultHandlerFor(aFileExtensionOrProtocol);
|
||||
return this.shellService.isDefaultHandlerFor(aFileExtensionOrProtocol);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Command-line handler for the Windows IOpenWithLauncher round-trip.
|
||||
*
|
||||
* When the ShellService.setAsDefault{PDF,Protocol}Handler launches the windows only
|
||||
* "Open with" picker, it hands it a bundled stub file path for file-type
|
||||
* defaults, or a URL for protocol defaults, and stashes a one-shot
|
||||
* { openWithArg, overrideUri } redirect.
|
||||
*
|
||||
* If the user picks Firefox, Windows invokes Firefox with
|
||||
* `-osint -url <openWithArg>`. This handler runs before BrowserContentHandler and
|
||||
* intercepts the launch: it asks ShellService whether the -url
|
||||
* value matches the pending openWithArg and, if so, suppresses the
|
||||
* open and optionally redirects to the stashed overrideUri so the user lands
|
||||
* somewhere meaningful in Firefox.
|
||||
*/
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
|
||||
WindowsSetDefaultRedirect:
|
||||
"moz-src:///browser/components/shell/WindowsSetDefaultRedirect.sys.mjs",
|
||||
});
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
|
||||
return console.createInstance({
|
||||
prefix: "WindowsSetDefaultAppCmdHandler",
|
||||
maxLogLevel: "Warn",
|
||||
});
|
||||
});
|
||||
|
||||
export class CommandLineHandler {
|
||||
static classID = Components.ID("{da7de528-7a15-452e-b5a7-521099997ca1}");
|
||||
static contractID = "@mozilla.org/browser/windows-default-clh;1";
|
||||
|
||||
QueryInterface = ChromeUtils.generateQI([Ci.nsICommandLineHandler]);
|
||||
|
||||
handle(aCmdLine) {
|
||||
if (aCmdLine.findFlag("osint", false) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const urlIdx = aCmdLine.findFlag("url", false);
|
||||
if (urlIdx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cmdArg = aCmdLine.getArgument(urlIdx + 1);
|
||||
|
||||
// null: not our openWithArg, leave -url for BrowserContentHandler
|
||||
const redirect = lazy.WindowsSetDefaultRedirect.consume(cmdArg);
|
||||
if (!redirect) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { overrideUri } = redirect;
|
||||
|
||||
lazy.logConsole.debug(
|
||||
`Claimed IOpenWithLauncher openWithArg ${cmdArg}: state=${aCmdLine.state}, overrideUri=${overrideUri}`
|
||||
);
|
||||
|
||||
// Consume the arg and suppress the default open so BrowserContentHandler
|
||||
// doesn't act on it
|
||||
aCmdLine.handleFlagWithParam("url", false);
|
||||
aCmdLine.preventDefault = true;
|
||||
if (overrideUri === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
lazy.logConsole.info(
|
||||
`Redirecting IOpenWithLauncher round-trip to ${overrideUri}`
|
||||
);
|
||||
|
||||
try {
|
||||
const win = lazy.BrowserWindowTracker.getTopWindow();
|
||||
if (win) {
|
||||
win.openTrustedLinkIn(overrideUri, "tab");
|
||||
return;
|
||||
}
|
||||
|
||||
const args = Cc["@mozilla.org/supports-string;1"].createInstance(
|
||||
Ci.nsISupportsString
|
||||
);
|
||||
args.data = overrideUri;
|
||||
lazy.BrowserWindowTracker.openWindow({ args });
|
||||
} catch (e) {
|
||||
lazy.logConsole.error(
|
||||
`Failed to open redirect target ${overrideUri}:`,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* The IOpenWithLauncher api call protocol shared by the producer
|
||||
* (ShellService.setAsDefault{PDF,Protocol}Handler) and the consumer
|
||||
* (WindowsSetDefaultAppCmdHandler).
|
||||
*
|
||||
* ShellService arms a one-shot redirect before launching the OS "Open with"
|
||||
* picker via IOpenWithLauncher; once the user picks Firefox, the OS relaunches
|
||||
* Firefox with the same value and the command-line handler consumes it. This
|
||||
* module owns that shared state (the pref shape and the matching rules).
|
||||
*/
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
|
||||
});
|
||||
|
||||
// This pref is an object { openWithArg, overrideUri, type } consumed by
|
||||
// WindowsSetDefaultAppCmdHandler when the user picks a default (file type or
|
||||
// protocol) using the IOpenWithLauncher API. It is reset anytime the dialog is
|
||||
// used again, or when we intercept the OS reopening one of our openWithArgs.
|
||||
export const SET_DEFAULT_REDIRECT_PREF =
|
||||
"browser.shell.setDefaultApp.pendingRedirect";
|
||||
|
||||
export class WindowsSetDefaultRedirect {
|
||||
// Supported default types to set using IOpenWithLauncher.
|
||||
static TYPE = {
|
||||
FILE: 1 << 0,
|
||||
PROTOCOL: 1 << 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Stash a one-shot redirect for the IOpenWithLauncher call.
|
||||
*
|
||||
* @param {string} openWithArg
|
||||
* The value handed to launchSetDefaultAppPicker, which the OS hands back as
|
||||
* "-osint -url <openWithArg>" once the user picks a new default. Depending on
|
||||
* type, this is either a file path on the system (file-type defaults) or a
|
||||
* URL (protocol defaults).
|
||||
* @param {?string} overrideUri
|
||||
* URI spec to open when openWithArg comes back, or null to consume the relaunch
|
||||
* and open nothing.
|
||||
* @param {number} type
|
||||
* One of WindowsSetDefaultRedirect.TYPE, identifying whether openWithArg is a
|
||||
* file path or a URL.
|
||||
*/
|
||||
static arm(openWithArg, overrideUri, type) {
|
||||
// Clear any stale object left by an older call.
|
||||
Services.prefs.clearUserPref(SET_DEFAULT_REDIRECT_PREF);
|
||||
|
||||
Services.prefs.setStringPref(
|
||||
SET_DEFAULT_REDIRECT_PREF,
|
||||
JSON.stringify({ openWithArg, overrideUri: overrideUri ?? null, type })
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear a pending redirect.
|
||||
*/
|
||||
static clear() {
|
||||
Services.prefs.clearUserPref(SET_DEFAULT_REDIRECT_PREF);
|
||||
}
|
||||
|
||||
/**
|
||||
* If `arg` is the openWithArg stashed by the most recent
|
||||
* launchSetDefaultAppPicker call, consume the one-shot redirect and return
|
||||
* its `{ overrideUri }`, where overrideUri is a URI spec to open or null to
|
||||
* just suppress the relaunch. Returns null when `arg` is unrelated to a
|
||||
* pending attempt to set a default.
|
||||
*
|
||||
* @param {string} arg - The -url value the OS handed back.
|
||||
* @returns {?{overrideUri: ?string}}
|
||||
*/
|
||||
static consume(arg) {
|
||||
const state = this.#read();
|
||||
if (!state || !this.#matches(state, arg)) {
|
||||
return null;
|
||||
}
|
||||
Services.prefs.clearUserPref(SET_DEFAULT_REDIRECT_PREF);
|
||||
return { overrideUri: state.overrideUri ?? null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and validate the pending redirect stashed by arm().
|
||||
*
|
||||
* @returns {?{openWithArg: string, overrideUri: ?string, type: number}} The
|
||||
* stored state, or null when the pref is unset, holds the wrong type, or is
|
||||
* malformed JSON.
|
||||
*/
|
||||
static #read() {
|
||||
let raw;
|
||||
try {
|
||||
raw = Services.prefs.getStringPref(SET_DEFAULT_REDIRECT_PREF, "");
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const state = JSON.parse(raw);
|
||||
return state && typeof state.openWithArg === "string" ? state : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the -url value the OS handed back matches the stashed redirect.
|
||||
*
|
||||
* @param {{openWithArg: string, type: number}} state - The stashed redirect.
|
||||
* @param {string} arg - The -url value from the OS relaunch.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static #matches(state, arg) {
|
||||
switch (state.type) {
|
||||
case this.TYPE.PROTOCOL:
|
||||
return state.openWithArg === arg;
|
||||
case this.TYPE.FILE:
|
||||
try {
|
||||
return new lazy.FileUtils.File(state.openWithArg).equals(
|
||||
new lazy.FileUtils.File(arg)
|
||||
);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
Classes = []
|
||||
|
||||
if buildconfig.substs["OS_ARCH"] == "WINNT":
|
||||
Classes += [
|
||||
{
|
||||
"cid": "{da7de528-7a15-452e-b5a7-521099997ca1}",
|
||||
"contract_ids": ["@mozilla.org/browser/windows-default-clh;1"],
|
||||
"categories": {
|
||||
"command-line-handler": "l-windows-default",
|
||||
},
|
||||
"esModule": "moz-src:///browser/components/shell/WindowsSetDefaultAppCmdHandler.sys.mjs",
|
||||
"constructor": "CommandLineHandler",
|
||||
},
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
@@ -73,6 +73,15 @@ elif CONFIG["OS_ARCH"] == "WINNT":
|
||||
"crypt32",
|
||||
"propsys",
|
||||
]
|
||||
FINAL_TARGET_FILES += ["content/blank.pdf", "content/confused_fox.pdf"]
|
||||
|
||||
MOZ_SRC_FILES += [
|
||||
"WindowsSetDefaultAppCmdHandler.sys.mjs",
|
||||
]
|
||||
|
||||
XPCOM_MANIFESTS += [
|
||||
"components.conf",
|
||||
]
|
||||
|
||||
XPIDL_MODULE = "shellservice"
|
||||
|
||||
@@ -83,6 +92,7 @@ MOZ_SRC_FILES += [
|
||||
"HeadlessShell.sys.mjs",
|
||||
"ScreenshotChild.sys.mjs",
|
||||
"ShellService.sys.mjs",
|
||||
"WindowsSetDefaultRedirect.sys.mjs",
|
||||
]
|
||||
|
||||
MOZ_SRC_FILES += [
|
||||
|
||||
@@ -10,6 +10,27 @@ interface nsIFile;
|
||||
[scriptable, uuid(fb9b59db-5a91-4e67-92b6-35e7d6e6d3fd)]
|
||||
interface nsIWindowsShellService : nsIShellService
|
||||
{
|
||||
/*
|
||||
* Bit flags for launchSetDefaultAppPicker's aFlags argument. They map 1:1
|
||||
* onto the flags accepted by the undocumented Windows IOpenWithLauncher COM
|
||||
* interface's Launch method.
|
||||
* The mapping below was determined empirically and determines the picker's
|
||||
* messaging and actions.
|
||||
*/
|
||||
// Suppresses the default open-in-selected-app behavior. Remote-controlled
|
||||
// by Microsoft.
|
||||
const long OPEN_WITH_SUPPRESS_OPEN = 1 << 2;
|
||||
// Switches picker messaging to protocol mode ("Select a default handler
|
||||
// for ___ links").
|
||||
const long OPEN_WITH_PROTOCOL_MESSAGING = 1 << 3;
|
||||
// Opens with the selected app just once; does not set a default.
|
||||
const long OPEN_WITH_OPEN_ONCE = 1 << 6;
|
||||
// Asks the user to set a default for the given file type or protocol. Messaging is
|
||||
// ideal on Windows 11 only.
|
||||
const long OPEN_WITH_SET_HANDLER = 1 << 7;
|
||||
// Like OPEN_WITH_SET_HANDLER, but with ideal messaging for Windows 10.
|
||||
const long OPEN_WITH_SET_HANDLER_WIN10 = 1 << 13;
|
||||
|
||||
/*
|
||||
* Creates a new shortcut (.lnk) file. This shortcut will be recorded in
|
||||
* a new shortcuts log file located in %PROGRAMDATA%\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38
|
||||
@@ -349,16 +370,19 @@ interface nsIWindowsShellService : nsIShellService
|
||||
);
|
||||
|
||||
/*
|
||||
* Launch the default app picker for a given file type via the Windows
|
||||
* IOpenWithLauncher COM interface.
|
||||
* Launch the default app picker via the Windows IOpenWithLauncher COM
|
||||
* interface. The caller picks the target (a file path whose extension
|
||||
* determines the picker for file-type defaults, or a URL for protocol
|
||||
* defaults) and the flag bits that drive the picker's messaging and actions.
|
||||
*
|
||||
* @param aFileType
|
||||
* The file extension (e.g. ".pdf") for which to open
|
||||
* the default app picker.
|
||||
* @param aTarget
|
||||
* File path or URL.
|
||||
* @param aFlags
|
||||
* Flag combination that determines the behavior of the IOpenWithLauncher dialog.
|
||||
* @throws NS_ERROR_FAILURE
|
||||
* If IOpenWithLauncher is unavailable or fails.
|
||||
*/
|
||||
void launchOpenWithDefaultPickerForFileType(in AString aFileType);
|
||||
void launchSetDefaultAppPicker(in AString aTarget, in long aFlags);
|
||||
|
||||
/*
|
||||
* Open the Windows modern settings dialog for choosing default apps
|
||||
|
||||
@@ -459,13 +459,21 @@ nsWindowsShellService::CanSetDefaultBrowserUserChoice(bool* aResult) {
|
||||
|
||||
class __declspec(novtable) IOpenWithLauncher : public IUnknown {
|
||||
public:
|
||||
// lpszPath selects what the picker offers to set as default. It accepts
|
||||
// several shapes:
|
||||
// - a file path: "C:\\path\\to\\file.pdf"
|
||||
// - a file type: ".pdf"
|
||||
// - a protocol: "http"
|
||||
// - a protocol URI: "https://example.com", "mailto:foo@example.com"
|
||||
// flags determines the messaging and actions available of the
|
||||
// IOpenWithLauncher dialog.
|
||||
virtual HRESULT STDMETHODCALLTYPE Launch(HWND hWndParent, LPCWSTR lpszPath,
|
||||
int flags) = 0;
|
||||
};
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWindowsShellService::LaunchOpenWithDefaultPickerForFileType(
|
||||
const nsAString& aFileType) {
|
||||
nsWindowsShellService::LaunchSetDefaultAppPicker(const nsAString& aTarget,
|
||||
int32_t aFlags) {
|
||||
static constexpr GUID IID_IOpenWithLauncher = {
|
||||
0x6a283fe2,
|
||||
0xecfa,
|
||||
@@ -500,10 +508,7 @@ nsWindowsShellService::LaunchOpenWithDefaultPickerForFileType(
|
||||
// Make sure the dialog is foregrounded.
|
||||
CoAllowSetForegroundWindow(pOWL, nullptr);
|
||||
|
||||
// The flag is a bit of a mystery; on Win11+ 0x84 gives ideal messaging, on
|
||||
// Win10 we use 0x2004.
|
||||
int flag = mozilla::IsWin11OrLater() ? 0x84 : 0x2004;
|
||||
hr = pOWL->Launch(nullptr, aFileType.Data(), flag);
|
||||
hr = pOWL->Launch(nullptr, PromiseFlatString(aTarget).get(), aFlags);
|
||||
|
||||
return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
@@ -120,3 +120,9 @@ tags = "os_integration"
|
||||
["browser_setDesktopBackgroundPreview.js"]
|
||||
support-files = ["large.png", "canvas.html"]
|
||||
tags = "os_integration"
|
||||
|
||||
["browser_windowsSetDefaultAppCmdHandler.js"]
|
||||
run-if = [
|
||||
"os == 'win'",
|
||||
]
|
||||
tags = "os_integration"
|
||||
|
||||
@@ -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,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"
|
||||
);
|
||||
});
|
||||
@@ -221,6 +221,13 @@
|
||||
@BINPATH@/@MOZ_DXC_DLL_NAME@
|
||||
#endif
|
||||
|
||||
; Bundled stub PDFs handed to the OS "Open with" picker by the
|
||||
; set-default-PDF-handler flow (see WindowsSetDefaultAppCmdHandler).
|
||||
#ifdef XP_WIN
|
||||
@RESPATH@/browser/confused_fox.pdf
|
||||
@RESPATH@/browser/blank.pdf
|
||||
#endif
|
||||
|
||||
; [Browser Chrome Files]
|
||||
@RESPATH@/browser/chrome.manifest
|
||||
@RESPATH@/browser/chrome/browser@JAREXT@
|
||||
|
||||
@@ -185,8 +185,14 @@ export const SpecialMessageActions = {
|
||||
*
|
||||
* @param {Window} window Reference to a window object
|
||||
*/
|
||||
async setDefaultPDFHandler(window, onlyIfKnownBrowser = false) {
|
||||
await window.getShellService().setAsDefaultPDFHandler(onlyIfKnownBrowser);
|
||||
async setDefaultPDFHandler(
|
||||
window,
|
||||
onlyIfKnownBrowser = false,
|
||||
openInFirefox = false
|
||||
) {
|
||||
await window
|
||||
.getShellService()
|
||||
.setAsDefaultPDFHandler(onlyIfKnownBrowser, openInFirefox);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -815,7 +821,8 @@ export const SpecialMessageActions = {
|
||||
case "SET_DEFAULT_PDF_HANDLER":
|
||||
await this.setDefaultPDFHandler(
|
||||
window,
|
||||
action.data?.onlyIfKnownBrowser ?? false
|
||||
action.data?.onlyIfKnownBrowser ?? false,
|
||||
action.data?.openInFirefox ?? false
|
||||
);
|
||||
break;
|
||||
case "DECLINE_DEFAULT_PDF_HANDLER":
|
||||
|
||||
+4
@@ -478,6 +478,10 @@
|
||||
"onlyIfKnownBrowser": {
|
||||
"type": "boolean",
|
||||
"description": "Only set Firefox as the default PDF handler if the current PDF handler is a known browser."
|
||||
},
|
||||
"openInFirefox": {
|
||||
"type": "boolean",
|
||||
"description": "If the OS hands the stub PDF back to Firefox after the user picks Firefox in the open-with dialog, open a follow-up PDF in a new tab instead of suppressing the launch."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -267,6 +267,10 @@ Windows only.
|
||||
// Only set Firefox as the default PDF handler if the current PDF handler is a
|
||||
// known browser.
|
||||
onlyIfKnownBrowser?: boolean;
|
||||
// If the OS hands the stub PDF back to Firefox after the user picks Firefox
|
||||
// in the open-with dialog, open a follow-up PDF in a new tab instead of
|
||||
// suppressing the launch.
|
||||
openInFirefox?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@ add_task(async function test_set_default_pdf_handler_no_data() {
|
||||
"setAsDefaultPDFHandler was called by the action"
|
||||
);
|
||||
Assert.ok(
|
||||
stub.calledWithExactly(false),
|
||||
stub.calledWithExactly(false, false),
|
||||
"setAsDefaultPDFHandler called with onlyIfKnownBrowser = false"
|
||||
);
|
||||
});
|
||||
@@ -55,7 +55,7 @@ add_task(async function test_set_default_pdf_handler_data_false() {
|
||||
"setAsDefaultPDFHandler was called by the action"
|
||||
);
|
||||
Assert.ok(
|
||||
stub.calledWithExactly(false),
|
||||
stub.calledWithExactly(false, false),
|
||||
"setAsDefaultPDFHandler called with onlyIfKnownBrowser = false"
|
||||
);
|
||||
});
|
||||
@@ -86,7 +86,7 @@ add_task(async function test_set_default_pdf_handler_data_true() {
|
||||
"setAsDefaultPDFHandler was called by the action"
|
||||
);
|
||||
Assert.ok(
|
||||
stub.calledWithExactly(true),
|
||||
stub.calledWithExactly(true, false),
|
||||
"setAsDefaultPDFHandler called with onlyIfKnownBrowser = true"
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user