tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 */