summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/encoding/streams/realms.window.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/encoding/streams/realms.window.js')
-rw-r--r--testing/web-platform/tests/encoding/streams/realms.window.js304
1 files changed, 304 insertions, 0 deletions
diff --git a/testing/web-platform/tests/encoding/streams/realms.window.js b/testing/web-platform/tests/encoding/streams/realms.window.js
new file mode 100644
index 0000000000..ca9ce21abc
--- /dev/null
+++ b/testing/web-platform/tests/encoding/streams/realms.window.js
@@ -0,0 +1,304 @@
+'use strict';
+
+// Test that objects created by the TextEncoderStream and TextDecoderStream APIs
+// are created in the correct realm. The tests work by creating an iframe for
+// each realm and then posting Javascript to them to be evaluated. Inputs and
+// outputs are passed around via global variables in each realm's scope.
+
+// Async setup is required before creating any tests, so require done() to be
+// called.
+setup({explicit_done: true});
+
+function createRealm() {
+ let iframe = document.createElement('iframe');
+ const scriptEndTag = '<' + '/script>';
+ iframe.srcdoc = `<!doctype html>
+<script>
+onmessage = event => {
+ if (event.source !== window.parent) {
+ throw new Error('unexpected message with source ' + event.source);
+ }
+ eval(event.data);
+};
+${scriptEndTag}`;
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+ let realmPromiseResolve;
+ const realmPromise = new Promise(resolve => {
+ realmPromiseResolve = resolve;
+ });
+ iframe.onload = () => {
+ realmPromiseResolve(iframe.contentWindow);
+ };
+ return realmPromise;
+}
+
+async function createRealms() {
+ // All realms are visible on the global object so they can access each other.
+
+ // The realm that the constructor function comes from.
+ window.constructorRealm = await createRealm();
+
+ // The realm in which the constructor object is called.
+ window.constructedRealm = await createRealm();
+
+ // The realm in which reading happens.
+ window.readRealm = await createRealm();
+
+ // The realm in which writing happens.
+ window.writeRealm = await createRealm();
+
+ // The realm that provides the definitions of Readable and Writable methods.
+ window.methodRealm = await createRealm();
+
+ await evalInRealmAndWait(methodRealm, `
+ window.ReadableStreamDefaultReader =
+ new ReadableStream().getReader().constructor;
+ window.WritableStreamDefaultWriter =
+ new WritableStream().getWriter().constructor;
+`);
+ window.readMethod = methodRealm.ReadableStreamDefaultReader.prototype.read;
+ window.writeMethod = methodRealm.WritableStreamDefaultWriter.prototype.write;
+}
+
+// In order for values to be visible between realms, they need to be
+// global. To prevent interference between tests, variable names are generated
+// automatically.
+const id = (() => {
+ let nextId = 0;
+ return () => {
+ return `realmsId${nextId++}`;
+ };
+})();
+
+// Eval string "code" in the content of realm "realm". Evaluation happens
+// asynchronously, meaning it hasn't happened when the function returns.
+function evalInRealm(realm, code) {
+ realm.postMessage(code, window.origin);
+}
+
+// Same as evalInRealm() but returns a Promise which will resolve when the
+// function has actually.
+async function evalInRealmAndWait(realm, code) {
+ const resolve = id();
+ const waitOn = new Promise(r => {
+ realm[resolve] = r;
+ });
+ evalInRealm(realm, code);
+ evalInRealm(realm, `${resolve}();`);
+ await waitOn;
+}
+
+// The same as evalInRealmAndWait but returns the result of evaluating "code" as
+// an expression.
+async function evalInRealmAndReturn(realm, code) {
+ const myId = id();
+ await evalInRealmAndWait(realm, `window.${myId} = ${code};`);
+ return realm[myId];
+}
+
+// Constructs an object in constructedRealm and copies it into readRealm and
+// writeRealm. Returns the id that can be used to access the object in those
+// realms. |what| can contain constructor arguments.
+async function constructAndStore(what) {
+ const objId = id();
+ // Call |constructorRealm|'s constructor from inside |constructedRealm|.
+ writeRealm[objId] = await evalInRealmAndReturn(
+ constructedRealm, `new parent.constructorRealm.${what}`);
+ readRealm[objId] = writeRealm[objId];
+ return objId;
+}
+
+// Calls read() on the readable side of the TransformStream stored in
+// readRealm[objId]. Locks the readable side as a side-effect.
+function readInReadRealm(objId) {
+ return evalInRealmAndReturn(readRealm, `
+parent.readMethod.call(window.${objId}.readable.getReader())`);
+}
+
+// Calls write() on the writable side of the TransformStream stored in
+// writeRealm[objId], passing |value|. Locks the writable side as a
+// side-effect.
+function writeInWriteRealm(objId, value) {
+ const valueId = id();
+ writeRealm[valueId] = value;
+ return evalInRealmAndReturn(writeRealm, `
+parent.writeMethod.call(window.${objId}.writable.getWriter(),
+ window.${valueId})`);
+}
+
+window.onload = () => {
+ createRealms().then(() => {
+ runGenericTests('TextEncoderStream');
+ runTextEncoderStreamTests();
+ runGenericTests('TextDecoderStream');
+ runTextDecoderStreamTests();
+ done();
+ });
+};
+
+function runGenericTests(classname) {
+ promise_test(async () => {
+ const obj = await evalInRealmAndReturn(
+ constructedRealm, `new parent.constructorRealm.${classname}()`);
+ assert_equals(obj.constructor, constructorRealm[classname],
+ 'obj should be in constructor realm');
+ }, `a ${classname} object should be associated with the realm the ` +
+ 'constructor came from');
+
+ promise_test(async () => {
+ const objId = await constructAndStore(classname);
+ const readableGetterId = id();
+ readRealm[readableGetterId] = Object.getOwnPropertyDescriptor(
+ methodRealm[classname].prototype, 'readable').get;
+ const writableGetterId = id();
+ writeRealm[writableGetterId] = Object.getOwnPropertyDescriptor(
+ methodRealm[classname].prototype, 'writable').get;
+ const readable = await evalInRealmAndReturn(
+ readRealm, `${readableGetterId}.call(${objId})`);
+ const writable = await evalInRealmAndReturn(
+ writeRealm, `${writableGetterId}.call(${objId})`);
+ assert_equals(readable.constructor, constructorRealm.ReadableStream,
+ 'readable should be in constructor realm');
+ assert_equals(writable.constructor, constructorRealm.WritableStream,
+ 'writable should be in constructor realm');
+ }, `${classname}'s readable and writable attributes should come from the ` +
+ 'same realm as the constructor definition');
+}
+
+function runTextEncoderStreamTests() {
+ promise_test(async () => {
+ const objId = await constructAndStore('TextEncoderStream');
+ const writePromise = writeInWriteRealm(objId, 'A');
+ const result = await readInReadRealm(objId);
+ await writePromise;
+ assert_equals(result.constructor, constructorRealm.Object,
+ 'result should be in constructor realm');
+ assert_equals(result.value.constructor, constructorRealm.Uint8Array,
+ 'chunk should be in constructor realm');
+ }, 'the output chunks when read is called after write should come from the ' +
+ 'same realm as the constructor of TextEncoderStream');
+
+ promise_test(async () => {
+ const objId = await constructAndStore('TextEncoderStream');
+ const chunkPromise = readInReadRealm(objId);
+ writeInWriteRealm(objId, 'A');
+ // Now the read() should resolve.
+ const result = await chunkPromise;
+ assert_equals(result.constructor, constructorRealm.Object,
+ 'result should be in constructor realm');
+ assert_equals(result.value.constructor, constructorRealm.Uint8Array,
+ 'chunk should be in constructor realm');
+ }, 'the output chunks when write is called with a pending read should come ' +
+ 'from the same realm as the constructor of TextEncoderStream');
+
+ // There is not absolute consensus regarding what realm exceptions should be
+ // created in. Implementations may vary. The expectations in exception-related
+ // tests may change in future once consensus is reached.
+ promise_test(async t => {
+ const objId = await constructAndStore('TextEncoderStream');
+ // Read first to relieve backpressure.
+ const readPromise = readInReadRealm(objId);
+
+ await promise_rejects_js(t, constructorRealm.TypeError,
+ writeInWriteRealm(objId, {
+ toString() { return {}; }
+ }),
+ 'write TypeError should come from constructor realm');
+
+ return promise_rejects_js(t, constructorRealm.TypeError, readPromise,
+ 'read TypeError should come from constructor realm');
+ }, 'TypeError for unconvertable chunk should come from constructor realm ' +
+ 'of TextEncoderStream');
+}
+
+function runTextDecoderStreamTests() {
+ promise_test(async () => {
+ const objId = await constructAndStore('TextDecoderStream');
+ const writePromise = writeInWriteRealm(objId, new Uint8Array([65]));
+ const result = await readInReadRealm(objId);
+ await writePromise;
+ assert_equals(result.constructor, constructorRealm.Object,
+ 'result should be in constructor realm');
+ // A string is not an object, so doesn't have an associated realm. Accessing
+ // string properties will create a transient object wrapper belonging to the
+ // current realm. So checking the realm of result.value is not useful.
+ }, 'the result object when read is called after write should come from the ' +
+ 'same realm as the constructor of TextDecoderStream');
+
+ promise_test(async () => {
+ const objId = await constructAndStore('TextDecoderStream');
+ const chunkPromise = readInReadRealm(objId);
+ writeInWriteRealm(objId, new Uint8Array([65]));
+ // Now the read() should resolve.
+ const result = await chunkPromise;
+ assert_equals(result.constructor, constructorRealm.Object,
+ 'result should be in constructor realm');
+ // A string is not an object, so doesn't have an associated realm. Accessing
+ // string properties will create a transient object wrapper belonging to the
+ // current realm. So checking the realm of result.value is not useful.
+ }, 'the result object when write is called with a pending ' +
+ 'read should come from the same realm as the constructor of TextDecoderStream');
+
+ promise_test(async t => {
+ const objId = await constructAndStore('TextDecoderStream');
+ // Read first to relieve backpressure.
+ const readPromise = readInReadRealm(objId);
+ await promise_rejects_js(
+ t, constructorRealm.TypeError,
+ writeInWriteRealm(objId, {}),
+ 'write TypeError should come from constructor realm'
+ );
+
+ return promise_rejects_js(
+ t, constructorRealm.TypeError, readPromise,
+ 'read TypeError should come from constructor realm'
+ );
+ }, 'TypeError for chunk with the wrong type should come from constructor ' +
+ 'realm of TextDecoderStream');
+
+ promise_test(async t => {
+ const objId =
+ await constructAndStore(`TextDecoderStream('utf-8', {fatal: true})`);
+ // Read first to relieve backpressure.
+ const readPromise = readInReadRealm(objId);
+
+ await promise_rejects_js(
+ t, constructorRealm.TypeError,
+ writeInWriteRealm(objId, new Uint8Array([0xff])),
+ 'write TypeError should come from constructor realm'
+ );
+
+ return promise_rejects_js(
+ t, constructorRealm.TypeError, readPromise,
+ 'read TypeError should come from constructor realm'
+ );
+ }, 'TypeError for invalid chunk should come from constructor realm ' +
+ 'of TextDecoderStream');
+
+ promise_test(async t => {
+ const objId =
+ await constructAndStore(`TextDecoderStream('utf-8', {fatal: true})`);
+ // Read first to relieve backpressure.
+ readInReadRealm(objId);
+ // Write an unfinished sequence of bytes.
+ const incompleteBytesId = id();
+ writeRealm[incompleteBytesId] = new Uint8Array([0xf0]);
+
+ return promise_rejects_js(
+ t, constructorRealm.TypeError,
+ // Can't use writeInWriteRealm() here because it doesn't make it possible
+ // to reuse the writer.
+ evalInRealmAndReturn(writeRealm, `
+(() => {
+ const writer = window.${objId}.writable.getWriter();
+ parent.writeMethod.call(writer, window.${incompleteBytesId});
+ return parent.methodRealm.WritableStreamDefaultWriter.prototype
+ .close.call(writer);
+})();
+`),
+ 'close TypeError should come from constructor realm'
+ );
+ }, 'TypeError for incomplete input should come from constructor realm ' +
+ 'of TextDecoderStream');
+}