diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/fetch/api/request | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/fetch/api/request')
55 files changed, 3374 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-frame.https.html b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-frame.https.html new file mode 100644 index 0000000000..f3f9f7856d --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-frame.https.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<title>Fetch destination tests for resources with no load event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> +let frame; +const kScope = 'resources/dummy.html?dest=frame'; + +// Set up the service worker and the frame. +promise_test(t => { + const kScript = 'resources/fetch-destination-worker-frame.js'; + return service_worker_unregister_and_register(t, kScript, kScope) + .then(registration => { + add_completion_callback(() => { + registration.unregister(); + }); + + return wait_for_state(t, registration.installing, 'activated'); + }); + }, 'Initialize global state'); + +var waitOnMessageFromSW = async t => { + await new Promise((resolve, reject) => { + navigator.serviceWorker.onmessage = t.step_func(event => { + if (event.data == "PASS") { + resolve(); + } else { + reject(); + } + }); + }).catch(() => {; + assert_unreached("Wrong destination."); + }); + t.add_cleanup(() => { frame.contentWindow.navigator.serviceWorker.onmessage = null; }); +} + +// Document destination +/////////////////////// +promise_test(async t => { + var f = document.createElement('frame'); + frame = f; + f.className = 'test-frame'; + f.src = kScope; + document.body.appendChild(f); + await waitOnMessageFromSW(t); + add_completion_callback(() => { f.remove(); }); +}, 'frame fetches with a "frame" Request.destination'); + +</script> diff --git a/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-iframe.https.html b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-iframe.https.html new file mode 100644 index 0000000000..1aa5a5613b --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-iframe.https.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<title>Fetch destination tests for resources with no load event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> +let frame; +const kScope = 'resources/dummy.html?dest=iframe'; + +// Set up the service worker and the frame. +promise_test(t => { + const kScript = 'resources/fetch-destination-worker-iframe.js'; + return service_worker_unregister_and_register(t, kScript, kScope) + .then(registration => { + add_completion_callback(() => { + registration.unregister(); + }); + + return wait_for_state(t, registration.installing, 'activated'); + }); + }, 'Initialize global state'); + +var waitOnMessageFromSW = async t => { + await new Promise((resolve, reject) => { + navigator.serviceWorker.onmessage = t.step_func(event => { + if (event.data == "PASS") { + resolve(); + } else { + reject(); + } + }); + }).catch(() => {; + assert_unreached("Wrong destination."); + }); + t.add_cleanup(() => { frame.contentWindow.navigator.serviceWorker.onmessage = null; }); +} + +// Document destination +/////////////////////// +promise_test(async t => { + var f = document.createElement('iframe'); + frame = f; + f.className = 'test-iframe'; + f.src = kScope; + document.body.appendChild(f); + await waitOnMessageFromSW(t); + add_completion_callback(() => { f.remove(); }); +}, 'iframe fetches with a "iframe" Request.destination'); + +</script> diff --git a/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-no-load-event.https.html b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-no-load-event.https.html new file mode 100644 index 0000000000..1778bf2581 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-no-load-event.https.html @@ -0,0 +1,124 @@ +<!DOCTYPE html> +<title>Fetch destination tests for resources with no load event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> +let frame; + +// Set up the service worker and the frame. +promise_test(t => { + const kScope = 'resources/'; + const kFrame = 'resources/empty.https.html'; + const kScript = 'resources/fetch-destination-worker-no-load-event.js'; + return service_worker_unregister_and_register(t, kScript, kScope) + .then(registration => { + add_completion_callback(() => { + registration.unregister(); + }); + + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(() => { + return with_iframe(kFrame); + }) + .then(f => { + frame = f; + add_completion_callback(() => { f.remove(); }); + }); + }, 'Initialize global state'); + +var waitOnMessageFromSW = async t => { + await new Promise((resolve, reject) => { + frame.contentWindow.navigator.serviceWorker.onmessage = t.step_func(event => { + if (event.data == "PASS") { + resolve(); + } else { + reject(); + } + }); + }).catch(() => {; + assert_unreached("Wrong destination."); + }); + t.add_cleanup(() => { frame.contentWindow.navigator.serviceWorker.onmessage = null; }); +} +// Actual tests + +// Image destination +//////////////////// + +// CSS background image - image destination +promise_test(async t => { + let node = frame.contentWindow.document.createElement("div"); + node.style = "background-image: url(dummy.png?t=bg2&dest=image)"; + frame.contentWindow.document.body.appendChild(node); + + await waitOnMessageFromSW(t); +}, 'Background image fetches with an "image" Request.destination'); + +// Font destination +/////////////////// + +// Font loading API - font destination +promise_test(async t => { + let font = new frame.contentWindow.FontFace("foo", "url(dummy.ttf?t=api&dest=font)"); + font.load(); + + await waitOnMessageFromSW(t); +}, 'Font loading API fetches with an "font" Request.destination'); + +// CSS font - font destination +promise_test(async t => { + let style = frame.contentWindow.document.createElement("style"); + style.innerHTML = "@font-face { font-family: foo; src: url(dummy.ttf?t=css&dest=font); }"; + style.innerHTML += "div {font-family: foo; }"; + let div = frame.contentWindow.document.createElement("div"); + div.innerHTML = "bar"; + frame.contentWindow.document.body.appendChild(style); + frame.contentWindow.document.body.appendChild(div); + + await waitOnMessageFromSW(t); +}, 'CSS font fetches with an "font" Request.destination'); + +// Empty string destination +/////////////////////////// + +// sendBeacon() - empty string destination +promise_test(async t => { + frame.contentWindow.navigator.sendBeacon("dummy?t=beacon&dest=", "foobar"); + + await waitOnMessageFromSW(t); +}, 'sendBeacon() fetches with an empty string Request.destination'); + +// Cache.add() - empty string destination +promise_test(async t => { + frame.contentWindow.caches.open("foo").then(cache => { + cache.add("dummy?t=cache&dest="); + }); + + await waitOnMessageFromSW(t); +}, 'Cache.add() fetches with an empty string Request.destination'); + +// script destination +///////////////////// + +// importScripts() - script destination +promise_test(async t => { + let worker = new frame.contentWindow.Worker("importer.js"); + + await waitOnMessageFromSW(t); +}, 'importScripts() fetches with a "script" Request.destination'); + +// style destination +///////////////////// +// @import - style destination +promise_test(async t => { + let node = frame.contentWindow.document.createElement("style"); + node.innerHTML = '@import url("dummy?t=import&dest=style")'; + frame.contentWindow.document.body.appendChild(node); + + await waitOnMessageFromSW(t); +}, '@import fetches with a "style" Request.destination'); + +</script> diff --git a/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-prefetch.https.html b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-prefetch.https.html new file mode 100644 index 0000000000..db99202df8 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-prefetch.https.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<title>Fetch destination test for prefetching</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/media.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> +let frame; + +// Set up the service worker and the frame. +promise_test(t => { + const kScope = 'resources/empty.https.html'; + const kScript = 'resources/fetch-destination-worker.js'; + return service_worker_unregister_and_register(t, kScript, kScope) + .then(registration => { + add_completion_callback(() => { + registration.unregister(); + }); + + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(() => { + return with_iframe(kScope); + }) + .then(f => { + frame = f; + add_completion_callback(() => { f.remove(); }); + }); + }, 'Initialize global state'); + +// HTMLLinkElement with rel=prefetch - empty string destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("link"); + node.rel = "prefetch"; + node.onload = resolve; + node.onerror = reject; + node.href = "dummy?dest="; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=prefetch fetches with an empty string Request.destination'); + +</script> diff --git a/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-worker.https.html b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-worker.https.html new file mode 100644 index 0000000000..5935c1ff31 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-worker.https.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<title>Fetch destination tests for resources with no load event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> +let frame; + +// Set up the service worker and the frame. +promise_test(t => { + const kScope = 'resources/dummy.html'; + const kScript = 'resources/fetch-destination-worker-no-load-event.js'; + return service_worker_unregister_and_register(t, kScript, kScope) + .then(registration => { + add_completion_callback(() => { + registration.unregister(); + }); + + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(() => { + return with_iframe(kScope); + }) + .then(f => { + frame = f; + add_completion_callback(() => { f.remove(); }); + }); + }, 'Initialize global state'); + +var waitOnMessageFromSW = async t => { + await new Promise((resolve, reject) => { + frame.contentWindow.navigator.serviceWorker.onmessage = t.step_func(event => { + if (event.data == "PASS") { + resolve(); + } else { + reject(); + } + }); + }).catch(() => {; + assert_unreached("Wrong destination."); + }); + t.add_cleanup(() => { frame.contentWindow.navigator.serviceWorker.onmessage = null; }); +} + +// worker destination +///////////////////// +promise_test(async t => { + // We can use an html file as we don't really care about the dedicated worker successfully loading. + let worker = new frame.contentWindow.Worker("dummy.html?t=worker&dest=worker"); + await waitOnMessageFromSW(t); +}, 'DedicatedWorker fetches with a "worker" Request.destination'); + +promise_test(async t => { + // We can use an html file as we don't really care about the shared worker successfully loading. + let worker = new frame.contentWindow.SharedWorker("dummy.html?t=sharedworker&dest=sharedworker"); + await waitOnMessageFromSW(t); +}, 'SharedWorker fetches with a "sharedworker" Request.destination'); + +</script> diff --git a/testing/web-platform/tests/fetch/api/request/destination/fetch-destination.https.html b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination.https.html new file mode 100644 index 0000000000..0094b0b6fe --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination.https.html @@ -0,0 +1,435 @@ +<!DOCTYPE html> +<title>Fetch destination tests</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/media.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> +let frame; + +// Set up the service worker and the frame. +promise_test(t => { + const kScope = 'resources/empty.https.html'; + const kScript = 'resources/fetch-destination-worker.js'; + return service_worker_unregister_and_register(t, kScript, kScope) + .then(registration => { + add_completion_callback(() => { + registration.unregister(); + }); + + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(() => { + return with_iframe(kScope); + }) + .then(f => { + frame = f; + add_completion_callback(() => { f.remove(); }); + }); + }, 'Initialize global state'); + +// Actual tests + +// Image destination +//////////////////// + +// HTMLImageElement - image destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("img"); + node.onload = resolve; + node.onerror = reject; + node.src = "dummy.png?dest=image"; + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLImageElement fetches with an "image" Request.destination'); + +// HTMLImageElement with srcset attribute - image destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("img"); + node.onload = resolve; + node.onerror = reject; + node.srcset = "dummy.png?t=srcset&dest=image"; + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLImageElement with srcset attribute fetches with an "image" Request.destination'); + +// HTMLImageElement with srcset attribute - image destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let img = frame.contentWindow.document.createElement("img"); + let picture = frame.contentWindow.document.createElement("picture"); + let source = frame.contentWindow.document.createElement("source"); + picture.appendChild(source); + picture.appendChild(img); + img.onload = resolve; + img.onerror = reject; + source.srcset = "dummy.png?t=picture&dest=image"; + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLImageElement with a HTMLPictureElement parent attribute fetches with an "image" Request.destination'); + +// SVGImageElement - image destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let svg = frame.contentWindow.document.createElementNS('http://www.w3.org/2000/svg','svg'); + svg.setAttributeNS('http://www.w3.org/2000/svg','xlink','http://www.w3.org/1999/xlink'); + let svgimg = frame.contentWindow.document.createElementNS('http://www.w3.org/2000/svg','image'); + svgimg.onload = resolve; + svgimg.onerror = reject; + svgimg.setAttributeNS('http://www.w3.org/1999/xlink','href','dummy.png?t=svg&dest=image'); + svg.appendChild(svgimg); + frame.contentWindow.document.documentElement.appendChild(svg); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'SVGImageElement fetches with an "image" Request.destination'); + +// Empty string destination +/////////////////////////// + +// fetch() - empty string destination +promise_test(async t => { + let response = await frame.contentWindow.fetch("dummy?dest="); + assert_true(response.ok); +}, 'fetch() fetches with an empty string Request.destination'); + +// XMLHttpRequest - empty string destination +promise_test(async t => { + let xhr; + await new Promise((resolve, reject) => { + xhr = new frame.contentWindow.XMLHttpRequest(); + xhr.onload = resolve; + xhr.onerror = reject; + xhr.open("GET", "dummy?t=xhr&dest="); + xhr.send(); + }).catch(() => { + assert_unreached("Fetch errored."); + }); + assert_equals(xhr.status, 200); +}, 'XMLHttpRequest() fetches with an empty string Request.destination'); + +// EventSource - empty string destination +promise_test(async t => { + let xhr; + await new Promise((resolve, reject) => { + eventSource = new frame.contentWindow.EventSource("dummy.es?t=eventsource&dest="); + eventSource.onopen = resolve; + eventSource.onerror = reject; + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'EventSource() fetches with an empty string Request.destination'); + +// HTMLAudioElement - audio destination +/////////////////////////////////////// +promise_test(async t => { + await new Promise((resolve, reject) => { + let audioURL = getAudioURI("dummy_audio"); + let node = frame.contentWindow.document.createElement("audio"); + node.onloadeddata = resolve; + node.onerror = reject; + node.src = audioURL + "?dest=audio"; + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLAudioElement fetches with an "audio" Request.destination'); + +// HTMLVideoElement - video destination +/////////////////////////////////////// +promise_test(async t => { + await new Promise((resolve, reject) => { + let videoURL = getVideoURI("dummy_video"); + let node = frame.contentWindow.document.createElement("video"); + node.onloadeddata = resolve; + node.onerror = reject; + node.src = videoURL + "?dest=video"; + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLVideoElement fetches with a "video" Request.destination'); + +// script destinations +////////////////////// + +// HTMLScriptElement - script destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("script"); + node.onload = resolve; + node.onerror = reject; + node.src = "dummy?dest=script"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLScriptElement fetches with a "script" Request.destination'); + +// audioworklet destination +////////////////////// +promise_test(async t => { + let audioContext = new frame.contentWindow.AudioContext(); + await audioContext.audioWorklet.addModule("dummy?dest=audioworklet"); +}, 'AudioWorklet module fetches with a "audioworklet" Request.destination'); + +// Style destination +//////////////////// + +// HTMLLinkElement with rel=stylesheet - style destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("link"); + node.rel = "stylesheet"; + node.onload = resolve; + node.onerror = reject; + node.href = "dummy?dest=style"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=stylesheet fetches with a "style" Request.destination'); + +// Preload tests +//////////////// +// HTMLLinkElement with rel=preload and as=fetch - empty string destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("link"); + node.rel = "preload"; + node.as = "fetch"; + if (node.as != "fetch") { + resolve(); + } + node.onload = resolve; + node.onerror = reject; + node.href = "dummy?t=2&dest="; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=preload and as=fetch fetches with an empty string Request.destination'); + +// HTMLLinkElement with rel=preload and as=style - style destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("link"); + node.rel = "preload"; + node.as = "style"; + if (node.as != "style") { + resolve(); + } + node.onload = resolve; + node.onerror = reject; + node.href = "dummy?t=2&dest=style"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=preload and as=style fetches with a "style" Request.destination'); + +// HTMLLinkElement with rel=preload and as=script - script destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("link"); + node.rel = "preload"; + node.as = "script"; + if (node.as != "script") { + resolve(); + } + node.onload = resolve; + node.onerror = reject; + node.href = "dummy?t=2&dest=script"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=preload and as=script fetches with a "script" Request.destination'); + +// HTMLLinkElement with rel=preload and as=font - font destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("link"); + node.rel = "preload"; + node.as = "font"; + if (node.as != "font") { + resolve(); + } + node.onload = resolve; + node.onerror = reject; + node.href = "dummy?t=2&dest=font"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=preload and as=font fetches with a "font" Request.destination'); + +// HTMLLinkElement with rel=preload and as=image - image destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("link"); + node.rel = "preload"; + node.as = "image"; + if (node.as != "image") { + resolve(); + } + node.onload = resolve; + node.onerror = reject; + node.href = "dummy.png?t=2&dest=image"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=preload and as=image fetches with a "image" Request.destination'); + +// HTMLLinkElement with rel=preload and as=audio - audio destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let audioURL = getAudioURI("dummy_audio"); + let node = frame.contentWindow.document.createElement("link"); + node.rel = "preload"; + node.as = "audio"; + if (node.as != "audio") { + resolve(); + } + node.onload = resolve; + node.onerror = reject; + node.href = audioURL + "?dest=audio"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=preload and as=audio fetches with a "audio" Request.destination'); + +// HTMLLinkElement with rel=preload and as=video - video destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let videoURL = getVideoURI("dummy_video"); + let node = frame.contentWindow.document.createElement("link"); + node.rel = "preload"; + node.as = "video"; + if (node.as != "video") { + resolve(); + } + node.onload = resolve; + node.onerror = reject; + node.href = videoURL + "?dest=video"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=preload and as=video fetches with a "video" Request.destination'); + +// HTMLLinkElement with rel=preload and as=track - track destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("link"); + node.rel = "preload"; + node.as = "track"; + if (node.as != "track") { + resolve(); + } + node.onload = resolve; + node.onerror = reject; + node.href = "dummy?dest=track"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=preload and as=track fetches with a "track" Request.destination'); + +// HTMLLinkElement with rel=preload and as=document - document destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("link"); + node.rel = "preload"; + node.as = "document"; + if (node.as != "document") { + resolve(); + } + node.onload = resolve; + node.onerror = reject; + node.href = "dummy?dest=document"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=preload and as=document fetches with a "document" Request.destination'); + +// HTMLLinkElement with rel=preload and as=worker - worker destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("link"); + node.rel = "preload"; + node.as = "worker"; + if (node.as != "worker") { + resolve(); + } + node.onload = resolve; + node.onerror = reject; + node.href = "dummy?dest=worker"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=preload and as=worker fetches with a "worker" Request.destination'); + +// HTMLLinkElement with rel=preload and as=sharedworker - sharedworker destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("link"); + node.rel = "preload"; + node.as = "sharedworker"; + if (node.as != "sharedworker") { + resolve(); + } + node.onload = resolve; + node.onerror = reject; + node.href = "dummy?dest=sharedworker"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=preload and as=sharedworker fetches with a "sharedworker" Request.destination'); + +// HTMLLinkElement with rel=preload and as=xslt - xslt destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("link"); + node.rel = "preload"; + node.as = "xslt"; + if (node.as != "xslt") { + resolve(); + } + node.onload = resolve; + node.onerror = reject; + node.href = "dummy?dest=xslt"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=preload and as=xslt fetches with a "xslt" Request.destination'); + +// HTMLLinkElement with rel=preload and as=manifest - manifest destination +promise_test(async t => { + await new Promise((resolve, reject) => { + let node = frame.contentWindow.document.createElement("link"); + node.rel = "preload"; + node.as = "manifest"; + if (node.as != "manifest") { + resolve(); + } + node.onload = resolve; + node.onerror = reject; + node.href = "dummy?dest=manifest"; + frame.contentWindow.document.body.appendChild(node); + }).catch(() => { + assert_unreached("Fetch errored."); + }); +}, 'HTMLLinkElement with rel=preload and as=manifest fetches with a "manifest" Request.destination'); + +</script> diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es.headers b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es.headers new file mode 100644 index 0000000000..9bb8badcad --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es.headers @@ -0,0 +1 @@ +Content-Type: text/event-stream diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.html b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.html new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.html diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.png b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.png Binary files differnew file mode 100644 index 0000000000..01c9666a8d --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.png diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.ttf b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.ttf Binary files differnew file mode 100644 index 0000000000..9023592ef5 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.ttf diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.mp3 b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.mp3 Binary files differnew file mode 100644 index 0000000000..0091330f1e --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.mp3 diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.oga b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.oga Binary files differnew file mode 100644 index 0000000000..239ad2bd08 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.oga diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.mp4 b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.mp4 Binary files differnew file mode 100644 index 0000000000..7022e75c15 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.mp4 diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.ogv b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.ogv Binary files differnew file mode 100644 index 0000000000..de99616ece --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.ogv diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/empty.https.html b/testing/web-platform/tests/fetch/api/request/destination/resources/empty.https.html new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/empty.https.html diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-frame.js b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-frame.js new file mode 100644 index 0000000000..b69de0b7df --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-frame.js @@ -0,0 +1,20 @@ +self.addEventListener('fetch', function(event) { + if (event.request.url.includes('dummy')) { + event.waitUntil(async function() { + let destination = new URL(event.request.url).searchParams.get("dest"); + let clients = await self.clients.matchAll({"includeUncontrolled": true}); + clients.forEach(function(client) { + if (client.url.includes("fetch-destination-frame")) { + if (event.request.destination == destination) { + client.postMessage("PASS"); + } else { + client.postMessage("FAIL"); + } + } + }) + }()); + } + event.respondWith(fetch(event.request)); +}); + + diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-iframe.js b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-iframe.js new file mode 100644 index 0000000000..76345839ea --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-iframe.js @@ -0,0 +1,20 @@ +self.addEventListener('fetch', function(event) { + if (event.request.url.includes('dummy')) { + event.waitUntil(async function() { + let destination = new URL(event.request.url).searchParams.get("dest"); + let clients = await self.clients.matchAll({"includeUncontrolled": true}); + clients.forEach(function(client) { + if (client.url.includes("fetch-destination-iframe")) { + if (event.request.destination == destination) { + client.postMessage("PASS"); + } else { + client.postMessage("FAIL"); + } + } + }) + }()); + } + event.respondWith(fetch(event.request)); +}); + + diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-no-load-event.js b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-no-load-event.js new file mode 100644 index 0000000000..a583b1272a --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-no-load-event.js @@ -0,0 +1,20 @@ +self.addEventListener('fetch', function(event) { + const url = event.request.url; + if (url.includes('dummy') && url.includes('?')) { + event.waitUntil(async function() { + let destination = new URL(url).searchParams.get("dest"); + var result = "FAIL"; + if (event.request.destination == destination || + (event.request.destination == "empty" && destination == "")) { + result = "PASS"; + } + let cl = await clients.matchAll({includeUncontrolled: true}); + for (i = 0; i < cl.length; i++) { + cl[i].postMessage(result); + } + }()) + } + event.respondWith(fetch(event.request)); +}); + + diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker.js b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker.js new file mode 100644 index 0000000000..904009c172 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker.js @@ -0,0 +1,12 @@ +self.addEventListener('fetch', function(event) { + if (event.request.url.includes('dummy')) { + let destination = new URL(event.request.url).searchParams.get("dest"); + if (event.request.destination == destination || + (event.request.destination == "empty" && destination == "")) { + event.respondWith(fetch(event.request)); + } else { + event.respondWith(Response.error()); + } + } +}); + diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/importer.js b/testing/web-platform/tests/fetch/api/request/destination/resources/importer.js new file mode 100644 index 0000000000..9568474d50 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/destination/resources/importer.js @@ -0,0 +1 @@ +importScripts("dummy?t=importScripts&dest=script"); diff --git a/testing/web-platform/tests/fetch/api/request/forbidden-method.any.js b/testing/web-platform/tests/fetch/api/request/forbidden-method.any.js new file mode 100644 index 0000000000..eb13f37f0b --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/forbidden-method.any.js @@ -0,0 +1,13 @@ +// META: global=window,worker + +// https://fetch.spec.whatwg.org/#forbidden-method +for (const method of [ + 'CONNECT', 'TRACE', 'TRACK', + 'connect', 'trace', 'track' + ]) { + test(function() { + assert_throws_js(TypeError, + function() { new Request('./', {method: method}); } + ); + }, 'Request() with a forbidden method ' + method + ' must throw.'); +} diff --git a/testing/web-platform/tests/fetch/api/request/multi-globals/current/current.html b/testing/web-platform/tests/fetch/api/request/multi-globals/current/current.html new file mode 100644 index 0000000000..9bb6e0bbf3 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/multi-globals/current/current.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<title>Current page used as a test helper</title> +<base href="success/"> diff --git a/testing/web-platform/tests/fetch/api/request/multi-globals/incumbent/incumbent.html b/testing/web-platform/tests/fetch/api/request/multi-globals/incumbent/incumbent.html new file mode 100644 index 0000000000..a885b8a0a7 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/multi-globals/incumbent/incumbent.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<title>Incumbent page used as a test helper</title> + +<iframe src="../current/current.html" id="c"></iframe> + +<script> +'use strict'; + +window.createRequest = (...args) => { + const current = document.querySelector('#c').contentWindow; + return new current.Request(...args); +}; + +</script> diff --git a/testing/web-platform/tests/fetch/api/request/multi-globals/url-parsing.html b/testing/web-platform/tests/fetch/api/request/multi-globals/url-parsing.html new file mode 100644 index 0000000000..df60e72507 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/multi-globals/url-parsing.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Request constructor URL parsing, with multiple globals in play</title> +<link rel="help" href="https://fetch.spec.whatwg.org/#dom-request"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- This is the entry global --> + +<iframe src="incumbent/incumbent.html"></iframe> + +<script> +'use strict'; + +const loadPromise = new Promise(resolve => { + window.addEventListener("load", () => resolve()); +}); + +promise_test(() => { + return loadPromise.then(() => { + const req = document.querySelector('iframe').contentWindow.createRequest("url"); + + assert_equals(req.url, new URL("current/success/url", location.href).href); + }); +}, "should parse the URL relative to the current settings object"); + +</script> diff --git a/testing/web-platform/tests/fetch/api/request/request-bad-port.any.js b/testing/web-platform/tests/fetch/api/request/request-bad-port.any.js new file mode 100644 index 0000000000..b0684d4be0 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-bad-port.any.js @@ -0,0 +1,92 @@ +// META: global=window,worker + +// list of bad ports according to +// https://fetch.spec.whatwg.org/#port-blocking +var BLOCKED_PORTS_LIST = [ + 1, // tcpmux + 7, // echo + 9, // discard + 11, // systat + 13, // daytime + 15, // netstat + 17, // qotd + 19, // chargen + 20, // ftp-data + 21, // ftp + 22, // ssh + 23, // telnet + 25, // smtp + 37, // time + 42, // name + 43, // nicname + 53, // domain + 69, // tftp + 77, // priv-rjs + 79, // finger + 87, // ttylink + 95, // supdup + 101, // hostriame + 102, // iso-tsap + 103, // gppitnp + 104, // acr-nema + 109, // pop2 + 110, // pop3 + 111, // sunrpc + 113, // auth + 115, // sftp + 117, // uucp-path + 119, // nntp + 123, // ntp + 135, // loc-srv / epmap + 137, // netbios-ns + 139, // netbios-ssn + 143, // imap2 + 161, // snmp + 179, // bgp + 389, // ldap + 427, // afp (alternate) + 465, // smtp (alternate) + 512, // print / exec + 513, // login + 514, // shell + 515, // printer + 526, // tempo + 530, // courier + 531, // chat + 532, // netnews + 540, // uucp + 548, // afp + 554, // rtsp + 556, // remotefs + 563, // nntp+ssl + 587, // smtp (outgoing) + 601, // syslog-conn + 636, // ldap+ssl + 989, // ftps-data + 990, // ftps + 993, // ldap+ssl + 995, // pop3+ssl + 1719, // h323gatestat + 1720, // h323hostcall + 1723, // pptp + 2049, // nfs + 3659, // apple-sasl + 4045, // lockd + 5060, // sip + 5061, // sips + 6000, // x11 + 6566, // sane-port + 6665, // irc (alternate) + 6666, // irc (alternate) + 6667, // irc (default) + 6668, // irc (alternate) + 6669, // irc (alternate) + 6697, // irc+tls + 10080, // amanda +]; + +BLOCKED_PORTS_LIST.map(function(a){ + promise_test(function(t){ + return promise_rejects_js(t, TypeError, fetch("http://example.com:" + a)) + }, 'Request on bad port ' + a + ' should throw TypeError.'); +}); diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-default-conditional.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-default-conditional.any.js new file mode 100644 index 0000000000..c5b2001cc8 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-cache-default-conditional.any.js @@ -0,0 +1,170 @@ +// META: global=window,worker +// META: title=Request cache - default with conditional requests +// META: timeout=long +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=request-cache.js + +var tests = [ + { + name: 'RequestCache "default" mode with an If-Modified-Since header (following a request without additional headers) is treated similarly to "no-store"', + state: "stale", + request_cache: ["default", "default"], + request_headers: [{}, {"If-Modified-Since": now.toGMTString()}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "default" mode with an If-Modified-Since header (following a request without additional headers) is treated similarly to "no-store"', + state: "fresh", + request_cache: ["default", "default"], + request_headers: [{}, {"If-Modified-Since": now.toGMTString()}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "default" mode with an If-Modified-Since header is treated similarly to "no-store"', + state: "stale", + request_cache: ["default", "default"], + request_headers: [{"If-Modified-Since": now.toGMTString()}, {}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [true, false], + }, + { + name: 'RequestCache "default" mode with an If-Modified-Since header is treated similarly to "no-store"', + state: "fresh", + request_cache: ["default", "default"], + request_headers: [{"If-Modified-Since": now.toGMTString()}, {}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [true, false], + }, + { + name: 'RequestCache "default" mode with an If-None-Match header (following a request without additional headers) is treated similarly to "no-store"', + state: "stale", + request_cache: ["default", "default"], + request_headers: [{}, {"If-None-Match": '"foo"'}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "default" mode with an If-None-Match header (following a request without additional headers) is treated similarly to "no-store"', + state: "fresh", + request_cache: ["default", "default"], + request_headers: [{}, {"If-None-Match": '"foo"'}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "default" mode with an If-None-Match header is treated similarly to "no-store"', + state: "stale", + request_cache: ["default", "default"], + request_headers: [{"If-None-Match": '"foo"'}, {}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [true, false], + }, + { + name: 'RequestCache "default" mode with an If-None-Match header is treated similarly to "no-store"', + state: "fresh", + request_cache: ["default", "default"], + request_headers: [{"If-None-Match": '"foo"'}, {}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [true, false], + }, + { + name: 'RequestCache "default" mode with an If-Unmodified-Since header (following a request without additional headers) is treated similarly to "no-store"', + state: "stale", + request_cache: ["default", "default"], + request_headers: [{}, {"If-Unmodified-Since": now.toGMTString()}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "default" mode with an If-Unmodified-Since header (following a request without additional headers) is treated similarly to "no-store"', + state: "fresh", + request_cache: ["default", "default"], + request_headers: [{}, {"If-Unmodified-Since": now.toGMTString()}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "default" mode with an If-Unmodified-Since header is treated similarly to "no-store"', + state: "stale", + request_cache: ["default", "default"], + request_headers: [{"If-Unmodified-Since": now.toGMTString()}, {}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [true, false], + }, + { + name: 'RequestCache "default" mode with an If-Unmodified-Since header is treated similarly to "no-store"', + state: "fresh", + request_cache: ["default", "default"], + request_headers: [{"If-Unmodified-Since": now.toGMTString()}, {}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [true, false], + }, + { + name: 'RequestCache "default" mode with an If-Match header (following a request without additional headers) is treated similarly to "no-store"', + state: "stale", + request_cache: ["default", "default"], + request_headers: [{}, {"If-Match": '"foo"'}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "default" mode with an If-Match header (following a request without additional headers) is treated similarly to "no-store"', + state: "fresh", + request_cache: ["default", "default"], + request_headers: [{}, {"If-Match": '"foo"'}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "default" mode with an If-Match header is treated similarly to "no-store"', + state: "stale", + request_cache: ["default", "default"], + request_headers: [{"If-Match": '"foo"'}, {}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [true, false], + }, + { + name: 'RequestCache "default" mode with an If-Match header is treated similarly to "no-store"', + state: "fresh", + request_cache: ["default", "default"], + request_headers: [{"If-Match": '"foo"'}, {}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [true, false], + }, + { + name: 'RequestCache "default" mode with an If-Range header (following a request without additional headers) is treated similarly to "no-store"', + state: "stale", + request_cache: ["default", "default"], + request_headers: [{}, {"If-Range": '"foo"'}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "default" mode with an If-Range header (following a request without additional headers) is treated similarly to "no-store"', + state: "fresh", + request_cache: ["default", "default"], + request_headers: [{}, {"If-Range": '"foo"'}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "default" mode with an If-Range header is treated similarly to "no-store"', + state: "stale", + request_cache: ["default", "default"], + request_headers: [{"If-Range": '"foo"'}, {}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [true, false], + }, + { + name: 'RequestCache "default" mode with an If-Range header is treated similarly to "no-store"', + state: "fresh", + request_cache: ["default", "default"], + request_headers: [{"If-Range": '"foo"'}, {}], + expected_validation_headers: [false, false], + expected_no_cache_headers: [true, false], + }, +]; +run_tests(tests); diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-default.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-default.any.js new file mode 100644 index 0000000000..dfa8369c9a --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-cache-default.any.js @@ -0,0 +1,39 @@ +// META: global=window,worker +// META: title=Request cache - default +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=request-cache.js + +var tests = [ + { + name: 'RequestCache "default" mode checks the cache for previously cached content and goes to the network for stale responses', + state: "stale", + request_cache: ["default", "default"], + expected_validation_headers: [false, true], + expected_no_cache_headers: [false, false], + }, + { + name: 'RequestCache "default" mode checks the cache for previously cached content and avoids going to the network if a fresh response exists', + state: "fresh", + request_cache: ["default", "default"], + expected_validation_headers: [false], + expected_no_cache_headers: [false], + }, + { + name: 'Responses with the "Cache-Control: no-store" header are not stored in the cache', + state: "stale", + cache_control: "no-store", + request_cache: ["default", "default"], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, false], + }, + { + name: 'Responses with the "Cache-Control: no-store" header are not stored in the cache', + state: "fresh", + cache_control: "no-store", + request_cache: ["default", "default"], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, false], + }, +]; +run_tests(tests); diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-force-cache.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-force-cache.any.js new file mode 100644 index 0000000000..00dce096c7 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-cache-force-cache.any.js @@ -0,0 +1,67 @@ +// META: global=window,worker +// META: title=Request cache - force-cache +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=request-cache.js + +var tests = [ + { + name: 'RequestCache "force-cache" mode checks the cache for previously cached content and avoid revalidation for stale responses', + state: "stale", + request_cache: ["default", "force-cache"], + expected_validation_headers: [false], + expected_no_cache_headers: [false], + }, + { + name: 'RequestCache "force-cache" mode checks the cache for previously cached content and avoid revalidation for fresh responses', + state: "fresh", + request_cache: ["default", "force-cache"], + expected_validation_headers: [false], + expected_no_cache_headers: [false], + }, + { + name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response is not found', + state: "stale", + request_cache: ["force-cache"], + expected_validation_headers: [false], + expected_no_cache_headers: [false], + }, + { + name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response is not found', + state: "fresh", + request_cache: ["force-cache"], + expected_validation_headers: [false], + expected_no_cache_headers: [false], + }, + { + name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response would vary', + state: "stale", + vary: "*", + request_cache: ["default", "force-cache"], + expected_validation_headers: [false, true], + expected_no_cache_headers: [false, false], + }, + { + name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response would vary', + state: "fresh", + vary: "*", + request_cache: ["default", "force-cache"], + expected_validation_headers: [false, true], + expected_no_cache_headers: [false, false], + }, + { + name: 'RequestCache "force-cache" stores the response in the cache if it goes to the network', + state: "stale", + request_cache: ["force-cache", "default"], + expected_validation_headers: [false, true], + expected_no_cache_headers: [false, false], + }, + { + name: 'RequestCache "force-cache" stores the response in the cache if it goes to the network', + state: "fresh", + request_cache: ["force-cache", "default"], + expected_validation_headers: [false], + expected_no_cache_headers: [false], + }, +]; +run_tests(tests); diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-no-cache.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-no-cache.any.js new file mode 100644 index 0000000000..41fc22baf2 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-cache-no-cache.any.js @@ -0,0 +1,25 @@ +// META: global=window,worker +// META: title=Request cache : no-cache +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=request-cache.js + +var tests = [ + { + name: 'RequestCache "no-cache" mode revalidates stale responses found in the cache', + state: "stale", + request_cache: ["default", "no-cache"], + expected_validation_headers: [false, true], + expected_no_cache_headers: [false, false], + expected_max_age_headers: [false, true], + }, + { + name: 'RequestCache "no-cache" mode revalidates fresh responses found in the cache', + state: "fresh", + request_cache: ["default", "no-cache"], + expected_validation_headers: [false, true], + expected_no_cache_headers: [false, false], + expected_max_age_headers: [false, true], + }, +]; +run_tests(tests); diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-no-store.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-no-store.any.js new file mode 100644 index 0000000000..9a28718bf2 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-cache-no-store.any.js @@ -0,0 +1,37 @@ +// META: global=window,worker +// META: title=Request cache - no store +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=request-cache.js + +var tests = [ + { + name: 'RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless', + state: "stale", + request_cache: ["default", "no-store"], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless', + state: "fresh", + request_cache: ["default", "no-store"], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "no-store" mode does not store the response in the cache', + state: "stale", + request_cache: ["no-store", "default"], + expected_validation_headers: [false, false], + expected_no_cache_headers: [true, false], + }, + { + name: 'RequestCache "no-store" mode does not store the response in the cache', + state: "fresh", + request_cache: ["no-store", "default"], + expected_validation_headers: [false, false], + expected_no_cache_headers: [true, false], + }, +]; +run_tests(tests); diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-only-if-cached.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-only-if-cached.any.js new file mode 100644 index 0000000000..1305787c7c --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-cache-only-if-cached.any.js @@ -0,0 +1,66 @@ +// META: global=window,dedicatedworker,sharedworker +// META: title=Request cache - only-if-cached +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=request-cache.js + +// FIXME: avoid mixed content requests to enable service worker global +var tests = [ + { + name: 'RequestCache "only-if-cached" mode checks the cache for previously cached content and avoids revalidation for stale responses', + state: "stale", + request_cache: ["default", "only-if-cached"], + expected_validation_headers: [false], + expected_no_cache_headers: [false] + }, + { + name: 'RequestCache "only-if-cached" mode checks the cache for previously cached content and avoids revalidation for fresh responses', + state: "fresh", + request_cache: ["default", "only-if-cached"], + expected_validation_headers: [false], + expected_no_cache_headers: [false] + }, + { + name: 'RequestCache "only-if-cached" mode checks the cache for previously cached content and does not go to the network if a cached response is not found', + state: "fresh", + request_cache: ["only-if-cached"], + response: ["error"], + expected_validation_headers: [], + expected_no_cache_headers: [] + }, + { + name: 'RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content', + state: "fresh", + request_cache: ["default", "only-if-cached"], + redirect: "same-origin", + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, false], + }, + { + name: 'RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content', + state: "stale", + request_cache: ["default", "only-if-cached"], + redirect: "same-origin", + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, false], + }, + { + name: 'RequestCache "only-if-cached" (with "same-origin") does not follow redirects across origins and rejects', + state: "fresh", + request_cache: ["default", "only-if-cached"], + redirect: "cross-origin", + response: [null, "error"], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, false], + }, + { + name: 'RequestCache "only-if-cached" (with "same-origin") does not follow redirects across origins and rejects', + state: "stale", + request_cache: ["default", "only-if-cached"], + redirect: "cross-origin", + response: [null, "error"], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, false], + }, +]; +run_tests(tests); diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-reload.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-reload.any.js new file mode 100644 index 0000000000..c7bfffb398 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-cache-reload.any.js @@ -0,0 +1,51 @@ +// META: global=window,worker +// META: title=Request cache - reload +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=request-cache.js + +var tests = [ + { + name: 'RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless', + state: "stale", + request_cache: ["default", "reload"], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless', + state: "fresh", + request_cache: ["default", "reload"], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, + { + name: 'RequestCache "reload" mode does store the response in the cache', + state: "stale", + request_cache: ["reload", "default"], + expected_validation_headers: [false, true], + expected_no_cache_headers: [true, false], + }, + { + name: 'RequestCache "reload" mode does store the response in the cache', + state: "fresh", + request_cache: ["reload", "default"], + expected_validation_headers: [false], + expected_no_cache_headers: [true], + }, + { + name: 'RequestCache "reload" mode does store the response in the cache even if a previous response is already stored', + state: "stale", + request_cache: ["default", "reload", "default"], + expected_validation_headers: [false, false, true], + expected_no_cache_headers: [false, true, false], + }, + { + name: 'RequestCache "reload" mode does store the response in the cache even if a previous response is already stored', + state: "fresh", + request_cache: ["default", "reload", "default"], + expected_validation_headers: [false, false], + expected_no_cache_headers: [false, true], + }, +]; +run_tests(tests); diff --git a/testing/web-platform/tests/fetch/api/request/request-cache.js b/testing/web-platform/tests/fetch/api/request/request-cache.js new file mode 100644 index 0000000000..f2fbecf496 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-cache.js @@ -0,0 +1,223 @@ +/** + * Each test is run twice: once using etag/If-None-Match and once with + * date/If-Modified-Since. Each test run gets its own URL and randomized + * content and operates independently. + * + * The test steps are run with request_cache.length fetch requests issued + * and their immediate results sanity-checked. The cache.py server script + * stashes an entry containing any If-None-Match, If-Modified-Since, Pragma, + * and Cache-Control observed headers for each request it receives. When + * the test fetches have run, this state is retrieved from cache.py and the + * expected_* lists are checked, including their length. + * + * This means that if a request_* fetch is expected to hit the cache and not + * touch the network, then there will be no entry for it in the expect_* + * lists. AKA (request_cache.length - expected_validation_headers.length) + * should equal the number of cache hits that didn't touch the network. + * + * Test dictionary keys: + * - state: required string that determines whether the Expires response for + * the fetched document should be set in the future ("fresh") or past + * ("stale"). + * - vary: optional string to be passed to the server for it to quote back + * in a Vary header on the response to us. + * - cache_control: optional string to be passed to the server for it to + * quote back in a Cache-Control header on the response to us. + * - redirect: optional string "same-origin" or "cross-origin". If + * provided, the server will issue an absolute redirect to the script on + * the same or a different origin, as appropriate. The redirected + * location is the script with the redirect parameter removed, so the + * content/state/etc. will be as if you hadn't specified a redirect. + * - request_cache: required array of cache modes to use (via `cache`). + * - request_headers: optional array of explicit fetch `headers` arguments. + * If provided, the server will log an empty dictionary for each request + * instead of the request headers it would normally log. + * - response: optional array of specialized response handling. Right now, + * "error" array entries indicate a network error response is expected + * which will reject with a TypeError. + * - expected_validation_headers: required boolean array indicating whether + * the server should have seen an If-None-Match/If-Modified-Since header + * in the request. + * - expected_no_cache_headers: required boolean array indicating whether + * the server should have seen Pragma/Cache-control:no-cache headers in + * the request. + * - expected_max_age_headers: optional boolean array indicating whether + * the server should have seen a Cache-Control:max-age=0 header in the + * request. + */ + +var now = new Date(); + +function base_path() { + return location.pathname.replace(/\/[^\/]*$/, '/'); +} +function make_url(uuid, id, value, content, info) { + var dates = { + fresh: new Date(now.getFullYear() + 1, now.getMonth(), now.getDay()).toGMTString(), + stale: new Date(now.getFullYear() - 1, now.getMonth(), now.getDay()).toGMTString(), + }; + var vary = ""; + if ("vary" in info) { + vary = "&vary=" + info.vary; + } + var cache_control = ""; + if ("cache_control" in info) { + cache_control = "&cache_control=" + info.cache_control; + } + var redirect = ""; + + var ignore_request_headers = ""; + if ("request_headers" in info) { + // Ignore the request headers that we send since they may be synthesized by the test. + ignore_request_headers = "&ignore"; + } + var url_sans_redirect = "resources/cache.py?token=" + uuid + + "&content=" + content + + "&" + id + "=" + value + + "&expires=" + dates[info.state] + + vary + cache_control + ignore_request_headers; + // If there's a redirect, the target is the script without any redirect at + // either the same domain or a different domain. + if ("redirect" in info) { + var host_info = get_host_info(); + var origin; + switch (info.redirect) { + case "same-origin": + origin = host_info['HTTP_ORIGIN']; + break; + case "cross-origin": + origin = host_info['HTTP_REMOTE_ORIGIN']; + break; + } + var redirected_url = origin + base_path() + url_sans_redirect; + return url_sans_redirect + "&redirect=" + encodeURIComponent(redirected_url); + } else { + return url_sans_redirect; + } +} +function expected_status(type, identifier, init) { + if (type == "date" && + init.headers && + init.headers["If-Modified-Since"] == identifier) { + // The server will respond with a 304 in this case. + return [304, "Not Modified"]; + } + return [200, "OK"]; +} +function expected_response_text(type, identifier, init, content) { + if (type == "date" && + init.headers && + init.headers["If-Modified-Since"] == identifier) { + // The server will respond with a 304 in this case. + return ""; + } + return content; +} +function server_state(uuid) { + return fetch("resources/cache.py?querystate&token=" + uuid) + .then(function(response) { + return response.text(); + }).then(function(text) { + // null will be returned if the server never received any requests + // for the given uuid. Normalize that to an empty list consistent + // with our representation. + return JSON.parse(text) || []; + }); +} +function make_test(type, info) { + return function(test) { + var uuid = token(); + var identifier = (type == "tag" ? Math.random() : now.toGMTString()); + var content = Math.random().toString(); + var url = make_url(uuid, type, identifier, content, info); + var fetch_functions = []; + for (var i = 0; i < info.request_cache.length; ++i) { + fetch_functions.push(function(idx) { + var init = {cache: info.request_cache[idx]}; + if ("request_headers" in info) { + init.headers = info.request_headers[idx]; + } + if (init.cache === "only-if-cached") { + // only-if-cached requires we use same-origin mode. + init.mode = "same-origin"; + } + return fetch(url, init) + .then(function(response) { + if ("response" in info && info.response[idx] === "error") { + assert_true(false, "fetch should have been an error"); + return; + } + assert_array_equals([response.status, response.statusText], + expected_status(type, identifier, init)); + return response.text(); + }).then(function(text) { + assert_equals(text, expected_response_text(type, identifier, init, content)); + }, function(reason) { + if ("response" in info && info.response[idx] === "error") { + assert_throws_js(TypeError, function() { throw reason; }); + } else { + throw reason; + } + }); + }); + } + var i = 0; + function run_next_step() { + if (fetch_functions.length) { + return fetch_functions.shift()(i++) + .then(run_next_step); + } else { + return Promise.resolve(); + } + } + return run_next_step() + .then(function() { + // Now, query the server state + return server_state(uuid); + }).then(function(state) { + var expectedState = []; + info.expected_validation_headers.forEach(function (validate) { + if (validate) { + if (type == "tag") { + expectedState.push({"If-None-Match": '"' + identifier + '"'}); + } else { + expectedState.push({"If-Modified-Since": identifier}); + } + } else { + expectedState.push({}); + } + }); + for (var i = 0; i < info.expected_no_cache_headers.length; ++i) { + if (info.expected_no_cache_headers[i]) { + expectedState[i]["Pragma"] = "no-cache"; + expectedState[i]["Cache-Control"] = "no-cache"; + } + } + if ("expected_max_age_headers" in info) { + for (var i = 0; i < info.expected_max_age_headers.length; ++i) { + if (info.expected_max_age_headers[i]) { + expectedState[i]["Cache-Control"] = "max-age=0"; + } + } + } + assert_equals(state.length, expectedState.length); + for (var i = 0; i < state.length; ++i) { + for (var header in state[i]) { + assert_equals(state[i][header], expectedState[i][header]); + delete expectedState[i][header]; + } + for (var header in expectedState[i]) { + assert_false(header in state[i]); + } + } + }); + }; +} + +function run_tests(tests) +{ + tests.forEach(function(info) { + promise_test(make_test("tag", info), info.name + " with Etag and " + info.state + " response"); + promise_test(make_test("date", info), info.name + " with Last-Modified and " + info.state + " response"); + }); +} diff --git a/testing/web-platform/tests/fetch/api/request/request-clone.sub.html b/testing/web-platform/tests/fetch/api/request/request-clone.sub.html new file mode 100644 index 0000000000..c690bb3dc0 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-clone.sub.html @@ -0,0 +1,63 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8"> + <title>Request clone</title> + <meta name="help" href="https://fetch.spec.whatwg.org/#request"> + <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="../resources/utils.js"></script> + </head> + <body> + <script> + var headers = new Headers({"name" : "value"}); + var emptyHeaders = new Headers(); + + var initValuesDict = {"method" : "POST", + "referrer" : "http://{{host}}:{{ports[http][0]}}/", + "referrerPolicy" : "origin", + "mode" : "same-origin", + "credentials" : "include", + "cache" : "no-cache", + "redirect" : "error", + "integrity" : "Request's Integrity", + "headers" : headers, + "body" : "Request's body" + }; + + var expectedInitialized = {"method" : "POST", + "referrer" : "http://{{host}}:{{ports[http][0]}}/", + "referrerPolicy" : "origin", + "mode" : "same-origin", + "credentials" : "include", + "cache" : "no-cache", + "redirect" : "error", + "integrity" : "Request's Integrity", + "headers" : headers, + "body" : "Request's body" + }; + + test(function() { + var RequestInitialized = new Request("", initValuesDict); + var requestToCheck = RequestInitialized.clone(); + checkRequest(requestToCheck, expectedInitialized); + }, "Check cloning a request"); + + test(function() { + var initialRequest = new Request("", {"headers" : new Headers({"a": "1", "b" : "2"})}); + var request = initialRequest.clone(); + assert_equals(request.headers.get("a"), "1", "cloned request should have header 'a'"); + assert_equals(request.headers.get("b"), "2", "cloned request should have header 'b'"); + + initialRequest.headers.delete("a"); + assert_equals(request.headers.get("a"), "1", "cloned request should still have header 'a'"); + + request.headers.delete("a"); + assert_equals(initialRequest.headers.get("b"), "2", "initial request should have header 'b'"); + + }, "Check cloning a request copies the headers"); + + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/api/request/request-consume-empty.any.js b/testing/web-platform/tests/fetch/api/request/request-consume-empty.any.js new file mode 100644 index 0000000000..034a86041a --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-consume-empty.any.js @@ -0,0 +1,101 @@ +// META: global=window,worker +// META: title=Request consume empty bodies + +function checkBodyText(test, request) { + return request.text().then(function(bodyAsText) { + assert_equals(bodyAsText, "", "Resolved value should be empty"); + assert_false(request.bodyUsed); + }); +} + +function checkBodyBlob(test, request) { + return request.blob().then(function(bodyAsBlob) { + var promise = new Promise(function(resolve, reject) { + var reader = new FileReader(); + reader.onload = function(evt) { + resolve(reader.result) + }; + reader.onerror = function() { + reject("Blob's reader failed"); + }; + reader.readAsText(bodyAsBlob); + }); + return promise.then(function(body) { + assert_equals(body, "", "Resolved value should be empty"); + assert_false(request.bodyUsed); + }); + }); +} + +function checkBodyArrayBuffer(test, request) { + return request.arrayBuffer().then(function(bodyAsArrayBuffer) { + assert_equals(bodyAsArrayBuffer.byteLength, 0, "Resolved value should be empty"); + assert_false(request.bodyUsed); + }); +} + +function checkBodyJSON(test, request) { + return request.json().then( + function(bodyAsJSON) { + assert_unreached("JSON parsing should fail"); + }, + function() { + assert_false(request.bodyUsed); + }); +} + +function checkBodyFormData(test, request) { + return request.formData().then(function(bodyAsFormData) { + assert_true(bodyAsFormData instanceof FormData, "Should receive a FormData"); + assert_false(request.bodyUsed); + }); +} + +function checkBodyFormDataError(test, request) { + return promise_rejects_js(test, TypeError, request.formData()).then(function() { + assert_false(request.bodyUsed); + }); +} + +function checkRequestWithNoBody(bodyType, checkFunction, headers = []) { + promise_test(function(test) { + var request = new Request("", {"method": "POST", "headers": headers}); + assert_false(request.bodyUsed); + return checkFunction(test, request); + }, "Consume request's body as " + bodyType); +} + +checkRequestWithNoBody("text", checkBodyText); +checkRequestWithNoBody("blob", checkBodyBlob); +checkRequestWithNoBody("arrayBuffer", checkBodyArrayBuffer); +checkRequestWithNoBody("json (error case)", checkBodyJSON); +checkRequestWithNoBody("formData with correct multipart type (error case)", checkBodyFormDataError, [["Content-Type", 'multipart/form-data; boundary="boundary"']]); +checkRequestWithNoBody("formData with correct urlencoded type", checkBodyFormData, [["Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"]]); +checkRequestWithNoBody("formData without correct type (error case)", checkBodyFormDataError); + +function checkRequestWithEmptyBody(bodyType, body, asText) { + promise_test(function(test) { + var request = new Request("", {"method": "POST", "body": body}); + assert_false(request.bodyUsed, "bodyUsed is false at init"); + if (asText) { + return request.text().then(function(bodyAsString) { + assert_equals(bodyAsString.length, 0, "Resolved value should be empty"); + assert_true(request.bodyUsed, "bodyUsed is true after being consumed"); + }); + } + return request.arrayBuffer().then(function(bodyAsArrayBuffer) { + assert_equals(bodyAsArrayBuffer.byteLength, 0, "Resolved value should be empty"); + assert_true(request.bodyUsed, "bodyUsed is true after being consumed"); + }); + }, "Consume empty " + bodyType + " request body as " + (asText ? "text" : "arrayBuffer")); +} + +// FIXME: Add BufferSource, FormData and URLSearchParams. +checkRequestWithEmptyBody("blob", new Blob([], { "type" : "text/plain" }), false); +checkRequestWithEmptyBody("text", "", false); +checkRequestWithEmptyBody("blob", new Blob([], { "type" : "text/plain" }), true); +checkRequestWithEmptyBody("text", "", true); +checkRequestWithEmptyBody("URLSearchParams", new URLSearchParams(""), true); +// FIXME: This test assumes that the empty string be returned but it is not clear whether that is right. See https://github.com/web-platform-tests/wpt/pull/3950. +checkRequestWithEmptyBody("FormData", new FormData(), true); +checkRequestWithEmptyBody("ArrayBuffer", new ArrayBuffer(), true); diff --git a/testing/web-platform/tests/fetch/api/request/request-consume.any.js b/testing/web-platform/tests/fetch/api/request/request-consume.any.js new file mode 100644 index 0000000000..aff5d65244 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-consume.any.js @@ -0,0 +1,145 @@ +// META: global=window,worker +// META: title=Request consume +// META: script=../resources/utils.js + +function checkBodyText(request, expectedBody) { + return request.text().then(function(bodyAsText) { + assert_equals(bodyAsText, expectedBody, "Retrieve and verify request's body"); + assert_true(request.bodyUsed, "body as text: bodyUsed turned true"); + }); +} + +function checkBodyBlob(request, expectedBody, checkContentType) { + return request.blob().then(function(bodyAsBlob) { + if (checkContentType) + assert_equals(bodyAsBlob.type, "text/plain", "Blob body type should be computed from the request Content-Type"); + + var promise = new Promise(function (resolve, reject) { + var reader = new FileReader(); + reader.onload = function(evt) { + resolve(reader.result) + }; + reader.onerror = function() { + reject("Blob's reader failed"); + }; + reader.readAsText(bodyAsBlob); + }); + return promise.then(function(body) { + assert_equals(body, expectedBody, "Retrieve and verify request's body"); + assert_true(request.bodyUsed, "body as blob: bodyUsed turned true"); + }); + }); +} + +function checkBodyArrayBuffer(request, expectedBody) { + return request.arrayBuffer().then(function(bodyAsArrayBuffer) { + validateBufferFromString(bodyAsArrayBuffer, expectedBody, "Retrieve and verify request's body"); + assert_true(request.bodyUsed, "body as arrayBuffer: bodyUsed turned true"); + }); +} + +function checkBodyJSON(request, expectedBody) { + return request.json().then(function(bodyAsJSON) { + var strBody = JSON.stringify(bodyAsJSON) + assert_equals(strBody, expectedBody, "Retrieve and verify request's body"); + assert_true(request.bodyUsed, "body as json: bodyUsed turned true"); + }); +} + +function checkBodyFormData(request, expectedBody) { + return request.formData().then(function(bodyAsFormData) { + assert_true(bodyAsFormData instanceof FormData, "Should receive a FormData"); + assert_true(request.bodyUsed, "body as formData: bodyUsed turned true"); + }); +} + +function checkRequestBody(body, expected, bodyType) { + promise_test(function(test) { + var request = new Request("", {"method": "POST", "body": body, "headers": [["Content-Type", "text/PLAIN"]] }); + assert_false(request.bodyUsed, "bodyUsed is false at init"); + return checkBodyText(request, expected); + }, "Consume " + bodyType + " request's body as text"); + promise_test(function(test) { + var request = new Request("", {"method": "POST", "body": body }); + assert_false(request.bodyUsed, "bodyUsed is false at init"); + return checkBodyBlob(request, expected); + }, "Consume " + bodyType + " request's body as blob"); + promise_test(function(test) { + var request = new Request("", {"method": "POST", "body": body }); + assert_false(request.bodyUsed, "bodyUsed is false at init"); + return checkBodyArrayBuffer(request, expected); + }, "Consume " + bodyType + " request's body as arrayBuffer"); + promise_test(function(test) { + var request = new Request("", {"method": "POST", "body": body }); + assert_false(request.bodyUsed, "bodyUsed is false at init"); + return checkBodyJSON(request, expected); + }, "Consume " + bodyType + " request's body as JSON"); +} + +var textData = JSON.stringify("This is response's body"); +var blob = new Blob([textData], { "type" : "text/plain" }); + +checkRequestBody(textData, textData, "String"); + +var string = "\"123456\""; +function getArrayBuffer() { + var arrayBuffer = new ArrayBuffer(8); + var int8Array = new Int8Array(arrayBuffer); + for (var cptr = 0; cptr < 8; cptr++) + int8Array[cptr] = string.charCodeAt(cptr); + return arrayBuffer; +} + +function getArrayBufferWithZeros() { + var arrayBuffer = new ArrayBuffer(10); + var int8Array = new Int8Array(arrayBuffer); + for (var cptr = 0; cptr < 8; cptr++) + int8Array[cptr + 1] = string.charCodeAt(cptr); + return arrayBuffer; +} + +checkRequestBody(getArrayBuffer(), string, "ArrayBuffer"); +checkRequestBody(new Uint8Array(getArrayBuffer()), string, "Uint8Array"); +checkRequestBody(new Int8Array(getArrayBufferWithZeros(), 1, 8), string, "Int8Array"); +checkRequestBody(new Float32Array(getArrayBuffer()), string, "Float32Array"); +checkRequestBody(new DataView(getArrayBufferWithZeros(), 1, 8), string, "DataView"); + +promise_test(function(test) { + var formData = new FormData(); + formData.append("name", "value") + var request = new Request("", {"method": "POST", "body": formData }); + assert_false(request.bodyUsed, "bodyUsed is false at init"); + return checkBodyFormData(request, formData); +}, "Consume FormData request's body as FormData"); + +function checkBlobResponseBody(blobBody, blobData, bodyType, checkFunction) { + promise_test(function(test) { + var response = new Response(blobBody); + assert_false(response.bodyUsed, "bodyUsed is false at init"); + return checkFunction(response, blobData); + }, "Consume blob response's body as " + bodyType); +} + +checkBlobResponseBody(blob, textData, "blob", checkBodyBlob); +checkBlobResponseBody(blob, textData, "text", checkBodyText); +checkBlobResponseBody(blob, textData, "json", checkBodyJSON); +checkBlobResponseBody(blob, textData, "arrayBuffer", checkBodyArrayBuffer); +checkBlobResponseBody(new Blob([""]), "", "blob (empty blob as input)", checkBodyBlob); + +var goodJSONValues = ["null", "1", "true", "\"string\""]; +goodJSONValues.forEach(function(value) { + promise_test(function(test) { + var request = new Request("", {"method": "POST", "body": value}); + return request.json().then(function(v) { + assert_equals(v, JSON.parse(value)); + }); + }, "Consume JSON from text: '" + JSON.stringify(value) + "'"); +}); + +var badJSONValues = ["undefined", "{", "a", "["]; +badJSONValues.forEach(function(value) { + promise_test(function(test) { + var request = new Request("", {"method": "POST", "body": value}); + return promise_rejects_js(test, SyntaxError, request.json()); + }, "Trying to consume bad JSON text as JSON: '" + value + "'"); +}); diff --git a/testing/web-platform/tests/fetch/api/request/request-disturbed.any.js b/testing/web-platform/tests/fetch/api/request/request-disturbed.any.js new file mode 100644 index 0000000000..8a11de78ff --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-disturbed.any.js @@ -0,0 +1,109 @@ +// META: global=window,worker +// META: title=Request disturbed +// META: script=../resources/utils.js + +var initValuesDict = {"method" : "POST", + "body" : "Request's body" +}; + +var noBodyConsumed = new Request(""); +var bodyConsumed = new Request("", initValuesDict); + +test(() => { + assert_equals(noBodyConsumed.body, null, "body's default value is null"); + assert_false(noBodyConsumed.bodyUsed , "bodyUsed is false when request is not disturbed"); + assert_not_equals(bodyConsumed.body, null, "non-null body"); + assert_true(bodyConsumed.body instanceof ReadableStream, "non-null body type"); + assert_false(noBodyConsumed.bodyUsed, "bodyUsed is false when request is not disturbed"); +}, "Request's body: initial state"); + +noBodyConsumed.blob(); +bodyConsumed.blob(); + +test(function() { + assert_false(noBodyConsumed.bodyUsed , "bodyUsed is false when request is not disturbed"); + try { + noBodyConsumed.clone(); + } catch (e) { + assert_unreached("Can use request not disturbed for creating or cloning request"); + } +}, "Request without body cannot be disturbed"); + +test(function() { + assert_true(bodyConsumed.bodyUsed , "bodyUsed is true when request is disturbed"); + assert_throws_js(TypeError, function() { bodyConsumed.clone(); }); +}, "Check cloning a disturbed request"); + +test(function() { + assert_true(bodyConsumed.bodyUsed , "bodyUsed is true when request is disturbed"); + assert_throws_js(TypeError, function() { new Request(bodyConsumed); }); +}, "Check creating a new request from a disturbed request"); + +promise_test(function() { + assert_true(bodyConsumed.bodyUsed , "bodyUsed is true when request is disturbed"); + const originalBody = bodyConsumed.body; + const bodyReplaced = new Request(bodyConsumed, { body: "Replaced body" }); + assert_not_equals(bodyReplaced.body, originalBody, "new request's body is new"); + assert_false(bodyReplaced.bodyUsed, "bodyUsed is false when request is not disturbed"); + return bodyReplaced.text().then(text => { + assert_equals(text, "Replaced body"); + }); +}, "Check creating a new request with a new body from a disturbed request"); + +promise_test(function() { + var bodyRequest = new Request("", initValuesDict); + const originalBody = bodyRequest.body; + assert_false(bodyRequest.bodyUsed , "bodyUsed is false when request is not disturbed"); + var requestFromRequest = new Request(bodyRequest); + assert_true(bodyRequest.bodyUsed , "bodyUsed is true when request is disturbed"); + assert_equals(bodyRequest.body, originalBody, "body should not change"); + assert_not_equals(originalBody, undefined, "body should not be undefined"); + assert_not_equals(originalBody, null, "body should not be null"); + assert_not_equals(requestFromRequest.body, originalBody, "new request's body is new"); + return requestFromRequest.text().then(text => { + assert_equals(text, "Request's body"); + }); +}, "Input request used for creating new request became disturbed"); + +promise_test(() => { + const bodyRequest = new Request("", initValuesDict); + const originalBody = bodyRequest.body; + assert_false(bodyRequest.bodyUsed , "bodyUsed is false when request is not disturbed"); + const requestFromRequest = new Request(bodyRequest, { body : "init body" }); + assert_true(bodyRequest.bodyUsed , "bodyUsed is true when request is disturbed"); + assert_equals(bodyRequest.body, originalBody, "body should not change"); + assert_not_equals(originalBody, undefined, "body should not be undefined"); + assert_not_equals(originalBody, null, "body should not be null"); + assert_not_equals(requestFromRequest.body, originalBody, "new request's body is new"); + + return requestFromRequest.text().then(text => { + assert_equals(text, "init body"); + }); +}, "Input request used for creating new request became disturbed even if body is not used"); + +promise_test(function(test) { + assert_true(bodyConsumed.bodyUsed , "bodyUsed is true when request is disturbed"); + return promise_rejects_js(test, TypeError, bodyConsumed.blob()); +}, "Check consuming a disturbed request"); + +test(function() { + var req = new Request(URL, {method: 'POST', body: 'hello'}); + assert_false(req.bodyUsed, + 'Request should not be flagged as used if it has not been ' + + 'consumed.'); + assert_throws_js(TypeError, + function() { new Request(req, {method: 'GET'}); }, + 'A get request may not have body.'); + + assert_false(req.bodyUsed, 'After the GET case'); + + assert_throws_js(TypeError, + function() { new Request(req, {method: 'CONNECT'}); }, + 'Request() with a forbidden method must throw.'); + + assert_false(req.bodyUsed, 'After the forbidden method case'); + + var req2 = new Request(req); + assert_true(req.bodyUsed, + 'Request should be flagged as used if it has been consumed.'); +}, 'Request construction failure should not set "bodyUsed"'); diff --git a/testing/web-platform/tests/fetch/api/request/request-error.any.js b/testing/web-platform/tests/fetch/api/request/request-error.any.js new file mode 100644 index 0000000000..9ec8015198 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-error.any.js @@ -0,0 +1,56 @@ +// META: global=window,worker +// META: title=Request error +// META: script=request-error.js + +// badRequestArgTests is from response-error.js +for (const { args, testName } of badRequestArgTests) { + test(() => { + assert_throws_js( + TypeError, + () => new Request(...args), + "Expect TypeError exception" + ); + }, testName); +} + +test(function() { + assert_throws_js( + TypeError, + () => Request("about:blank"), + "Calling Request constructor without 'new' must throw" + ); +}); + +test(function() { + var initialHeaders = new Headers([["Content-Type", "potato"]]); + var initialRequest = new Request("", {"headers" : initialHeaders}); + var request = new Request(initialRequest); + assert_equals(request.headers.get("Content-Type"), "potato"); +}, "Request should get its content-type from the init request"); + +test(function() { + var initialHeaders = new Headers([["Content-Type", "potato"]]); + var initialRequest = new Request("", {"headers" : initialHeaders}); + var headers = new Headers([]); + var request = new Request(initialRequest, {"headers" : headers}); + assert_false(request.headers.has("Content-Type")); +}, "Request should not get its content-type from the init request if init headers are provided"); + +test(function() { + var initialHeaders = new Headers([["Content-Type-Extra", "potato"]]); + var initialRequest = new Request("", {"headers" : initialHeaders, "body" : "this is my plate", "method" : "POST"}); + var request = new Request(initialRequest); + assert_equals(request.headers.get("Content-Type"), "text/plain;charset=UTF-8"); +}, "Request should get its content-type from the body if none is provided"); + +test(function() { + var initialHeaders = new Headers([["Content-Type", "potato"]]); + var initialRequest = new Request("", {"headers" : initialHeaders, "body" : "this is my plate", "method" : "POST"}); + var request = new Request(initialRequest); + assert_equals(request.headers.get("Content-Type"), "potato"); +}, "Request should get its content-type from init headers if one is provided"); + +test(function() { + var options = {"cache": "only-if-cached", "mode": "same-origin"}; + new Request("test", options); +}, "Request with cache mode: only-if-cached and fetch mode: same-origin"); diff --git a/testing/web-platform/tests/fetch/api/request/request-error.js b/testing/web-platform/tests/fetch/api/request/request-error.js new file mode 100644 index 0000000000..cf77313f5b --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-error.js @@ -0,0 +1,57 @@ +const badRequestArgTests = [ + { + args: ["", { "window": "http://test.url" }], + testName: "RequestInit's window is not null" + }, + { + args: ["http://:not a valid URL"], + testName: "Input URL is not valid" + }, + { + args: ["http://user:pass@test.url"], + testName: "Input URL has credentials" + }, + { + args: ["", { "mode": "navigate" }], + testName: "RequestInit's mode is navigate" + }, + { + args: ["", { "referrer": "http://:not a valid URL" }], + testName: "RequestInit's referrer is invalid" + }, + { + args: ["", { "method": "IN VALID" }], + testName: "RequestInit's method is invalid" + }, + { + args: ["", { "method": "TRACE" }], + testName: "RequestInit's method is forbidden" + }, + { + args: ["", { "mode": "no-cors", "method": "PUT" }], + testName: "RequestInit's mode is no-cors and method is not simple" + }, + { + args: ["", { "mode": "cors", "cache": "only-if-cached" }], + testName: "RequestInit's cache mode is only-if-cached and mode is not same-origin" + }, + { + args: ["test", { "cache": "only-if-cached", "mode": "cors" }], + testName: "Request with cache mode: only-if-cached and fetch mode cors" + }, + { + args: ["test", { "cache": "only-if-cached", "mode": "no-cors" }], + testName: "Request with cache mode: only-if-cached and fetch mode no-cors" + } +]; + +badRequestArgTests.push( + ...["referrerPolicy", "mode", "credentials", "cache", "redirect"].map(optionProp => { + const options = {}; + options[optionProp] = "BAD"; + return { + args: ["", options], + testName: `Bad ${optionProp} init parameter value` + }; + }) +); diff --git a/testing/web-platform/tests/fetch/api/request/request-headers.any.js b/testing/web-platform/tests/fetch/api/request/request-headers.any.js new file mode 100644 index 0000000000..b73b398013 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-headers.any.js @@ -0,0 +1,176 @@ +// META: global=window,worker +// META: title=Request Headers + +var validRequestHeaders = [ + ["Content-Type", "OK"], + ["Potato", "OK"], + ["proxy", "OK"], + ["proxya", "OK"], + ["sec", "OK"], + ["secb", "OK"], + ["Set-Cookie2", "OK"], +]; +var invalidRequestHeaders = [ + ["Accept-Charset", "KO"], + ["accept-charset", "KO"], + ["ACCEPT-ENCODING", "KO"], + ["Accept-Encoding", "KO"], + ["Access-Control-Request-Headers", "KO"], + ["Access-Control-Request-Method", "KO"], + ["Access-Control-Request-Private-Network", "KO"], + ["Connection", "KO"], + ["Content-Length", "KO"], + ["Cookie", "KO"], + ["Cookie2", "KO"], + ["Date", "KO"], + ["DNT", "KO"], + ["Expect", "KO"], + ["Host", "KO"], + ["Keep-Alive", "KO"], + ["Origin", "KO"], + ["Referer", "KO"], + ["Set-Cookie", "KO"], + ["TE", "KO"], + ["Trailer", "KO"], + ["Transfer-Encoding", "KO"], + ["Upgrade", "KO"], + ["Via", "KO"], + ["Proxy-", "KO"], + ["proxy-a", "KO"], + ["Sec-", "KO"], + ["sec-b", "KO"], +]; + +var validRequestNoCorsHeaders = [ + ["Accept", "OK"], + ["Accept-Language", "OK"], + ["content-language", "OK"], + ["content-type", "application/x-www-form-urlencoded"], + ["content-type", "application/x-www-form-urlencoded;charset=UTF-8"], + ["content-type", "multipart/form-data"], + ["content-type", "multipart/form-data;charset=UTF-8"], + ["content-TYPE", "text/plain"], + ["CONTENT-type", "text/plain;charset=UTF-8"], +]; +var invalidRequestNoCorsHeaders = [ + ["Content-Type", "KO"], + ["Potato", "KO"], + ["proxy", "KO"], + ["proxya", "KO"], + ["sec", "KO"], + ["secb", "KO"], +]; + +validRequestHeaders.forEach(function(header) { + test(function() { + var request = new Request(""); + request.headers.set(header[0], header[1]); + assert_equals(request.headers.get(header[0]), header[1]); + }, "Adding valid request header \"" + header[0] + ": " + header[1] + "\""); +}); +invalidRequestHeaders.forEach(function(header) { + test(function() { + var request = new Request(""); + request.headers.set(header[0], header[1]); + assert_equals(request.headers.get(header[0]), null); + }, "Adding invalid request header \"" + header[0] + ": " + header[1] + "\""); +}); + +validRequestNoCorsHeaders.forEach(function(header) { + test(function() { + var requestNoCors = new Request("", {"mode": "no-cors"}); + requestNoCors.headers.set(header[0], header[1]); + assert_equals(requestNoCors.headers.get(header[0]), header[1]); + }, "Adding valid no-cors request header \"" + header[0] + ": " + header[1] + "\""); +}); +invalidRequestNoCorsHeaders.forEach(function(header) { + test(function() { + var requestNoCors = new Request("", {"mode": "no-cors"}); + requestNoCors.headers.set(header[0], header[1]); + assert_equals(requestNoCors.headers.get(header[0]), null); + }, "Adding invalid no-cors request header \"" + header[0] + ": " + header[1] + "\""); +}); + +test(function() { + var headers = new Headers([["Cookie2", "potato"]]); + var request = new Request("", {"headers": headers}); + assert_equals(request.headers.get("Cookie2"), null); +}, "Check that request constructor is filtering headers provided as init parameter"); + +test(function() { + var headers = new Headers([["Content-Type", "potato"]]); + var request = new Request("", {"headers": headers, "mode": "no-cors"}); + assert_equals(request.headers.get("Content-Type"), null); +}, "Check that no-cors request constructor is filtering headers provided as init parameter"); + +test(function() { + var headers = new Headers([["Content-Type", "potato"]]); + var initialRequest = new Request("", {"headers": headers}); + var request = new Request(initialRequest, {"mode": "no-cors"}); + assert_equals(request.headers.get("Content-Type"), null); +}, "Check that no-cors request constructor is filtering headers provided as part of request parameter"); + +test(function() { + var initialHeaders = new Headers([["Content-Type", "potato"]]); + var initialRequest = new Request("", {"headers" : initialHeaders}); + var request = new Request(initialRequest); + assert_equals(request.headers.get("Content-Type"), "potato"); +}, "Request should get its content-type from the init request"); + +test(function() { + var initialHeaders = new Headers([["Content-Type", "potato"]]); + var initialRequest = new Request("", {"headers" : initialHeaders}); + var headers = new Headers([]); + var request = new Request(initialRequest, {"headers" : headers}); + assert_false(request.headers.has("Content-Type")); +}, "Request should not get its content-type from the init request if init headers are provided"); + +test(function() { + var initialHeaders = new Headers([["Content-Type-Extra", "potato"]]); + var initialRequest = new Request("", {"headers" : initialHeaders, "body" : "this is my plate", "method" : "POST"}); + var request = new Request(initialRequest); + assert_equals(request.headers.get("Content-Type"), "text/plain;charset=UTF-8"); +}, "Request should get its content-type from the body if none is provided"); + +test(function() { + var initialHeaders = new Headers([["Content-Type", "potato"]]); + var initialRequest = new Request("", {"headers" : initialHeaders, "body" : "this is my plate", "method" : "POST"}); + var request = new Request(initialRequest); + assert_equals(request.headers.get("Content-Type"), "potato"); +}, "Request should get its content-type from init headers if one is provided"); + +test(function() { + var array = [["hello", "worldAHH"]]; + var object = {"hello": 'worldOOH'}; + var headers = new Headers(array); + + assert_equals(headers.get("hello"), "worldAHH"); + + var request1 = new Request("", {"headers": headers}); + var request2 = new Request("", {"headers": array}); + var request3 = new Request("", {"headers": object}); + + assert_equals(request1.headers.get("hello"), "worldAHH"); + assert_equals(request2.headers.get("hello"), "worldAHH"); + assert_equals(request3.headers.get("hello"), "worldOOH"); +}, "Testing request header creations with various objects"); + +promise_test(function(test) { + var request = new Request("", {"headers" : [["Content-Type", ""]], "body" : "this is my plate", "method" : "POST"}); + return request.blob().then(function(blob) { + assert_equals(blob.type, "", "Blob type should be the empty string"); + }); +}, "Testing empty Request Content-Type header"); + +test(function() { + const request1 = new Request(""); + assert_equals(request1.headers, request1.headers); + + const request2 = new Request("", {"headers": {"X-Foo": "bar"}}); + assert_equals(request2.headers, request2.headers); + const headers = request2.headers; + request2.headers.set("X-Foo", "quux"); + assert_equals(headers, request2.headers); + headers.set("X-Other-Header", "baz"); + assert_equals(headers, request2.headers); +}, "Test that Request.headers has the [SameObject] extended attribute"); diff --git a/testing/web-platform/tests/fetch/api/request/request-init-001.sub.html b/testing/web-platform/tests/fetch/api/request/request-init-001.sub.html new file mode 100644 index 0000000000..cc495a6652 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-init-001.sub.html @@ -0,0 +1,112 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8"> + <title>Request init: simple cases</title> + <meta name="help" href="https://fetch.spec.whatwg.org/#request"> + <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <script> + // https://fetch.spec.whatwg.org/#concept-method-normalize + var methods = { + "givenValues" : [ + "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", + "get", "head", "post", "put", "delete", "options", + "Get", "hEad", "poSt", "Put", "deleTe", "optionS", + "PATCH", "patch", "patCh" + ], + "expectedValues" : [ + "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", + "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", + "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", + "PATCH", "patch", "patCh" + ] + }; + var referrers = {"givenValues" : ["/relative/ressource", + "http://{{host}}:{{ports[http][0]}}/relative/ressource?query=true#fragment", + "http://{{host}}:{{ports[http][0]}}/", + "http://test.url", + "about:client", + "" + ], + "expectedValues" : ["http://{{host}}:{{ports[http][0]}}/relative/ressource", + "http://{{host}}:{{ports[http][0]}}/relative/ressource?query=true#fragment", + "http://{{host}}:{{ports[http][0]}}/", + "about:client", + "about:client", + "" + ] + }; + var referrerPolicies = {"givenValues" : [ "", + "no-referrer", + "no-referrer-when-downgrade", + "origin", + "origin-when-cross-origin", + "unsafe-url", + "same-origin", + "strict-origin", + "strict-origin-when-cross-origin" + ], + "expectedValues" : ["", + "no-referrer", + "no-referrer-when-downgrade", + "origin", + "origin-when-cross-origin", + "unsafe-url", + "same-origin", + "strict-origin", + "strict-origin-when-cross-origin" + ] + }; + var modes = {"givenValues" : ["same-origin", "no-cors", "cors"], + "expectedValues" : ["same-origin", "no-cors", "cors"] + }; + var credentials = {"givenValues" : ["omit", "same-origin", "include"], + "expectedValues" : ["omit", "same-origin", "include"] + }; + var caches = {"givenValues" : [ "default", "no-store", "reload", "no-cache", "force-cache"], + "expectedValues" : [ "default", "no-store", "reload", "no-cache", "force-cache"] + }; + var redirects = {"givenValues" : ["follow", "error", "manual"], + "expectedValues" : ["follow", "error", "manual"] + }; + var integrities = {"givenValues" : ["", "AZERTYUIOP1234567890" ], + "expectedValues" : ["", "AZERTYUIOP1234567890"] + }; + + //there is no getter for window, init's window might be null + var windows = {"givenValues" : [ null ], + "expectedValues" : [undefined] + }; + + var initValuesDict = { "method" : methods, + "referrer" : referrers, + "referrerPolicy" : referrerPolicies, + "mode" : modes, + "credentials" : credentials, + "cache" : caches, + "redirect" : redirects, + "integrity" : integrities, + "window" : windows + }; + + for (var attributeName in initValuesDict) { + var valuesToTest = initValuesDict[attributeName]; + for (var valueIdx in valuesToTest["givenValues"]) { + var givenValue = valuesToTest["givenValues"][valueIdx]; + var expectedValue = valuesToTest["expectedValues"][valueIdx]; + test(function() { + var requestInit = {}; + requestInit[attributeName] = givenValue + var request = new Request("", requestInit); + assert_equals(request[attributeName], expectedValue, + "Expect request's " + attributeName + " is " + expectedValue + " when initialized with " + givenValue); + }, "Check " + attributeName + " init value of " + givenValue + " and associated getter"); + } + } + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/api/request/request-init-002.any.js b/testing/web-platform/tests/fetch/api/request/request-init-002.any.js new file mode 100644 index 0000000000..abb6689f1e --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-init-002.any.js @@ -0,0 +1,60 @@ +// META: global=window,worker +// META: title=Request init: headers and body + +test(function() { + var headerDict = {"name1": "value1", + "name2": "value2", + "name3": "value3" + }; + var headers = new Headers(headerDict); + var request = new Request("", { "headers" : headers }) + for (var name in headerDict) { + assert_equals(request.headers.get(name), headerDict[name], + "request's headers has " + name + " : " + headerDict[name]); + } +}, "Initialize Request with headers values"); + +function makeRequestInit(body, method) { + return {"method": method, "body": body}; +} + +function checkRequestInit(body, bodyType, expectedTextBody) { + promise_test(function(test) { + var request = new Request("", makeRequestInit(body, "POST")); + if (body) { + assert_throws_js(TypeError, function() { new Request("", makeRequestInit(body, "GET")); }); + assert_throws_js(TypeError, function() { new Request("", makeRequestInit(body, "HEAD")); }); + } else { + new Request("", makeRequestInit(body, "GET")); // should not throw + } + var reqHeaders = request.headers; + var mime = reqHeaders.get("Content-Type"); + assert_true(!body || (mime && mime.search(bodyType) > -1), "Content-Type header should be \"" + bodyType + "\", not \"" + mime + "\""); + return request.text().then(function(bodyAsText) { + //not equals: cannot guess formData exact value + assert_true( bodyAsText.search(expectedTextBody) > -1, "Retrieve and verify request body"); + }); + }, `Initialize Request's body with "${body}", ${bodyType}`); +} + +var blob = new Blob(["This is a blob"], {type: "application/octet-binary"}); +var formaData = new FormData(); +formaData.append("name", "value"); +var usvString = "This is a USVString" + +checkRequestInit(undefined, undefined, ""); +checkRequestInit(null, null, ""); +checkRequestInit(blob, "application/octet-binary", "This is a blob"); +checkRequestInit(formaData, "multipart/form-data", "name=\"name\"\r\n\r\nvalue"); +checkRequestInit(usvString, "text/plain;charset=UTF-8", "This is a USVString"); +checkRequestInit({toString: () => "hi!"}, "text/plain;charset=UTF-8", "hi!"); + +// Ensure test does not time out in case of missing URLSearchParams support. +if (self.URLSearchParams) { + var urlSearchParams = new URLSearchParams("name=value"); + checkRequestInit(urlSearchParams, "application/x-www-form-urlencoded;charset=UTF-8", "name=value"); +} else { + promise_test(function(test) { + return Promise.reject("URLSearchParams not supported"); + }, "Initialize Request's body with application/x-www-form-urlencoded;charset=UTF-8"); +} diff --git a/testing/web-platform/tests/fetch/api/request/request-init-003.sub.html b/testing/web-platform/tests/fetch/api/request/request-init-003.sub.html new file mode 100644 index 0000000000..79c91cdfe8 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-init-003.sub.html @@ -0,0 +1,84 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8"> + <title>Request: init with request or url</title> + <meta name="help" href="https://fetch.spec.whatwg.org/#request"> + <meta name="help" href="https://url.spec.whatwg.org/#concept-url-serializer"> + <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <script src="../resources/utils.js"></script> + <script> + var headers = new Headers( {"name":"value"} ); + var emptyHeaders = new Headers(); + + var initValuesDict = {"method" : "POST", + "referrer" : "http://{{host}}:{{ports[http][0]}}/", + "referrerPolicy" : "origin", + "mode" : "same-origin", + "credentials" : "include", + "cache" : "no-cache", + "redirect" : "error", + "integrity" : "Request's Integrity", + "headers" : headers, + "body" : "Request's body" + }; + + var expectedInitialized = {"method" : "POST", + "referrer" : "http://{{host}}:{{ports[http][0]}}/", + "referrerPolicy" : "origin", + "mode" : "same-origin", + "credentials" : "include", + "cache" : "no-cache", + "redirect" : "error", + "integrity" : "Request's Integrity", + "headers" : headers, + "body" : "Request's body" + }; + + var expectedDefault = {"method" : "GET", + "url" : location.href, + "referrer" : "about:client", + "referrerPolicy" : "", + "mode" : "cors", + "credentials" : "same-origin", + "cache" : "default", + "redirect" : "follow", + "integrity" : "", + "headers" : emptyHeaders + }; + + var requestDefault = new Request(""); + var requestInitialized = new Request("", initValuesDict); + + test(function() { + var requestToCheck = new Request(requestInitialized); + checkRequest(requestToCheck, expectedInitialized); + }, "Check request values when initialized from Request"); + + test(function() { + var requestToCheck = new Request(requestDefault, initValuesDict); + checkRequest(requestToCheck, expectedInitialized); + }, "Check request values when initialized from Request and init values"); + + test(function() { + var url = "http://url.test:1234/path/subpath?query=true"; + url += "#fragment"; + expectedDefault["url"] = url; + var requestToCheck = new Request(url); + checkRequest(requestToCheck, expectedDefault); + }, "Check request values when initialized from url string"); + + test(function() { + var url = "http://url.test:1234/path/subpath?query=true"; + url += "#fragment"; + expectedInitialized["url"] = url; + var requestToCheck = new Request(url , initValuesDict); + checkRequest(requestToCheck, expectedInitialized); + }, "Check request values when initialized from url and init values"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/api/request/request-init-contenttype.any.js b/testing/web-platform/tests/fetch/api/request/request-init-contenttype.any.js new file mode 100644 index 0000000000..18a6969d4f --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-init-contenttype.any.js @@ -0,0 +1,141 @@ +function requestFromBody(body) { + return new Request( + "https://example.com", + { + method: "POST", + body, + duplex: "half", + }, + ); +} + +test(() => { + const request = requestFromBody(undefined); + assert_equals(request.headers.get("Content-Type"), null); +}, "Default Content-Type for Request with empty body"); + +test(() => { + const blob = new Blob([]); + const request = requestFromBody(blob); + assert_equals(request.headers.get("Content-Type"), null); +}, "Default Content-Type for Request with Blob body (no type set)"); + +test(() => { + const blob = new Blob([], { type: "" }); + const request = requestFromBody(blob); + assert_equals(request.headers.get("Content-Type"), null); +}, "Default Content-Type for Request with Blob body (empty type)"); + +test(() => { + const blob = new Blob([], { type: "a/b; c=d" }); + const request = requestFromBody(blob); + assert_equals(request.headers.get("Content-Type"), "a/b; c=d"); +}, "Default Content-Type for Request with Blob body (set type)"); + +test(() => { + const buffer = new Uint8Array(); + const request = requestFromBody(buffer); + assert_equals(request.headers.get("Content-Type"), null); +}, "Default Content-Type for Request with buffer source body"); + +promise_test(async () => { + const formData = new FormData(); + formData.append("a", "b"); + const request = requestFromBody(formData); + const boundary = (await request.text()).split("\r\n")[0].slice(2); + assert_equals( + request.headers.get("Content-Type"), + `multipart/form-data; boundary=${boundary}`, + ); +}, "Default Content-Type for Request with FormData body"); + +test(() => { + const usp = new URLSearchParams(); + const request = requestFromBody(usp); + assert_equals( + request.headers.get("Content-Type"), + "application/x-www-form-urlencoded;charset=UTF-8", + ); +}, "Default Content-Type for Request with URLSearchParams body"); + +test(() => { + const request = requestFromBody(""); + assert_equals( + request.headers.get("Content-Type"), + "text/plain;charset=UTF-8", + ); +}, "Default Content-Type for Request with string body"); + +test(() => { + const stream = new ReadableStream(); + const request = requestFromBody(stream); + assert_equals(request.headers.get("Content-Type"), null); +}, "Default Content-Type for Request with ReadableStream body"); + +// ----------------------------------------------------------------------------- + +const OVERRIDE_MIME = "test/only; mime=type"; + +function requestFromBodyWithOverrideMime(body) { + return new Request( + "https://example.com", + { + method: "POST", + body, + headers: { "Content-Type": OVERRIDE_MIME }, + duplex: "half", + }, + ); +} + +test(() => { + const request = requestFromBodyWithOverrideMime(undefined); + assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Request with empty body"); + +test(() => { + const blob = new Blob([]); + const request = requestFromBodyWithOverrideMime(blob); + assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Request with Blob body (no type set)"); + +test(() => { + const blob = new Blob([], { type: "" }); + const request = requestFromBodyWithOverrideMime(blob); + assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Request with Blob body (empty type)"); + +test(() => { + const blob = new Blob([], { type: "a/b; c=d" }); + const request = requestFromBodyWithOverrideMime(blob); + assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Request with Blob body (set type)"); + +test(() => { + const buffer = new Uint8Array(); + const request = requestFromBodyWithOverrideMime(buffer); + assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Request with buffer source body"); + +test(() => { + const formData = new FormData(); + const request = requestFromBodyWithOverrideMime(formData); + assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Request with FormData body"); + +test(() => { + const usp = new URLSearchParams(); + const request = requestFromBodyWithOverrideMime(usp); + assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Request with URLSearchParams body"); + +test(() => { + const request = requestFromBodyWithOverrideMime(""); + assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Request with string body"); + +test(() => { + const stream = new ReadableStream(); + const request = requestFromBodyWithOverrideMime(stream); + assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Request with ReadableStream body"); diff --git a/testing/web-platform/tests/fetch/api/request/request-init-stream.any.js b/testing/web-platform/tests/fetch/api/request/request-init-stream.any.js new file mode 100644 index 0000000000..f0ae441a00 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-init-stream.any.js @@ -0,0 +1,147 @@ +// META: global=window,worker + +"use strict"; + +const duplex = "half"; +const method = "POST"; + +test(() => { + const body = new ReadableStream(); + const request = new Request("...", { method, body, duplex }); + assert_equals(request.body, body); +}, "Constructing a Request with a stream holds the original object."); + +test((t) => { + const body = new ReadableStream(); + body.getReader(); + assert_throws_js(TypeError, + () => new Request("...", { method, body, duplex })); +}, "Constructing a Request with a stream on which getReader() is called"); + +test((t) => { + const body = new ReadableStream(); + body.getReader().read(); + assert_throws_js(TypeError, + () => new Request("...", { method, body, duplex })); +}, "Constructing a Request with a stream on which read() is called"); + +promise_test(async (t) => { + const body = new ReadableStream({ pull: c => c.enqueue(new Uint8Array()) }); + const reader = body.getReader(); + await reader.read(); + reader.releaseLock(); + assert_throws_js(TypeError, + () => new Request("...", { method, body, duplex })); +}, "Constructing a Request with a stream on which read() and releaseLock() are called"); + +test((t) => { + const request = new Request("...", { method: "POST", body: "..." }); + request.body.getReader(); + assert_throws_js(TypeError, () => new Request(request)); + // This doesn't throw. + new Request(request, { body: "..." }); +}, "Constructing a Request with a Request on which body.getReader() is called"); + +test((t) => { + const request = new Request("...", { method: "POST", body: "..." }); + request.body.getReader().read(); + assert_throws_js(TypeError, () => new Request(request)); + // This doesn't throw. + new Request(request, { body: "..." }); +}, "Constructing a Request with a Request on which body.getReader().read() is called"); + +promise_test(async (t) => { + const request = new Request("...", { method: "POST", body: "..." }); + const reader = request.body.getReader(); + await reader.read(); + reader.releaseLock(); + assert_throws_js(TypeError, () => new Request(request)); + // This doesn't throw. + new Request(request, { body: "..." }); +}, "Constructing a Request with a Request on which read() and releaseLock() are called"); + +test((t) => { + new Request("...", { method, body: null }); +}, "It is OK to omit .duplex when the body is null."); + +test((t) => { + new Request("...", { method, body: "..." }); +}, "It is OK to omit .duplex when the body is a string."); + +test((t) => { + new Request("...", { method, body: new Uint8Array(3) }); +}, "It is OK to omit .duplex when the body is a Uint8Array."); + +test((t) => { + new Request("...", { method, body: new Blob([]) }); +}, "It is OK to omit .duplex when the body is a Blob."); + +test((t) => { + const body = new ReadableStream(); + assert_throws_js(TypeError, + () => new Request("...", { method, body })); +}, "It is error to omit .duplex when the body is a ReadableStream."); + +test((t) => { + new Request("...", { method, body: null, duplex: "half" }); +}, "It is OK to set .duplex = 'half' when the body is null."); + +test((t) => { + new Request("...", { method, body: "...", duplex: "half" }); +}, "It is OK to set .duplex = 'half' when the body is a string."); + +test((t) => { + new Request("...", { method, body: new Uint8Array(3), duplex: "half" }); +}, "It is OK to set .duplex = 'half' when the body is a Uint8Array."); + +test((t) => { + new Request("...", { method, body: new Blob([]), duplex: "half" }); +}, "It is OK to set .duplex = 'half' when the body is a Blob."); + +test((t) => { + const body = new ReadableStream(); + new Request("...", { method, body, duplex: "half" }); +}, "It is OK to set .duplex = 'half' when the body is a ReadableStream."); + +test((t) => { + const body = null; + const duplex = "full"; + assert_throws_js(TypeError, + () => new Request("...", { method, body, duplex })); +}, "It is error to set .duplex = 'full' when the body is null."); + +test((t) => { + const body = "..."; + const duplex = "full"; + assert_throws_js(TypeError, + () => new Request("...", { method, body, duplex })); +}, "It is error to set .duplex = 'full' when the body is a string."); + +test((t) => { + const body = new Uint8Array(3); + const duplex = "full"; + assert_throws_js(TypeError, + () => new Request("...", { method, body, duplex })); +}, "It is error to set .duplex = 'full' when the body is a Uint8Array."); + +test((t) => { + const body = new Blob([]); + const duplex = "full"; + assert_throws_js(TypeError, + () => new Request("...", { method, body, duplex })); +}, "It is error to set .duplex = 'full' when the body is a Blob."); + +test((t) => { + const body = new ReadableStream(); + const duplex = "full"; + assert_throws_js(TypeError, + () => new Request("...", { method, body, duplex })); +}, "It is error to set .duplex = 'full' when the body is a ReadableStream."); + +test((t) => { + const body = new ReadableStream(); + const duplex = "half"; + const req1 = new Request("...", { method, body, duplex }); + const req2 = new Request(req1); +}, "It is OK to omit duplex when init.body is not given and input.body is given."); + diff --git a/testing/web-platform/tests/fetch/api/request/request-keepalive-quota.html b/testing/web-platform/tests/fetch/api/request/request-keepalive-quota.html new file mode 100644 index 0000000000..548ab38d7e --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-keepalive-quota.html @@ -0,0 +1,97 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8"> + <title>Request Keepalive Quota Tests</title> + <meta name="timeout" content="long"> + <meta name="help" href="https://fetch.spec.whatwg.org/#request"> + <meta name="help" href="https://fetch.spec.whatwg.org/#body-mixin"> + <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com"> + <meta name="variant" content="?include=fast"> + <meta name="variant" content="?include=slow-1"> + <meta name="variant" content="?include=slow-2"> + <meta name="variant" content="?include=slow-3"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/common/subset-tests-by-key.js"></script> + </head> + <body> + <script> + 'use strict'; + + // We want to ensure that our keepalive requests hang slightly before completing so we can validate + // the effects of a rolling quota. To do this we will utilize trickle.py with a 1s delay. This should + // prevent any of the Fetch's from finishing in this window. + const trickleURL = '../resources/trickle.py?count=1&ms='; + const noDelay = 0; + const standardDelay = 1000; + function wait(ms) { + return new Promise(resolve => step_timeout(resolve, ms)); + } + + // We should expect 64KiB of rolling quota for any type of keep-alive request sent. + const expectedQuota = 65536; + + function fetchKeepAliveRequest(delay, bodySize) { + // Create a body of the specified size that's filled with *'s + const body = '*'.repeat(bodySize); + return fetch(trickleURL + delay, {keepalive: true, body, method: 'POST'}).then(res => { + return res.text(); + }).then(() => { + return wait(1); + }); + } + + // Test 1 Byte + subsetTestByKey("fast", promise_test, function(test) { + return fetchKeepAliveRequest(noDelay, 1 /* bodySize */); + }, 'A Keep-Alive fetch() with a small body should succeed.'); + + // Test Quota full limit + subsetTestByKey("fast", promise_test, function(test) { + return fetchKeepAliveRequest(noDelay, expectedQuota /* bodySize */); + }, 'A Keep-Alive fetch() with a body at the Quota Limit should succeed.'); + + // Test Quota + 1 Byte + subsetTestByKey("fast", promise_test, function(test) { + return promise_rejects_js(test, TypeError, fetchKeepAliveRequest(noDelay, expectedQuota + 1)); + }, 'A Keep-Alive fetch() with a body over the Quota Limit should reject.'); + + // Test the Quota becomes available upon promise completion. + subsetTestByKey("slow-1", promise_test, function (test) { + // Fill our Quota then try to send a second fetch. + return fetchKeepAliveRequest(standardDelay, expectedQuota).then(() => { + // Now validate that we can send another Keep-Alive fetch for the full size of the quota. + return fetchKeepAliveRequest(noDelay, expectedQuota); + }); + }, 'A Keep-Alive fetch() should return its allocated Quota upon promise resolution.'); + + // Ensure only the correct amount of Quota becomes available when a fetch completes. + subsetTestByKey("slow-2", promise_test, function(test) { + // Create a fetch that uses all but 1 Byte of the Quota and runs for 2x as long as the other requests. + const first = fetchKeepAliveRequest(standardDelay * 2, expectedQuota - 1); + + // Now create a single Byte request that will complete quicker. + const second = fetchKeepAliveRequest(standardDelay, 1 /* bodySize */).then(() => { + // We shouldn't be able to create a 2 Byte request right now as only 1 Byte should have freed up. + return promise_rejects_js(test, TypeError, fetchKeepAliveRequest(noDelay, 2 /* bodySize */)); + }).then(() => { + // Now validate that we can send another Keep-Alive fetch for just 1 Byte. + return fetchKeepAliveRequest(noDelay, 1 /* bodySize */); + }); + + return Promise.all([first, second]); + }, 'A Keep-Alive fetch() should return only its allocated Quota upon promise resolution.'); + + // Test rejecting a fetch() after the quota is used up. + subsetTestByKey("slow-3", promise_test, function (test) { + // Fill our Quota then try to send a second fetch. + const p = fetchKeepAliveRequest(standardDelay, expectedQuota); + + const q = promise_rejects_js(test, TypeError, fetchKeepAliveRequest(noDelay, 1 /* bodySize */)); + return Promise.all([p, q]); + }, 'A Keep-Alive fetch() should not be allowed if the Quota is used up.'); + + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/api/request/request-keepalive.any.js b/testing/web-platform/tests/fetch/api/request/request-keepalive.any.js new file mode 100644 index 0000000000..cb4506db46 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-keepalive.any.js @@ -0,0 +1,17 @@ +// META: global=window,worker +// META: title=Request keepalive +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js + +test(() => { + assert_false(new Request('/').keepalive, 'default'); + assert_true(new Request('/', {keepalive: true}).keepalive, 'true'); + assert_false(new Request('/', {keepalive: false}).keepalive, 'false'); + assert_true(new Request('/', {keepalive: 1}).keepalive, 'truish'); + assert_false(new Request('/', {keepalive: 0}).keepalive, 'falsy'); +}, 'keepalive flag'); + +test(() => { + const init = {method: 'POST', keepalive: true, body: new ReadableStream()}; + assert_throws_js(TypeError, () => {new Request('/', init)}); +}, 'keepalive flag with stream body'); diff --git a/testing/web-platform/tests/fetch/api/request/request-reset-attributes.https.html b/testing/web-platform/tests/fetch/api/request/request-reset-attributes.https.html new file mode 100644 index 0000000000..7be3608d73 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-reset-attributes.https.html @@ -0,0 +1,96 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<body> +<script> +const worker = 'resources/request-reset-attributes-worker.js'; + +function wait(ms) { + return new Promise(resolve => step_timeout(resolve, ms)); +} + +promise_test(async (t) => { + const scope = 'resources/hello.txt?name=isReloadNavigation'; + let frame; + let reg; + + try { + reg = await service_worker_unregister_and_register(t, worker, scope); + await wait_for_state(t, reg.installing, 'activated'); + frame = await with_iframe(scope); + assert_equals(frame.contentDocument.body.textContent, + 'old: false, new: false'); + await new Promise((resolve) => { + frame.onload = resolve; + frame.contentWindow.location.reload(); + }); + assert_equals(frame.contentDocument.body.textContent, + 'old: true, new: false'); + } finally { + if (frame) { + frame.remove(); + } + if (reg) { + await reg.unregister(); + } + } + }, 'Request.isReloadNavigation is reset with non-empty RequestInit'); + +promise_test(async (t) => { + const scope = 'resources/hello.html?name=isHistoryNavigation'; + let frame; + let reg; + + try { + reg = await service_worker_unregister_and_register(t, worker, scope); + await wait_for_state(t, reg.installing, 'activated'); + frame = await with_iframe(scope); + assert_equals(frame.contentDocument.body.textContent, + 'old: false, new: false'); + // Use step_timeout(0) to ensure the history entry is created for Blink + // and WebKit. See https://bugs.webkit.org/show_bug.cgi?id=42861. + await wait(0); + await new Promise((resolve) => { + frame.onload = resolve; + frame.src = 'resources/hello.html?ignore'; + }); + await wait(0); + await new Promise((resolve) => { + frame.onload = resolve; + frame.contentWindow.history.go(-1); + }); + assert_equals(frame.contentDocument.body.textContent, + 'old: true, new: false'); + } finally { + if (frame) { + frame.remove(); + } + if (reg) { + await reg.unregister(); + } + } +}, 'Request.isHistoryNavigation is reset with non-empty RequestInit'); + +promise_test(async (t) => { + const scope = 'resources/hello.txt?name=mode'; + let frame; + let reg; + + try { + reg = await service_worker_unregister_and_register(t, worker, scope); + await wait_for_state(t, reg.installing, 'activated'); + frame = await with_iframe(scope); + assert_equals(frame.contentDocument.body.textContent, + 'old: navigate, new: same-origin'); + } finally { + if (frame) { + frame.remove(); + } + if (reg) { + await reg.unregister(); + } + } + }, 'Request.mode is reset with non-empty RequestInit when it\'s "navigate"'); +</script> diff --git a/testing/web-platform/tests/fetch/api/request/request-structure.any.js b/testing/web-platform/tests/fetch/api/request/request-structure.any.js new file mode 100644 index 0000000000..3d55c70ac1 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/request-structure.any.js @@ -0,0 +1,133 @@ +// META: global=window,worker +// META: title=Request structure + +var request = new Request(""); +var methods = ["clone", + //Request implements Body + "arrayBuffer", + "blob", + "formData", + "json", + "text" + ]; +var attributes = ["method", + "url", + "headers", + "destination", + "referrer", + "referrerPolicy", + "mode", + "credentials", + "cache", + "redirect", + "integrity", + "isReloadNavigation", + "isHistoryNavigation", + "duplex", + //Request implements Body + "bodyUsed" + ]; + +function isReadOnly(request, attributeToCheck) { + var defaultValue = undefined; + var newValue = undefined; + switch (attributeToCheck) { + case "method": + defaultValue = "GET"; + newValue = "POST"; + break; + + case "url": + //default value is base url + //i.e http://example.com/fetch/api/request-structure.html + newValue = "http://url.test"; + break; + + case "headers": + request.headers = new Headers ({"name":"value"}); + assert_false(request.headers.has("name"), "Headers attribute is read only"); + return; + + case "destination": + defaultValue = ""; + newValue = "worker"; + break; + + case "referrer": + defaultValue = "about:client"; + newValue = "http://url.test"; + break; + + case "referrerPolicy": + defaultValue = ""; + newValue = "unsafe-url"; + break; + + case "mode": + defaultValue = "cors"; + newValue = "navigate"; + break; + + case "credentials": + defaultValue = "same-origin"; + newValue = "cors"; + break; + + case "cache": + defaultValue = "default"; + newValue = "reload"; + break; + + case "redirect": + defaultValue = "follow"; + newValue = "manual"; + break; + + case "integrity": + newValue = "CannotWriteIntegrity"; + break; + + case "bodyUsed": + defaultValue = false; + newValue = true; + break; + + case "isReloadNavigation": + defaultValue = false; + newValue = true; + break; + + case "isHistoryNavigation": + defaultValue = false; + newValue = true; + break; + + case "duplex": + defaultValue = "half"; + newValue = "full"; + break; + + default: + return; + } + + request[attributeToCheck] = newValue; + if (defaultValue === undefined) + assert_not_equals(request[attributeToCheck], newValue, "Attribute " + attributeToCheck + " is read only"); + else + assert_equals(request[attributeToCheck], defaultValue, + "Attribute " + attributeToCheck + " is read only. Default value is " + defaultValue); +} + +for (var idx in methods) { + test(function() { + assert_true(methods[idx] in request, "request has " + methods[idx] + " method"); + }, "Request has " + methods[idx] + " method"); +} + +for (var idx in attributes) { + test(function() { + assert_true(attributes[idx] in request, "request has " + attributes[idx] + " attribute"); + isReadOnly(request, attributes[idx]); + }, "Check " + attributes[idx] + " attribute"); +} diff --git a/testing/web-platform/tests/fetch/api/request/resources/cache.py b/testing/web-platform/tests/fetch/api/request/resources/cache.py new file mode 100644 index 0000000000..ca0bd644b4 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/resources/cache.py @@ -0,0 +1,67 @@ +from wptserve.utils import isomorphic_decode + +def main(request, response): + token = request.GET.first(b"token", None) + if b"querystate" in request.GET: + from json import JSONEncoder + response.headers.set(b"Content-Type", b"text/plain") + return JSONEncoder().encode(request.server.stash.take(token)) + content = request.GET.first(b"content", None) + tag = request.GET.first(b"tag", None) + date = request.GET.first(b"date", None) + expires = request.GET.first(b"expires", None) + vary = request.GET.first(b"vary", None) + cc = request.GET.first(b"cache_control", None) + redirect = request.GET.first(b"redirect", None) + inm = request.headers.get(b"If-None-Match", None) + ims = request.headers.get(b"If-Modified-Since", None) + pragma = request.headers.get(b"Pragma", None) + cache_control = request.headers.get(b"Cache-Control", None) + ignore = b"ignore" in request.GET + + if tag: + tag = b'"%s"' % tag + + server_state = request.server.stash.take(token) + if not server_state: + server_state = [] + state = dict() + if not ignore: + if inm: + state[u"If-None-Match"] = isomorphic_decode(inm) + if ims: + state[u"If-Modified-Since"] = isomorphic_decode(ims) + if pragma: + state[u"Pragma"] = isomorphic_decode(pragma) + if cache_control: + state[u"Cache-Control"] = isomorphic_decode(cache_control) + server_state.append(state) + request.server.stash.put(token, server_state) + + if tag: + response.headers.set(b"ETag", b'%s' % tag) + elif date: + response.headers.set(b"Last-Modified", date) + if expires: + response.headers.set(b"Expires", expires) + if vary: + response.headers.set(b"Vary", vary) + if cc: + response.headers.set(b"Cache-Control", cc) + + # The only-if-cached redirect tests wants CORS to be okay, the other tests + # are all same-origin anyways and don't care. + response.headers.set(b"Access-Control-Allow-Origin", b"*") + + if redirect: + response.headers.set(b"Location", redirect) + response.status = (302, b"Redirect") + return b"" + elif ((inm is not None and inm == tag) or + (ims is not None and ims == date)): + response.status = (304, b"Not Modified") + return b"" + else: + response.status = (200, b"OK") + response.headers.set(b"Content-Type", b"text/plain") + return content diff --git a/testing/web-platform/tests/fetch/api/request/resources/hello.txt b/testing/web-platform/tests/fetch/api/request/resources/hello.txt new file mode 100644 index 0000000000..ce01362503 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/resources/hello.txt @@ -0,0 +1 @@ +hello diff --git a/testing/web-platform/tests/fetch/api/request/resources/request-reset-attributes-worker.js b/testing/web-platform/tests/fetch/api/request/resources/request-reset-attributes-worker.js new file mode 100644 index 0000000000..4b264ca2fe --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/resources/request-reset-attributes-worker.js @@ -0,0 +1,19 @@ +self.addEventListener('fetch', (event) => { + const params = new URL(event.request.url).searchParams; + if (params.has('ignore')) { + return; + } + if (!params.has('name')) { + event.respondWith(Promise.reject(TypeError('No name is provided.'))); + return; + } + + const name = params.get('name'); + const old_attribute = event.request[name]; + // If any of |init|'s member is present... + const init = {cache: 'no-store'} + const new_attribute = (new Request(event.request, init))[name]; + + event.respondWith( + new Response(`old: ${old_attribute}, new: ${new_attribute}`)); + }); diff --git a/testing/web-platform/tests/fetch/api/request/url-encoding.html b/testing/web-platform/tests/fetch/api/request/url-encoding.html new file mode 100644 index 0000000000..31c1ed3920 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/request/url-encoding.html @@ -0,0 +1,25 @@ +<!doctype html> +<meta charset=windows-1252> +<title>Fetch: URL encoding</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> +const expectedURL = new URL("?%C3%9F", location.href).href; +const expectedURL2 = new URL("?%EF%BF%BD", location.href).href; +test(() => { + let r = new Request("?\u00DF"); + assert_equals(r.url, expectedURL); + + r = new Request("?\uD83D"); + assert_equals(r.url, expectedURL2); +}, "URL encoding and Request"); + +promise_test(() => { + return fetch("?\u00DF").then(res => { + assert_equals(res.url, expectedURL); + return fetch("?\uD83D").then(res2 => { + assert_equals(res2.url, expectedURL2); + }); + }); +}, "URL encoding and fetch()"); +</script> |