304 lines
12 KiB
JavaScript
304 lines
12 KiB
JavaScript
'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');
|
|
}
|