| <!doctype html> |
| <meta charset="utf-8"> |
| <title>Ensure Stream objects are created in expected globals. </title> |
| |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| |
| <body></body> |
| <script> |
| // These tests are loosely derived from Gecko's readable-stream-globals.js, |
| // which is a test case designed around the JS Streams implementation. |
| // |
| // Unlike in JS Streams, where function calls switch realms and change |
| // the resulting global of the resulting objects, in WebIDL streams, |
| // the global of an object is (currently underspecified, but) intended |
| // to be the "Relevant Global" of the 'this' object. |
| // |
| // See: |
| // https://html.spec.whatwg.org/multipage/webappapis.html#relevant |
| // https://github.com/whatwg/streams/issues/1213 |
| "use strict" |
| |
| const iframe = document.createElement("iframe") |
| document.body.append(iframe) |
| |
| const otherGlobal = iframe.contentWindow; |
| const OtherReadableStream = otherGlobal.ReadableStream |
| const OtherReadableStreamDefaultReader = otherGlobal.ReadableStreamDefaultReader; |
| const OtherReadableStreamDefaultController = otherGlobal.ReadableStreamDefaultController; |
| |
| promise_test(async () => { |
| |
| // Controllers |
| let controller; |
| let otherController; |
| |
| // Get Stream Prototypes and controllers. |
| let streamController; |
| let stream = new ReadableStream({start(c) { streamController = c; }}); |
| |
| const callReaderThisGlobal = OtherReadableStream.prototype.getReader.call(stream); |
| const newReaderOtherGlobal = new OtherReadableStreamDefaultReader(new ReadableStream()); |
| |
| // Relevant Global Checking. |
| assert_equals(callReaderThisGlobal instanceof ReadableStreamDefaultReader, true, "reader was created in this global (.call)"); |
| assert_equals(newReaderOtherGlobal instanceof ReadableStreamDefaultReader, false, "reader was created in other global (new)"); |
| |
| assert_equals(callReaderThisGlobal instanceof OtherReadableStreamDefaultReader, false, "reader isn't coming from other global (.call)" ); |
| assert_equals(newReaderOtherGlobal instanceof OtherReadableStreamDefaultReader, true, "reader isn't coming from other global (new)"); |
| |
| assert_equals(otherController instanceof ReadableStreamDefaultController, false, "otherController should come from other gloal") |
| |
| |
| const request = callReaderThisGlobal.read(); |
| assert_equals(request instanceof Promise, true, "Promise comes from this global"); |
| |
| streamController.close(); |
| const requestResult = await request; |
| assert_equals(requestResult instanceof Object, true, "returned object comes from this global"); |
| }, "Stream objects created in expected globals") |
| |
| promise_test(async () => { |
| const stream = new ReadableStream(); |
| const otherReader = new OtherReadableStreamDefaultReader(stream); |
| const cancelPromise = ReadableStreamDefaultReader.prototype.cancel.call(otherReader); |
| assert_equals(cancelPromise instanceof Promise, true, "Cancel promise comes from the same global as the stream"); |
| assert_equals(await cancelPromise, undefined, "Cancel promise resolves to undefined"); |
| }, "Cancel promise is created in same global as stream") |
| |
| // Refresh the streams and controllers. |
| function getFreshInstances() { |
| let controller; |
| let otherController; |
| let stream = new ReadableStream({ |
| start(c) { |
| controller = c; |
| } |
| }); |
| |
| new OtherReadableStream({ |
| start(c) { |
| otherController = c; |
| } |
| }); |
| |
| return {stream, controller, otherController} |
| } |
| |
| |
| promise_test(async () => { |
| // Test closed promise on reader from another global (connected to a this-global stream) |
| const {stream, controller, otherController} = getFreshInstances(); |
| |
| const otherReader = new OtherReadableStreamDefaultReader(stream); |
| const closedPromise = otherReader.closed; |
| assert_equals(closedPromise instanceof otherGlobal.Promise, true, "Closed promise in other global."); |
| }, "Closed Promise in correct global"); |
| |
| promise_test(async () => { |
| const {stream, controller, otherController} = getFreshInstances(); |
| |
| const otherReader = OtherReadableStream.prototype.getReader.call(stream); |
| assert_equals(otherReader instanceof ReadableStreamDefaultReader, true, "Reader comes from this global") |
| const request = otherReader.read(); |
| assert_equals(request instanceof Promise, true, "Promise still comes from stream's realm (this realm)"); |
| otherController.close.call(controller); |
| assert_equals((await request) instanceof otherGlobal.Object, true, "Object comes from other realm"); |
| }, "Reader objects in correct global"); |
| |
| |
| promise_test(async () => { |
| const {stream, controller, otherController} = getFreshInstances(); |
| assert_equals(controller.desiredSize, 1, "Desired size is expected"); |
| Object.defineProperty(controller, "desiredSize", |
| Object.getOwnPropertyDescriptor(OtherReadableStreamDefaultController.prototype, "desiredSize")); |
| assert_equals(controller.desiredSize, 1, "Grafting getter from other prototype still returns desired size"); |
| }, "Desired size can be grafted from one prototype to another"); |
| |
| promise_test(async () => { |
| const {stream, controller, otherController} = getFreshInstances(); |
| |
| // Make sure the controller close method returns the correct TypeError |
| const enqueuedError = { name: "enqueuedError" }; |
| controller.error(enqueuedError); |
| |
| assert_throws_js(TypeError, () => controller.close(), "Current Global controller"); |
| assert_throws_js(otherGlobal.TypeError, () => otherController.close.call(controller), "Other global controller"); |
| }, "Closing errored stream throws object in appropriate global") |
| |
| promise_test(async () => { |
| const {otherController} = getFreshInstances(); |
| // We can enqueue chunks from multiple globals |
| const chunk = { name: "chunk" }; |
| |
| let controller; |
| const stream = new ReadableStream({ start(c) { controller = c; } }, { size() {return 1} }); |
| otherController.enqueue.call(controller, chunk); |
| otherController.enqueue.call(controller, new otherGlobal.Uint8Array(10)); |
| controller.enqueue(new otherGlobal.Uint8Array(10)); |
| }, "Can enqueue chunks from multiple globals") |
| |
| promise_test(async () => { |
| const {stream, controller, otherController} = getFreshInstances(); |
| const chunk = { name: "chunk" }; |
| |
| // We get the correct type errors out of a closed stream. |
| controller.close(); |
| assert_throws_js(TypeError, () => controller.enqueue(new otherGlobal.Uint8Array(10))); |
| assert_throws_js(otherGlobal.TypeError, () => otherController.enqueue.call(controller, chunk)); |
| assert_throws_js(otherGlobal.TypeError, () => otherController.enqueue.call(controller, new otherGlobal.Uint8Array(10))); |
| }, "Correct errors and globals for closed streams"); |
| |
| |
| promise_test(async () => { |
| const {stream, controller, otherController} = getFreshInstances(); |
| // Branches out of tee are in the correct global |
| |
| const [branch1, branch2] = otherGlobal.ReadableStream.prototype.tee.call(stream); |
| assert_equals(branch1 instanceof ReadableStream, true, "Branch created in this global (as stream is in this global)"); |
| assert_equals(branch2 instanceof ReadableStream, true, "Branch created in this global (as stream is in this global)"); |
| }, "Tee Branches in correct global"); |
| </script> |