1<!doctype html>
2<meta charset="utf-8">
3<title>Ensure Stream objects are created in expected globals. </title>
4
5<script src="/resources/testharness.js"></script>
6<script src="/resources/testharnessreport.js"></script>
7
8<body></body>
9<script>
10// These tests are loosely derived from Gecko's readable-stream-globals.js,
11// which is a test case designed around the JS Streams implementation.
12//
13// Unlike in JS Streams, where function calls switch realms and change
14// the resulting global of the resulting objects, in WebIDL streams,
15// the global of an object is (currently underspecified, but) intended
16// to be the "Relevant Global" of the 'this' object.
17//
18// See:
19// https://html.spec.whatwg.org/multipage/webappapis.html#relevant
20// https://github.com/whatwg/streams/issues/1213
21"use strict"
22
23const iframe = document.createElement("iframe")
24document.body.append(iframe)
25
26const otherGlobal = iframe.contentWindow;
27const OtherReadableStream = otherGlobal.ReadableStream
28const OtherReadableStreamDefaultReader = otherGlobal.ReadableStreamDefaultReader;
29const OtherReadableStreamDefaultController = otherGlobal.ReadableStreamDefaultController;
30
31promise_test(async () => {
32
33    // Controllers
34    let controller;
35    let otherController;
36
37    // Get Stream Prototypes and controllers.
38    let streamController;
39    let stream = new ReadableStream({start(c) { streamController = c; }});
40
41    const callReaderThisGlobal = OtherReadableStream.prototype.getReader.call(stream);
42    const newReaderOtherGlobal = new OtherReadableStreamDefaultReader(new ReadableStream());
43
44    // Relevant Global Checking.
45    assert_equals(callReaderThisGlobal instanceof ReadableStreamDefaultReader, true, "reader was created in this global (.call)");
46    assert_equals(newReaderOtherGlobal instanceof ReadableStreamDefaultReader, false, "reader was created in other global (new)");
47
48    assert_equals(callReaderThisGlobal instanceof OtherReadableStreamDefaultReader, false, "reader isn't coming from other global (.call)" );
49    assert_equals(newReaderOtherGlobal instanceof OtherReadableStreamDefaultReader, true, "reader isn't coming from other global (new)");
50
51    assert_equals(otherController instanceof ReadableStreamDefaultController, false, "otherController should come from other gloal")
52
53
54    const request = callReaderThisGlobal.read();
55    assert_equals(request instanceof Promise, true, "Promise comes from this global");
56
57    streamController.close();
58    const requestResult = await request;
59    assert_equals(requestResult instanceof Object, true, "returned object comes from this global");
60}, "Stream objects created in expected globals")
61
62promise_test(async () => {
63    const stream = new ReadableStream();
64    const otherReader = new OtherReadableStreamDefaultReader(stream);
65    const cancelPromise = ReadableStreamDefaultReader.prototype.cancel.call(otherReader);
66    assert_equals(cancelPromise instanceof Promise, true, "Cancel promise comes from the same global as the stream");
67    assert_equals(await cancelPromise, undefined, "Cancel promise resolves to undefined");
68}, "Cancel promise is created in same global as stream")
69
70// Refresh the streams and controllers.
71function getFreshInstances() {
72    let controller;
73    let otherController;
74    let stream = new ReadableStream({
75        start(c) {
76            controller = c;
77        }
78    });
79
80    new OtherReadableStream({
81        start(c) {
82            otherController = c;
83        }
84    });
85
86    return {stream, controller, otherController}
87}
88
89
90promise_test(async () => {
91    // Test closed promise on reader from another global (connected to a this-global stream)
92    const {stream, controller, otherController} = getFreshInstances();
93
94    const otherReader = new OtherReadableStreamDefaultReader(stream);
95    const closedPromise = otherReader.closed;
96    assert_equals(closedPromise instanceof otherGlobal.Promise, true, "Closed promise in other global.");
97}, "Closed Promise in correct global");
98
99promise_test(async () => {
100    const {stream, controller, otherController} = getFreshInstances();
101
102    const otherReader = OtherReadableStream.prototype.getReader.call(stream);
103    assert_equals(otherReader instanceof ReadableStreamDefaultReader, true, "Reader comes from this global")
104    const request = otherReader.read();
105    assert_equals(request instanceof Promise, true, "Promise still comes from stream's realm (this realm)");
106    otherController.close.call(controller);
107    assert_equals((await request) instanceof otherGlobal.Object, true, "Object comes from other realm");
108}, "Reader objects in correct global");
109
110
111promise_test(async () => {
112    const {stream, controller, otherController} = getFreshInstances();
113    assert_equals(controller.desiredSize, 1, "Desired size is expected");
114    Object.defineProperty(controller, "desiredSize",
115        Object.getOwnPropertyDescriptor(OtherReadableStreamDefaultController.prototype, "desiredSize"));
116    assert_equals(controller.desiredSize, 1, "Grafting getter from other prototype still returns desired size");
117}, "Desired size can be grafted from one prototype to another");
118
119promise_test(async () => {
120    const {stream, controller, otherController} = getFreshInstances();
121
122    // Make sure the controller close method returns the correct TypeError
123    const enqueuedError = { name: "enqueuedError" };
124    controller.error(enqueuedError);
125
126    assert_throws_js(TypeError, () => controller.close(),  "Current Global controller");
127    assert_throws_js(otherGlobal.TypeError, () => otherController.close.call(controller),  "Other global controller");
128}, "Closing errored stream throws object in appropriate global")
129
130promise_test(async () => {
131    const {otherController} = getFreshInstances();
132    // We can enqueue chunks from multiple globals
133    const chunk = { name: "chunk" };
134
135    let controller;
136    const stream = new ReadableStream({ start(c) { controller = c; } }, { size() {return 1} });
137    otherController.enqueue.call(controller, chunk);
138    otherController.enqueue.call(controller, new otherGlobal.Uint8Array(10));
139    controller.enqueue(new otherGlobal.Uint8Array(10));
140}, "Can enqueue chunks from multiple globals")
141
142promise_test(async () => {
143    const {stream, controller, otherController} = getFreshInstances();
144    const chunk = { name: "chunk" };
145
146    // We get the correct type errors out of a closed stream.
147    controller.close();
148    assert_throws_js(TypeError, () => controller.enqueue(new otherGlobal.Uint8Array(10)));
149    assert_throws_js(otherGlobal.TypeError, () => otherController.enqueue.call(controller, chunk));
150    assert_throws_js(otherGlobal.TypeError, () => otherController.enqueue.call(controller, new otherGlobal.Uint8Array(10)));
151}, "Correct errors and globals for closed streams");
152
153
154promise_test(async () => {
155    const {stream, controller, otherController} = getFreshInstances();
156    // Branches out of tee are in the correct global
157
158    const [branch1, branch2] = otherGlobal.ReadableStream.prototype.tee.call(stream);
159    assert_equals(branch1 instanceof ReadableStream, true, "Branch created in this global (as stream is in this global)");
160    assert_equals(branch2 instanceof ReadableStream, true, "Branch created in this global (as stream is in this global)");
161}, "Tee Branches in correct global");
162</script>
163