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.',
- ],
- },
- },
- } : {}),
};