11cb0ef41Sopenharmony_ci'use strict';
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ci// Test that objects created by the TextEncoderStream and TextDecoderStream APIs
41cb0ef41Sopenharmony_ci// are created in the correct realm. The tests work by creating an iframe for
51cb0ef41Sopenharmony_ci// each realm and then posting Javascript to them to be evaluated. Inputs and
61cb0ef41Sopenharmony_ci// outputs are passed around via global variables in each realm's scope.
71cb0ef41Sopenharmony_ci
81cb0ef41Sopenharmony_ci// Async setup is required before creating any tests, so require done() to be
91cb0ef41Sopenharmony_ci// called.
101cb0ef41Sopenharmony_cisetup({explicit_done: true});
111cb0ef41Sopenharmony_ci
121cb0ef41Sopenharmony_cifunction createRealm() {
131cb0ef41Sopenharmony_ci  let iframe = document.createElement('iframe');
141cb0ef41Sopenharmony_ci  const scriptEndTag = '<' + '/script>';
151cb0ef41Sopenharmony_ci  iframe.srcdoc = `<!doctype html>
161cb0ef41Sopenharmony_ci<script>
171cb0ef41Sopenharmony_cionmessage = event => {
181cb0ef41Sopenharmony_ci  if (event.source !== window.parent) {
191cb0ef41Sopenharmony_ci    throw new Error('unexpected message with source ' + event.source);
201cb0ef41Sopenharmony_ci  }
211cb0ef41Sopenharmony_ci  eval(event.data);
221cb0ef41Sopenharmony_ci};
231cb0ef41Sopenharmony_ci${scriptEndTag}`;
241cb0ef41Sopenharmony_ci  iframe.style.display = 'none';
251cb0ef41Sopenharmony_ci  document.body.appendChild(iframe);
261cb0ef41Sopenharmony_ci  let realmPromiseResolve;
271cb0ef41Sopenharmony_ci  const realmPromise = new Promise(resolve => {
281cb0ef41Sopenharmony_ci    realmPromiseResolve = resolve;
291cb0ef41Sopenharmony_ci  });
301cb0ef41Sopenharmony_ci  iframe.onload = () => {
311cb0ef41Sopenharmony_ci    realmPromiseResolve(iframe.contentWindow);
321cb0ef41Sopenharmony_ci  };
331cb0ef41Sopenharmony_ci  return realmPromise;
341cb0ef41Sopenharmony_ci}
351cb0ef41Sopenharmony_ci
361cb0ef41Sopenharmony_ciasync function createRealms() {
371cb0ef41Sopenharmony_ci  // All realms are visible on the global object so they can access each other.
381cb0ef41Sopenharmony_ci
391cb0ef41Sopenharmony_ci  // The realm that the constructor function comes from.
401cb0ef41Sopenharmony_ci  window.constructorRealm = await createRealm();
411cb0ef41Sopenharmony_ci
421cb0ef41Sopenharmony_ci  // The realm in which the constructor object is called.
431cb0ef41Sopenharmony_ci  window.constructedRealm = await createRealm();
441cb0ef41Sopenharmony_ci
451cb0ef41Sopenharmony_ci  // The realm in which reading happens.
461cb0ef41Sopenharmony_ci  window.readRealm = await createRealm();
471cb0ef41Sopenharmony_ci
481cb0ef41Sopenharmony_ci  // The realm in which writing happens.
491cb0ef41Sopenharmony_ci  window.writeRealm = await createRealm();
501cb0ef41Sopenharmony_ci
511cb0ef41Sopenharmony_ci  // The realm that provides the definitions of Readable and Writable methods.
521cb0ef41Sopenharmony_ci  window.methodRealm = await createRealm();
531cb0ef41Sopenharmony_ci
541cb0ef41Sopenharmony_ci  await evalInRealmAndWait(methodRealm, `
551cb0ef41Sopenharmony_ci  window.ReadableStreamDefaultReader =
561cb0ef41Sopenharmony_ci      new ReadableStream().getReader().constructor;
571cb0ef41Sopenharmony_ci  window.WritableStreamDefaultWriter =
581cb0ef41Sopenharmony_ci      new WritableStream().getWriter().constructor;
591cb0ef41Sopenharmony_ci`);
601cb0ef41Sopenharmony_ci  window.readMethod = methodRealm.ReadableStreamDefaultReader.prototype.read;
611cb0ef41Sopenharmony_ci  window.writeMethod = methodRealm.WritableStreamDefaultWriter.prototype.write;
621cb0ef41Sopenharmony_ci}
631cb0ef41Sopenharmony_ci
641cb0ef41Sopenharmony_ci// In order for values to be visible between realms, they need to be
651cb0ef41Sopenharmony_ci// global. To prevent interference between tests, variable names are generated
661cb0ef41Sopenharmony_ci// automatically.
671cb0ef41Sopenharmony_ciconst id = (() => {
681cb0ef41Sopenharmony_ci  let nextId = 0;
691cb0ef41Sopenharmony_ci  return () => {
701cb0ef41Sopenharmony_ci    return `realmsId${nextId++}`;
711cb0ef41Sopenharmony_ci  };
721cb0ef41Sopenharmony_ci})();
731cb0ef41Sopenharmony_ci
741cb0ef41Sopenharmony_ci// Eval string "code" in the content of realm "realm". Evaluation happens
751cb0ef41Sopenharmony_ci// asynchronously, meaning it hasn't happened when the function returns.
761cb0ef41Sopenharmony_cifunction evalInRealm(realm, code) {
771cb0ef41Sopenharmony_ci  realm.postMessage(code, window.origin);
781cb0ef41Sopenharmony_ci}
791cb0ef41Sopenharmony_ci
801cb0ef41Sopenharmony_ci// Same as evalInRealm() but returns a Promise which will resolve when the
811cb0ef41Sopenharmony_ci// function has actually.
821cb0ef41Sopenharmony_ciasync function evalInRealmAndWait(realm, code) {
831cb0ef41Sopenharmony_ci  const resolve = id();
841cb0ef41Sopenharmony_ci  const waitOn = new Promise(r => {
851cb0ef41Sopenharmony_ci    realm[resolve] = r;
861cb0ef41Sopenharmony_ci  });
871cb0ef41Sopenharmony_ci  evalInRealm(realm, code);
881cb0ef41Sopenharmony_ci  evalInRealm(realm, `${resolve}();`);
891cb0ef41Sopenharmony_ci  await waitOn;
901cb0ef41Sopenharmony_ci}
911cb0ef41Sopenharmony_ci
921cb0ef41Sopenharmony_ci// The same as evalInRealmAndWait but returns the result of evaluating "code" as
931cb0ef41Sopenharmony_ci// an expression.
941cb0ef41Sopenharmony_ciasync function evalInRealmAndReturn(realm, code) {
951cb0ef41Sopenharmony_ci  const myId = id();
961cb0ef41Sopenharmony_ci  await evalInRealmAndWait(realm, `window.${myId} = ${code};`);
971cb0ef41Sopenharmony_ci  return realm[myId];
981cb0ef41Sopenharmony_ci}
991cb0ef41Sopenharmony_ci
1001cb0ef41Sopenharmony_ci// Constructs an object in constructedRealm and copies it into readRealm and
1011cb0ef41Sopenharmony_ci// writeRealm. Returns the id that can be used to access the object in those
1021cb0ef41Sopenharmony_ci// realms. |what| can contain constructor arguments.
1031cb0ef41Sopenharmony_ciasync function constructAndStore(what) {
1041cb0ef41Sopenharmony_ci  const objId = id();
1051cb0ef41Sopenharmony_ci  // Call |constructorRealm|'s constructor from inside |constructedRealm|.
1061cb0ef41Sopenharmony_ci  writeRealm[objId] = await evalInRealmAndReturn(
1071cb0ef41Sopenharmony_ci      constructedRealm, `new parent.constructorRealm.${what}`);
1081cb0ef41Sopenharmony_ci  readRealm[objId] = writeRealm[objId];
1091cb0ef41Sopenharmony_ci  return objId;
1101cb0ef41Sopenharmony_ci}
1111cb0ef41Sopenharmony_ci
1121cb0ef41Sopenharmony_ci// Calls read() on the readable side of the TransformStream stored in
1131cb0ef41Sopenharmony_ci// readRealm[objId]. Locks the readable side as a side-effect.
1141cb0ef41Sopenharmony_cifunction readInReadRealm(objId) {
1151cb0ef41Sopenharmony_ci  return evalInRealmAndReturn(readRealm, `
1161cb0ef41Sopenharmony_ciparent.readMethod.call(window.${objId}.readable.getReader())`);
1171cb0ef41Sopenharmony_ci}
1181cb0ef41Sopenharmony_ci
1191cb0ef41Sopenharmony_ci// Calls write() on the writable side of the TransformStream stored in
1201cb0ef41Sopenharmony_ci// writeRealm[objId], passing |value|. Locks the writable side as a
1211cb0ef41Sopenharmony_ci// side-effect.
1221cb0ef41Sopenharmony_cifunction writeInWriteRealm(objId, value) {
1231cb0ef41Sopenharmony_ci  const valueId = id();
1241cb0ef41Sopenharmony_ci  writeRealm[valueId] = value;
1251cb0ef41Sopenharmony_ci  return evalInRealmAndReturn(writeRealm, `
1261cb0ef41Sopenharmony_ciparent.writeMethod.call(window.${objId}.writable.getWriter(),
1271cb0ef41Sopenharmony_ci                        window.${valueId})`);
1281cb0ef41Sopenharmony_ci}
1291cb0ef41Sopenharmony_ci
1301cb0ef41Sopenharmony_ciwindow.onload = () => {
1311cb0ef41Sopenharmony_ci  createRealms().then(() => {
1321cb0ef41Sopenharmony_ci    runGenericTests('TextEncoderStream');
1331cb0ef41Sopenharmony_ci    runTextEncoderStreamTests();
1341cb0ef41Sopenharmony_ci    runGenericTests('TextDecoderStream');
1351cb0ef41Sopenharmony_ci    runTextDecoderStreamTests();
1361cb0ef41Sopenharmony_ci    done();
1371cb0ef41Sopenharmony_ci  });
1381cb0ef41Sopenharmony_ci};
1391cb0ef41Sopenharmony_ci
1401cb0ef41Sopenharmony_cifunction runGenericTests(classname) {
1411cb0ef41Sopenharmony_ci  promise_test(async () => {
1421cb0ef41Sopenharmony_ci    const obj = await evalInRealmAndReturn(
1431cb0ef41Sopenharmony_ci        constructedRealm, `new parent.constructorRealm.${classname}()`);
1441cb0ef41Sopenharmony_ci    assert_equals(obj.constructor, constructorRealm[classname],
1451cb0ef41Sopenharmony_ci                  'obj should be in constructor realm');
1461cb0ef41Sopenharmony_ci  }, `a ${classname} object should be associated with the realm the ` +
1471cb0ef41Sopenharmony_ci     'constructor came from');
1481cb0ef41Sopenharmony_ci
1491cb0ef41Sopenharmony_ci  promise_test(async () => {
1501cb0ef41Sopenharmony_ci    const objId = await constructAndStore(classname);
1511cb0ef41Sopenharmony_ci    const readableGetterId = id();
1521cb0ef41Sopenharmony_ci    readRealm[readableGetterId] = Object.getOwnPropertyDescriptor(
1531cb0ef41Sopenharmony_ci        methodRealm[classname].prototype, 'readable').get;
1541cb0ef41Sopenharmony_ci    const writableGetterId = id();
1551cb0ef41Sopenharmony_ci    writeRealm[writableGetterId] = Object.getOwnPropertyDescriptor(
1561cb0ef41Sopenharmony_ci        methodRealm[classname].prototype, 'writable').get;
1571cb0ef41Sopenharmony_ci    const readable = await evalInRealmAndReturn(
1581cb0ef41Sopenharmony_ci        readRealm, `${readableGetterId}.call(${objId})`);
1591cb0ef41Sopenharmony_ci    const writable = await evalInRealmAndReturn(
1601cb0ef41Sopenharmony_ci        writeRealm, `${writableGetterId}.call(${objId})`);
1611cb0ef41Sopenharmony_ci    assert_equals(readable.constructor, constructorRealm.ReadableStream,
1621cb0ef41Sopenharmony_ci                  'readable should be in constructor realm');
1631cb0ef41Sopenharmony_ci    assert_equals(writable.constructor, constructorRealm.WritableStream,
1641cb0ef41Sopenharmony_ci                  'writable should be in constructor realm');
1651cb0ef41Sopenharmony_ci  }, `${classname}'s readable and writable attributes should come from the ` +
1661cb0ef41Sopenharmony_ci     'same realm as the constructor definition');
1671cb0ef41Sopenharmony_ci}
1681cb0ef41Sopenharmony_ci
1691cb0ef41Sopenharmony_cifunction runTextEncoderStreamTests() {
1701cb0ef41Sopenharmony_ci  promise_test(async () => {
1711cb0ef41Sopenharmony_ci    const objId = await constructAndStore('TextEncoderStream');
1721cb0ef41Sopenharmony_ci    const writePromise = writeInWriteRealm(objId, 'A');
1731cb0ef41Sopenharmony_ci    const result = await readInReadRealm(objId);
1741cb0ef41Sopenharmony_ci    await writePromise;
1751cb0ef41Sopenharmony_ci    assert_equals(result.constructor, constructorRealm.Object,
1761cb0ef41Sopenharmony_ci                  'result should be in constructor realm');
1771cb0ef41Sopenharmony_ci    assert_equals(result.value.constructor, constructorRealm.Uint8Array,
1781cb0ef41Sopenharmony_ci                  'chunk should be in constructor realm');
1791cb0ef41Sopenharmony_ci  }, 'the output chunks when read is called after write should come from the ' +
1801cb0ef41Sopenharmony_ci     'same realm as the constructor of TextEncoderStream');
1811cb0ef41Sopenharmony_ci
1821cb0ef41Sopenharmony_ci  promise_test(async () => {
1831cb0ef41Sopenharmony_ci    const objId = await constructAndStore('TextEncoderStream');
1841cb0ef41Sopenharmony_ci    const chunkPromise = readInReadRealm(objId);
1851cb0ef41Sopenharmony_ci    writeInWriteRealm(objId, 'A');
1861cb0ef41Sopenharmony_ci    // Now the read() should resolve.
1871cb0ef41Sopenharmony_ci    const result = await chunkPromise;
1881cb0ef41Sopenharmony_ci    assert_equals(result.constructor, constructorRealm.Object,
1891cb0ef41Sopenharmony_ci                  'result should be in constructor realm');
1901cb0ef41Sopenharmony_ci    assert_equals(result.value.constructor, constructorRealm.Uint8Array,
1911cb0ef41Sopenharmony_ci                  'chunk should be in constructor realm');
1921cb0ef41Sopenharmony_ci  }, 'the output chunks when write is called with a pending read should come ' +
1931cb0ef41Sopenharmony_ci     'from the same realm as the constructor of TextEncoderStream');
1941cb0ef41Sopenharmony_ci
1951cb0ef41Sopenharmony_ci  // There is not absolute consensus regarding what realm exceptions should be
1961cb0ef41Sopenharmony_ci  // created in. Implementations may vary. The expectations in exception-related
1971cb0ef41Sopenharmony_ci  // tests may change in future once consensus is reached.
1981cb0ef41Sopenharmony_ci  promise_test(async t => {
1991cb0ef41Sopenharmony_ci    const objId = await constructAndStore('TextEncoderStream');
2001cb0ef41Sopenharmony_ci    // Read first to relieve backpressure.
2011cb0ef41Sopenharmony_ci    const readPromise = readInReadRealm(objId);
2021cb0ef41Sopenharmony_ci
2031cb0ef41Sopenharmony_ci    await promise_rejects_js(t, constructorRealm.TypeError,
2041cb0ef41Sopenharmony_ci                             writeInWriteRealm(objId, {
2051cb0ef41Sopenharmony_ci                               toString() { return {}; }
2061cb0ef41Sopenharmony_ci                             }),
2071cb0ef41Sopenharmony_ci                             'write TypeError should come from constructor realm');
2081cb0ef41Sopenharmony_ci
2091cb0ef41Sopenharmony_ci    return promise_rejects_js(t, constructorRealm.TypeError, readPromise,
2101cb0ef41Sopenharmony_ci                              'read TypeError should come from constructor realm');
2111cb0ef41Sopenharmony_ci  }, 'TypeError for unconvertable chunk should come from constructor realm ' +
2121cb0ef41Sopenharmony_ci     'of TextEncoderStream');
2131cb0ef41Sopenharmony_ci}
2141cb0ef41Sopenharmony_ci
2151cb0ef41Sopenharmony_cifunction runTextDecoderStreamTests() {
2161cb0ef41Sopenharmony_ci  promise_test(async () => {
2171cb0ef41Sopenharmony_ci    const objId = await constructAndStore('TextDecoderStream');
2181cb0ef41Sopenharmony_ci    const writePromise = writeInWriteRealm(objId, new Uint8Array([65]));
2191cb0ef41Sopenharmony_ci    const result = await readInReadRealm(objId);
2201cb0ef41Sopenharmony_ci    await writePromise;
2211cb0ef41Sopenharmony_ci    assert_equals(result.constructor, constructorRealm.Object,
2221cb0ef41Sopenharmony_ci                  'result should be in constructor realm');
2231cb0ef41Sopenharmony_ci    // A string is not an object, so doesn't have an associated realm. Accessing
2241cb0ef41Sopenharmony_ci    // string properties will create a transient object wrapper belonging to the
2251cb0ef41Sopenharmony_ci    // current realm. So checking the realm of result.value is not useful.
2261cb0ef41Sopenharmony_ci  }, 'the result object when read is called after write should come from the ' +
2271cb0ef41Sopenharmony_ci     'same realm as the constructor of TextDecoderStream');
2281cb0ef41Sopenharmony_ci
2291cb0ef41Sopenharmony_ci  promise_test(async () => {
2301cb0ef41Sopenharmony_ci    const objId = await constructAndStore('TextDecoderStream');
2311cb0ef41Sopenharmony_ci    const chunkPromise = readInReadRealm(objId);
2321cb0ef41Sopenharmony_ci    writeInWriteRealm(objId, new Uint8Array([65]));
2331cb0ef41Sopenharmony_ci    // Now the read() should resolve.
2341cb0ef41Sopenharmony_ci    const result = await chunkPromise;
2351cb0ef41Sopenharmony_ci    assert_equals(result.constructor, constructorRealm.Object,
2361cb0ef41Sopenharmony_ci                  'result should be in constructor realm');
2371cb0ef41Sopenharmony_ci    // A string is not an object, so doesn't have an associated realm. Accessing
2381cb0ef41Sopenharmony_ci    // string properties will create a transient object wrapper belonging to the
2391cb0ef41Sopenharmony_ci    // current realm. So checking the realm of result.value is not useful.
2401cb0ef41Sopenharmony_ci  }, 'the result object when write is called with a pending ' +
2411cb0ef41Sopenharmony_ci     'read should come from the same realm as the constructor of TextDecoderStream');
2421cb0ef41Sopenharmony_ci
2431cb0ef41Sopenharmony_ci  promise_test(async t => {
2441cb0ef41Sopenharmony_ci    const objId = await constructAndStore('TextDecoderStream');
2451cb0ef41Sopenharmony_ci    // Read first to relieve backpressure.
2461cb0ef41Sopenharmony_ci    const readPromise = readInReadRealm(objId);
2471cb0ef41Sopenharmony_ci    await promise_rejects_js(
2481cb0ef41Sopenharmony_ci      t, constructorRealm.TypeError,
2491cb0ef41Sopenharmony_ci      writeInWriteRealm(objId, {}),
2501cb0ef41Sopenharmony_ci      'write TypeError should come from constructor realm'
2511cb0ef41Sopenharmony_ci    );
2521cb0ef41Sopenharmony_ci
2531cb0ef41Sopenharmony_ci    return promise_rejects_js(
2541cb0ef41Sopenharmony_ci      t, constructorRealm.TypeError, readPromise,
2551cb0ef41Sopenharmony_ci      'read TypeError should come from constructor realm'
2561cb0ef41Sopenharmony_ci    );
2571cb0ef41Sopenharmony_ci  }, 'TypeError for chunk with the wrong type should come from constructor ' +
2581cb0ef41Sopenharmony_ci     'realm of TextDecoderStream');
2591cb0ef41Sopenharmony_ci
2601cb0ef41Sopenharmony_ci  promise_test(async t => {
2611cb0ef41Sopenharmony_ci    const objId =
2621cb0ef41Sopenharmony_ci          await constructAndStore(`TextDecoderStream('utf-8', {fatal: true})`);
2631cb0ef41Sopenharmony_ci    // Read first to relieve backpressure.
2641cb0ef41Sopenharmony_ci    const readPromise = readInReadRealm(objId);
2651cb0ef41Sopenharmony_ci
2661cb0ef41Sopenharmony_ci    await promise_rejects_js(
2671cb0ef41Sopenharmony_ci      t, constructorRealm.TypeError,
2681cb0ef41Sopenharmony_ci      writeInWriteRealm(objId, new Uint8Array([0xff])),
2691cb0ef41Sopenharmony_ci      'write TypeError should come from constructor realm'
2701cb0ef41Sopenharmony_ci    );
2711cb0ef41Sopenharmony_ci
2721cb0ef41Sopenharmony_ci    return promise_rejects_js(
2731cb0ef41Sopenharmony_ci      t, constructorRealm.TypeError, readPromise,
2741cb0ef41Sopenharmony_ci      'read TypeError should come from constructor realm'
2751cb0ef41Sopenharmony_ci    );
2761cb0ef41Sopenharmony_ci  }, 'TypeError for invalid chunk should come from constructor realm ' +
2771cb0ef41Sopenharmony_ci     'of TextDecoderStream');
2781cb0ef41Sopenharmony_ci
2791cb0ef41Sopenharmony_ci  promise_test(async t => {
2801cb0ef41Sopenharmony_ci    const objId =
2811cb0ef41Sopenharmony_ci          await constructAndStore(`TextDecoderStream('utf-8', {fatal: true})`);
2821cb0ef41Sopenharmony_ci    // Read first to relieve backpressure.
2831cb0ef41Sopenharmony_ci    readInReadRealm(objId);
2841cb0ef41Sopenharmony_ci    // Write an unfinished sequence of bytes.
2851cb0ef41Sopenharmony_ci    const incompleteBytesId = id();
2861cb0ef41Sopenharmony_ci    writeRealm[incompleteBytesId] = new Uint8Array([0xf0]);
2871cb0ef41Sopenharmony_ci
2881cb0ef41Sopenharmony_ci    return promise_rejects_js(
2891cb0ef41Sopenharmony_ci      t, constructorRealm.TypeError,
2901cb0ef41Sopenharmony_ci      // Can't use writeInWriteRealm() here because it doesn't make it possible
2911cb0ef41Sopenharmony_ci      // to reuse the writer.
2921cb0ef41Sopenharmony_ci      evalInRealmAndReturn(writeRealm, `
2931cb0ef41Sopenharmony_ci(() => {
2941cb0ef41Sopenharmony_ci  const writer = window.${objId}.writable.getWriter();
2951cb0ef41Sopenharmony_ci  parent.writeMethod.call(writer, window.${incompleteBytesId});
2961cb0ef41Sopenharmony_ci  return parent.methodRealm.WritableStreamDefaultWriter.prototype
2971cb0ef41Sopenharmony_ci    .close.call(writer);
2981cb0ef41Sopenharmony_ci})();
2991cb0ef41Sopenharmony_ci`),
3001cb0ef41Sopenharmony_ci      'close TypeError should come from constructor realm'
3011cb0ef41Sopenharmony_ci    );
3021cb0ef41Sopenharmony_ci  }, 'TypeError for incomplete input should come from constructor realm ' +
3031cb0ef41Sopenharmony_ci     'of TextDecoderStream');
3041cb0ef41Sopenharmony_ci}
305