helper-addons.js (8445B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /* import-globals-from head.js */ 7 8 function _getSupportsFile(path) { 9 const cr = Cc["@mozilla.org/chrome/chrome-registry;1"].getService( 10 Ci.nsIChromeRegistry 11 ); 12 const uri = Services.io.newURI(CHROME_URL_ROOT + path); 13 const fileurl = cr.convertChromeURL(uri); 14 return fileurl.QueryInterface(Ci.nsIFileURL); 15 } 16 17 async function enableExtensionDebugging() { 18 // Disable security prompt 19 await pushPref("devtools.debugger.prompt-connection", false); 20 } 21 /* exported enableExtensionDebugging */ 22 23 /** 24 * Install an extension using the AddonManager so it does not show up as temporary. 25 */ 26 async function installRegularExtension(pathOrFile) { 27 const isFile = typeof pathOrFile.isFile === "function" && pathOrFile.isFile(); 28 const file = isFile ? pathOrFile : _getSupportsFile(pathOrFile).file; 29 const install = await AddonManager.getInstallForFile(file); 30 return new Promise((resolve, reject) => { 31 if (!install) { 32 throw new Error(`An install was not created for ${file.path}`); 33 } 34 install.addListener({ 35 onDownloadFailed: reject, 36 onDownloadCancelled: reject, 37 onInstallFailed: reject, 38 onInstallCancelled: reject, 39 onInstallEnded: resolve, 40 }); 41 install.install(); 42 }); 43 } 44 /* exported installRegularExtension */ 45 46 /** 47 * Install a temporary extension at the provided path, with the provided name. 48 * Will use a mock file picker to select the file. 49 */ 50 async function installTemporaryExtension(pathOrFile, name, document) { 51 const { Management } = ChromeUtils.importESModule( 52 "resource://gre/modules/Extension.sys.mjs" 53 ); 54 55 info("Install temporary extension named " + name); 56 // Mock the file picker to select a test addon 57 prepareMockFilePicker(pathOrFile); 58 59 const onAddonInstalled = new Promise(done => { 60 Management.on("startup", function listener(event, extension) { 61 if (extension.name != name) { 62 return; 63 } 64 65 Management.off("startup", listener); 66 done(extension); 67 }); 68 }); 69 70 // Trigger the file picker by clicking on the button 71 document.querySelector(".qa-temporary-extension-install-button").click(); 72 73 info("Wait for addon to be installed"); 74 return onAddonInstalled; 75 } 76 /* exported installTemporaryExtension */ 77 78 function createTemporaryXPI(xpiData) { 79 const { ExtensionTestCommon } = ChromeUtils.importESModule( 80 "resource://testing-common/ExtensionTestCommon.sys.mjs" 81 ); 82 83 const { background, files, id, name, extraProperties } = xpiData; 84 info("Generate XPI file for " + id); 85 86 const manifest = Object.assign( 87 {}, 88 { 89 browser_specific_settings: { gecko: { id } }, 90 manifest_version: 2, 91 name, 92 version: "1.0", 93 }, 94 extraProperties 95 ); 96 97 const xpiFile = ExtensionTestCommon.generateXPI({ 98 background, 99 files, 100 manifest, 101 }); 102 registerCleanupFunction(() => xpiFile.exists() && xpiFile.remove(false)); 103 return xpiFile; 104 } 105 /* exported createTemporaryXPI */ 106 107 /** 108 * Remove the existing temporary XPI file generated by ExtensionTestCommon and create a 109 * new one at the same location. 110 * 111 * @return {File} the temporary extension XPI file created 112 */ 113 function updateTemporaryXPI(xpiData, existingXPI) { 114 info("Delete and regenerate XPI for " + xpiData.id); 115 116 // Store the current name to check the xpi is correctly replaced. 117 const existingName = existingXPI.leafName; 118 info("Delete existing XPI named: " + existingName); 119 existingXPI.exists() && existingXPI.remove(false); 120 121 const xpiFile = createTemporaryXPI(xpiData); 122 // Check that the name of the new file is correct 123 if (xpiFile.leafName !== existingName) { 124 throw new Error( 125 "New XPI created with unexpected name: " + xpiFile.leafName 126 ); 127 } 128 return xpiFile; 129 } 130 /* exported updateTemporaryXPI */ 131 132 /** 133 * Install a fake temporary extension by creating a temporary in-memory XPI file. 134 * 135 * @return {File} the temporary extension XPI file created 136 */ 137 async function installTemporaryExtensionFromXPI(xpiData, document) { 138 const xpiFile = createTemporaryXPI(xpiData); 139 const extension = await installTemporaryExtension( 140 xpiFile, 141 xpiData.name, 142 document 143 ); 144 145 info("Wait until the addon debug target appears"); 146 await waitUntil(() => findDebugTargetByText(xpiData.name, document)); 147 return { extension, xpiFile }; 148 } 149 /* exported installTemporaryExtensionFromXPI */ 150 151 async function removeTemporaryExtension(name, document) { 152 info(`Wait for removable extension with name: '${name}'`); 153 const buttonName = ".qa-temporary-extension-remove-button"; 154 await waitUntil(() => { 155 const extension = findDebugTargetByText(name, document); 156 return extension && extension.querySelector(buttonName); 157 }); 158 info(`Remove the temporary extension with name: '${name}'`); 159 const temporaryExtensionItem = findDebugTargetByText(name, document); 160 temporaryExtensionItem.querySelector(buttonName).click(); 161 162 info("Wait until the debug target item disappears"); 163 await waitUntil(() => !findDebugTargetByText(name, document)); 164 } 165 /* exported removeTemporaryExtension */ 166 167 async function removeExtension(id, name, document) { 168 info( 169 "Retrieve the extension instance from the addon manager, and uninstall it" 170 ); 171 const extension = await AddonManager.getAddonByID(id); 172 extension.uninstall(); 173 174 info("Wait until the addon disappears from about:debugging"); 175 await waitUntil(() => !findDebugTargetByText(name, document)); 176 } 177 /* exported removeExtension */ 178 179 function prepareMockFilePicker(pathOrFile) { 180 const isFile = typeof pathOrFile.isFile === "function" && pathOrFile.isFile(); 181 const file = isFile ? pathOrFile : _getSupportsFile(pathOrFile).file; 182 183 // Mock the file picker to select a test addon 184 const MockFilePicker = SpecialPowers.MockFilePicker; 185 MockFilePicker.init(window.browsingContext); 186 MockFilePicker.setFiles([file]); 187 } 188 /* exported prepareMockFilePicker */ 189 190 function promiseBackgroundContextEvent(extensionId, eventName) { 191 const { Management } = ChromeUtils.importESModule( 192 "resource://gre/modules/Extension.sys.mjs" 193 ); 194 195 return new Promise(resolve => { 196 Management.on(eventName, function listener(_evtName, context) { 197 if (context.extension.id === extensionId) { 198 Management.off(eventName, listener); 199 resolve(); 200 } 201 }); 202 }); 203 } 204 205 function promiseBackgroundContextLoaded(extensionId) { 206 return promiseBackgroundContextEvent(extensionId, "proxy-context-load"); 207 } 208 /* exported promiseBackgroundContextLoaded */ 209 210 function promiseBackgroundContextUnloaded(extensionId) { 211 return promiseBackgroundContextEvent(extensionId, "proxy-context-unload"); 212 } 213 /* exported promiseBackgroundContextUnloaded */ 214 215 async function assertBackgroundStatus( 216 extName, 217 { document, expectedStatus, targetElement } 218 ) { 219 const target = targetElement || findDebugTargetByText(extName, document); 220 const getBackgroundStatusElement = () => 221 target.querySelector(".extension-backgroundscript__status"); 222 await waitFor( 223 () => 224 getBackgroundStatusElement()?.classList.contains( 225 `extension-backgroundscript__status--${expectedStatus}` 226 ), 227 `Wait ${extName} Background script status "${expectedStatus}" to be rendered` 228 ); 229 } 230 /* exported assertBackgroundStatus */ 231 232 function getExtensionInstance(extensionId) { 233 const policy = WebExtensionPolicy.getByID(extensionId); 234 ok(policy, `Got a WebExtensionPolicy instance for ${extensionId}`); 235 ok(policy.extension, `Got an Extension class instance for ${extensionId}`); 236 return policy.extension; 237 } 238 /* exported getExtensionInstance */ 239 240 async function triggerExtensionEventPageIdleTimeout(extensionId) { 241 await getExtensionInstance(extensionId).terminateBackground(); 242 } 243 /* exported triggerExtensionEventPageIdleTimeout */ 244 245 async function wakeupExtensionEventPage(extensionId) { 246 await getExtensionInstance(extensionId).wakeupBackground(); 247 } 248 /* exported wakeupExtensionEventPage */ 249 250 function promiseTerminateBackgroundScriptIgnored(extensionId) { 251 const extension = getExtensionInstance(extensionId); 252 return new Promise(resolve => { 253 extension.once("background-script-suspend-ignored", resolve); 254 }); 255 } 256 /* exported promiseTerminateBackgroundScriptIgnored */ 257 258 async function promiseBackgroundStatusUpdate(window) { 259 waitForDispatch( 260 window.AboutDebugging.store, 261 "EXTENSION_BGSCRIPT_STATUS_UPDATED" 262 ); 263 } 264 /* exported promiseBackgroundStatusUpdate */