summaryrefslogtreecommitdiffstats
path: root/dom/file/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/file/tests
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/file/tests')
-rw-r--r--dom/file/tests/common_blob.js395
-rw-r--r--dom/file/tests/common_blob_reading.js49
-rw-r--r--dom/file/tests/common_blob_types.js82
-rw-r--r--dom/file/tests/common_fileReader.js848
-rw-r--r--dom/file/tests/crashtests/1480354.html14
-rw-r--r--dom/file/tests/crashtests/1562891.html16
-rw-r--r--dom/file/tests/crashtests/1747185.html11
-rw-r--r--dom/file/tests/crashtests/1748342.html26
-rw-r--r--dom/file/tests/crashtests/crashtests.list4
-rw-r--r--dom/file/tests/create_file_objects.js20
-rw-r--r--dom/file/tests/file_blobURL_expiring.html4
-rw-r--r--dom/file/tests/file_mozfiledataurl_audio.oggbin0 -> 135861 bytes
-rw-r--r--dom/file/tests/file_mozfiledataurl_doc.html6
-rw-r--r--dom/file/tests/file_mozfiledataurl_img.jpgbin0 -> 2711 bytes
-rw-r--r--dom/file/tests/file_mozfiledataurl_inner.html76
-rw-r--r--dom/file/tests/file_mozfiledataurl_text.txt1
-rw-r--r--dom/file/tests/file_nonascii_blob_url.html24
-rw-r--r--dom/file/tests/fileapi_chromeScript.js55
-rw-r--r--dom/file/tests/mochitest.toml86
-rw-r--r--dom/file/tests/test_blobURL_expiring.html48
-rw-r--r--dom/file/tests/test_blob_fragment_and_query.html62
-rw-r--r--dom/file/tests/test_blob_reading.html34
-rw-r--r--dom/file/tests/test_blobconstructor.html246
-rw-r--r--dom/file/tests/test_bloburi.js24
-rw-r--r--dom/file/tests/test_bug1507893.html63
-rw-r--r--dom/file/tests/test_bug1742540.html83
-rw-r--r--dom/file/tests/test_createFile.js52
-rw-r--r--dom/file/tests/test_file_from_blob.html110
-rw-r--r--dom/file/tests/test_file_negative_date.html29
-rw-r--r--dom/file/tests/test_fileapi_basic.html24
-rw-r--r--dom/file/tests/test_fileapi_basic_worker.html38
-rw-r--r--dom/file/tests/test_fileapi_encoding.html24
-rw-r--r--dom/file/tests/test_fileapi_encoding_worker.html38
-rw-r--r--dom/file/tests/test_fileapi_other.html24
-rw-r--r--dom/file/tests/test_fileapi_other_worker.html38
-rw-r--r--dom/file/tests/test_fileapi_slice_image.html139
-rw-r--r--dom/file/tests/test_fileapi_slice_memFile_1.html37
-rw-r--r--dom/file/tests/test_fileapi_slice_memFile_2.html37
-rw-r--r--dom/file/tests/test_fileapi_slice_realFile_1.html37
-rw-r--r--dom/file/tests/test_fileapi_slice_realFile_2.html37
-rw-r--r--dom/file/tests/test_fileapi_twice.html24
-rw-r--r--dom/file/tests/test_fileapi_twice_worker.html38
-rw-r--r--dom/file/tests/test_ipc_messagemanager_blob.js102
-rw-r--r--dom/file/tests/test_mozfiledataurl.html224
-rw-r--r--dom/file/tests/test_nonascii_blob_url.html28
-rw-r--r--dom/file/tests/worker_blob_reading.js26
-rw-r--r--dom/file/tests/worker_bug1507893.js5
-rw-r--r--dom/file/tests/worker_bug1742540.js5
-rw-r--r--dom/file/tests/worker_fileReader.js30
-rw-r--r--dom/file/tests/xpcshell.toml8
50 files changed, 3431 insertions, 0 deletions
diff --git a/dom/file/tests/common_blob.js b/dom/file/tests/common_blob.js
new file mode 100644
index 0000000000..261909af0d
--- /dev/null
+++ b/dom/file/tests/common_blob.js
@@ -0,0 +1,395 @@
+const RANGE_1 = 1;
+const RANGE_2 = 2;
+
+function testBlob(file, contents, testName) {
+ // Load file using FileReader
+ return (
+ new Promise(resolve => {
+ let r = new FileReader();
+ r.onload = event => {
+ is(
+ event.target.readyState,
+ FileReader.DONE,
+ "[FileReader] readyState in test FileReader.readAsBinaryString of " +
+ testName
+ );
+ is(
+ event.target.error,
+ null,
+ "[FileReader] no error in test FileReader.readAsBinaryString of " +
+ testName
+ );
+ // Do not use |is(event.target.result, contents, "...");| that may output raw binary data.
+ is(
+ event.target.result.length,
+ contents.length,
+ "[FileReader] Length of result in test FileReader.readAsBinaryString of " +
+ testName
+ );
+ ok(
+ event.target.result == contents,
+ "[FileReader] Content of result in test FileReader.readAsBinaryString of " +
+ testName
+ );
+ is(
+ event.lengthComputable,
+ true,
+ "[FileReader] lengthComputable in test FileReader.readAsBinaryString of " +
+ testName
+ );
+ is(
+ event.loaded,
+ contents.length,
+ "[FileReader] Loaded length in test FileReader.readAsBinaryString of " +
+ testName
+ );
+ is(
+ event.total,
+ contents.length,
+ "[FileReader] Total length in test FileReader.readAsBinaryString of " +
+ testName
+ );
+ resolve();
+ };
+ r.readAsBinaryString(file);
+ })
+
+ // Load file using URL.createObjectURL and XMLHttpRequest
+ .then(() => {
+ return new Promise(resolve => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", URL.createObjectURL(file));
+ xhr.onload = event => {
+ XHRLoadHandler(
+ event,
+ resolve,
+ contents,
+ "XMLHttpRequest load of " + testName
+ );
+ };
+ xhr.overrideMimeType("text/plain; charset=x-user-defined");
+ xhr.send();
+ });
+ })
+
+ // Send file to server using FormData and XMLHttpRequest
+ .then(() => {
+ return new Promise(resolve => {
+ let xhr = new XMLHttpRequest();
+ xhr.onload = function (event) {
+ checkMPSubmission(JSON.parse(event.target.responseText), [
+ { name: "hello", value: "world" },
+ {
+ name: "myfile",
+ value: contents,
+ fileName: file.name || "blob",
+ contentType: file.type || "application/octet-stream",
+ },
+ ]);
+ resolve();
+ };
+ xhr.open("POST", "../../../dom/html/test/form_submit_server.sjs");
+
+ let fd = new FormData();
+ fd.append("hello", "world");
+ fd.append("myfile", file);
+
+ xhr.send(fd);
+ });
+ })
+
+ // Send file to server using plain XMLHttpRequest
+ .then(() => {
+ return new Promise(resolve => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("POST", "../../../dom/xhr/tests/file_XHRSendData.sjs");
+
+ xhr.onload = function (event) {
+ is(
+ event.target.getResponseHeader("Result-Content-Type"),
+ file.type ? file.type : null,
+ "request content-type in XMLHttpRequest send of " + testName
+ );
+ is(
+ event.target.getResponseHeader("Result-Content-Length"),
+ String(file.size),
+ "request content-length in XMLHttpRequest send of " + testName
+ );
+ };
+
+ xhr.addEventListener("load", event => {
+ XHRLoadHandler(
+ event,
+ resolve,
+ contents,
+ "XMLHttpRequest send of " + testName
+ );
+ });
+ xhr.overrideMimeType("text/plain; charset=x-user-defined");
+ xhr.send(file);
+ });
+ })
+ );
+}
+
+function testSlice(file, size, type, contents, fileType, range) {
+ is(file.type, type, fileType + " file is correct type");
+ is(file.size, size, fileType + " file is correct size");
+ if (fileType == "fileFile") {
+ ok(file instanceof File, fileType + " file is a File");
+ } else if (fileType == "memFile") {
+ ok(!(file instanceof File), fileType + " file is not a File");
+ }
+ ok(file instanceof Blob, fileType + " file is also a Blob");
+
+ let slice = file.slice(0, size);
+ ok(slice instanceof Blob, fileType + " fullsize slice is a Blob");
+ ok(!(slice instanceof File), fileType + " fullsize slice is not a File");
+
+ slice = file.slice(0, 1234);
+ ok(slice instanceof Blob, fileType + " sized slice is a Blob");
+ ok(!(slice instanceof File), fileType + " sized slice is not a File");
+
+ slice = file.slice(0, size, "foo/bar");
+ is(slice.type, "foo/bar", fileType + " fullsize slice foo/bar type");
+
+ slice = file.slice(0, 5432, "foo/bar");
+ is(slice.type, "foo/bar", fileType + " sized slice foo/bar type");
+
+ is(slice.slice(0, 10).type, "", fileType + " slice-slice type");
+ is(slice.slice(0, 10).size, 10, fileType + " slice-slice size");
+ is(
+ slice.slice(0, 10, "hello/world").type,
+ "hello/world",
+ fileType + " slice-slice hello/world type"
+ );
+ is(
+ slice.slice(0, 10, "hello/world").size,
+ 10,
+ fileType + " slice-slice hello/world size"
+ );
+
+ // Start, end, expected size
+ var indexes_range_1 = [
+ [0, size, size],
+ [0, 1234, 1234],
+ [size - 500, size, 500],
+ [size - 500, size + 500, 500],
+ [size + 500, size + 1500, 0],
+ [0, 0, 0],
+ [1000, 1000, 0],
+ [size, size, 0],
+ [undefined, undefined, size],
+ [0, undefined, size],
+ ];
+
+ var indexes_range_2 = [
+ [100, undefined, size - 100],
+ [-100, undefined, 100],
+ [100, -100, size - 200],
+ [-size - 100, undefined, size],
+ [-2 * size - 100, 500, 500],
+ [0, -size - 100, 0],
+ [100, -size - 100, 0],
+ [50, -size + 100, 50],
+ [0, 33000, 33000],
+ [1000, 34000, 33000],
+ ];
+
+ let indexes;
+ if (range == RANGE_1) {
+ indexes = indexes_range_1;
+ } else if (range == RANGE_2) {
+ indexes = indexes_range_2;
+ } else {
+ throw "Invalid range!";
+ }
+
+ function runNextTest() {
+ if (!indexes.length) {
+ return Promise.resolve(true);
+ }
+
+ let index = indexes.shift();
+
+ let sliceContents;
+ let testName;
+ if (index[0] == undefined) {
+ slice = file.slice();
+ sliceContents = contents.slice();
+ testName = fileType + " slice()";
+ } else if (index[1] == undefined) {
+ slice = file.slice(index[0]);
+ sliceContents = contents.slice(index[0]);
+ testName = fileType + " slice(" + index[0] + ")";
+ } else {
+ slice = file.slice(index[0], index[1]);
+ sliceContents = contents.slice(index[0], index[1]);
+ testName = fileType + " slice(" + index[0] + ", " + index[1] + ")";
+ }
+
+ is(slice.type, "", testName + " type");
+ is(slice.size, index[2], testName + " size");
+ is(sliceContents.length, index[2], testName + " data size");
+
+ return testBlob(slice, sliceContents, testName).then(runNextTest);
+ }
+
+ return runNextTest()
+ .then(() => {
+ // Slice of slice
+ let sliceOfSlice = file.slice(0, 40000);
+ return testBlob(
+ sliceOfSlice.slice(5000, 42000),
+ contents.slice(5000, 40000),
+ "file slice slice"
+ );
+ })
+ .then(() => {
+ // ...of slice of slice
+ let sliceOfSlice = file
+ .slice(0, 40000)
+ .slice(5000, 42000)
+ .slice(400, 700);
+ SpecialPowers.gc();
+ return testBlob(
+ sliceOfSlice,
+ contents.slice(5400, 5700),
+ "file slice slice slice"
+ );
+ });
+}
+
+function convertXHRBinary(s) {
+ let res = "";
+ for (let i = 0; i < s.length; ++i) {
+ res += String.fromCharCode(s.charCodeAt(i) & 255);
+ }
+ return res;
+}
+
+function XHRLoadHandler(event, resolve, contents, testName) {
+ is(event.target.readyState, 4, "[XHR] readyState in test " + testName);
+ is(event.target.status, 200, "[XHR] no error in test " + testName);
+ // Do not use |is(convertXHRBinary(event.target.responseText), contents, "...");| that may output raw binary data.
+ let convertedData = convertXHRBinary(event.target.responseText);
+ is(
+ convertedData.length,
+ contents.length,
+ "[XHR] Length of result in test " + testName
+ );
+ ok(convertedData == contents, "[XHR] Content of result in test " + testName);
+ is(
+ event.lengthComputable,
+ event.total != 0,
+ "[XHR] lengthComputable in test " + testName
+ );
+ is(event.loaded, contents.length, "[XHR] Loaded length in test " + testName);
+ is(event.total, contents.length, "[XHR] Total length in test " + testName);
+ resolve();
+}
+
+function checkMPSubmission(sub, expected) {
+ function getPropCount(o) {
+ let x,
+ l = 0;
+ for (x in o) {
+ ++l;
+ }
+ return l;
+ }
+
+ is(sub.length, expected.length, "Correct number of items");
+ let i;
+ for (i = 0; i < expected.length; ++i) {
+ if (!("fileName" in expected[i])) {
+ is(
+ sub[i].headers["Content-Disposition"],
+ 'form-data; name="' + expected[i].name + '"',
+ "Correct name (A)"
+ );
+ is(getPropCount(sub[i].headers), 1, "Wrong number of headers (A)");
+ } else {
+ is(
+ sub[i].headers["Content-Disposition"],
+ 'form-data; name="' +
+ expected[i].name +
+ '"; filename="' +
+ expected[i].fileName +
+ '"',
+ "Correct name (B)"
+ );
+ is(
+ sub[i].headers["Content-Type"],
+ expected[i].contentType,
+ "Correct content type (B)"
+ );
+ is(getPropCount(sub[i].headers), 2, "Wrong number of headers (B)");
+ }
+ // Do not use |is(sub[i].body, expected[i].value, "...");| that may output raw binary data.
+ is(sub[i].body.length, expected[i].value.length, "Length of correct value");
+ ok(sub[i].body == expected[i].value, "Content of correct value");
+ }
+}
+
+function createCanvasURL() {
+ return new Promise(resolve => {
+ // Create a decent-sized image
+ let cx = $("canvas").getContext("2d");
+ let s = cx.canvas.width;
+ let grad = cx.createLinearGradient(0, 0, s - 1, s - 1);
+ for (i = 0; i < 0.95; i += 0.1) {
+ grad.addColorStop(i, "white");
+ grad.addColorStop(i + 0.05, "black");
+ }
+ grad.addColorStop(1, "white");
+ cx.fillStyle = grad;
+ cx.fillRect(0, 0, s - 1, s - 1);
+ cx.fillStyle = "rgba(200, 0, 0, 0.9)";
+ cx.fillRect(0.1 * s, 0.1 * s, 0.7 * s, 0.7 * s);
+ cx.strokeStyle = "rgba(0, 0, 130, 0.5)";
+ cx.lineWidth = 0.14 * s;
+ cx.beginPath();
+ cx.arc(0.6 * s, 0.6 * s, 0.3 * s, 0, Math.PI * 2, true);
+ cx.stroke();
+ cx.closePath();
+ cx.fillStyle = "rgb(0, 255, 0)";
+ cx.beginPath();
+ cx.arc(0.1 * s, 0.8 * s, 0.1 * s, 0, Math.PI * 2, true);
+ cx.fill();
+ cx.closePath();
+
+ let data = atob(
+ cx.canvas
+ .toDataURL("image/png")
+ .substring("data:text/png;base64,".length + 1)
+ );
+
+ // This might fail if we dramatically improve the png encoder. If that happens
+ // please increase the complexity or size of the image generated above to ensure
+ // that we're testing with files that are large enough.
+ ok(data.length > 65536, "test data sufficiently large");
+
+ resolve(data);
+ });
+}
+
+function createFile(data, name) {
+ return new Promise(resolve => {
+ SpecialPowers.createFiles([{ name, data }], files => {
+ resolve(files[0]);
+ });
+ });
+}
+
+function toBlobPromise(canvas) {
+ function BlobListener(callback, file) {
+ var reader = new FileReader();
+ reader.onload = () => callback(file);
+ reader.readAsDataURL(file);
+ }
+
+ return new Promise(resolve => {
+ canvas.toBlob(BlobListener.bind(undefined, resolve));
+ });
+}
diff --git a/dom/file/tests/common_blob_reading.js b/dom/file/tests/common_blob_reading.js
new file mode 100644
index 0000000000..18586bbc99
--- /dev/null
+++ b/dom/file/tests/common_blob_reading.js
@@ -0,0 +1,49 @@
+async function testBlobText(blob, content) {
+ let text = await blob.text();
+ is(text, content, "blob.text()");
+}
+
+async function testBlobArrayBuffer(blob, content) {
+ let ab = await blob.arrayBuffer();
+ is(ab.byteLength, content.length, "blob.arrayBuffer()");
+}
+
+async function testBlobStream(blob, content) {
+ let s = await blob.stream();
+ ok(s instanceof ReadableStream, "We have a ReadableStream");
+
+ let data = await s.getReader().read();
+ ok(!data.done, "Nothing is done yet");
+ for (let i = 0; i < data.value.length; ++i) {
+ is(String.fromCharCode(data.value[i]), content[i], "blob.stream() - " + i);
+ }
+}
+
+function workify(func, blob, content) {
+ info("Workifying " + func);
+
+ return new Promise((resolve, reject) => {
+ let worker = new Worker("worker_blob_reading.js");
+ worker.postMessage({ func, blob, content });
+ worker.onmessage = function (e) {
+ if (e.data.type == "done") {
+ resolve();
+ return;
+ }
+
+ if (e.data.type == "error") {
+ reject(e.data.message);
+ return;
+ }
+
+ if (e.data.type == "test") {
+ ok(e.data.test, e.data.message);
+ return;
+ }
+
+ if (e.data.type == "info") {
+ info(e.data.message);
+ }
+ };
+ });
+}
diff --git a/dom/file/tests/common_blob_types.js b/dom/file/tests/common_blob_types.js
new file mode 100644
index 0000000000..95501e58e5
--- /dev/null
+++ b/dom/file/tests/common_blob_types.js
@@ -0,0 +1,82 @@
+let blobTypes = [
+ {
+ type: "memory",
+ factory: async content => {
+ return new Blob([content]);
+ },
+ blobImplType: "MultipartBlobImpl[StringBlobImpl]",
+ },
+
+ {
+ type: "ipcBlob",
+ factory: async content => {
+ return new Promise(resolve => {
+ let bc1 = new BroadcastChannel("blob tests");
+ bc1.onmessage = e => {
+ resolve(e.data);
+ };
+
+ let bc2 = new BroadcastChannel("blob tests");
+ bc2.postMessage(new Blob([content]));
+ });
+ },
+ blobImplType:
+ "StreamBlobImpl[StreamBlobImpl[MultipartBlobImpl[StringBlobImpl]]]",
+ },
+
+ {
+ type: "memoryBlob",
+ factory: async content => {
+ return new Promise(resolve => {
+ var xhr = new XMLHttpRequest();
+ xhr.open(
+ "POST",
+ "http://mochi.test:8888/browser/dom/xhr/tests/temporaryFileBlob.sjs"
+ );
+ xhr.responseType = "blob";
+ xhr.send(content);
+ xhr.onloadend = _ => {
+ resolve(xhr.response);
+ };
+ });
+ },
+ blobImplType: "MemoryBlobImpl",
+ },
+
+ {
+ type: "temporaryBlob",
+ factory: async content => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.blob.memoryToTemporaryFile", 1]],
+ });
+
+ return new Promise(resolve => {
+ var xhr = new XMLHttpRequest();
+ xhr.open(
+ "POST",
+ "http://mochi.test:8888/browser/dom/xhr/tests/temporaryFileBlob.sjs"
+ );
+ xhr.responseType = "blob";
+ xhr.send(content);
+ xhr.onloadend = _ => {
+ resolve(xhr.response);
+ };
+ });
+ },
+ blobImplType: "StreamBlobImpl[TemporaryFileBlobImpl]",
+ },
+];
+
+async function forEachBlobType(content, cb) {
+ for (let i = 0; i < blobTypes.length; ++i) {
+ info("Running tests for " + blobTypes[i].type);
+ let blob = await blobTypes[i].factory(content);
+ is(
+ SpecialPowers.wrap(blob).blobImplType,
+ blobTypes[i].blobImplType,
+ "Correct blobImplType"
+ );
+ ok(blob instanceof Blob, "Blob created");
+ await cb(blob, content);
+ }
+}
diff --git a/dom/file/tests/common_fileReader.js b/dom/file/tests/common_fileReader.js
new file mode 100644
index 0000000000..9ee6a32b93
--- /dev/null
+++ b/dom/file/tests/common_fileReader.js
@@ -0,0 +1,848 @@
+function test_setup() {
+ return new Promise(resolve => {
+ const minFileSize = 20000;
+
+ // Create strings containing data we'll test with. We'll want long
+ // strings to ensure they span multiple buffers while loading
+ let testTextData = "asd b\tlah\u1234w\u00a0r";
+ while (testTextData.length < minFileSize) {
+ testTextData = testTextData + testTextData;
+ }
+
+ let testASCIIData = "abcdef 123456\n";
+ while (testASCIIData.length < minFileSize) {
+ testASCIIData = testASCIIData + testASCIIData;
+ }
+
+ let testBinaryData = "";
+ for (let i = 0; i < 256; i++) {
+ testBinaryData += String.fromCharCode(i);
+ }
+ while (testBinaryData.length < minFileSize) {
+ testBinaryData = testBinaryData + testBinaryData;
+ }
+
+ let dataurldata0 = testBinaryData.substr(
+ 0,
+ testBinaryData.length - (testBinaryData.length % 3)
+ );
+ let dataurldata1 = testBinaryData.substr(
+ 0,
+ testBinaryData.length - 2 - (testBinaryData.length % 3)
+ );
+ let dataurldata2 = testBinaryData.substr(
+ 0,
+ testBinaryData.length - 1 - (testBinaryData.length % 3)
+ );
+
+ //Set up files for testing
+ let openerURL = SimpleTest.getTestFileURL("fileapi_chromeScript.js");
+ let opener = SpecialPowers.loadChromeScript(openerURL);
+
+ opener.addMessageListener("files.opened", message => {
+ let [
+ asciiFile,
+ binaryFile,
+ nonExistingFile,
+ utf8TextFile,
+ utf16TextFile,
+ emptyFile,
+ dataUrlFile0,
+ dataUrlFile1,
+ dataUrlFile2,
+ ] = message;
+
+ resolve({
+ blobs: {
+ asciiFile,
+ binaryFile,
+ nonExistingFile,
+ utf8TextFile,
+ utf16TextFile,
+ emptyFile,
+ dataUrlFile0,
+ dataUrlFile1,
+ dataUrlFile2,
+ },
+ data: {
+ text: testTextData,
+ ascii: testASCIIData,
+ binary: testBinaryData,
+ url0: dataurldata0,
+ url1: dataurldata1,
+ url2: dataurldata2,
+ },
+ });
+ });
+
+ opener.sendAsyncMessage("files.open", [
+ testASCIIData,
+ testBinaryData,
+ null,
+ convertToUTF8(testTextData),
+ convertToUTF16(testTextData),
+ "",
+ dataurldata0,
+ dataurldata1,
+ dataurldata2,
+ ]);
+ });
+}
+
+function runBasicTests(data) {
+ return test_basic()
+ .then(() => {
+ return test_readAsText(data.blobs.asciiFile, data.data.ascii);
+ })
+ .then(() => {
+ return test_readAsBinaryString(data.blobs.binaryFile, data.data.binary);
+ })
+ .then(() => {
+ return test_readAsArrayBuffer(data.blobs.binaryFile, data.data.binary);
+ });
+}
+
+function runEncodingTests(data) {
+ return test_readAsTextWithEncoding(
+ data.blobs.asciiFile,
+ data.data.ascii,
+ data.data.ascii.length,
+ ""
+ )
+ .then(() => {
+ return test_readAsTextWithEncoding(
+ data.blobs.asciiFile,
+ data.data.ascii,
+ data.data.ascii.length,
+ "iso8859-1"
+ );
+ })
+ .then(() => {
+ return test_readAsTextWithEncoding(
+ data.blobs.utf8TextFile,
+ data.data.text,
+ convertToUTF8(data.data.text).length,
+ "utf8"
+ );
+ })
+ .then(() => {
+ return test_readAsTextWithEncoding(
+ data.blobs.utf16TextFile,
+ data.data.text,
+ convertToUTF16(data.data.text).length,
+ "utf-16"
+ );
+ })
+ .then(() => {
+ return test_readAsTextWithEncoding(data.blobs.emptyFile, "", 0, "");
+ })
+ .then(() => {
+ return test_readAsTextWithEncoding(data.blobs.emptyFile, "", 0, "utf8");
+ })
+ .then(() => {
+ return test_readAsTextWithEncoding(data.blobs.emptyFile, "", 0, "utf-16");
+ });
+}
+
+function runEmptyTests(data) {
+ return test_onlyResult()
+ .then(() => {
+ return test_readAsText(data.blobs.emptyFile, "");
+ })
+ .then(() => {
+ return test_readAsBinaryString(data.blobs.emptyFile, "");
+ })
+ .then(() => {
+ return test_readAsArrayBuffer(data.blobs.emptyFile, "");
+ })
+ .then(() => {
+ return test_readAsDataURL(data.blobs.emptyFile, convertToDataURL(""), 0);
+ });
+}
+
+function runTwiceTests(data) {
+ return test_readAsTextTwice(data.blobs.asciiFile, data.data.ascii)
+ .then(() => {
+ return test_readAsBinaryStringTwice(
+ data.blobs.binaryFile,
+ data.data.binary
+ );
+ })
+ .then(() => {
+ return test_readAsDataURLTwice(
+ data.blobs.binaryFile,
+ convertToDataURL(data.data.binary),
+ data.data.binary.length
+ );
+ })
+ .then(() => {
+ return test_readAsArrayBufferTwice(
+ data.blobs.binaryFile,
+ data.data.binary
+ );
+ })
+ .then(() => {
+ return test_readAsArrayBufferTwice2(
+ data.blobs.binaryFile,
+ data.data.binary
+ );
+ });
+}
+
+function runOtherTests(data) {
+ return test_readAsDataURL_customLength(
+ data.blobs.dataUrlFile0,
+ convertToDataURL(data.data.url0),
+ data.data.url0.length,
+ 0
+ )
+ .then(() => {
+ return test_readAsDataURL_customLength(
+ data.blobs.dataUrlFile1,
+ convertToDataURL(data.data.url1),
+ data.data.url1.length,
+ 1
+ );
+ })
+ .then(() => {
+ return test_readAsDataURL_customLength(
+ data.blobs.dataUrlFile2,
+ convertToDataURL(data.data.url2),
+ data.data.url2.length,
+ 2
+ );
+ })
+ .then(() => {
+ return test_abort(data.blobs.asciiFile);
+ })
+ .then(() => {
+ return test_abort_readAsX(data.blobs.asciiFile, data.data.ascii);
+ })
+ .then(() => {
+ return test_nonExisting(data.blobs.nonExistingFile);
+ });
+}
+
+function convertToUTF16(s) {
+ let res = "";
+ for (let i = 0; i < s.length; ++i) {
+ c = s.charCodeAt(i);
+ res += String.fromCharCode(c & 255, c >>> 8);
+ }
+ return res;
+}
+
+function convertToUTF8(s) {
+ return unescape(encodeURIComponent(s));
+}
+
+function convertToDataURL(s) {
+ return "data:application/octet-stream;base64," + btoa(s);
+}
+
+function loadEventHandler_string(
+ event,
+ resolve,
+ reader,
+ data,
+ dataLength,
+ testName
+) {
+ is(event.target, reader, "Correct target.");
+ is(
+ event.target.readyState,
+ FileReader.DONE,
+ "readyState in test " + testName
+ );
+ is(event.target.error, null, "no error in test " + testName);
+ is(event.target.result, data, "result in test " + testName);
+ is(event.lengthComputable, true, "lengthComputable in test " + testName);
+ is(event.loaded, dataLength, "loaded in test " + testName);
+ is(event.total, dataLength, "total in test " + testName);
+ resolve();
+}
+
+function loadEventHandler_arrayBuffer(event, resolve, reader, data, testName) {
+ is(
+ event.target.readyState,
+ FileReader.DONE,
+ "readyState in test " + testName
+ );
+ is(event.target.error, null, "no error in test " + testName);
+ is(event.lengthComputable, true, "lengthComputable in test " + testName);
+ is(event.loaded, data.length, "loaded in test " + testName);
+ is(event.total, data.length, "total in test " + testName);
+ is(
+ event.target.result.byteLength,
+ data.length,
+ "array buffer size in test " + testName
+ );
+
+ let u8v = new Uint8Array(event.target.result);
+ is(
+ String.fromCharCode.apply(String, u8v),
+ data,
+ "array buffer contents in test " + testName
+ );
+ u8v = null;
+
+ if ("SpecialPowers" in self) {
+ SpecialPowers.gc();
+
+ is(
+ event.target.result.byteLength,
+ data.length,
+ "array buffer size after gc in test " + testName
+ );
+ u8v = new Uint8Array(event.target.result);
+ is(
+ String.fromCharCode.apply(String, u8v),
+ data,
+ "array buffer contents after gc in test " + testName
+ );
+ }
+
+ resolve();
+}
+
+function test_basic() {
+ return new Promise(resolve => {
+ is(FileReader.EMPTY, 0, "correct EMPTY value");
+ is(FileReader.LOADING, 1, "correct LOADING value");
+ is(FileReader.DONE, 2, "correct DONE value");
+ resolve();
+ });
+}
+
+function test_readAsText(blob, text) {
+ return new Promise(resolve => {
+ let onloadHasRun = false;
+ let onloadStartHasRun = false;
+
+ let r = new FileReader();
+ is(r.readyState, FileReader.EMPTY, "correct initial text readyState");
+
+ r.onload = event => {
+ loadEventHandler_string(
+ event,
+ resolve,
+ r,
+ text,
+ text.length,
+ "readAsText"
+ );
+ };
+
+ r.addEventListener("load", () => {
+ onloadHasRun = true;
+ });
+ r.addEventListener("loadstart", () => {
+ onloadStartHasRun = true;
+ });
+
+ r.readAsText(blob);
+
+ is(r.readyState, FileReader.LOADING, "correct loading text readyState");
+ is(onloadHasRun, false, "text loading must be async");
+ is(onloadStartHasRun, false, "text loadstart should fire async");
+ });
+}
+
+function test_readAsBinaryString(blob, text) {
+ return new Promise(resolve => {
+ let onloadHasRun = false;
+ let onloadStartHasRun = false;
+
+ let r = new FileReader();
+ is(r.readyState, FileReader.EMPTY, "correct initial binary readyState");
+
+ r.addEventListener("load", function () {
+ onloadHasRun = true;
+ });
+ r.addEventListener("loadstart", function () {
+ onloadStartHasRun = true;
+ });
+
+ r.readAsBinaryString(blob);
+
+ r.onload = event => {
+ loadEventHandler_string(
+ event,
+ resolve,
+ r,
+ text,
+ text.length,
+ "readAsBinaryString"
+ );
+ };
+
+ is(r.readyState, FileReader.LOADING, "correct loading binary readyState");
+ is(onloadHasRun, false, "binary loading must be async");
+ is(onloadStartHasRun, false, "binary loadstart should fire async");
+ });
+}
+
+function test_readAsArrayBuffer(blob, text) {
+ return new Promise(resolve => {
+ let onloadHasRun = false;
+ let onloadStartHasRun = false;
+
+ r = new FileReader();
+ is(
+ r.readyState,
+ FileReader.EMPTY,
+ "correct initial arrayBuffer readyState"
+ );
+
+ r.addEventListener("load", function () {
+ onloadHasRun = true;
+ });
+ r.addEventListener("loadstart", function () {
+ onloadStartHasRun = true;
+ });
+
+ r.readAsArrayBuffer(blob);
+
+ r.onload = event => {
+ loadEventHandler_arrayBuffer(
+ event,
+ resolve,
+ r,
+ text,
+ "readAsArrayBuffer"
+ );
+ };
+
+ is(
+ r.readyState,
+ FileReader.LOADING,
+ "correct loading arrayBuffer readyState"
+ );
+ is(onloadHasRun, false, "arrayBuffer loading must be async");
+ is(onloadStartHasRun, false, "arrayBuffer loadstart should fire sync");
+ });
+}
+
+// Test a variety of encodings, and make sure they work properly
+function test_readAsTextWithEncoding(blob, text, length, charset) {
+ return new Promise(resolve => {
+ let r = new FileReader();
+ r.onload = event => {
+ loadEventHandler_string(
+ event,
+ resolve,
+ r,
+ text,
+ length,
+ "readAsText-" + charset
+ );
+ };
+ r.readAsText(blob, charset);
+ });
+}
+
+// Test get result without reading
+function test_onlyResult() {
+ return new Promise(resolve => {
+ let r = new FileReader();
+ is(
+ r.readyState,
+ FileReader.EMPTY,
+ "readyState in test reader get result without reading"
+ );
+ is(r.error, null, "no error in test reader get result without reading");
+ is(r.result, null, "result in test reader get result without reading");
+ resolve();
+ });
+}
+
+function test_readAsDataURL(blob, text, length) {
+ return new Promise(resolve => {
+ let r = new FileReader();
+ r.onload = event => {
+ loadEventHandler_string(event, resolve, r, text, length, "readAsDataURL");
+ };
+ r.readAsDataURL(blob);
+ });
+}
+
+// Test reusing a FileReader to read multiple times
+function test_readAsTextTwice(blob, text) {
+ return new Promise(resolve => {
+ let r = new FileReader();
+ r.onload = event => {
+ loadEventHandler_string(
+ event,
+ () => {},
+ r,
+ text,
+ text.length,
+ "readAsText-reused-once"
+ );
+ };
+
+ let anotherListener = event => {
+ let r1 = event.target;
+ r1.removeEventListener("load", anotherListener);
+ r1.onload = evt => {
+ loadEventHandler_string(
+ evt,
+ resolve,
+ r1,
+ text,
+ text.length,
+ "readAsText-reused-twice"
+ );
+ };
+ r1.readAsText(blob);
+ };
+
+ r.addEventListener("load", anotherListener);
+ r.readAsText(blob);
+ });
+}
+
+// Test reusing a FileReader to read multiple times
+function test_readAsBinaryStringTwice(blob, text) {
+ return new Promise(resolve => {
+ let r = new FileReader();
+ r.onload = event => {
+ loadEventHandler_string(
+ event,
+ () => {},
+ r,
+ text,
+ text.length,
+ "readAsBinaryString-reused-once"
+ );
+ };
+
+ let anotherListener = event => {
+ let r1 = event.target;
+ r1.removeEventListener("load", anotherListener);
+ r1.onload = evt => {
+ loadEventHandler_string(
+ evt,
+ resolve,
+ r1,
+ text,
+ text.length,
+ "readAsBinaryString-reused-twice"
+ );
+ };
+ r1.readAsBinaryString(blob);
+ };
+
+ r.addEventListener("load", anotherListener);
+ r.readAsBinaryString(blob);
+ });
+}
+
+function test_readAsDataURLTwice(blob, text, length) {
+ return new Promise(resolve => {
+ let r = new FileReader();
+ r.onload = event => {
+ loadEventHandler_string(
+ event,
+ () => {},
+ r,
+ text,
+ length,
+ "readAsDataURL-reused-once"
+ );
+ };
+
+ let anotherListener = event => {
+ let r1 = event.target;
+ r1.removeEventListener("load", anotherListener);
+ r1.onload = evt => {
+ loadEventHandler_string(
+ evt,
+ resolve,
+ r1,
+ text,
+ length,
+ "readAsDataURL-reused-twice"
+ );
+ };
+ r1.readAsDataURL(blob);
+ };
+
+ r.addEventListener("load", anotherListener);
+ r.readAsDataURL(blob);
+ });
+}
+
+function test_readAsArrayBufferTwice(blob, text) {
+ return new Promise(resolve => {
+ let r = new FileReader();
+ r.onload = event => {
+ loadEventHandler_arrayBuffer(
+ event,
+ () => {},
+ r,
+ text,
+ "readAsArrayBuffer-reused-once"
+ );
+ };
+
+ let anotherListener = event => {
+ let r1 = event.target;
+ r1.removeEventListener("load", anotherListener);
+ r1.onload = evt => {
+ loadEventHandler_arrayBuffer(
+ evt,
+ resolve,
+ r1,
+ text,
+ "readAsArrayBuffer-reused-twice"
+ );
+ };
+ r1.readAsArrayBuffer(blob);
+ };
+
+ r.addEventListener("load", anotherListener);
+ r.readAsArrayBuffer(blob);
+ });
+}
+
+// Test first reading as ArrayBuffer then read as something else (BinaryString)
+// and doesn't crash
+function test_readAsArrayBufferTwice2(blob, text) {
+ return new Promise(resolve => {
+ let r = new FileReader();
+ r.onload = event => {
+ loadEventHandler_arrayBuffer(
+ event,
+ () => {},
+ r,
+ text,
+ "readAsArrayBuffer-reused-once2"
+ );
+ };
+
+ let anotherListener = event => {
+ let r1 = event.target;
+ r1.removeEventListener("load", anotherListener);
+ r1.onload = evt => {
+ loadEventHandler_string(
+ evt,
+ resolve,
+ r1,
+ text,
+ text.length,
+ "readAsArrayBuffer-reused-twice2"
+ );
+ };
+ r1.readAsBinaryString(blob);
+ };
+
+ r.addEventListener("load", anotherListener);
+ r.readAsArrayBuffer(blob);
+ });
+}
+
+function test_readAsDataURL_customLength(blob, text, length, numb) {
+ return new Promise(resolve => {
+ is(length % 3, numb, "Want to test data with length %3 == " + numb);
+ let r = new FileReader();
+ r.onload = event => {
+ loadEventHandler_string(
+ event,
+ resolve,
+ r,
+ text,
+ length,
+ "dataurl reading, %3 = " + numb
+ );
+ };
+ r.readAsDataURL(blob);
+ });
+}
+
+// Test abort()
+function test_abort(blob) {
+ return new Promise(resolve => {
+ let abortHasRun = false;
+ let loadEndHasRun = false;
+
+ let r = new FileReader();
+
+ r.onabort = function (event) {
+ is(abortHasRun, false, "abort should only fire once");
+ is(loadEndHasRun, false, "loadend shouldn't have fired yet");
+ abortHasRun = true;
+ is(
+ event.target.readyState,
+ FileReader.DONE,
+ "should be DONE while firing onabort"
+ );
+ is(
+ event.target.error.name,
+ "AbortError",
+ "error set to AbortError for aborted reads"
+ );
+ is(
+ event.target.result,
+ null,
+ "file data should be null on aborted reads"
+ );
+ };
+
+ r.onloadend = function (event) {
+ is(abortHasRun, true, "abort should fire before loadend");
+ is(loadEndHasRun, false, "loadend should only fire once");
+ loadEndHasRun = true;
+ is(
+ event.target.readyState,
+ FileReader.DONE,
+ "should be DONE while firing onabort"
+ );
+ is(
+ event.target.error.name,
+ "AbortError",
+ "error set to AbortError for aborted reads"
+ );
+ is(
+ event.target.result,
+ null,
+ "file data should be null on aborted reads"
+ );
+ };
+
+ r.onload = function () {
+ ok(false, "load should not fire for aborted reads");
+ };
+ r.onerror = function () {
+ ok(false, "error should not fire for aborted reads");
+ };
+ r.onprogress = function () {
+ ok(false, "progress should not fire for aborted reads");
+ };
+
+ let abortThrew = false;
+ try {
+ r.abort();
+ } catch (e) {
+ abortThrew = true;
+ }
+
+ is(abortThrew, false, "abort() doesn't throw");
+ is(abortHasRun, false, "abort() is a no-op unless loading");
+
+ r.readAsText(blob);
+ r.abort();
+
+ is(abortHasRun, true, "abort should fire sync");
+ is(loadEndHasRun, true, "loadend should fire sync");
+
+ resolve();
+ });
+}
+
+// Test calling readAsX to cause abort()
+function test_abort_readAsX(blob, text) {
+ return new Promise(resolve => {
+ let reuseAbortHasRun = false;
+
+ let r = new FileReader();
+ r.onabort = function (event) {
+ is(reuseAbortHasRun, false, "abort should only fire once");
+ reuseAbortHasRun = true;
+ is(
+ event.target.readyState,
+ FileReader.DONE,
+ "should be DONE while firing onabort"
+ );
+ is(
+ event.target.error.name,
+ "AbortError",
+ "error set to AbortError for aborted reads"
+ );
+ is(
+ event.target.result,
+ null,
+ "file data should be null on aborted reads"
+ );
+ };
+ r.onload = function () {
+ ok(false, "load should fire for nested reads");
+ };
+
+ let abortThrew = false;
+ try {
+ r.abort();
+ } catch (e) {
+ abortThrew = true;
+ }
+
+ is(abortThrew, false, "abort() should not throw");
+ is(reuseAbortHasRun, false, "abort() is a no-op unless loading");
+ r.readAsText(blob);
+
+ let readThrew = false;
+ try {
+ r.readAsText(blob);
+ } catch (e) {
+ readThrew = true;
+ }
+
+ is(readThrew, true, "readAsText() must throw if loading");
+ is(reuseAbortHasRun, false, "abort should not fire");
+
+ r.onload = event => {
+ loadEventHandler_string(
+ event,
+ resolve,
+ r,
+ text,
+ text.length,
+ "reuse-as-abort reading"
+ );
+ };
+ });
+}
+
+// Test reading from nonexistent files
+function test_nonExisting(blob) {
+ return new Promise(resolve => {
+ let r = new FileReader();
+
+ r.onerror = function (event) {
+ is(
+ event.target.readyState,
+ FileReader.DONE,
+ "should be DONE while firing onerror"
+ );
+ is(
+ event.target.error.name,
+ "NotFoundError",
+ "error set to NotFoundError for nonexistent files"
+ );
+ is(
+ event.target.result,
+ null,
+ "file data should be null on aborted reads"
+ );
+ resolve();
+ };
+ r.onload = function (event) {
+ is(false, "nonexistent file shouldn't load! (FIXME: bug 1122788)");
+ };
+
+ let didThrow = false;
+ try {
+ r.readAsDataURL(blob);
+ } catch (ex) {
+ didThrow = true;
+ }
+
+ // Once this test passes, we should test that onerror gets called and
+ // that the FileReader object is in the right state during that call.
+ is(
+ didThrow,
+ false,
+ "shouldn't throw when opening nonexistent file, should fire error instead"
+ );
+ });
+}
diff --git a/dom/file/tests/crashtests/1480354.html b/dom/file/tests/crashtests/1480354.html
new file mode 100644
index 0000000000..19e53bb1ca
--- /dev/null
+++ b/dom/file/tests/crashtests/1480354.html
@@ -0,0 +1,14 @@
+<html>
+<body>
+ <script>
+function createBlob(blocksize) {
+ var blob = new Blob();
+ while (blob.size < 25 * 1024 * 1024) { // 25 MB
+ blob = new Blob([blob, new Uint8Array(blocksize)]);
+ }
+ URL.createObjectURL(blob);
+}
+createBlob(1024 * 25);
+ </script>
+</body>
+</html>
diff --git a/dom/file/tests/crashtests/1562891.html b/dom/file/tests/crashtests/1562891.html
new file mode 100644
index 0000000000..fff7606a8a
--- /dev/null
+++ b/dom/file/tests/crashtests/1562891.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+ <script>
+ function start () {
+ const canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas')
+ SpecialPowers.forceGC();
+ canvas.toBlob(function (blob) {
+ blob.stream()
+ blob.arrayBuffer().then(() => {})
+ })
+ }
+
+ window.addEventListener('load', start)
+ </script>
+</head>
+</html>
diff --git a/dom/file/tests/crashtests/1747185.html b/dom/file/tests/crashtests/1747185.html
new file mode 100644
index 0000000000..89af55504f
--- /dev/null
+++ b/dom/file/tests/crashtests/1747185.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+ <script>
+ window.addEventListener('load', () => {
+ let a = new Blob([new Uint8Array(2147483647)])
+ let b = new File([a], '')
+ b.stream()
+ })
+ </script>
+</head>
+</html>
diff --git a/dom/file/tests/crashtests/1748342.html b/dom/file/tests/crashtests/1748342.html
new file mode 100644
index 0000000000..5f0811b5bb
--- /dev/null
+++ b/dom/file/tests/crashtests/1748342.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script id="worker1" type="javascript/worker">
+ self.onmessage = async function (e) {
+ self.close()
+ const reader = new FileReader()
+ for (let i = 0; i < 25; i++) {
+ try { reader.readAsBinaryString(e.data[0]) } catch (e) {}
+ }
+ reader.addEventListener("progress", () => {}, {})
+ }
+ </script>
+ <script>
+ window.addEventListener("load", () => {
+ const script = new Blob([document.querySelector("#worker1").textContent], { type: "text/javascript" })
+ const worker = new Worker(window.URL.createObjectURL(script))
+ const data = new Blob(["70𯸟\nℽ㮼٠𛪇\0𛃧كe۰҅妽󠶖󠙻𝅧𡴶𝌋쮁偵9󠰾󠋼7\r𐇽0🥂.\b፟+⍳፟D󠢪3𣚽󠜎🐾�c_߰a<<=𝅦9𝆭𛰅9󠵼ௌ󠌌𖓄ΐ0�⡖‑뢈/-᭰*٠٪᷁e �𯯐𯬓𯕏‫‑걢V*=­￿**󠛐\u2028שּׁ&‭0󠄯𒡯e\n𛰵󠥿⁤𫍰,󠶒𝅥t\nl𧶈a𦜠09k𯔆䴋�|󠄷🦻𛝫󠁥𖭄", "*𒗬۰0\u2029‌/\n\r+󠎔𖼣k*=🿹𯀊\r٪\r󠳐𝲍慑B�\r󠮱\r\n\"\r\\𯉆۹c卑4󠣰󠻬鴗ꛌ\0⌕:\r\n𝚨9ꛅ٠󠃭𯋾\nJ󠯲9\r゙鈷P\u2029҉󠷒۹e \b緁︡𤆥^𯏂゚੿|٫󠕉揅ᷛ𩊜s𯑳2凅9c8H𦰤-\f%٠𨮫󠚵‑2𫈮P𝋄窥57\n-゙҄H󠭗𯪞𣃂-ᷢשּׁ貌솽|𝉃‬c㙡𯐉᭯mL\r"], {
+ "type": "image/png",
+ "endings": "transparent"
+ })
+ worker.postMessage([data], [])
+ })
+ </script>
+</head>
+</html>
diff --git a/dom/file/tests/crashtests/crashtests.list b/dom/file/tests/crashtests/crashtests.list
new file mode 100644
index 0000000000..22eec1962e
--- /dev/null
+++ b/dom/file/tests/crashtests/crashtests.list
@@ -0,0 +1,4 @@
+skip-if(ThreadSanitizer) load 1480354.html
+load 1562891.html
+skip-if(Android||ThreadSanitizer) load 1747185.html # Crashes on Android, times out on TSan.
+load 1748342.html
diff --git a/dom/file/tests/create_file_objects.js b/dom/file/tests/create_file_objects.js
new file mode 100644
index 0000000000..b30759869f
--- /dev/null
+++ b/dom/file/tests/create_file_objects.js
@@ -0,0 +1,20 @@
+/* eslint-env mozilla/chrome-script */
+
+// eslint-disable-next-line mozilla/reject-importGlobalProperties
+Cu.importGlobalProperties(["File"]);
+
+addMessageListener("create-file-objects", function (message) {
+ let files = [];
+ let promises = [];
+ for (fileName of message.fileNames) {
+ promises.push(
+ File.createFromFileName(fileName).then(function (file) {
+ files.push(file);
+ })
+ );
+ }
+
+ Promise.all(promises).then(function () {
+ sendAsyncMessage("created-file-objects", files);
+ });
+});
diff --git a/dom/file/tests/file_blobURL_expiring.html b/dom/file/tests/file_blobURL_expiring.html
new file mode 100644
index 0000000000..a1ae725709
--- /dev/null
+++ b/dom/file/tests/file_blobURL_expiring.html
@@ -0,0 +1,4 @@
+<script>
+var blob = new Blob([123]);
+parent.postMessage(URL.createObjectURL(blob), "*");
+</script>
diff --git a/dom/file/tests/file_mozfiledataurl_audio.ogg b/dom/file/tests/file_mozfiledataurl_audio.ogg
new file mode 100644
index 0000000000..88b2c1b5b2
--- /dev/null
+++ b/dom/file/tests/file_mozfiledataurl_audio.ogg
Binary files differ
diff --git a/dom/file/tests/file_mozfiledataurl_doc.html b/dom/file/tests/file_mozfiledataurl_doc.html
new file mode 100644
index 0000000000..763b20a0f9
--- /dev/null
+++ b/dom/file/tests/file_mozfiledataurl_doc.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<html>
+<body>
+<p>This here is a document!</p>
+<img id=img src="file_mozfiledataurl_img.jpg">
+</html>
diff --git a/dom/file/tests/file_mozfiledataurl_img.jpg b/dom/file/tests/file_mozfiledataurl_img.jpg
new file mode 100644
index 0000000000..dcd99b9670
--- /dev/null
+++ b/dom/file/tests/file_mozfiledataurl_img.jpg
Binary files differ
diff --git a/dom/file/tests/file_mozfiledataurl_inner.html b/dom/file/tests/file_mozfiledataurl_inner.html
new file mode 100644
index 0000000000..a2e539bef7
--- /dev/null
+++ b/dom/file/tests/file_mozfiledataurl_inner.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+<script type="application/javascript">
+var img;
+var audio;
+var iframe;
+
+addEventListener("message", function(e) {
+ mess = JSON.parse(e.data);
+
+ if ("img" in mess)
+ img.src = mess.img;
+ else if ("audio" in mess)
+ audio.src = mess.audio
+ else if ("iframe" in mess)
+ iframe.src = mess.iframe;
+ else if ("xhr" in mess) {
+ let xhr = new XMLHttpRequest();
+ xhr.onerror = function() {
+ sendItUp({ didError: true });
+ }
+ xhr.onload = function() {
+ sendItUp({ text: xhr.responseText });
+ }
+ try {
+ xhr.open("GET", mess.xhr);
+ xhr.send();
+ }
+ catch (ex) {
+ sendItUp({ didThrow: true });
+ }
+ }
+
+}, false);
+
+function sendItUp(obj) {
+ window.parent.postMessage(JSON.stringify(obj), "*");
+}
+
+function audioNotifyParent(e) {
+ sendItUp({ type: e.type });
+}
+
+function imgNotifyParent(e) {
+ sendItUp({ type: e.type,
+ width: e.target.width,
+ height: e.target.height });
+}
+
+function iframeNotifyParent(e) {
+ res = { type: e.type };
+ try {
+ res.text = e.target.contentDocument.getElementsByTagName("p")[0].textContent;
+ } catch (ex) {}
+ try {
+ res.imgWidth = e.target.contentDocument.getElementById("img").width;
+ } catch (ex) {}
+
+ sendItUp(res);
+}
+
+onload = function() {
+ img = document.getElementById('img');
+ img.onerror = img.onload = imgNotifyParent;
+ iframe = document.getElementById('iframe');
+ iframe.onerror = iframe.onload = iframeNotifyParent;
+ audio = document.getElementById('audio');
+ audio.onerror = audio.onloadeddata = audioNotifyParent;
+}
+
+</script>
+<body>
+<img id=img>
+<audio id=audio>
+<iframe id=iframe></iframe>
+</html>
diff --git a/dom/file/tests/file_mozfiledataurl_text.txt b/dom/file/tests/file_mozfiledataurl_text.txt
new file mode 100644
index 0000000000..315338aa9b
--- /dev/null
+++ b/dom/file/tests/file_mozfiledataurl_text.txt
@@ -0,0 +1 @@
+Yarr, here be plaintext file, ya landlubber
diff --git a/dom/file/tests/file_nonascii_blob_url.html b/dom/file/tests/file_nonascii_blob_url.html
new file mode 100644
index 0000000000..89183f4613
--- /dev/null
+++ b/dom/file/tests/file_nonascii_blob_url.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test blob URL for non-ascii domain</title>
+</head>
+<body>
+ <p id="result"></p>
+ <script type="application/javascript">
+
+window.onmessage = function(e) {
+ var blob = new Blob([e.data]);
+ var url = URL.createObjectURL(blob);
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, false);
+ xhr.send(null);
+
+ parent.postMessage(xhr.responseText, '*');
+}
+
+ </script>
+</body>
+</html>
diff --git a/dom/file/tests/fileapi_chromeScript.js b/dom/file/tests/fileapi_chromeScript.js
new file mode 100644
index 0000000000..e93f17cd53
--- /dev/null
+++ b/dom/file/tests/fileapi_chromeScript.js
@@ -0,0 +1,55 @@
+/* eslint-env mozilla/chrome-script */
+
+// eslint-disable-next-line mozilla/reject-importGlobalProperties
+Cu.importGlobalProperties(["File"]);
+
+function createFileWithData(fileData) {
+ var willDelete = fileData === null;
+
+ var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(
+ Ci.nsIProperties
+ );
+
+ var testFile = dirSvc.get("ProfD", Ci.nsIFile);
+ testFile.append("fileAPItestfile");
+ testFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ var outStream = Cc[
+ "@mozilla.org/network/file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+ outStream.init(
+ testFile,
+ 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666,
+ 0
+ );
+ if (willDelete) {
+ fileData = "some irrelevant test data\n";
+ }
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+ return File.createFromNsIFile(testFile).then(domFile => {
+ if (willDelete) {
+ testFile.remove(/* recursive: */ false);
+ }
+
+ return domFile;
+ });
+}
+
+addMessageListener("files.open", function (message) {
+ let promises = [];
+ let list = [];
+
+ for (let fileData of message) {
+ promises.push(
+ createFileWithData(fileData).then(domFile => {
+ list.push(domFile);
+ })
+ );
+ }
+
+ Promise.all(promises).then(() => {
+ sendAsyncMessage("files.opened", list);
+ });
+});
diff --git a/dom/file/tests/mochitest.toml b/dom/file/tests/mochitest.toml
new file mode 100644
index 0000000000..edd640ee8d
--- /dev/null
+++ b/dom/file/tests/mochitest.toml
@@ -0,0 +1,86 @@
+[DEFAULT]
+support-files = [
+ "common_blob.js",
+ "create_file_objects.js",
+ "common_fileReader.js",
+ "common_blob_types.js",
+ "file_blobURL_expiring.html",
+ "file_mozfiledataurl_img.jpg",
+ "file_mozfiledataurl_audio.ogg",
+ "file_mozfiledataurl_doc.html",
+ "file_mozfiledataurl_text.txt",
+ "file_mozfiledataurl_inner.html",
+ "file_nonascii_blob_url.html",
+ "fileapi_chromeScript.js",
+ "worker_fileReader.js",
+ "!/dom/html/test/form_submit_server.sjs",
+ "!/dom/xhr/tests/file_XHRSendData.sjs",
+ "!/dom/xhr/tests/temporaryFileBlob.sjs",
+]
+
+["test_blobURL_expiring.html"]
+
+["test_blob_fragment_and_query.html"]
+
+["test_blob_reading.html"]
+support-files = [
+ "common_blob_reading.js",
+ "worker_blob_reading.js",
+]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_blobconstructor.html"]
+
+["test_bug1507893.html"]
+support-files = ["worker_bug1507893.js"]
+
+["test_bug1742540.html"]
+support-files = ["worker_bug1742540.js"]
+skip-if = ["os == 'android'"] #TIMED_OUT
+
+["test_file_from_blob.html"]
+
+["test_file_negative_date.html"]
+
+["test_fileapi_basic.html"]
+
+["test_fileapi_basic_worker.html"]
+
+["test_fileapi_encoding.html"]
+
+["test_fileapi_encoding_worker.html"]
+
+["test_fileapi_other.html"]
+
+["test_fileapi_other_worker.html"]
+
+["test_fileapi_slice_image.html"]
+
+["test_fileapi_slice_memFile_1.html"]
+
+["test_fileapi_slice_memFile_2.html"]
+
+["test_fileapi_slice_realFile_1.html"]
+
+["test_fileapi_slice_realFile_2.html"]
+skip-if = ["verify && !debug && os == 'win'"]
+
+["test_fileapi_twice.html"]
+
+["test_fileapi_twice_worker.html"]
+
+["test_mozfiledataurl.html"]
+skip-if = [
+ "os == 'android'", #TIMED_OUT
+ "http3",
+ "http2",
+]
+
+["test_nonascii_blob_url.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
diff --git a/dom/file/tests/test_blobURL_expiring.html b/dom/file/tests/test_blobURL_expiring.html
new file mode 100644
index 0000000000..7fdf461371
--- /dev/null
+++ b/dom/file/tests/test_blobURL_expiring.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Blob URI expiration</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script>
+
+onmessage = function(e) {
+ var blobURL = e.data;
+
+ (new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", blobURL);
+ xhr.send();
+ xhr.onload = function() {
+ is(xhr.response, "123", "Response matches!");
+ resolve();
+ }
+ })).then(function() {
+ document.body.removeChild(iframe);
+ }).then(function() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", blobURL);
+ xhr.onerror = function() {
+ ok(true, "The URL should be done!");
+ SimpleTest.finish();
+ }
+ xhr.onload = function() {
+ ok(false, "The URL should be done!");
+ SimpleTest.finish();
+ }
+ xhr.send();
+ });
+}
+
+var iframe = document.createElement('iframe');
+iframe.src = 'file_blobURL_expiring.html';
+document.body.appendChild(iframe);
+
+SimpleTest.waitForExplicitFinish();
+
+ </script>
+</body>
+</html>
diff --git a/dom/file/tests/test_blob_fragment_and_query.html b/dom/file/tests/test_blob_fragment_and_query.html
new file mode 100644
index 0000000000..fa3709c419
--- /dev/null
+++ b/dom/file/tests/test_blob_fragment_and_query.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Blob URI with fragments</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script>
+
+var blob = new Blob(['hello world']);
+ok(blob, "We have a blob.");
+
+var tests = [
+ { part: "", revoke: false, ok: true },
+ { part: "", revoke: true, ok: false },
+ { part: "?aa", revoke: false, ok: false },
+ { part: "?cc#dd", revoke: false, ok: false },
+ // Stripping #fragment on fetch
+ { part: "#bb", revoke: false, ok: true },
+ { part: "#ee?ff", revoke: false, ok: true }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var url = URL.createObjectURL(blob);
+ ok(url, "We have a URI");
+
+ var test = tests.shift();
+
+ if (test.revoke) {
+ URL.revokeObjectURL(url + test.part);
+ }
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url + test.part);
+
+ xhr.onload = function() {
+ ok(test.ok, `URL with "${test.part}" should send()`);
+ is(xhr.responseText, 'hello world', 'URL: ' + url + test.part);
+ runTest();
+ }
+
+ xhr.onerror = function() {
+ ok(!test.ok, `URL with "${test.part}" should fail on send()`);
+ runTest();
+ }
+
+ xhr.send();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+ </script>
+</body>
+</html>
diff --git a/dom/file/tests/test_blob_reading.html b/dom/file/tests/test_blob_reading.html
new file mode 100644
index 0000000000..6efc5c4835
--- /dev/null
+++ b/dom/file/tests/test_blob_reading.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Reading blobs</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="common_blob_reading.js"></script>
+ <script src="common_blob_types.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script>
+
+SimpleTest.waitForExplicitFinish();
+
+async function runAllTests() {
+ let content = "hello world";
+ await forEachBlobType(content, async blob => {
+ await testBlobText(blob, content);
+ await workify('testBlobText', blob, content);
+
+ await testBlobArrayBuffer(blob, content);
+ await workify('testBlobArrayBuffer', blob, content);
+
+ await testBlobStream(blob, content);
+ await workify('testBlobStream', blob, content);
+ });
+}
+
+runAllTests().then(SimpleTest.finish);
+
+ </script>
+</body>
+</html>
diff --git a/dom/file/tests/test_blobconstructor.html b/dom/file/tests/test_blobconstructor.html
new file mode 100644
index 0000000000..d95444f9ea
--- /dev/null
+++ b/dom/file/tests/test_blobconstructor.html
@@ -0,0 +1,246 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=721569
+-->
+<head>
+ <title>Test for Blob constructor (Bug 721569)</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="common_blob.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=721569">Mozilla Bug 721569</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+"use strict";
+/** Test for Bug 721569 **/
+var blob = new Blob();
+ok(blob, "Blob should exist");
+
+ok(blob.size !== undefined, "Blob should have a size property");
+ok(blob.type !== undefined, "Blob should have a type property");
+ok(blob.slice, "Blob should have a slice method");
+
+blob = new Blob([], {type: null});
+ok(blob, "Blob should exist");
+is(blob.type, "null", "Blob type should be stringified");
+
+blob = new Blob([], {type: undefined});
+ok(blob, "Blob should exist");
+is(blob.type, "", "Blob type should be treated as missing");
+
+try {
+blob = new Blob([]);
+ok(true, "an empty blobParts argument should not throw");
+} catch(e) {
+ok(false, "NOT REACHED");
+}
+
+try {
+blob = new Blob(null);
+ok(false, "NOT REACHED");
+} catch(e) {
+ok(true, "a null blobParts member should throw");
+}
+
+try {
+blob = new Blob([], null);
+ok(true, "a null options member should not throw");
+} catch(e) {
+ok(false, "NOT REACHED");
+}
+
+try {
+blob = new Blob([], undefined);
+ok(true, "an undefined options member should not throw");
+} catch(e) {
+ok(false, "NOT REACHED");
+}
+
+try {
+blob = new Blob([], false);
+ok(false, "NOT REACHED");
+} catch(e) {
+ok(true, "a boolean options member should throw");
+}
+
+try {
+blob = new Blob([], 0);
+ok(false, "NOT REACHED");
+} catch(e) {
+ok(true, "a numeric options member should throw");
+}
+
+try {
+blob = new Blob([], "");
+ok(false, "NOT REACHED");
+} catch(e) {
+ok(true, "a string options member should throw");
+}
+
+/** Test for dictionary initialization order **/
+(function() {
+ var o = {};
+ var p = {type: "text/plain", endings: "transparent"};
+ var called = [];
+ function add_to_called(n) {
+ called.push(n);
+ return p[n];
+ }
+ ["type", "endings"].forEach(function(n) {
+ Object.defineProperty(o, n, { get: add_to_called.bind(null, n) });
+ });
+ var b = new Blob([], o);
+ is(JSON.stringify(called), JSON.stringify(["endings", "type"]), "dictionary members should be get in lexicographical order");
+})();
+
+let blob1 = new Blob(["squiggle"]);
+ok(blob1 instanceof Blob, "Blob constructor should produce Blobs");
+ok(!(blob1 instanceof File), "Blob constructor should not produce Files");
+is(blob1.type, "", "Blob constructor with no options should return Blob with empty type");
+is(blob1.size, 8, "Blob constructor should return Blob with correct size");
+
+let blob2 = new Blob(["steak"], {type: "content/type"});
+ok(blob2 instanceof Blob, "Blob constructor should produce Blobs");
+ok(!(blob2 instanceof File), "Blob constructor should not produce Files");
+is(blob2.type, "content/type", "Blob constructor with a type option should return Blob with the type");
+is(blob2.size, 5, "Blob constructor should return Blob with correct size");
+
+
+let aB = new ArrayBuffer(16);
+var int8View = new Int8Array(aB);
+for (var i = 0; i < 16; i++) {
+ int8View[i] = i+65;
+}
+
+let testData =
+ [
+ // Test 3 strings
+ [["foo", "bar", "baz"], {},
+ [{start: 0, length: 9, contents: "foobarbaz"},
+ {start: 0, length: 3, contents: "foo"},
+ {start: 3, length:6, contents: "barbaz"},
+ {start: 6, length: 3, contents: "baz"},
+ {start: 6, length: 6, contents: "baz"},
+ {start: 0, length: 9, contents: "foobarbaz"},
+ {start: 0, length: 11, contents: "foobarbaz"},
+ {start: 10, length: 5, contents: ""}]],
+ // Test string, Blob, string
+ [["foo", blob1, "baz"], {},
+ [{start: 0, length: 3, contents: "foo"},
+ {start: 3, length: 8, contents: "squiggle"},
+ {start: 2, length: 2, contents: "os"},
+ {start: 10, length: 2, contents: "eb"}]],
+ // Test blob, string, blob
+ [[blob1, "foo", blob1], {},
+ [{start: 0, length: 8, contents: "squiggle"},
+ {start: 7, length: 2, contents: "ef"},
+ {start: 10, length: 2, contents: "os"},
+ {start: 1, length: 3, contents: "qui"},
+ {start: 12, length: 3, contents: "qui"},
+ {start: 40, length: 20, contents: ""}]],
+ // Test blobs all the way down
+ [[blob2, blob1, blob2], {},
+ [{start: 0, length: 5, contents: "steak"},
+ {start: 5, length: 8, contents: "squiggle"},
+ {start: 13, length: 5, contents: "steak"},
+ {start: 1, length: 2, contents: "te"},
+ {start: 6, length: 4, contents: "quig"}]],
+ // Test an array buffer
+ [[aB, blob1, "foo"], {},
+ [{start: 0, length: 8, contents: "ABCDEFGH"},
+ {start: 8, length:10, contents: "IJKLMNOPsq"},
+ {start: 17, length: 3, contents: "qui"},
+ {start: 4, length: 8, contents: "EFGHIJKL"}]],
+ // Test an ArrayBufferView
+ [[int8View, blob1, "foo"], {},
+ [{start: 0, length: 8, contents: "ABCDEFGH"},
+ {start: 8, length:10, contents: "IJKLMNOPsq"},
+ {start: 17, length: 3, contents: "qui"},
+ {start: 4, length: 8, contents: "EFGHIJKL"}]],
+ // Test a partial ArrayBufferView
+ [[new Uint8Array(aB, 3, 5), blob1, "foo"], {},
+ [{start: 0, length: 8, contents: "DEFGHsqu"},
+ {start: 8, length:10, contents: "igglefoo"},
+ {start: 4, length: 8, contents: "Hsquiggl"}]],
+ // Test transparent line endings
+ [["foo\r\n", "bar\r", "baz\n"], { endings: "transparent" },
+ [{start: 0, length: 5, contents: "foo\r\n"},
+ {start: 5, length: 4, contents: "bar\r"},
+ {start: 9, length: 4, contents: "baz\n"}]],
+ // Test transparent line endings when the second argument is omitted
+ [["foo\r\n", "bar\r", "baz\n"], undefined,
+ [{start: 0, length: 5, contents: "foo\r\n"},
+ {start: 5, length: 4, contents: "bar\r"},
+ {start: 9, length: 4, contents: "baz\n"}]],
+ // Test native line endings
+ [["foo\r\n", "bar\r", "baz\n"], { endings: "native" },
+ navigator.platform.includes("Win") ?
+ [{start: 0, length: 5, contents: "foo\r\n"},
+ {start: 5, length: 5, contents: "bar\r\n"},
+ {start: 10, length: 5, contents: "baz\r\n"}] :
+ [{start: 0, length: 4, contents: "foo\n"},
+ {start: 4, length: 4, contents: "bar\n"},
+ {start: 8, length: 4, contents: "baz\n"}]],
+ // Test type coercion of a number
+ [[3, int8View, "foo"], {},
+ [{start: 0, length: 8, contents: "3ABCDEFG"},
+ {start: 8, length:10, contents: "HIJKLMNOPf"},
+ {start: 17, length: 4, contents: "foo"},
+ {start: 4, length: 8, contents: "DEFGHIJK"}]]
+ ];
+
+let currentTest = null;
+let testCounter = 0;
+
+function runTests() {
+ if (!currentTest || !currentTest[2].length) {
+ if (!testData.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ currentTest = testData.shift();
+ ++testCounter;
+ }
+
+ let [blobs, options] = currentTest;
+ let test = currentTest[2].shift();
+
+ let blob3;
+ if (options !== undefined) {
+ blob3 = new Blob(blobs, options);
+ } else {
+ blob3 = new Blob(blobs);
+ }
+
+ ok(blob3, "Test " + testCounter + " got blob");
+ ok(blob3 instanceof Blob, "Test " + testCounter + " blob is a Blob");
+ ok(!(blob3 instanceof File), "Test " + testCounter + " blob is not a File");
+
+ let slice = blob3.slice(test.start, test.start + test.length);
+ ok(slice, "Test " + testCounter + " got slice");
+ ok(slice instanceof Blob, "Test " + testCounter + " slice is a Blob");
+ ok(!(slice instanceof File), "Test " + testCounter + " slice is not a File");
+ is(slice.size, test.contents.length, "Test " + testCounter + " slice is correct size");
+
+ testBlob(slice, test.contents, "Test " + testCounter).then(() => {
+ SpecialPowers.gc();
+ runTests();
+ });
+}
+
+SimpleTest.requestLongerTimeout(2);
+SimpleTest.waitForExplicitFinish();
+runTests();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/file/tests/test_bloburi.js b/dom/file/tests/test_bloburi.js
new file mode 100644
index 0000000000..3485b8a681
--- /dev/null
+++ b/dom/file/tests/test_bloburi.js
@@ -0,0 +1,24 @@
+var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+var uris = [
+ {
+ uri: "blob:https://example.com/230d5d50-35f9-9745-a64a-15e47b731a81",
+ local: true,
+ },
+ {
+ uri: "rstp://1.2.3.4/some_path?param=a",
+ local: false,
+ },
+];
+
+function run_test() {
+ for (let i = 0; i < uris.length; i++) {
+ let uri = ios.newURI(uris[i].uri);
+ let flags = ios.getDynamicProtocolFlags(uri);
+
+ Assert.equal(
+ Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE & flags,
+ uris[i].local ? Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE : 0
+ );
+ }
+}
diff --git a/dom/file/tests/test_bug1507893.html b/dom/file/tests/test_bug1507893.html
new file mode 100644
index 0000000000..f0f83ff9ce
--- /dev/null
+++ b/dom/file/tests/test_bug1507893.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Blob URLs fetched in workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script>
+
+SimpleTest.waitForExplicitFinish();
+
+// Let's be positive.
+Promise.resolve()
+
+// Create a file.
+.then(_ => {
+ return new Promise(resolve => {
+ let openerURL = SimpleTest.getTestFileURL("fileapi_chromeScript.js");
+ let opener = SpecialPowers.loadChromeScript(openerURL);
+
+ opener.addMessageListener("files.opened", files => {
+ resolve(files[0]);
+ });
+
+ opener.sendAsyncMessage("files.open", [ "I am the blob content" ]);
+ })
+})
+
+// Just a couple of checks
+.then(file => {
+ ok(file instanceof File, "We want a file");
+ ok(file.size > 0, "We have content");
+ return file;
+})
+
+// Let's create a blobURL
+.then(file => URL.createObjectURL(file))
+
+// Let's send it to a worker.
+.then(url => {
+ return new Promise(resolve => {
+ let w = new Worker('worker_bug1507893.js');
+ w.onmessage = e => {
+ resolve(e.data);
+ };
+ w.postMessage(url);
+ });
+})
+
+// Let's check the worker's output
+.then(blob => {
+ ok(blob instanceof File, "The worker sends us a blob");
+ ok(blob.size > 0, "We have data");
+})
+
+// All done.
+.then(SimpleTest.finish);
+
+ </script>
+</body>
+</html>
diff --git a/dom/file/tests/test_bug1742540.html b/dom/file/tests/test_bug1742540.html
new file mode 100644
index 0000000000..7516f38a85
--- /dev/null
+++ b/dom/file/tests/test_bug1742540.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1742540</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <script>
+
+SimpleTest.waitForExplicitFinish();
+
+add_task(function setupPrefs() {
+ return SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ });
+});
+
+function get_file() {
+ return new Promise(resolve => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "/dynamic/getMyDirectory.sjs", false);
+ xhr.send();
+ let basePath = xhr.responseText;
+
+ let script = SpecialPowers.loadChromeScript(
+ SimpleTest.getTestFileURL("create_file_objects.js")
+ );
+ script.addMessageListener("created-file-objects", files => {
+ resolve(files[0]);
+ });
+ script.sendAsyncMessage("create-file-objects", {
+ fileNames: [basePath + "file_mozfiledataurl_audio.ogg"],
+ });
+ });
+}
+
+function wait_for_message(port, expected_message) {
+ return new Promise(resolve => {
+ port.onmessage = event => {
+ port.onmessage = null;
+ ok(event.data === expected_message, event.data);
+ resolve();
+ };
+ });
+}
+
+function unregister_and_done(registration) {
+ return registration.unregister().then(() => {
+ ok(true, "Will find leaks of nsPipe in BloatView without fix.");
+ SimpleTest.finish;
+ });
+}
+
+add_task(async function send_file_to_serviceworker() {
+ let registration = await navigator.serviceWorker
+ .register("worker_bug1742540.js", { scope: "./" })
+ .then(() => {
+ return navigator.serviceWorker.ready;
+ });
+
+ ok(registration.active, "ServiceWorker is activated");
+
+ let file = await get_file();
+ ok(file.size > 100000, "File size is big enough.");
+ let message = "ServiceWorker receives a file and did not reference it.";
+ let channel = new MessageChannel();
+ let received = wait_for_message(channel.port1, message);
+ registration.active.postMessage({ port: channel.port2, message, file }, [
+ channel.port2,
+ ]);
+ await received;
+
+ let finish = await unregister_and_done(registration);
+});
+
+ </script>
+</body>
+</html>
diff --git a/dom/file/tests/test_createFile.js b/dom/file/tests/test_createFile.js
new file mode 100644
index 0000000000..af9f684ca8
--- /dev/null
+++ b/dom/file/tests/test_createFile.js
@@ -0,0 +1,52 @@
+add_task(async function () {
+ do_get_profile();
+
+ let existingFile = Services.dirsvc
+ .QueryInterface(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ existingFile.append("exists.js");
+ existingFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ var outStream = Cc[
+ "@mozilla.org/network/file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+ outStream.init(
+ existingFile,
+ 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666,
+ 0
+ );
+
+ var fileData = "Hello World!";
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ ok(existingFile.exists(), "exists.js exists");
+
+ let unknownFile = Services.dirsvc
+ .QueryInterface(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ unknownFile.append("wow.txt");
+
+ ok(!unknownFile.exists(), unknownFile.path + " doesn't exist");
+
+ let a = await File.createFromNsIFile(existingFile, { existenceCheck: false });
+ Assert.notEqual(a.size, 0, "The size is correctly set");
+
+ let b = await File.createFromNsIFile(unknownFile, { existenceCheck: false });
+ Assert.equal(b.size, 0, "The size is 0 for unknown file");
+
+ let c = await File.createFromNsIFile(existingFile, { existenceCheck: true });
+ Assert.notEqual(c.size, 0, "The size is correctly set");
+
+ let d = await File.createFromNsIFile(unknownFile, {
+ existenceCheck: true,
+ }).then(
+ _ => true,
+ _ => false
+ );
+ Assert.strictEqual(d, false, "Exception thrown");
+
+ existingFile.remove(true);
+ ok(!existingFile.exists(), "exists.js doesn't exist anymore");
+});
diff --git a/dom/file/tests/test_file_from_blob.html b/dom/file/tests/test_file_from_blob.html
new file mode 100644
index 0000000000..8c12a2823a
--- /dev/null
+++ b/dom/file/tests/test_file_from_blob.html
@@ -0,0 +1,110 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=819900
+-->
+ <head>
+<title>Test for crash caused by unloading and reloading srcdoc iframes</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=819900">Mozilla Bug 819900</a>
+
+<pre id="test">
+<script>
+
+ var b = new Blob(['1234567890']);
+ ok(b, 'Blob created');
+ is(b.size, 10, 'Blob has the right size');
+
+ var status = false;
+ try {
+ f = new File(b);
+ } catch(e) {
+ status = true;
+ }
+ ok(status, "File throws if the second argument is missing");
+
+ status = false;
+ try {
+ f = new File(42, 'foobar.txt');
+ } catch(e) {
+ status = true;
+ }
+ ok(status, "File throws if the argument is not an array");
+
+ status = false;
+ try {
+ f = new File({}, 'foobar.txt');
+ } catch(e) {
+ status = true;
+ }
+ ok(status, "File throws if the argument is not an array");
+
+ status = false;
+ try {
+ f = new File("hello world", 'foobar.txt');
+ } catch(e) {
+ status = true;
+ }
+ ok(status, "File throws if the argument is not an array");
+
+ f = new File(['1234567890'], '');
+ ok(f, 'File created');
+ is(f.size, 10, 'File has the right size');
+ is(f.name, '');
+ is(f.type, '');
+
+ f = new File(['1234567890'], 42);
+ ok(f, 'File created');
+ is(f.size, 10, 'File has the right size');
+ is(f.name, '42');
+ is(f.type, '');
+
+ f = new File(['1234567890'], 'text.txt');
+ ok(f, 'File created');
+ is(f.size, 10, 'File has the right size');
+ is(f.name, 'text.txt');
+ is(f.type, '');
+
+ f = new File(['1234567890'], 'text.txt', { type: 'plain/text' });
+ ok(f, 'File created');
+ is(f.size, 10, 'File has the right size');
+ is(f.name, 'text.txt');
+ is(f.type, 'plain/text');
+
+ f = new File([b], 'text.txt');
+ ok(f, 'File created');
+ is(f.name, 'text.txt');
+ is(f.type, '');
+ is(f.size, b.size);
+
+ f = new File([b], 'test.txt', { type: 'plain/text' });
+ ok(f, 'File created');
+ is(f.name, 'test.txt');
+ is(f.type, 'plain/text');
+ is(f.size, b.size);
+
+ f = new File([b, b], 'test.txt', { type: 'plain/text' });
+ ok(f, 'File created');
+ is(f.name, 'test.txt');
+ is(f.type, 'plain/text');
+ is(f.size, b.size * 2);
+
+ var f2 = new File([f, f], 'test.txt', { type: 'plain/text' });
+ ok(f2, 'File created');
+ is(f2.name, 'test.txt');
+ is(f2.type, 'plain/text');
+ is(f2.size, f.size * 2);
+
+ var f2 = new File([f, f], 'test.txt', b);
+ ok(f2, 'File created');
+ is(f2.name, 'test.txt');
+ is(f2.type, b.type);
+ is(f2.size, f.size * 2);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/file/tests/test_file_negative_date.html b/dom/file/tests/test_file_negative_date.html
new file mode 100644
index 0000000000..2e8528b88d
--- /dev/null
+++ b/dom/file/tests/test_file_negative_date.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1158437
+-->
+<head>
+ <title>Test for negative date in File (Bug 1158437)</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1158437">Mozilla Bug 1158437</a>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+
+var blob = new Blob(['hello world']);
+var f1 = new File([blob], 'f1.txt', { lastModified: 0 });
+var f2 = new File([blob], 'f2.txt', { lastModified: -1 });
+var f3 = new File([blob], 'f3.txt', { lastModified: -1000 });
+
+is(f1.lastModified, 0, "lastModified == 0 is supported");
+is(f2.lastModified, -1, "lastModified == -1 is supported");
+is(f3.lastModified, -1000, "lastModified == -1000 is supported");
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_fileapi_basic.html b/dom/file/tests/test_fileapi_basic.html
new file mode 100644
index 0000000000..7f7aed788e
--- /dev/null
+++ b/dom/file/tests/test_fileapi_basic.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for FileReader API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="common_fileReader.js"></script>
+</head>
+
+<body>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(3);
+
+test_setup()
+.then(data => {
+ return runBasicTests(data);
+})
+.then(SimpleTest.finish);
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_fileapi_basic_worker.html b/dom/file/tests/test_fileapi_basic_worker.html
new file mode 100644
index 0000000000..03db1b4cb6
--- /dev/null
+++ b/dom/file/tests/test_fileapi_basic_worker.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for FileReader API in workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="common_fileReader.js"></script>
+</head>
+
+<body>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(3);
+
+test_setup()
+.then(data => {
+ let worker = new Worker('worker_fileReader.js');
+ worker.postMessage({ tests: 'basic', data });
+
+ worker.onmessage = event => {
+ if (event.data.type == 'finish') {
+ SimpleTest.finish();
+ return;
+ }
+
+ if (event.data.type == 'check') {
+ ok(event.data.status, event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message.");
+ }
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_fileapi_encoding.html b/dom/file/tests/test_fileapi_encoding.html
new file mode 100644
index 0000000000..f20a46ac6c
--- /dev/null
+++ b/dom/file/tests/test_fileapi_encoding.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for FileReader API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="common_fileReader.js"></script>
+</head>
+
+<body>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(3);
+
+test_setup()
+.then(data => {
+ return runEncodingTests(data);
+})
+.then(SimpleTest.finish);
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_fileapi_encoding_worker.html b/dom/file/tests/test_fileapi_encoding_worker.html
new file mode 100644
index 0000000000..ab6827f086
--- /dev/null
+++ b/dom/file/tests/test_fileapi_encoding_worker.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for FileReader API in workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="common_fileReader.js"></script>
+</head>
+
+<body>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(3);
+
+test_setup()
+.then(data => {
+ let worker = new Worker('worker_fileReader.js');
+ worker.postMessage({ tests: 'encoding', data });
+
+ worker.onmessage = event => {
+ if (event.data.type == 'finish') {
+ SimpleTest.finish();
+ return;
+ }
+
+ if (event.data.type == 'check') {
+ ok(event.data.status, event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message.");
+ }
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_fileapi_other.html b/dom/file/tests/test_fileapi_other.html
new file mode 100644
index 0000000000..6c01415be8
--- /dev/null
+++ b/dom/file/tests/test_fileapi_other.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for FileReader API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="common_fileReader.js"></script>
+</head>
+
+<body>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(3);
+
+test_setup()
+.then(data => {
+ return runOtherTests(data);
+})
+.then(SimpleTest.finish);
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_fileapi_other_worker.html b/dom/file/tests/test_fileapi_other_worker.html
new file mode 100644
index 0000000000..a535d3fcb4
--- /dev/null
+++ b/dom/file/tests/test_fileapi_other_worker.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for FileReader API in workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="common_fileReader.js"></script>
+</head>
+
+<body>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(3);
+
+test_setup()
+.then(data => {
+ let worker = new Worker('worker_fileReader.js');
+ worker.postMessage({ tests: 'other', data });
+
+ worker.onmessage = event => {
+ if (event.data.type == 'finish') {
+ SimpleTest.finish();
+ return;
+ }
+
+ if (event.data.type == 'check') {
+ ok(event.data.status, event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message.");
+ }
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_fileapi_slice_image.html b/dom/file/tests/test_fileapi_slice_image.html
new file mode 100644
index 0000000000..c873afa438
--- /dev/null
+++ b/dom/file/tests/test_fileapi_slice_image.html
@@ -0,0 +1,139 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for File API + Slice</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="common_blob.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+
+<p id="display">
+ <canvas id=canvas width=1100 height=1100 hidden moz-opaque></canvas>
+ <canvas id=testcanvas hidden moz-opaque></canvas>
+</p>
+
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(4);
+
+// Create files containing data we'll test with. We'll want long
+// strings to ensure they span multiple buffers while loading
+
+let canvasData;
+let testBinaryData;
+
+function imageLoadHandler(event, resolve) {
+ let origcanvas = $("canvas");
+ let testcanvas = $("testcanvas");
+ let image = event.target;
+ is(image.naturalWidth, origcanvas.width, "width correct");
+ is(image.naturalHeight, origcanvas.height, "height correct");
+
+ testcanvas.width = origcanvas.width;
+ testcanvas.height = origcanvas.height;
+ testcanvas.getContext("2d").drawImage(image, 0, 0);
+ // Do not use |is(testcanvas.toDataURL("image/png"), origcanvas.toDataURL("image/png"), "...");| that results in a _very_ long line.
+ let origDataURL = origcanvas.toDataURL("image/png");
+ let testDataURL = testcanvas.toDataURL("image/png");
+ is(testDataURL.length, origDataURL.length,
+ "Length of correct image data");
+ ok(testDataURL == origDataURL,
+ "Content of correct image data");
+ resolve();
+}
+
+createCanvasURL()
+.then(data => {
+ for (var i = 0; i < 256; i++) {
+ testBinaryData += String.fromCharCode(i);
+ }
+ while (testBinaryData.length < 20000) {
+ testBinaryData += testBinaryData;
+ }
+
+ canvasData = data;
+})
+
+// image in the middle
+.then(() => {
+ return createFile(testBinaryData + canvasData + testBinaryData, "middleTestFile");
+})
+
+// image in the middle - loader
+.then(file => {
+ return new Promise(resolve => {
+ is(file.size, canvasData.length + testBinaryData.length * 2, "correct file size (middle)");
+
+ var img = new Image();
+ img.src = URL.createObjectURL(file.slice(testBinaryData.length,
+ testBinaryData.length + canvasData.length));
+ img.onload = event => {
+ imageLoadHandler(event, resolve);
+ }
+ });
+})
+
+// image at start
+.then(() => {
+ return createFile(canvasData + testBinaryData, "startTestFile");
+})
+
+// image at start - loader
+.then(file => {
+ return new Promise(resolve => {
+ is(file.size, canvasData.length + testBinaryData.length, "correct file size (start)");
+
+ var img = new Image();
+ img.src = URL.createObjectURL(file.slice(0, canvasData.length));
+ img.onload = event => {
+ imageLoadHandler(event, resolve);
+ }
+ });
+})
+
+// image at end
+.then(() => {
+ return createFile(testBinaryData + canvasData, "endTestFile");
+})
+
+// image at end - loader
+.then(file => {
+ return new Promise(resolve => {
+ is(file.size, canvasData.length + testBinaryData.length, "correct file size (end)");
+
+ var img = new Image();
+ img.src = URL.createObjectURL(file.slice(testBinaryData.length,
+ testBinaryData.length + canvasData.length));
+ img.onload = event => {
+ imageLoadHandler(event, resolve);
+ }
+ });
+})
+
+// image past end
+.then(() => {
+ return createFile(testBinaryData + canvasData, "pastEndTestFile");
+})
+
+// image past end - loader
+.then(file => {
+ return new Promise(resolve => {
+ is(file.size, canvasData.length + testBinaryData.length, "correct file size (end)");
+
+ var img = new Image();
+ img.src = URL.createObjectURL(file.slice(testBinaryData.length,
+ testBinaryData.length + canvasData.length + 1000));
+ img.onload = event => {
+ imageLoadHandler(event, resolve);
+ }
+ });
+})
+
+.then(SimpleTest.finish);
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_fileapi_slice_memFile_1.html b/dom/file/tests/test_fileapi_slice_memFile_1.html
new file mode 100644
index 0000000000..56d63d15ba
--- /dev/null
+++ b/dom/file/tests/test_fileapi_slice_memFile_1.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for File API + Slice (in memory)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="common_blob.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+
+<p id="display">
+ <canvas id=canvas width=1100 height=1100 hidden moz-opaque></canvas>
+</p>
+
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(4);
+
+createCanvasURL()
+.then(data => {
+ let cx = $("canvas").getContext('2d');
+ return Promise.all([toBlobPromise(cx.canvas),
+ Promise.resolve(data)]);
+})
+
+.then(args => {
+ let [memFile, data] = args;
+ return testSlice(memFile, data.length, "image/png", data, "memFile", RANGE_1);
+})
+
+.then(SimpleTest.finish);
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_fileapi_slice_memFile_2.html b/dom/file/tests/test_fileapi_slice_memFile_2.html
new file mode 100644
index 0000000000..aef8813bf5
--- /dev/null
+++ b/dom/file/tests/test_fileapi_slice_memFile_2.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for File API + Slice (in memory)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="common_blob.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+
+<p id="display">
+ <canvas id=canvas width=1100 height=1100 hidden moz-opaque></canvas>
+</p>
+
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(4);
+
+createCanvasURL()
+.then(data => {
+ let cx = $("canvas").getContext('2d');
+ return Promise.all([toBlobPromise(cx.canvas),
+ Promise.resolve(data)]);
+})
+
+.then(args => {
+ let [memFile, data] = args;
+ return testSlice(memFile, data.length, "image/png", data, "memFile", RANGE_2);
+})
+
+.then(SimpleTest.finish);
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_fileapi_slice_realFile_1.html b/dom/file/tests/test_fileapi_slice_realFile_1.html
new file mode 100644
index 0000000000..97798d805a
--- /dev/null
+++ b/dom/file/tests/test_fileapi_slice_realFile_1.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for File API + Slice (in file)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="common_blob.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+
+<p id="display">
+ <canvas id=canvas width=1100 height=1100 hidden moz-opaque></canvas>
+</p>
+
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(4);
+
+let canvasData;
+
+createCanvasURL()
+.then(data => {
+ canvasData = data;
+ return createFile(data, "basicTestFile1");
+})
+
+.then(file => {
+ return testSlice(file, canvasData.length, "", canvasData, "fileFile", RANGE_1);
+})
+
+.then(SimpleTest.finish);
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_fileapi_slice_realFile_2.html b/dom/file/tests/test_fileapi_slice_realFile_2.html
new file mode 100644
index 0000000000..882ce61c03
--- /dev/null
+++ b/dom/file/tests/test_fileapi_slice_realFile_2.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for File API + Slice (in file)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="common_blob.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+
+<p id="display">
+ <canvas id=canvas width=1100 height=1100 hidden moz-opaque></canvas>
+</p>
+
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(4);
+
+let canvasData;
+
+createCanvasURL()
+.then(data => {
+ canvasData = data;
+ return createFile(data, "basicTestFile2");
+})
+
+.then(file => {
+ return testSlice(file, canvasData.length, "", canvasData, "fileFile", RANGE_2);
+})
+
+.then(SimpleTest.finish);
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_fileapi_twice.html b/dom/file/tests/test_fileapi_twice.html
new file mode 100644
index 0000000000..96e7febdce
--- /dev/null
+++ b/dom/file/tests/test_fileapi_twice.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for FileReader API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="common_fileReader.js"></script>
+</head>
+
+<body>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(3);
+
+test_setup()
+.then(data => {
+ return runTwiceTests(data);
+})
+.then(SimpleTest.finish);
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_fileapi_twice_worker.html b/dom/file/tests/test_fileapi_twice_worker.html
new file mode 100644
index 0000000000..a79d3992b2
--- /dev/null
+++ b/dom/file/tests/test_fileapi_twice_worker.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for FileReader API in workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="common_fileReader.js"></script>
+</head>
+
+<body>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(3);
+
+test_setup()
+.then(data => {
+ let worker = new Worker('worker_fileReader.js');
+ worker.postMessage({ tests: 'twice', data });
+
+ worker.onmessage = event => {
+ if (event.data.type == 'finish') {
+ SimpleTest.finish();
+ return;
+ }
+
+ if (event.data.type == 'check') {
+ ok(event.data.status, event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message.");
+ }
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/test_ipc_messagemanager_blob.js b/dom/file/tests/test_ipc_messagemanager_blob.js
new file mode 100644
index 0000000000..dacdc9e7bb
--- /dev/null
+++ b/dom/file/tests/test_ipc_messagemanager_blob.js
@@ -0,0 +1,102 @@
+"use strict";
+
+const { XPCShellContentUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/XPCShellContentUtils.sys.mjs"
+);
+
+XPCShellContentUtils.init(this);
+
+function childFrameScript() {
+ addMessageListener("test:ipcClonedMessage", function (message) {
+ if (!Blob.isInstance(message.json)) {
+ sendAsyncMessage(message.name, message.json);
+ return;
+ }
+
+ let reader = new FileReader();
+ reader.addEventListener("load", function () {
+ let response =
+ reader.result == "this is a great success!" ? message.json : "error";
+ sendAsyncMessage(message.name, response);
+ });
+ reader.readAsText(message.json);
+ });
+}
+
+add_task(async function test() {
+ let page = await XPCShellContentUtils.loadContentPage("about:blank", {
+ remote: true,
+ });
+
+ page.loadFrameScript(childFrameScript);
+
+ const blobString = "this is a great success!";
+
+ const messages = [
+ "hi!",
+ "",
+ 2,
+ -0.04,
+ 34329873249872400000000000000,
+ true,
+ false,
+ null,
+ 0,
+
+ // Make sure this one is always last.
+ new Blob(["this ", "is ", "a ", "great ", "success!"], {
+ type: "text/plain",
+ }),
+ ];
+ let receivedMessageIndex = 0;
+
+ let mm = page.browser.messageManager;
+ let done = new Promise(resolve => {
+ mm.addMessageListener("test:ipcClonedMessage", async message => {
+ let data = message.json;
+
+ if (Blob.isInstance(data)) {
+ equal(receivedMessageIndex, messages.length - 1, "Blob is last");
+ equal(
+ data.size,
+ messages[receivedMessageIndex].size,
+ "Correct blob size"
+ );
+ equal(
+ data.type,
+ messages[receivedMessageIndex].type,
+ "Correct blob type"
+ );
+
+ let reader1 = new FileReader();
+ reader1.readAsText(data);
+
+ let reader2 = new FileReader();
+ reader2.readAsText(messages[receivedMessageIndex]);
+
+ await Promise.all([
+ new Promise(res => (reader1.onload = res)),
+ new Promise(res => (reader2.onload = res)),
+ ]);
+
+ equal(reader1.result, blobString, "Result 1");
+ equal(reader2.result, blobString, "Result 2");
+
+ resolve();
+ } else {
+ equal(
+ data,
+ messages[receivedMessageIndex++],
+ "Got correct round-tripped response"
+ );
+ }
+ });
+ });
+
+ for (let message of messages) {
+ mm.sendAsyncMessage("test:ipcClonedMessage", message);
+ }
+
+ await done;
+ await page.close();
+});
diff --git a/dom/file/tests/test_mozfiledataurl.html b/dom/file/tests/test_mozfiledataurl.html
new file mode 100644
index 0000000000..68c88fa28c
--- /dev/null
+++ b/dom/file/tests/test_mozfiledataurl.html
@@ -0,0 +1,224 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
+ <title>Test for File urls</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="start()">
+<p id="display">
+<iframe id=inner></iframe>
+<iframe id=iframe></iframe>
+<img id=img onload="gen.next(event);">
+<audio id=audio onloadeddata="gen.next(event);">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+try {
+ URL.createObjectURL(undefined);
+} catch(e) { }
+
+window.addEventListener("message", function(e) {
+ gen.next(JSON.parse(e.data));
+});
+
+const innerSameSiteURI = "file_mozfiledataurl_inner.html";
+const innerCrossSiteURI = "http://example.com/tests/dom/file/tests/file_mozfiledataurl_inner.html"
+
+var fileNames = ["file_mozfiledataurl_img.jpg",
+ "file_mozfiledataurl_audio.ogg",
+ "file_mozfiledataurl_doc.html",
+ "file_mozfiledataurl_text.txt"];
+
+function start() {
+ let xhr = new XMLHttpRequest;
+ xhr.open("GET", "/dynamic/getMyDirectory.sjs", false);
+ xhr.send();
+ let basePath = xhr.responseText;
+
+ let fullFileNames = [];
+ for (let name of fileNames) {
+ fullFileNames.push(basePath + name);
+ }
+
+ var script = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL("create_file_objects.js"));
+
+ script.addMessageListener("created-file-objects", function handler(files) {
+ script.removeMessageListener("created-file-objects", handler);
+ gen = runTest(files);
+ gen.next();
+ });
+
+ script.sendAsyncMessage("create-file-objects", {fileNames: fullFileNames});
+};
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.expectAssertions(0, 3);
+
+function* runTest([imgFile, audioFile, docFile, xhrFile]) {
+ inner = document.getElementById('inner');
+ img = document.getElementById('img');
+ audio = document.getElementById('audio');
+ iframe = document.getElementById('iframe');
+ inner.onload = function() { gen.next("inner loaded"); };
+
+ // Attempt to load a image in this document
+ var fileurl = URL.createObjectURL(imgFile);
+ img.src = fileurl;
+ var e = (yield);
+ is(e.type, "load", "loaded successfully");
+ is(img.width, 120, "correct width");
+ is(img.height, 90, "correct height");
+
+ // Revoke url and attempt to load a image in this document
+ img.src = "file_mozfiledataurl_img.jpg";
+ is((yield).type, "load", "successfull reset image");
+ URL.revokeObjectURL(fileurl);
+ todo(false, "urls need to act like 404s, not fail to parse");
+/* img.src = fileurl;
+ var e = (yield);
+ is(e.type, "error", "failed successfully");
+ isnot(img.width, 120, "correct error width");
+ isnot(img.height, 90, "correct error height");
+*/
+ // Generate new fileurl and make sure it's different from the old
+ var oldFileurl = fileurl;
+ fileurl = URL.createObjectURL(imgFile);
+ isnot(fileurl, oldFileurl, "URL.createObjectURL generated the same url twice");
+
+ // Attempt to load an image in a different same-origin document
+ inner.src = innerSameSiteURI;
+ yield undefined;
+ inner.contentWindow.postMessage(JSON.stringify({img:fileurl}), "*");
+ var res = (yield);
+ is(res.type, "load", "loaded successfully");
+ is(res.width, 120, "correct width");
+ is(res.height, 90, "correct height");
+
+ // Attempt to load an image in a different cross-origin document
+ inner.src = innerCrossSiteURI;
+ yield undefined;
+ inner.contentWindow.postMessage(JSON.stringify({img:fileurl}), "*");
+ var res = (yield);
+ is(res.type, "error", "failed successfully");
+ isnot(res.width, 120, "correct error width");
+ isnot(res.height, 90, "correct error height");
+
+ // Attempt to load an audio in this document
+ fileurl = URL.createObjectURL(audioFile);
+ audio.src = fileurl;
+ var e = (yield);
+ is(e.type, "loadeddata", "loaded successfully");
+
+ // Revoke url and attempt to load a audio in this document
+ audio.src = "file_mozfiledataurl_audio.ogg";
+ is((yield).type, "loadeddata", "successfully reset audio");
+ URL.revokeObjectURL(fileurl);
+ todo(false, "urls need to act like 404s, not fail to parse");
+/* img.src = fileurl;
+ var e = (yield);
+ is(e.type, "error", "failed successfully");
+ isnot(img.width, 120, "correct error width");
+ isnot(img.height, 90, "correct error height");
+*/
+ // Generate new fileurl and make sure it's different from the old
+ var oldFileurl = fileurl;
+ fileurl = URL.createObjectURL(audioFile);
+ isnot(fileurl, oldFileurl, "URL.createObjectURL generated the same url twice");
+
+ // Attempt to load an audio in a different same-origin document
+ inner.src = innerSameSiteURI;
+ yield undefined;
+ inner.contentWindow.postMessage(JSON.stringify({audio:fileurl}), "*");
+ var res = (yield);
+ is(res.type, "loadeddata", "loaded successfully");
+
+ // Attempt to load an audio in a different cross-origin document
+ inner.src = innerCrossSiteURI;
+ yield undefined;
+ inner.contentWindow.postMessage(JSON.stringify({audio:fileurl}), "*");
+ var res = (yield);
+ is(res.type, "error", "failed successfully");
+
+ // Attempt to load a HTML document in an iframe in this document
+ iframe.onload = function() { gen.next(); };
+ iframe.src = "file_mozfiledataurl_doc.html";
+ yield undefined;
+ is(iframe.contentDocument.getElementsByTagName("p")[0].textContent,
+ "This here is a document!",
+ "iframe loaded successfully");
+ is(iframe.contentDocument.getElementById("img").width, 120,
+ "image in iframe width");
+ is(iframe.contentDocument.getElementById("img").height, 90,
+ "image in iframe height");
+
+ // Attempt to load a HTML document in an iframe in this document, using file url
+ fileurl = URL.createObjectURL(docFile);
+ iframe.src = fileurl;
+ yield undefined;
+ is(iframe.contentDocument.getElementsByTagName("p")[0].textContent,
+ "This here is a document!",
+ "iframe loaded successfully");
+ isnot(iframe.contentDocument.getElementById("img").width, 120,
+ "failed image in iframe width");
+ isnot(iframe.contentDocument.getElementById("img").height, 90,
+ "failed image in iframe height");
+
+ // Attempt to load a HTML document in an iframe in inner document
+ inner.src = innerSameSiteURI;
+ is((yield), "inner loaded", "correct gen.next()");
+ inner.contentWindow.postMessage(JSON.stringify({iframe:"file_mozfiledataurl_doc.html"}), "*");
+ var res = (yield);
+ is(res.type, "load", "loaded successfully");
+ is(res.text, "This here is a document!", "loaded successfully");
+ is(res.imgWidth, 120, "correct width");
+
+ // Attempt to load a HTML document in an iframe in inner document, using file url
+ inner.contentWindow.postMessage(JSON.stringify({iframe:fileurl}), "*");
+ var res = (yield);
+ is(res.type, "load", "loaded successfully");
+ is(res.text, "This here is a document!", "loaded successfully");
+ isnot(res.imgWidth, 120, "correct width");
+
+ // Attempt to load a HTML document in an iframe in inner cross-site document, using file url
+ inner.src = innerCrossSiteURI;
+ is((yield), "inner loaded", "correct gen.next()");
+ inner.contentWindow.postMessage(JSON.stringify({iframe:fileurl}), "*");
+ var res = (yield);
+ is(res.type, "error", "load failed successfully");
+
+ // Attempt to load file url using XHR
+ fileurl = URL.createObjectURL(xhrFile);
+ xhr = new XMLHttpRequest;
+ xhr.onload = function() { gen.next("XHR finished"); };
+ xhr.open("GET", fileurl);
+ xhr.send();
+ is((yield), "XHR finished", "correct gen.next()");
+ xhr.responseText == "Yarr, here be plaintext file, ya landlubber\n";
+
+ // Attempt to load file url using XHR in inner document
+ inner.src = innerSameSiteURI;
+ is((yield), "inner loaded", "correct gen.next()");
+ inner.contentWindow.postMessage(JSON.stringify({xhr:fileurl}), "*");
+ var res = (yield);
+ is(res.didThrow, undefined, "load successful");
+ is(res.text, "Yarr, here be plaintext file, ya landlubber\n", "load successful");
+
+ // Attempt to load file url using XHR
+ inner.src = innerCrossSiteURI;
+ is((yield), "inner loaded", "correct gen.next()");
+ inner.contentWindow.postMessage(JSON.stringify({xhr:fileurl}), "*");
+ var res = (yield);
+ is(res.didError, true, "load failed successfully");
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/file/tests/test_nonascii_blob_url.html b/dom/file/tests/test_nonascii_blob_url.html
new file mode 100644
index 0000000000..1c6c833958
--- /dev/null
+++ b/dom/file/tests/test_nonascii_blob_url.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test blob URL for non-ascii domain</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="content"></div>
+<script class="testbody" type="text/javascript">
+
+var iframe = document.createElement('iframe');
+iframe.src = 'http://xn--exmple-cua.test/tests/dom/file/tests/file_nonascii_blob_url.html';
+iframe.onload = function() {
+ iframe.contentWindow.postMessage('hello world', '*');
+ onmessage = function(e) {
+ is(e.data, 'hello world', "Blob URL for non-ascii domain works");
+ SimpleTest.finish();
+ }
+}
+
+document.getElementById('content').appendChild(iframe);
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/dom/file/tests/worker_blob_reading.js b/dom/file/tests/worker_blob_reading.js
new file mode 100644
index 0000000000..f57161c220
--- /dev/null
+++ b/dom/file/tests/worker_blob_reading.js
@@ -0,0 +1,26 @@
+importScripts("common_blob_reading.js");
+
+function info(message) {
+ postMessage({ type: "info", message });
+}
+
+function ok(a, message) {
+ postMessage({ type: "test", test: !!a, message });
+}
+
+function is(a, b, message) {
+ ok(a === b, message);
+}
+
+onmessage = function (e) {
+ self[e.data.func](e.data.blob, e.data.content).then(
+ () => {
+ postMessage({ type: "done" });
+ },
+ exc => {
+ dump(exc);
+ dump(exc.stack);
+ postMessage({ type: "error", message: exc.toString() });
+ }
+ );
+};
diff --git a/dom/file/tests/worker_bug1507893.js b/dom/file/tests/worker_bug1507893.js
new file mode 100644
index 0000000000..06fce2b2ef
--- /dev/null
+++ b/dom/file/tests/worker_bug1507893.js
@@ -0,0 +1,5 @@
+onmessage = e => {
+ fetch(e.data)
+ .then(r => r.blob())
+ .then(blob => postMessage(blob));
+};
diff --git a/dom/file/tests/worker_bug1742540.js b/dom/file/tests/worker_bug1742540.js
new file mode 100644
index 0000000000..c0330ccb56
--- /dev/null
+++ b/dom/file/tests/worker_bug1742540.js
@@ -0,0 +1,5 @@
+onmessage = e => {
+ let file = e.data.file;
+ let port = e.data.port;
+ port.postMessage(e.data.message);
+};
diff --git a/dom/file/tests/worker_fileReader.js b/dom/file/tests/worker_fileReader.js
new file mode 100644
index 0000000000..2e8408d1bb
--- /dev/null
+++ b/dom/file/tests/worker_fileReader.js
@@ -0,0 +1,30 @@
+importScripts("common_fileReader.js");
+
+function ok(a, msg) {
+ postMessage({ type: "check", msg, status: !!a });
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+onmessage = event => {
+ let p;
+
+ if (event.data.tests == "basic") {
+ p = runBasicTests(event.data.data);
+ } else if (event.data.tests == "encoding") {
+ p = runEncodingTests(event.data.data);
+ } else if (event.data.tests == "twice") {
+ p = runTwiceTests(event.data.data);
+ } else if (event.data.tests == "other") {
+ p = runOtherTests(event.data.data);
+ } else {
+ postMessage({ type: "error" });
+ return;
+ }
+
+ p.then(() => {
+ postMessage({ type: "finish" });
+ });
+};
diff --git a/dom/file/tests/xpcshell.toml b/dom/file/tests/xpcshell.toml
new file mode 100644
index 0000000000..712d9965d6
--- /dev/null
+++ b/dom/file/tests/xpcshell.toml
@@ -0,0 +1,8 @@
+[DEFAULT]
+
+["test_bloburi.js"]
+
+["test_createFile.js"]
+
+["test_ipc_messagemanager_blob.js"]
+skip-if = ["os == 'android'"]