Commit 8b080b9d0e9 for nodejs

commit 8b080b9d0e911485234a4745a2cc66d1808258ef
Author: Node.js GitHub Bot <github-bot@iojs.org>
Date:   Thu Oct 15 16:28:44 2026 +0000

    test: update WPT for FileAPI to 5878c7a7c0

    PR-URL: https://github.com/nodejs/node/pull/66776
    Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
    Reviewed-By: Aviv Keller <me@aviv.sh>
    Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
    Reviewed-By: James M Snell <jasnell@gmail.com>

diff --git a/test/fixtures/wpt/FileAPI/BlobURL/WEB_FEATURES.yml b/test/fixtures/wpt/FileAPI/BlobURL/WEB_FEATURES.yml
new file mode 100644
index 00000000000..69d4514d8f3
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/BlobURL/WEB_FEATURES.yml
@@ -0,0 +1,2 @@
+rules:
+- "**": [file]
diff --git a/test/fixtures/wpt/FileAPI/BlobURL/cross-partition-navigation.https.html b/test/fixtures/wpt/FileAPI/BlobURL/cross-partition-navigation.https.html
index a92e0f7a8a9..978370f8805 100644
--- a/test/fixtures/wpt/FileAPI/BlobURL/cross-partition-navigation.https.html
+++ b/test/fixtures/wpt/FileAPI/BlobURL/cross-partition-navigation.https.html
@@ -247,5 +247,52 @@ promise_test(t => {
   });
 }, "Blob URL area element click should enforce noopener for a cross-top-level-site navigation");

+const open_blob_url_window_via_svg_a_click = (blob_url) => `
+  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+  const a = document.createElementNS("http://www.w3.org/2000/svg", "a");
+  a.setAttribute("href", "${blob_url}");
+  a.setAttribute("target", "_blank");
+  a.setAttribute("rel", "opener");
+  const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
+  text.textContent = "click";
+  a.appendChild(text);
+  svg.appendChild(a);
+  document.body.appendChild(svg);
+  a.dispatchEvent(new MouseEvent("click", {bubbles: true}));
+`;
+
+// Tests blob URL SVG `<a target="_blank" rel="opener">` click for same and
+// cross partition iframes.
+promise_test(t => {
+  return new Promise(async (resolve, reject) => {
+    try {
+      // Creates same and cross partition iframes.
+      const noopener_response_queue = token();
+
+      const [cross_site_iframe_uuid, same_site_iframe_uuid] = await create_test_iframes(t, token());
+
+      const blob = new Blob([opener_check_frame_html(noopener_response_queue)], {type : "text/html"});
+      const blob_url = URL.createObjectURL(blob);
+
+      // Attempt to click blob URL in cross partition iframe.
+      await send(cross_site_iframe_uuid, open_blob_url_window_via_svg_a_click(blob_url));
+      const noopener_response_1 = await receive(noopener_response_queue);
+      if (noopener_response_1 !== opener_null_response) {
+        reject(`Blob URL page opener wasn't null in not-same-top-level-site iframe.`);
+      }
+
+      // Attempt to click blob URL in same partition iframe.
+      await send(same_site_iframe_uuid, open_blob_url_window_via_svg_a_click(blob_url));
+      const noopener_response_2 = await receive(noopener_response_queue);
+      if (noopener_response_2 !== opener_not_null_response) {
+        reject(`Blob URL page opener was null in same-top-level-site iframe`);
+      }
+      resolve();
+    } catch (e) {
+      reject(e);
+    }
+  });
+}, "Blob URL SVG <a> element click should enforce noopener for a cross-top-level-site navigation");
+
 </script>
 </body>
