Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- This WPT test may be referenced by the following Test IDs:
- /encoding/streams/realms.window.html - WPT Dashboard Interop Dashboard
'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');
}