summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/FileAPI
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/FileAPI
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/FileAPI')
-rw-r--r--testing/web-platform/tests/FileAPI/Blob-methods-from-detached-frame.html59
-rw-r--r--testing/web-platform/tests/FileAPI/BlobURL/cross-partition.tentative.https.html276
-rw-r--r--testing/web-platform/tests/FileAPI/BlobURL/support/file_test2.txt0
-rw-r--r--testing/web-platform/tests/FileAPI/BlobURL/test2-manual.html62
-rw-r--r--testing/web-platform/tests/FileAPI/FileReader/progress_event_bubbles_cancelable.html33
-rw-r--r--testing/web-platform/tests/FileAPI/FileReader/support/file_test1.txt0
-rw-r--r--testing/web-platform/tests/FileAPI/FileReader/test_errors-manual.html72
-rw-r--r--testing/web-platform/tests/FileAPI/FileReader/test_notreadableerrors-manual.html42
-rw-r--r--testing/web-platform/tests/FileAPI/FileReader/test_securityerrors-manual.html40
-rw-r--r--testing/web-platform/tests/FileAPI/FileReader/workers.html27
-rw-r--r--testing/web-platform/tests/FileAPI/FileReaderSync.worker.js56
-rw-r--r--testing/web-platform/tests/FileAPI/META.yml6
-rw-r--r--testing/web-platform/tests/FileAPI/blob/Blob-array-buffer.any.js45
-rw-r--r--testing/web-platform/tests/FileAPI/blob/Blob-constructor-dom.window.js53
-rw-r--r--testing/web-platform/tests/FileAPI/blob/Blob-constructor-endings.html104
-rw-r--r--testing/web-platform/tests/FileAPI/blob/Blob-constructor.any.js468
-rw-r--r--testing/web-platform/tests/FileAPI/blob/Blob-in-worker.worker.js9
-rw-r--r--testing/web-platform/tests/FileAPI/blob/Blob-slice-overflow.any.js32
-rw-r--r--testing/web-platform/tests/FileAPI/blob/Blob-slice.any.js231
-rw-r--r--testing/web-platform/tests/FileAPI/blob/Blob-stream-byob-crash.html11
-rw-r--r--testing/web-platform/tests/FileAPI/blob/Blob-stream.any.js73
-rw-r--r--testing/web-platform/tests/FileAPI/blob/Blob-text.any.js64
-rw-r--r--testing/web-platform/tests/FileAPI/file/File-constructor-endings.html104
-rw-r--r--testing/web-platform/tests/FileAPI/file/File-constructor.any.js155
-rw-r--r--testing/web-platform/tests/FileAPI/file/Worker-read-file-constructor.worker.js15
-rw-r--r--testing/web-platform/tests/FileAPI/file/resources/echo-content-escaped.py26
-rw-r--r--testing/web-platform/tests/FileAPI/file/send-file-form-controls.html113
-rw-r--r--testing/web-platform/tests/FileAPI/file/send-file-form-iso-2022-jp.html65
-rw-r--r--testing/web-platform/tests/FileAPI/file/send-file-form-punctuation.html226
-rw-r--r--testing/web-platform/tests/FileAPI/file/send-file-form-utf-8.html62
-rw-r--r--testing/web-platform/tests/FileAPI/file/send-file-form-windows-1252.html62
-rw-r--r--testing/web-platform/tests/FileAPI/file/send-file-form-x-user-defined.html63
-rw-r--r--testing/web-platform/tests/FileAPI/file/send-file-form.html25
-rw-r--r--testing/web-platform/tests/FileAPI/file/send-file-formdata-controls.any.js69
-rw-r--r--testing/web-platform/tests/FileAPI/file/send-file-formdata-punctuation.any.js144
-rw-r--r--testing/web-platform/tests/FileAPI/file/send-file-formdata-utf-8.any.js33
-rw-r--r--testing/web-platform/tests/FileAPI/file/send-file-formdata.any.js8
-rw-r--r--testing/web-platform/tests/FileAPI/fileReader.any.js59
-rw-r--r--testing/web-platform/tests/FileAPI/filelist-section/filelist.html57
-rw-r--r--testing/web-platform/tests/FileAPI/filelist-section/filelist_multiple_selected_files-manual.html64
-rw-r--r--testing/web-platform/tests/FileAPI/filelist-section/filelist_selected_file-manual.html64
-rw-r--r--testing/web-platform/tests/FileAPI/filelist-section/support/upload.txt1
-rw-r--r--testing/web-platform/tests/FileAPI/filelist-section/support/upload.zipbin0 -> 220 bytes
-rw-r--r--testing/web-platform/tests/FileAPI/historical.https.html65
-rw-r--r--testing/web-platform/tests/FileAPI/idlharness-manual.html45
-rw-r--r--testing/web-platform/tests/FileAPI/idlharness.any.js19
-rw-r--r--testing/web-platform/tests/FileAPI/idlharness.html37
-rw-r--r--testing/web-platform/tests/FileAPI/idlharness.worker.js17
-rw-r--r--testing/web-platform/tests/FileAPI/progress-manual.html49
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/Determining-Encoding.any.js81
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/FileReader-event-handler-attributes.any.js17
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/FileReader-multiple-reads.any.js81
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/filereader_abort.any.js38
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/filereader_error.any.js19
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/filereader_events.any.js19
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/filereader_file-manual.html69
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/filereader_file_img-manual.html47
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsArrayBuffer.any.js23
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsBinaryString.any.js23
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsDataURL.any.js42
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsText.any.js36
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/filereader_readystate.any.js19
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/filereader_result.any.js82
-rw-r--r--testing/web-platform/tests/FileAPI/reading-data-section/support/blue-100x100.pngbin0 -> 227 bytes
-rw-r--r--testing/web-platform/tests/FileAPI/support/Blob.js70
-rw-r--r--testing/web-platform/tests/FileAPI/support/document-domain-setter.sub.html7
-rw-r--r--testing/web-platform/tests/FileAPI/support/empty-document.html3
-rw-r--r--testing/web-platform/tests/FileAPI/support/historical-serviceworker.js5
-rw-r--r--testing/web-platform/tests/FileAPI/support/incumbent.sub.html22
-rw-r--r--testing/web-platform/tests/FileAPI/support/send-file-form-helper.js282
-rw-r--r--testing/web-platform/tests/FileAPI/support/send-file-formdata-helper.js99
-rw-r--r--testing/web-platform/tests/FileAPI/support/upload.txt1
-rw-r--r--testing/web-platform/tests/FileAPI/support/url-origin.html6
-rw-r--r--testing/web-platform/tests/FileAPI/unicode.html46
-rw-r--r--testing/web-platform/tests/FileAPI/url/cross-global-revoke.sub.html62
-rw-r--r--testing/web-platform/tests/FileAPI/url/multi-global-origin-serialization.sub.html26
-rw-r--r--testing/web-platform/tests/FileAPI/url/resources/create-helper.html7
-rw-r--r--testing/web-platform/tests/FileAPI/url/resources/create-helper.js4
-rw-r--r--testing/web-platform/tests/FileAPI/url/resources/fetch-tests.js71
-rw-r--r--testing/web-platform/tests/FileAPI/url/resources/revoke-helper.html7
-rw-r--r--testing/web-platform/tests/FileAPI/url/resources/revoke-helper.js9
-rw-r--r--testing/web-platform/tests/FileAPI/url/sandboxed-iframe.html32
-rw-r--r--testing/web-platform/tests/FileAPI/url/unicode-origin.sub.html23
-rw-r--r--testing/web-platform/tests/FileAPI/url/url-charset.window.js34
-rw-r--r--testing/web-platform/tests/FileAPI/url/url-format.any.js70
-rw-r--r--testing/web-platform/tests/FileAPI/url/url-in-tags-revoke.window.js115
-rw-r--r--testing/web-platform/tests/FileAPI/url/url-in-tags.window.js48
-rw-r--r--testing/web-platform/tests/FileAPI/url/url-lifetime.html56
-rw-r--r--testing/web-platform/tests/FileAPI/url/url-reload.window.js36
-rw-r--r--testing/web-platform/tests/FileAPI/url/url-with-fetch.any.js72
-rw-r--r--testing/web-platform/tests/FileAPI/url/url-with-xhr.any.js68
-rw-r--r--testing/web-platform/tests/FileAPI/url/url_createobjecturl_file-manual.html45
-rw-r--r--testing/web-platform/tests/FileAPI/url/url_createobjecturl_file_img-manual.html28
-rw-r--r--testing/web-platform/tests/FileAPI/url/url_xmlhttprequest_img-ref.html12
-rw-r--r--testing/web-platform/tests/FileAPI/url/url_xmlhttprequest_img.html27
95 files changed, 5399 insertions, 0 deletions
diff --git a/testing/web-platform/tests/FileAPI/Blob-methods-from-detached-frame.html b/testing/web-platform/tests/FileAPI/Blob-methods-from-detached-frame.html
new file mode 100644
index 0000000000..37efd5ed20
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/Blob-methods-from-detached-frame.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Blob methods from detached frame work as expected</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe id="emptyDocumentIframe" src="../support/empty-document.html"></iframe>
+
+<script>
+const BlobPrototypeFromDetachedFramePromise = new Promise(resolve => {
+ emptyDocumentIframe.onload = () => {
+ const BlobPrototype = emptyDocumentIframe.contentWindow.Blob.prototype;
+ emptyDocumentIframe.remove();
+ resolve(BlobPrototype);
+ };
+});
+
+const charCodeArrayToString = charCodeArray => Array.from(charCodeArray, c => String.fromCharCode(c)).join("");
+const charCodeBufferToString = charCodeBuffer => charCodeArrayToString(new Uint8Array(charCodeBuffer));
+
+promise_test(async () => {
+ const { slice } = await BlobPrototypeFromDetachedFramePromise;
+ const blob = new Blob(["foobar"]);
+
+ const slicedBlob = slice.call(blob, 1, 3);
+ assert_true(slicedBlob instanceof Blob);
+
+ assert_equals(await slicedBlob.text(), "oo");
+ assert_equals(charCodeBufferToString(await slicedBlob.arrayBuffer()), "oo");
+
+ const reader = slicedBlob.stream().getReader();
+ const { value } = await reader.read();
+ assert_equals(charCodeArrayToString(value), "oo");
+}, "slice()");
+
+promise_test(async () => {
+ const { text } = await BlobPrototypeFromDetachedFramePromise;
+ const blob = new Blob(["foo"]);
+
+ assert_equals(await text.call(blob), "foo");
+}, "text()");
+
+promise_test(async () => {
+ const { arrayBuffer } = await BlobPrototypeFromDetachedFramePromise;
+ const blob = new Blob(["bar"]);
+
+ const charCodeBuffer = await arrayBuffer.call(blob);
+ assert_equals(charCodeBufferToString(charCodeBuffer), "bar");
+}, "arrayBuffer()");
+
+promise_test(async () => {
+ const { stream } = await BlobPrototypeFromDetachedFramePromise;
+ const blob = new Blob(["baz"]);
+
+ const reader = stream.call(blob).getReader();
+ const { value } = await reader.read();
+ assert_equals(charCodeArrayToString(value), "baz");
+}, "stream()");
+</script>
diff --git a/testing/web-platform/tests/FileAPI/BlobURL/cross-partition.tentative.https.html b/testing/web-platform/tests/FileAPI/BlobURL/cross-partition.tentative.https.html
new file mode 100644
index 0000000000..c75ce07d05
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/BlobURL/cross-partition.tentative.https.html
@@ -0,0 +1,276 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<!-- Pull in executor_path needed by newPopup / newIframe -->
+<script src="/html/cross-origin-embedder-policy/credentialless/resources/common.js"></script>
+<!-- Pull in importScript / newPopup / newIframe -->
+<script src="/html/anonymous-iframe/resources/common.js"></script>
+<body>
+<script>
+
+const did_revoke_response = "URL.revokeObjectURL did revoke";
+const did_not_revoke_response = "URL.revokeObjectURL did not revoke";
+
+const can_blob_url_be_revoked_js = (blob_url, response_queue_name) => `
+ async function test() {
+ if (!('revokeObjectURL' in URL)) {
+ return send("${response_queue_name}", "URL.revokeObjectURL is not exposed");
+ }
+ try {
+ var blob = await fetch("${blob_url}").then(response => response.blob());
+ await blob.text();
+ } catch {
+ return send("${response_queue_name}", "Blob URL invalid");
+ }
+ try {
+ URL.revokeObjectURL("${blob_url}");
+ } catch(e) {
+ return send("${response_queue_name}", e.toString());
+ }
+ try {
+ const blob = await fetch("${blob_url}").then(response => response.blob());
+ } catch(e) {
+ return send("${response_queue_name}", "${did_revoke_response}");
+ }
+ return send("${response_queue_name}", "${did_not_revoke_response}");
+ }
+ await test();
+`;
+
+const add_iframe_js = (iframe_origin, response_queue_uuid) => `
+ const importScript = ${importScript};
+ await importScript("/html/cross-origin-embedder-policy/credentialless" +
+ "/resources/common.js");
+ await importScript("/html/anonymous-iframe/resources/common.js");
+ await importScript("/common/utils.js");
+ await send("${response_queue_uuid}", newIframe("${iframe_origin}"));
+`;
+
+const same_site_origin = get_host_info().HTTPS_ORIGIN;
+const cross_site_origin = get_host_info().HTTPS_NOTSAMESITE_ORIGIN;
+
+async function create_test_iframes(t, response_queue_uuid) {
+
+ // Create a same-origin iframe in a cross-site popup.
+ const not_same_site_popup_uuid = newPopup(t, cross_site_origin);
+ await send(not_same_site_popup_uuid,
+ add_iframe_js(same_site_origin, response_queue_uuid));
+ const iframe_1_uuid = await receive(response_queue_uuid);
+
+ // Create a same-origin iframe in a same-site popup.
+ const same_origin_popup_uuid = newPopup(t, same_site_origin);
+ await send(same_origin_popup_uuid,
+ add_iframe_js(same_site_origin, response_queue_uuid));
+ const iframe_2_uuid = await receive(response_queue_uuid);
+
+ return [iframe_1_uuid, iframe_2_uuid];
+}
+
+promise_test(t => {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const response_queue_uuid = token();
+
+ const [iframe_1_uuid, iframe_2_uuid] =
+ await create_test_iframes(t, response_queue_uuid);
+
+ const blob = new Blob(["blob data"], {type : "text/plain"});
+ const blob_url = window.URL.createObjectURL(blob);
+ t.add_cleanup(() => window.URL.revokeObjectURL(blob_url));
+
+ await send(iframe_1_uuid,
+ can_blob_url_be_revoked_js(blob_url, response_queue_uuid));
+ var response = await receive(response_queue_uuid);
+ if (response !== did_not_revoke_response) {
+ reject(`Blob URL was revoked in not-same-top-level-site iframe: ${response}`);
+ }
+
+ await send(iframe_2_uuid,
+ can_blob_url_be_revoked_js(blob_url, response_queue_uuid));
+ response = await receive(response_queue_uuid);
+ if (response !== did_revoke_response) {
+ reject(`Blob URL wasn't revoked in same-top-level-site iframe: ${response}`);
+ }
+
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ });
+}, "Blob URL shouldn't be revocable from a cross-partition iframe");
+
+const newWorker = (origin) => {
+ const worker_token = token();
+ const worker_url = origin + executor_worker_path + `&uuid=${worker_token}`;
+ const worker = new Worker(worker_url);
+ return worker_token;
+}
+
+promise_test(t => {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const response_queue_uuid = token();
+
+ const create_worker_js = (origin) => `
+ const importScript = ${importScript};
+ await importScript("/html/cross-origin-embedder-policy/credentialless" +
+ "/resources/common.js");
+ await importScript("/html/anonymous-iframe/resources/common.js");
+ await importScript("/common/utils.js");
+ const newWorker = ${newWorker};
+ await send("${response_queue_uuid}", newWorker("${origin}"));
+ `;
+
+ const [iframe_1_uuid, iframe_2_uuid] =
+ await create_test_iframes(t, response_queue_uuid);
+
+ // Create a dedicated worker in the cross-top-level-site iframe.
+ await send(iframe_1_uuid, create_worker_js(same_site_origin));
+ const worker_1_uuid = await receive(response_queue_uuid);
+
+ // Create a dedicated worker in the same-top-level-site iframe.
+ await send(iframe_2_uuid, create_worker_js(same_site_origin));
+ const worker_2_uuid = await receive(response_queue_uuid);
+
+ const blob = new Blob(["blob data"], {type : "text/plain"});
+ const blob_url = window.URL.createObjectURL(blob);
+ t.add_cleanup(() => window.URL.revokeObjectURL(blob_url));
+
+ await send(worker_1_uuid,
+ can_blob_url_be_revoked_js(blob_url, response_queue_uuid));
+ var response = await receive(response_queue_uuid);
+ if (response !== did_not_revoke_response) {
+ reject(`Blob URL was revoked in not-same-top-level-site dedicated worker: ${response}`);
+ }
+
+ await send(worker_2_uuid,
+ can_blob_url_be_revoked_js(blob_url, response_queue_uuid));
+ response = await receive(response_queue_uuid);
+ if (response !== did_revoke_response) {
+ reject(`Blob URL wasn't revoked in same-top-level-site dedicated worker: ${response}`);
+ }
+
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ });
+}, "Blob URL shouldn't be revocable from a cross-partition dedicated worker");
+
+const newSharedWorker = (origin) => {
+ const worker_token = token();
+ const worker_url = origin + executor_worker_path + `&uuid=${worker_token}`;
+ const worker = new SharedWorker(worker_url, worker_token);
+ return worker_token;
+}
+
+promise_test(t => {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const response_queue_uuid = token();
+
+ const create_worker_js = (origin) => `
+ const importScript = ${importScript};
+ await importScript("/html/cross-origin-embedder-policy/credentialless" +
+ "/resources/common.js");
+ await importScript("/html/anonymous-iframe/resources/common.js");
+ await importScript("/common/utils.js");
+ const newSharedWorker = ${newSharedWorker};
+ await send("${response_queue_uuid}", newSharedWorker("${origin}"));
+ `;
+
+ const [iframe_1_uuid, iframe_2_uuid] =
+ await create_test_iframes(t, response_queue_uuid);
+
+ // Create a shared worker in the cross-top-level-site iframe.
+ await send(iframe_1_uuid, create_worker_js(same_site_origin));
+ const worker_1_uuid = await receive(response_queue_uuid);
+
+ // Create a shared worker in the same-top-level-site iframe.
+ await send(iframe_2_uuid, create_worker_js(same_site_origin));
+ const worker_2_uuid = await receive(response_queue_uuid);
+
+ const blob = new Blob(["blob data"], {type : "text/plain"});
+ const blob_url = window.URL.createObjectURL(blob);
+ t.add_cleanup(() => window.URL.revokeObjectURL(blob_url));
+
+ await send(worker_1_uuid,
+ can_blob_url_be_revoked_js(blob_url, response_queue_uuid));
+ var response = await receive(response_queue_uuid);
+ if (response !== did_not_revoke_response) {
+ reject(`Blob URL was revoked in not-same-top-level-site shared worker: ${response}`);
+ }
+
+ await send(worker_2_uuid,
+ can_blob_url_be_revoked_js(blob_url, response_queue_uuid));
+ response = await receive(response_queue_uuid);
+ if (response !== did_revoke_response) {
+ reject(`Blob URL wasn't revoked in same-top-level-site shared worker: ${response}`);
+ }
+
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ });
+}, "Blob URL shouldn't be revocable from a cross-partition shared worker");
+
+const newServiceWorker = async (origin) => {
+ const worker_token = token();
+ const worker_url = origin + executor_service_worker_path +
+ `&uuid=${worker_token}`;
+ const worker_url_path = executor_service_worker_path.substring(0,
+ executor_service_worker_path.lastIndexOf('/'));
+ const scope = worker_url_path + "/not-used/";
+ const reg = await navigator.serviceWorker.register(worker_url,
+ {'scope': scope});
+ return worker_token;
+}
+
+promise_test(t => {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const response_queue_uuid = token();
+
+ const create_worker_js = (origin) => `
+ const importScript = ${importScript};
+ await importScript("/html/cross-origin-embedder-policy/credentialless" +
+ "/resources/common.js");
+ await importScript("/html/anonymous-iframe/resources/common.js");
+ await importScript("/common/utils.js");
+ const newServiceWorker = ${newServiceWorker};
+ await send("${response_queue_uuid}", await newServiceWorker("${origin}"));
+ `;
+
+ const [iframe_1_uuid, iframe_2_uuid] =
+ await create_test_iframes(t, response_queue_uuid);
+
+ // Create a service worker in either iframe.
+ await send(iframe_1_uuid, create_worker_js(same_site_origin));
+ var worker_1_uuid = await receive(response_queue_uuid);
+ t.add_cleanup(() =>
+ send(worker_1_uuid, "self.registration.unregister();"));
+
+ const blob = new Blob(["blob data"], {type : "text/plain"});
+ const blob_url = window.URL.createObjectURL(blob);
+ t.add_cleanup(() => window.URL.revokeObjectURL(blob_url));
+
+ await send(worker_1_uuid,
+ can_blob_url_be_revoked_js(blob_url, response_queue_uuid));
+ const response = await receive(response_queue_uuid);
+ if (response !== "URL.revokeObjectURL is not exposed") {
+ reject(`URL.revokeObjectURL is exposed in a Service Worker context: ${response}`);
+ }
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ });
+}, "Blob URL shouldn't be revocable from a service worker");
+</script>
+</body>
diff --git a/testing/web-platform/tests/FileAPI/BlobURL/support/file_test2.txt b/testing/web-platform/tests/FileAPI/BlobURL/support/file_test2.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/BlobURL/support/file_test2.txt
diff --git a/testing/web-platform/tests/FileAPI/BlobURL/test2-manual.html b/testing/web-platform/tests/FileAPI/BlobURL/test2-manual.html
new file mode 100644
index 0000000000..07fb27ef8a
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/BlobURL/test2-manual.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Blob and File reference URL Test(2)</title>
+ <link rel=help href="http://dev.w3.org/2006/webapi/FileAPI/#convenienceAPI">
+ <link rel=author title="Breezewish" href="mailto:me@breeswish.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <form name="upload">
+ <input type="file" id="fileChooser"><br><input type="button" id="start" value="start">
+ </form>
+
+ <div>
+ <p>Test steps:</p>
+ <ol>
+ <li>Download the <a href="support/file_test2.txt">file</a>.</li>
+ <li>Select the file in the file inputbox.</li>
+ <li>Delete the file.</li>
+ <li>Click the 'start' button.</li>
+ </ol>
+ </div>
+
+ <div id="log"></div>
+
+ <script>
+
+ var fileChooser = document.querySelector('#fileChooser');
+
+ setup({explicit_done: true});
+ setup({explicit_timeout: true});
+
+ on_event(document.querySelector('#start'), 'click', function() {
+
+ async_test(function(t) {
+
+ var url = URL.createObjectURL(fileChooser.files[0]);
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, true);
+ xhr.onreadystatechange = t.step_func(function() {
+ switch (xhr.readyState) {
+ case xhr.DONE:
+ assert_equals(xhr.status, 500, 'status code should be 500.');
+ t.done();
+ return;
+ }
+ });
+
+ xhr.send();
+
+ }, 'Check whether the browser response 500 in XHR if the selected file which File/Blob URL refered is not found');
+
+ done();
+
+ });
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/FileAPI/FileReader/progress_event_bubbles_cancelable.html b/testing/web-platform/tests/FileAPI/FileReader/progress_event_bubbles_cancelable.html
new file mode 100644
index 0000000000..6a03243f93
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/FileReader/progress_event_bubbles_cancelable.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>File API Test: Progress Event - bubbles, cancelable</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="http://www.w3.org/TR/FileAPI/#events">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ async_test(function(){
+ var blob = new Blob(["TEST"]);
+ var reader = new FileReader();
+
+ reader.onloadstart = this.step_func(function(evt) {
+ assert_false(evt.bubbles, "The bubbles must be false when the event is dispatched");
+ assert_false(evt.cancelable, "The cancelable must be false when the event is dispatched");
+ });
+
+ reader.onload = this.step_func(function(evt) {
+ assert_false(evt.bubbles, "The bubbles must be false when the event is dispatched");
+ assert_false(evt.cancelable, "The cancelable must be false when the event is dispatched");
+ });
+
+ reader.onloadend = this.step_func(function(evt) {
+ assert_false(evt.bubbles, "The bubbles must be false when the event is dispatched");
+ assert_false(evt.cancelable, "The cancelable must be false when the event is dispatched");
+ this.done();
+ });
+
+ reader.readAsText(blob);
+ }, "Check the values of bubbles and cancelable are false when the progress event is dispatched");
+</script>
+
diff --git a/testing/web-platform/tests/FileAPI/FileReader/support/file_test1.txt b/testing/web-platform/tests/FileAPI/FileReader/support/file_test1.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/FileReader/support/file_test1.txt
diff --git a/testing/web-platform/tests/FileAPI/FileReader/test_errors-manual.html b/testing/web-platform/tests/FileAPI/FileReader/test_errors-manual.html
new file mode 100644
index 0000000000..b8c3f84d2b
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/FileReader/test_errors-manual.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>FileReader Errors Test</title>
+ <link rel=help href="http://dev.w3.org/2006/webapi/FileAPI/#convenienceAPI">
+ <link rel=author title="Breezewish" href="mailto:me@breeswish.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <form name="upload">
+ <input type="file" id="fileChooser"><br><input type="button" id="start" value="start">
+ </form>
+
+ <div>
+ <p>Test steps:</p>
+ <ol>
+ <li>Download the <a href="support/file_test1.txt">file</a>.</li>
+ <li>Select the file in the file inputbox.</li>
+ <li>Delete the file.</li>
+ <li>Click the 'start' button.</li>
+ </ol>
+ </div>
+
+ <div id="log"></div>
+
+ <script>
+
+ var fileChooser = document.querySelector('#fileChooser');
+
+ setup({explicit_done: true});
+ setup({explicit_timeout: true});
+
+ on_event(fileChooser, 'change', function() {
+
+ async_test(function(t) {
+
+ var reader = new FileReader();
+ reader.readAsArrayBuffer(fileChooser.files[0]);
+
+ reader.onloadend = t.step_func_done(function(event) {
+ assert_equals(event.target.readyState, FileReader.DONE);
+ assert_equals(reader.error, null);
+ });
+
+ }, 'FileReader.error should be null if there are no errors when reading');
+
+ });
+
+ on_event(document.querySelector('#start'), 'click', function() {
+
+ async_test(function(t) {
+
+ var reader = new FileReader();
+ reader.readAsArrayBuffer(fileChooser.files[0]);
+
+ reader.onloadend = t.step_func_done(function(event) {
+ assert_equals(event.target.readyState, FileReader.DONE);
+ assert_equals(reader.error.code, 8);
+ assert_true(reader.error instanceof DOMException);
+ });
+
+ }, 'FileReader.error should be NOT_FOUND_ERR if the file is not found when reading');
+
+ done();
+
+ });
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/FileAPI/FileReader/test_notreadableerrors-manual.html b/testing/web-platform/tests/FileAPI/FileReader/test_notreadableerrors-manual.html
new file mode 100644
index 0000000000..46d73598a0
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/FileReader/test_notreadableerrors-manual.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FileReader NotReadableError Test</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://w3c.github.io/FileAPI/#dfn-error-codes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<form name="upload">
+ <input type="file" id="fileChooser"><br><input type="button" id="start" value="start">
+</form>
+
+<div>
+ <p>Test steps:</p>
+ <ol>
+ <li>Download the <a href="support/file_test1.txt">file</a>.</li>
+ <li>Select the file in the file inputbox.</li>
+ <li>Delete the file's readable permission.</li>
+ <li>Click the 'start' button.</li>
+ </ol>
+</div>
+
+<script>
+
+ const fileChooser = document.querySelector('#fileChooser');
+
+ setup({explicit_done: true});
+ setup({explicit_timeout: true});
+
+ on_event(document.querySelector('#start'), 'click', () => {
+ async_test(t => {
+ const reader = new FileReader();
+ reader.readAsArrayBuffer(fileChooser.files[0]);
+ reader.onloadend = t.step_func_done(event => {
+ assert_equals(event.target.readyState, FileReader.DONE);
+ assert_equals(reader.error.name, "NotReadableError");
+ });
+ }, 'FileReader.error should be NotReadableError if the file is unreadable');
+ done();
+ });
+
+</script>
+
diff --git a/testing/web-platform/tests/FileAPI/FileReader/test_securityerrors-manual.html b/testing/web-platform/tests/FileAPI/FileReader/test_securityerrors-manual.html
new file mode 100644
index 0000000000..add93ed69d
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/FileReader/test_securityerrors-manual.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FileReader SecurityError Test</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://w3c.github.io/FileAPI/#dfn-error-codes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<form name="upload">
+ <input type="file" id="fileChooser"><br><input type="button" id="start" value="start">
+</form>
+
+<div>
+ <p>Test steps:</p>
+ <ol>
+ <li>Select a system sensitive file (e.g. files in /usr/bin, password files,
+ and other native operating system executables) in the file inputbox.</li>
+ <li>Click the 'start' button.</li>
+ </ol>
+</div>
+
+<script>
+
+ const fileChooser = document.querySelector('#fileChooser');
+
+ setup({explicit_done: true});
+ setup({explicit_timeout: true});
+
+ on_event(document.querySelector('#start'), 'click', () => {
+ async_test(t => {
+ const reader = new FileReader();
+ reader.readAsArrayBuffer(fileChooser.files[0]);
+ reader.onloadend = t.step_func_done(event => {
+ assert_equals(event.target.readyState, FileReader.DONE);
+ assert_equals(reader.error.name, "SecurityError");
+ });
+ }, 'FileReader.error should be SECURITY_ERROR if the file is a system sensitive file');
+ done();
+ });
+
+</script>
diff --git a/testing/web-platform/tests/FileAPI/FileReader/workers.html b/testing/web-platform/tests/FileAPI/FileReader/workers.html
new file mode 100644
index 0000000000..8e114eeaf8
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/FileReader/workers.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+async_test(t => {
+ function workerCode() {
+ close();
+ var blob = new Blob([123]);
+ var fr = new FileReader();
+ fr.readAsText(blob);
+ fr.abort()
+ fr.readAsArrayBuffer(blob);
+ postMessage(true);
+ }
+
+ var workerBlob = new Blob([workerCode.toString() + ";workerCode();"], {type:"application/javascript"});
+
+ var w = new Worker(URL.createObjectURL(workerBlob));
+ w.onmessage = function(e) {
+ assert_true(e.data, "FileReader created during worker shutdown.");
+ t.done();
+ }
+}, 'FileReader created after a worker self.close()');
+
+</script>
diff --git a/testing/web-platform/tests/FileAPI/FileReaderSync.worker.js b/testing/web-platform/tests/FileAPI/FileReaderSync.worker.js
new file mode 100644
index 0000000000..3d7a0222f3
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/FileReaderSync.worker.js
@@ -0,0 +1,56 @@
+importScripts("/resources/testharness.js");
+
+var blob, empty_blob, readerSync;
+setup(() => {
+ readerSync = new FileReaderSync();
+ blob = new Blob(["test"]);
+ empty_blob = new Blob();
+});
+
+test(() => {
+ assert_true(readerSync instanceof FileReaderSync);
+}, "Interface");
+
+test(() => {
+ var text = readerSync.readAsText(blob);
+ assert_equals(text, "test");
+}, "readAsText");
+
+test(() => {
+ var text = readerSync.readAsText(empty_blob);
+ assert_equals(text, "");
+}, "readAsText with empty blob");
+
+test(() => {
+ var data = readerSync.readAsDataURL(blob);
+ assert_equals(data.indexOf("data:"), 0);
+}, "readAsDataURL");
+
+test(() => {
+ var data = readerSync.readAsDataURL(empty_blob);
+ assert_equals(data.indexOf("data:"), 0);
+}, "readAsDataURL with empty blob");
+
+test(() => {
+ var data = readerSync.readAsBinaryString(blob);
+ assert_equals(data, "test");
+}, "readAsBinaryString");
+
+test(() => {
+ var data = readerSync.readAsBinaryString(empty_blob);
+ assert_equals(data, "");
+}, "readAsBinaryString with empty blob");
+
+test(() => {
+ var data = readerSync.readAsArrayBuffer(blob);
+ assert_true(data instanceof ArrayBuffer);
+ assert_equals(data.byteLength, "test".length);
+}, "readAsArrayBuffer");
+
+test(() => {
+ var data = readerSync.readAsArrayBuffer(empty_blob);
+ assert_true(data instanceof ArrayBuffer);
+ assert_equals(data.byteLength, 0);
+}, "readAsArrayBuffer with empty blob");
+
+done();
diff --git a/testing/web-platform/tests/FileAPI/META.yml b/testing/web-platform/tests/FileAPI/META.yml
new file mode 100644
index 0000000000..506a59fec1
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/META.yml
@@ -0,0 +1,6 @@
+spec: https://w3c.github.io/FileAPI/
+suggested_reviewers:
+ - inexorabletash
+ - zqzhang
+ - jdm
+ - mkruisselbrink
diff --git a/testing/web-platform/tests/FileAPI/blob/Blob-array-buffer.any.js b/testing/web-platform/tests/FileAPI/blob/Blob-array-buffer.any.js
new file mode 100644
index 0000000000..2310646e5f
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/blob/Blob-array-buffer.any.js
@@ -0,0 +1,45 @@
+// META: title=Blob Array Buffer
+// META: script=../support/Blob.js
+'use strict';
+
+promise_test(async () => {
+ const input_arr = new TextEncoder().encode("PASS");
+ const blob = new Blob([input_arr]);
+ const array_buffer = await blob.arrayBuffer();
+ assert_true(array_buffer instanceof ArrayBuffer);
+ assert_equals_typed_array(new Uint8Array(array_buffer), input_arr);
+}, "Blob.arrayBuffer()")
+
+promise_test(async () => {
+ const input_arr = new TextEncoder().encode("");
+ const blob = new Blob([input_arr]);
+ const array_buffer = await blob.arrayBuffer();
+ assert_true(array_buffer instanceof ArrayBuffer);
+ assert_equals_typed_array(new Uint8Array(array_buffer), input_arr);
+}, "Blob.arrayBuffer() empty Blob data")
+
+promise_test(async () => {
+ const input_arr = new TextEncoder().encode("\u08B8\u000a");
+ const blob = new Blob([input_arr]);
+ const array_buffer = await blob.arrayBuffer();
+ assert_equals_typed_array(new Uint8Array(array_buffer), input_arr);
+}, "Blob.arrayBuffer() non-ascii input")
+
+promise_test(async () => {
+ const input_arr = [8, 241, 48, 123, 151];
+ const typed_arr = new Uint8Array(input_arr);
+ const blob = new Blob([typed_arr]);
+ const array_buffer = await blob.arrayBuffer();
+ assert_equals_typed_array(new Uint8Array(array_buffer), typed_arr);
+}, "Blob.arrayBuffer() non-unicode input")
+
+promise_test(async () => {
+ const input_arr = new TextEncoder().encode("PASS");
+ const blob = new Blob([input_arr]);
+ const array_buffer_results = await Promise.all([blob.arrayBuffer(),
+ blob.arrayBuffer(), blob.arrayBuffer()]);
+ for (let array_buffer of array_buffer_results) {
+ assert_true(array_buffer instanceof ArrayBuffer);
+ assert_equals_typed_array(new Uint8Array(array_buffer), input_arr);
+ }
+}, "Blob.arrayBuffer() concurrent reads")
diff --git a/testing/web-platform/tests/FileAPI/blob/Blob-constructor-dom.window.js b/testing/web-platform/tests/FileAPI/blob/Blob-constructor-dom.window.js
new file mode 100644
index 0000000000..4fd4a43ec4
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/blob/Blob-constructor-dom.window.js
@@ -0,0 +1,53 @@
+// META: title=Blob constructor
+// META: script=../support/Blob.js
+'use strict';
+
+var test_error = {
+ name: "test",
+ message: "test error",
+};
+
+test(function() {
+ var args = [
+ document.createElement("div"),
+ window,
+ ];
+ args.forEach(function(arg) {
+ assert_throws_js(TypeError, function() {
+ new Blob(arg);
+ }, "Should throw for argument " + format_value(arg) + ".");
+ });
+}, "Passing platform objects for blobParts should throw a TypeError.");
+
+test(function() {
+ var element = document.createElement("div");
+ element.appendChild(document.createElement("div"));
+ element.appendChild(document.createElement("p"));
+ var list = element.children;
+ Object.defineProperty(list, "length", {
+ get: function() { throw test_error; }
+ });
+ assert_throws_exactly(test_error, function() {
+ new Blob(list);
+ });
+}, "A platform object that supports indexed properties should be treated as a sequence for the blobParts argument (overwritten 'length'.)");
+
+test_blob(function() {
+ var select = document.createElement("select");
+ select.appendChild(document.createElement("option"));
+ return new Blob(select);
+}, {
+ expected: "[object HTMLOptionElement]",
+ type: "",
+ desc: "Passing an platform object that supports indexed properties as the blobParts array should work (select)."
+});
+
+test_blob(function() {
+ var elm = document.createElement("div");
+ elm.setAttribute("foo", "bar");
+ return new Blob(elm.attributes);
+}, {
+ expected: "[object Attr]",
+ type: "",
+ desc: "Passing an platform object that supports indexed properties as the blobParts array should work (attributes)."
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/FileAPI/blob/Blob-constructor-endings.html b/testing/web-platform/tests/FileAPI/blob/Blob-constructor-endings.html
new file mode 100644
index 0000000000..04edd2a303
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/blob/Blob-constructor-endings.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Blob constructor: endings option</title>
+<link rel=help href="https://w3c.github.io/FileAPI/#constructorBlob">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+// Windows platforms use CRLF as the native line ending. All others use LF.
+const crlf = navigator.platform.startsWith('Win');
+const native_ending = crlf ? '\r\n' : '\n';
+
+function readBlobAsPromise(blob) {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsText(blob);
+ reader.onload = e => resolve(reader.result);
+ reader.onerror = e => reject(reader.error);
+ });
+}
+
+[
+ 'transparent',
+ 'native'
+].forEach(value => test(t => {
+ assert_class_string(new Blob([], {endings: value}), 'Blob',
+ `Constructor should allow "${value}" endings`);
+}, `Valid "endings" value: ${JSON.stringify(value)}`));
+
+[
+ null,
+ '',
+ 'invalidEnumValue',
+ 'Transparent',
+ 'NATIVE',
+ 0,
+ {}
+].forEach(value => test(t => {
+ assert_throws_js(TypeError, () => new Blob([], {endings: value}),
+ 'Blob constructor should throw');
+}, `Invalid "endings" value: ${JSON.stringify(value)}`));
+
+test(t => {
+ const test_error = {name: 'test'};
+ assert_throws_exactly(
+ test_error,
+ () => new Blob([], { get endings() { throw test_error; }}),
+ 'Blob constructor should propagate exceptions from "endings" property');
+}, 'Exception propagation from options');
+
+test(t => {
+ let got = false;
+ new Blob([], { get endings() { got = true; } });
+ assert_true(got, 'The "endings" property was accessed during construction.');
+}, 'The "endings" options property is used');
+
+[
+ {name: 'LF', input: '\n', native: native_ending},
+ {name: 'CR', input: '\r', native: native_ending},
+
+ {name: 'CRLF', input: '\r\n', native: native_ending},
+ {name: 'CRCR', input: '\r\r', native: native_ending.repeat(2)},
+ {name: 'LFCR', input: '\n\r', native: native_ending.repeat(2)},
+ {name: 'LFLF', input: '\n\n', native: native_ending.repeat(2)},
+
+ {name: 'CRCRLF', input: '\r\r\n', native: native_ending.repeat(2)},
+ {name: 'CRLFLF', input: '\r\n\n', native: native_ending.repeat(2)},
+ {name: 'CRLFCR', input: '\r\n\r\n', native: native_ending.repeat(2)},
+
+ {name: 'CRLFCRLF', input: '\r\n\r\n', native: native_ending.repeat(2)},
+ {name: 'LFCRLFCR', input: '\n\r\n\r', native: native_ending.repeat(3)},
+
+].forEach(testCase => {
+ promise_test(async t => {
+ const blob = new Blob([testCase.input]);
+ assert_equals(
+ await readBlobAsPromise(blob), testCase.input,
+ 'Newlines should not change with endings unspecified');
+ }, `Input ${testCase.name} with endings unspecified`);
+
+ promise_test(async t => {
+ const blob = new Blob([testCase.input], {endings: 'transparent'});
+ assert_equals(
+ await readBlobAsPromise(blob), testCase.input,
+ 'Newlines should not change with endings "transparent"');
+ }, `Input ${testCase.name} with endings 'transparent'`);
+
+ promise_test(async t => {
+ const blob = new Blob([testCase.input], {endings: 'native'});
+ assert_equals(
+ await readBlobAsPromise(blob), testCase.native,
+ 'Newlines should match the platform with endings "native"');
+ }, `Input ${testCase.name} with endings 'native'`);
+});
+
+promise_test(async t => {
+ const blob = new Blob(['\r', '\n'], {endings: 'native'});
+ const expected = native_ending.repeat(2);
+ assert_equals(
+ await readBlobAsPromise(blob), expected,
+ 'CR/LF in adjacent strings should be converted to two platform newlines');
+}, `CR/LF in adjacent input strings`);
+
+</script>
diff --git a/testing/web-platform/tests/FileAPI/blob/Blob-constructor.any.js b/testing/web-platform/tests/FileAPI/blob/Blob-constructor.any.js
new file mode 100644
index 0000000000..d16f760cae
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/blob/Blob-constructor.any.js
@@ -0,0 +1,468 @@
+// META: title=Blob constructor
+// META: script=../support/Blob.js
+'use strict';
+
+test(function() {
+ assert_true("Blob" in globalThis, "globalThis should have a Blob property.");
+ assert_equals(Blob.length, 0, "Blob.length should be 0.");
+ assert_true(Blob instanceof Function, "Blob should be a function.");
+}, "Blob interface object");
+
+// Step 1.
+test(function() {
+ var blob = new Blob();
+ assert_true(blob instanceof Blob);
+ assert_equals(String(blob), '[object Blob]');
+ assert_equals(blob.size, 0);
+ assert_equals(blob.type, "");
+}, "Blob constructor with no arguments");
+test(function() {
+ assert_throws_js(TypeError, function() { var blob = Blob(); });
+}, "Blob constructor with no arguments, without 'new'");
+test(function() {
+ var blob = new Blob;
+ assert_true(blob instanceof Blob);
+ assert_equals(blob.size, 0);
+ assert_equals(blob.type, "");
+}, "Blob constructor without brackets");
+test(function() {
+ var blob = new Blob(undefined);
+ assert_true(blob instanceof Blob);
+ assert_equals(String(blob), '[object Blob]');
+ assert_equals(blob.size, 0);
+ assert_equals(blob.type, "");
+}, "Blob constructor with undefined as first argument");
+
+// blobParts argument (WebIDL).
+test(function() {
+ var args = [
+ null,
+ true,
+ false,
+ 0,
+ 1,
+ 1.5,
+ "FAIL",
+ new Date(),
+ new RegExp(),
+ {},
+ { 0: "FAIL", length: 1 },
+ ];
+ args.forEach(function(arg) {
+ assert_throws_js(TypeError, function() {
+ new Blob(arg);
+ }, "Should throw for argument " + format_value(arg) + ".");
+ });
+}, "Passing non-objects, Dates and RegExps for blobParts should throw a TypeError.");
+
+test_blob(function() {
+ return new Blob({
+ [Symbol.iterator]: Array.prototype[Symbol.iterator],
+ });
+}, {
+ expected: "",
+ type: "",
+ desc: "A plain object with @@iterator should be treated as a sequence for the blobParts argument."
+});
+test(t => {
+ const blob = new Blob({
+ [Symbol.iterator]() {
+ var i = 0;
+ return {next: () => [
+ {done:false, value:'ab'},
+ {done:false, value:'cde'},
+ {done:true}
+ ][i++]
+ };
+ }
+ });
+ assert_equals(blob.size, 5, 'Custom @@iterator should be treated as a sequence');
+}, "A plain object with custom @@iterator should be treated as a sequence for the blobParts argument.");
+test_blob(function() {
+ return new Blob({
+ [Symbol.iterator]: Array.prototype[Symbol.iterator],
+ 0: "PASS",
+ length: 1
+ });
+}, {
+ expected: "PASS",
+ type: "",
+ desc: "A plain object with @@iterator and a length property should be treated as a sequence for the blobParts argument."
+});
+test_blob(function() {
+ return new Blob(new String("xyz"));
+}, {
+ expected: "xyz",
+ type: "",
+ desc: "A String object should be treated as a sequence for the blobParts argument."
+});
+test_blob(function() {
+ return new Blob(new Uint8Array([1, 2, 3]));
+}, {
+ expected: "123",
+ type: "",
+ desc: "A Uint8Array object should be treated as a sequence for the blobParts argument."
+});
+
+var test_error = {
+ name: "test",
+ message: "test error",
+};
+
+test(function() {
+ var obj = {
+ [Symbol.iterator]: Array.prototype[Symbol.iterator],
+ get length() { throw test_error; }
+ };
+ assert_throws_exactly(test_error, function() {
+ new Blob(obj);
+ });
+}, "The length getter should be invoked and any exceptions should be propagated.");
+
+test(function() {
+ assert_throws_exactly(test_error, function() {
+ var obj = {
+ [Symbol.iterator]: Array.prototype[Symbol.iterator],
+ length: {
+ valueOf: null,
+ toString: function() { throw test_error; }
+ }
+ };
+ new Blob(obj);
+ });
+ assert_throws_exactly(test_error, function() {
+ var obj = {
+ [Symbol.iterator]: Array.prototype[Symbol.iterator],
+ length: { valueOf: function() { throw test_error; } }
+ };
+ new Blob(obj);
+ });
+}, "ToUint32 should be applied to the length and any exceptions should be propagated.");
+
+test(function() {
+ var received = [];
+ var obj = {
+ get [Symbol.iterator]() {
+ received.push("Symbol.iterator");
+ return Array.prototype[Symbol.iterator];
+ },
+ get length() {
+ received.push("length getter");
+ return {
+ valueOf: function() {
+ received.push("length valueOf");
+ return 3;
+ }
+ };
+ },
+ get 0() {
+ received.push("0 getter");
+ return {
+ toString: function() {
+ received.push("0 toString");
+ return "a";
+ }
+ };
+ },
+ get 1() {
+ received.push("1 getter");
+ throw test_error;
+ },
+ get 2() {
+ received.push("2 getter");
+ assert_unreached("Should not call the getter for 2 if the getter for 1 threw.");
+ }
+ };
+ assert_throws_exactly(test_error, function() {
+ new Blob(obj);
+ });
+ assert_array_equals(received, [
+ "Symbol.iterator",
+ "length getter",
+ "length valueOf",
+ "0 getter",
+ "0 toString",
+ "length getter",
+ "length valueOf",
+ "1 getter",
+ ]);
+}, "Getters and value conversions should happen in order until an exception is thrown.");
+
+// XXX should add tests edge cases of ToLength(length)
+
+test(function() {
+ assert_throws_exactly(test_error, function() {
+ new Blob([{ toString: function() { throw test_error; } }]);
+ }, "Throwing toString");
+ assert_throws_exactly(test_error, function() {
+ new Blob([{ toString: undefined, valueOf: function() { throw test_error; } }]);
+ }, "Throwing valueOf");
+ assert_throws_exactly(test_error, function() {
+ new Blob([{
+ toString: function() { throw test_error; },
+ valueOf: function() { assert_unreached("Should not call valueOf if toString is present."); }
+ }]);
+ }, "Throwing toString and valueOf");
+ assert_throws_js(TypeError, function() {
+ new Blob([{toString: null, valueOf: null}]);
+ }, "Null toString and valueOf");
+}, "ToString should be called on elements of the blobParts array and any exceptions should be propagated.");
+
+test_blob(function() {
+ var arr = [
+ { toString: function() { arr.pop(); return "PASS"; } },
+ { toString: function() { assert_unreached("Should have removed the second element of the array rather than called toString() on it."); } }
+ ];
+ return new Blob(arr);
+}, {
+ expected: "PASS",
+ type: "",
+ desc: "Changes to the blobParts array should be reflected in the returned Blob (pop)."
+});
+
+test_blob(function() {
+ var arr = [
+ {
+ toString: function() {
+ if (arr.length === 3) {
+ return "A";
+ }
+ arr.unshift({
+ toString: function() {
+ assert_unreached("Should only access index 0 once.");
+ }
+ });
+ return "P";
+ }
+ },
+ {
+ toString: function() {
+ return "SS";
+ }
+ }
+ ];
+ return new Blob(arr);
+}, {
+ expected: "PASS",
+ type: "",
+ desc: "Changes to the blobParts array should be reflected in the returned Blob (unshift)."
+});
+
+test_blob(function() {
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17652
+ return new Blob([
+ null,
+ undefined,
+ true,
+ false,
+ 0,
+ 1,
+ new String("stringobject"),
+ [],
+ ['x', 'y'],
+ {},
+ { 0: "FAIL", length: 1 },
+ { toString: function() { return "stringA"; } },
+ { toString: undefined, valueOf: function() { return "stringB"; } },
+ { valueOf: function() { assert_unreached("Should not call valueOf if toString is present on the prototype."); } }
+ ]);
+}, {
+ expected: "nullundefinedtruefalse01stringobjectx,y[object Object][object Object]stringAstringB[object Object]",
+ type: "",
+ desc: "ToString should be called on elements of the blobParts array."
+});
+
+test_blob(function() {
+ return new Blob([
+ new ArrayBuffer(8)
+ ]);
+}, {
+ expected: "\0\0\0\0\0\0\0\0",
+ type: "",
+ desc: "ArrayBuffer elements of the blobParts array should be supported."
+});
+
+test_blob(function() {
+ return new Blob([
+ new Uint8Array([0x50, 0x41, 0x53, 0x53]),
+ new Int8Array([0x50, 0x41, 0x53, 0x53]),
+ new Uint16Array([0x4150, 0x5353]),
+ new Int16Array([0x4150, 0x5353]),
+ new Uint32Array([0x53534150]),
+ new Int32Array([0x53534150]),
+ new Float32Array([0xD341500000])
+ ]);
+}, {
+ expected: "PASSPASSPASSPASSPASSPASSPASS",
+ type: "",
+ desc: "Passing typed arrays as elements of the blobParts array should work."
+});
+test_blob(function() {
+ return new Blob([
+ // 0x535 3415053534150
+ // 0x535 = 0b010100110101 -> Sign = +, Exponent = 1333 - 1023 = 310
+ // 0x13415053534150 * 2**(-52)
+ // ==> 0x13415053534150 * 2**258 = 2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680
+ new Float64Array([2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680])
+ ]);
+}, {
+ expected: "PASSPASS",
+ type: "",
+ desc: "Passing a Float64Array as element of the blobParts array should work."
+});
+
+test_blob(function() {
+ return new Blob([
+ new BigInt64Array([BigInt("0x5353415053534150")]),
+ new BigUint64Array([BigInt("0x5353415053534150")])
+ ]);
+}, {
+ expected: "PASSPASSPASSPASS",
+ type: "",
+ desc: "Passing BigInt typed arrays as elements of the blobParts array should work."
+});
+
+var t_ports = async_test("Passing a FrozenArray as the blobParts array should work (FrozenArray<MessagePort>).");
+t_ports.step(function() {
+ var channel = new MessageChannel();
+ channel.port2.onmessage = this.step_func(function(e) {
+ var b_ports = new Blob(e.ports);
+ assert_equals(b_ports.size, "[object MessagePort]".length);
+ this.done();
+ });
+ var channel2 = new MessageChannel();
+ channel.port1.postMessage('', [channel2.port1]);
+});
+
+test_blob(function() {
+ var blob = new Blob(['foo']);
+ return new Blob([blob, blob]);
+}, {
+ expected: "foofoo",
+ type: "",
+ desc: "Array with two blobs"
+});
+
+test_blob_binary(function() {
+ var view = new Uint8Array([0, 255, 0]);
+ return new Blob([view.buffer, view.buffer]);
+}, {
+ expected: [0, 255, 0, 0, 255, 0],
+ type: "",
+ desc: "Array with two buffers"
+});
+
+test_blob_binary(function() {
+ var view = new Uint8Array([0, 255, 0, 4]);
+ var blob = new Blob([view, view]);
+ assert_equals(blob.size, 8);
+ var view1 = new Uint16Array(view.buffer, 2);
+ return new Blob([view1, view.buffer, view1]);
+}, {
+ expected: [0, 4, 0, 255, 0, 4, 0, 4],
+ type: "",
+ desc: "Array with two bufferviews"
+});
+
+test_blob(function() {
+ var view = new Uint8Array([0]);
+ var blob = new Blob(["fo"]);
+ return new Blob([view.buffer, blob, "foo"]);
+}, {
+ expected: "\0fofoo",
+ type: "",
+ desc: "Array with mixed types"
+});
+
+test(function() {
+ const accessed = [];
+ const stringified = [];
+
+ new Blob([], {
+ get type() { accessed.push('type'); },
+ get endings() { accessed.push('endings'); }
+ });
+ new Blob([], {
+ type: { toString: () => { stringified.push('type'); return ''; } },
+ endings: { toString: () => { stringified.push('endings'); return 'transparent'; } }
+ });
+ assert_array_equals(accessed, ['endings', 'type']);
+ assert_array_equals(stringified, ['endings', 'type']);
+}, "options properties should be accessed in lexicographic order.");
+
+test(function() {
+ assert_throws_exactly(test_error, function() {
+ new Blob(
+ [{ toString: function() { throw test_error } }],
+ {
+ get type() { assert_unreached("type getter should not be called."); }
+ }
+ );
+ });
+}, "Arguments should be evaluated from left to right.");
+
+[
+ null,
+ undefined,
+ {},
+ { unrecognized: true },
+ /regex/,
+ function() {}
+].forEach(function(arg, idx) {
+ test_blob(function() {
+ return new Blob([], arg);
+ }, {
+ expected: "",
+ type: "",
+ desc: "Passing " + format_value(arg) + " (index " + idx + ") for options should use the defaults."
+ });
+ test_blob(function() {
+ return new Blob(["\na\r\nb\n\rc\r"], arg);
+ }, {
+ expected: "\na\r\nb\n\rc\r",
+ type: "",
+ desc: "Passing " + format_value(arg) + " (index " + idx + ") for options should use the defaults (with newlines)."
+ });
+});
+
+[
+ 123,
+ 123.4,
+ true,
+ 'abc'
+].forEach(arg => {
+ test(t => {
+ assert_throws_js(TypeError, () => new Blob([], arg),
+ 'Blob constructor should throw with invalid property bag');
+ }, `Passing ${JSON.stringify(arg)} for options should throw`);
+});
+
+var type_tests = [
+ // blobParts, type, expected type
+ [[], '', ''],
+ [[], 'a', 'a'],
+ [[], 'A', 'a'],
+ [[], 'text/html', 'text/html'],
+ [[], 'TEXT/HTML', 'text/html'],
+ [[], 'text/plain;charset=utf-8', 'text/plain;charset=utf-8'],
+ [[], '\u00E5', ''],
+ [[], '\uD801\uDC7E', ''], // U+1047E
+ [[], ' image/gif ', ' image/gif '],
+ [[], '\timage/gif\t', ''],
+ [[], 'image/gif;\u007f', ''],
+ [[], '\u0130mage/gif', ''], // uppercase i with dot
+ [[], '\u0131mage/gif', ''], // lowercase dotless i
+ [[], 'image/gif\u0000', ''],
+ // check that type isn't changed based on sniffing
+ [[0x3C, 0x48, 0x54, 0x4D, 0x4C, 0x3E], 'unknown/unknown', 'unknown/unknown'], // "<HTML>"
+ [[0x00, 0xFF], 'text/plain', 'text/plain'],
+ [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61], 'image/png', 'image/png'], // "GIF89a"
+];
+
+type_tests.forEach(function(t) {
+ test(function() {
+ var arr = new Uint8Array([t[0]]).buffer;
+ var b = new Blob([arr], {type:t[1]});
+ assert_equals(b.type, t[2]);
+ }, "Blob with type " + format_value(t[1]));
+});
diff --git a/testing/web-platform/tests/FileAPI/blob/Blob-in-worker.worker.js b/testing/web-platform/tests/FileAPI/blob/Blob-in-worker.worker.js
new file mode 100644
index 0000000000..a0ca84551d
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/blob/Blob-in-worker.worker.js
@@ -0,0 +1,9 @@
+importScripts("/resources/testharness.js");
+
+promise_test(async () => {
+ const data = "TEST";
+ const blob = new Blob([data], {type: "text/plain"});
+ assert_equals(await blob.text(), data);
+}, 'Create Blob in Worker');
+
+done();
diff --git a/testing/web-platform/tests/FileAPI/blob/Blob-slice-overflow.any.js b/testing/web-platform/tests/FileAPI/blob/Blob-slice-overflow.any.js
new file mode 100644
index 0000000000..388fd9282c
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/blob/Blob-slice-overflow.any.js
@@ -0,0 +1,32 @@
+// META: title=Blob slice overflow
+'use strict';
+
+var text = '';
+
+for (var i = 0; i < 2000; ++i) {
+ text += 'A';
+}
+
+test(function() {
+ var blob = new Blob([text]);
+ var sliceBlob = blob.slice(-1, blob.size);
+ assert_equals(sliceBlob.size, 1, "Blob slice size");
+}, "slice start is negative, relativeStart will be max((size + start), 0)");
+
+test(function() {
+ var blob = new Blob([text]);
+ var sliceBlob = blob.slice(blob.size + 1, blob.size);
+ assert_equals(sliceBlob.size, 0, "Blob slice size");
+}, "slice start is greater than blob size, relativeStart will be min(start, size)");
+
+test(function() {
+ var blob = new Blob([text]);
+ var sliceBlob = blob.slice(blob.size - 2, -1);
+ assert_equals(sliceBlob.size, 1, "Blob slice size");
+}, "slice end is negative, relativeEnd will be max((size + end), 0)");
+
+test(function() {
+ var blob = new Blob([text]);
+ var sliceBlob = blob.slice(blob.size - 2, blob.size + 999);
+ assert_equals(sliceBlob.size, 2, "Blob slice size");
+}, "slice end is greater than blob size, relativeEnd will be min(end, size)");
diff --git a/testing/web-platform/tests/FileAPI/blob/Blob-slice.any.js b/testing/web-platform/tests/FileAPI/blob/Blob-slice.any.js
new file mode 100644
index 0000000000..1f85d44d26
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/blob/Blob-slice.any.js
@@ -0,0 +1,231 @@
+// META: title=Blob slice
+// META: script=../support/Blob.js
+'use strict';
+
+test_blob(function() {
+ var blobTemp = new Blob(["PASS"]);
+ return blobTemp.slice();
+}, {
+ expected: "PASS",
+ type: "",
+ desc: "no-argument Blob slice"
+});
+
+test(function() {
+ var blob1, blob2;
+
+ test_blob(function() {
+ return blob1 = new Blob(["squiggle"]);
+ }, {
+ expected: "squiggle",
+ type: "",
+ desc: "blob1."
+ });
+
+ test_blob(function() {
+ return blob2 = new Blob(["steak"], {type: "content/type"});
+ }, {
+ expected: "steak",
+ type: "content/type",
+ desc: "blob2."
+ });
+
+ test_blob(function() {
+ return new Blob().slice(0,0,null);
+ }, {
+ expected: "",
+ type: "null",
+ desc: "null type Blob slice"
+ });
+
+ test_blob(function() {
+ return new Blob().slice(0,0,undefined);
+ }, {
+ expected: "",
+ type: "",
+ desc: "undefined type Blob slice"
+ });
+
+ test_blob(function() {
+ return new Blob().slice(0,0);
+ }, {
+ expected: "",
+ type: "",
+ desc: "no type Blob slice"
+ });
+
+ var arrayBuffer = new ArrayBuffer(16);
+ var int8View = new Int8Array(arrayBuffer);
+ for (var i = 0; i < 16; i++) {
+ int8View[i] = i + 65;
+ }
+
+ var testData = [
+ [
+ ["PASSSTRING"],
+ [{start: -6, contents: "STRING"},
+ {start: -12, contents: "PASSSTRING"},
+ {start: 4, contents: "STRING"},
+ {start: 12, contents: ""},
+ {start: 0, end: -6, contents: "PASS"},
+ {start: 0, end: -12, contents: ""},
+ {start: 0, end: 4, contents: "PASS"},
+ {start: 0, end: 12, contents: "PASSSTRING"},
+ {start: 7, end: 4, contents: ""}]
+ ],
+
+ // Test 3 strings
+ [
+ ["foo", "bar", "baz"],
+ [{start: 0, end: 9, contents: "foobarbaz"},
+ {start: 0, end: 3, contents: "foo"},
+ {start: 3, end: 9, contents: "barbaz"},
+ {start: 6, end: 9, contents: "baz"},
+ {start: 6, end: 12, contents: "baz"},
+ {start: 0, end: 9, contents: "foobarbaz"},
+ {start: 0, end: 11, contents: "foobarbaz"},
+ {start: 10, end: 15, contents: ""}]
+ ],
+
+ // Test string, Blob, string
+ [
+ ["foo", blob1, "baz"],
+ [{start: 0, end: 3, contents: "foo"},
+ {start: 3, end: 11, contents: "squiggle"},
+ {start: 2, end: 4, contents: "os"},
+ {start: 10, end: 12, contents: "eb"}]
+ ],
+
+ // Test blob, string, blob
+ [
+ [blob1, "foo", blob1],
+ [{start: 0, end: 8, contents: "squiggle"},
+ {start: 7, end: 9, contents: "ef"},
+ {start: 10, end: 12, contents: "os"},
+ {start: 1, end: 4, contents: "qui"},
+ {start: 12, end: 15, contents: "qui"},
+ {start: 40, end: 60, contents: ""}]
+ ],
+
+ // Test blobs all the way down
+ [
+ [blob2, blob1, blob2],
+ [{start: 0, end: 5, contents: "steak"},
+ {start: 5, end: 13, contents: "squiggle"},
+ {start: 13, end: 18, contents: "steak"},
+ {start: 1, end: 3, contents: "te"},
+ {start: 6, end: 10, contents: "quig"}]
+ ],
+
+ // Test an ArrayBufferView
+ [
+ [int8View, blob1, "foo"],
+ [{start: 0, end: 8, contents: "ABCDEFGH"},
+ {start: 8, end: 18, contents: "IJKLMNOPsq"},
+ {start: 17, end: 20, contents: "qui"},
+ {start: 4, end: 12, contents: "EFGHIJKL"}]
+ ],
+
+ // Test a partial ArrayBufferView
+ [
+ [new Uint8Array(arrayBuffer, 3, 5), blob1, "foo"],
+ [{start: 0, end: 8, contents: "DEFGHsqu"},
+ {start: 8, end: 18, contents: "igglefoo"},
+ {start: 4, end: 12, contents: "Hsquiggl"}]
+ ],
+
+ // Test type coercion of a number
+ [
+ [3, int8View, "foo"],
+ [{start: 0, end: 8, contents: "3ABCDEFG"},
+ {start: 8, end: 18, contents: "HIJKLMNOPf"},
+ {start: 17, end: 21, contents: "foo"},
+ {start: 4, end: 12, contents: "DEFGHIJK"}]
+ ],
+
+ [
+ [(new Uint8Array([0, 255, 0])).buffer,
+ new Blob(['abcd']),
+ 'efgh',
+ 'ijklmnopqrstuvwxyz'],
+ [{start: 1, end: 4, contents: "\uFFFD\u0000a"},
+ {start: 4, end: 8, contents: "bcde"},
+ {start: 8, end: 12, contents: "fghi"},
+ {start: 1, end: 12, contents: "\uFFFD\u0000abcdefghi"}]
+ ]
+ ];
+
+ testData.forEach(function(data, i) {
+ var blobs = data[0];
+ var tests = data[1];
+ tests.forEach(function(expectations, j) {
+ test(function() {
+ var blob = new Blob(blobs);
+ assert_true(blob instanceof Blob);
+ assert_false(blob instanceof File);
+
+ test_blob(function() {
+ return expectations.end === undefined
+ ? blob.slice(expectations.start)
+ : blob.slice(expectations.start, expectations.end);
+ }, {
+ expected: expectations.contents,
+ type: "",
+ desc: "Slicing test: slice (" + i + "," + j + ")."
+ });
+ }, "Slicing test (" + i + "," + j + ").");
+ });
+ });
+}, "Slices");
+
+var invalidTypes = [
+ "\xFF",
+ "te\x09xt/plain",
+ "te\x00xt/plain",
+ "te\x1Fxt/plain",
+ "te\x7Fxt/plain"
+];
+invalidTypes.forEach(function(type) {
+ test_blob(function() {
+ var blob = new Blob(["PASS"]);
+ return blob.slice(0, 4, type);
+ }, {
+ expected: "PASS",
+ type: "",
+ desc: "Invalid contentType (" + format_value(type) + ")"
+ });
+});
+
+var validTypes = [
+ "te(xt/plain",
+ "te)xt/plain",
+ "te<xt/plain",
+ "te>xt/plain",
+ "te@xt/plain",
+ "te,xt/plain",
+ "te;xt/plain",
+ "te:xt/plain",
+ "te\\xt/plain",
+ "te\"xt/plain",
+ "te/xt/plain",
+ "te[xt/plain",
+ "te]xt/plain",
+ "te?xt/plain",
+ "te=xt/plain",
+ "te{xt/plain",
+ "te}xt/plain",
+ "te\x20xt/plain",
+ "TEXT/PLAIN",
+ "text/plain;charset = UTF-8",
+ "text/plain;charset=UTF-8"
+];
+validTypes.forEach(function(type) {
+ test_blob(function() {
+ var blob = new Blob(["PASS"]);
+ return blob.slice(0, 4, type);
+ }, {
+ expected: "PASS",
+ type: type.toLowerCase(),
+ desc: "Valid contentType (" + format_value(type) + ")"
+ });
+});
diff --git a/testing/web-platform/tests/FileAPI/blob/Blob-stream-byob-crash.html b/testing/web-platform/tests/FileAPI/blob/Blob-stream-byob-crash.html
new file mode 100644
index 0000000000..5992ed1396
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/blob/Blob-stream-byob-crash.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script type="module">
+ let a = new Blob(['', '', undefined], { })
+ let b = a.stream()
+ let c = new ReadableStreamBYOBReader(b)
+ let d = new Int16Array(8)
+ await c.read(d)
+ c.releaseLock()
+ await a.text()
+ await b.cancel()
+</script>
diff --git a/testing/web-platform/tests/FileAPI/blob/Blob-stream.any.js b/testing/web-platform/tests/FileAPI/blob/Blob-stream.any.js
new file mode 100644
index 0000000000..8f1d5c555a
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/blob/Blob-stream.any.js
@@ -0,0 +1,73 @@
+// META: title=Blob Stream
+// META: script=../support/Blob.js
+// META: script=/common/gc.js
+'use strict';
+
+// Helper function that triggers garbage collection while reading a chunk
+// if perform_gc is true.
+async function read_and_gc(reader, perform_gc) {
+ const read_promise = reader.read();
+ if (perform_gc) {
+ await garbageCollect();
+ }
+ return read_promise;
+}
+
+// Takes in a ReadableStream and reads from it until it is done, returning
+// an array that contains the results of each read operation. If perform_gc
+// is true, garbage collection is triggered while reading every chunk.
+async function read_all_chunks(stream, perform_gc = false) {
+ assert_true(stream instanceof ReadableStream);
+ assert_true('getReader' in stream);
+ const reader = stream.getReader();
+
+ assert_true('read' in reader);
+ let read_value = await read_and_gc(reader, perform_gc);
+
+ let out = [];
+ let i = 0;
+ while (!read_value.done) {
+ for (let val of read_value.value) {
+ out[i++] = val;
+ }
+ read_value = await read_and_gc(reader, perform_gc);
+ }
+ return out;
+}
+
+promise_test(async () => {
+ const blob = new Blob(["PASS"]);
+ const stream = blob.stream();
+ const chunks = await read_all_chunks(stream);
+ for (let [index, value] of chunks.entries()) {
+ assert_equals(value, "PASS".charCodeAt(index));
+ }
+}, "Blob.stream()")
+
+promise_test(async () => {
+ const blob = new Blob();
+ const stream = blob.stream();
+ const chunks = await read_all_chunks(stream);
+ assert_array_equals(chunks, []);
+}, "Blob.stream() empty Blob")
+
+promise_test(async () => {
+ const input_arr = [8, 241, 48, 123, 151];
+ const typed_arr = new Uint8Array(input_arr);
+ const blob = new Blob([typed_arr]);
+ const stream = blob.stream();
+ const chunks = await read_all_chunks(stream);
+ assert_array_equals(chunks, input_arr);
+}, "Blob.stream() non-unicode input")
+
+promise_test(async() => {
+ const input_arr = [8, 241, 48, 123, 151];
+ const typed_arr = new Uint8Array(input_arr);
+ let blob = new Blob([typed_arr]);
+ const stream = blob.stream();
+ blob = null;
+ await garbageCollect();
+ const chunks = await read_all_chunks(stream, /*perform_gc=*/true);
+ assert_array_equals(chunks, input_arr);
+}, "Blob.stream() garbage collection of blob shouldn't break stream" +
+ "consumption")
diff --git a/testing/web-platform/tests/FileAPI/blob/Blob-text.any.js b/testing/web-platform/tests/FileAPI/blob/Blob-text.any.js
new file mode 100644
index 0000000000..d04fa97cff
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/blob/Blob-text.any.js
@@ -0,0 +1,64 @@
+// META: title=Blob Text
+// META: script=../support/Blob.js
+'use strict';
+
+promise_test(async () => {
+ const blob = new Blob(["PASS"]);
+ const text = await blob.text();
+ assert_equals(text, "PASS");
+}, "Blob.text()")
+
+promise_test(async () => {
+ const blob = new Blob();
+ const text = await blob.text();
+ assert_equals(text, "");
+}, "Blob.text() empty blob data")
+
+promise_test(async () => {
+ const blob = new Blob(["P", "A", "SS"]);
+ const text = await blob.text();
+ assert_equals(text, "PASS");
+}, "Blob.text() multi-element array in constructor")
+
+promise_test(async () => {
+ const non_unicode = "\u0061\u030A";
+ const input_arr = new TextEncoder().encode(non_unicode);
+ const blob = new Blob([input_arr]);
+ const text = await blob.text();
+ assert_equals(text, non_unicode);
+}, "Blob.text() non-unicode")
+
+promise_test(async () => {
+ const blob = new Blob(["PASS"], { type: "text/plain;charset=utf-16le" });
+ const text = await blob.text();
+ assert_equals(text, "PASS");
+}, "Blob.text() different charset param in type option")
+
+promise_test(async () => {
+ const non_unicode = "\u0061\u030A";
+ const input_arr = new TextEncoder().encode(non_unicode);
+ const blob = new Blob([input_arr], { type: "text/plain;charset=utf-16le" });
+ const text = await blob.text();
+ assert_equals(text, non_unicode);
+}, "Blob.text() different charset param with non-ascii input")
+
+promise_test(async () => {
+ const input_arr = new Uint8Array([192, 193, 245, 246, 247, 248, 249, 250, 251,
+ 252, 253, 254, 255]);
+ const blob = new Blob([input_arr]);
+ const text = await blob.text();
+ assert_equals(text, "\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
+ "\ufffd\ufffd\ufffd\ufffd");
+}, "Blob.text() invalid utf-8 input")
+
+promise_test(async () => {
+ const input_arr = new Uint8Array([192, 193, 245, 246, 247, 248, 249, 250, 251,
+ 252, 253, 254, 255]);
+ const blob = new Blob([input_arr]);
+ const text_results = await Promise.all([blob.text(), blob.text(),
+ blob.text()]);
+ for (let text of text_results) {
+ assert_equals(text, "\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
+ "\ufffd\ufffd\ufffd\ufffd");
+ }
+}, "Blob.text() concurrent reads")
diff --git a/testing/web-platform/tests/FileAPI/file/File-constructor-endings.html b/testing/web-platform/tests/FileAPI/file/File-constructor-endings.html
new file mode 100644
index 0000000000..1282b6c5ac
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/File-constructor-endings.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>File constructor: endings option</title>
+<link rel=help href="https://w3c.github.io/FileAPI/#file-constructor">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+// Windows platforms use CRLF as the native line ending. All others use LF.
+const crlf = navigator.platform.startsWith('Win');
+const native_ending = crlf ? '\r\n' : '\n';
+
+function readBlobAsPromise(blob) {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsText(blob);
+ reader.onload = e => resolve(reader.result);
+ reader.onerror = e => reject(reader.error);
+ });
+}
+
+[
+ 'transparent',
+ 'native'
+].forEach(value => test(t => {
+ assert_class_string(new File([], "name", {endings: value}), 'File',
+ `Constructor should allow "${value}" endings`);
+}, `Valid "endings" value: ${JSON.stringify(value)}`));
+
+[
+ null,
+ '',
+ 'invalidEnumValue',
+ 'Transparent',
+ 'NATIVE',
+ 0,
+ {}
+].forEach(value => test(t => {
+ assert_throws_js(TypeError, () => new File([], "name", {endings: value}),
+ 'File constructor should throw');
+}, `Invalid "endings" value: ${JSON.stringify(value)}`));
+
+test(t => {
+ const test_error = {name: 'test'};
+ assert_throws_exactly(
+ test_error,
+ () => new File([], "name", { get endings() { throw test_error; }}),
+ 'File constructor should propagate exceptions from "endings" property');
+}, 'Exception propagation from options');
+
+test(t => {
+ let got = false;
+ new File([], "name", { get endings() { got = true; } });
+ assert_true(got, 'The "endings" property was accessed during construction.');
+}, 'The "endings" options property is used');
+
+[
+ {name: 'LF', input: '\n', native: native_ending},
+ {name: 'CR', input: '\r', native: native_ending},
+
+ {name: 'CRLF', input: '\r\n', native: native_ending},
+ {name: 'CRCR', input: '\r\r', native: native_ending.repeat(2)},
+ {name: 'LFCR', input: '\n\r', native: native_ending.repeat(2)},
+ {name: 'LFLF', input: '\n\n', native: native_ending.repeat(2)},
+
+ {name: 'CRCRLF', input: '\r\r\n', native: native_ending.repeat(2)},
+ {name: 'CRLFLF', input: '\r\n\n', native: native_ending.repeat(2)},
+ {name: 'CRLFCR', input: '\r\n\r\n', native: native_ending.repeat(2)},
+
+ {name: 'CRLFCRLF', input: '\r\n\r\n', native: native_ending.repeat(2)},
+ {name: 'LFCRLFCR', input: '\n\r\n\r', native: native_ending.repeat(3)},
+
+].forEach(testCase => {
+ promise_test(async t => {
+ const file = new File([testCase.input], "name");
+ assert_equals(
+ await readBlobAsPromise(file), testCase.input,
+ 'Newlines should not change with endings unspecified');
+ }, `Input ${testCase.name} with endings unspecified`);
+
+ promise_test(async t => {
+ const file = new File([testCase.input], "name", {endings: 'transparent'});
+ assert_equals(
+ await readBlobAsPromise(file), testCase.input,
+ 'Newlines should not change with endings "transparent"');
+ }, `Input ${testCase.name} with endings 'transparent'`);
+
+ promise_test(async t => {
+ const file = new File([testCase.input], "name", {endings: 'native'});
+ assert_equals(
+ await readBlobAsPromise(file), testCase.native,
+ 'Newlines should match the platform with endings "native"');
+ }, `Input ${testCase.name} with endings 'native'`);
+});
+
+promise_test(async t => {
+ const file = new File(['\r', '\n'], "name", {endings: 'native'});
+ const expected = native_ending.repeat(2);
+ assert_equals(
+ await readBlobAsPromise(file), expected,
+ 'CR/LF in adjacent strings should be converted to two platform newlines');
+}, `CR/LF in adjacent input strings`);
+
+</script>
diff --git a/testing/web-platform/tests/FileAPI/file/File-constructor.any.js b/testing/web-platform/tests/FileAPI/file/File-constructor.any.js
new file mode 100644
index 0000000000..0b0185c40b
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/File-constructor.any.js
@@ -0,0 +1,155 @@
+// META: title=File constructor
+
+const to_string_obj = { toString: () => 'a string' };
+const to_string_throws = { toString: () => { throw new Error('expected'); } };
+
+test(function() {
+ assert_true("File" in globalThis, "globalThis should have a File property.");
+}, "File interface object exists");
+
+test(t => {
+ assert_throws_js(TypeError, () => new File(),
+ 'Bits argument is required');
+ assert_throws_js(TypeError, () => new File([]),
+ 'Name argument is required');
+}, 'Required arguments');
+
+function test_first_argument(arg1, expectedSize, testName) {
+ test(function() {
+ var file = new File(arg1, "dummy");
+ assert_true(file instanceof File);
+ assert_equals(file.name, "dummy");
+ assert_equals(file.size, expectedSize);
+ assert_equals(file.type, "");
+ // assert_false(file.isClosed); XXX: File.isClosed doesn't seem to be implemented
+ assert_not_equals(file.lastModified, "");
+ }, testName);
+}
+
+test_first_argument([], 0, "empty fileBits");
+test_first_argument(["bits"], 4, "DOMString fileBits");
+test_first_argument(["𝓽𝓮𝔁𝓽"], 16, "Unicode DOMString fileBits");
+test_first_argument([new String('string object')], 13, "String object fileBits");
+test_first_argument([new Blob()], 0, "Empty Blob fileBits");
+test_first_argument([new Blob(["bits"])], 4, "Blob fileBits");
+test_first_argument([new File([], 'world.txt')], 0, "Empty File fileBits");
+test_first_argument([new File(["bits"], 'world.txt')], 4, "File fileBits");
+test_first_argument([new ArrayBuffer(8)], 8, "ArrayBuffer fileBits");
+test_first_argument([new Uint8Array([0x50, 0x41, 0x53, 0x53])], 4, "Typed array fileBits");
+test_first_argument(["bits", new Blob(["bits"]), new Blob(), new Uint8Array([0x50, 0x41]),
+ new Uint16Array([0x5353]), new Uint32Array([0x53534150])], 16, "Various fileBits");
+test_first_argument([12], 2, "Number in fileBits");
+test_first_argument([[1,2,3]], 5, "Array in fileBits");
+test_first_argument([{}], 15, "Object in fileBits"); // "[object Object]"
+if (globalThis.document !== undefined) {
+ test_first_argument([document.body], 24, "HTMLBodyElement in fileBits"); // "[object HTMLBodyElement]"
+}
+test_first_argument([to_string_obj], 8, "Object with toString in fileBits");
+test_first_argument({[Symbol.iterator]() {
+ let i = 0;
+ return {next: () => [
+ {done:false, value:'ab'},
+ {done:false, value:'cde'},
+ {done:true}
+ ][i++]};
+}}, 5, 'Custom @@iterator');
+
+[
+ 'hello',
+ 0,
+ null
+].forEach(arg => {
+ test(t => {
+ assert_throws_js(TypeError, () => new File(arg, 'world.html'),
+ 'Constructor should throw for invalid bits argument');
+ }, `Invalid bits argument: ${JSON.stringify(arg)}`);
+});
+
+test(t => {
+ assert_throws_js(Error, () => new File([to_string_throws], 'name.txt'),
+ 'Constructor should propagate exceptions');
+}, 'Bits argument: object that throws');
+
+
+function test_second_argument(arg2, expectedFileName, testName) {
+ test(function() {
+ var file = new File(["bits"], arg2);
+ assert_true(file instanceof File);
+ assert_equals(file.name, expectedFileName);
+ }, testName);
+}
+
+test_second_argument("dummy", "dummy", "Using fileName");
+test_second_argument("dummy/foo", "dummy/foo",
+ "No replacement when using special character in fileName");
+test_second_argument(null, "null", "Using null fileName");
+test_second_argument(1, "1", "Using number fileName");
+test_second_argument('', '', "Using empty string fileName");
+if (globalThis.document !== undefined) {
+ test_second_argument(document.body, '[object HTMLBodyElement]', "Using object fileName");
+}
+
+// testing the third argument
+[
+ {type: 'text/plain', expected: 'text/plain'},
+ {type: 'text/plain;charset=UTF-8', expected: 'text/plain;charset=utf-8'},
+ {type: 'TEXT/PLAIN', expected: 'text/plain'},
+ {type: '𝓽𝓮𝔁𝓽/𝔭𝔩𝔞𝔦𝔫', expected: ''},
+ {type: 'ascii/nonprintable\u001F', expected: ''},
+ {type: 'ascii/nonprintable\u007F', expected: ''},
+ {type: 'nonascii\u00EE', expected: ''},
+ {type: 'nonascii\u1234', expected: ''},
+ {type: 'nonparsable', expected: 'nonparsable'}
+].forEach(testCase => {
+ test(t => {
+ var file = new File(["bits"], "dummy", { type: testCase.type});
+ assert_true(file instanceof File);
+ assert_equals(file.type, testCase.expected);
+ }, `Using type in File constructor: ${testCase.type}`);
+});
+test(function() {
+ var file = new File(["bits"], "dummy", { lastModified: 42 });
+ assert_true(file instanceof File);
+ assert_equals(file.lastModified, 42);
+}, "Using lastModified");
+test(function() {
+ var file = new File(["bits"], "dummy", { name: "foo" });
+ assert_true(file instanceof File);
+ assert_equals(file.name, "dummy");
+}, "Misusing name");
+test(function() {
+ var file = new File(["bits"], "dummy", { unknownKey: "value" });
+ assert_true(file instanceof File);
+ assert_equals(file.name, "dummy");
+}, "Unknown properties are ignored");
+
+[
+ 123,
+ 123.4,
+ true,
+ 'abc'
+].forEach(arg => {
+ test(t => {
+ assert_throws_js(TypeError, () => new File(['bits'], 'name.txt', arg),
+ 'Constructor should throw for invalid property bag type');
+ }, `Invalid property bag: ${JSON.stringify(arg)}`);
+});
+
+[
+ null,
+ undefined,
+ [1,2,3],
+ /regex/,
+ function() {}
+].forEach(arg => {
+ test(t => {
+ assert_equals(new File(['bits'], 'name.txt', arg).size, 4,
+ 'Constructor should accept object-ish property bag type');
+ }, `Unusual but valid property bag: ${arg}`);
+});
+
+test(t => {
+ assert_throws_js(Error,
+ () => new File(['bits'], 'name.txt', {type: to_string_throws}),
+ 'Constructor should propagate exceptions');
+}, 'Property bag propagates exceptions');
diff --git a/testing/web-platform/tests/FileAPI/file/Worker-read-file-constructor.worker.js b/testing/web-platform/tests/FileAPI/file/Worker-read-file-constructor.worker.js
new file mode 100644
index 0000000000..4e003b3c95
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/Worker-read-file-constructor.worker.js
@@ -0,0 +1,15 @@
+importScripts("/resources/testharness.js");
+
+async_test(function() {
+ var file = new File(["bits"], "dummy", { 'type': 'text/plain', lastModified: 42 });
+ var reader = new FileReader();
+ reader.onload = this.step_func_done(function() {
+ assert_equals(file.name, "dummy", "file name");
+ assert_equals(reader.result, "bits", "file content");
+ assert_equals(file.lastModified, 42, "file lastModified");
+ });
+ reader.onerror = this.unreached_func("Unexpected error event");
+ reader.readAsText(file);
+}, "FileReader in Worker");
+
+done();
diff --git a/testing/web-platform/tests/FileAPI/file/resources/echo-content-escaped.py b/testing/web-platform/tests/FileAPI/file/resources/echo-content-escaped.py
new file mode 100644
index 0000000000..5370e1e46a
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/resources/echo-content-escaped.py
@@ -0,0 +1,26 @@
+from wptserve.utils import isomorphic_encode
+
+# Outputs the request body, with controls and non-ASCII bytes escaped
+# (b"\n" becomes b"\\x0a"), and with backslashes doubled.
+# As a convenience, CRLF newlines are left as is.
+
+def escape_byte(byte):
+ # Convert int byte into a single-char binary string.
+ byte = bytes([byte])
+ if b"\0" <= byte <= b"\x1F" or byte >= b"\x7F":
+ return b"\\x%02x" % ord(byte)
+ if byte == b"\\":
+ return b"\\\\"
+ return byte
+
+def main(request, response):
+
+ headers = [(b"X-Request-Method", isomorphic_encode(request.method)),
+ (b"X-Request-Content-Length", request.headers.get(b"Content-Length", b"NO")),
+ (b"X-Request-Content-Type", request.headers.get(b"Content-Type", b"NO")),
+ # Avoid any kind of content sniffing on the response.
+ (b"Content-Type", b"text/plain; charset=UTF-8")]
+
+ content = b"".join(map(escape_byte, request.body)).replace(b"\\x0d\\x0a", b"\r\n")
+
+ return headers, content
diff --git a/testing/web-platform/tests/FileAPI/file/send-file-form-controls.html b/testing/web-platform/tests/FileAPI/file/send-file-form-controls.html
new file mode 100644
index 0000000000..6347065bca
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/send-file-form-controls.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Upload files named using controls</title>
+<link
+ rel="help"
+ href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data"
+/>
+<link
+ rel="help"
+ href="https://html.spec.whatwg.org/multipage/dnd.html#datatransferitemlist"
+/>
+<link rel="help" href="https://w3c.github.io/FileAPI/#file-constructor" />
+<link
+ rel="author"
+ title="Benjamin C. Wiley Sittler"
+ href="mailto:bsittler@chromium.org"
+/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/send-file-form-helper.js"></script>
+<script>
+ "use strict";
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-NUL-[\0].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-NUL-[\0].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-BS-[\b].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-BS-[\b].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-VT-[\v].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-VT-[\v].txt",
+ });
+
+ // These have characters that undergo processing in name=,
+ // filename=, and/or value; formPostFileUploadTest postprocesses
+ // expectedEncodedBaseName for these internally.
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-LF-[\n].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-LF-[\n].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-LF-CR-[\n\r].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-LF-CR-[\n\r].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-CR-[\r].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-CR-[\r].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-CR-LF-[\r\n].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-CR-LF-[\r\n].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-HT-[\t].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-HT-[\t].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-FF-[\f].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-FF-[\f].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-DEL-[\x7F].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-DEL-[\x7F].txt",
+ });
+
+ // The rest should be passed through unmodified:
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-ESC-[\x1B].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-ESC-[\x1B].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-SPACE-[ ].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-SPACE-[ ].txt",
+ });
+</script>
diff --git a/testing/web-platform/tests/FileAPI/file/send-file-form-iso-2022-jp.html b/testing/web-platform/tests/FileAPI/file/send-file-form-iso-2022-jp.html
new file mode 100644
index 0000000000..c931c9be3a
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/send-file-form-iso-2022-jp.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name=timeout content=long>
+<title>Upload files in ISO-2022-JP form</title>
+<link rel="help"
+ href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data">
+<link rel="help"
+ href="https://html.spec.whatwg.org/multipage/dnd.html#datatransferitemlist">
+<link rel="help"
+ href="https://w3c.github.io/FileAPI/#file-constructor">
+<link rel="author" title="Benjamin C. Wiley Sittler"
+ href="mailto:bsittler@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/send-file-form-helper.js"></script>
+<script>
+'use strict';
+
+formPostFileUploadTest({
+ fileNameSource: 'ASCII',
+ fileBaseName: 'file-for-upload-in-form.txt',
+ formEncoding: 'ISO-2022-JP',
+ expectedEncodedBaseName: 'file-for-upload-in-form.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'x-user-defined',
+ fileBaseName: 'file-for-upload-in-form-\uF7F0\uF793\uF783\uF7A0.txt',
+ formEncoding: 'ISO-2022-JP',
+ expectedEncodedBaseName: (
+ 'file-for-upload-in-form-&#63472;&#63379;&#63363;&#63392;.txt'),
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'windows-1252',
+ fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+ formEncoding: 'ISO-2022-JP',
+ expectedEncodedBaseName: (
+ 'file-for-upload-in-form-&#226;&#732;&#186;&#240;&#376;&#732;&#8218;.txt'),
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'JIS X 0201 and JIS X 0208',
+ fileBaseName: 'file-for-upload-in-form-★星★.txt',
+ formEncoding: 'ISO-2022-JP',
+ expectedEncodedBaseName: 'file-for-upload-in-form-\x1B$B!z@1!z\x1B(B.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'Unicode',
+ fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+ formEncoding: 'ISO-2022-JP',
+ expectedEncodedBaseName: 'file-for-upload-in-form-&#9786;&#128514;.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'Unicode',
+ fileBaseName: `file-for-upload-in-form-${kTestChars}.txt`,
+ formEncoding: 'ISO-2022-JP',
+ expectedEncodedBaseName: `file-for-upload-in-form-${
+ kTestFallbackIso2022jp
+ }.txt`,
+});
+
+</script>
diff --git a/testing/web-platform/tests/FileAPI/file/send-file-form-punctuation.html b/testing/web-platform/tests/FileAPI/file/send-file-form-punctuation.html
new file mode 100644
index 0000000000..a6568e2e56
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/send-file-form-punctuation.html
@@ -0,0 +1,226 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Upload files named using punctuation</title>
+<link
+ rel="help"
+ href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data"
+/>
+<link
+ rel="help"
+ href="https://html.spec.whatwg.org/multipage/dnd.html#datatransferitemlist"
+/>
+<link rel="help" href="https://w3c.github.io/FileAPI/#file-constructor" />
+<link
+ rel="author"
+ title="Benjamin C. Wiley Sittler"
+ href="mailto:bsittler@chromium.org"
+/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/send-file-form-helper.js"></script>
+<script>
+ "use strict";
+
+ // These have characters that undergo processing in name=,
+ // filename=, and/or value; formPostFileUploadTest postprocesses
+ // expectedEncodedBaseName for these internally.
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-QUOTATION-MARK-[\x22].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName:
+ "file-for-upload-in-form-QUOTATION-MARK-[\x22].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: '"file-for-upload-in-form-double-quoted.txt"',
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: '"file-for-upload-in-form-double-quoted.txt"',
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-REVERSE-SOLIDUS-[\\].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName:
+ "file-for-upload-in-form-REVERSE-SOLIDUS-[\\].txt",
+ });
+
+ // The rest should be passed through unmodified:
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-EXCLAMATION-MARK-[!].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-EXCLAMATION-MARK-[!].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-DOLLAR-SIGN-[$].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-DOLLAR-SIGN-[$].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-PERCENT-SIGN-[%].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-PERCENT-SIGN-[%].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-AMPERSAND-[&].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-AMPERSAND-[&].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-APOSTROPHE-['].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-APOSTROPHE-['].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-LEFT-PARENTHESIS-[(].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-LEFT-PARENTHESIS-[(].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-RIGHT-PARENTHESIS-[)].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName:
+ "file-for-upload-in-form-RIGHT-PARENTHESIS-[)].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-ASTERISK-[*].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-ASTERISK-[*].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-PLUS-SIGN-[+].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-PLUS-SIGN-[+].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-COMMA-[,].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-COMMA-[,].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-FULL-STOP-[.].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-FULL-STOP-[.].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-SOLIDUS-[/].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-SOLIDUS-[/].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-COLON-[:].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-COLON-[:].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-SEMICOLON-[;].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-SEMICOLON-[;].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-EQUALS-SIGN-[=].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-EQUALS-SIGN-[=].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-QUESTION-MARK-[?].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-QUESTION-MARK-[?].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-CIRCUMFLEX-ACCENT-[^].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName:
+ "file-for-upload-in-form-CIRCUMFLEX-ACCENT-[^].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-LEFT-SQUARE-BRACKET-[[].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName:
+ "file-for-upload-in-form-LEFT-SQUARE-BRACKET-[[].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-RIGHT-SQUARE-BRACKET-[]].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName:
+ "file-for-upload-in-form-RIGHT-SQUARE-BRACKET-[]].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-LEFT-CURLY-BRACKET-[{].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName:
+ "file-for-upload-in-form-LEFT-CURLY-BRACKET-[{].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-VERTICAL-LINE-[|].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-VERTICAL-LINE-[|].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-RIGHT-CURLY-BRACKET-[}].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName:
+ "file-for-upload-in-form-RIGHT-CURLY-BRACKET-[}].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-TILDE-[~].txt",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "file-for-upload-in-form-TILDE-[~].txt",
+ });
+
+ formPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "'file-for-upload-in-form-single-quoted.txt'",
+ formEncoding: "UTF-8",
+ expectedEncodedBaseName: "'file-for-upload-in-form-single-quoted.txt'",
+ });
+</script>
diff --git a/testing/web-platform/tests/FileAPI/file/send-file-form-utf-8.html b/testing/web-platform/tests/FileAPI/file/send-file-form-utf-8.html
new file mode 100644
index 0000000000..1be44f4f4d
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/send-file-form-utf-8.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Upload files in UTF-8 form</title>
+<link rel="help"
+ href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data">
+<link rel="help"
+ href="https://html.spec.whatwg.org/multipage/dnd.html#datatransferitemlist">
+<link rel="help"
+ href="https://w3c.github.io/FileAPI/#file-constructor">
+<link rel="author" title="Benjamin C. Wiley Sittler"
+ href="mailto:bsittler@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/send-file-form-helper.js"></script>
+<script>
+'use strict';
+
+formPostFileUploadTest({
+ fileNameSource: 'ASCII',
+ fileBaseName: 'file-for-upload-in-form.txt',
+ formEncoding: 'UTF-8',
+ expectedEncodedBaseName: 'file-for-upload-in-form.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'x-user-defined',
+ fileBaseName: 'file-for-upload-in-form-\uF7F0\uF793\uF783\uF7A0.txt',
+ formEncoding: 'UTF-8',
+ expectedEncodedBaseName: (
+ 'file-for-upload-in-form-\xEF\x9F\xB0\xEF\x9E\x93\xEF\x9E\x83\xEF\x9E\xA0.txt'),
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'windows-1252',
+ fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+ formEncoding: 'UTF-8',
+ expectedEncodedBaseName: (
+ 'file-for-upload-in-form-\xC3\xA2\xCB\x9C\xC2\xBA\xC3\xB0\xC5\xB8\xCB\x9C\xE2\x80\x9A.txt'),
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'JIS X 0201 and JIS X 0208',
+ fileBaseName: 'file-for-upload-in-form-★星★.txt',
+ formEncoding: 'UTF-8',
+ expectedEncodedBaseName: 'file-for-upload-in-form-\xE2\x98\x85\xE6\x98\x9F\xE2\x98\x85.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'Unicode',
+ fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+ formEncoding: 'UTF-8',
+ expectedEncodedBaseName: 'file-for-upload-in-form-\xE2\x98\xBA\xF0\x9F\x98\x82.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'Unicode',
+ fileBaseName: `file-for-upload-in-form-${kTestChars}.txt`,
+ formEncoding: 'UTF-8',
+ expectedEncodedBaseName: `file-for-upload-in-form-${kTestFallbackUtf8}.txt`,
+});
+
+</script>
diff --git a/testing/web-platform/tests/FileAPI/file/send-file-form-windows-1252.html b/testing/web-platform/tests/FileAPI/file/send-file-form-windows-1252.html
new file mode 100644
index 0000000000..21b219ffd2
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/send-file-form-windows-1252.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Upload files in Windows-1252 form</title>
+<link rel="help"
+ href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data">
+<link rel="help"
+ href="https://html.spec.whatwg.org/multipage/dnd.html#datatransferitemlist">
+<link rel="help"
+ href="https://w3c.github.io/FileAPI/#file-constructor">
+<link rel="author" title="Benjamin C. Wiley Sittler"
+ href="mailto:bsittler@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/send-file-form-helper.js"></script>
+<script>
+'use strict';
+
+formPostFileUploadTest({
+ fileNameSource: 'ASCII',
+ fileBaseName: 'file-for-upload-in-form.txt',
+ formEncoding: 'windows-1252',
+ expectedEncodedBaseName: 'file-for-upload-in-form.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'x-user-defined',
+ fileBaseName: 'file-for-upload-in-form-\uF7F0\uF793\uF783\uF7A0.txt',
+ formEncoding: 'windows-1252',
+ expectedEncodedBaseName: 'file-for-upload-in-form-&#63472;&#63379;&#63363;&#63392;.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'windows-1252',
+ fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+ formEncoding: 'windows-1252',
+ expectedEncodedBaseName: 'file-for-upload-in-form-\xE2\x98\xBA\xF0\x9F\x98\x82.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'JIS X 0201 and JIS X 0208',
+ fileBaseName: 'file-for-upload-in-form-★星★.txt',
+ formEncoding: 'windows-1252',
+ expectedEncodedBaseName: 'file-for-upload-in-form-&#9733;&#26143;&#9733;.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'Unicode',
+ fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+ formEncoding: 'windows-1252',
+ expectedEncodedBaseName: 'file-for-upload-in-form-&#9786;&#128514;.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'Unicode',
+ fileBaseName: `file-for-upload-in-form-${kTestChars}.txt`,
+ formEncoding: 'windows-1252',
+ expectedEncodedBaseName: `file-for-upload-in-form-${
+ kTestFallbackWindows1252
+ }.txt`,
+});
+
+</script>
diff --git a/testing/web-platform/tests/FileAPI/file/send-file-form-x-user-defined.html b/testing/web-platform/tests/FileAPI/file/send-file-form-x-user-defined.html
new file mode 100644
index 0000000000..8d6605d86d
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/send-file-form-x-user-defined.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Upload files in x-user-defined form</title>
+<link rel="help"
+ href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data">
+<link rel="help"
+ href="https://html.spec.whatwg.org/multipage/dnd.html#datatransferitemlist">
+<link rel="help"
+ href="https://w3c.github.io/FileAPI/#file-constructor">
+<link rel="author" title="Benjamin C. Wiley Sittler"
+ href="mailto:bsittler@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/send-file-form-helper.js"></script>
+<script>
+'use strict';
+
+formPostFileUploadTest({
+ fileNameSource: 'ASCII',
+ fileBaseName: 'file-for-upload-in-form.txt',
+ formEncoding: 'x-user-defined',
+ expectedEncodedBaseName: 'file-for-upload-in-form.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'x-user-defined',
+ fileBaseName: 'file-for-upload-in-form-\uF7F0\uF793\uF783\uF7A0.txt',
+ formEncoding: 'x-user-defined',
+ expectedEncodedBaseName: 'file-for-upload-in-form-\xF0\x93\x83\xA0.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'windows-1252',
+ fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+ formEncoding: 'x-user-defined',
+ expectedEncodedBaseName: ('file-for-upload-in-form-' +
+ '&#226;&#732;&#186;&#240;&#376;&#732;&#8218;.txt'),
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'JIS X 0201 and JIS X 0208',
+ fileBaseName: 'file-for-upload-in-form-★星★.txt',
+ formEncoding: 'x-user-defined',
+ expectedEncodedBaseName: 'file-for-upload-in-form-&#9733;&#26143;&#9733;.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'Unicode',
+ fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+ formEncoding: 'x-user-defined',
+ expectedEncodedBaseName: 'file-for-upload-in-form-&#9786;&#128514;.txt',
+});
+
+formPostFileUploadTest({
+ fileNameSource: 'Unicode',
+ fileBaseName: `file-for-upload-in-form-${kTestChars}.txt`,
+ formEncoding: 'x-user-defined',
+ expectedEncodedBaseName: `file-for-upload-in-form-${
+ kTestFallbackXUserDefined
+ }.txt`,
+});
+
+</script>
diff --git a/testing/web-platform/tests/FileAPI/file/send-file-form.html b/testing/web-platform/tests/FileAPI/file/send-file-form.html
new file mode 100644
index 0000000000..baa8d4286c
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/send-file-form.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Upload ASCII-named file in UTF-8 form</title>
+<link rel="help"
+ href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data">
+<link rel="help"
+ href="https://html.spec.whatwg.org/multipage/dnd.html#datatransferitemlist">
+<link rel="help"
+ href="https://w3c.github.io/FileAPI/#file-constructor">
+<link rel="author" title="Benjamin C. Wiley Sittler"
+ href="mailto:bsittler@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/send-file-form-helper.js"></script>
+<script>
+'use strict';
+
+formPostFileUploadTest({
+ fileNameSource: 'ASCII',
+ fileBaseName: 'file-for-upload-in-form.txt',
+ formEncoding: 'UTF-8',
+ expectedEncodedBaseName: 'file-for-upload-in-form.txt',
+});
+
+</script>
diff --git a/testing/web-platform/tests/FileAPI/file/send-file-formdata-controls.any.js b/testing/web-platform/tests/FileAPI/file/send-file-formdata-controls.any.js
new file mode 100644
index 0000000000..e95d3aada4
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/send-file-formdata-controls.any.js
@@ -0,0 +1,69 @@
+// META: title=FormData: FormData: Upload files named using controls
+// META: script=../support/send-file-formdata-helper.js
+ "use strict";
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-NUL-[\0].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-BS-[\b].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-VT-[\v].txt",
+ });
+
+ // These have characters that undergo processing in name=,
+ // filename=, and/or value; formDataPostFileUploadTest postprocesses
+ // expectedEncodedBaseName for these internally.
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-LF-[\n].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-LF-CR-[\n\r].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-CR-[\r].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-CR-LF-[\r\n].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-HT-[\t].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-FF-[\f].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-DEL-[\x7F].txt",
+ });
+
+ // The rest should be passed through unmodified:
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-ESC-[\x1B].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-SPACE-[ ].txt",
+ });
diff --git a/testing/web-platform/tests/FileAPI/file/send-file-formdata-punctuation.any.js b/testing/web-platform/tests/FileAPI/file/send-file-formdata-punctuation.any.js
new file mode 100644
index 0000000000..987dba39af
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/send-file-formdata-punctuation.any.js
@@ -0,0 +1,144 @@
+// META: title=FormData: FormData: Upload files named using punctuation
+// META: script=../support/send-file-formdata-helper.js
+ "use strict";
+
+ // These have characters that undergo processing in name=,
+ // filename=, and/or value; formDataPostFileUploadTest postprocesses
+ // expectedEncodedBaseName for these internally.
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-QUOTATION-MARK-[\x22].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: '"file-for-upload-in-form-double-quoted.txt"',
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-REVERSE-SOLIDUS-[\\].txt",
+ });
+
+ // The rest should be passed through unmodified:
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-EXCLAMATION-MARK-[!].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-DOLLAR-SIGN-[$].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-PERCENT-SIGN-[%].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-AMPERSAND-[&].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-APOSTROPHE-['].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-LEFT-PARENTHESIS-[(].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-RIGHT-PARENTHESIS-[)].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-ASTERISK-[*].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-PLUS-SIGN-[+].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-COMMA-[,].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-FULL-STOP-[.].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-SOLIDUS-[/].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-COLON-[:].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-SEMICOLON-[;].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-EQUALS-SIGN-[=].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-QUESTION-MARK-[?].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-CIRCUMFLEX-ACCENT-[^].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-LEFT-SQUARE-BRACKET-[[].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-RIGHT-SQUARE-BRACKET-[]].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-LEFT-CURLY-BRACKET-[{].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-VERTICAL-LINE-[|].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-RIGHT-CURLY-BRACKET-[}].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form-TILDE-[~].txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "'file-for-upload-in-form-single-quoted.txt'",
+ });
diff --git a/testing/web-platform/tests/FileAPI/file/send-file-formdata-utf-8.any.js b/testing/web-platform/tests/FileAPI/file/send-file-formdata-utf-8.any.js
new file mode 100644
index 0000000000..b8bd74c717
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/send-file-formdata-utf-8.any.js
@@ -0,0 +1,33 @@
+// META: title=FormData: FormData: Upload files in UTF-8 fetch()
+// META: script=../support/send-file-formdata-helper.js
+ "use strict";
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form.txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "x-user-defined",
+ fileBaseName: "file-for-upload-in-form-\uF7F0\uF793\uF783\uF7A0.txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "windows-1252",
+ fileBaseName: "file-for-upload-in-form-☺😂.txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "JIS X 0201 and JIS X 0208",
+ fileBaseName: "file-for-upload-in-form-★星★.txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "Unicode",
+ fileBaseName: "file-for-upload-in-form-☺😂.txt",
+ });
+
+ formDataPostFileUploadTest({
+ fileNameSource: "Unicode",
+ fileBaseName: `file-for-upload-in-form-${kTestChars}.txt`,
+ });
diff --git a/testing/web-platform/tests/FileAPI/file/send-file-formdata.any.js b/testing/web-platform/tests/FileAPI/file/send-file-formdata.any.js
new file mode 100644
index 0000000000..e13a34828a
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/file/send-file-formdata.any.js
@@ -0,0 +1,8 @@
+// META: title=FormData: Upload ASCII-named file in UTF-8 form
+// META: script=../support/send-file-formdata-helper.js
+ "use strict";
+
+ formDataPostFileUploadTest({
+ fileNameSource: "ASCII",
+ fileBaseName: "file-for-upload-in-form.txt",
+ });
diff --git a/testing/web-platform/tests/FileAPI/fileReader.any.js b/testing/web-platform/tests/FileAPI/fileReader.any.js
new file mode 100644
index 0000000000..2876dcb4ce
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/fileReader.any.js
@@ -0,0 +1,59 @@
+// META: title=FileReader States
+
+'use strict';
+
+test(function () {
+ assert_true(
+ "FileReader" in globalThis,
+ "globalThis should have a FileReader property.",
+ );
+}, "FileReader interface object");
+
+test(function () {
+ var fileReader = new FileReader();
+ assert_true(fileReader instanceof FileReader);
+}, "no-argument FileReader constructor");
+
+var t_abort = async_test("FileReader States -- abort");
+t_abort.step(function () {
+ var fileReader = new FileReader();
+ assert_equals(fileReader.readyState, 0);
+ assert_equals(fileReader.readyState, FileReader.EMPTY);
+
+ var blob = new Blob();
+ fileReader.readAsArrayBuffer(blob);
+ assert_equals(fileReader.readyState, 1);
+ assert_equals(fileReader.readyState, FileReader.LOADING);
+
+ fileReader.onabort = this.step_func(function (e) {
+ assert_equals(fileReader.readyState, 2);
+ assert_equals(fileReader.readyState, FileReader.DONE);
+ t_abort.done();
+ });
+ fileReader.abort();
+ fileReader.onabort = this.unreached_func("abort event should fire sync");
+});
+
+var t_event = async_test("FileReader States -- events");
+t_event.step(function () {
+ var fileReader = new FileReader();
+
+ var blob = new Blob();
+ fileReader.readAsArrayBuffer(blob);
+
+ fileReader.onloadstart = this.step_func(function (e) {
+ assert_equals(fileReader.readyState, 1);
+ assert_equals(fileReader.readyState, FileReader.LOADING);
+ });
+
+ fileReader.onprogress = this.step_func(function (e) {
+ assert_equals(fileReader.readyState, 1);
+ assert_equals(fileReader.readyState, FileReader.LOADING);
+ });
+
+ fileReader.onloadend = this.step_func(function (e) {
+ assert_equals(fileReader.readyState, 2);
+ assert_equals(fileReader.readyState, FileReader.DONE);
+ t_event.done();
+ });
+});
diff --git a/testing/web-platform/tests/FileAPI/filelist-section/filelist.html b/testing/web-platform/tests/FileAPI/filelist-section/filelist.html
new file mode 100644
index 0000000000..b97dcde19f
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/filelist-section/filelist.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>FileAPI Test: filelist</title>
+ <link rel='author' title='Intel' href='http://www.intel.com'>
+ <link rel='help' href='http://dev.w3.org/2006/webapi/FileAPI/#filelist-section'>
+ <link rel="help" href="http://dev.w3.org/2006/webapi/FileAPI/#dfn-length">
+ <link rel="help" href="http://dev.w3.org/2006/webapi/FileAPI/#dfn-item">
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ </head>
+
+ <body>
+ <form name='uploadData' style="display:none">
+ <input type='file' id='fileChooser'>
+ </form>
+ <div id='log'></div>
+
+ <script>
+ var fileList;
+
+ setup(function () {
+ fileList = document.querySelector('#fileChooser').files;
+ });
+
+ test(function () {
+ assert_true('FileList' in window, 'window has a FileList property');
+ }, 'Check if window has a FileList property');
+
+ test(function () {
+ assert_equals(FileList.length, 0, 'FileList.length is 0');
+ }, 'Check if FileList.length is 0');
+
+ test(function () {
+ assert_true(fileList.item instanceof Function, 'item is a instanceof Function');
+ }, 'Check if item is a instanceof Function');
+
+ test(function() {
+ assert_inherits(fileList, 'item', 'item is a method of fileList');
+ }, 'Check if item is a method of fileList');
+
+ test(function() {
+ assert_equals(fileList.item(0), null, 'item method returns null');
+ }, 'Check if the item method returns null when no file selected');
+
+ test(function() {
+ assert_inherits(fileList, 'length', 'length is fileList attribute');
+ }, 'Check if length is fileList\'s attribute');
+
+ test(function() {
+ assert_equals(fileList.length, 0, 'fileList length is 0');
+ }, 'Check if the fileList length is 0 when no file selected');
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/FileAPI/filelist-section/filelist_multiple_selected_files-manual.html b/testing/web-platform/tests/FileAPI/filelist-section/filelist_multiple_selected_files-manual.html
new file mode 100644
index 0000000000..2efaa059fa
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/filelist-section/filelist_multiple_selected_files-manual.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>FileAPI Test: filelist_multiple_selected_files</title>
+ <link rel='author' title='Intel' href='http://www.intel.com'>
+ <link rel='help' href='http://dev.w3.org/2006/webapi/FileAPI/#filelist-section'>
+ <link rel="help" href="http://dev.w3.org/2006/webapi/FileAPI/#dfn-length">
+ <link rel="help" href="http://dev.w3.org/2006/webapi/FileAPI/#dfn-item">
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ </head>
+
+ <body>
+ <form name='uploadData'>
+ <input type='file' id='fileChooser' multiple>
+ </form>
+ <div>
+ <p>Test steps:</p>
+ <ol>
+ <li>Download <a href='support/upload.txt'>upload.txt</a>, <a href="support/upload.zip">upload.zip</a> to local.</li>
+ <li>Select the local two files (upload.txt, upload.zip) to run the test.</li>
+ </ol>
+ </div>
+
+ <div id='log'></div>
+
+ <script>
+ var fileInput = document.querySelector('#fileChooser');
+ var fileList;
+
+ setup({explicit_done: true, explicit_timeout: true});
+
+ on_event(fileInput, 'change', function(evt) {
+ test(function() {
+ fileList = document.querySelector('#fileChooser').files;
+ assert_equals(fileList.length, 2, 'fileList length is 2');
+ }, 'Check if the fileList length is 2 when selected two files');
+
+ test(function() {
+ fileList = document.querySelector('#fileChooser').files;
+ assert_true(fileList.item(0) instanceof File, 'item method is instanceof File');
+ }, 'Check if the item method returns the File interface when selected two files');
+
+ test(function() {
+ fileList = document.querySelector('#fileChooser').files;
+ assert_not_equals(fileList.item(1), null, 'item(1) is not null');
+ }, 'Check if item(1) is not null when selected two files. Index must be treated by user agents as value for the position of a File object in the FileList, with 0 representing the first file.');
+
+ test(function() {
+ fileList = document.querySelector('#fileChooser').files;
+ assert_equals(fileList.item(2), null, 'item(2) is null');
+ }, 'Check if item(2) is null when selected two files');
+
+ test(function() {
+ fileList = document.querySelector('#fileChooser').files;
+ assert_array_equals([fileList.item(0).name, fileList.item(1).name], ['upload.txt', 'upload.zip'], 'file name string is the name of selected files "upload.txt", "upload.zip"');
+ }, 'Check if the file name string is the name of selected files');
+
+ done();
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/FileAPI/filelist-section/filelist_selected_file-manual.html b/testing/web-platform/tests/FileAPI/filelist-section/filelist_selected_file-manual.html
new file mode 100644
index 0000000000..966aadda61
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/filelist-section/filelist_selected_file-manual.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>FileAPI Test: filelist_selected_file</title>
+ <link rel='author' title='Intel' href='http://www.intel.com'>
+ <link rel='help' href='http://dev.w3.org/2006/webapi/FileAPI/#filelist-section'>
+ <link rel="help" href="http://dev.w3.org/2006/webapi/FileAPI/#dfn-length">
+ <link rel="help" href="http://dev.w3.org/2006/webapi/FileAPI/#dfn-item">
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ </head>
+
+ <body>
+ <form name='uploadData'>
+ <input type='file' id='fileChooser'>
+ </form>
+ <div>
+ <p>Test steps:</p>
+ <ol>
+ <li>Download <a href='support/upload.txt'>upload.txt</a> to local.</li>
+ <li>Select the local upload.txt file to run the test.</li>
+ </ol>
+ </div>
+
+ <div id='log'></div>
+
+ <script>
+ var fileInput = document.querySelector('#fileChooser');
+ var fileList;
+
+ setup({explicit_done: true, explicit_timeout: true});
+
+ on_event(fileInput, 'change', function(evt) {
+ test(function() {
+ fileList = document.querySelector('#fileChooser').files;
+ assert_equals(fileList.length, 1, 'fileList length is 1');
+ }, 'Check if the fileList length is 1 when selected one file');
+
+ test(function() {
+ fileList = document.querySelector('#fileChooser').files;
+ assert_true(fileList.item(0) instanceof File, 'item method is instanceof File');
+ }, 'Check if the item method returns the File interface when selected one file');
+
+ test(function() {
+ fileList = document.querySelector('#fileChooser').files;
+ assert_not_equals(fileList.item(0), null, 'item(0) is not null');
+ }, 'Check if item(0) is not null when selected one file. Index must be treated by user agents as value for the position of a File object in the FileList, with 0 representing the first file.');
+
+ test(function() {
+ fileList = document.querySelector('#fileChooser').files;
+ assert_equals(fileList.item(1), null, 'item(1) is null');
+ }, 'Check if item(1) is null when selected one file only');
+
+ test(function() {
+ fileList = document.querySelector('#fileChooser').files;
+ assert_equals(fileList.item(0).name, 'upload.txt', 'file name string is "upload.txt"');
+ }, 'Check if the file name string is the selected "upload.txt"');
+
+ done();
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/FileAPI/filelist-section/support/upload.txt b/testing/web-platform/tests/FileAPI/filelist-section/support/upload.txt
new file mode 100644
index 0000000000..f45965b711
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/filelist-section/support/upload.txt
@@ -0,0 +1 @@
+Hello, this is test file for file upload.
diff --git a/testing/web-platform/tests/FileAPI/filelist-section/support/upload.zip b/testing/web-platform/tests/FileAPI/filelist-section/support/upload.zip
new file mode 100644
index 0000000000..a933d6a949
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/filelist-section/support/upload.zip
Binary files differ
diff --git a/testing/web-platform/tests/FileAPI/historical.https.html b/testing/web-platform/tests/FileAPI/historical.https.html
new file mode 100644
index 0000000000..4f841f1763
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/historical.https.html
@@ -0,0 +1,65 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Historical features</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+ <script>
+ var removedFromWindow = [
+ 'toNativeLineEndings',
+ 'FileError',
+ 'FileException',
+ 'FileHandle',
+ 'FileRequest',
+ 'MutableFile',
+ ];
+
+ removedFromWindow.forEach(function(name) {
+ test(function() {
+ assert_false(name in window);
+ }, '"' + name + '" should not be supported');
+ });
+
+ test(function() {
+ var b = new Blob();
+ var prefixes = ['op', 'moz', 'webkit', 'ms'];
+ for (var i = 0; i < prefixes.length; ++i) {
+ assert_false(prefixes[i]+'Slice' in b, "'"+prefixes[i]+"Slice' in b");
+ assert_false(prefixes[i]+'Slice' in Blob.prototype, "'"+prefixes[i]+"Slice in Blob.prototype");
+ }
+ }, 'Blob should not support slice prefixed');
+
+ test(function() {
+ var prefixes = ['', 'O', 'Moz', 'WebKit', 'MS'];
+ for (var i = 0; i < prefixes.length; ++i) {
+ assert_false(prefixes[i]+'BlobBuilder' in window, prefixes[i]+'BlobBuilder');
+ }
+ }, 'BlobBuilder should not be supported.');
+
+ test(function() {
+ assert_false('createFor' in URL);
+ }, 'createFor method should not be supported');
+
+ test(function() {
+ var b = new Blob();
+ assert_false('close' in b, 'close in b');
+ assert_false('close' in Blob.prototype, 'close in Blob.prototype');
+ assert_false('isClosed' in b, 'isClosed in b');
+ assert_false('isClosed' in Blob.prototype, 'isClosed in Blob.prototype');
+ }, 'Blob.close() should not be supported');
+
+ test(() => {
+ const f = new File([], "");
+ assert_false("lastModifiedDate" in f);
+ assert_false("lastModifiedDate" in File.prototype);
+ }, "File's lastModifiedDate should not be supported");
+
+ service_worker_test('support/historical-serviceworker.js', 'Service worker test setup');
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/FileAPI/idlharness-manual.html b/testing/web-platform/tests/FileAPI/idlharness-manual.html
new file mode 100644
index 0000000000..c1d8b0c714
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/idlharness-manual.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>File API manual IDL tests</title>
+ <link rel="author" title="Intel" href="http://www.intel.com">
+ <link rel="help" href="http://dev.w3.org/2006/webapi/FileAPI/#conformance">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+ </head>
+ <body>
+ <h1>File API manual IDL tests</h1>
+
+ <p>Either download <a href="support/upload.txt">upload.txt</a> and select it below or select an
+ arbitrary local file.</p>
+
+ <form name="uploadData">
+ <input type="file" id="fileChooser">
+ </form>
+
+ <div id="log"></div>
+
+ <script>
+ const fileInput = document.querySelector("#fileChooser");
+
+ setup({explicit_timeout: true});
+
+ idl_test(
+ ['FileAPI'],
+ ['dom', 'html', 'url'],
+ async idl_array => {
+ await new Promise(resolve => {
+ on_event(fileInput, "change", resolve);
+ });
+ idl_array.add_objects({
+ FileList: [fileInput.files],
+ File: [fileInput.files[0]],
+ });
+ }
+ );
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/FileAPI/idlharness.any.js b/testing/web-platform/tests/FileAPI/idlharness.any.js
new file mode 100644
index 0000000000..1744242b9f
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/idlharness.any.js
@@ -0,0 +1,19 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: timeout=long
+
+'use strict';
+
+// https://w3c.github.io/FileAPI/
+
+idl_test(
+ ['FileAPI'],
+ ['dom', 'html', 'url'],
+ idl_array => {
+ idl_array.add_objects({
+ Blob: ['new Blob(["TEST"])'],
+ File: ['new File(["myFileBits"], "myFileName")'],
+ FileReader: ['new FileReader()']
+ });
+ }
+);
diff --git a/testing/web-platform/tests/FileAPI/idlharness.html b/testing/web-platform/tests/FileAPI/idlharness.html
new file mode 100644
index 0000000000..45e8684f00
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/idlharness.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>File API automated IDL tests (requiring dom)</title>
+ <link rel="author" title="Intel" href="http://www.intel.com">
+ <link rel="help" href="http://dev.w3.org/2006/webapi/FileAPI/#conformance">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+ </head>
+ <body>
+ <h1>File API automated IDL tests</h1>
+
+ <div id="log"></div>
+
+ <form name="uploadData">
+ <input type="file" id="fileChooser">
+ </form>
+
+ <script>
+ 'use strict';
+
+ idl_test(
+ ['FileAPI'],
+ ['dom', 'html', 'url'],
+ idl_array => {
+ idl_array.add_objects({
+ FileList: ['document.querySelector("#fileChooser").files']
+ });
+ }
+ );
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/FileAPI/idlharness.worker.js b/testing/web-platform/tests/FileAPI/idlharness.worker.js
new file mode 100644
index 0000000000..002aaed40a
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/idlharness.worker.js
@@ -0,0 +1,17 @@
+importScripts("/resources/testharness.js");
+importScripts("/resources/WebIDLParser.js", "/resources/idlharness.js");
+
+'use strict';
+
+// https://w3c.github.io/FileAPI/
+
+idl_test(
+ ['FileAPI'],
+ ['dom', 'html', 'url'],
+ idl_array => {
+ idl_array.add_objects({
+ FileReaderSync: ['new FileReaderSync()']
+ });
+ }
+);
+done();
diff --git a/testing/web-platform/tests/FileAPI/progress-manual.html b/testing/web-platform/tests/FileAPI/progress-manual.html
new file mode 100644
index 0000000000..b2e03b3eb2
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/progress-manual.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Process Events for FileReader</title>
+<link rel=help href="http://dev.w3.org/2006/webapi/FileAPI/#event-handler-attributes-section">
+<link rel=author title="Jinks Zhao" href="mailto:jinks@maxthon.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+Please choose one file through this input below.<br>
+<input type="file" id="filer">
+<div id="log"></div>
+<script>
+var input, reader, progressEventCounter, progressEventTimeList,
+ lastProgressEventTime;
+setup(function() {
+ input = document.getElementById('filer');
+ reader = new FileReader();
+ progressEventCounter = 0;
+ progressEventTimeList = [];
+ lastProgressEventTime;
+}, { explicit_timeout: true });
+
+var t = async_test("FileReader progress events.")
+
+reader.onprogress = t.step_func(function () {
+ var newTime = new Date;
+ var timeout = newTime - lastProgressEventTime;
+
+ progressEventTimeList.push(timeout);
+ lastProgressEventTime = newTime;
+ progressEventCounter++;
+
+ assert_less_than_equal(timeout, 50, "The progress event should be fired every 50ms.");
+});
+
+reader.onload = t.step_func_done(function () {
+ assert_greater_than_equal(progressEventCounter, 1,
+ "When read completely, the progress event must be fired at least once.")
+});
+
+input.onchange = t.step_func(function () {
+ var files = input.files;
+
+ assert_greater_than(files.length, 0);
+ var file = files[0];
+
+ lastProgressEventTime = new Date;
+ reader.readAsArrayBuffer(file);
+});
+</script>
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/Determining-Encoding.any.js b/testing/web-platform/tests/FileAPI/reading-data-section/Determining-Encoding.any.js
new file mode 100644
index 0000000000..5b69f7ed98
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/Determining-Encoding.any.js
@@ -0,0 +1,81 @@
+// META: title=FileAPI Test: Blob Determining Encoding
+
+var t = async_test("Blob Determing Encoding with encoding argument");
+t.step(function() {
+ // string 'hello'
+ var data = [0xFE,0xFF,0x00,0x68,0x00,0x65,0x00,0x6C,0x00,0x6C,0x00,0x6F];
+ var blob = new Blob([new Uint8Array(data)]);
+ var reader = new FileReader();
+
+ reader.onloadend = t.step_func_done (function(event) {
+ assert_equals(this.result, "hello", "The FileReader should read the ArrayBuffer through UTF-16BE.")
+ }, reader);
+
+ reader.readAsText(blob, "UTF-16BE");
+});
+
+var t = async_test("Blob Determing Encoding with type attribute");
+t.step(function() {
+ var data = [0xFE,0xFF,0x00,0x68,0x00,0x65,0x00,0x6C,0x00,0x6C,0x00,0x6F];
+ var blob = new Blob([new Uint8Array(data)], {type:"text/plain;charset=UTF-16BE"});
+ var reader = new FileReader();
+
+ reader.onloadend = t.step_func_done (function(event) {
+ assert_equals(this.result, "hello", "The FileReader should read the ArrayBuffer through UTF-16BE.")
+ }, reader);
+
+ reader.readAsText(blob);
+});
+
+
+var t = async_test("Blob Determing Encoding with UTF-8 BOM");
+t.step(function() {
+ var data = [0xEF,0xBB,0xBF,0x68,0x65,0x6C,0x6C,0xC3,0xB6];
+ var blob = new Blob([new Uint8Array(data)]);
+ var reader = new FileReader();
+
+ reader.onloadend = t.step_func_done (function(event) {
+ assert_equals(this.result, "hellö", "The FileReader should read the blob with UTF-8.");
+ }, reader);
+
+ reader.readAsText(blob);
+});
+
+var t = async_test("Blob Determing Encoding without anything implying charset.");
+t.step(function() {
+ var data = [0x68,0x65,0x6C,0x6C,0xC3,0xB6];
+ var blob = new Blob([new Uint8Array(data)]);
+ var reader = new FileReader();
+
+ reader.onloadend = t.step_func_done (function(event) {
+ assert_equals(this.result, "hellö", "The FileReader should read the blob by default with UTF-8.");
+ }, reader);
+
+ reader.readAsText(blob);
+});
+
+var t = async_test("Blob Determing Encoding with UTF-16BE BOM");
+t.step(function() {
+ var data = [0xFE,0xFF,0x00,0x68,0x00,0x65,0x00,0x6C,0x00,0x6C,0x00,0x6F];
+ var blob = new Blob([new Uint8Array(data)]);
+ var reader = new FileReader();
+
+ reader.onloadend = t.step_func_done (function(event) {
+ assert_equals(this.result, "hello", "The FileReader should read the ArrayBuffer through UTF-16BE.");
+ }, reader);
+
+ reader.readAsText(blob);
+});
+
+var t = async_test("Blob Determing Encoding with UTF-16LE BOM");
+t.step(function() {
+ var data = [0xFF,0xFE,0x68,0x00,0x65,0x00,0x6C,0x00,0x6C,0x00,0x6F,0x00];
+ var blob = new Blob([new Uint8Array(data)]);
+ var reader = new FileReader();
+
+ reader.onloadend = t.step_func_done (function(event) {
+ assert_equals(this.result, "hello", "The FileReader should read the ArrayBuffer through UTF-16LE.");
+ }, reader);
+
+ reader.readAsText(blob);
+});
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/FileReader-event-handler-attributes.any.js b/testing/web-platform/tests/FileAPI/reading-data-section/FileReader-event-handler-attributes.any.js
new file mode 100644
index 0000000000..fc71c64348
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/FileReader-event-handler-attributes.any.js
@@ -0,0 +1,17 @@
+// META: title=FileReader event handler attributes
+
+var attributes = [
+ "onloadstart",
+ "onprogress",
+ "onload",
+ "onabort",
+ "onerror",
+ "onloadend",
+];
+attributes.forEach(function(a) {
+ test(function() {
+ var reader = new FileReader();
+ assert_equals(reader[a], null,
+ "event handler attribute should initially be null");
+ }, "FileReader." + a + ": initial value");
+});
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/FileReader-multiple-reads.any.js b/testing/web-platform/tests/FileAPI/reading-data-section/FileReader-multiple-reads.any.js
new file mode 100644
index 0000000000..4b19c69b42
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/FileReader-multiple-reads.any.js
@@ -0,0 +1,81 @@
+// META: title=FileReader: starting new reads while one is in progress
+
+test(function() {
+ var blob_1 = new Blob(['TEST000000001'])
+ var blob_2 = new Blob(['TEST000000002'])
+ var reader = new FileReader();
+ reader.readAsText(blob_1)
+ assert_equals(reader.readyState, FileReader.LOADING, "readyState Must be LOADING")
+ assert_throws_dom("InvalidStateError", function () {
+ reader.readAsText(blob_2)
+ })
+}, 'test FileReader InvalidStateError exception for readAsText');
+
+test(function() {
+ var blob_1 = new Blob(['TEST000000001'])
+ var blob_2 = new Blob(['TEST000000002'])
+ var reader = new FileReader();
+ reader.readAsDataURL(blob_1)
+ assert_equals(reader.readyState, FileReader.LOADING, "readyState Must be LOADING")
+ assert_throws_dom("InvalidStateError", function () {
+ reader.readAsDataURL(blob_2)
+ })
+}, 'test FileReader InvalidStateError exception for readAsDataURL');
+
+test(function() {
+ var blob_1 = new Blob(['TEST000000001'])
+ var blob_2 = new Blob(['TEST000000002'])
+ var reader = new FileReader();
+ reader.readAsArrayBuffer(blob_1)
+ assert_equals(reader.readyState, FileReader.LOADING, "readyState Must be LOADING")
+ assert_throws_dom("InvalidStateError", function () {
+ reader.readAsArrayBuffer(blob_2)
+ })
+}, 'test FileReader InvalidStateError exception for readAsArrayBuffer');
+
+async_test(function() {
+ var blob_1 = new Blob(['TEST000000001'])
+ var blob_2 = new Blob(['TEST000000002'])
+ var reader = new FileReader();
+ var triggered = false;
+ reader.onloadstart = this.step_func_done(function() {
+ assert_false(triggered, "Only one loadstart event should be dispatched");
+ triggered = true;
+ assert_equals(reader.readyState, FileReader.LOADING,
+ "readyState must be LOADING")
+ assert_throws_dom("InvalidStateError", function () {
+ reader.readAsArrayBuffer(blob_2)
+ })
+ });
+ reader.readAsArrayBuffer(blob_1)
+ assert_equals(reader.readyState, FileReader.LOADING, "readyState Must be LOADING")
+}, 'test FileReader InvalidStateError exception in onloadstart event for readAsArrayBuffer');
+
+async_test(function() {
+ var blob_1 = new Blob(['TEST000000001'])
+ var blob_2 = new Blob(['TEST000000002'])
+ var reader = new FileReader();
+ reader.onloadend = this.step_func_done(function() {
+ assert_equals(reader.readyState, FileReader.DONE,
+ "readyState must be DONE")
+ reader.readAsArrayBuffer(blob_2)
+ assert_equals(reader.readyState, FileReader.LOADING, "readyState Must be LOADING")
+ });
+ reader.readAsArrayBuffer(blob_1)
+ assert_equals(reader.readyState, FileReader.LOADING, "readyState Must be LOADING")
+}, 'test FileReader no InvalidStateError exception in loadend event handler for readAsArrayBuffer');
+
+async_test(function() {
+ var blob_1 = new Blob([new Uint8Array(0x414141)]);
+ var blob_2 = new Blob(['TEST000000002']);
+ var reader = new FileReader();
+ reader.onloadstart = this.step_func(function() {
+ reader.abort();
+ reader.onloadstart = null;
+ reader.onloadend = this.step_func_done(function() {
+ assert_equals('TEST000000002', reader.result);
+ });
+ reader.readAsText(blob_2);
+ });
+ reader.readAsText(blob_1);
+}, 'test abort and restart in onloadstart event for readAsText');
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/filereader_abort.any.js b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_abort.any.js
new file mode 100644
index 0000000000..c778ae55bb
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_abort.any.js
@@ -0,0 +1,38 @@
+// META: title=FileAPI Test: filereader_abort
+
+ test(function() {
+ var readerNoRead = new FileReader();
+ readerNoRead.abort();
+ assert_equals(readerNoRead.readyState, readerNoRead.EMPTY);
+ assert_equals(readerNoRead.result, null);
+ }, "Aborting before read");
+
+ promise_test(t => {
+ var blob = new Blob(["TEST THE ABORT METHOD"]);
+ var readerAbort = new FileReader();
+
+ var eventWatcher = new EventWatcher(t, readerAbort,
+ ['abort', 'loadstart', 'loadend', 'error', 'load']);
+
+ // EventWatcher doesn't let us inspect the state after the abort event,
+ // so add an extra event handler for that.
+ readerAbort.addEventListener('abort', t.step_func(e => {
+ assert_equals(readerAbort.readyState, readerAbort.DONE);
+ }));
+
+ readerAbort.readAsText(blob);
+ return eventWatcher.wait_for('loadstart')
+ .then(() => {
+ assert_equals(readerAbort.readyState, readerAbort.LOADING);
+ // 'abort' and 'loadend' events are dispatched synchronously, so
+ // call wait_for before calling abort.
+ var nextEvent = eventWatcher.wait_for(['abort', 'loadend']);
+ readerAbort.abort();
+ return nextEvent;
+ })
+ .then(() => {
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=24401
+ assert_equals(readerAbort.result, null);
+ assert_equals(readerAbort.readyState, readerAbort.DONE);
+ });
+ }, "Aborting after read");
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/filereader_error.any.js b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_error.any.js
new file mode 100644
index 0000000000..9845962090
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_error.any.js
@@ -0,0 +1,19 @@
+// META: title=FileAPI Test: filereader_error
+
+ async_test(function() {
+ var blob = new Blob(["TEST THE ERROR ATTRIBUTE AND ERROR EVENT"]);
+ var reader = new FileReader();
+ assert_equals(reader.error, null, "The error is null when no error occurred");
+
+ reader.onload = this.step_func(function(evt) {
+ assert_unreached("Should not dispatch the load event");
+ });
+
+ reader.onloadend = this.step_func(function(evt) {
+ assert_equals(reader.result, null, "The result is null");
+ this.done();
+ });
+
+ reader.readAsText(blob);
+ reader.abort();
+ });
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/filereader_events.any.js b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_events.any.js
new file mode 100644
index 0000000000..ac692907d1
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_events.any.js
@@ -0,0 +1,19 @@
+promise_test(async t => {
+ var reader = new FileReader();
+ var eventWatcher = new EventWatcher(t, reader, ['loadstart', 'progress', 'abort', 'error', 'load', 'loadend']);
+ reader.readAsText(new Blob([]));
+ await eventWatcher.wait_for('loadstart');
+ // No progress event for an empty blob, as no data is loaded.
+ await eventWatcher.wait_for('load');
+ await eventWatcher.wait_for('loadend');
+}, 'events are dispatched in the correct order for an empty blob');
+
+promise_test(async t => {
+ var reader = new FileReader();
+ var eventWatcher = new EventWatcher(t, reader, ['loadstart', 'progress', 'abort', 'error', 'load', 'loadend']);
+ reader.readAsText(new Blob(['a']));
+ await eventWatcher.wait_for('loadstart');
+ await eventWatcher.wait_for('progress');
+ await eventWatcher.wait_for('load');
+ await eventWatcher.wait_for('loadend');
+}, 'events are dispatched in the correct order for a non-empty blob');
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/filereader_file-manual.html b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_file-manual.html
new file mode 100644
index 0000000000..702ca9afd7
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_file-manual.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>FileAPI Test: filereader_file</title>
+ <link rel="author" title="Intel" href="http://www.intel.com">
+ <link rel="help" href="http://dev.w3.org/2006/webapi/FileAPI/#FileReader-interface">
+ <link rel="help" href="http://dev.w3.org/2006/webapi/FileAPI/#file">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div>
+ <p>Test step:</p>
+ <ol>
+ <li>Download <a href="support/blue-100x100.png">blue-100x100.png</a> to local.</li>
+ <li>Select the local file (blue-100x100.png) to run the test.</li>
+ </ol>
+ </div>
+
+ <form name="uploadData">
+ <input type="file" id="fileChooser">
+ </form>
+
+ <div id="log"></div>
+ <script>
+ var fileInput = document.querySelector('#fileChooser');
+ var reader = new FileReader();
+
+ //readType: 1-> ArrayBuffer, 2-> Text, 3-> DataURL
+ var readType = 1;
+
+ setup({
+ explicit_done: true,
+ explicit_timeout: true,
+ });
+
+ on_event(fileInput, "change", function(evt) {
+ reader.readAsArrayBuffer(fileInput.files[0]);
+ });
+
+ on_event(reader, "load", function(evt) {
+ if (readType == 1) {
+ test(function() {
+ assert_true(reader.result instanceof ArrayBuffer, "The result is instanceof ArrayBuffer");
+ }, "Check if the readAsArrayBuffer works");
+
+ readType++;
+ reader.readAsText(fileInput.files[0]);
+ } else if (readType == 2) {
+ test(function() {
+ assert_equals(typeof reader.result, "string", "The result is typeof string");
+ }, "Check if the readAsText works");
+
+ readType++;
+ reader.readAsDataURL(fileInput.files[0]);
+ } else if (readType == 3) {
+ test(function() {
+ assert_equals(typeof reader.result, "string", "The result is typeof string");
+ assert_equals(reader.result.indexOf("data"), 0, "The result starts with 'data'");
+ assert_true(reader.result.indexOf("base64") > 0, "The result contains 'base64'");
+ }, "Check if the readAsDataURL works");
+
+ done();
+ }
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/filereader_file_img-manual.html b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_file_img-manual.html
new file mode 100644
index 0000000000..fca42c7fce
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_file_img-manual.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>FileAPI Test: filereader_file_img</title>
+ <link rel="author" title="Intel" href="http://www.intel.com">
+ <link rel="help" href="http://dev.w3.org/2006/webapi/FileAPI/#FileReader-interface">
+ <link rel="help" href="http://dev.w3.org/2006/webapi/FileAPI/#file">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div>
+ <p>Test step:</p>
+ <ol>
+ <li>Download <a href="support/blue-100x100.png">blue-100x100.png</a> to local.</li>
+ <li>Select the local file (blue-100x100.png) to run the test.</li>
+ </ol>
+ </div>
+
+ <form name="uploadData">
+ <input type="file" id="fileChooser">
+ </form>
+
+ <div id="log"></div>
+ <script>
+ var fileInput = document.querySelector('#fileChooser');
+ var reader = new FileReader();
+
+ setup({
+ explicit_done: true,
+ explicit_timeout: true,
+ });
+
+ fileInput.addEventListener("change", function(evt) {
+ reader.readAsDataURL(fileInput.files[0]);
+ }, false);
+
+ reader.addEventListener("loadend", function(evt) {
+ test(function () {
+ assert_true(reader.result.indexOf("iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAqklEQVR42u3RsREAMAgDMe+/M4E7ZkhBoeI9gJWkWpfaeToTECACAkRAgAgIEAEB4gQgAgJEQIAICBABASIgAgJEQIAICBABASIgAgJEQIAICBABASIgAgJEQIAICBABASIgAgJEQIAICBABASIgAgJEQIAICBABASIgAgJEQIAICBABASIgAgJEQIAICBABASIgAgJEQIAICBABASIgQJwARECACAgQ/W4AQauujc8IdAoAAAAASUVORK5CYII=") != -1, "Encoded image")
+ }, "Check if readAsDataURL returns correct image");
+ done();
+ }, false);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsArrayBuffer.any.js b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsArrayBuffer.any.js
new file mode 100644
index 0000000000..d06e317078
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsArrayBuffer.any.js
@@ -0,0 +1,23 @@
+// META: title=FileAPI Test: filereader_readAsArrayBuffer
+
+ async_test(function() {
+ var blob = new Blob(["TEST"]);
+ var reader = new FileReader();
+
+ reader.onload = this.step_func(function(evt) {
+ assert_equals(reader.result.byteLength, 4, "The byteLength is 4");
+ assert_true(reader.result instanceof ArrayBuffer, "The result is instanceof ArrayBuffer");
+ assert_equals(reader.readyState, reader.DONE);
+ this.done();
+ });
+
+ reader.onloadstart = this.step_func(function(evt) {
+ assert_equals(reader.readyState, reader.LOADING);
+ });
+
+ reader.onprogress = this.step_func(function(evt) {
+ assert_equals(reader.readyState, reader.LOADING);
+ });
+
+ reader.readAsArrayBuffer(blob);
+ });
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsBinaryString.any.js b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsBinaryString.any.js
new file mode 100644
index 0000000000..e69ff15e75
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsBinaryString.any.js
@@ -0,0 +1,23 @@
+// META: title=FileAPI Test: filereader_readAsBinaryString
+
+async_test(t => {
+ const blob = new Blob(["σ"]);
+ const reader = new FileReader();
+
+ reader.onload = t.step_func_done(() => {
+ assert_equals(typeof reader.result, "string", "The result is string");
+ assert_equals(reader.result.length, 2, "The result length is 2");
+ assert_equals(reader.result, "\xcf\x83", "The result is \xcf\x83");
+ assert_equals(reader.readyState, reader.DONE);
+ });
+
+ reader.onloadstart = t.step_func(() => {
+ assert_equals(reader.readyState, reader.LOADING);
+ });
+
+ reader.onprogress = t.step_func(() => {
+ assert_equals(reader.readyState, reader.LOADING);
+ });
+
+ reader.readAsBinaryString(blob);
+});
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsDataURL.any.js b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsDataURL.any.js
new file mode 100644
index 0000000000..d681212129
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsDataURL.any.js
@@ -0,0 +1,42 @@
+// META: title=FileAPI Test: FileReader.readAsDataURL
+
+async_test(function(testCase) {
+ var blob = new Blob(["TEST"]);
+ var reader = new FileReader();
+
+ reader.onload = this.step_func(function(evt) {
+ assert_equals(reader.readyState, reader.DONE);
+ testCase.done();
+ });
+ reader.onloadstart = this.step_func(function(evt) {
+ assert_equals(reader.readyState, reader.LOADING);
+ });
+ reader.onprogress = this.step_func(function(evt) {
+ assert_equals(reader.readyState, reader.LOADING);
+ });
+
+ reader.readAsDataURL(blob);
+}, 'FileReader readyState during readAsDataURL');
+
+async_test(function(testCase) {
+ var blob = new Blob(["TEST"], { type: 'text/plain' });
+ var reader = new FileReader();
+
+ reader.onload = this.step_func(function() {
+ assert_equals(reader.result, "data:text/plain;base64,VEVTVA==");
+ testCase.done();
+ });
+ reader.readAsDataURL(blob);
+}, 'readAsDataURL result for Blob with specified MIME type');
+
+async_test(function(testCase) {
+ var blob = new Blob(["TEST"]);
+ var reader = new FileReader();
+
+ reader.onload = this.step_func(function() {
+ assert_equals(reader.result,
+ "data:application/octet-stream;base64,VEVTVA==");
+ testCase.done();
+ });
+ reader.readAsDataURL(blob);
+}, 'readAsDataURL result for Blob with unspecified MIME type'); \ No newline at end of file
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsText.any.js b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsText.any.js
new file mode 100644
index 0000000000..4d0fa11393
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readAsText.any.js
@@ -0,0 +1,36 @@
+// META: title=FileAPI Test: filereader_readAsText
+
+ async_test(function() {
+ var blob = new Blob(["TEST"]);
+ var reader = new FileReader();
+
+ reader.onload = this.step_func(function(evt) {
+ assert_equals(typeof reader.result, "string", "The result is typeof string");
+ assert_equals(reader.result, "TEST", "The result is TEST");
+ this.done();
+ });
+
+ reader.onloadstart = this.step_func(function(evt) {
+ assert_equals(reader.readyState, reader.LOADING, "The readyState");
+ });
+
+ reader.onprogress = this.step_func(function(evt) {
+ assert_equals(reader.readyState, reader.LOADING);
+ });
+
+ reader.readAsText(blob);
+ }, "readAsText should correctly read UTF-8.");
+
+ async_test(function() {
+ var blob = new Blob(["TEST"]);
+ var reader = new FileReader();
+ var reader_UTF16 = new FileReader();
+ reader_UTF16.onload = this.step_func(function(evt) {
+ // "TEST" in UTF-8 is 0x54 0x45 0x53 0x54.
+ // Decoded as utf-16 (little-endian), we get 0x4554 0x5453.
+ assert_equals(reader_UTF16.readyState, reader.DONE, "The readyState");
+ assert_equals(reader_UTF16.result, "\u4554\u5453", "The result is not TEST");
+ this.done();
+ });
+ reader_UTF16.readAsText(blob, "UTF-16");
+ }, "readAsText should correctly read UTF-16.");
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readystate.any.js b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readystate.any.js
new file mode 100644
index 0000000000..3cb36ab999
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_readystate.any.js
@@ -0,0 +1,19 @@
+// META: title=FileAPI Test: filereader_readystate
+
+ async_test(function() {
+ var blob = new Blob(["THIS TEST THE READYSTATE WHEN READ BLOB"]);
+ var reader = new FileReader();
+
+ assert_equals(reader.readyState, reader.EMPTY);
+
+ reader.onloadstart = this.step_func(function(evt) {
+ assert_equals(reader.readyState, reader.LOADING);
+ });
+
+ reader.onloadend = this.step_func(function(evt) {
+ assert_equals(reader.readyState, reader.DONE);
+ this.done();
+ });
+
+ reader.readAsDataURL(blob);
+ });
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/filereader_result.any.js b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_result.any.js
new file mode 100644
index 0000000000..28c068bb34
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/filereader_result.any.js
@@ -0,0 +1,82 @@
+// META: title=FileAPI Test: filereader_result
+
+ var blob, blob2;
+ setup(function() {
+ blob = new Blob(["This test the result attribute"]);
+ blob2 = new Blob(["This is a second blob"]);
+ });
+
+ async_test(function() {
+ var readText = new FileReader();
+ assert_equals(readText.result, null);
+
+ readText.onloadend = this.step_func(function(evt) {
+ assert_equals(typeof readText.result, "string", "The result type is string");
+ assert_equals(readText.result, "This test the result attribute", "The result is correct");
+ this.done();
+ });
+
+ readText.readAsText(blob);
+ }, "readAsText");
+
+ async_test(function() {
+ var readDataURL = new FileReader();
+ assert_equals(readDataURL.result, null);
+
+ readDataURL.onloadend = this.step_func(function(evt) {
+ assert_equals(typeof readDataURL.result, "string", "The result type is string");
+ assert_true(readDataURL.result.indexOf("VGhpcyB0ZXN0IHRoZSByZXN1bHQgYXR0cmlidXRl") != -1, "return the right base64 string");
+ this.done();
+ });
+
+ readDataURL.readAsDataURL(blob);
+ }, "readAsDataURL");
+
+ async_test(function() {
+ var readArrayBuffer = new FileReader();
+ assert_equals(readArrayBuffer.result, null);
+
+ readArrayBuffer.onloadend = this.step_func(function(evt) {
+ assert_true(readArrayBuffer.result instanceof ArrayBuffer, "The result is instanceof ArrayBuffer");
+ this.done();
+ });
+
+ readArrayBuffer.readAsArrayBuffer(blob);
+ }, "readAsArrayBuffer");
+
+ async_test(function() {
+ var readBinaryString = new FileReader();
+ assert_equals(readBinaryString.result, null);
+
+ readBinaryString.onloadend = this.step_func(function(evt) {
+ assert_equals(typeof readBinaryString.result, "string", "The result type is string");
+ assert_equals(readBinaryString.result, "This test the result attribute", "The result is correct");
+ this.done();
+ });
+
+ readBinaryString.readAsBinaryString(blob);
+ }, "readAsBinaryString");
+
+
+ for (let event of ['loadstart', 'progress']) {
+ for (let method of ['readAsText', 'readAsDataURL', 'readAsArrayBuffer', 'readAsBinaryString']) {
+ promise_test(async function(t) {
+ var reader = new FileReader();
+ assert_equals(reader.result, null, 'result is null before read');
+
+ var eventWatcher = new EventWatcher(t, reader,
+ [event, 'loadend']);
+
+ reader[method](blob);
+ assert_equals(reader.result, null, 'result is null after first read call');
+ await eventWatcher.wait_for(event);
+ assert_equals(reader.result, null, 'result is null during event');
+ await eventWatcher.wait_for('loadend');
+ assert_not_equals(reader.result, null);
+ reader[method](blob);
+ assert_equals(reader.result, null, 'result is null after second read call');
+ await eventWatcher.wait_for(event);
+ assert_equals(reader.result, null, 'result is null during second read event');
+ }, 'result is null during "' + event + '" event for ' + method);
+ }
+ }
diff --git a/testing/web-platform/tests/FileAPI/reading-data-section/support/blue-100x100.png b/testing/web-platform/tests/FileAPI/reading-data-section/support/blue-100x100.png
new file mode 100644
index 0000000000..5748719ff2
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/support/blue-100x100.png
Binary files differ
diff --git a/testing/web-platform/tests/FileAPI/support/Blob.js b/testing/web-platform/tests/FileAPI/support/Blob.js
new file mode 100644
index 0000000000..2c24974685
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/support/Blob.js
@@ -0,0 +1,70 @@
+'use strict'
+
+self.test_blob = (fn, expectations) => {
+ var expected = expectations.expected,
+ type = expectations.type,
+ desc = expectations.desc;
+
+ var t = async_test(desc);
+ t.step(function() {
+ var blob = fn();
+ assert_true(blob instanceof Blob);
+ assert_false(blob instanceof File);
+ assert_equals(blob.type, type);
+ assert_equals(blob.size, expected.length);
+
+ var fr = new FileReader();
+ fr.onload = t.step_func_done(function(event) {
+ assert_equals(this.result, expected);
+ }, fr);
+ fr.onerror = t.step_func(function(e) {
+ assert_unreached("got error event on FileReader");
+ });
+ fr.readAsText(blob, "UTF-8");
+ });
+}
+
+self.test_blob_binary = (fn, expectations) => {
+ var expected = expectations.expected,
+ type = expectations.type,
+ desc = expectations.desc;
+
+ var t = async_test(desc);
+ t.step(function() {
+ var blob = fn();
+ assert_true(blob instanceof Blob);
+ assert_false(blob instanceof File);
+ assert_equals(blob.type, type);
+ assert_equals(blob.size, expected.length);
+
+ var fr = new FileReader();
+ fr.onload = t.step_func_done(function(event) {
+ assert_true(this.result instanceof ArrayBuffer,
+ "Result should be an ArrayBuffer");
+ assert_array_equals(new Uint8Array(this.result), expected);
+ }, fr);
+ fr.onerror = t.step_func(function(e) {
+ assert_unreached("got error event on FileReader");
+ });
+ fr.readAsArrayBuffer(blob);
+ });
+}
+
+// Assert that two TypedArray objects have the same byte values
+self.assert_equals_typed_array = (array1, array2) => {
+ const [view1, view2] = [array1, array2].map((array) => {
+ assert_true(array.buffer instanceof ArrayBuffer,
+ 'Expect input ArrayBuffers to contain field `buffer`');
+ return new DataView(array.buffer, array.byteOffset, array.byteLength);
+ });
+
+ assert_equals(view1.byteLength, view2.byteLength,
+ 'Expect both arrays to be of the same byte length');
+
+ const byteLength = view1.byteLength;
+
+ for (let i = 0; i < byteLength; ++i) {
+ assert_equals(view1.getUint8(i), view2.getUint8(i),
+ `Expect byte at buffer position ${i} to be equal`);
+ }
+}
diff --git a/testing/web-platform/tests/FileAPI/support/document-domain-setter.sub.html b/testing/web-platform/tests/FileAPI/support/document-domain-setter.sub.html
new file mode 100644
index 0000000000..61aebdf326
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/support/document-domain-setter.sub.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<title>Relevant/current/blob source page used as a test helper</title>
+
+<script>
+"use strict";
+document.domain = "{{host}}";
+</script>
diff --git a/testing/web-platform/tests/FileAPI/support/empty-document.html b/testing/web-platform/tests/FileAPI/support/empty-document.html
new file mode 100644
index 0000000000..b9cd130a07
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/support/empty-document.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<body>
diff --git a/testing/web-platform/tests/FileAPI/support/historical-serviceworker.js b/testing/web-platform/tests/FileAPI/support/historical-serviceworker.js
new file mode 100644
index 0000000000..8bd89a23ad
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/support/historical-serviceworker.js
@@ -0,0 +1,5 @@
+importScripts('/resources/testharness.js');
+
+test(() => {
+ assert_false('FileReaderSync' in self);
+}, '"FileReaderSync" should not be supported in service workers');
diff --git a/testing/web-platform/tests/FileAPI/support/incumbent.sub.html b/testing/web-platform/tests/FileAPI/support/incumbent.sub.html
new file mode 100644
index 0000000000..63a81cd328
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/support/incumbent.sub.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>Incumbent page used as a test helper</title>
+
+<iframe src="//{{domains[www1]}}:{{location[port]}}/FileAPI/support/document-domain-setter.sub.html" id="c"></iframe>
+<iframe src="//{{domains[www2]}}:{{location[port]}}/FileAPI/support/document-domain-setter.sub.html" id="r"></iframe>
+<iframe src="//{{domains[élève]}}:{{location[port]}}/FileAPI/support/document-domain-setter.sub.html" id="bs"></iframe>
+
+<script>
+"use strict";
+document.domain = "{{host}}";
+
+window.createBlobURL = () => {
+ const current = document.querySelector("#c").contentWindow;
+ const relevant = document.querySelector("#r").contentWindow;
+ const blobSource = document.querySelector("#bs").contentWindow;
+
+ const blob = new blobSource.Blob(["Test Blob"]);
+
+ return current.URL.createObjectURL.call(relevant, blob);
+};
+
+</script>
diff --git a/testing/web-platform/tests/FileAPI/support/send-file-form-helper.js b/testing/web-platform/tests/FileAPI/support/send-file-form-helper.js
new file mode 100644
index 0000000000..d6adf21ec3
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/support/send-file-form-helper.js
@@ -0,0 +1,282 @@
+'use strict';
+
+// See /FileAPI/file/resources/echo-content-escaped.py
+function escapeString(string) {
+ return string.replace(/\\/g, "\\\\").replace(
+ /[^\x20-\x7E]/g,
+ (x) => {
+ let hex = x.charCodeAt(0).toString(16);
+ if (hex.length < 2) hex = "0" + hex;
+ return `\\x${hex}`;
+ },
+ ).replace(/\\x0d\\x0a/g, "\r\n");
+}
+
+// Rationale for this particular test character sequence, which is
+// used in filenames and also in file contents:
+//
+// - ABC~ ensures the string starts with something we can read to
+// ensure it is from the correct source; ~ is used because even
+// some 1-byte otherwise-ASCII-like parts of ISO-2022-JP
+// interpret it differently.
+// - ‾¥ are inside a single-byte range of ISO-2022-JP and help
+// diagnose problems due to filesystem encoding or locale
+// - ≈ is inside IBM437 and helps diagnose problems due to filesystem
+// encoding or locale
+// - ¤ is inside Latin-1 and helps diagnose problems due to
+// filesystem encoding or locale; it is also the "simplest" case
+// needing substitution in ISO-2022-JP
+// - ・ is inside a single-byte range of ISO-2022-JP in some variants
+// and helps diagnose problems due to filesystem encoding or locale;
+// on the web it is distinct when decoding but unified when encoding
+// - ・ is inside a double-byte range of ISO-2022-JP and helps
+// diagnose problems due to filesystem encoding or locale
+// - • is inside Windows-1252 and helps diagnose problems due to
+// filesystem encoding or locale and also ensures these aren't
+// accidentally turned into e.g. control codes
+// - ∙ is inside IBM437 and helps diagnose problems due to filesystem
+// encoding or locale
+// - · is inside Latin-1 and helps diagnose problems due to
+// filesystem encoding or locale and also ensures HTML named
+// character references (e.g. &middot;) are not used
+// - ☼ is inside IBM437 shadowing C0 and helps diagnose problems due to
+// filesystem encoding or locale and also ensures these aren't
+// accidentally turned into e.g. control codes
+// - ★ is inside ISO-2022-JP on a non-Kanji page and makes correct
+// output easier to spot
+// - 星 is inside ISO-2022-JP on a Kanji page and makes correct
+// output easier to spot
+// - 🌟 is outside the BMP and makes incorrect surrogate pair
+// substitution detectable and ensures substitutions work
+// correctly immediately after Kanji 2-byte ISO-2022-JP
+// - 星 repeated here ensures the correct codec state is used
+// after a non-BMP substitution
+// - ★ repeated here also makes correct output easier to spot
+// - ☼ is inside IBM437 shadowing C0 and helps diagnose problems due to
+// filesystem encoding or locale and also ensures these aren't
+// accidentally turned into e.g. control codes and also ensures
+// substitutions work correctly immediately after non-Kanji
+// 2-byte ISO-2022-JP
+// - · is inside Latin-1 and helps diagnose problems due to
+// filesystem encoding or locale and also ensures HTML named
+// character references (e.g. &middot;) are not used
+// - ∙ is inside IBM437 and helps diagnose problems due to filesystem
+// encoding or locale
+// - • is inside Windows-1252 and again helps diagnose problems
+// due to filesystem encoding or locale
+// - ・ is inside a double-byte range of ISO-2022-JP and helps
+// diagnose problems due to filesystem encoding or locale
+// - ・ is inside a single-byte range of ISO-2022-JP in some variants
+// and helps diagnose problems due to filesystem encoding or locale;
+// on the web it is distinct when decoding but unified when encoding
+// - ¤ is inside Latin-1 and helps diagnose problems due to
+// filesystem encoding or locale; again it is a "simple"
+// substitution case
+// - ≈ is inside IBM437 and helps diagnose problems due to filesystem
+// encoding or locale
+// - ¥‾ are inside a single-byte range of ISO-2022-JP and help
+// diagnose problems due to filesystem encoding or locale
+// - ~XYZ ensures earlier errors don't lead to misencoding of
+// simple ASCII
+//
+// Overall the near-symmetry makes common I18N mistakes like
+// off-by-1-after-non-BMP easier to spot. All the characters
+// are also allowed in Windows Unicode filenames.
+const kTestChars = 'ABC~‾¥≈¤・・•∙·☼★星🌟星★☼·∙•・・¤≈¥‾~XYZ';
+
+// The kTestFallback* strings represent the expected byte sequence from
+// encoding kTestChars with the given encoding with "html" replacement
+// mode, isomorphic-decoded. That means, characters that can't be
+// encoded in that encoding get HTML-escaped, but no further
+// `escapeString`-like escapes are needed.
+const kTestFallbackUtf8 = (
+ "ABC~\xE2\x80\xBE\xC2\xA5\xE2\x89\x88\xC2\xA4\xEF\xBD\xA5\xE3\x83\xBB\xE2" +
+ "\x80\xA2\xE2\x88\x99\xC2\xB7\xE2\x98\xBC\xE2\x98\x85\xE6\x98\x9F\xF0\x9F" +
+ "\x8C\x9F\xE6\x98\x9F\xE2\x98\x85\xE2\x98\xBC\xC2\xB7\xE2\x88\x99\xE2\x80" +
+ "\xA2\xE3\x83\xBB\xEF\xBD\xA5\xC2\xA4\xE2\x89\x88\xC2\xA5\xE2\x80\xBE~XYZ"
+);
+
+const kTestFallbackIso2022jp = (
+ ("ABC~\x1B(J~\\≈¤\x1B$B!&!&\x1B(B•∙·☼\x1B$B!z@1\x1B(B🌟" +
+ "\x1B$B@1!z\x1B(B☼·∙•\x1B$B!&!&\x1B(B¤≈\x1B(J\\~\x1B(B~XYZ")
+ .replace(/[^\0-\x7F]/gu, (x) => `&#${x.codePointAt(0)};`)
+);
+
+const kTestFallbackWindows1252 = (
+ "ABC~‾\xA5≈\xA4・・\x95∙\xB7☼★星🌟星★☼\xB7∙\x95・・\xA4≈\xA5‾~XYZ".replace(
+ /[^\0-\xFF]/gu,
+ (x) => `&#${x.codePointAt(0)};`,
+ )
+);
+
+const kTestFallbackXUserDefined = kTestChars.replace(
+ /[^\0-\x7F]/gu,
+ (x) => `&#${x.codePointAt(0)};`,
+);
+
+// formPostFileUploadTest - verifies multipart upload structure and
+// numeric character reference replacement for filenames, field names,
+// and field values using form submission.
+//
+// Uses /FileAPI/file/resources/echo-content-escaped.py to echo the
+// upload POST with controls and non-ASCII bytes escaped. This is done
+// because navigations whose response body contains [\0\b\v] may get
+// treated as a download, which is not what we want. Use the
+// `escapeString` function to replicate that kind of escape (note that
+// it takes an isomorphic-decoded string, not a byte sequence).
+//
+// Fields in the parameter object:
+//
+// - fileNameSource: purely explanatory and gives a clue about which
+// character encoding is the source for the non-7-bit-ASCII parts of
+// the fileBaseName, or Unicode if no smaller-than-Unicode source
+// contains all the characters. Used in the test name.
+// - fileBaseName: the not-necessarily-just-7-bit-ASCII file basename
+// used for the constructed test file. Used in the test name.
+// - formEncoding: the acceptCharset of the form used to submit the
+// test file. Used in the test name.
+// - expectedEncodedBaseName: the expected formEncoding-encoded
+// version of fileBaseName, isomorphic-decoded. That means, characters
+// that can't be encoded in that encoding get HTML-escaped, but no
+// further `escapeString`-like escapes are needed.
+const formPostFileUploadTest = ({
+ fileNameSource,
+ fileBaseName,
+ formEncoding,
+ expectedEncodedBaseName,
+}) => {
+ promise_test(async testCase => {
+
+ if (document.readyState !== 'complete') {
+ await new Promise(resolve => addEventListener('load', resolve));
+ }
+
+ const formTargetFrame = Object.assign(document.createElement('iframe'), {
+ name: 'formtargetframe',
+ });
+ document.body.append(formTargetFrame);
+ testCase.add_cleanup(() => {
+ document.body.removeChild(formTargetFrame);
+ });
+
+ const form = Object.assign(document.createElement('form'), {
+ acceptCharset: formEncoding,
+ action: '/FileAPI/file/resources/echo-content-escaped.py',
+ method: 'POST',
+ enctype: 'multipart/form-data',
+ target: formTargetFrame.name,
+ });
+ document.body.append(form);
+ testCase.add_cleanup(() => {
+ document.body.removeChild(form);
+ });
+
+ // Used to verify that the browser agrees with the test about
+ // which form charset is used.
+ form.append(Object.assign(document.createElement('input'), {
+ type: 'hidden',
+ name: '_charset_',
+ }));
+
+ // Used to verify that the browser agrees with the test about
+ // field value replacement and encoding independently of file system
+ // idiosyncracies.
+ form.append(Object.assign(document.createElement('input'), {
+ type: 'hidden',
+ name: 'filename',
+ value: fileBaseName,
+ }));
+
+ // Same, but with name and value reversed to ensure field names
+ // get the same treatment.
+ form.append(Object.assign(document.createElement('input'), {
+ type: 'hidden',
+ name: fileBaseName,
+ value: 'filename',
+ }));
+
+ const fileInput = Object.assign(document.createElement('input'), {
+ type: 'file',
+ name: 'file',
+ });
+ form.append(fileInput);
+
+ // Removes c:\fakepath\ or other pseudofolder and returns just the
+ // final component of filePath; allows both / and \ as segment
+ // delimiters.
+ const baseNameOfFilePath = filePath => filePath.split(/[\/\\]/).pop();
+ await new Promise(resolve => {
+ const dataTransfer = new DataTransfer;
+ dataTransfer.items.add(
+ new File([kTestChars], fileBaseName, {type: 'text/plain'}));
+ fileInput.files = dataTransfer.files;
+ // For historical reasons .value will be prefixed with
+ // c:\fakepath\, but the basename should match the file name
+ // exposed through the newer .files[0].name API. This check
+ // verifies that assumption.
+ assert_equals(
+ baseNameOfFilePath(fileInput.files[0].name),
+ baseNameOfFilePath(fileInput.value),
+ `The basename of the field's value should match its files[0].name`);
+ form.submit();
+ formTargetFrame.onload = resolve;
+ });
+
+ const formDataText = formTargetFrame.contentDocument.body.textContent;
+ const formDataLines = formDataText.split('\n');
+ if (formDataLines.length && !formDataLines[formDataLines.length - 1]) {
+ --formDataLines.length;
+ }
+ assert_greater_than(
+ formDataLines.length,
+ 2,
+ `${fileBaseName}: multipart form data must have at least 3 lines: ${
+ JSON.stringify(formDataText)
+ }`);
+ const boundary = formDataLines[0];
+ assert_equals(
+ formDataLines[formDataLines.length - 1],
+ boundary + '--',
+ `${fileBaseName}: multipart form data must end with ${boundary}--: ${
+ JSON.stringify(formDataText)
+ }`);
+
+ const asValue = expectedEncodedBaseName.replace(/\r\n?|\n/g, "\r\n");
+ const asName = asValue.replace(/[\r\n"]/g, encodeURIComponent);
+ const asFilename = expectedEncodedBaseName.replace(/[\r\n"]/g, encodeURIComponent);
+
+ // The response body from echo-content-escaped.py has controls and non-ASCII
+ // bytes escaped, so any caller-provided field that might contain such bytes
+ // must be passed to `escapeString`, after any other expected
+ // transformations.
+ const expectedText = [
+ boundary,
+ 'Content-Disposition: form-data; name="_charset_"',
+ '',
+ formEncoding,
+ boundary,
+ 'Content-Disposition: form-data; name="filename"',
+ '',
+ // Unlike for names and filenames, multipart/form-data values don't escape
+ // \r\n linebreaks, and when they're read from an iframe they become \n.
+ escapeString(asValue).replace(/\r\n/g, "\n"),
+ boundary,
+ `Content-Disposition: form-data; name="${escapeString(asName)}"`,
+ '',
+ 'filename',
+ boundary,
+ `Content-Disposition: form-data; name="file"; ` +
+ `filename="${escapeString(asFilename)}"`,
+ 'Content-Type: text/plain',
+ '',
+ escapeString(kTestFallbackUtf8),
+ boundary + '--',
+ ].join('\n');
+
+ assert_true(
+ formDataText.startsWith(expectedText),
+ `Unexpected multipart-shaped form data received:\n${
+ formDataText
+ }\nExpected:\n${expectedText}`);
+ }, `Upload ${fileBaseName} (${fileNameSource}) in ${formEncoding} form`);
+};
diff --git a/testing/web-platform/tests/FileAPI/support/send-file-formdata-helper.js b/testing/web-platform/tests/FileAPI/support/send-file-formdata-helper.js
new file mode 100644
index 0000000000..53c8cca7e0
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/support/send-file-formdata-helper.js
@@ -0,0 +1,99 @@
+"use strict";
+
+const kTestChars = "ABC~‾¥≈¤・・•∙·☼★星🌟星★☼·∙•・・¤≈¥‾~XYZ";
+
+// formDataPostFileUploadTest - verifies multipart upload structure and
+// numeric character reference replacement for filenames, field names,
+// and field values using FormData and fetch().
+//
+// Uses /fetch/api/resources/echo-content.py to echo the upload
+// POST (unlike in send-file-form-helper.js, here we expect all
+// multipart/form-data request bodies to be UTF-8, so we don't need to
+// escape controls and non-ASCII bytes).
+//
+// Fields in the parameter object:
+//
+// - fileNameSource: purely explanatory and gives a clue about which
+// character encoding is the source for the non-7-bit-ASCII parts of
+// the fileBaseName, or Unicode if no smaller-than-Unicode source
+// contains all the characters. Used in the test name.
+// - fileBaseName: the not-necessarily-just-7-bit-ASCII file basename
+// used for the constructed test file. Used in the test name.
+const formDataPostFileUploadTest = ({
+ fileNameSource,
+ fileBaseName,
+}) => {
+ promise_test(async (testCase) => {
+ const formData = new FormData();
+ let file = new Blob([kTestChars], { type: "text/plain" });
+ try {
+ // Switch to File in browsers that allow this
+ file = new File([file], fileBaseName, { type: file.type });
+ } catch (ignoredException) {
+ }
+
+ // Used to verify that the browser agrees with the test about
+ // field value replacement and encoding independently of file system
+ // idiosyncracies.
+ formData.append("filename", fileBaseName);
+
+ // Same, but with name and value reversed to ensure field names
+ // get the same treatment.
+ formData.append(fileBaseName, "filename");
+
+ formData.append("file", file, fileBaseName);
+
+ const formDataText = await (await fetch(
+ `/fetch/api/resources/echo-content.py`,
+ {
+ method: "POST",
+ body: formData,
+ },
+ )).text();
+ const formDataLines = formDataText.split("\r\n");
+ if (formDataLines.length && !formDataLines[formDataLines.length - 1]) {
+ --formDataLines.length;
+ }
+ assert_greater_than(
+ formDataLines.length,
+ 2,
+ `${fileBaseName}: multipart form data must have at least 3 lines: ${
+ JSON.stringify(formDataText)
+ }`,
+ );
+ const boundary = formDataLines[0];
+ assert_equals(
+ formDataLines[formDataLines.length - 1],
+ boundary + "--",
+ `${fileBaseName}: multipart form data must end with ${boundary}--: ${
+ JSON.stringify(formDataText)
+ }`,
+ );
+
+ const asValue = fileBaseName.replace(/\r\n?|\n/g, "\r\n");
+ const asName = asValue.replace(/[\r\n"]/g, encodeURIComponent);
+ const asFilename = fileBaseName.replace(/[\r\n"]/g, encodeURIComponent);
+ const expectedText = [
+ boundary,
+ 'Content-Disposition: form-data; name="filename"',
+ "",
+ asValue,
+ boundary,
+ `Content-Disposition: form-data; name="${asName}"`,
+ "",
+ "filename",
+ boundary,
+ `Content-Disposition: form-data; name="file"; ` +
+ `filename="${asFilename}"`,
+ "Content-Type: text/plain",
+ "",
+ kTestChars,
+ boundary + "--",
+ ].join("\r\n");
+
+ assert_true(
+ formDataText.startsWith(expectedText),
+ `Unexpected multipart-shaped form data received:\n${formDataText}\nExpected:\n${expectedText}`,
+ );
+ }, `Upload ${fileBaseName} (${fileNameSource}) in fetch with FormData`);
+};
diff --git a/testing/web-platform/tests/FileAPI/support/upload.txt b/testing/web-platform/tests/FileAPI/support/upload.txt
new file mode 100644
index 0000000000..5ab2f8a432
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/support/upload.txt
@@ -0,0 +1 @@
+Hello \ No newline at end of file
diff --git a/testing/web-platform/tests/FileAPI/support/url-origin.html b/testing/web-platform/tests/FileAPI/support/url-origin.html
new file mode 100644
index 0000000000..6375511391
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/support/url-origin.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+const blob = new Blob(["Test Blob"]);
+const url = URL.createObjectURL(blob);
+window.parent.postMessage({url: url}, '*');
+</script>
diff --git a/testing/web-platform/tests/FileAPI/unicode.html b/testing/web-platform/tests/FileAPI/unicode.html
new file mode 100644
index 0000000000..ce3e3579d7
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/unicode.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Blob/Unicode interaction: normalization and encoding</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+const OMICRON_WITH_OXIA = '\u1F79'; // NFC normalized to U+3CC
+const CONTAINS_UNPAIRED_SURROGATES = 'abc\uDC00def\uD800ghi';
+const REPLACED = 'abc\uFFFDdef\uFFFDghi';
+
+function readBlobAsPromise(blob) {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsText(blob);
+ reader.onload = () => resolve(reader.result);
+ reader.onerror = () => reject(reader.error);
+ });
+}
+
+promise_test(async t => {
+ const blob = new Blob([OMICRON_WITH_OXIA]);
+ const result = await readBlobAsPromise(blob);
+ assert_equals(result, OMICRON_WITH_OXIA, 'String should not be normalized');
+}, 'Test that strings are not NFC normalized by Blob constructor');
+
+promise_test(async t => {
+ const file = new File([OMICRON_WITH_OXIA], 'name');
+ const result = await readBlobAsPromise(file);
+ assert_equals(result, OMICRON_WITH_OXIA, 'String should not be normalized');
+}, 'Test that strings are not NFC normalized by File constructor');
+
+promise_test(async t => {
+ const blob = new Blob([CONTAINS_UNPAIRED_SURROGATES]);
+ const result = await readBlobAsPromise(blob);
+ assert_equals(result, REPLACED, 'Unpaired surrogates should be replaced.');
+}, 'Test that unpaired surrogates are replaced by Blob constructor');
+
+promise_test(async t => {
+ const file = new File([CONTAINS_UNPAIRED_SURROGATES], 'name');
+ const result = await readBlobAsPromise(file);
+ assert_equals(result, REPLACED, 'Unpaired surrogates should be replaced.');
+}, 'Test that unpaired surrogates are replaced by File constructor');
+
+</script>
diff --git a/testing/web-platform/tests/FileAPI/url/cross-global-revoke.sub.html b/testing/web-platform/tests/FileAPI/url/cross-global-revoke.sub.html
new file mode 100644
index 0000000000..ce9d680709
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/cross-global-revoke.sub.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<body>
+<script>
+async_test(t => {
+ const blob_contents = 'test blob contents';
+ const blob = new Blob([blob_contents]);
+ const url = URL.createObjectURL(blob);
+ const frame = document.createElement('iframe');
+ frame.setAttribute('style', 'display:none;');
+ frame.src = 'resources/revoke-helper.html';
+ document.body.appendChild(frame);
+
+ frame.onload = t.step_func(e => {
+ frame.contentWindow.postMessage({url: url}, '*');
+ });
+
+ self.addEventListener('message', t.step_func(e => {
+ if (e.source !== frame.contentWindow) return;
+ assert_equals(e.data, 'revoked');
+ promise_rejects_js(t, TypeError, fetch(url)).then(t.step_func_done());
+ }));
+}, 'It is possible to revoke same-origin blob URLs from different frames.');
+
+async_test(t => {
+ const blob_contents = 'test blob contents';
+ const blob = new Blob([blob_contents]);
+ const url = URL.createObjectURL(blob);
+ const worker = new Worker('resources/revoke-helper.js');
+ worker.onmessage = t.step_func(e => {
+ assert_equals(e.data, 'revoked');
+ promise_rejects_js(t, TypeError, fetch(url)).then(t.step_func_done());
+ });
+ worker.postMessage({url: url});
+}, 'It is possible to revoke same-origin blob URLs from a different worker global.');
+
+async_test(t => {
+ const blob_contents = 'test blob contents';
+ const blob = new Blob([blob_contents]);
+ const url = URL.createObjectURL(blob);
+ const frame = document.createElement('iframe');
+ frame.setAttribute('style', 'display:none;');
+ frame.src = get_host_info().HTTP_REMOTE_ORIGIN + '/FileAPI/url/resources/revoke-helper.html';
+ document.body.appendChild(frame);
+
+ frame.onload = t.step_func(e => {
+ frame.contentWindow.postMessage({url: url}, '*');
+ });
+
+ self.addEventListener('message', t.step_func(e => {
+ if (e.source !== frame.contentWindow) return;
+ assert_equals(e.data, 'revoked');
+ fetch(url).then(response => response.text()).then(t.step_func_done(text => {
+ assert_equals(text, blob_contents);
+ }), t.unreached_func('Unexpected promise rejection'));
+ }));
+}, 'It is not possible to revoke cross-origin blob URLs.');
+
+</script>
diff --git a/testing/web-platform/tests/FileAPI/url/multi-global-origin-serialization.sub.html b/testing/web-platform/tests/FileAPI/url/multi-global-origin-serialization.sub.html
new file mode 100644
index 0000000000..0052b26fa6
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/multi-global-origin-serialization.sub.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Blob URL serialization (specifically the origin) in multi-global situations</title>
+<link rel="help" href="https://w3c.github.io/FileAPI/#unicodeBlobURL">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- this page is the entry global -->
+
+<iframe src="//{{domains[www]}}:{{location[port]}}/FileAPI/support/incumbent.sub.html"></iframe>
+
+<script>
+"use strict";
+setup({ single_test: true });
+document.domain = "{{host}}";
+
+window.onload = () => {
+ const url = frames[0].createBlobURL();
+ const desired = "blob:{{location[scheme]}}://www1";
+ assert_equals(url.substring(0, desired.length), desired,
+ "Origin should contain www1, from the current settings object");
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/FileAPI/url/resources/create-helper.html b/testing/web-platform/tests/FileAPI/url/resources/create-helper.html
new file mode 100644
index 0000000000..fa6cf4e671
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/resources/create-helper.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<script>
+self.addEventListener('message', e => {
+ let url = URL.createObjectURL(e.data.blob);
+ e.source.postMessage({url: url}, '*');
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/FileAPI/url/resources/create-helper.js b/testing/web-platform/tests/FileAPI/url/resources/create-helper.js
new file mode 100644
index 0000000000..e6344f700c
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/resources/create-helper.js
@@ -0,0 +1,4 @@
+self.addEventListener('message', e => {
+ let url = URL.createObjectURL(e.data.blob);
+ self.postMessage({url: url});
+});
diff --git a/testing/web-platform/tests/FileAPI/url/resources/fetch-tests.js b/testing/web-platform/tests/FileAPI/url/resources/fetch-tests.js
new file mode 100644
index 0000000000..a81ea1e7b1
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/resources/fetch-tests.js
@@ -0,0 +1,71 @@
+// This method generates a number of tests verifying fetching of blob URLs,
+// allowing the same tests to be used both with fetch() and XMLHttpRequest.
+//
+// |fetch_method| is only used in test names, and should describe the
+// (javascript) method being used by the other two arguments (i.e. 'fetch' or 'XHR').
+//
+// |fetch_should_succeed| is a callback that is called with the Test and a URL.
+// Fetching the URL is expected to succeed. The callback should return a promise
+// resolved with whatever contents were fetched.
+//
+// |fetch_should_fail| similarly is a callback that is called with the Test, a URL
+// to fetch, and optionally a method to use to do the fetch. If no method is
+// specified the callback should use the 'GET' method. Fetching of these URLs is
+// expected to fail, and the callback should return a promise that resolves iff
+// fetching did indeed fail.
+function fetch_tests(fetch_method, fetch_should_succeed, fetch_should_fail) {
+ const blob_contents = 'test blob contents';
+ const blob = new Blob([blob_contents]);
+
+ promise_test(t => {
+ const url = URL.createObjectURL(blob);
+
+ return fetch_should_succeed(t, url).then(text => {
+ assert_equals(text, blob_contents);
+ });
+ }, 'Blob URLs can be used in ' + fetch_method);
+
+ promise_test(t => {
+ const url = URL.createObjectURL(blob);
+
+ return fetch_should_succeed(t, url + '#fragment').then(text => {
+ assert_equals(text, blob_contents);
+ });
+ }, fetch_method + ' with a fragment should succeed');
+
+ promise_test(t => {
+ const url = URL.createObjectURL(blob);
+ URL.revokeObjectURL(url);
+
+ return fetch_should_fail(t, url);
+ }, fetch_method + ' of a revoked URL should fail');
+
+ promise_test(t => {
+ const url = URL.createObjectURL(blob);
+ URL.revokeObjectURL(url + '#fragment');
+
+ return fetch_should_succeed(t, url).then(text => {
+ assert_equals(text, blob_contents);
+ });
+ }, 'Only exact matches should revoke URLs, using ' + fetch_method);
+
+ promise_test(t => {
+ const url = URL.createObjectURL(blob);
+
+ return fetch_should_fail(t, url + '?querystring');
+ }, 'Appending a query string should cause ' + fetch_method + ' to fail');
+
+ promise_test(t => {
+ const url = URL.createObjectURL(blob);
+
+ return fetch_should_fail(t, url + '/path');
+ }, 'Appending a path should cause ' + fetch_method + ' to fail');
+
+ for (const method of ['HEAD', 'POST', 'DELETE', 'OPTIONS', 'PUT', 'CUSTOM']) {
+ const url = URL.createObjectURL(blob);
+
+ promise_test(t => {
+ return fetch_should_fail(t, url, method);
+ }, fetch_method + ' with method "' + method + '" should fail');
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/FileAPI/url/resources/revoke-helper.html b/testing/web-platform/tests/FileAPI/url/resources/revoke-helper.html
new file mode 100644
index 0000000000..adf5a014a6
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/resources/revoke-helper.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<script>
+self.addEventListener('message', e => {
+ URL.revokeObjectURL(e.data.url);
+ e.source.postMessage('revoked', '*');
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/FileAPI/url/resources/revoke-helper.js b/testing/web-platform/tests/FileAPI/url/resources/revoke-helper.js
new file mode 100644
index 0000000000..c3e05b64b1
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/resources/revoke-helper.js
@@ -0,0 +1,9 @@
+self.addEventListener('message', e => {
+ URL.revokeObjectURL(e.data.url);
+ // Registering a new object URL will make absolutely sure that the revocation
+ // has propagated. Without this at least in chrome it is possible for the
+ // below postMessage to arrive at its destination before the revocation has
+ // been fully processed.
+ URL.createObjectURL(new Blob([]));
+ self.postMessage('revoked');
+});
diff --git a/testing/web-platform/tests/FileAPI/url/sandboxed-iframe.html b/testing/web-platform/tests/FileAPI/url/sandboxed-iframe.html
new file mode 100644
index 0000000000..a52939a3eb
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/sandboxed-iframe.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>FileAPI Test: Verify behavior of Blob URL in unique origins</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe id="sandboxed-iframe" sandbox="allow-scripts"></iframe>
+
+<script>
+
+const iframe_scripts = [
+ 'resources/fetch-tests.js',
+ 'url-format.any.js',
+ 'url-in-tags.window.js',
+ 'url-with-xhr.any.js',
+ 'url-with-fetch.any.js',
+];
+
+let html = '<!doctype html>\n<meta charset="utf-8">\n<body>\n';
+html = html + '<script src="/resources/testharness.js"></' + 'script>\n';
+html = html + '<script>setup({"explicit_timeout": true});</' + 'script>\n';
+for (const script of iframe_scripts)
+ html = html + '<script src="' + script + '"></' + 'script>\n';
+
+const frame = document.querySelector('#sandboxed-iframe');
+frame.setAttribute('srcdoc', html);
+frame.setAttribute('style', 'display:none;');
+
+fetch_tests_from_window(frame.contentWindow);
+
+</script>
diff --git a/testing/web-platform/tests/FileAPI/url/unicode-origin.sub.html b/testing/web-platform/tests/FileAPI/url/unicode-origin.sub.html
new file mode 100644
index 0000000000..2c4921c034
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/unicode-origin.sub.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FileAPI Test: Verify origin of Blob URL</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+async_test(t => {
+ const frame = document.createElement('iframe');
+ self.addEventListener('message', t.step_func(e => {
+ if (e.source != frame.contentWindow) return;
+ const url = e.data.url;
+ assert_false(url.includes('天気の良い日'),
+ 'Origin should be ascii rather than unicode');
+ assert_equals(new URL(url).origin, e.origin,
+ 'Origin of URL should match origin of frame');
+ assert_true(url.startsWith('blob:{{location[scheme]}}://xn--'));
+ t.done();
+ }));
+ frame.src = '{{location[scheme]}}://{{domains[天気の良い日]}}:{{location[port]}}/FileAPI/support/url-origin.html';
+ document.body.appendChild(frame);
+}, 'Verify serialization of non-ascii origin in Blob URLs');
+</script>
diff --git a/testing/web-platform/tests/FileAPI/url/url-charset.window.js b/testing/web-platform/tests/FileAPI/url/url-charset.window.js
new file mode 100644
index 0000000000..777709b64a
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/url-charset.window.js
@@ -0,0 +1,34 @@
+async_test(t => {
+ // This could be detected as ISO-2022-JP, in which case there would be no
+ // <textarea>, and thus the script inside would be interpreted as actual
+ // script.
+ const blob = new Blob(
+ [
+ `aaa\u001B$@<textarea>\u001B(B<script>/* xss */<\/script></textarea>bbb`
+ ],
+ {type: 'text/html;charset=utf-8'});
+ const url = URL.createObjectURL(blob);
+ const win = window.open(url);
+ t.add_cleanup(() => {
+ win.close();
+ });
+
+ win.onload = t.step_func_done(() => {
+ assert_equals(win.document.charset, 'UTF-8');
+ });
+}, 'Blob charset should override any auto-detected charset.');
+
+async_test(t => {
+ const blob = new Blob(
+ [`<!doctype html>\n<meta charset="ISO-8859-1">`],
+ {type: 'text/html;charset=utf-8'});
+ const url = URL.createObjectURL(blob);
+ const win = window.open(url);
+ t.add_cleanup(() => {
+ win.close();
+ });
+
+ win.onload = t.step_func_done(() => {
+ assert_equals(win.document.charset, 'UTF-8');
+ });
+}, 'Blob charset should override <meta charset>.');
diff --git a/testing/web-platform/tests/FileAPI/url/url-format.any.js b/testing/web-platform/tests/FileAPI/url/url-format.any.js
new file mode 100644
index 0000000000..69c51113e6
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/url-format.any.js
@@ -0,0 +1,70 @@
+// META: timeout=long
+const blob = new Blob(['test']);
+const file = new File(['test'], 'name');
+
+test(t => {
+ const url_count = 5000;
+ let list = [];
+
+ t.add_cleanup(() => {
+ for (let url of list) {
+ URL.revokeObjectURL(url);
+ }
+ });
+
+ for (let i = 0; i < url_count; ++i)
+ list.push(URL.createObjectURL(blob));
+
+ list.sort();
+
+ for (let i = 1; i < list.length; ++i)
+ assert_not_equals(list[i], list[i-1], 'generated Blob URLs should be unique');
+}, 'Generated Blob URLs are unique');
+
+test(() => {
+ const url = URL.createObjectURL(blob);
+ assert_equals(typeof url, 'string');
+ assert_true(url.startsWith('blob:'));
+}, 'Blob URL starts with "blob:"');
+
+test(() => {
+ const url = URL.createObjectURL(file);
+ assert_equals(typeof url, 'string');
+ assert_true(url.startsWith('blob:'));
+}, 'Blob URL starts with "blob:" for Files');
+
+test(() => {
+ const url = URL.createObjectURL(blob);
+ assert_equals(new URL(url).origin, location.origin);
+ if (location.origin !== 'null') {
+ assert_true(url.includes(location.origin));
+ assert_true(url.startsWith('blob:' + location.protocol));
+ }
+}, 'Origin of Blob URL matches our origin');
+
+test(() => {
+ const url = URL.createObjectURL(blob);
+ const url_record = new URL(url);
+ assert_equals(url_record.protocol, 'blob:');
+ assert_equals(url_record.origin, location.origin);
+ assert_equals(url_record.host, '', 'host should be an empty string');
+ assert_equals(url_record.port, '', 'port should be an empty string');
+ const uuid_path_re = /\/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
+ assert_true(uuid_path_re.test(url_record.pathname), 'Path must end with a valid UUID');
+ if (location.origin !== 'null') {
+ const nested_url = new URL(url_record.pathname);
+ assert_equals(nested_url.origin, location.origin);
+ assert_equals(nested_url.pathname.search(uuid_path_re), 0, 'Path must be a valid UUID');
+ assert_true(url.includes(location.origin));
+ assert_true(url.startsWith('blob:' + location.protocol));
+ }
+}, 'Blob URL parses correctly');
+
+test(() => {
+ const url = URL.createObjectURL(file);
+ assert_equals(new URL(url).origin, location.origin);
+ if (location.origin !== 'null') {
+ assert_true(url.includes(location.origin));
+ assert_true(url.startsWith('blob:' + location.protocol));
+ }
+}, 'Origin of Blob URL matches our origin for Files');
diff --git a/testing/web-platform/tests/FileAPI/url/url-in-tags-revoke.window.js b/testing/web-platform/tests/FileAPI/url/url-in-tags-revoke.window.js
new file mode 100644
index 0000000000..1cdad79f7e
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/url-in-tags-revoke.window.js
@@ -0,0 +1,115 @@
+// META: timeout=long
+async_test(t => {
+ const run_result = 'test_frame_OK';
+ const blob_contents = '<!doctype html>\n<meta charset="utf-8">\n' +
+ '<script>window.test_result = "' + run_result + '";</script>';
+ const blob = new Blob([blob_contents], {type: 'text/html'});
+ const url = URL.createObjectURL(blob);
+
+ const frame = document.createElement('iframe');
+ frame.setAttribute('src', url);
+ frame.setAttribute('style', 'display:none;');
+ document.body.appendChild(frame);
+ URL.revokeObjectURL(url);
+
+ frame.onload = t.step_func_done(() => {
+ assert_equals(frame.contentWindow.test_result, run_result);
+ });
+}, 'Fetching a blob URL immediately before revoking it works in an iframe.');
+
+async_test(t => {
+ const run_result = 'test_frame_OK';
+ const blob_contents = '<!doctype html>\n<meta charset="utf-8">\n' +
+ '<script>window.test_result = "' + run_result + '";</script>';
+ const blob = new Blob([blob_contents], {type: 'text/html'});
+ const url = URL.createObjectURL(blob);
+
+ const frame = document.createElement('iframe');
+ frame.setAttribute('src', '/common/blank.html');
+ frame.setAttribute('style', 'display:none;');
+ document.body.appendChild(frame);
+
+ frame.onload = t.step_func(() => {
+ frame.contentWindow.location = url;
+ URL.revokeObjectURL(url);
+ frame.onload = t.step_func_done(() => {
+ assert_equals(frame.contentWindow.test_result, run_result);
+ });
+ });
+}, 'Fetching a blob URL immediately before revoking it works in an iframe navigation.');
+
+async_test(t => {
+ const run_result = 'test_frame_OK';
+ const blob_contents = '<!doctype html>\n<meta charset="utf-8">\n' +
+ '<script>window.test_result = "' + run_result + '";</script>';
+ const blob = new Blob([blob_contents], {type: 'text/html'});
+ const url = URL.createObjectURL(blob);
+ const win = window.open(url);
+ URL.revokeObjectURL(url);
+ add_completion_callback(() => { win.close(); });
+
+ win.onload = t.step_func_done(() => {
+ assert_equals(win.test_result, run_result);
+ });
+}, 'Opening a blob URL in a new window immediately before revoking it works.');
+
+function receive_message_on_channel(t, channel_name) {
+ const channel = new BroadcastChannel(channel_name);
+ return new Promise(resolve => {
+ channel.addEventListener('message', t.step_func(e => {
+ resolve(e.data);
+ }));
+ });
+}
+
+function window_contents_for_channel(channel_name) {
+ return '<!doctype html>\n' +
+ '<script>\n' +
+ 'new BroadcastChannel("' + channel_name + '").postMessage("foobar");\n' +
+ 'self.close();\n' +
+ '</script>';
+}
+
+async_test(t => {
+ const channel_name = 'noopener-window-test';
+ const blob = new Blob([window_contents_for_channel(channel_name)], {type: 'text/html'});
+ receive_message_on_channel(t, channel_name).then(t.step_func_done(t => {
+ assert_equals(t, 'foobar');
+ }));
+ const url = URL.createObjectURL(blob);
+ const win = window.open();
+ win.opener = null;
+ win.location = url;
+ URL.revokeObjectURL(url);
+}, 'Opening a blob URL in a noopener about:blank window immediately before revoking it works.');
+
+async_test(t => {
+ const run_result = 'test_script_OK';
+ const blob_contents = 'window.script_test_result = "' + run_result + '";';
+ const blob = new Blob([blob_contents]);
+ const url = URL.createObjectURL(blob);
+
+ const e = document.createElement('script');
+ e.setAttribute('src', url);
+ e.onload = t.step_func_done(() => {
+ assert_equals(window.script_test_result, run_result);
+ });
+
+ document.body.appendChild(e);
+ URL.revokeObjectURL(url);
+}, 'Fetching a blob URL immediately before revoking it works in <script> tags.');
+
+async_test(t => {
+ const channel_name = 'a-click-test';
+ const blob = new Blob([window_contents_for_channel(channel_name)], {type: 'text/html'});
+ receive_message_on_channel(t, channel_name).then(t.step_func_done(t => {
+ assert_equals(t, 'foobar');
+ }));
+ const url = URL.createObjectURL(blob);
+ const anchor = document.createElement('a');
+ anchor.href = url;
+ anchor.target = '_blank';
+ document.body.appendChild(anchor);
+ anchor.click();
+ URL.revokeObjectURL(url);
+}, 'Opening a blob URL in a new window by clicking an <a> tag works immediately before revoking the URL.');
diff --git a/testing/web-platform/tests/FileAPI/url/url-in-tags.window.js b/testing/web-platform/tests/FileAPI/url/url-in-tags.window.js
new file mode 100644
index 0000000000..f20b359901
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/url-in-tags.window.js
@@ -0,0 +1,48 @@
+async_test(t => {
+ const run_result = 'test_script_OK';
+ const blob_contents = 'window.test_result = "' + run_result + '";';
+ const blob = new Blob([blob_contents]);
+ const url = URL.createObjectURL(blob);
+
+ const e = document.createElement('script');
+ e.setAttribute('src', url);
+ e.onload = t.step_func_done(() => {
+ assert_equals(window.test_result, run_result);
+ });
+
+ document.body.appendChild(e);
+}, 'Blob URLs can be used in <script> tags');
+
+async_test(t => {
+ const run_result = 'test_frame_OK';
+ const blob_contents = '<!doctype html>\n<meta charset="utf-8">\n' +
+ '<script>window.test_result = "' + run_result + '";</script>';
+ const blob = new Blob([blob_contents], {type: 'text/html'});
+ const url = URL.createObjectURL(blob);
+
+ const frame = document.createElement('iframe');
+ frame.setAttribute('src', url);
+ frame.setAttribute('style', 'display:none;');
+ document.body.appendChild(frame);
+
+ frame.onload = t.step_func_done(() => {
+ assert_equals(frame.contentWindow.test_result, run_result);
+ });
+}, 'Blob URLs can be used in iframes, and are treated same origin');
+
+async_test(t => {
+ const blob_contents = '<!doctype html>\n<meta charset="utf-8">\n' +
+ '<style>body { margin: 0; } .block { height: 5000px; }</style>\n' +
+ '<body>\n' +
+ '<a id="block1"></a><div class="block"></div>\n' +
+ '<a id="block2"></a><div class="block"></div>';
+ const blob = new Blob([blob_contents], {type: 'text/html'});
+ const url = URL.createObjectURL(blob);
+
+ const frame = document.createElement('iframe');
+ frame.setAttribute('src', url + '#block2');
+ document.body.appendChild(frame);
+ frame.contentWindow.onscroll = t.step_func_done(() => {
+ assert_equals(frame.contentWindow.scrollY, 5000);
+ });
+}, 'Blob URL fragment is implemented.');
diff --git a/testing/web-platform/tests/FileAPI/url/url-lifetime.html b/testing/web-platform/tests/FileAPI/url/url-lifetime.html
new file mode 100644
index 0000000000..ad5d667193
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/url-lifetime.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+promise_test(t => {
+ const blob_contents = 'test blob contents';
+ const blob = new Blob([blob_contents]);
+ const worker = new Worker('resources/create-helper.js');
+ let url;
+ return new Promise(resolve => {
+ worker.onmessage = e => resolve(e.data);
+ worker.postMessage({blob: blob});
+ }).then(data => {
+ url = data.url;
+ let result = fetch(url);
+ worker.terminate();
+ return result;
+ }).then(response => response.text()).then(text => {
+ assert_equals(text, blob_contents);
+ return new Promise(resolve => t.step_timeout(resolve, 100));
+ }).then(() => promise_rejects_js(t, TypeError, fetch(url)));
+}, 'Terminating worker revokes its URLs');
+
+promise_test(t => {
+ const blob_contents = 'test blob contents';
+ const blob = new Blob([blob_contents]);
+ const frame = document.createElement('iframe');
+ frame.setAttribute('style', 'display:none;');
+ frame.src = 'resources/create-helper.html';
+ document.body.appendChild(frame);
+
+ let url;
+ return new Promise(resolve => {
+ frame.onload = t.step_func(e => {
+ resolve(e);
+ });
+ }).then(e => {
+ frame.contentWindow.postMessage({blob: blob}, '*');
+ return new Promise(resolve => {
+ self.addEventListener('message', t.step_func(e => {
+ if (e.source === frame.contentWindow) resolve(e);
+ }));
+ });
+ }).then(e => {
+ url = e.data.url;
+ let fetch_result = fetch(url);
+ document.body.removeChild(frame);
+ return fetch_result;
+ }).then(response => response.text()).then(text => {
+ assert_equals(text, blob_contents);
+ return new Promise(resolve => t.step_timeout(resolve, 100));
+ }).then(() => promise_rejects_js(t, TypeError, fetch(url)));
+}, 'Removing an iframe revokes its URLs');
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/FileAPI/url/url-reload.window.js b/testing/web-platform/tests/FileAPI/url/url-reload.window.js
new file mode 100644
index 0000000000..d333b3a74a
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/url-reload.window.js
@@ -0,0 +1,36 @@
+function blob_url_reload_test(t, revoke_before_reload) {
+ const run_result = 'test_frame_OK';
+ const blob_contents = '<!doctype html>\n<meta charset="utf-8">\n' +
+ '<script>window.test_result = "' + run_result + '";</script>';
+ const blob = new Blob([blob_contents], {type: 'text/html'});
+ const url = URL.createObjectURL(blob);
+
+ const frame = document.createElement('iframe');
+ frame.setAttribute('src', url);
+ frame.setAttribute('style', 'display:none;');
+ document.body.appendChild(frame);
+
+ frame.onload = t.step_func(() => {
+ if (revoke_before_reload)
+ URL.revokeObjectURL(url);
+ assert_equals(frame.contentWindow.test_result, run_result);
+ frame.contentWindow.test_result = null;
+ frame.onload = t.step_func_done(() => {
+ assert_equals(frame.contentWindow.test_result, run_result);
+ });
+ // Slight delay before reloading to ensure revoke actually has had a chance
+ // to be processed.
+ t.step_timeout(() => {
+ frame.contentWindow.location.reload();
+ }, 250);
+ });
+}
+
+async_test(t => {
+ blob_url_reload_test(t, false);
+}, 'Reloading a blob URL succeeds.');
+
+
+async_test(t => {
+ blob_url_reload_test(t, true);
+}, 'Reloading a blob URL succeeds even if the URL was revoked.');
diff --git a/testing/web-platform/tests/FileAPI/url/url-with-fetch.any.js b/testing/web-platform/tests/FileAPI/url/url-with-fetch.any.js
new file mode 100644
index 0000000000..54e6a3da5a
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/url-with-fetch.any.js
@@ -0,0 +1,72 @@
+// META: script=resources/fetch-tests.js
+// META: script=/common/gc.js
+
+function fetch_should_succeed(test, request) {
+ return fetch(request).then(response => response.text());
+}
+
+function fetch_should_fail(test, url, method = 'GET') {
+ return promise_rejects_js(test, TypeError, fetch(url, {method: method}));
+}
+
+fetch_tests('fetch', fetch_should_succeed, fetch_should_fail);
+
+promise_test(t => {
+ const blob_contents = 'test blob contents';
+ const blob_type = 'image/png';
+ const blob = new Blob([blob_contents], {type: blob_type});
+ const url = URL.createObjectURL(blob);
+
+ return fetch(url).then(response => {
+ assert_equals(response.headers.get('Content-Type'), blob_type);
+ });
+}, 'fetch should return Content-Type from Blob');
+
+promise_test(t => {
+ const blob_contents = 'test blob contents';
+ const blob = new Blob([blob_contents]);
+ const url = URL.createObjectURL(blob);
+ const request = new Request(url);
+
+ // Revoke the object URL. Request should take a reference to the blob as
+ // soon as it receives it in open(), so the request succeeds even though we
+ // revoke the URL before calling fetch().
+ URL.revokeObjectURL(url);
+
+ return fetch_should_succeed(t, request).then(text => {
+ assert_equals(text, blob_contents);
+ });
+}, 'Revoke blob URL after creating Request, will fetch');
+
+promise_test(async t => {
+ const blob_contents = 'test blob contents';
+ const blob = new Blob([blob_contents]);
+ const url = URL.createObjectURL(blob);
+ let request = new Request(url);
+
+ // Revoke the object URL. Request should take a reference to the blob as
+ // soon as it receives it in open(), so the request succeeds even though we
+ // revoke the URL before calling fetch().
+ URL.revokeObjectURL(url);
+
+ request = request.clone();
+ await garbageCollect();
+
+ const text = await fetch_should_succeed(t, request);
+ assert_equals(text, blob_contents);
+}, 'Revoke blob URL after creating Request, then clone Request, will fetch');
+
+promise_test(function(t) {
+ const blob_contents = 'test blob contents';
+ const blob = new Blob([blob_contents]);
+ const url = URL.createObjectURL(blob);
+
+ const result = fetch_should_succeed(t, url).then(text => {
+ assert_equals(text, blob_contents);
+ });
+
+ // Revoke the object URL. fetch should have already resolved the blob URL.
+ URL.revokeObjectURL(url);
+
+ return result;
+}, 'Revoke blob URL after calling fetch, fetch should succeed');
diff --git a/testing/web-platform/tests/FileAPI/url/url-with-xhr.any.js b/testing/web-platform/tests/FileAPI/url/url-with-xhr.any.js
new file mode 100644
index 0000000000..29d83080ab
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/url-with-xhr.any.js
@@ -0,0 +1,68 @@
+// META: script=resources/fetch-tests.js
+
+function xhr_should_succeed(test, url) {
+ return new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+ xhr.open('GET', url);
+ xhr.onload = test.step_func(() => {
+ assert_equals(xhr.status, 200);
+ assert_equals(xhr.statusText, 'OK');
+ resolve(xhr.response);
+ });
+ xhr.onerror = () => reject('Got unexpected error event');
+ xhr.send();
+ });
+}
+
+function xhr_should_fail(test, url, method = 'GET') {
+ const xhr = new XMLHttpRequest();
+ xhr.open(method, url);
+ const result1 = new Promise((resolve, reject) => {
+ xhr.onload = () => reject('Got unexpected load event');
+ xhr.onerror = resolve;
+ });
+ const result2 = new Promise(resolve => {
+ xhr.onreadystatechange = test.step_func(() => {
+ if (xhr.readyState !== xhr.DONE) return;
+ assert_equals(xhr.status, 0);
+ resolve();
+ });
+ });
+ xhr.send();
+ return Promise.all([result1, result2]);
+}
+
+fetch_tests('XHR', xhr_should_succeed, xhr_should_fail);
+
+async_test(t => {
+ const blob_contents = 'test blob contents';
+ const blob_type = 'image/png';
+ const blob = new Blob([blob_contents], {type: blob_type});
+ const url = URL.createObjectURL(blob);
+ const xhr = new XMLHttpRequest();
+ xhr.open('GET', url);
+ xhr.onloadend = t.step_func_done(() => {
+ assert_equals(xhr.getResponseHeader('Content-Type'), blob_type);
+ });
+ xhr.send();
+}, 'XHR should return Content-Type from Blob');
+
+async_test(t => {
+ const blob_contents = 'test blob contents';
+ const blob = new Blob([blob_contents]);
+ const url = URL.createObjectURL(blob);
+ const xhr = new XMLHttpRequest();
+ xhr.open('GET', url);
+
+ // Revoke the object URL. XHR should take a reference to the blob as soon as
+ // it receives it in open(), so the request succeeds even though we revoke the
+ // URL before calling send().
+ URL.revokeObjectURL(url);
+
+ xhr.onload = t.step_func_done(() => {
+ assert_equals(xhr.response, blob_contents);
+ });
+ xhr.onerror = t.unreached_func('Got unexpected error event');
+
+ xhr.send();
+}, 'Revoke blob URL after open(), will fetch');
diff --git a/testing/web-platform/tests/FileAPI/url/url_createobjecturl_file-manual.html b/testing/web-platform/tests/FileAPI/url/url_createobjecturl_file-manual.html
new file mode 100644
index 0000000000..7ae32512e0
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/url_createobjecturl_file-manual.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FileAPI Test: Creating Blob URL with File</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="author" title="JunChen Xia" href="mailto:xjconlyme@gmail.com">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div>
+ <p>Test steps:</p>
+ <ol>
+ <li>Download <a href="/images/blue96x96.png">blue96x96.png</a> to local.</li>
+ <li>Select the local file (blue96x96.png) to run the test.</li>
+ </ol>
+</div>
+
+<form name="uploadData">
+ <input type="file" id="fileChooser">
+</form>
+
+<div id="log"></div>
+
+<script>
+ async_test(function(t) {
+ var fileInput = document.querySelector('#fileChooser');
+
+ fileInput.onchange = t.step_func(function(e) {
+ var blobURL, file = fileInput.files[0];
+
+ test(function() {
+ assert_true(file instanceof File, "FileList contains File");
+ }, "Check if FileList contains File");
+
+ test(function() {
+ blobURL = window.URL.createObjectURL(file);
+ assert_equals(typeof blobURL, "string", "Blob URL is type of string");
+ assert_equals(blobURL.indexOf("blob"), 0, "Blob URL's scheme is blob");
+ }, "Check if URL.createObjectURL(File) returns a Blob URL");
+
+ t.done();
+ });
+ });
+</script>
+
diff --git a/testing/web-platform/tests/FileAPI/url/url_createobjecturl_file_img-manual.html b/testing/web-platform/tests/FileAPI/url/url_createobjecturl_file_img-manual.html
new file mode 100644
index 0000000000..534c1de996
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/url_createobjecturl_file_img-manual.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FileAPI Test: Creating Blob URL with File as image source</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="author" title="JunChen Xia" href="mailto:xjconlyme@gmail.com">
+
+<div>
+ <p>Test steps:</p>
+ <ol>
+ <li>Download <a href="/images/blue96x96.png">blue96x96.png</a> to local.</li>
+ <li>Select the local file (blue96x96.png) to run the test.</li>
+ </ol>
+ <p>Pass/fail criteria:</p>
+ <p>Test passes if there is a filled blue square.</p>
+
+ <p><input type="file" accept="image/*" id="fileChooser"></p>
+ <p><img id="displayImage"></img></p>
+</div>
+
+<script>
+ var fileInput = document.querySelector("#fileChooser");
+ var img = document.querySelector("#displayImage");
+
+ fileInput.addEventListener("change", function(evt) {
+ img.src = window.URL.createObjectURL(fileInput.files[0]);
+ }, false);
+</script>
+
diff --git a/testing/web-platform/tests/FileAPI/url/url_xmlhttprequest_img-ref.html b/testing/web-platform/tests/FileAPI/url/url_xmlhttprequest_img-ref.html
new file mode 100644
index 0000000000..7d7390442d
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/url_xmlhttprequest_img-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FileAPI Reference File</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="author" title="JunChen Xia" href="mailto:xjconlyme@gmail.com">
+
+<p>Test passes if there is a filled blue square.</p>
+
+<p>
+ <img id="fileDisplay" src="/images/blue96x96.png">
+</p>
+
diff --git a/testing/web-platform/tests/FileAPI/url/url_xmlhttprequest_img.html b/testing/web-platform/tests/FileAPI/url/url_xmlhttprequest_img.html
new file mode 100644
index 0000000000..468dcb086d
--- /dev/null
+++ b/testing/web-platform/tests/FileAPI/url/url_xmlhttprequest_img.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>FileAPI Test: Creating Blob URL via XMLHttpRequest as image source</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="author" title="JunChen Xia" href="mailto:xjconlyme@gmail.com">
+<link rel="match" href="url_xmlhttprequest_img-ref.html">
+
+<p>Test passes if there is a filled blue square.</p>
+
+<p>
+ <img id="fileDisplay">
+</p>
+
+<script src="/common/reftest-wait.js"></script>
+<script>
+ var http = new XMLHttpRequest();
+ http.open("GET", "/images/blue96x96.png", true);
+ http.responseType = "blob";
+ http.onloadend = function() {
+ var fileDisplay = document.querySelector("#fileDisplay");
+ fileDisplay.src = window.URL.createObjectURL(http.response);
+ fileDisplay.onload = takeScreenshot;
+ };
+ http.send();
+</script>
+</html>