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