Differential Revision: https://phabricator.services.mozilla.com/D291691
174 lines
4.5 KiB
JavaScript
174 lines
4.5 KiB
JavaScript
/* 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/. */
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
DownloadPaths: "resource://gre/modules/DownloadPaths.sys.mjs",
|
|
Downloads: "resource://gre/modules/Downloads.sys.mjs",
|
|
FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
|
|
});
|
|
|
|
const HELPER_APP_DIALOG_HANDLER_CID = Services.uuid.generateUUID();
|
|
const HELPER_APP_DIALOG_CONTRACT_ID = "@mozilla.org/helperapplauncherdialog;1";
|
|
|
|
async function computeTargetFile(
|
|
aLauncher,
|
|
aDefaultFileName,
|
|
aSuggestedFileExtension
|
|
) {
|
|
let targetDir;
|
|
const browsingContextId = aLauncher.browsingContextId;
|
|
if (browsingContextId) {
|
|
try {
|
|
const bc = BrowsingContext.get(browsingContextId);
|
|
const folderOverride = bc?.top?.downloadFolderOverride;
|
|
if (folderOverride) {
|
|
targetDir = folderOverride;
|
|
}
|
|
} catch (e) {
|
|
// BrowsingContext may have been destroyed.
|
|
}
|
|
}
|
|
|
|
if (!targetDir) {
|
|
targetDir = await lazy.Downloads.getPreferredDownloadsDirectory();
|
|
}
|
|
|
|
let leafName;
|
|
if (aDefaultFileName) {
|
|
leafName = lazy.DownloadPaths.sanitize(aDefaultFileName, {
|
|
compressWhitespaces: true,
|
|
});
|
|
}
|
|
|
|
if (!leafName) {
|
|
const ext = aSuggestedFileExtension || "";
|
|
leafName = "unnamed" + ext;
|
|
}
|
|
|
|
const dir = new lazy.FileUtils.File(targetDir);
|
|
dir.append(leafName);
|
|
return lazy.DownloadPaths.createNiceUniqueFile(dir);
|
|
}
|
|
|
|
/**
|
|
* The HelperAppDialogHandler can override the default component factory for
|
|
* nsIHelperAppLauncherDialog to silently save downloads without showing UI
|
|
* when WebDriver controls download behavior.
|
|
* See the main implementation in https://searchfox.org/firefox-main/source/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs.
|
|
*/
|
|
class HelperAppDialogHandlerClass {
|
|
#callers;
|
|
#originalCID;
|
|
#registeredFactory;
|
|
#registrar;
|
|
|
|
constructor() {
|
|
this.#registeredFactory = null;
|
|
|
|
this.#registrar = Components.manager.QueryInterface(
|
|
Ci.nsIComponentRegistrar
|
|
);
|
|
this.#originalCID = this.#registrar.contractIDToCID(
|
|
HELPER_APP_DIALOG_CONTRACT_ID
|
|
);
|
|
|
|
this.#callers = new Set();
|
|
}
|
|
|
|
/**
|
|
* Stop intercepting helper app launcher dialogs on behalf of the provided
|
|
* caller. Dialogs will only be restored once all callers have called
|
|
* restoreDialogs.
|
|
*
|
|
* @param {object} caller
|
|
*/
|
|
restoreDialogs(caller) {
|
|
if (!this.#callers.has(caller)) {
|
|
return;
|
|
}
|
|
|
|
this.#callers.delete(caller);
|
|
|
|
if (!this.#registeredFactory) {
|
|
return;
|
|
}
|
|
|
|
if (this.#callers.size) {
|
|
return;
|
|
}
|
|
|
|
this.#registrar.unregisterFactory(
|
|
HELPER_APP_DIALOG_HANDLER_CID,
|
|
this.#registeredFactory
|
|
);
|
|
this.#registeredFactory = null;
|
|
|
|
this.#registrar.registerFactory(
|
|
this.#originalCID,
|
|
"",
|
|
HELPER_APP_DIALOG_CONTRACT_ID,
|
|
null
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Intercept all helper app launcher dialogs by registering a custom factory
|
|
* that silently saves downloads instead of showing UI.
|
|
*
|
|
* @param {object} caller
|
|
*/
|
|
interceptDialogs(caller) {
|
|
this.#callers.add(caller);
|
|
|
|
if (this.#registeredFactory) {
|
|
return;
|
|
}
|
|
|
|
this.#registeredFactory = {
|
|
createInstance(iid) {
|
|
const proxy = {
|
|
show(aLauncher, _aContext, _aReason) {
|
|
aLauncher.promptForSaveDestination();
|
|
},
|
|
|
|
promptForSaveToFileAsync(
|
|
aLauncher,
|
|
_aContext,
|
|
aDefaultFileName,
|
|
aSuggestedFileExtension,
|
|
_aForcePrompt
|
|
) {
|
|
computeTargetFile(
|
|
aLauncher,
|
|
aDefaultFileName,
|
|
aSuggestedFileExtension
|
|
).then(
|
|
file => aLauncher.saveDestinationAvailable(file),
|
|
() => aLauncher.saveDestinationAvailable(null)
|
|
);
|
|
},
|
|
|
|
QueryInterface: ChromeUtils.generateQI([
|
|
"nsIHelperAppLauncherDialog",
|
|
]),
|
|
};
|
|
return proxy.QueryInterface(iid);
|
|
},
|
|
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
|
};
|
|
|
|
this.#registrar.registerFactory(
|
|
HELPER_APP_DIALOG_HANDLER_CID,
|
|
"WebDriver HelperAppLauncherDialog handler",
|
|
HELPER_APP_DIALOG_CONTRACT_ID,
|
|
this.#registeredFactory
|
|
);
|
|
}
|
|
}
|
|
|
|
// Expose a singleton shared by all WebDriver sessions.
|
|
export const HelperAppDialogHandler = new HelperAppDialogHandlerClass();
|