/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const { AddonManager } = ChromeUtils.importESModule( "resource://gre/modules/AddonManager.sys.mjs" ); const { ExtensionTestUtils } = ChromeUtils.importESModule( "resource://testing-common/ExtensionXPCShellUtils.sys.mjs" ); const DistinctDevToolsServer = getDistinctDevToolsServer(); ExtensionTestUtils.init(this); add_setup(async () => { Services.prefs.setBoolPref("extensions.blocklist.enabled", false); await startupAddonsManager(); }); // Basic request wrapper that sends a request and resolves on the next packet. // Will only work for very basic scenarios, without events emitted on the server // etc... async function sendRequest(transport, request) { return new Promise(resolve => { transport.hooks = { onPacket: packet => { dump(`received packet: ${JSON.stringify(packet)}\n`); // Let's resolve only when we get a packet that is related to our // request. It is needed because some methods do not return the correct // response right away. This is the case of the `reload` method, which // receives a `addonListChanged` message first and then a `reload` // message. if (packet.from === request.to) { resolve(packet); } }, }; transport.send(request); }); } // If this test case fails, please reach out to webext peers because // https://github.com/mozilla/web-ext relies on the APIs tested here. add_task(async function test_webext_run_apis() { DistinctDevToolsServer.init(); DistinctDevToolsServer.registerAllActors(); const transport = DistinctDevToolsServer.connectPipe(); // After calling connectPipe, the root actor will be created on the server // and a packet will be emitted after a tick. Wait for the initial packet. await new Promise(resolve => { transport.hooks = { onPacket: resolve }; }); const getRootResponse = await sendRequest(transport, { to: "root", type: "getRoot", }); ok(getRootResponse, "received a response after calling RootActor::getRoot"); ok(getRootResponse.addonsActor, "getRoot returned an addonsActor id"); // installTemporaryAddon const addonId = "test-addons-actor@mozilla.org"; const addonPath = getFilePath("addons/web-extension", false, true); const promiseStarted = AddonTestUtils.promiseWebExtensionStartup(addonId); const { addon } = await sendRequest(transport, { to: getRootResponse.addonsActor, type: "installTemporaryAddon", addonPath, // The openDevTools parameter is not always passed by web-ext. This test // omits it, to make sure that the request without the flag is accepted. // openDevTools: false, }); await promiseStarted; ok(addon, "addonsActor allows to install a temporary add-on"); equal(addon.id, addonId, "temporary add-on is the expected one"); equal(addon.actor, false, "temporary add-on does not have an actor"); // listAddons let { addons } = await sendRequest(transport, { to: "root", type: "listAddons", }); ok(Array.isArray(addons), "listAddons() returns a list of add-ons"); equal(addons.length, 1, "expected an add-on installed"); const installedAddon = addons[0]; equal(installedAddon.id, addonId, "installed add-on is the expected one"); ok(installedAddon.actor, "returned add-on has an actor"); // reload const promiseReloaded = AddonTestUtils.promiseAddonEvent("onInstalled"); const promiseRestarted = AddonTestUtils.promiseWebExtensionStartup(addonId); await sendRequest(transport, { to: installedAddon.actor, type: "reload", }); await Promise.all([promiseReloaded, promiseRestarted]); // uninstallAddon const promiseUninstalled = new Promise(resolve => { const listener = {}; listener.onUninstalled = uninstalledAddon => { if (uninstalledAddon.id == addonId) { AddonManager.removeAddonListener(listener); resolve(); } }; AddonManager.addAddonListener(listener); }); await sendRequest(transport, { to: getRootResponse.addonsActor, type: "uninstallAddon", addonId, }); await promiseUninstalled; ({ addons } = await sendRequest(transport, { to: "root", type: "listAddons", })); equal(addons.length, 0, "expected no add-on installed"); // Attempt to uninstall an add-on that is (no longer) installed. let error = await sendRequest(transport, { to: getRootResponse.addonsActor, type: "uninstallAddon", addonId, }); equal( error?.message, `Could not uninstall add-on "${addonId}"`, "expected error" ); // Attempt to uninstall a non-temporarily loaded extension, which we do not // allow at the moment. We start by loading an extension, then we call the // `uninstallAddon`. const id = "not-a-temporary@extension"; const extension = ExtensionTestUtils.loadExtension({ manifest: { browser_specific_settings: { gecko: { id } }, }, useAddonManager: "permanent", }); await extension.startup(); error = await sendRequest(transport, { to: getRootResponse.addonsActor, type: "uninstallAddon", addonId: id, }); equal(error?.message, `Could not uninstall add-on "${id}"`, "expected error"); await extension.unload(); transport.close(); });