tor-browser

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

test_structuredCloneAndExposed.html (8362B)


      1 <!DOCTYPE html>
      2 <html>
      3 <!--
      4 https://bugzilla.mozilla.org/show_bug.cgi?id=1828264
      5 -->
      6 <head>
      7  <title>Basic structured clone tests</title>
      8  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      9  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
     10 </head>
     11 <body>
     12 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1828264">Mozilla Bug 1828264</a>
     13 <p id="display">
     14 <iframe id="frame"></iframe>
     15 <image href="four-colors.png" height="320" width="240"/>
     16 </p>
     17 <div id="content" style="display: none"></div>
     18 
     19 
     20 <pre id="test">
     21 <script class="testbody" type="application/javascript">
     22 
     23 SimpleTest.waitForExplicitFinish();
     24 
     25 class WebIDLGlobal {
     26  globalNames;
     27  resolve;
     28  #otherSide;
     29 
     30  constructor(globalNames, initSides) {
     31    this.globalNames = globalNames;
     32    let global = this;
     33    this.#otherSide = initSides().then(([ourSide, otherSide, cleanup]) => {
     34      if (cleanup) {
     35        SimpleTest.registerCleanupFunction(cleanup);
     36      }
     37      ourSide.addEventListener("message", ({ data }) => {
     38        global.resultReceived(data);
     39      });
     40      return otherSide;
     41    });
     42  }
     43 
     44  get otherSide() {
     45    return this.#otherSide;
     46  }
     47 
     48  waitForResult() {
     49    ok(!this.resolve, "Still waiting on previous message?");
     50    return new Promise(resolve => {
     51      this.resolve = resolve;
     52    });
     53  }
     54  resultReceived(value) {
     55    this.resolve(value);
     56    this.resolve = undefined;
     57  }
     58 }
     59 
     60 // Init functions return an array containing in order the object to use for
     61 // sending a messages to the other global, the object to use to listen for
     62 // messages from the other global and optioanlly a cleanup function.
     63 
     64 function waitForInitMessage(target) {
     65  return new Promise(resolve => {
     66    target.addEventListener("message", resolve, { once: true });
     67  });
     68 }
     69 
     70 function getModuleURL(additionalScript = "") {
     71  return new URL(
     72    `file_structuredCloneAndExposed.sjs?additionalScript=${encodeURIComponent(
     73      additionalScript
     74    )}`,
     75    location.href
     76  );
     77 }
     78 
     79 function initFrame() {
     80  let callInstallListeners = `
     81    installListeners(globalThis, parent);
     82    `;
     83  frame.srcdoc =
     84    `<script src="${getModuleURL(callInstallListeners)}" type="module"></` +
     85    `script>`;
     86  return waitForInitMessage(self).then(() => [self, frame.contentWindow]);
     87 }
     88 
     89 function initDedicatedWorker() {
     90  let callInstallListeners = `
     91    installListeners(globalThis, globalThis);
     92    `;
     93  const worker = new Worker(getModuleURL(callInstallListeners), {
     94    type: "module",
     95  });
     96  return waitForInitMessage(worker).then(() => [worker, worker]);
     97 }
     98 
     99 function initSharedWorker() {
    100  let callInstallListeners = `
    101    self.addEventListener("connect", (e) => {
    102      const port = e.ports[0];
    103      port.start();
    104      installListeners(port, port);
    105    });
    106    `;
    107  const worker = new SharedWorker(getModuleURL(callInstallListeners), {
    108    type: "module",
    109  });
    110  worker.port.start();
    111  return waitForInitMessage(worker.port).then(() => [worker.port, worker.port]);
    112 }
    113 
    114 function initServiceWorker() {
    115  let callInstallListeners = `
    116    addEventListener("message", ({ source: client }) => {
    117      installListeners(globalThis, client);
    118    }, { once: true });
    119    `;
    120 
    121  return navigator.serviceWorker
    122    .register(getModuleURL(callInstallListeners), { type: "module" })
    123    .then(registration => {
    124      let worker =
    125        registration.installing || registration.waiting || registration.active;
    126      worker.postMessage("installListeners");
    127      return waitForInitMessage(navigator.serviceWorker).then(() => [
    128        navigator.serviceWorker,
    129        worker,
    130        () => registration.unregister(),
    131      ]);
    132    });
    133 }
    134 
    135 function initAudioWorklet() {
    136  const exporter = `
    137    export { installListeners };
    138  `;
    139 
    140  const workletSource = `
    141    import { installListeners } from "${getModuleURL(exporter)}";
    142 
    143    class CustomAudioWorkletProcessor extends AudioWorkletProcessor {
    144      constructor() {
    145        super();
    146        this.port.start();
    147        installListeners(this.port, this.port, "AudioWorklet");
    148      }
    149 
    150      process(inputs, outputs, params) {
    151        // Do nothing, output silence
    152      }
    153    }
    154 
    155    registerProcessor("custom-audio-worklet-processor", CustomAudioWorkletProcessor);
    156    `;
    157  let workletURL = URL.createObjectURL(
    158    new Blob([workletSource], { type: "text/javascript" })
    159  );
    160 
    161  // We need to keep the context alive while we're using the message ports.
    162  globalThis.context = new OfflineAudioContext(1, 64, 8000);
    163  return context.audioWorklet.addModule(workletURL).then(() => {
    164    let node = new AudioWorkletNode(context, "custom-audio-worklet-processor");
    165    node.port.start();
    166    return waitForInitMessage(node.port).then(() => [
    167      node.port,
    168      node.port,
    169      () => {
    170        node.port.close();
    171        delete globalThis.context;
    172      },
    173    ]);
    174  });
    175 }
    176 
    177 const globals = [
    178  ["Window", ["Window"], initFrame],
    179  [
    180    "DedicatedWorkerGlobalScope",
    181    ["Worker", "DedicatedWorker"],
    182    initDedicatedWorker,
    183  ],
    184  ["SharedWorkerGlobalScope", ["Worker", "SharedWorker"], initSharedWorker],
    185  ["ServiceWorkerGlobalScope", ["Worker", "ServiceWorker"], initServiceWorker],
    186  // WorkerDebuggerGlobalScope can only receive string messages.
    187  ["AudioWorkletGlobalScope", ["Worklet", "AudioWorklet"], initAudioWorklet],
    188  // PaintWorkletGlobalScope doesn't have a messaging mechanism
    189 ].map(([global, globalNames, init]) => [
    190  global,
    191  new WebIDLGlobal(globalNames, init),
    192 ]);
    193 
    194 
    195 function generateCertificate() {
    196  return RTCPeerConnection.generateCertificate({
    197    name: "ECDSA",
    198    namedCurve: "P-256",
    199  });
    200 }
    201 
    202 function makeCanvas() {
    203  const width = 20;
    204  const height = 20;
    205  let canvas = new OffscreenCanvas(width, height);
    206  let ctx = canvas.getContext("2d");
    207  ctx.fillStyle = "rgba(50, 100, 150, 255)";
    208  ctx.fillRect(0, 0, width, height);
    209  return canvas;
    210 }
    211 
    212 function makeVideoFrame() {
    213  return new VideoFrame(makeCanvas(), { timestamp: 1, alpha: "discard" });
    214 }
    215 
    216 function makeImageBitmap() {
    217  return makeCanvas().transferToImageBitmap();
    218 }
    219 
    220 const serializable = [
    221  ["DOMException", ["Window", "Worker"], () => new DOMException()],
    222  ["DOMMatrixReadOnly", ["Window", "Worker"], () => new DOMMatrixReadOnly()],
    223  ["ImageBitmap", ["Window", "Worker"], makeImageBitmap],
    224  ["RTCCertificate", ["Window"], generateCertificate],
    225  ["VideoFrame", ["Window", "DedicatedWorker"], makeVideoFrame],
    226 ];
    227 
    228 const transferable = [
    229  [
    230    "MessagePort",
    231    ["Window", "Worker", "AudioWorklet"],
    232    () => new MessageChannel().port1,
    233  ],
    234  ["ImageBitmap", ["Window", "Worker"], makeImageBitmap],
    235  ["ReadableStream", ["*"], () => new ReadableStream()],
    236  ["VideoFrame", ["Window", "DedicatedWorker"], makeVideoFrame],
    237 ];
    238 
    239 
    240 function isExposed(exposure, global) {
    241  if (exposure.length === 1 && exposure[0] === "*") {
    242    return true;
    243  }
    244  return !!global.globalNames.filter(v => exposure.includes(v)).length;
    245 }
    246 
    247 
    248 async function runTest() {
    249  async function testDOMClass(domClass, exposure, createObject, transferable) {
    250    for ([globalName, webidlGlobal] of globals) {
    251      info(
    252        `Testing ${
    253          transferable ? "transfer" : "serialization"
    254        } of ${domClass} with ${globalName}`
    255      );
    256 
    257      let object = await createObject();
    258      let otherSide = await webidlGlobal.otherSide;
    259      let options = { targetOrigin: "*" };
    260      let message;
    261      if (transferable) {
    262        options.transfer = [object];
    263      } else {
    264        message = object;
    265      }
    266      otherSide.postMessage(message, options);
    267 
    268      let result = await webidlGlobal.waitForResult();
    269      let expected = isExposed(exposure, webidlGlobal);
    270 
    271      if (
    272        domClass === "ImageBitmap" &&
    273        globalName === "ServiceWorkerGlobalScope"
    274      ) {
    275        // Service workers don't support transferring shared memory objects
    276        // (see ServiceWorker::PostMessage).
    277        expected = false;
    278      }
    279 
    280      is(
    281        result,
    282        expected,
    283        `Deserialization for ${domClass} in ${globalName} ${
    284          expected ? "should" : "shouldn't"
    285        } succeed`
    286      );
    287    }
    288  }
    289 
    290  for ([domClass, exposure, createObject] of serializable) {
    291    await testDOMClass(domClass, exposure, createObject, false);
    292  }
    293  for ([domClass, exposure, createObject] of transferable) {
    294    await testDOMClass(domClass, exposure, createObject, true);
    295  }
    296 
    297  SimpleTest.finish();
    298 }
    299 
    300 runTest();
    301 
    302 </script>
    303 </pre>
    304 </body>
    305 </html>