diff options
Diffstat (limited to 'testing/web-platform/tests/encoding/streams/realms.window.js')
-rw-r--r-- | testing/web-platform/tests/encoding/streams/realms.window.js | 304 |
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'); +} |