tor-browser

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

SpecialPowersProcessActor.sys.mjs (6069B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  SpecialPowersSandbox:
      9    "resource://testing-common/SpecialPowersSandbox.sys.mjs",
     10 });
     11 
     12 let nextSpfpId = 1;
     13 
     14 /**
     15 * SpecialPowersForProcess wraps a content process, and allows the caller to
     16 * spawn() tasks like SpecialPowers.spawn() and contentPage.spawn(), including
     17 * Assert functionality. Assertion messages are passed back to the test scope,
     18 * which must be passed along with the process to the constructor.
     19 */
     20 export class SpecialPowersForProcess {
     21  static instances = new Map();
     22 
     23  /**
     24   * Create a new SpecialPowersForProcess that enables callers to spawn tasks
     25   * in the given content process.
     26   *
     27   * @param {any} scope
     28   *        The test scope to receive assertion messages.
     29   *        In test files this is often equivalent to globalThis.
     30   * @param {nsIDOMProcessParent} domProcess
     31   *        The content process where the spawned code should run.
     32   */
     33  constructor(testScope, domProcess) {
     34    this.isXpcshellScope = !!testScope.do_get_profile;
     35    this.isSimpleTestScope = !this.isXpcshellScope && !!testScope.SimpleTest;
     36    if (!this.isXpcshellScope && !this.isSimpleTestScope) {
     37      // Must be global of xpcshell test, or global of browser chrome mochitest.
     38      throw new Error("testScope cannot receive assertion messages!");
     39    }
     40 
     41    if (!(domProcess instanceof Ci.nsIDOMProcessParent)) {
     42      throw new Error("domProcess is not a nsIDOMProcessParent!");
     43    }
     44 
     45    // This actor is registered via SpecialPowersParent.registerActor().
     46    // In mochitests that is part of the SpecialPowers add-on initialization.
     47    // Xpcshell tests can initialize it with XPCShellContentUtils.init(), via
     48    // XPCShellContentUtils.ensureInitialized().
     49    this.actor = domProcess.getActor("SpecialPowersProcessActor");
     50 
     51    this.testScope = testScope;
     52 
     53    this.spfpId = nextSpfpId++;
     54    SpecialPowersForProcess.instances.set(this.spfpId, this);
     55 
     56    testScope.registerCleanupFunction(() => this.destroy());
     57  }
     58 
     59  destroy() {
     60    if (!this.testScope) {
     61      // Already destroyed.
     62      return;
     63    }
     64    SpecialPowersForProcess.instances.delete(this.spfpId);
     65    this.actor = null;
     66    this.testScope = null;
     67  }
     68 
     69  /**
     70   * Like `SpecialPowers.spawn`, but spawns a task in a child process instead.
     71   * The task has access to Assert.
     72   *
     73   * @param {Array<any>} args
     74   *        An array of arguments to pass to the task. All arguments
     75   *        must be structured clone compatible, and will be cloned
     76   *        before being passed to the task.
     77   * @param {function} task
     78   *        The function to run in the context of the target process. The
     79   *        function will be stringified and re-evaluated in the context
     80   *        of the target's content process. It may return any structured
     81   *        clone compatible value, or a Promise which resolves to the
     82   *        same, which will be returned to the caller.
     83   * @returns {Promise<any>}
     84   *        A promise which resolves to the return value of the task, or
     85   *        which rejects if the task raises an exception. As this is
     86   *        being written, the rejection value will always be undefined
     87   *        in the cases where the task throws an error, though that may
     88   *        change in the future.
     89   */
     90  spawn(args, task) {
     91    return this.actor.sendQuery("Spawn", {
     92      args,
     93      task: String(task),
     94      caller: Cu.getFunctionSourceLocation(task),
     95      spfpId: this.spfpId,
     96    });
     97  }
     98 
     99  // Called when ProxiedAssert is received; this data is sent from
    100  // SpecialPowersSandbox's reportCallback in response to assertion messages.
    101  reportCallback(data) {
    102    if ("info" in data) {
    103      if (this.isXpcshellScope) {
    104        this.testScope.info(data.info);
    105      } else if (this.isSimpleTestScope) {
    106        this.testScope.SimpleTest.info(data.info);
    107      } else {
    108        // We checked this in the constructor, so this is unexpected.
    109        throw new Error(`testScope cannot receive assertion messages!?!`);
    110      }
    111      return;
    112    }
    113 
    114    const { name, diag, passed, stack, expectFail } = data;
    115    if (this.isXpcshellScope) {
    116      this.testScope.do_report_result(passed, name, stack);
    117    } else if (this.isSimpleTestScope) {
    118      // browser chrome mochitest
    119      let expected = expectFail ? "fail" : "pass";
    120      this.testScope.SimpleTest.record(passed, name, diag, stack, expected);
    121    } else {
    122      // We checked this in the constructor, so this is unexpected.
    123      throw new Error(`testScope cannot receive assertion messages!?!`);
    124    }
    125  }
    126 }
    127 
    128 // A minimal process actor that allows spawn() to run in the given process.
    129 export class SpecialPowersProcessActorParent extends JSProcessActorParent {
    130  receiveMessage(aMessage) {
    131    switch (aMessage.name) {
    132      case "ProxiedAssert": {
    133        const { spfpId, data } = aMessage.data;
    134        const spfp = SpecialPowersForProcess.instances.get(spfpId);
    135        if (!spfp) {
    136          dump(`Unexpected ProxiedAssert: ${uneval(data)}\n`);
    137          throw new Error(`Unexpected message for ${spfpId} `);
    138        }
    139        spfp.reportCallback(data);
    140        return undefined;
    141      }
    142      default:
    143        throw new Error(
    144          `Unknown SpecialPowersProcessActorParent action: ${aMessage.name}`
    145        );
    146    }
    147  }
    148 }
    149 
    150 export class SpecialPowersProcessActorChild extends JSProcessActorChild {
    151  receiveMessage(aMessage) {
    152    switch (aMessage.name) {
    153      case "Spawn": {
    154        return this._spawnTaskInChild(aMessage.data);
    155      }
    156      default:
    157        throw new Error(
    158          `Unknown SpecialPowersProcessActorChild action: ${aMessage.name}`
    159        );
    160    }
    161  }
    162 
    163  _spawnTaskInChild({ task, args, caller, spfpId }) {
    164    let sb = new lazy.SpecialPowersSandbox(null, data => {
    165      this.sendAsyncMessage("ProxiedAssert", { spfpId, data });
    166    });
    167 
    168    return sb.execute(task, args, caller);
    169  }
    170 }