diff --git a/test/fixtures/wpt/FileAPI/BlobURL/opaque-origin.html b/test/fixtures/wpt/FileAPI/BlobURL/opaque-origin.html
new file mode 100644
index 00000000000..c17e902128a
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/BlobURL/opaque-origin.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<html>
+<head>
+<meta charset=utf-8>
+<title>Opaque origin blob URL created from workers</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+</head>
+<body>
+<script>
+
+promise_test(t => {
+  return new Promise((resolve) => {
+    const worker_code = `
+      const blob = new Blob([''], {type: 'text/javascript'});
+      const url = URL.createObjectURL(blob);
+      self.postMessage(url);
+    `;
+    const worker = new Worker("data:text/javascript," + encodeURIComponent(worker_code));
+    worker.onmessage = t.step_func(e => {
+      assert_regexp_match(e.data, /^blob:null\//, "Blob URL should have 'null' as serialized origin");
+      resolve();
+    });
+  });
+}, "Blob URL created from data URL dedicated worker should have 'null' origin");
+
+promise_test(t => {
+  return new Promise((resolve) => {
+    const worker_code = `
+      self.onconnect = e => {
+        const blob = new Blob([''], {type: 'text/javascript'});
+        const url = URL.createObjectURL(blob);
+        e.ports[0].postMessage(url);
+      };
+    `;
+    const worker = new SharedWorker("data:text/javascript," + encodeURIComponent(worker_code));
+    worker.port.onmessage = t.step_func(e => {
+      assert_regexp_match(e.data, /^blob:null\//, "Blob URL should have 'null' as serialized origin");
+      resolve();
+    });
+    worker.port.start();
+  });
+}, "Blob URL created from data URL shared worker should have 'null' origin");
+
+</script>
+</body>
+</html>
diff --git a/test/fixtures/wpt/FileAPI/FileReader/WEB_FEATURES.yml b/test/fixtures/wpt/FileAPI/FileReader/WEB_FEATURES.yml
new file mode 100644
index 00000000000..69d4514d8f3
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/FileReader/WEB_FEATURES.yml
@@ -0,0 +1,2 @@
+rules:
+- "**": [file]
diff --git a/test/fixtures/wpt/FileAPI/FileReaderSync.worker.js b/test/fixtures/wpt/FileAPI/FileReaderSync.worker.js
index 3d7a0222f31..372cc3c3850 100644
--- a/test/fixtures/wpt/FileAPI/FileReaderSync.worker.js
+++ b/test/fixtures/wpt/FileAPI/FileReaderSync.worker.js
@@ -28,7 +28,7 @@ test(() => {

 test(() => {
     var data = readerSync.readAsDataURL(empty_blob);
-    assert_equals(data.indexOf("data:"), 0);
+    assert_equals(data, "data:application/octet-stream;base64,");
 }, "readAsDataURL with empty blob");

 test(() => {
@@ -41,6 +41,12 @@ test(() => {
     assert_equals(data, "");
 }, "readAsBinaryString with empty blob");

+test(() => {
+    var data = readerSync.readAsBinaryString(new Blob(["σ"]));
+    assert_equals(data.length, 2, "The result length is 2");
+    assert_equals(data, "\xcf\x83", "The result is \xcf\x83");
+}, "readAsBinaryString with multi-byte UTF-8 char");
+
 test(() => {
     var data = readerSync.readAsArrayBuffer(blob);
     assert_true(data instanceof ArrayBuffer);
diff --git a/test/fixtures/wpt/FileAPI/WEB_FEATURES.yml b/test/fixtures/wpt/FileAPI/WEB_FEATURES.yml
index 8d279ceaaea..ea7c8227a28 100644
--- a/test/fixtures/wpt/FileAPI/WEB_FEATURES.yml
+++ b/test/fixtures/wpt/FileAPI/WEB_FEATURES.yml
@@ -1,3 +1,2 @@
-features:
-- name: file
-  files: "**"
+rules:
+- "*": [file]
diff --git a/test/fixtures/wpt/FileAPI/blob/Blob-array-buffer-data-isolation.html b/test/fixtures/wpt/FileAPI/blob/Blob-array-buffer-data-isolation.html
new file mode 100644
index 00000000000..1bb7f3b2d79
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/blob/Blob-array-buffer-data-isolation.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Blob data isolation from input ArrayBuffers</title>
+<link rel="help" href="https://w3c.github.io/FileAPI/#constructorBlob">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+// These tests verify that Blob correctly copies its input data, so that
+// subsequent modifications to the input do not affect the Blob's contents.
+// This is especially important to test with small buffers that might trigger
+// buffer pool optimizations in some implementations.
+
+function readBlobAsArrayBuffer(blob) {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.onload = () => resolve(reader.result);
+    reader.onerror = () => reject(reader.error);
+    reader.readAsArrayBuffer(blob);
+  });
+}
+
+promise_test(async () => {
+  const input = new Uint8Array([1, 2, 3, 4]);
+  const blob = new Blob([input]);
+
+  input[0] = 99;
+
+  const result = new Uint8Array(await readBlobAsArrayBuffer(blob));
+  assert_array_equals(result, [1, 2, 3, 4], "Blob data should not be affected by modifying the input Uint8Array");
+}, "Modifying a Uint8Array after Blob construction does not affect the Blob");
+
+promise_test(async () => {
+  const input = new Uint8Array([5, 6, 7, 8]);
+  const inputBuffer = input.buffer;
+  const blob = new Blob([inputBuffer]);
+
+  input[0] = 99;
+
+  const result = new Uint8Array(await readBlobAsArrayBuffer(blob));
+  assert_array_equals(result, [5, 6, 7, 8], "Blob data should not be affected by modifying the underlying ArrayBuffer");
+}, "Modifying an ArrayBuffer after Blob construction does not affect the Blob");
+
+promise_test(async () => {
+  // Test with a view into a larger buffer (common pattern that exercises offset handling)
+  const largerBuffer = new ArrayBuffer(16);
+  const view = new Uint8Array(largerBuffer, 4, 4);
+  view.set([10, 11, 12, 13]);
+
+  const blob = new Blob([view]);
+
+  view[0] = 99;
+
+  const result = new Uint8Array(await readBlobAsArrayBuffer(blob));
+  assert_array_equals(result, [10, 11, 12, 13], "Blob data should not be affected by modifying a typed array view");
+}, "Modifying a typed array view after Blob construction does not affect the Blob");
+
+promise_test(async () => {
+  // Very small buffer - most likely to hit buffer pool optimizations
+  const input = new Uint8Array([42]);
+  const blob = new Blob([input]);
+
+  input[0] = 0;
+
+  const result = new Uint8Array(await readBlobAsArrayBuffer(blob));
+  assert_array_equals(result, [42], "Blob data should not be affected even with single-byte input");
+}, "Modifying a single-byte Uint8Array after Blob construction does not affect the Blob");
+
+promise_test(async () => {
+  const blob = new Blob([new Uint8Array([1, 2, 3, 4])]);
+
+  const result1 = await readBlobAsArrayBuffer(blob);
+  new Uint8Array(result1)[0] = 99;
+
+  const result2 = new Uint8Array(await readBlobAsArrayBuffer(blob));
+  assert_array_equals(
+    result2,
+    [1, 2, 3, 4],
+    "Blob data should not be affected by modifying a previously-read ArrayBuffer"
+  );
+}, "Modifying a returned ArrayBuffer does not affect subsequent reads");
+</script>
diff --git a/test/fixtures/wpt/FileAPI/blob/Blob-constructor-detached-buffer.any.js b/test/fixtures/wpt/FileAPI/blob/Blob-constructor-detached-buffer.any.js
new file mode 100644
index 00000000000..1868199cfb8
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/blob/Blob-constructor-detached-buffer.any.js
@@ -0,0 +1,59 @@
+// META: title=Blob constructor with detached ArrayBuffer/ArrayBufferView
+// META: script=../support/Blob.js
+'use strict';
+
+function detachBuffer(buffer) {
+  // Transfer the buffer to detach it. Works in both window and worker contexts.
+  new MessageChannel().port1.postMessage(buffer, [buffer]);
+}
+
+test_blob(function() {
+  const buffer = new ArrayBuffer(4);
+  const view = new Uint8Array(buffer);
+  view[0] = 0x41;
+  view[1] = 0x42;
+  view[2] = 0x43;
+  view[3] = 0x44;
+  detachBuffer(buffer);
+  return new Blob([view]);
+}, {
+  expected: '',
+  type: '',
+  desc: 'Blob from a detached ArrayBufferView should be empty'
+});
+
+test_blob(function() {
+  const buffer = new ArrayBuffer(4);
+  const view = new Uint8Array(buffer, 1, 2); // offset=1, length=2
+  view[0] = 0x41;
+  view[1] = 0x42;
+  detachBuffer(buffer);
+  return new Blob([view]);
+}, {
+  expected: '',
+  type: '',
+  desc: 'Blob from a detached ArrayBufferView with offset should be empty'
+});
+
+test_blob(function() {
+  const buffer = new ArrayBuffer(4);
+  detachBuffer(buffer);
+  return new Blob([buffer]);
+}, {
+  expected: '',
+  type: '',
+  desc: 'Blob from a detached ArrayBuffer should be empty'
+});
+
+test_blob(function() {
+  const buffer = new ArrayBuffer(4);
+  const view = new Uint8Array(buffer);
+  view[0] = 0x41;
+  detachBuffer(buffer);
+  // Mix detached view with a string
+  return new Blob([view, 'hello']);
+}, {
+  expected: 'hello',
+  type: '',
+  desc: 'Blob from a detached ArrayBufferView mixed with string, detached part ignored'
+});
diff --git a/test/fixtures/wpt/FileAPI/blob/Blob-constructor-endings.any.js b/test/fixtures/wpt/FileAPI/blob/Blob-constructor-endings.any.js
new file mode 100644
index 00000000000..1057818328e
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/blob/Blob-constructor-endings.any.js
@@ -0,0 +1,35 @@
+// META: title=Blob constructor: endings option
+[
+  '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');
\ No newline at end of file
diff --git a/test/fixtures/wpt/FileAPI/blob/Blob-constructor-endings.html b/test/fixtures/wpt/FileAPI/blob/Blob-constructor-endings.html
index 04edd2a303b..a395ad78d2e 100644
--- a/test/fixtures/wpt/FileAPI/blob/Blob-constructor-endings.html
+++ b/test/fixtures/wpt/FileAPI/blob/Blob-constructor-endings.html
@@ -19,41 +19,6 @@ function readBlobAsPromise(blob) {
   });
 }

-[
-  '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},
diff --git a/test/fixtures/wpt/FileAPI/blob/Blob-constructor.any.js b/test/fixtures/wpt/FileAPI/blob/Blob-constructor.any.js
index 03155b99cb2..bdcc1941600 100644
--- a/test/fixtures/wpt/FileAPI/blob/Blob-constructor.any.js
+++ b/test/fixtures/wpt/FileAPI/blob/Blob-constructor.any.js
@@ -335,6 +335,8 @@ test_blob(function() {
   desc: "ArrayBuffer elements of the blobParts array should be supported."
 });

+var isLittleEndian = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
+
 test_blob(function() {
   return new Blob([
     new Uint8Array([0x50, 0x41, 0x53, 0x53]),
@@ -346,7 +348,7 @@ test_blob(function() {
     new Float32Array([0xD341500000])
   ]);
 }, {
-  expected: "PASSPASSPASSPASSPASSPASSPASS",
+  expected: isLittleEndian ? "PASSPASSPASSPASSPASSPASSPASS" : "PASSPASSAPSSAPSSSSAPSSAPSSAP",
   type: "",
   desc: "Passing typed arrays as elements of the blobParts array should work."
 });
@@ -355,20 +357,20 @@ test_blob(function() {
     new Float16Array([2.65625, 58.59375])
   ]);
 }, {
-  expected: "PASS",
+  expected: isLittleEndian ? "PASS" : "APSS",
   type: "",
   desc: "Passing a Float16Array as element of the blobParts array should work."
 });
 test_blob(function() {
+  // 0x535 3415053534150
+  // 0x535 = 0b010100110101 -> Sign = +, Exponent = 1333 - 1023 = 310
+  // 0x13415053534150 * 2**(-52)
+  // ==> 0x13415053534150 * 2**258 = 2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680
   return new Blob([
-    // 0x535 3415053534150
-    // 0x535 = 0b010100110101 -> Sign = +, Exponent = 1333 - 1023 = 310
-    // 0x13415053534150 * 2**(-52)
-    // ==> 0x13415053534150 * 2**258 = 2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680
     new Float64Array([2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680])
   ]);
 }, {
-  expected: "PASSPASS",
+  expected: isLittleEndian ? "PASSPASS" : "SSAPSSAP",
   type: "",
   desc: "Passing a Float64Array as element of the blobParts array should work."
 });
@@ -379,7 +381,7 @@ test_blob(function() {
     new BigUint64Array([BigInt("0x5353415053534150")])
   ]);
 }, {
-  expected: "PASSPASSPASSPASS",
+  expected: isLittleEndian ? "PASSPASSPASSPASS" : "SSAPSSAPSSAPSSAP",
   type: "",
   desc: "Passing BigInt typed arrays as elements of the blobParts array should work."
 });
@@ -528,3 +530,4 @@ type_tests.forEach(function(t) {
     assert_equals(b.type, t[2]);
   }, "Blob with type " + format_value(t[1]));
 });
+
diff --git a/test/fixtures/wpt/FileAPI/blob/Blob-textStream.any.js b/test/fixtures/wpt/FileAPI/blob/Blob-textStream.any.js
new file mode 100644
index 00000000000..7f22d8bb330
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/blob/Blob-textStream.any.js
@@ -0,0 +1,90 @@
+// META: title=Blob textStream
+// META: global=window,worker
+// META: script=../support/Blob.js
+'use strict';
+
+async function readAllChunks(stream) {
+  const reader = stream.getReader();
+  const chunks = [];
+  while (true) {
+    const {done, value} = await reader.read();
+    if (done) {
+      break;
+    }
+    chunks.push(value);
+  }
+  return chunks;
+}
+
+test(() => {
+  assert_true('textStream' in Blob.prototype, "textStream exists on Blob.prototype");
+  assert_equals(typeof Blob.prototype.textStream, "function", "Blob.prototype.textStream is a function");
+}, "textStream method existence");
+
+promise_test(async () => {
+  const blob = new Blob(["hello world"]);
+  const stream = blob.textStream();
+  assert_true(stream instanceof ReadableStream, "textStream() returns a ReadableStream");
+
+  const chunks = await readAllChunks(stream);
+  assert_greater_than(chunks.length, 0);
+  for (const chunk of chunks) {
+    assert_equals(typeof chunk, "string", "each chunk should be a string");
+  }
+  assert_equals(chunks.join(""), "hello world", "concatenated chunks match the blob content");
+}, "Blob.textStream() basic functionality");
+
+promise_test(async () => {
+  const blob = new Blob();
+  const stream = blob.textStream();
+  assert_true(stream instanceof ReadableStream);
+  const chunks = await readAllChunks(stream);
+  assert_equals(chunks.length, 0, "no chunks should be read from empty blob textStream");
+}, "Blob.textStream() with empty blob");
+
+promise_test(async () => {
+  const blob = new Blob(["hello ", "world"]);
+  const stream = blob.textStream();
+  const chunks = await readAllChunks(stream);
+  assert_equals(chunks.join(""), "hello world");
+}, "Blob.textStream() with multi-part blob");
+
+promise_test(async () => {
+  const buffer = new Uint8Array([0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00]); // "hello" in UTF-16LE
+  const blob = new Blob([buffer], {
+    type: "text/plain; charset=utf-16le"
+  });
+  const stream = blob.textStream();
+  const chunks = await readAllChunks(stream);
+  assert_equals(chunks.join(""), "h\0e\0l\0l\0o\0", "ignores charset=utf-16le type parameter");
+}, "Blob.textStream() ignores type charset (UTF-16LE)");
+
+promise_test(async () => {
+  const blob = new Blob([new TextEncoder().encode("hello")], {
+    type: "text/plain; charset=invalid-charset"
+  });
+  const stream = blob.textStream();
+  const chunks = await readAllChunks(stream);
+  assert_equals(chunks.join(""), "hello", "ignores invalid-charset type parameter and decodes as UTF-8");
+}, "Blob.textStream() ignores invalid type charset (invalid-charset)");
+
+promise_test(async () => {
+  const blob = new Blob([new TextEncoder().encode("é")], {
+    type: "text/plain; charset=iso-8859-1"
+  });
+  const stream = blob.textStream();
+  const chunks = await readAllChunks(stream);
+  assert_equals(chunks.join(""), "é", "ignores charset=iso-8859-1 type parameter and decodes as UTF-8");
+}, "Blob.textStream() ignores type charset (iso-8859-1)");
+
+promise_test(async () => {
+  const blob = new Blob(["hello"]);
+  const stream1 = blob.textStream();
+  const stream2 = blob.textStream();
+  assert_not_equals(stream1, stream2, "multiple calls return new streams");
+
+  const chunks1 = await readAllChunks(stream1);
+  const chunks2 = await readAllChunks(stream2);
+  assert_equals(chunks1.join(""), "hello");
+  assert_equals(chunks2.join(""), "hello");
+}, "Blob.textStream() can be called multiple times");
diff --git a/test/fixtures/wpt/FileAPI/blob/WEB_FEATURES.yml b/test/fixtures/wpt/FileAPI/blob/WEB_FEATURES.yml
new file mode 100644
index 00000000000..69d4514d8f3
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/blob/WEB_FEATURES.yml
@@ -0,0 +1,2 @@
+rules:
+- "**": [file]
diff --git a/test/fixtures/wpt/FileAPI/file/File-constructor-endings.any.js b/test/fixtures/wpt/FileAPI/file/File-constructor-endings.any.js
new file mode 100644
index 00000000000..ac0c0a45a65
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/file/File-constructor-endings.any.js
@@ -0,0 +1,35 @@
+// META: title=File constructor: endings option
+[
+  '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');
\ No newline at end of file
diff --git a/test/fixtures/wpt/FileAPI/file/File-constructor-endings.html b/test/fixtures/wpt/FileAPI/file/File-constructor-endings.html
index 1282b6c5ac2..ffbc9867e9b 100644
--- a/test/fixtures/wpt/FileAPI/file/File-constructor-endings.html
+++ b/test/fixtures/wpt/FileAPI/file/File-constructor-endings.html
@@ -19,41 +19,6 @@ function readBlobAsPromise(blob) {
   });
 }

