summaryrefslogtreecommitdiffstats
path: root/dom/streams/test/xpcshell
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/streams/test/xpcshell/bug-1387503-1.js44
-rw-r--r--dom/streams/test/xpcshell/bug-1387503-2.js52
-rw-r--r--dom/streams/test/xpcshell/bug-1503406.js20
-rw-r--r--dom/streams/test/xpcshell/bug-1773237.js11
-rw-r--r--dom/streams/test/xpcshell/dom_stream_prototype_test.js29
-rw-r--r--dom/streams/test/xpcshell/fetch.js35
-rw-r--r--dom/streams/test/xpcshell/head.js45
-rw-r--r--dom/streams/test/xpcshell/large-pipeto.js101
-rw-r--r--dom/streams/test/xpcshell/proper-realm-cancel.js5
-rw-r--r--dom/streams/test/xpcshell/proper-realm-pull.js6
-rw-r--r--dom/streams/test/xpcshell/response.js30
-rw-r--r--dom/streams/test/xpcshell/subclassing.js123
-rw-r--r--dom/streams/test/xpcshell/too-big-array-buffer.js15
-rw-r--r--dom/streams/test/xpcshell/xpcshell.toml32
14 files changed, 548 insertions, 0 deletions
diff --git a/dom/streams/test/xpcshell/bug-1387503-1.js b/dom/streams/test/xpcshell/bug-1387503-1.js
new file mode 100644
index 0000000000..777f614c44
--- /dev/null
+++ b/dom/streams/test/xpcshell/bug-1387503-1.js
@@ -0,0 +1,44 @@
+// Test uncatchable error when a stream source's pull() method is called.
+let readerCreated = false;
+let fnFinished = false;
+let g;
+
+add_task(async function test() {
+ // Make `debugger;` raise an uncatchable error.
+ g = newGlobal({ newCompartment: true });
+ g.parent = this;
+ g.hit = false;
+ g.eval(
+ ` new Debugger(parent).onDebuggerStatement = _frame => (hit = true, null);`
+ );
+
+ // Create a stream whose pull() method raises an uncatchable error,
+ // and try reading from it.
+
+ async function fn() {
+ try {
+ let stream = new ReadableStream({
+ start(controller) {},
+ pull(controller) {
+ // eslint-disable-next-line no-debugger
+ debugger;
+ },
+ });
+
+ let reader = stream.getReader();
+ let p = reader.read();
+ readerCreated = true;
+ await p;
+ } finally {
+ fnFinished = true;
+ }
+ }
+
+ fn();
+});
+
+add_task(() => {
+ equal(readerCreated, true);
+ equal(g.hit, true);
+ equal(fnFinished, false);
+});
diff --git a/dom/streams/test/xpcshell/bug-1387503-2.js b/dom/streams/test/xpcshell/bug-1387503-2.js
new file mode 100644
index 0000000000..1678b46649
--- /dev/null
+++ b/dom/streams/test/xpcshell/bug-1387503-2.js
@@ -0,0 +1,52 @@
+// Test uncatchable error when a stream's queuing strategy's size() method is called.
+/* global newGlobal */
+
+let fnFinished = false;
+let g;
+add_task(async function test() {
+ // Make `debugger;` raise an uncatchable exception.
+ g = newGlobal();
+ g.parent = this;
+ g.hit = false;
+ g.info = info;
+ g.eval(`
+ var dbg = new Debugger(parent);
+ dbg.onDebuggerStatement = (_frame, exc) => {hit = true; info("hit"); return null};
+`);
+
+ async function fn() {
+ // Await once to postpone the uncatchable error until we're running inside
+ // a reaction job. We don't want the rest of the test to be terminated.
+ // (`drainJobQueue` catches uncatchable errors!)
+ await 1;
+
+ try {
+ // Create a stream with a strategy whose .size() method raises an
+ // uncatchable exception, and have it call that method.
+ new ReadableStream(
+ {
+ start(controller) {
+ controller.enqueue("FIRST POST"); // this calls .size()
+ },
+ },
+ {
+ size() {
+ // eslint-disable-next-line no-debugger
+ debugger;
+ },
+ }
+ );
+ } finally {
+ fnFinished = true;
+ }
+ }
+
+ fn()
+ .then(() => info("Resolved"))
+ .catch(() => info("Rejected"));
+});
+
+add_task(() => {
+ equal(g.hit, true, "We hit G");
+ equal(fnFinished, false, "We didn't hit the finally block");
+});
diff --git a/dom/streams/test/xpcshell/bug-1503406.js b/dom/streams/test/xpcshell/bug-1503406.js
new file mode 100644
index 0000000000..8e45291faf
--- /dev/null
+++ b/dom/streams/test/xpcshell/bug-1503406.js
@@ -0,0 +1,20 @@
+let read;
+let reader;
+
+add_task(async function test() {
+ let g = newGlobal({ wantGlobalProperties: ["ReadableStream"] });
+ reader = g.eval(`
+ let stream = new ReadableStream({
+ start(controller) {
+ controller.enqueue([]);
+ },
+ });
+ let [b1, b2] = stream.tee();
+ b1.getReader();
+`);
+ read = new ReadableStream({}).getReader().read;
+});
+
+add_task(async function test2() {
+ read.call(reader);
+});
diff --git a/dom/streams/test/xpcshell/bug-1773237.js b/dom/streams/test/xpcshell/bug-1773237.js
new file mode 100644
index 0000000000..0d0107fe85
--- /dev/null
+++ b/dom/streams/test/xpcshell/bug-1773237.js
@@ -0,0 +1,11 @@
+// This test fails if there is an unhandled promise rejection
+var stream = new ReadableStream({
+ pull() {
+ return Promise.reject("foobar");
+ },
+});
+var response = new Response(stream);
+var text = response.text().then(
+ () => {},
+ e => {}
+);
diff --git a/dom/streams/test/xpcshell/dom_stream_prototype_test.js b/dom/streams/test/xpcshell/dom_stream_prototype_test.js
new file mode 100644
index 0000000000..b127368318
--- /dev/null
+++ b/dom/streams/test/xpcshell/dom_stream_prototype_test.js
@@ -0,0 +1,29 @@
+"use strict";
+
+var log = [];
+const stream = new ReadableStream({
+ start(controller) {
+ log.push("started");
+ },
+ pull(controller) {
+ log.push("pulled");
+ controller.enqueue("hi from pull");
+ },
+ cancel() {
+ log.push("cancelled");
+ },
+});
+
+print(log); // Currently prints "started"!
+
+add_task(async function helper() {
+ var reader = stream.getReader();
+ var readPromise = reader.read();
+ readPromise.then(x => print(`Printing promise result ${x}, log ${log}`));
+ print(log);
+
+ var x = await readPromise;
+ print(`Promise result ${x} ${x.value}`);
+ Assert.equal(x.value, "hi from pull");
+ Assert.ok(true);
+});
diff --git a/dom/streams/test/xpcshell/fetch.js b/dom/streams/test/xpcshell/fetch.js
new file mode 100644
index 0000000000..af414735f0
--- /dev/null
+++ b/dom/streams/test/xpcshell/fetch.js
@@ -0,0 +1,35 @@
+"use strict";
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+
+AddonTestUtils.init(this);
+AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ "42",
+ "42"
+);
+
+add_task(async function helper() {
+ do_get_profile();
+
+ // The SearchService is also needed in order to construct the initial state,
+ // which means that the AddonManager needs to be available.
+ await AddonTestUtils.promiseStartupManager();
+
+ // The example.com domain will be used to host the dynamic layout JSON and
+ // the top stories JSON.
+ let server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] });
+ server.registerDirectory("/", do_get_cwd());
+
+ Assert.equal(true, fetch instanceof Function);
+ var k = await fetch("http://example.com/");
+ console.log(k);
+ console.log(k.body);
+ var r = k.body.getReader();
+ console.log(r);
+ var v = await r.read();
+ console.log(v);
+});
diff --git a/dom/streams/test/xpcshell/head.js b/dom/streams/test/xpcshell/head.js
new file mode 100644
index 0000000000..510cabe757
--- /dev/null
+++ b/dom/streams/test/xpcshell/head.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const { addDebuggerToGlobal } = ChromeUtils.importESModule(
+ "resource://gre/modules/jsdebugger.sys.mjs"
+);
+
+const SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"].createInstance(
+ Ci.nsIPrincipal
+);
+
+function addTestingFunctionsToGlobal(global) {
+ global.eval(
+ `
+ const testingFunctions = Cu.getJSTestingFunctions();
+ for (let k in testingFunctions) {
+
+ this[k] = testingFunctions[k];
+ }
+ `
+ );
+ if (!global.print) {
+ global.print = info;
+ }
+ if (!global.newGlobal) {
+ global.newGlobal = newGlobal;
+ }
+ if (!global.Debugger) {
+ addDebuggerToGlobal(global);
+ }
+}
+
+addTestingFunctionsToGlobal(this);
+
+/* Create a new global, with all the JS shell testing functions. Similar to the
+ * newGlobal function exposed to JS shells, and useful for porting JS shell
+ * tests to xpcshell tests.
+ */
+function newGlobal(args) {
+ const global = new Cu.Sandbox(SYSTEM_PRINCIPAL, {
+ freshCompartment: true,
+ ...args,
+ });
+ addTestingFunctionsToGlobal(global);
+ return global;
+}
diff --git a/dom/streams/test/xpcshell/large-pipeto.js b/dom/streams/test/xpcshell/large-pipeto.js
new file mode 100644
index 0000000000..420adfea20
--- /dev/null
+++ b/dom/streams/test/xpcshell/large-pipeto.js
@@ -0,0 +1,101 @@
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+// Stamp an array buffer with a pattern; verified by verify_chunk below.
+function init(array) {
+ for (var i = 0; i < array.length; i++) {
+ array[i] = i % 256;
+ }
+}
+
+// The construction of the file below ends up with 12 instances
+// of the array buffer -- we want this to be larger than 2**32 bytes
+// to exercise potential truncation of nsIInputStream::Read's count
+// parameter.
+const ABLENGTH = (2 ** 32 + 8) / 12;
+
+// Get a large file (bigger than 2GB!)
+function get_file() {
+ const array = new ArrayBuffer(ABLENGTH);
+ const buff = new Uint8Array(array);
+
+ // Stamp with pattern.
+ init(buff);
+
+ const blob = new Blob([buff, buff], {});
+ return new File([blob, blob, blob, blob, blob, blob], {});
+}
+
+// Verify that the chunks the stream recieve correspond to the initialization
+function verify_chunk(chunk, verification_state) {
+ for (var j = 0; j < chunk.length; j++) {
+ // If we don't match the fill pattern.
+ if (chunk[j] != verification_state.expected) {
+ // we ran out of array buffer; so we should be looking at the first byte of the array again.
+ if (
+ verification_state.total_index % ABLENGTH != 0 ||
+ chunk[j] != 0 /* ASSUME THAT THE INITIALIZATION OF THE BUFFER IS ZERO */
+ ) {
+ throw new Error(
+ `Mismatch: chunk[${j}] (${chunk[j]}) != ${verification_state.expected} (total_index ${verification_state.total_index})`
+ );
+ }
+ // Reset the fill expectation to 1 for next round.
+ verification_state.expected = 1;
+ } else {
+ // We are inside regular fill section
+ verification_state.expected = (verification_state.expected + 1) % 256;
+ }
+ verification_state.total_index++;
+ }
+}
+
+// Pipe To Testing: Win32 can't handle the file size created in this test and OOMs.
+add_task(
+ {
+ skip_if: () => AppConstants.platform == "win" && !Services.appinfo.is64Bit,
+ },
+ async () => {
+ var chunk_verification_state = {
+ expected: 0,
+ total_index: 0,
+ };
+
+ const file = get_file();
+
+ await file.stream().pipeTo(
+ new WritableStream({
+ write(chunk) {
+ verify_chunk(chunk, chunk_verification_state);
+ },
+ })
+ );
+ }
+);
+
+// Do the same test as above, but this time don't use pipeTo.
+add_task(
+ {
+ skip_if: () => AppConstants.platform == "win" && !Services.appinfo.is64Bit,
+ },
+ async () => {
+ var file = get_file();
+
+ var chunk_verification_state = {
+ expected: 0,
+ total_index: 0,
+ };
+
+ var streamReader = file.stream().getReader();
+
+ while (true) {
+ var res = await streamReader.read();
+ if (res.done) {
+ break;
+ }
+ var chunk = res.value;
+ verify_chunk(chunk, chunk_verification_state);
+ }
+ }
+);
diff --git a/dom/streams/test/xpcshell/proper-realm-cancel.js b/dom/streams/test/xpcshell/proper-realm-cancel.js
new file mode 100644
index 0000000000..1de6db3172
--- /dev/null
+++ b/dom/streams/test/xpcshell/proper-realm-cancel.js
@@ -0,0 +1,5 @@
+// This test passes if we don't have a CCW assertion.
+var g = newGlobal();
+var ccwCancelMethod = new g.Function("return 17;");
+
+new ReadableStream({ cancel: ccwCancelMethod }).cancel("bye");
diff --git a/dom/streams/test/xpcshell/proper-realm-pull.js b/dom/streams/test/xpcshell/proper-realm-pull.js
new file mode 100644
index 0000000000..9d42d9a65d
--- /dev/null
+++ b/dom/streams/test/xpcshell/proper-realm-pull.js
@@ -0,0 +1,6 @@
+// This test passes if we don't have a CCW assertion.
+
+var g = newGlobal({ newCompartment: true });
+var ccwPullMethod = new g.Function("return 17;");
+
+new ReadableStream({ pull: ccwPullMethod });
diff --git a/dom/streams/test/xpcshell/response.js b/dom/streams/test/xpcshell/response.js
new file mode 100644
index 0000000000..ead1be527f
--- /dev/null
+++ b/dom/streams/test/xpcshell/response.js
@@ -0,0 +1,30 @@
+"use strict";
+
+add_task(async function (test) {
+ return new Response(new Blob([], { type: "text/plain" })).body.cancel();
+});
+
+add_task(function (test) {
+ var response = new Response(
+ new Blob(["This is data"], { type: "text/plain" })
+ );
+ var reader = response.body.getReader();
+ reader.read();
+ return reader.cancel();
+});
+
+add_task(function (test) {
+ var response = new Response(new Blob(["T"], { type: "text/plain" }));
+ var reader = response.body.getReader();
+
+ var closedPromise = reader.closed.then(function () {
+ return reader.cancel();
+ });
+ reader.read().then(function readMore({ done, value }) {
+ if (!done) {
+ return reader.read().then(readMore);
+ }
+ return undefined;
+ });
+ return closedPromise;
+});
diff --git a/dom/streams/test/xpcshell/subclassing.js b/dom/streams/test/xpcshell/subclassing.js
new file mode 100644
index 0000000000..b2b86cf353
--- /dev/null
+++ b/dom/streams/test/xpcshell/subclassing.js
@@ -0,0 +1,123 @@
+// Adapted from js/src/tests/non262/ReadableStream/subclassing.js to suit requirements of xpcshell-testing.
+
+function assertEq(a, b) {
+ Assert.equal(a, b);
+}
+function assertThrowsInstanceOf(fun, err) {
+ var regexp = new RegExp(err.name);
+ print(regexp);
+ Assert.throws(fun, regexp);
+}
+
+// Spot-check subclassing of stream constructors.
+
+// ReadableStream can be subclassed.
+class PartyStreamer extends ReadableStream {}
+
+let started = false;
+add_task(function subclass_helper() {
+ // The base class constructor is called.
+ let stream = new PartyStreamer({
+ // (The ReadableStream constructor calls this start method.)
+ start(c) {
+ started = true;
+ },
+ });
+
+ 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.
+ stream.getReader();
+ assertEq(stream.locked, true);
+});
+
+add_task(function strategy_helper() {
+ // 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.
+add_task(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);
+});
+
+add_task(function default_controller() {
+ // 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);
+});
diff --git a/dom/streams/test/xpcshell/too-big-array-buffer.js b/dom/streams/test/xpcshell/too-big-array-buffer.js
new file mode 100644
index 0000000000..b80c36e813
--- /dev/null
+++ b/dom/streams/test/xpcshell/too-big-array-buffer.js
@@ -0,0 +1,15 @@
+add_task(async function helper() {
+ // Note: this test assumes the largest possible ArrayBuffer is
+ // smaller than 10GB -- if that changes, this test will fail.
+ let rs = new ReadableStream({
+ type: "bytes",
+ autoAllocateChunkSize: 10 * 1024 * 1024 * 1024,
+ });
+ let reader = rs.getReader();
+ try {
+ await reader.read();
+ Assert.equal(true, false, "Shouldn't succeed at reading");
+ } catch (e) {
+ Assert.equal(e instanceof RangeError, true, "Should throw RangeError");
+ }
+});
diff --git a/dom/streams/test/xpcshell/xpcshell.toml b/dom/streams/test/xpcshell/xpcshell.toml
new file mode 100644
index 0000000000..3fe0232d17
--- /dev/null
+++ b/dom/streams/test/xpcshell/xpcshell.toml
@@ -0,0 +1,32 @@
+[DEFAULT]
+head = "head.js"
+skip-if = ["os == 'android'"]
+support-files = ""
+
+["bug-1387503-1.js"]
+prefs = ["security.allow_parent_unrestricted_js_loads=true"]
+
+["bug-1503406.js"]
+
+["bug-1773237.js"]
+
+["dom_stream_prototype_test.js"]
+
+["fetch.js"]
+
+["large-pipeto.js"]
+skip-if = [
+ "os == 'win'",
+ "tsan", # Causes claim expired errors; see Bug 1770170.
+]
+run-sequentially = "very high failure rate in parallel"
+
+["proper-realm-cancel.js"]
+
+["proper-realm-pull.js"]
+
+["response.js"]
+
+["subclassing.js"]
+
+["too-big-array-buffer.js"]