test_webext_apis.js (5347B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { AddonManager } = ChromeUtils.importESModule( 7 "resource://gre/modules/AddonManager.sys.mjs" 8 ); 9 const { ExtensionTestUtils } = ChromeUtils.importESModule( 10 "resource://testing-common/ExtensionXPCShellUtils.sys.mjs" 11 ); 12 13 const DistinctDevToolsServer = getDistinctDevToolsServer(); 14 ExtensionTestUtils.init(this); 15 16 add_setup(async () => { 17 Services.prefs.setBoolPref("extensions.blocklist.enabled", false); 18 await startupAddonsManager(); 19 }); 20 21 // Basic request wrapper that sends a request and resolves on the next packet. 22 // Will only work for very basic scenarios, without events emitted on the server 23 // etc... 24 async function sendRequest(transport, request) { 25 return new Promise(resolve => { 26 transport.hooks = { 27 onPacket: packet => { 28 dump(`received packet: ${JSON.stringify(packet)}\n`); 29 // Let's resolve only when we get a packet that is related to our 30 // request. It is needed because some methods do not return the correct 31 // response right away. This is the case of the `reload` method, which 32 // receives a `addonListChanged` message first and then a `reload` 33 // message. 34 if (packet.from === request.to) { 35 resolve(packet); 36 } 37 }, 38 }; 39 transport.send(request); 40 }); 41 } 42 43 // If this test case fails, please reach out to webext peers because 44 // https://github.com/mozilla/web-ext relies on the APIs tested here. 45 add_task(async function test_webext_run_apis() { 46 DistinctDevToolsServer.init(); 47 DistinctDevToolsServer.registerAllActors(); 48 49 const transport = DistinctDevToolsServer.connectPipe(); 50 51 // After calling connectPipe, the root actor will be created on the server 52 // and a packet will be emitted after a tick. Wait for the initial packet. 53 await new Promise(resolve => { 54 transport.hooks = { onPacket: resolve }; 55 }); 56 57 const getRootResponse = await sendRequest(transport, { 58 to: "root", 59 type: "getRoot", 60 }); 61 62 ok(getRootResponse, "received a response after calling RootActor::getRoot"); 63 ok(getRootResponse.addonsActor, "getRoot returned an addonsActor id"); 64 65 // installTemporaryAddon 66 const addonId = "test-addons-actor@mozilla.org"; 67 const addonPath = getFilePath("addons/web-extension", false, true); 68 const promiseStarted = AddonTestUtils.promiseWebExtensionStartup(addonId); 69 const { addon } = await sendRequest(transport, { 70 to: getRootResponse.addonsActor, 71 type: "installTemporaryAddon", 72 addonPath, 73 // The openDevTools parameter is not always passed by web-ext. This test 74 // omits it, to make sure that the request without the flag is accepted. 75 // openDevTools: false, 76 }); 77 await promiseStarted; 78 79 ok(addon, "addonsActor allows to install a temporary add-on"); 80 equal(addon.id, addonId, "temporary add-on is the expected one"); 81 equal(addon.actor, false, "temporary add-on does not have an actor"); 82 83 // listAddons 84 let { addons } = await sendRequest(transport, { 85 to: "root", 86 type: "listAddons", 87 }); 88 ok(Array.isArray(addons), "listAddons() returns a list of add-ons"); 89 equal(addons.length, 1, "expected an add-on installed"); 90 91 const installedAddon = addons[0]; 92 equal(installedAddon.id, addonId, "installed add-on is the expected one"); 93 ok(installedAddon.actor, "returned add-on has an actor"); 94 95 // reload 96 const promiseReloaded = AddonTestUtils.promiseAddonEvent("onInstalled"); 97 const promiseRestarted = AddonTestUtils.promiseWebExtensionStartup(addonId); 98 await sendRequest(transport, { 99 to: installedAddon.actor, 100 type: "reload", 101 }); 102 await Promise.all([promiseReloaded, promiseRestarted]); 103 104 // uninstallAddon 105 const promiseUninstalled = new Promise(resolve => { 106 const listener = {}; 107 listener.onUninstalled = uninstalledAddon => { 108 if (uninstalledAddon.id == addonId) { 109 AddonManager.removeAddonListener(listener); 110 resolve(); 111 } 112 }; 113 AddonManager.addAddonListener(listener); 114 }); 115 await sendRequest(transport, { 116 to: getRootResponse.addonsActor, 117 type: "uninstallAddon", 118 addonId, 119 }); 120 await promiseUninstalled; 121 122 ({ addons } = await sendRequest(transport, { 123 to: "root", 124 type: "listAddons", 125 })); 126 equal(addons.length, 0, "expected no add-on installed"); 127 128 // Attempt to uninstall an add-on that is (no longer) installed. 129 let error = await sendRequest(transport, { 130 to: getRootResponse.addonsActor, 131 type: "uninstallAddon", 132 addonId, 133 }); 134 equal( 135 error?.message, 136 `Could not uninstall add-on "${addonId}"`, 137 "expected error" 138 ); 139 140 // Attempt to uninstall a non-temporarily loaded extension, which we do not 141 // allow at the moment. We start by loading an extension, then we call the 142 // `uninstallAddon`. 143 const id = "not-a-temporary@extension"; 144 const extension = ExtensionTestUtils.loadExtension({ 145 manifest: { 146 browser_specific_settings: { gecko: { id } }, 147 }, 148 useAddonManager: "permanent", 149 }); 150 await extension.startup(); 151 152 error = await sendRequest(transport, { 153 to: getRootResponse.addonsActor, 154 type: "uninstallAddon", 155 addonId: id, 156 }); 157 equal(error?.message, `Could not uninstall add-on "${id}"`, "expected error"); 158 159 await extension.unload(); 160 161 transport.close(); 162 });