-[
-  '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},
diff --git a/test/fixtures/wpt/FileAPI/file/File-lastModified.html b/test/fixtures/wpt/FileAPI/file/File-lastModified.html
new file mode 100644
index 00000000000..b61e1b9b4d9
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/file/File-lastModified.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>File's lastModified</title>
+<link rel=help href="https://w3c.github.io/FileAPI/#dfn-lastModified">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+promise_test(async t => {
+  const before = Date.now();
+  const file = new File([], "dummy");
+  const after = Date.now();
+  await t.step_wait(() => Date.now() > after, "The clock advances beyond construction");
+  assert_between_inclusive(file.lastModified, before, after);
+}, "lastModified defaults to the time of construction");
+</script>
diff --git a/test/fixtures/wpt/FileAPI/file/WEB_FEATURES.yml b/test/fixtures/wpt/FileAPI/file/WEB_FEATURES.yml
new file mode 100644
index 00000000000..69d4514d8f3
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/file/WEB_FEATURES.yml
@@ -0,0 +1,2 @@
+rules:
+- "**": [file]
diff --git a/test/fixtures/wpt/FileAPI/filelist-section/WEB_FEATURES.yml b/test/fixtures/wpt/FileAPI/filelist-section/WEB_FEATURES.yml
new file mode 100644
index 00000000000..841cbf85c27
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/filelist-section/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+rules:
+- filelist_multiple_selected_files-manual.html: [input-file-multiple]
+- "*": [file]
diff --git a/test/fixtures/wpt/FileAPI/reading-data-section/FileReader-multiple-reads.any.js b/test/fixtures/wpt/FileAPI/reading-data-section/FileReader-multiple-reads.any.js
index 4b19c69b425..25ff25b06d6 100644
--- a/test/fixtures/wpt/FileAPI/reading-data-section/FileReader-multiple-reads.any.js
+++ b/test/fixtures/wpt/FileAPI/reading-data-section/FileReader-multiple-reads.any.js
@@ -79,3 +79,66 @@ async_test(function() {
   });
   reader.readAsText(blob_1);
 }, 'test abort and restart in onloadstart event for readAsText');
