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