summaryrefslogtreecommitdiffstats
path: root/js/src/tests/non262/ReadableStream
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/tests/non262/ReadableStream/basic-pull.js37
-rw-r--r--js/src/tests/non262/ReadableStream/basic-push.js53
-rw-r--r--js/src/tests/non262/ReadableStream/bug-1501502.js25
-rw-r--r--js/src/tests/non262/ReadableStream/bug-1549768.js18
-rw-r--r--js/src/tests/non262/ReadableStream/closed-is-handled.js27
-rw-r--r--js/src/tests/non262/ReadableStream/constructor-default.js27
-rw-r--r--js/src/tests/non262/ReadableStream/readable-stream-globals.js367
-rw-r--r--js/src/tests/non262/ReadableStream/shell.js30
-rw-r--r--js/src/tests/non262/ReadableStream/subclassing.js111
-rw-r--r--js/src/tests/non262/ReadableStream/tee-start.js11
10 files changed, 706 insertions, 0 deletions
diff --git a/js/src/tests/non262/ReadableStream/basic-pull.js b/js/src/tests/non262/ReadableStream/basic-pull.js
new file mode 100644
index 0000000000..760937c67a
--- /dev/null
+++ b/js/src/tests/non262/ReadableStream/basic-pull.js
@@ -0,0 +1,37 @@
+// |reftest| skip-if(!this.ReadableStream||!this.drainJobQueue)
+
+if ("ignoreUnhandledRejections" in this) {
+ ignoreUnhandledRejections();
+}
+
+// Example of a stream that produces data on demand, the "pull" model.
+let fibStream = new ReadableStream({
+ start(controller) {
+ this.a = 0;
+ this.b = 1;
+ controller.enqueue(0);
+ controller.enqueue(1);
+ },
+
+ pull(controller) {
+ [this.a, this.b] = [this.b, this.a + this.b];
+ controller.enqueue(this.b);
+ }
+});
+
+async function test() {
+ assertEq(fibStream.locked, false);
+ let reader = fibStream.getReader();
+ assertEq(fibStream.locked, true);
+
+ let results = [];
+ while (results.length < 10) {
+ results.push((await reader.read()).value);
+ }
+
+ assertEq(results.join(), "0,1,1,2,3,5,8,13,21,34");
+ reader.releaseLock();
+ assertEq(fibStream.locked, false);
+}
+
+runAsyncTest(test);
diff --git a/js/src/tests/non262/ReadableStream/basic-push.js b/js/src/tests/non262/ReadableStream/basic-push.js
new file mode 100644
index 0000000000..65bb29880d
--- /dev/null
+++ b/js/src/tests/non262/ReadableStream/basic-push.js
@@ -0,0 +1,53 @@
+// |reftest| skip-if(!this.ReadableStream||!this.drainJobQueue)
+
+if ("ignoreUnhandledRejections" in this) {
+ ignoreUnhandledRejections();
+}
+
+// Example of a stream that enqueues data asynchronously, whether the reader
+// wants it or not, the "push" model.
+let fbStream = new ReadableStream({
+ start(controller) {
+ simulatePacketsDriftingIn(controller);
+ },
+});
+
+async function simulatePacketsDriftingIn(controller) {
+ for (let i = 1; i <= 30; i++) {
+ let importantData =
+ (i % 15 == 0 ? "FizzBuzz" :
+ i % 5 == 0 ? "Buzz":
+ i % 3 == 0 ? "Fizz" :
+ String(i));
+ controller.enqueue(importantData);
+ await asyncSleep(1 + i % 7);
+ }
+ controller.close();
+}
+
+const expected = [
+ "1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz",
+ "11", "Fizz", "13", "14", "FizzBuzz", "16", "17", "Fizz", "19", "Buzz",
+ "Fizz", "22", "23", "Fizz", "Buzz", "26", "Fizz", "28", "29", "FizzBuzz"
+];
+
+async function test() {
+ assertEq(fbStream.locked, false);
+ let reader = fbStream.getReader();
+ assertEq(fbStream.locked, true);
+
+ let results = [];
+ while (true) {
+ let r = await reader.read();
+ if (r.done) {
+ break;
+ }
+ results.push(r.value);
+ }
+
+ assertEq(results.join("-"), expected.join("-"));
+ reader.releaseLock();
+ assertEq(fbStream.locked, false);
+}
+
+runAsyncTest(test);
diff --git a/js/src/tests/non262/ReadableStream/bug-1501502.js b/js/src/tests/non262/ReadableStream/bug-1501502.js
new file mode 100644
index 0000000000..e6b5c0c85b
--- /dev/null
+++ b/js/src/tests/non262/ReadableStream/bug-1501502.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('ReadableStream'))
+// A stream can become errored with an exception from another realm.
+
+let g = newGlobal();
+let g_enqueue;
+new g.ReadableStream({
+ start(controller) {
+ g_enqueue = controller.enqueue;
+ },
+});
+
+let controller;
+let stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+}, {
+ size(chunk) {}
+});
+
+assertThrowsInstanceOf(() => g_enqueue.call(controller, {}), g.RangeError);
+
+if (typeof reportCompare == 'function')
+ reportCompare(0, 0);
+
diff --git a/js/src/tests/non262/ReadableStream/bug-1549768.js b/js/src/tests/non262/ReadableStream/bug-1549768.js
new file mode 100644
index 0000000000..747cb895a9
--- /dev/null
+++ b/js/src/tests/non262/ReadableStream/bug-1549768.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('ReadableStream'))
+
+var otherGlobal = newGlobal({ newCompartment: true });
+var obj = { start(c) { } };
+var Cancel = otherGlobal.ReadableStream.prototype.tee.call(new ReadableStream(obj))[0].cancel;
+
+var stream = new ReadableStream(obj);
+var [branch1, branch2] = ReadableStream.prototype.tee.call(stream);
+
+Cancel.call(branch1, {});
+
+gczeal(2, 1);
+
+Cancel.call(branch2, {});
+
+if (typeof reportCompare === 'function') {
+ reportCompare(0, 0);
+}
diff --git a/js/src/tests/non262/ReadableStream/closed-is-handled.js b/js/src/tests/non262/ReadableStream/closed-is-handled.js
new file mode 100644
index 0000000000..276cfbbaff
--- /dev/null
+++ b/js/src/tests/non262/ReadableStream/closed-is-handled.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.ReadableStream||!this.drainJobQueue)
+
+// 3.5.6. ReadableStreamError ( stream, e ) nothrow
+//
+// 9. Reject reader.[[closedPromise]] with e.
+// 10. Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
+//
+// Rejection for [[closedPromise]] shouldn't be reported as unhandled.
+
+const rs = new ReadableStream({
+ start() {
+ return Promise.reject(new Error("test"));
+ }
+});
+
+let rejected = false;
+rs.getReader().read().then(() => {}, () => { rejected = true; });
+
+drainJobQueue();
+
+assertEq(rejected, true);
+
+if (typeof reportCompare === 'function') {
+ reportCompare(0, 0);
+}
+
+// Shell itself reports unhandled rejection if there's any.
diff --git a/js/src/tests/non262/ReadableStream/constructor-default.js b/js/src/tests/non262/ReadableStream/constructor-default.js
new file mode 100644
index 0000000000..9f60204c72
--- /dev/null
+++ b/js/src/tests/non262/ReadableStream/constructor-default.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('ReadableStream'))
+
+// The second argument to `new ReadableStream` defaults to `{}`, so it observes
+// properties hacked onto Object.prototype.
+
+let log = [];
+
+Object.defineProperty(Object.prototype, "size", {
+ configurable: true,
+ get() {
+ log.push("size");
+ log.push(this);
+ return undefined;
+ }
+});
+Object.prototype.highWaterMark = 1337;
+
+let s = new ReadableStream({
+ start(controller) {
+ log.push("start");
+ log.push(controller.desiredSize);
+ }
+});
+assertDeepEq(log, ["size", {}, "start", 1337]);
+
+if (typeof reportCompare == "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/ReadableStream/readable-stream-globals.js b/js/src/tests/non262/ReadableStream/readable-stream-globals.js
new file mode 100644
index 0000000000..37e73d5cbf
--- /dev/null
+++ b/js/src/tests/non262/ReadableStream/readable-stream-globals.js
@@ -0,0 +1,367 @@
+// |reftest| skip-if(!this.hasOwnProperty("ReadableStream"))
+
+if ("ignoreUnhandledRejections" in this) {
+ ignoreUnhandledRejections();
+}
+
+async function test() {
+ if (typeof newGlobal !== 'undefined') {
+ otherGlobal = newGlobal();
+ }
+
+ OtherReadableStream = otherGlobal.ReadableStream;
+
+ ReadableStreamReader = new ReadableStream().getReader().constructor;
+ OtherReadableStreamReader = new otherGlobal.ReadableStream().getReader().constructor;
+
+ let byteStreamsSupported = false;
+ try {
+ let controller;
+ let reader = new ReadableStream({
+ start(c) {
+ ByteStreamController = c.constructor;
+ controller = c;
+ },
+ type: "bytes"
+ }).getReader({ mode: "byob" })
+ ReadableStreamBYOBReader = reader.constructor;
+ reader.read(new Uint8Array(10));
+ BYOBRequest = controller.byobRequest.constructor;
+ reader = new otherGlobal.ReadableStream({
+ start(c) {
+ OtherByteStreamController = c.constructor;
+ controller = c;
+ },
+ type: "bytes"
+ }).getReader({ mode: "byob" });
+ OtherReadableStreamBYOBReader = reader.constructor;
+ reader.read(new Uint8Array(10));
+ OtherBYOBRequest = controller.byobRequest.constructor;
+
+ BYOBRequestGetter = Object.getOwnPropertyDescriptor(ByteStreamController.prototype,
+ "byobRequest").get;
+ OtherBYOBRequestGetter = Object.getOwnPropertyDescriptor(OtherByteStreamController.prototype,
+ "byobRequest").get;
+
+ byteStreamsSupported = true;
+ } catch (e) {
+ }
+
+ let chunk = { name: "chunk" };
+ let enqueuedError = { name: "enqueuedError" };
+
+ let controller;
+ let stream;
+ let otherStream;
+ let otherController;
+ let reader;
+ let otherReader;
+
+ function getFreshInstances(type, otherType = type) {
+ stream = new ReadableStream({ start(c) { controller = c; }, type });
+
+ otherStream = new OtherReadableStream({ start(c) { otherController = c; }, type: otherType });
+ }
+
+ getFreshInstances();
+
+ Controller = controller.constructor;
+ OtherController = otherController.constructor;
+
+
+ otherReader = OtherReadableStream.prototype.getReader.call(stream);
+ assertEq(otherReader instanceof ReadableStreamReader, false);
+ assertEq(otherReader instanceof OtherReadableStreamReader, true);
+ assertEq(otherController instanceof Controller, false);
+
+ assertEq(stream.locked, true);
+ Object.defineProperty(stream, "locked",
+ Object.getOwnPropertyDescriptor(OtherReadableStream.prototype, "locked"));
+ assertEq(stream.locked, true);
+
+
+ request = otherReader.read();
+ assertEq(request instanceof otherGlobal.Promise, true);
+ controller.close();
+ assertEq(await request instanceof Object, true);
+
+ getFreshInstances();
+ otherReader = new OtherReadableStreamReader(stream);
+
+ getFreshInstances();
+ otherReader = new OtherReadableStreamReader(stream);
+ let cancelSucceeded = false;
+ let cancelPromise = ReadableStreamReader.prototype.cancel.call(otherReader);
+ assertEq(cancelPromise instanceof Promise, true);
+ assertEq(await cancelPromise, undefined);
+
+ getFreshInstances();
+ otherReader = new OtherReadableStreamReader(stream);
+ let closeSucceeded = false;
+ Object.defineProperty(otherReader, "closed",
+ Object.getOwnPropertyDescriptor(ReadableStreamReader.prototype, "closed"));
+ let closedPromise = otherReader.closed;
+ assertEq(closedPromise instanceof otherGlobal.Promise, true);
+ controller.close();
+ assertEq(await closedPromise, undefined);
+
+ getFreshInstances();
+
+ otherReader = OtherReadableStream.prototype.getReader.call(stream);
+ request = otherReader.read();
+ assertEq(request instanceof otherGlobal.Promise, true);
+ otherController.close.call(controller);
+ assertEq(await request instanceof otherGlobal.Object, true);
+
+ getFreshInstances();
+
+ assertEq(controller.desiredSize, 1);
+ Object.defineProperty(controller, "desiredSize",
+ Object.getOwnPropertyDescriptor(OtherController.prototype, "desiredSize"));
+ assertEq(controller.desiredSize, 1);
+
+
+ request = otherReader.read();
+
+ controller.error(enqueuedError);
+
+ expectException(() => controller.close(), TypeError);
+ expectException(() => otherController.close.call(controller), otherGlobal.TypeError);
+
+ otherReader.releaseLock();
+
+ reader = stream.getReader();
+ assertEq(await expectAsyncException(async () => reader.read(), enqueuedError.constructor),
+ enqueuedError);
+
+ otherReader.releaseLock.call(reader);
+ assertEq(reader.closed instanceof otherGlobal.Promise, true);
+
+ // getFreshInstances();
+
+ // reader = stream.getReader();
+ // request = otherReader.read.call(reader);
+ // assertEq(request instanceof otherGlobal.Promise, true);
+ // controller.enqueue(chunk);
+ // assertEq((await request).value, chunk);
+
+ // reader.releaseLock();
+
+ // getFreshInstances();
+
+ // reader = stream.getReader();
+ // request = otherReader.read.call(reader);
+ // otherController.enqueue.call(controller, chunk);
+ // otherController.enqueue.call(controller, new otherGlobal.Uint8Array(10));
+ // controller.enqueue(new otherGlobal.Uint8Array(10));
+ // request = otherReader.read.call(reader);
+
+ getFreshInstances();
+
+ 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));
+
+ getFreshInstances();
+
+ controller.close();
+ expectException(() => controller.enqueue(new otherGlobal.Uint8Array(10)), TypeError);
+ expectException(() => otherController.enqueue.call(controller, chunk), otherGlobal.TypeError);
+ expectException(() => otherController.enqueue.call(controller, new otherGlobal.Uint8Array(10)),
+ otherGlobal.TypeError);
+
+ getFreshInstances();
+
+ let [branch1, branch2] = otherGlobal.ReadableStream.prototype.tee.call(stream);
+ assertEq(branch1 instanceof otherGlobal.ReadableStream, true);
+ assertEq(branch2 instanceof otherGlobal.ReadableStream, true);
+
+ controller.enqueue(chunk);
+ reader = branch1.getReader();
+ result = await reader.read();
+ reader.releaseLock();
+ let subPromiseCreated = false;
+ let speciesInvoked = false;
+ class SubPromise extends Promise {
+ constructor(executor) {
+ super(executor);
+ subPromiseCreated = true;
+ }
+ }
+ Object.defineProperty(Promise, Symbol.species, {get: function() {
+ speciesInvoked = true;
+ return SubPromise;
+ }
+ });
+
+ otherGlobal.eval(`
+ subPromiseCreated = false;
+ speciesInvoked = false;
+ class OtherSubPromise extends Promise {
+ constructor(executor) {
+ super(executor);
+ subPromiseCreated = true;
+ }
+ }
+ Object.defineProperty(Promise, Symbol.species, {get: function() {
+ speciesInvoked = true;
+ return OtherSubPromise;
+ }
+ });`);
+
+ controller.error(enqueuedError);
+ subPromiseCreated = false;
+ speciesInvoked = false;
+ otherGlobal.subPromiseCreated = false;
+ otherGlobal.speciesInvoked = false;
+ let cancelPromise1 = branch1.cancel({ name: "cancel 1" });
+ assertEq(cancelPromise1 instanceof otherGlobal.Promise, true);
+ assertEq(subPromiseCreated, false);
+ assertEq(speciesInvoked, false);
+ assertEq(otherGlobal.subPromiseCreated, false);
+ assertEq(otherGlobal.speciesInvoked, false);
+ subPromiseCreated = false;
+ speciesInvoked = false;
+ otherGlobal.subPromiseCreated = false;
+ otherGlobal.speciesInvoked = false;
+ let cancelPromise2 = branch2.cancel({ name: "cancel 2" });
+ assertEq(cancelPromise2 instanceof otherGlobal.Promise, true);
+ assertEq(subPromiseCreated, false);
+ assertEq(speciesInvoked, false);
+ assertEq(otherGlobal.subPromiseCreated, false);
+ assertEq(otherGlobal.speciesInvoked, false);
+ await 1;
+
+
+ getFreshInstances();
+
+ [branch1, branch2] = otherGlobal.ReadableStream.prototype.tee.call(stream);
+ assertEq(branch1 instanceof otherGlobal.ReadableStream, true);
+ assertEq(branch2 instanceof otherGlobal.ReadableStream, true);
+
+ controller.enqueue(chunk);
+ reader = branch1.getReader();
+ result = await reader.read();
+ reader.releaseLock();
+
+
+ assertEq(result.value, chunk);
+
+ controller.error(enqueuedError);
+ subPromiseCreated = false;
+ speciesInvoked = false;
+ otherGlobal.subPromiseCreated = false;
+ otherGlobal.speciesInvoked = false;
+ cancelPromise1 = ReadableStream.prototype.cancel.call(branch1, { name: "cancel 1" });
+ assertEq(cancelPromise1 instanceof Promise, true);
+ assertEq(subPromiseCreated, false);
+ assertEq(speciesInvoked, false);
+ assertEq(otherGlobal.subPromiseCreated, false);
+ assertEq(otherGlobal.speciesInvoked, false);
+ subPromiseCreated = false;
+ speciesInvoked = false;
+ otherGlobal.subPromiseCreated = false;
+ otherGlobal.speciesInvoked = false;
+ cancelPromise2 = ReadableStream.prototype.cancel.call(branch2, { name: "cancel 2" });
+ assertEq(cancelPromise2 instanceof Promise, true);
+ assertEq(subPromiseCreated, false);
+ assertEq(speciesInvoked, false);
+ assertEq(otherGlobal.subPromiseCreated, false);
+ assertEq(otherGlobal.speciesInvoked, false);
+
+ if (!byteStreamsSupported) {
+ return;
+ }
+
+ if (typeof nukeCCW === 'function') {
+ getFreshInstances("bytes");
+ assertEq(otherController instanceof OtherByteStreamController, true);
+ reader = ReadableStream.prototype.getReader.call(otherStream);
+ otherGlobal.reader = reader;
+ otherGlobal.nukeCCW(otherGlobal.reader);
+ let chunk = new Uint8Array(10);
+ expectException(() => otherController.enqueue(chunk), otherGlobal.TypeError);
+ // otherController.error();
+ expectException(() => reader.read(), TypeError);
+ }
+
+ function testBYOBRequest(controller, view) {
+ const request = new BYOBRequest(controller, view);
+ let storedView = request.view;
+ assertEq(storedView, view);
+ storedView = Object.getOwnPropertyDescriptor(OtherBYOBRequest.prototype, "view").get.call(request);
+ assertEq(storedView, view);
+ request.respond(10);
+ OtherBYOBRequest.prototype.respond.call(request, 10);
+ request.respondWithNewView(new view.constructor(10));
+ OtherBYOBRequest.prototype.respondWithNewView.call(request, new view.constructor(10));
+ }
+
+ expectException(() => new BYOBRequest(), TypeError);
+ getFreshInstances("bytes");
+ expectException(() => new BYOBRequest(controller, new Uint8Array(10)), TypeError);
+ expectException(() => new BYOBRequest(otherController, new Uint8Array(10)), TypeError);
+ expectException(() => new BYOBRequest(otherController, new Uint8Array(10)), TypeError);
+ expectException(() => new BYOBRequest(otherController, new otherGlobal.Uint8Array(10)), TypeError);
+
+ getFreshInstances("bytes");
+
+ reader = stream.getReader({ mode: "byob" });
+ request = OtherReadableStreamBYOBReader.prototype.read.call(reader, new Uint8Array(10));
+ assertEq(request instanceof otherGlobal.Promise, true);
+ controller.enqueue(new Uint8Array([1, 2, 3, 4]));
+ result = await request;
+
+ getFreshInstances("bytes");
+
+ reader = stream.getReader({ mode: "byob" });
+ request = OtherReadableStreamBYOBReader.prototype.read.call(reader, new Uint8Array(10));
+ assertEq(request instanceof otherGlobal.Promise, true);
+ try {
+ let byobRequest = OtherBYOBRequestGetter.call(controller);
+ } catch (e) {
+ print(e, '\n', e.stack);
+ }
+ controller.enqueue(new Uint8Array([1, 2, 3, 4]));
+ result = await request;
+
+ await 1;
+}
+
+function expectException(closure, errorType) {
+ let error;
+ try {
+ closure();
+ } catch (e) {
+ error = e;
+ }
+ assertEq(error !== undefined, true);
+ assertEq(error.constructor, errorType);
+ return error;
+}
+
+async function expectAsyncException(closure, errorType) {
+ let error;
+ try {
+ await closure();
+ } catch (e) {
+ error = e;
+ }
+ assertEq(error !== undefined, true);
+ assertEq(error.constructor, errorType);
+ return error;
+}
+
+async function runTest() {
+ try {
+ await test();
+ } catch (e) {
+ assertEq(false, true, `Unexpected exception ${e}\n${e.stack}`);
+ }
+ console.log("done");
+ if (typeof reportCompare === "function")
+ reportCompare(true, true);
+}
+
+runTest();
diff --git a/js/src/tests/non262/ReadableStream/shell.js b/js/src/tests/non262/ReadableStream/shell.js
new file mode 100644
index 0000000000..7e83ed2934
--- /dev/null
+++ b/js/src/tests/non262/ReadableStream/shell.js
@@ -0,0 +1,30 @@
+// Return a promise that will resolve to `undefined` the next time jobs are
+// processed.
+//
+// `ticks` indicates how long the promise should "wait" before resolving: a
+// promise created with `asyncSleep(n)` will become settled and fire its handlers
+// before a promise created with `asyncSleep(n+1)`.
+//
+function asyncSleep(ticks) {
+ let p = Promise.resolve();
+ if (ticks > 0) {
+ return p.then(() => asyncSleep(ticks - 1));
+ }
+ return p;
+}
+
+// Run the async function `test`. Wait for it to finish running. Throw if it
+// throws or if it fails to finish (awaiting a value forever).
+function runAsyncTest(test) {
+ let passed = false;
+ let problem = "test did not finish";
+ test()
+ .then(_ => { passed = true; })
+ .catch(exc => { problem = exc; });
+ drainJobQueue();
+ if (!passed) {
+ throw problem;
+ }
+
+ reportCompare(0, 0);
+}
diff --git a/js/src/tests/non262/ReadableStream/subclassing.js b/js/src/tests/non262/ReadableStream/subclassing.js
new file mode 100644
index 0000000000..813ed278c2
--- /dev/null
+++ b/js/src/tests/non262/ReadableStream/subclassing.js
@@ -0,0 +1,111 @@
+// |reftest| skip-if(!xulRuntime.shell||!this.hasOwnProperty('ReadableStream')) -- needs drainJobQueue
+
+if ("ignoreUnhandledRejections" in this) {
+ ignoreUnhandledRejections();
+}
+
+// Spot-check subclassing of stream constructors.
+
+// ReadableStream can be subclassed.
+class PartyStreamer extends ReadableStream {}
+
+// The base class constructor is called.
+let started = false;
+let stream = new PartyStreamer({
+ // (The ReadableStream constructor calls this start method.)
+ start(c) { started = true; }
+});
+drainJobQueue();
+assertEq(started, true);
+
+// The instance's prototype chain is correct.
+assertEq(stream.__proto__, PartyStreamer.prototype);
+assertEq(stream.__proto__.__proto__, ReadableStream.prototype);
+assertEq(stream.__proto__.__proto__.__proto__, Object.prototype);
+assertEq(stream.__proto__.__proto__.__proto__.__proto__, null);
+assertEq(stream instanceof ReadableStream, true);
+
+// Non-generic methods can be called on the resulting stream.
+let reader = stream.getReader();
+assertEq(stream.locked, true);
+
+
+// CountQueuingStrategy can be subclassed.
+class PixelStrategy extends CountQueuingStrategy {}
+assertEq(new PixelStrategy({highWaterMark: 4}).__proto__, PixelStrategy.prototype);
+
+// The base class constructor is called.
+assertThrowsInstanceOf(() => new PixelStrategy, TypeError);
+assertEq(new PixelStrategy({highWaterMark: -1}).highWaterMark, -1);
+
+
+// VerySmartStrategy can be subclassed.
+class VerySmartStrategy extends ByteLengthQueuingStrategy {
+ size(chunk) {
+ return super.size(chunk) * 8;
+ }
+}
+let vss = new VerySmartStrategy({highWaterMark: 12});
+assertEq(vss.size(new ArrayBuffer(8)), 64);
+assertEq(vss.__proto__, VerySmartStrategy.prototype);
+
+
+// Even ReadableStreamDefaultReader can be subclassed.
+async function readerTest() {
+ const ReadableStreamDefaultReader = new ReadableStream().getReader().constructor;
+ class MindReader extends ReadableStreamDefaultReader {
+ async read() {
+ let foretold = {value: "death", done: false};
+ let actual = await super.read();
+ actual = foretold; // ZOMG I WAS RIGHT, EXACTLY AS FORETOLD they should call me a righter
+ return actual;
+ }
+ }
+
+ let stream = new ReadableStream({
+ start(c) { c.enqueue("one"); c.enqueue("two"); },
+ pull(c) { c.close(); }
+ });
+ let reader = new MindReader(stream);
+ let result = await reader.read();
+ assertEq(result.value, "death");
+ reader.releaseLock();
+
+ reader = stream.getReader();
+ result = await reader.read();
+ assertEq(result.done, false);
+ assertEq(result.value, "two");
+ result = await reader.read();
+ assertEq(result.done, true);
+ assertEq(result.value, undefined);
+}
+runAsyncTest(readerTest);
+
+
+// Even ReadableStreamDefaultController, which can't be constructed,
+// can be subclassed.
+let ReadableStreamDefaultController;
+new ReadableStream({
+ start(c) {
+ ReadableStreamDefaultController = c.constructor;
+ }
+});
+class MasterController extends ReadableStreamDefaultController {
+ constructor() {
+ // don't call super, it'll just throw
+ return Object.create(MasterController.prototype);
+ }
+}
+let c = new MasterController();
+
+// The prototype chain is per spec.
+assertEq(c instanceof ReadableStreamDefaultController, true);
+
+// But the instance does not have the internal slots of a
+// ReadableStreamDefaultController, so the non-generic methods can't be used.
+assertThrowsInstanceOf(() => c.enqueue("horse"), TypeError);
+
+
+if (typeof reportCompare === 'function') {
+ reportCompare(0, 0);
+}
diff --git a/js/src/tests/non262/ReadableStream/tee-start.js b/js/src/tests/non262/ReadableStream/tee-start.js
new file mode 100644
index 0000000000..dbb6b1142d
--- /dev/null
+++ b/js/src/tests/non262/ReadableStream/tee-start.js
@@ -0,0 +1,11 @@
+// |reftest| skip-if(!this.ReadableStream||!this.drainJobQueue)
+// stream.tee() shouldn't try to call a .start() method.
+
+Object.prototype.start = function () { throw "FAIL"; };
+let source = Object.create(null);
+new ReadableStream(source).tee();
+
+drainJobQueue();
+
+if (typeof reportCompare == 'function')
+ reportCompare(0, 0);