+
+promise_test(async t => {
+  const reader = new FileReader();
+  const events = [];
+
+  for (const type of ['abort', 'load', 'loadend']) {
+    reader.addEventListener(type, () => events.push(type));
+  }
+
+  reader.addEventListener('error', t.unreached_func('Unexpected error event'));
+  reader.addEventListener('abort', t.step_func(() => {
+    reader.readAsText(new Blob(['second']));
+  }),
+                          {once: true});
+
+  const firstLoadstart = new Promise(resolve => {
+    reader.addEventListener('loadstart', resolve, {once: true});
+  });
+  const secondLoadend = new Promise(resolve => {
+    reader.addEventListener('loadend', t.step_func(() => {
+      if (reader.result === 'second') {
+        resolve();
+      }
+    }));
+  });
+
+  reader.readAsText(new Blob([new Uint8Array(1024 * 1024)]));
+  await firstLoadstart;
+  reader.abort();
+  await secondLoadend;
+
+  assert_array_equals(events, ['abort', 'load', 'loadend']);
+}, 'A new read in an abort handler suppresses the old read loadend');
+
+promise_test(async t => {
+  const reader = new FileReader();
+  const events = [];
+  let load_count = 0;
+
+  for (const type of ['load', 'loadend']) {
+    reader.addEventListener(type, () => events.push(type));
+  }
+
+  reader.addEventListener('error', t.unreached_func('Unexpected error event'));
+  reader.addEventListener('load', t.step_func(() => {
+    if (++load_count === 1) {
+      reader.readAsText(new Blob(['second']));
+    }
+  }));
+
+  const secondLoadend = new Promise(resolve => {
+    reader.addEventListener('loadend', t.step_func(() => {
+      if (reader.result === 'second') {
+        resolve();
+      }
+    }));
+  });
+
+  reader.readAsText(new Blob(['first']));
+  await secondLoadend;
+
+  assert_array_equals(events, ['load', 'load', 'loadend']);
+}, 'A new read in a load handler suppresses the old read loadend');
diff --git a/test/fixtures/wpt/FileAPI/reading-data-section/WEB_FEATURES.yml b/test/fixtures/wpt/FileAPI/reading-data-section/WEB_FEATURES.yml
new file mode 100644
index 00000000000..69d4514d8f3
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/reading-data-section/WEB_FEATURES.yml
@@ -0,0 +1,2 @@
+rules:
+- "**": [file]
diff --git a/test/fixtures/wpt/FileAPI/reading-data-section/filereader_abort.any.js b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_abort.any.js
index c778ae55bb5..2563dce5ec2 100644
--- a/test/fixtures/wpt/FileAPI/reading-data-section/filereader_abort.any.js
+++ b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_abort.any.js
@@ -36,3 +36,32 @@
               assert_equals(readerAbort.readyState, readerAbort.DONE);
             });
       }, "Aborting after read");
