diff options
Diffstat (limited to 'devtools/server/tests/xpcshell/test_webext_apis.js')
-rw-r--r-- | devtools/server/tests/xpcshell/test_webext_apis.js | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/devtools/server/tests/xpcshell/test_webext_apis.js b/devtools/server/tests/xpcshell/test_webext_apis.js new file mode 100644 index 0000000000..5a2f2b990a --- /dev/null +++ b/devtools/server/tests/xpcshell/test_webext_apis.js @@ -0,0 +1,162 @@ +/* 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(); +}); |