Select the local file (blue-100x100.png) to run the test.
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsArrayBuffer.html b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsArrayBuffer.html
new file mode 100644
index 00000000000000..31001a51a0727f
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsArrayBuffer.html
@@ -0,0 +1,38 @@
+
+
+
+
+ FileAPI Test: filereader_readAsArrayBuffer
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsBinaryString.html b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsBinaryString.html
new file mode 100644
index 00000000000000..b550e4d0a96dc7
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsBinaryString.html
@@ -0,0 +1,32 @@
+
+
+FileAPI Test: filereader_readAsBinaryString
+
+
+
+
+
diff --git a/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsDataURL.html b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsDataURL.html
new file mode 100644
index 00000000000000..5bc39499a229d1
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsDataURL.html
@@ -0,0 +1,51 @@
+
+
+FileAPI Test: FileReader.readAsDataURL
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsText.html b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsText.html
new file mode 100644
index 00000000000000..7d639d0111473b
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsText.html
@@ -0,0 +1,51 @@
+
+
+
+
+ FileAPI Test: filereader_readAsText
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readystate.html b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readystate.html
new file mode 100644
index 00000000000000..1586b8995059f7
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readystate.html
@@ -0,0 +1,34 @@
+
+
+
+
+ FileAPI Test: filereader_readystate
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/FileAPI/reading-data-section/filereader_result.html b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_result.html
new file mode 100644
index 00000000000000..b80322ed424f83
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_result.html
@@ -0,0 +1,97 @@
+
+
+
+
+ FileAPI Test: filereader_result
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/FileAPI/reading-data-section/support/blue-100x100.png b/test/fixtures/wpt/FileAPI/reading-data-section/support/blue-100x100.png
new file mode 100644
index 00000000000000..b662fe18ec4797
Binary files /dev/null and b/test/fixtures/wpt/FileAPI/reading-data-section/support/blue-100x100.png differ
diff --git a/test/fixtures/wpt/FileAPI/support/Blob.js b/test/fixtures/wpt/FileAPI/support/Blob.js
new file mode 100644
index 00000000000000..04069acd3ccbe7
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/support/Blob.js
@@ -0,0 +1,70 @@
+'use strict'
+
+function 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");
+ });
+}
+
+function 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/test/fixtures/wpt/FileAPI/support/document-domain-setter.sub.html b/test/fixtures/wpt/FileAPI/support/document-domain-setter.sub.html
new file mode 100644
index 00000000000000..61aebdf326679c
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/support/document-domain-setter.sub.html
@@ -0,0 +1,7 @@
+
+Relevant/current/blob source page used as a test helper
+
+
diff --git a/test/fixtures/wpt/FileAPI/support/historical-serviceworker.js b/test/fixtures/wpt/FileAPI/support/historical-serviceworker.js
new file mode 100644
index 00000000000000..8bd89a23adb70f
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/FileAPI/support/incumbent.sub.html b/test/fixtures/wpt/FileAPI/support/incumbent.sub.html
new file mode 100644
index 00000000000000..63a81cd3281c46
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/support/incumbent.sub.html
@@ -0,0 +1,22 @@
+
+Incumbent page used as a test helper
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/FileAPI/support/send-file-form-helper.js b/test/fixtures/wpt/FileAPI/support/send-file-form-helper.js
new file mode 100644
index 00000000000000..d6adf21ec33795
--- /dev/null
+++ b/test/fixtures/wpt/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. ·) 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. ·) 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/test/fixtures/wpt/FileAPI/support/send-file-formdata-helper.js b/test/fixtures/wpt/FileAPI/support/send-file-formdata-helper.js
new file mode 100644
index 00000000000000..53572ef36c8d1b
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/support/send-file-formdata-helper.js
@@ -0,0 +1,97 @@
+"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 asName = fileBaseName.replace(/[\r\n"]/g, encodeURIComponent);
+ const expectedText = [
+ boundary,
+ 'Content-Disposition: form-data; name="filename"',
+ "",
+ fileBaseName,
+ boundary,
+ `Content-Disposition: form-data; name="${asName}"`,
+ "",
+ "filename",
+ boundary,
+ `Content-Disposition: form-data; name="file"; ` +
+ `filename="${asName}"`,
+ "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/test/fixtures/wpt/FileAPI/support/upload.txt b/test/fixtures/wpt/FileAPI/support/upload.txt
new file mode 100644
index 00000000000000..5ab2f8a4323aba
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/support/upload.txt
@@ -0,0 +1 @@
+Hello
\ No newline at end of file
diff --git a/test/fixtures/wpt/FileAPI/support/url-origin.html b/test/fixtures/wpt/FileAPI/support/url-origin.html
new file mode 100644
index 00000000000000..63755113915f9f
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/support/url-origin.html
@@ -0,0 +1,6 @@
+
+
diff --git a/test/fixtures/wpt/FileAPI/unicode.html b/test/fixtures/wpt/FileAPI/unicode.html
new file mode 100644
index 00000000000000..ce3e3579d7c2c7
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/unicode.html
@@ -0,0 +1,46 @@
+
+
+Blob/Unicode interaction: normalization and encoding
+
+
+
diff --git a/test/fixtures/wpt/FileAPI/url/cross-global-revoke.sub.html b/test/fixtures/wpt/FileAPI/url/cross-global-revoke.sub.html
new file mode 100644
index 00000000000000..21b8c5bb1986d5
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/url/cross-global-revoke.sub.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/fixtures/wpt/FileAPI/url/multi-global-origin-serialization.sub.html b/test/fixtures/wpt/FileAPI/url/multi-global-origin-serialization.sub.html
new file mode 100644
index 00000000000000..0052b26fa62130
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/url/multi-global-origin-serialization.sub.html
@@ -0,0 +1,26 @@
+
+
+Blob URL serialization (specifically the origin) in multi-global situations
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/FileAPI/url/resources/create-helper.html b/test/fixtures/wpt/FileAPI/url/resources/create-helper.html
new file mode 100644
index 00000000000000..fa6cf4e671e835
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/url/resources/create-helper.html
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/test/fixtures/wpt/FileAPI/url/resources/create-helper.js b/test/fixtures/wpt/FileAPI/url/resources/create-helper.js
new file mode 100644
index 00000000000000..e6344f700ced60
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/FileAPI/url/resources/fetch-tests.js b/test/fixtures/wpt/FileAPI/url/resources/fetch-tests.js
new file mode 100644
index 00000000000000..a81ea1e7b1de35
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/FileAPI/url/resources/revoke-helper.html b/test/fixtures/wpt/FileAPI/url/resources/revoke-helper.html
new file mode 100644
index 00000000000000..adf5a014a668d6
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/url/resources/revoke-helper.html
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/test/fixtures/wpt/FileAPI/url/resources/revoke-helper.js b/test/fixtures/wpt/FileAPI/url/resources/revoke-helper.js
new file mode 100644
index 00000000000000..c3e05b64b1a6c8
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/FileAPI/url/sandboxed-iframe.html b/test/fixtures/wpt/FileAPI/url/sandboxed-iframe.html
new file mode 100644
index 00000000000000..a52939a3eb297c
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/url/sandboxed-iframe.html
@@ -0,0 +1,32 @@
+
+
+FileAPI Test: Verify behavior of Blob URL in unique origins
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/FileAPI/url/unicode-origin.sub.html b/test/fixtures/wpt/FileAPI/url/unicode-origin.sub.html
new file mode 100644
index 00000000000000..2c4921c0344998
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/url/unicode-origin.sub.html
@@ -0,0 +1,23 @@
+
+
+FileAPI Test: Verify origin of Blob URL
+
+
+
+
diff --git a/test/fixtures/wpt/FileAPI/url/url-charset.window.js b/test/fixtures/wpt/FileAPI/url/url-charset.window.js
new file mode 100644
index 00000000000000..777709b64a50e5
--- /dev/null
+++ b/test/fixtures/wpt/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
+ //