+
+    promise_test(t => {
+        var reader = new FileReader();
+
+        var eventWatcher = new EventWatcher(t, reader,
+            ['abort', 'loadstart', 'loadend', 'error', 'load']);
+
+        // First read: run to completion so the reader reaches the DONE state.
+        reader.readAsText(new Blob(["first read"]));
+        return eventWatcher.wait_for('loadstart')
+          .then(() => eventWatcher.wait_for('load'))
+          .then(() => eventWatcher.wait_for('loadend'))
+          .then(() => {
+              assert_equals(reader.readyState, reader.DONE);
+
+              // Reuse the same reader for a second read and abort it while it is
+              // still LOADING. 'abort' and 'loadend' are dispatched synchronously,
+              // so call wait_for before calling abort.
+              reader.readAsText(new Blob(["second read"]));
+              assert_equals(reader.readyState, reader.LOADING);
+              var nextEvent = eventWatcher.wait_for(['abort', 'loadend']);
+              reader.abort();
+              return nextEvent;
+            })
+          .then(() => {
+              assert_equals(reader.result, null);
+              assert_equals(reader.readyState, reader.DONE);
+            });
+      }, "Aborting a reused reader after a previous read completed");
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 00000000000..70ca365616d
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsDataURL.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FileAPI Test: FileReader.readAsDataURL</title>
+<link rel="help" href="https://w3c.github.io/FileAPI/#readAsDataURL">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+async_test(t => {
+  const blob = new Blob(["Hello"], {
+    type: "text/plain;charset=utf-8"
+  });
+
+  const fileReader = new FileReader();
+  fileReader.addEventListener("load", () => {
+    assert_equals(
+      fileReader.result,
+      "data:text/plain;charset=utf-8;base64,SGVsbG8="
+    );
+    t.done();
+  });
+
+  fileReader.readAsDataURL(blob);
+}, "readAsDataURL result for Blob with specified MIME type including charset");
+</script>
diff --git a/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsText_blob_type_charset.any.js b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsText_blob_type_charset.any.js
new file mode 100644
index 00000000000..3692fbc2928
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/reading-data-section/filereader_readAsText_blob_type_charset.any.js
@@ -0,0 +1,51 @@
+// META: title=FileAPI Test: readAsText uses the Blob type's charset when no encoding argument is given
+
+// Per the File API "read as text" algorithm, the encoding is determined in this
+// order: (1) the explicit `encoding` argument if it is a supported encoding;
+// (2) otherwise, if the blob's `type` has a `charset` parameter, that charset;
+// (3) otherwise UTF-8. These tests exercise step (2) with BOM-free data so that
+// the charset parameter is the only thing that can select the right decoder
+// (i.e. they are not accidentally satisfied by BOM sniffing).
+
+async_test(function() {
+    // 0x80 is the Euro sign (U+20AC) in windows-1252. As UTF-8 it is a lone
+    // continuation byte and decodes to U+FFFD.
+    var blob = new Blob([new Uint8Array([0x80])], { type: "text/plain;charset=windows-1252" });
+    var reader = new FileReader();
+
+    reader.onloadend = this.step_func_done(function() {
+        assert_equals(reader.result, "€",
+            "readAsText should honor the windows-1252 charset from the Blob type and decode 0x80 as U+20AC, not UTF-8 U+FFFD.");
+    });
+
+    reader.readAsText(blob);
+}, "readAsText() uses the charset parameter of the Blob's type (windows-1252)");
+
+async_test(function() {
+    // "héllo" in windows-1252: 0xE9 is 'é'. As UTF-8, the lone 0xE9 is an
+    // invalid lead byte and decodes to U+FFFD.
+    var blob = new Blob([new Uint8Array([0x68, 0xE9, 0x6C, 0x6C, 0x6F])], { type: "text/plain;charset=windows-1252" });
+    var reader = new FileReader();
+
+    reader.onloadend = this.step_func_done(function() {
+        assert_equals(reader.result, "héllo",
+            "readAsText should decode the body using the windows-1252 charset declared in the Blob type.");
+    });
+
+    reader.readAsText(blob);
+}, "readAsText() decodes a windows-1252 Blob body using the type's charset");
+
+async_test(function() {
+    // An explicit encoding argument must still win over the Blob type charset
+    // (step 1 of the algorithm). This already works in WebKit and guards against
+    // a fix that over-corrects by always preferring the type charset.
+    var blob = new Blob([new Uint8Array([0x80])], { type: "text/plain;charset=UTF-8" });
+    var reader = new FileReader();
+
+    reader.onloadend = this.step_func_done(function() {
+        assert_equals(reader.result, "€",
+            "An explicit encoding argument takes precedence over the Blob type's charset.");
+    });
+
+    reader.readAsText(blob, "windows-1252");
+}, "readAsText() encoding argument overrides the Blob type's charset");
diff --git a/test/fixtures/wpt/FileAPI/unicode.html b/test/fixtures/wpt/FileAPI/unicode.any.js
similarity index 61%
rename from test/fixtures/wpt/FileAPI/unicode.html
rename to test/fixtures/wpt/FileAPI/unicode.any.js
index ce3e3579d7c..841b937a67f 100644
--- a/test/fixtures/wpt/FileAPI/unicode.html
+++ b/test/fixtures/wpt/FileAPI/unicode.any.js
@@ -1,46 +1,31 @@
-<!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>
+// META: title=Blob/Unicode interaction: normalization and encoding
+
 '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);
