Source code

Revision control

Copy as Markdown

Other Tools

/* Any copyright is dedicated to the Public Domain.
"use strict";
/* import-globals-from head.js */
function _getSupportsFile(path) {
const cr = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(
Ci.nsIChromeRegistry
);
const uri = Services.io.newURI(CHROME_URL_ROOT + path);
const fileurl = cr.convertChromeURL(uri);
return fileurl.QueryInterface(Ci.nsIFileURL);
}
async function enableExtensionDebugging() {
// Disable security prompt
await pushPref("devtools.debugger.prompt-connection", false);
}
/* exported enableExtensionDebugging */
/**
* Install an extension using the AddonManager so it does not show up as temporary.
*/
async function installRegularExtension(pathOrFile) {
const isFile = typeof pathOrFile.isFile === "function" && pathOrFile.isFile();
const file = isFile ? pathOrFile : _getSupportsFile(pathOrFile).file;
const install = await AddonManager.getInstallForFile(file);
return new Promise((resolve, reject) => {
if (!install) {
throw new Error(`An install was not created for ${file.path}`);
}
install.addListener({
onDownloadFailed: reject,
onDownloadCancelled: reject,
onInstallFailed: reject,
onInstallCancelled: reject,
onInstallEnded: resolve,
});
install.install();
});
}
/* exported installRegularExtension */
/**
* Install a temporary extension at the provided path, with the provided name.
* Will use a mock file picker to select the file.
*/
async function installTemporaryExtension(pathOrFile, name, document) {
const { Management } = ChromeUtils.importESModule(
"resource://gre/modules/Extension.sys.mjs"
);
info("Install temporary extension named " + name);
// Mock the file picker to select a test addon
prepareMockFilePicker(pathOrFile);
const onAddonInstalled = new Promise(done => {
Management.on("startup", function listener(event, extension) {
if (extension.name != name) {
return;
}
Management.off("startup", listener);
done(extension);
});
});
// Trigger the file picker by clicking on the button
document.querySelector(".qa-temporary-extension-install-button").click();
info("Wait for addon to be installed");
return onAddonInstalled;
}
/* exported installTemporaryExtension */
function createTemporaryXPI(xpiData) {
const { ExtensionTestCommon } = ChromeUtils.importESModule(
);
const { background, files, id, name, extraProperties } = xpiData;
info("Generate XPI file for " + id);
const manifest = Object.assign(
{},
{
browser_specific_settings: { gecko: { id } },
manifest_version: 2,
name,
version: "1.0",
},
extraProperties
);
const xpiFile = ExtensionTestCommon.generateXPI({
background,
files,
manifest,
});
registerCleanupFunction(() => xpiFile.exists() && xpiFile.remove(false));
return xpiFile;
}
/* exported createTemporaryXPI */
/**
* Remove the existing temporary XPI file generated by ExtensionTestCommon and create a
* new one at the same location.
* @return {File} the temporary extension XPI file created
*/
function updateTemporaryXPI(xpiData, existingXPI) {
info("Delete and regenerate XPI for " + xpiData.id);
// Store the current name to check the xpi is correctly replaced.
const existingName = existingXPI.leafName;
info("Delete existing XPI named: " + existingName);
existingXPI.exists() && existingXPI.remove(false);
const xpiFile = createTemporaryXPI(xpiData);
// Check that the name of the new file is correct
if (xpiFile.leafName !== existingName) {
throw new Error(
"New XPI created with unexpected name: " + xpiFile.leafName
);
}
return xpiFile;
}
/* exported updateTemporaryXPI */
/**
* Install a fake temporary extension by creating a temporary in-memory XPI file.
* @return {File} the temporary extension XPI file created
*/
async function installTemporaryExtensionFromXPI(xpiData, document) {
const xpiFile = createTemporaryXPI(xpiData);
const extension = await installTemporaryExtension(
xpiFile,
xpiData.name,
document
);
info("Wait until the addon debug target appears");
await waitUntil(() => findDebugTargetByText(xpiData.name, document));
return { extension, xpiFile };
}
/* exported installTemporaryExtensionFromXPI */
async function removeTemporaryExtension(name, document) {
info(`Wait for removable extension with name: '${name}'`);
const buttonName = ".qa-temporary-extension-remove-button";
await waitUntil(() => {
const extension = findDebugTargetByText(name, document);
return extension && extension.querySelector(buttonName);
});
info(`Remove the temporary extension with name: '${name}'`);
const temporaryExtensionItem = findDebugTargetByText(name, document);
temporaryExtensionItem.querySelector(buttonName).click();
info("Wait until the debug target item disappears");
await waitUntil(() => !findDebugTargetByText(name, document));
}
/* exported removeTemporaryExtension */
async function removeExtension(id, name, document) {
info(
"Retrieve the extension instance from the addon manager, and uninstall it"
);
const extension = await AddonManager.getAddonByID(id);
extension.uninstall();
info("Wait until the addon disappears from about:debugging");
await waitUntil(() => !findDebugTargetByText(name, document));
}
/* exported removeExtension */
function prepareMockFilePicker(pathOrFile) {
const isFile = typeof pathOrFile.isFile === "function" && pathOrFile.isFile();
const file = isFile ? pathOrFile : _getSupportsFile(pathOrFile).file;
// Mock the file picker to select a test addon
const MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(window.browsingContext);
MockFilePicker.setFiles([file]);
}
/* exported prepareMockFilePicker */
function promiseBackgroundContextEvent(extensionId, eventName) {
const { Management } = ChromeUtils.importESModule(
"resource://gre/modules/Extension.sys.mjs"
);
return new Promise(resolve => {
Management.on(eventName, function listener(_evtName, context) {
if (context.extension.id === extensionId) {
Management.off(eventName, listener);
resolve();
}
});
});
}
function promiseBackgroundContextLoaded(extensionId) {
return promiseBackgroundContextEvent(extensionId, "proxy-context-load");
}
/* exported promiseBackgroundContextLoaded */
function promiseBackgroundContextUnloaded(extensionId) {
return promiseBackgroundContextEvent(extensionId, "proxy-context-unload");
}
/* exported promiseBackgroundContextUnloaded */
async function assertBackgroundStatus(
extName,
{ document, expectedStatus, targetElement }
) {
const target = targetElement || findDebugTargetByText(extName, document);
const getBackgroundStatusElement = () =>
target.querySelector(".extension-backgroundscript__status");
await waitFor(
() =>
getBackgroundStatusElement()?.classList.contains(
`extension-backgroundscript__status--${expectedStatus}`
),
`Wait ${extName} Background script status "${expectedStatus}" to be rendered`
);
}
/* exported assertBackgroundStatus */
function getExtensionInstance(extensionId) {
const policy = WebExtensionPolicy.getByID(extensionId);
ok(policy, `Got a WebExtensionPolicy instance for ${extensionId}`);
ok(policy.extension, `Got an Extension class instance for ${extensionId}`);
return policy.extension;
}
/* exported getExtensionInstance */
async function triggerExtensionEventPageIdleTimeout(extensionId) {
await getExtensionInstance(extensionId).terminateBackground();
}
/* exported triggerExtensionEventPageIdleTimeout */
async function wakeupExtensionEventPage(extensionId) {
await getExtensionInstance(extensionId).wakeupBackground();
}
/* exported wakeupExtensionEventPage */
function promiseTerminateBackgroundScriptIgnored(extensionId) {
const extension = getExtensionInstance(extensionId);
return new Promise(resolve => {
extension.once("background-script-suspend-ignored", resolve);
});
}
/* exported promiseTerminateBackgroundScriptIgnored */
async function promiseBackgroundStatusUpdate(window) {
waitForDispatch(
window.AboutDebugging.store,
"EXTENSION_BGSCRIPT_STATUS_UPDATED"
);
}
/* exported promiseBackgroundStatusUpdate */