tor-browser

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

utils.js (7224B)


      1 function waitForState(worker, state, context) {
      2  return new Promise(resolve => {
      3    function onStateChange() {
      4      if (worker.state === state) {
      5        worker.removeEventListener("statechange", onStateChange);
      6        resolve(context);
      7      }
      8    }
      9 
     10    // First add an event listener, so we won't miss any change that happens
     11    // before we check the current state.
     12    worker.addEventListener("statechange", onStateChange);
     13 
     14    // Now check if the worker is already in the desired state.
     15    onStateChange();
     16  });
     17 }
     18 
     19 /**
     20 * Helper for browser tests to issue register calls from the content global and
     21 * wait for the SW to progress to the active state, as most tests desire.
     22 * From the ContentTask.spawn, use via
     23 * `content.wrappedJSObject.registerAndWaitForActive`.
     24 */
     25 async function registerAndWaitForActive(script, maybeScope) {
     26  console.log("...calling register");
     27  let opts = undefined;
     28  if (maybeScope) {
     29    opts = { scope: maybeScope };
     30  }
     31  const reg = await navigator.serviceWorker.register(script, opts);
     32  // Unless registration resurrection happens, the SW should be in the
     33  // installing slot.
     34  console.log("...waiting for activation");
     35  await waitForState(reg.installing, "activated", reg);
     36  console.log("...activated!");
     37  return reg;
     38 }
     39 
     40 /**
     41 * Helper to create an iframe with the given URL and return the first
     42 * postMessage payload received.  This is intended to be used when creating
     43 * cross-origin iframes.
     44 *
     45 * A promise will be returned that resolves with the payload of the postMessage
     46 * call.
     47 */
     48 function createIframeAndWaitForMessage(url) {
     49  const iframe = document.createElement("iframe");
     50  document.body.appendChild(iframe);
     51  return new Promise(resolve => {
     52    window.addEventListener(
     53      "message",
     54      event => {
     55        resolve(event.data);
     56      },
     57      { once: true }
     58    );
     59    iframe.src = url;
     60  });
     61 }
     62 
     63 /**
     64 * Helper to create a nested iframe into the iframe created by
     65 * createIframeAndWaitForMessage().
     66 *
     67 * A promise will be returned that resolves with the payload of the postMessage
     68 * call.
     69 */
     70 function createNestedIframeAndWaitForMessage(url) {
     71  const iframe = document.getElementsByTagName("iframe")[0];
     72  iframe.contentWindow.postMessage("create nested iframe", "*");
     73  return new Promise(resolve => {
     74    window.addEventListener(
     75      "message",
     76      event => {
     77        resolve(event.data);
     78      },
     79      { once: true }
     80    );
     81  });
     82 }
     83 
     84 async function unregisterAll() {
     85  const registrations = await navigator.serviceWorker.getRegistrations();
     86  for (const reg of registrations) {
     87    await reg.unregister();
     88  }
     89 }
     90 
     91 /**
     92 * Make a blob that contains random data and therefore shouldn't compress all
     93 * that well.
     94 */
     95 function makeRandomBlob(size) {
     96  const arr = new Uint8Array(size);
     97  let offset = 0;
     98  /**
     99   * getRandomValues will only provide a maximum of 64k of data at a time and
    100   * will error if we ask for more, so using a while loop for get a random value
    101   * which much larger than 64k.
    102   * https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#exceptions
    103   */
    104  while (offset < size) {
    105    const nextSize = Math.min(size - offset, 65536);
    106    window.crypto.getRandomValues(new Uint8Array(arr.buffer, offset, nextSize));
    107    offset += nextSize;
    108  }
    109  return new Blob([arr], { type: "application/octet-stream" });
    110 }
    111 
    112 async function fillStorage(cacheBytes, idbBytes) {
    113  // ## Fill Cache API Storage
    114  const cache = await caches.open("filler");
    115  await cache.put("fill", new Response(makeRandomBlob(cacheBytes)));
    116 
    117  // ## Fill IDB
    118  const storeName = "filler";
    119  let db = await new Promise((resolve, reject) => {
    120    let openReq = indexedDB.open("filler", 1);
    121    openReq.onerror = event => {
    122      reject(event.target.error);
    123    };
    124    openReq.onsuccess = event => {
    125      resolve(event.target.result);
    126    };
    127    openReq.onupgradeneeded = event => {
    128      const useDB = event.target.result;
    129      useDB.onerror = error => {
    130        reject(error);
    131      };
    132      const store = useDB.createObjectStore(storeName);
    133      store.put({ blob: makeRandomBlob(idbBytes) }, "filler-blob");
    134    };
    135  });
    136 }
    137 
    138 const messagingChannels = {};
    139 
    140 // This method should ideally be called during a setup phase of the test to make
    141 // sure our BroadcastChannel is fully connected before anything that could cause
    142 // something to send a message to the channel can happen.  Because IPC ordering
    143 // is more predictable these days (single channel per process pair), this is
    144 // primarily an issue of:
    145 // - Helping you not have to worry about there being a race here at all.
    146 // - Potentially be able to refactor this to run on the WPT infrastructure in
    147 //   the future which likely cannot provide the same ordering guarantees.
    148 function setupMessagingChannel(name) {
    149  if (messagingChannels[name]) {
    150    return;
    151  }
    152 
    153  messagingChannels[name] = new BroadcastChannel(name);
    154 }
    155 
    156 function waitForBroadcastMessage(channelName, messageToWaitFor) {
    157  if (!messagingChannels[channelName]) {
    158    throw new Error(`You forgot to call setupMessagingChannel(${channelName})`);
    159  }
    160  return new Promise((resolve, reject) => {
    161    const channel = messagingChannels[channelName];
    162    const listener = evt => {
    163      // Add `--setpref="devtools.console.stdout.content=true"` to your mach
    164      // invocation to get this to stdout for extra debugging.
    165      console.log("Helper seeing message", evt.data, "on channel", channelName);
    166      if (evt.data === messageToWaitFor) {
    167        resolve();
    168        channel.removeEventListener("message", listener);
    169      } else if (evt.data?.error) {
    170        // Anything reporting an error means we should fail fast.
    171        reject(evt.data);
    172        channel.removeEventListener("message", listener);
    173      }
    174    };
    175    channel.addEventListener("message", listener);
    176  });
    177 }
    178 
    179 async function postMessageScopeAndWaitFor(
    180  channelName,
    181  scope,
    182  messageToSend,
    183  messageToWaitFor
    184 ) {
    185  // This will throw for us if the channel does not exist.
    186  const waitPromise = waitForBroadcastMessage(channelName, messageToWaitFor);
    187  const channel = messagingChannels[channelName];
    188 
    189  const reg = await navigator.serviceWorker.getRegistration(scope);
    190  if (!reg) {
    191    throw new Error(`Unable to find registration for scope: ${scope}`);
    192  }
    193  if (!reg.active) {
    194    throw new Error(`There is no active SW on the reg for scope: ${scope}`);
    195  }
    196  reg.active.postMessage(messageToSend);
    197 
    198  await waitPromise;
    199 }
    200 
    201 async function broadcastAndWaitFor(
    202  channelName,
    203  messageToBroadcast,
    204  messageToWaitFor
    205 ) {
    206  // This will throw for us if the channel does not exist.
    207  const waitPromise = waitForBroadcastMessage(channelName, messageToWaitFor);
    208  const channel = messagingChannels[channelName];
    209 
    210  channel.postMessage(messageToBroadcast);
    211 
    212  await waitPromise;
    213 }
    214 
    215 async function updateScopeAndWaitFor(channelName, scope, messageToWaitFor) {
    216  // This will throw for us if the channel does not exist.
    217  const waitPromise = waitForBroadcastMessage(channelName, messageToWaitFor);
    218  const channel = messagingChannels[channelName];
    219 
    220  const reg = await navigator.serviceWorker.getRegistration(scope);
    221  if (!reg) {
    222    throw new Error(`Unable to find registration for scope: ${scope}`);
    223  }
    224  reg.update();
    225 
    226  await waitPromise;
    227 }