• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3// Test that objects created by the TextEncoderStream and TextDecoderStream APIs
4// are created in the correct realm. The tests work by creating an iframe for
5// each realm and then posting Javascript to them to be evaluated. Inputs and
6// outputs are passed around via global variables in each realm's scope.
7
8// Async setup is required before creating any tests, so require done() to be
9// called.
10setup({explicit_done: true});
11
12function createRealm() {
13  let iframe = document.createElement('iframe');
14  const scriptEndTag = '<' + '/script>';
15  iframe.srcdoc = `<!doctype html>
16<script>
17onmessage = event => {
18  if (event.source !== window.parent) {
19    throw new Error('unexpected message with source ' + event.source);
20  }
21  eval(event.data);
22};
23${scriptEndTag}`;
24  iframe.style.display = 'none';
25  document.body.appendChild(iframe);
26  let realmPromiseResolve;
27  const realmPromise = new Promise(resolve => {
28    realmPromiseResolve = resolve;
29  });
30  iframe.onload = () => {
31    realmPromiseResolve(iframe.contentWindow);
32  };
33  return realmPromise;
34}
35
36async function createRealms() {
37  // All realms are visible on the global object so they can access each other.
38
39  // The realm that the constructor function comes from.
40  window.constructorRealm = await createRealm();
41
42  // The realm in which the constructor object is called.
43  window.constructedRealm = await createRealm();
44
45  // The realm in which reading happens.
46  window.readRealm = await createRealm();
47
48  // The realm in which writing happens.
49  window.writeRealm = await createRealm();
50
51  // The realm that provides the definitions of Readable and Writable methods.
52  window.methodRealm = await createRealm();
53
54  await evalInRealmAndWait(methodRealm, `
55  window.ReadableStreamDefaultReader =
56      new ReadableStream().getReader().constructor;
57  window.WritableStreamDefaultWriter =
58      new WritableStream().getWriter().constructor;
59`);
60  window.readMethod = methodRealm.ReadableStreamDefaultReader.prototype.read;
61  window.writeMethod = methodRealm.WritableStreamDefaultWriter.prototype.write;
62}
63
64// In order for values to be visible between realms, they need to be
65// global. To prevent interference between tests, variable names are generated
66// automatically.
67const id = (() => {
68  let nextId = 0;
69  return () => {
70    return `realmsId${nextId++}`;
71  };
72})();
73
74// Eval string "code" in the content of realm "realm". Evaluation happens
75// asynchronously, meaning it hasn't happened when the function returns.
76function evalInRealm(realm, code) {
77  realm.postMessage(code, window.origin);
78}
79
80// Same as evalInRealm() but returns a Promise which will resolve when the
81// function has actually.
82async function evalInRealmAndWait(realm, code) {
83  const resolve = id();
84  const waitOn = new Promise(r => {
85    realm[resolve] = r;
86  });
87  evalInRealm(realm, code);
88  evalInRealm(realm, `${resolve}();`);
89  await waitOn;
90}
91
92// The same as evalInRealmAndWait but returns the result of evaluating "code" as
93// an expression.
94async function evalInRealmAndReturn(realm, code) {
95  const myId = id();
96  await evalInRealmAndWait(realm, `window.${myId} = ${code};`);
97  return realm[myId];
98}
99
100// Constructs an object in constructedRealm and copies it into readRealm and
101// writeRealm. Returns the id that can be used to access the object in those
102// realms. |what| can contain constructor arguments.
103async function constructAndStore(what) {
104  const objId = id();
105  // Call |constructorRealm|'s constructor from inside |constructedRealm|.
106  writeRealm[objId] = await evalInRealmAndReturn(
107      constructedRealm, `new parent.constructorRealm.${what}`);
108  readRealm[objId] = writeRealm[objId];
109  return objId;
110}
111
112// Calls read() on the readable side of the TransformStream stored in
113// readRealm[objId]. Locks the readable side as a side-effect.
114function readInReadRealm(objId) {
115  return evalInRealmAndReturn(readRealm, `
116parent.readMethod.call(window.${objId}.readable.getReader())`);
117}
118
119// Calls write() on the writable side of the TransformStream stored in
120// writeRealm[objId], passing |value|. Locks the writable side as a
121// side-effect.
122function writeInWriteRealm(objId, value) {
123  const valueId = id();
124  writeRealm[valueId] = value;
125  return evalInRealmAndReturn(writeRealm, `
126parent.writeMethod.call(window.${objId}.writable.getWriter(),
127                        window.${valueId})`);
128}
129
130window.onload = () => {
131  createRealms().then(() => {
132    runGenericTests('TextEncoderStream');
133    runTextEncoderStreamTests();
134    runGenericTests('TextDecoderStream');
135    runTextDecoderStreamTests();
136    done();
137  });
138};
139
140function runGenericTests(classname) {
141  promise_test(async () => {
142    const obj = await evalInRealmAndReturn(
143        constructedRealm, `new parent.constructorRealm.${classname}()`);
144    assert_equals(obj.constructor, constructorRealm[classname],
145                  'obj should be in constructor realm');
146  }, `a ${classname} object should be associated with the realm the ` +
147     'constructor came from');
148
149  promise_test(async () => {
150    const objId = await constructAndStore(classname);
151    const readableGetterId = id();
152    readRealm[readableGetterId] = Object.getOwnPropertyDescriptor(
153        methodRealm[classname].prototype, 'readable').get;
154    const writableGetterId = id();
155    writeRealm[writableGetterId] = Object.getOwnPropertyDescriptor(
156        methodRealm[classname].prototype, 'writable').get;
157    const readable = await evalInRealmAndReturn(
158        readRealm, `${readableGetterId}.call(${objId})`);
159    const writable = await evalInRealmAndReturn(
160        writeRealm, `${writableGetterId}.call(${objId})`);
161    assert_equals(readable.constructor, constructorRealm.ReadableStream,
162                  'readable should be in constructor realm');
163    assert_equals(writable.constructor, constructorRealm.WritableStream,
164                  'writable should be in constructor realm');
165  }, `${classname}'s readable and writable attributes should come from the ` +
166     'same realm as the constructor definition');
167}
168
169function runTextEncoderStreamTests() {
170  promise_test(async () => {
171    const objId = await constructAndStore('TextEncoderStream');
172    const writePromise = writeInWriteRealm(objId, 'A');
173    const result = await readInReadRealm(objId);
174    await writePromise;
175    assert_equals(result.constructor, constructorRealm.Object,
176                  'result should be in constructor realm');
177    assert_equals(result.value.constructor, constructorRealm.Uint8Array,
178                  'chunk should be in constructor realm');
179  }, 'the output chunks when read is called after write should come from the ' +
180     'same realm as the constructor of TextEncoderStream');
181
182  promise_test(async () => {
183    const objId = await constructAndStore('TextEncoderStream');
184    const chunkPromise = readInReadRealm(objId);
185    writeInWriteRealm(objId, 'A');
186    // Now the read() should resolve.
187    const result = await chunkPromise;
188    assert_equals(result.constructor, constructorRealm.Object,
189                  'result should be in constructor realm');
190    assert_equals(result.value.constructor, constructorRealm.Uint8Array,
191                  'chunk should be in constructor realm');
192  }, 'the output chunks when write is called with a pending read should come ' +
193     'from the same realm as the constructor of TextEncoderStream');
194
195  // There is not absolute consensus regarding what realm exceptions should be
196  // created in. Implementations may vary. The expectations in exception-related
197  // tests may change in future once consensus is reached.
198  promise_test(async t => {
199    const objId = await constructAndStore('TextEncoderStream');
200    // Read first to relieve backpressure.
201    const readPromise = readInReadRealm(objId);
202
203    await promise_rejects_js(t, constructorRealm.TypeError,
204                             writeInWriteRealm(objId, {
205                               toString() { return {}; }
206                             }),
207                             'write TypeError should come from constructor realm');
208
209    return promise_rejects_js(t, constructorRealm.TypeError, readPromise,
210                              'read TypeError should come from constructor realm');
211  }, 'TypeError for unconvertable chunk should come from constructor realm ' +
212     'of TextEncoderStream');
213}
214
215function runTextDecoderStreamTests() {
216  promise_test(async () => {
217    const objId = await constructAndStore('TextDecoderStream');
218    const writePromise = writeInWriteRealm(objId, new Uint8Array([65]));
219    const result = await readInReadRealm(objId);
220    await writePromise;
221    assert_equals(result.constructor, constructorRealm.Object,
222                  'result should be in constructor realm');
223    // A string is not an object, so doesn't have an associated realm. Accessing
224    // string properties will create a transient object wrapper belonging to the
225    // current realm. So checking the realm of result.value is not useful.
226  }, 'the result object when read is called after write should come from the ' +
227     'same realm as the constructor of TextDecoderStream');
228
229  promise_test(async () => {
230    const objId = await constructAndStore('TextDecoderStream');
231    const chunkPromise = readInReadRealm(objId);
232    writeInWriteRealm(objId, new Uint8Array([65]));
233    // Now the read() should resolve.
234    const result = await chunkPromise;
235    assert_equals(result.constructor, constructorRealm.Object,
236                  'result should be in constructor realm');
237    // A string is not an object, so doesn't have an associated realm. Accessing
238    // string properties will create a transient object wrapper belonging to the
239    // current realm. So checking the realm of result.value is not useful.
240  }, 'the result object when write is called with a pending ' +
241     'read should come from the same realm as the constructor of TextDecoderStream');
242
243  promise_test(async t => {
244    const objId = await constructAndStore('TextDecoderStream');
245    // Read first to relieve backpressure.
246    const readPromise = readInReadRealm(objId);
247    await promise_rejects_js(
248      t, constructorRealm.TypeError,
249      writeInWriteRealm(objId, {}),
250      'write TypeError should come from constructor realm'
251    );
252
253    return promise_rejects_js(
254      t, constructorRealm.TypeError, readPromise,
255      'read TypeError should come from constructor realm'
256    );
257  }, 'TypeError for chunk with the wrong type should come from constructor ' +
258     'realm of TextDecoderStream');
259
260  promise_test(async t => {
261    const objId =
262          await constructAndStore(`TextDecoderStream('utf-8', {fatal: true})`);
263    // Read first to relieve backpressure.
264    const readPromise = readInReadRealm(objId);
265
266    await promise_rejects_js(
267      t, constructorRealm.TypeError,
268      writeInWriteRealm(objId, new Uint8Array([0xff])),
269      'write TypeError should come from constructor realm'
270    );
271
272    return promise_rejects_js(
273      t, constructorRealm.TypeError, readPromise,
274      'read TypeError should come from constructor realm'
275    );
276  }, 'TypeError for invalid chunk should come from constructor realm ' +
277     'of TextDecoderStream');
278
279  promise_test(async t => {
280    const objId =
281          await constructAndStore(`TextDecoderStream('utf-8', {fatal: true})`);
282    // Read first to relieve backpressure.
283    readInReadRealm(objId);
284    // Write an unfinished sequence of bytes.
285    const incompleteBytesId = id();
286    writeRealm[incompleteBytesId] = new Uint8Array([0xf0]);
287
288    return promise_rejects_js(
289      t, constructorRealm.TypeError,
290      // Can't use writeInWriteRealm() here because it doesn't make it possible
291      // to reuse the writer.
292      evalInRealmAndReturn(writeRealm, `
293(() => {
294  const writer = window.${objId}.writable.getWriter();
295  parent.writeMethod.call(writer, window.${incompleteBytesId});
296  return parent.methodRealm.WritableStreamDefaultWriter.prototype
297    .close.call(writer);
298})();
299`),
300      'close TypeError should come from constructor realm'
301    );
302  }, 'TypeError for incomplete input should come from constructor realm ' +
303     'of TextDecoderStream');
304}
305