+  const result = await blob.text();
   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);
+  const result = await file.text();
   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);
+  const result = await blob.text();
   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);
+  const result = await file.text();
   assert_equals(result, REPLACED, 'Unpaired surrogates should be replaced.');
 }, 'Test that unpaired surrogates are replaced by File constructor');
-
-</script>
diff --git a/test/fixtures/wpt/FileAPI/url/WEB_FEATURES.yml b/test/fixtures/wpt/FileAPI/url/WEB_FEATURES.yml
new file mode 100644
index 00000000000..69d4514d8f3
--- /dev/null
+++ b/test/fixtures/wpt/FileAPI/url/WEB_FEATURES.yml
@@ -0,0 +1,2 @@
+rules:
+- "**": [file]
diff --git a/test/fixtures/wpt/FileAPI/url/url-in-tags-revoke.window.js b/test/fixtures/wpt/FileAPI/url/url-in-tags-revoke.window.js
index 1cdad79f7e3..476daba6f1b 100644
--- a/test/fixtures/wpt/FileAPI/url/url-in-tags-revoke.window.js
+++ b/test/fixtures/wpt/FileAPI/url/url-in-tags-revoke.window.js
@@ -94,11 +94,30 @@ async_test(t => {
   e.onload = t.step_func_done(() => {
     assert_equals(window.script_test_result, run_result);
   });
+  e.onerror = t.unreached_func();

   document.body.appendChild(e);
   URL.revokeObjectURL(url);
 }, 'Fetching a blob URL immediately before revoking it works in <script> tags.');

