tor-browser

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

realms.window.js (12105B)


      1 'use strict';
      2 
      3 // Test that objects created by the TextEncoderStream and TextDecoderStream APIs
      4 // are created in the correct realm. The tests work by creating an iframe for
      5 // each realm and then posting Javascript to them to be evaluated. Inputs and
      6 // outputs are passed around via global variables in each realm's scope.
      7 
      8 // Async setup is required before creating any tests, so require done() to be
      9 // called.
     10 setup({explicit_done: true});
     11 
     12 function createRealm() {
     13  let iframe = document.createElement('iframe');
     14  const scriptEndTag = '<' + '/script>';
     15  iframe.srcdoc = `<!doctype html>
     16 <script>
     17 onmessage = event => {
     18  if (event.source !== window.parent) {
     19    throw new Error('unexpected message with source ' + event.source);
     20  }
     21  eval(event.data);
     22 };
     23 ${scriptEndTag}`;
     24  iframe.style.display = 'none';
     25  document.body.appendChild(iframe);
     26  let realmPromiseResolve;
     27  const realmPromise = new Promise(resolve => {
     28    realmPromiseResolve = resolve;
     29  });
     30  iframe.onload = () => {
     31    realmPromiseResolve(iframe.contentWindow);
     32  };
     33  return realmPromise;
     34 }
     35 
     36 async function createRealms() {
     37  // All realms are visible on the global object so they can access each other.
     38 
     39  // The realm that the constructor function comes from.
     40  window.constructorRealm = await createRealm();
     41 
     42  // The realm in which the constructor object is called.
     43  window.constructedRealm = await createRealm();
     44 
     45  // The realm in which reading happens.
     46  window.readRealm = await createRealm();
     47 
     48  // The realm in which writing happens.
     49  window.writeRealm = await createRealm();
     50 
     51  // The realm that provides the definitions of Readable and Writable methods.
     52  window.methodRealm = await createRealm();
     53 
     54  await evalInRealmAndWait(methodRealm, `
     55  window.ReadableStreamDefaultReader =
     56      new ReadableStream().getReader().constructor;
     57  window.WritableStreamDefaultWriter =
     58      new WritableStream().getWriter().constructor;
     59 `);
     60  window.readMethod = methodRealm.ReadableStreamDefaultReader.prototype.read;
     61  window.writeMethod = methodRealm.WritableStreamDefaultWriter.prototype.write;
     62 }
     63 
     64 // In order for values to be visible between realms, they need to be
     65 // global. To prevent interference between tests, variable names are generated
     66 // automatically.
     67 const id = (() => {
     68  let nextId = 0;
     69  return () => {
     70    return `realmsId${nextId++}`;
     71  };
     72 })();
     73 
     74 // Eval string "code" in the content of realm "realm". Evaluation happens
     75 // asynchronously, meaning it hasn't happened when the function returns.
     76 function evalInRealm(realm, code) {
     77  realm.postMessage(code, window.origin);
     78 }
     79 
     80 // Same as evalInRealm() but returns a Promise which will resolve when the
     81 // function has actually.
     82 async function evalInRealmAndWait(realm, code) {
     83  const resolve = id();
     84  const waitOn = new Promise(r => {
     85    realm[resolve] = r;
     86  });
     87  evalInRealm(realm, code);
     88  evalInRealm(realm, `${resolve}();`);
     89  await waitOn;
     90 }
     91 
     92 // The same as evalInRealmAndWait but returns the result of evaluating "code" as
     93 // an expression.
     94 async function evalInRealmAndReturn(realm, code) {
     95  const myId = id();
     96  await evalInRealmAndWait(realm, `window.${myId} = ${code};`);
     97  return realm[myId];
     98 }
     99 
    100 // Constructs an object in constructedRealm and copies it into readRealm and
    101 // writeRealm. Returns the id that can be used to access the object in those
    102 // realms. |what| can contain constructor arguments.
    103 async function constructAndStore(what) {
    104  const objId = id();
    105  // Call |constructorRealm|'s constructor from inside |constructedRealm|.
    106  writeRealm[objId] = await evalInRealmAndReturn(
    107      constructedRealm, `new parent.constructorRealm.${what}`);
    108  readRealm[objId] = writeRealm[objId];
    109  return objId;
    110 }
    111 
    112 // Calls read() on the readable side of the TransformStream stored in
    113 // readRealm[objId]. Locks the readable side as a side-effect.
    114 function readInReadRealm(objId) {
    115  return evalInRealmAndReturn(readRealm, `
    116 parent.readMethod.call(window.${objId}.readable.getReader())`);
    117 }
    118 
    119 // Calls write() on the writable side of the TransformStream stored in
    120 // writeRealm[objId], passing |value|. Locks the writable side as a
    121 // side-effect.
    122 function writeInWriteRealm(objId, value) {
    123  const valueId = id();
    124  writeRealm[valueId] = value;
    125  return evalInRealmAndReturn(writeRealm, `
    126 parent.writeMethod.call(window.${objId}.writable.getWriter(),
    127                        window.${valueId})`);
    128 }
    129 
    130 window.onload = () => {
    131  createRealms().then(() => {
    132    runGenericTests('TextEncoderStream');
    133    runTextEncoderStreamTests();
    134    runGenericTests('TextDecoderStream');
    135    runTextDecoderStreamTests();
    136    done();
    137  });
    138 };
    139 
    140 function runGenericTests(classname) {
    141  promise_test(async () => {
    142    const obj = await evalInRealmAndReturn(
    143        constructedRealm, `new parent.constructorRealm.${classname}()`);
    144    assert_equals(obj.constructor, constructorRealm[classname],
    145                  'obj should be in constructor realm');
    146  }, `a ${classname} object should be associated with the realm the ` +
    147     'constructor came from');
    148 
    149  promise_test(async () => {
    150    const objId = await constructAndStore(classname);
    151    const readableGetterId = id();
    152    readRealm[readableGetterId] = Object.getOwnPropertyDescriptor(
    153        methodRealm[classname].prototype, 'readable').get;
    154    const writableGetterId = id();
    155    writeRealm[writableGetterId] = Object.getOwnPropertyDescriptor(
    156        methodRealm[classname].prototype, 'writable').get;
    157    const readable = await evalInRealmAndReturn(
    158        readRealm, `${readableGetterId}.call(${objId})`);
    159    const writable = await evalInRealmAndReturn(
    160        writeRealm, `${writableGetterId}.call(${objId})`);
    161    assert_equals(readable.constructor, constructorRealm.ReadableStream,
    162                  'readable should be in constructor realm');
    163    assert_equals(writable.constructor, constructorRealm.WritableStream,
    164                  'writable should be in constructor realm');
    165  }, `${classname}'s readable and writable attributes should come from the ` +
    166     'same realm as the constructor definition');
    167 }
    168 
    169 function runTextEncoderStreamTests() {
    170  promise_test(async () => {
    171    const objId = await constructAndStore('TextEncoderStream');
    172    const writePromise = writeInWriteRealm(objId, 'A');
    173    const result = await readInReadRealm(objId);
    174    await writePromise;
    175    assert_equals(result.constructor, constructorRealm.Object,
    176                  'result should be in constructor realm');
    177    assert_equals(result.value.constructor, constructorRealm.Uint8Array,
    178                  'chunk should be in constructor realm');
    179  }, 'the output chunks when read is called after write should come from the ' +
    180     'same realm as the constructor of TextEncoderStream');
    181 
    182  promise_test(async () => {
    183    const objId = await constructAndStore('TextEncoderStream');
    184    const chunkPromise = readInReadRealm(objId);
    185    writeInWriteRealm(objId, 'A');
    186    // Now the read() should resolve.
    187    const result = await chunkPromise;
    188    assert_equals(result.constructor, constructorRealm.Object,
    189                  'result should be in constructor realm');
    190    assert_equals(result.value.constructor, constructorRealm.Uint8Array,
    191                  'chunk should be in constructor realm');
    192  }, 'the output chunks when write is called with a pending read should come ' +
    193     'from the same realm as the constructor of TextEncoderStream');
    194 
    195  // There is not absolute consensus regarding what realm exceptions should be
    196  // created in. Implementations may vary. The expectations in exception-related
    197  // tests may change in future once consensus is reached.
    198  promise_test(async t => {
    199    const objId = await constructAndStore('TextEncoderStream');
    200    // Read first to relieve backpressure.
    201    const readPromise = readInReadRealm(objId);
    202 
    203    await promise_rejects_js(t, constructorRealm.TypeError,
    204                             writeInWriteRealm(objId, {
    205                               toString() { return {}; }
    206                             }),
    207                             'write TypeError should come from constructor realm');
    208 
    209    return promise_rejects_js(t, constructorRealm.TypeError, readPromise,
    210                              'read TypeError should come from constructor realm');
    211  }, 'TypeError for unconvertable chunk should come from constructor realm ' +
    212     'of TextEncoderStream');
    213 }
    214 
    215 function runTextDecoderStreamTests() {
    216  promise_test(async () => {
    217    const objId = await constructAndStore('TextDecoderStream');
    218    const writePromise = writeInWriteRealm(objId, new Uint8Array([65]));
    219    const result = await readInReadRealm(objId);
    220    await writePromise;
    221    assert_equals(result.constructor, constructorRealm.Object,
    222                  'result should be in constructor realm');
    223    // A string is not an object, so doesn't have an associated realm. Accessing
    224    // string properties will create a transient object wrapper belonging to the
    225    // current realm. So checking the realm of result.value is not useful.
    226  }, 'the result object when read is called after write should come from the ' +
    227     'same realm as the constructor of TextDecoderStream');
    228 
    229  promise_test(async () => {
    230    const objId = await constructAndStore('TextDecoderStream');
    231    const chunkPromise = readInReadRealm(objId);
    232    writeInWriteRealm(objId, new Uint8Array([65]));
    233    // Now the read() should resolve.
    234    const result = await chunkPromise;
    235    assert_equals(result.constructor, constructorRealm.Object,
    236                  'result should be in constructor realm');
    237    // A string is not an object, so doesn't have an associated realm. Accessing
    238    // string properties will create a transient object wrapper belonging to the
    239    // current realm. So checking the realm of result.value is not useful.
    240  }, 'the result object when write is called with a pending ' +
    241     'read should come from the same realm as the constructor of TextDecoderStream');
    242 
    243  promise_test(async t => {
    244    const objId = await constructAndStore('TextDecoderStream');
    245    // Read first to relieve backpressure.
    246    const readPromise = readInReadRealm(objId);
    247    await promise_rejects_js(
    248      t, constructorRealm.TypeError,
    249      writeInWriteRealm(objId, {}),
    250      'write TypeError should come from constructor realm'
    251    );
    252 
    253    return promise_rejects_js(
    254      t, constructorRealm.TypeError, readPromise,
    255      'read TypeError should come from constructor realm'
    256    );
    257  }, 'TypeError for chunk with the wrong type should come from constructor ' +
    258     'realm of TextDecoderStream');
    259 
    260  promise_test(async t => {
    261    const objId =
    262          await constructAndStore(`TextDecoderStream('utf-8', {fatal: true})`);
    263    // Read first to relieve backpressure.
    264    const readPromise = readInReadRealm(objId);
    265 
    266    await promise_rejects_js(
    267      t, constructorRealm.TypeError,
    268      writeInWriteRealm(objId, new Uint8Array([0xff])),
    269      'write TypeError should come from constructor realm'
    270    );
    271 
    272    return promise_rejects_js(
    273      t, constructorRealm.TypeError, readPromise,
    274      'read TypeError should come from constructor realm'
    275    );
    276  }, 'TypeError for invalid chunk should come from constructor realm ' +
    277     'of TextDecoderStream');
    278 
    279  promise_test(async t => {
    280    const objId =
    281          await constructAndStore(`TextDecoderStream('utf-8', {fatal: true})`);
    282    // Read first to relieve backpressure.
    283    readInReadRealm(objId);
    284    // Write an unfinished sequence of bytes.
    285    const incompleteBytesId = id();
    286    writeRealm[incompleteBytesId] = new Uint8Array([0xf0]);
    287 
    288    return promise_rejects_js(
    289      t, constructorRealm.TypeError,
    290      // Can't use writeInWriteRealm() here because it doesn't make it possible
    291      // to reuse the writer.
    292      evalInRealmAndReturn(writeRealm, `
    293 (() => {
    294  const writer = window.${objId}.writable.getWriter();
    295  parent.writeMethod.call(writer, window.${incompleteBytesId});
    296  return parent.methodRealm.WritableStreamDefaultWriter.prototype
    297    .close.call(writer);
    298 })();
    299 `),
    300      'close TypeError should come from constructor realm'
    301    );
    302  }, 'TypeError for incomplete input should come from constructor realm ' +
    303     'of TextDecoderStream');
    304 }