tor-browser

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

testharness-shadowrealm-outer.js (5176B)


      1 // testharness file with ShadowRealm utilities to be imported in the realm
      2 // hosting the ShadowRealm
      3 
      4 /**
      5 * Convenience function for evaluating some async code in the ShadowRealm and
      6 * waiting for the result.
      7 *
      8 * In case of error, this function intentionally exposes the stack trace (if it
      9 * is available) to the hosting realm, for debugging purposes.
     10 *
     11 * @param {ShadowRealm} realm - the ShadowRealm to evaluate the code in
     12 * @param {string} asyncBody - the code to evaluate; will be put in the body of
     13 *   an async function, and must return a value explicitly if a value is to be
     14 *   returned to the hosting realm.
     15 */
     16 globalThis.shadowRealmEvalAsync = function (realm, asyncBody) {
     17  return new Promise(realm.evaluate(`
     18    (resolve, reject) => {
     19      (async () => {
     20        ${asyncBody}
     21      })().then(resolve, (e) => reject(e.toString() + "\\n" + (e.stack || "")));
     22    }
     23  `));
     24 };
     25 
     26 /**
     27 * Convenience adaptor function for fetch() that can be passed to
     28 * setShadowRealmGlobalProperties() (see testharness-shadowrealm-inner.js).
     29 * Used to adapt the hosting realm's fetch(), if present, to fetch a resource
     30 * and pass its text through the callable boundary to the ShadowRealm.
     31 */
     32 globalThis.fetchAdaptor = (resource) => (resolve, reject) => {
     33  fetch(resource)
     34    .then(res => res.text())
     35    .then(resolve, (e) => reject(e.toString()));
     36 };
     37 
     38 let workerMessagePortPromise;
     39 /**
     40 * Used when the hosting realm is a worker. This value is a Promise that
     41 * resolves to a function that posts a message to the worker's message port,
     42 * just like postMessage(). The message port is only available asynchronously in
     43 * SharedWorkers and ServiceWorkers.
     44 */
     45 globalThis.getPostMessageFunc = async function () {
     46  if (typeof postMessage === "function") {
     47    return postMessage;  // postMessage available directly in dedicated worker
     48  }
     49 
     50  if (workerMessagePortPromise) {
     51    return await workerMessagePortPromise;
     52  }
     53 
     54  throw new Error("getPostMessageFunc is intended for Worker scopes");
     55 }
     56 
     57 // Port available asynchronously in shared worker, but not via an async func
     58 let savedResolver;
     59 if (globalThis.constructor.name === "SharedWorkerGlobalScope") {
     60  workerMessagePortPromise = new Promise((resolve) => {
     61    savedResolver = resolve;
     62  });
     63  addEventListener("connect", function (event) {
     64    const port = event.ports[0];
     65    savedResolver(port.postMessage.bind(port));
     66  });
     67 } else if (globalThis.constructor.name === "ServiceWorkerGlobalScope") {
     68  workerMessagePortPromise = new Promise((resolve) => {
     69    savedResolver = resolve;
     70  });
     71  addEventListener("message", (e) => {
     72    if (typeof e.data === "object" && e.data !== null && e.data.type === "connect") {
     73      const client = e.source;
     74      savedResolver(client.postMessage.bind(client));
     75    }
     76  });
     77 }
     78 
     79 /**
     80 * Used when the hosting realm does not permit dynamic import, e.g. in
     81 * ServiceWorkers or AudioWorklets. Requires an adaptor function such as
     82 * fetchAdaptor() above, or an equivalent if fetch() is not present in the
     83 * hosting realm.
     84 *
     85 * @param {ShadowRealm} realm - the ShadowRealm in which to setup a
     86 *   fakeDynamicImport() global function.
     87 * @param {function} adaptor - an adaptor function that does what fetchAdaptor()
     88 *   does.
     89 */
     90 globalThis.setupFakeDynamicImportInShadowRealm = function(realm, adaptor) {
     91  function fetchModuleTextExecutor(url) {
     92    return (resolve, reject) => {
     93      new Promise(adaptor(url))
     94        .then(text => realm.evaluate(text + ";\nundefined"))
     95        .then(resolve, (e) => reject(e.toString()));
     96    }
     97  }
     98 
     99  realm.evaluate(`
    100    (fetchModuleTextExecutor) => {
    101      globalThis.fakeDynamicImport = function (url) {
    102        return new Promise(fetchModuleTextExecutor(url));
    103      }
    104    }
    105  `)(fetchModuleTextExecutor);
    106 };
    107 
    108 /**
    109 * Used when the hosting realm does not expose fetch(), i.e. in worklets. The
    110 * port on the other side of the channel needs to send messages starting with
    111 * 'fetchRequest::' and listen for messages starting with 'fetchResult::'. See
    112 * testharness-shadowrealm-audioworkletprocessor.js.
    113 *
    114 * @param {port} MessagePort - the message port on which to listen for fetch
    115 *   requests
    116 */
    117 globalThis.setupFakeFetchOverMessagePort = function (port) {
    118  port.addEventListener("message", (event) => {
    119    if (typeof event.data !== "string" || !event.data.startsWith("fetchRequest::")) {
    120      return;
    121    }
    122 
    123    fetch(event.data.slice("fetchRequest::".length))
    124      .then(res => res.text())
    125      .then(
    126        text => port.postMessage(`fetchResult::success::${text}`),
    127        error => port.postMessage(`fetchResult::fail::${error}`),
    128      );
    129  });
    130  port.start();
    131 }
    132 
    133 /**
    134 * Returns a message suitable for posting with postMessage() that will signal to
    135 * the test harness that the tests are finished and there was an error in the
    136 * setup code.
    137 *
    138 * @param {message} any - error
    139 */
    140 globalThis.createSetupErrorResult = function (message) {
    141  return {
    142    type: "complete",
    143    tests: [],
    144    asserts: [],
    145    status: {
    146      status: 1, // TestsStatus.ERROR,
    147      message: String(message),
    148      stack: typeof message === "object" && message !== null && "stack" in message ? message.stack : undefined,
    149    },
    150  };
    151 };