tor-browser

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

webextension-helpers.js (6180B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 /* globals browser */
      5 
      6 "use strict";
      7 
      8 /**
      9 * Test helpers shared by the devtools server xpcshell tests related to webextensions.
     10 */
     11 
     12 const { FileUtils } = ChromeUtils.importESModule(
     13  "resource://gre/modules/FileUtils.sys.mjs"
     14 );
     15 const { ExtensionTestUtils } = ChromeUtils.importESModule(
     16  "resource://testing-common/ExtensionXPCShellUtils.sys.mjs"
     17 );
     18 
     19 const {
     20  CommandsFactory,
     21 } = require("resource://devtools/shared/commands/commands-factory.js");
     22 
     23 /**
     24 * Loads and starts up a test extension given the provided extension configuration.
     25 *
     26 * @param {object} extConfig - The extension configuration object
     27 * @return {ExtensionWrapper} extension - Resolves with an extension object once the
     28 * extension has started up.
     29 */
     30 async function startupExtension(extConfig) {
     31  const extension = ExtensionTestUtils.loadExtension(extConfig);
     32 
     33  await extension.startup();
     34 
     35  return extension;
     36 }
     37 exports.startupExtension = startupExtension;
     38 
     39 /**
     40 * Initializes the extensionStorage actor for a given extension. This is effectively
     41 * what happens when the addon storage panel is opened in the browser.
     42 *
     43 * @param {string} - id, The addon id
     44 * @return {object} - Resolves with the DevTools "commands" objact and the extensionStorage
     45 * resource/front.
     46 */
     47 async function openAddonStoragePanel(id) {
     48  const commands = await CommandsFactory.forAddon(id);
     49  await commands.targetCommand.startListening();
     50 
     51  // Fetch the EXTENSION_STORAGE resource.
     52  // Unfortunately, we can't use resourceCommand.waitForNextResource as it would destroy
     53  // the actor by immediately unwatching for the resource type.
     54  const extensionStorage = await new Promise(resolve => {
     55    commands.resourceCommand.watchResources(
     56      [commands.resourceCommand.TYPES.EXTENSION_STORAGE],
     57      {
     58        onAvailable(resources) {
     59          resolve(resources[0]);
     60        },
     61      }
     62    );
     63  });
     64 
     65  return { commands, extensionStorage };
     66 }
     67 exports.openAddonStoragePanel = openAddonStoragePanel;
     68 
     69 /**
     70 * Builds the extension configuration object passed into ExtensionTestUtils.loadExtension
     71 *
     72 * @param {object} options - Options, if any, to add to the configuration
     73 * @param {Function} options.background - A function comprising the test extension's
     74 * background script if provided
     75 * @param {object} options.files - An object whose keys correspond to file names and
     76 * values map to the file contents
     77 * @param {object} options.manifest - An object representing the extension's manifest
     78 * @return {object} - The extension configuration object
     79 */
     80 function getExtensionConfig(options = {}) {
     81  const { manifest, ...otherOptions } = options;
     82  const baseConfig = {
     83    manifest: {
     84      ...manifest,
     85      permissions: ["storage"],
     86    },
     87    useAddonManager: "temporary",
     88  };
     89  return {
     90    ...baseConfig,
     91    ...otherOptions,
     92  };
     93 }
     94 exports.getExtensionConfig = getExtensionConfig;
     95 
     96 /**
     97 * Shared files for a test extension that has no background page but adds storage
     98 * items via a transient extension page in a tab
     99 */
    100 const ext_no_bg = {
    101  files: {
    102    "extension_page_in_tab.html": `<!DOCTYPE html>
    103      <html>
    104        <head>
    105          <meta charset="utf-8">
    106        </head>
    107        <body>
    108          <h1>Extension Page in a Tab</h1>
    109          <script src="extension_page_in_tab.js"></script>
    110        </body>
    111      </html>`,
    112    "extension_page_in_tab.js": extensionScriptWithMessageListener,
    113  },
    114 };
    115 exports.ext_no_bg = ext_no_bg;
    116 
    117 /**
    118 * An extension script that can be used in any extension context (e.g. as a background
    119 * script or as an extension page script loaded in a tab).
    120 */
    121 async function extensionScriptWithMessageListener() {
    122  let fireOnChanged = false;
    123  browser.storage.onChanged.addListener(() => {
    124    if (fireOnChanged) {
    125      // Do not fire it again until explicitly requested again using the "storage-local-fireOnChanged" test message.
    126      fireOnChanged = false;
    127      browser.test.sendMessage("storage-local-onChanged");
    128    }
    129  });
    130 
    131  browser.test.onMessage.addListener(async (msg, ...args) => {
    132    let item = null;
    133    switch (msg) {
    134      case "storage-local-set":
    135        await browser.storage.local.set(args[0]);
    136        break;
    137      case "storage-local-get":
    138        item = await browser.storage.local.get(args[0]);
    139        break;
    140      case "storage-local-remove":
    141        await browser.storage.local.remove(args[0]);
    142        break;
    143      case "storage-local-clear":
    144        await browser.storage.local.clear();
    145        break;
    146      case "storage-local-fireOnChanged": {
    147        // Allow the storage.onChanged listener to send a test event
    148        // message when onChanged is being fired.
    149        fireOnChanged = true;
    150        // Do not fire fireOnChanged:done.
    151        return;
    152      }
    153      default:
    154        browser.test.fail(`Unexpected test message: ${msg}`);
    155    }
    156 
    157    browser.test.sendMessage(`${msg}:done`, item);
    158  });
    159  // window is available in background scripts
    160  // eslint-disable-next-line no-undef
    161  browser.test.sendMessage("extension-origin", window.location.origin);
    162 }
    163 exports.extensionScriptWithMessageListener = extensionScriptWithMessageListener;
    164 
    165 /**
    166 * Shutdown procedure common to all tasks.
    167 *
    168 * @param {object} extension - The test extension
    169 * @param {object} commands - The web extension commands used by the DevTools to interact with the backend
    170 */
    171 async function shutdown(extension, commands) {
    172  if (commands) {
    173    await commands.destroy();
    174  }
    175  await extension.unload();
    176 }
    177 exports.shutdown = shutdown;
    178 
    179 /**
    180 * Mocks the missing 'storage/permanent' directory needed by the "indexedDB"
    181 * storage actor's 'populateStoresForHosts' method. This
    182 * directory exists in a full browser i.e. mochitest.
    183 */
    184 function createMissingIndexedDBDirs() {
    185  const dir = Services.dirsvc.get("ProfD", Ci.nsIFile).clone();
    186  dir.append("storage");
    187  if (!dir.exists()) {
    188    dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
    189  }
    190  dir.append("permanent");
    191  if (!dir.exists()) {
    192    dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
    193  }
    194 
    195  return dir;
    196 }
    197 exports.createMissingIndexedDBDirs = createMissingIndexedDBDirs;