+async_test(t => {
+  const run_result = 'test_script_OK';
+  const blob_contents = 'window.script_test_result = "' + run_result + '";';
+  const blob = new Blob([blob_contents], {type: "application/javascript"});
+  const url = URL.createObjectURL(blob);
+
+  const e = document.createElement('script');
+  e.setAttribute('src', url);
+  e.setAttribute('type', 'module');
+  e.onload = t.step_func_done(() => {
+    assert_equals(window.script_test_result, run_result);
+  });
+  e.onerror = t.unreached_func();
+
+  document.body.appendChild(e);
+  URL.revokeObjectURL(url);
+}, 'Fetching a blob URL immediately before revoking it works in module <script> tags.');
+
 async_test(t => {
   const channel_name = 'a-click-test';
   const blob = new Blob([window_contents_for_channel(channel_name)], {type: 'text/html'});
diff --git a/test/fixtures/wpt/FileAPI/url/url-in-tags.window.js b/test/fixtures/wpt/FileAPI/url/url-in-tags.window.js
index 8a722dcac82..e14c41a5c9a 100644
--- a/test/fixtures/wpt/FileAPI/url/url-in-tags.window.js
+++ b/test/fixtures/wpt/FileAPI/url/url-in-tags.window.js
@@ -16,10 +16,28 @@ async_test(t => {
   e.onload = t.step_func_done(() => {
     assert_equals(window.test_result, run_result);
   });
+  e.onerror = t.unreached_func();

   document.body.appendChild(e);
 }, 'Blob URLs can be used in <script> tags');

+async_test(t => {
+  const run_result = 'test_script_OK';
+  const blob_contents = 'window.test_result = "' + run_result + '";';
+  const blob = new Blob([blob_contents], {type: 'application/javascript'});
+  const url = URL.createObjectURL(blob);
+
+  const e = document.createElement('script');
+  e.setAttribute('src', url);
+  e.setAttribute('type', 'module');
+  e.onload = t.step_func_done(() => {
+    assert_equals(window.test_result, run_result);
+  });
+  e.onerror = t.unreached_func();
+
+  document.body.appendChild(e);
+}, 'Blob URLs can be used in module <script> tags');
+
 async_test(t => {
   const run_result = 'test_frame_OK';
   const blob_contents = '<!doctype html>\n<meta charset="utf-8">\n' +
diff --git a/test/fixtures/wpt/FileAPI/url/url_createobjecturl_file_img-manual.html b/test/fixtures/wpt/FileAPI/url/url_createobjecturl_file_img-manual.html
index 534c1de9968..36fbe136ec2 100644
--- a/test/fixtures/wpt/FileAPI/url/url_createobjecturl_file_img-manual.html
+++ b/test/fixtures/wpt/FileAPI/url/url_createobjecturl_file_img-manual.html
@@ -14,7 +14,7 @@
   <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>
+  <p><img id="displayImage"></p>
 </div>

 <script>
diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json
index 711d469cc36..743a05f94f8 100644
--- a/test/fixtures/wpt/versions.json
+++ b/test/fixtures/wpt/versions.json
@@ -28,7 +28,7 @@
     "path": "fetch/data-urls/resources"
   },
   "FileAPI": {
-    "commit": "7f5130188818b6c12c636491186b459ec2bf131f",
+    "commit": "5878c7a7c0201cef04904f098351825c5c671d3f",
     "path": "FileAPI"
   },
   "hr-time": {
diff --git a/test/wpt/status/FileAPI/blob.cjs b/test/wpt/status/FileAPI/blob.cjs
index c59516ca1dc..ff0f72445e9 100644
--- a/test/wpt/status/FileAPI/blob.cjs
+++ b/test/wpt/status/FileAPI/blob.cjs
@@ -23,17 +23,4 @@ module.exports = {
   'Blob-constructor-dom.window.js': {
     skip: 'Depends on DOM API',
   },
-  ...(os.endianness() === 'BE' ? {
-    'Blob-constructor.any.js': {
-      fail: {
-        note: 'The expected bytes assume a little-endian platform',
-        expected: [
-          'Passing typed arrays as elements of the blobParts array should work.',
-          'Passing a Float16Array as element of the blobParts array should work.',
-          'Passing a Float64Array as element of the blobParts array should work.',
-          'Passing BigInt typed arrays as elements of the blobParts array should work.',
-        ],
-      },
-    },
-  } : {}),
 };