diff options
Diffstat (limited to '')
81 files changed, 2222 insertions, 0 deletions
diff --git a/dom/serviceworkers/test/fetch.js b/dom/serviceworkers/test/fetch.js new file mode 100644 index 0000000000..bf1bb4acb3 --- /dev/null +++ b/dom/serviceworkers/test/fetch.js @@ -0,0 +1,33 @@ +function get_query_params(url) { + var search = new URL(url).search; + if (!search) { + return {}; + } + var ret = {}; + var params = search.substring(1).split("&"); + params.forEach(function (param) { + var element = param.split("="); + ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]); + }); + return ret; +} + +addEventListener("fetch", function (event) { + if (event.request.url.includes("fail.html")) { + event.respondWith(fetch("hello.html", { integrity: "abc" })); + } else if (event.request.url.includes("fake.html")) { + event.respondWith(fetch("hello.html")); + } else if (event.request.url.includes("file_js_cache")) { + event.respondWith(fetch(event.request)); + } else if (event.request.url.includes("redirect")) { + let param = get_query_params(event.request.url); + let url = param.url; + let mode = param.mode; + + event.respondWith(fetch(url, { mode })); + } +}); + +addEventListener("activate", function (event) { + event.waitUntil(clients.claim()); +}); diff --git a/dom/serviceworkers/test/fetch/cookie/cookie_test.js b/dom/serviceworkers/test/fetch/cookie/cookie_test.js new file mode 100644 index 0000000000..4102b4b341 --- /dev/null +++ b/dom/serviceworkers/test/fetch/cookie/cookie_test.js @@ -0,0 +1,11 @@ +self.addEventListener("fetch", function (event) { + if (event.request.url.includes("synth.html")) { + var body = + "<script>" + + 'window.parent.postMessage({status: "done", cookie: document.cookie}, "*");' + + "</script>"; + event.respondWith( + new Response(body, { headers: { "Content-Type": "text/html" } }) + ); + } +}); diff --git a/dom/serviceworkers/test/fetch/cookie/register.html b/dom/serviceworkers/test/fetch/cookie/register.html new file mode 100644 index 0000000000..99eabaf0a2 --- /dev/null +++ b/dom/serviceworkers/test/fetch/cookie/register.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<script src="../../utils.js"></script> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + document.cookie = "foo=bar"; + + navigator.serviceWorker.register("cookie_test.js", {scope: "."}) + .then(reg => { + return waitForState(reg.installing, "activated", reg); + }).then(done); +</script> diff --git a/dom/serviceworkers/test/fetch/cookie/unregister.html b/dom/serviceworkers/test/fetch/cookie/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/cookie/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script> diff --git a/dom/serviceworkers/test/fetch/deliver-gzip.sjs b/dom/serviceworkers/test/fetch/deliver-gzip.sjs new file mode 100644 index 0000000000..d7cbdef06e --- /dev/null +++ b/dom/serviceworkers/test/fetch/deliver-gzip.sjs @@ -0,0 +1,21 @@ +"use strict"; + +function handleRequest(request, response) { + // The string "hello" repeated 10 times followed by newline. Compressed using gzip. + // prettier-ignore + let bytes = [0x1f, 0x8b, 0x08, 0x08, 0x4d, 0xe2, 0xf9, 0x54, 0x00, 0x03, 0x68, + 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0xcf, + 0x20, 0x85, 0xe0, 0x02, 0x00, 0xf5, 0x4b, 0x38, 0xcf, 0x33, 0x00, + 0x00, 0x00]; + + response.setHeader("Content-Encoding", "gzip", false); + response.setHeader("Content-Length", "" + bytes.length, false); + response.setHeader("Content-Type", "text/plain", false); + + let bos = Components.classes[ + "@mozilla.org/binaryoutputstream;1" + ].createInstance(Components.interfaces.nsIBinaryOutputStream); + bos.setOutputStream(response.bodyOutputStream); + + bos.writeByteArray(bytes); +} diff --git a/dom/serviceworkers/test/fetch/fetch_tests.js b/dom/serviceworkers/test/fetch/fetch_tests.js new file mode 100644 index 0000000000..69b8a89679 --- /dev/null +++ b/dom/serviceworkers/test/fetch/fetch_tests.js @@ -0,0 +1,716 @@ +var origin = "http://mochi.test:8888"; + +function fetchXHRWithMethod(name, method, onload, onerror, headers) { + expectAsyncResult(); + + onload = + onload || + function () { + my_ok(false, "XHR load should not complete successfully"); + finish(); + }; + onerror = + onerror || + function () { + my_ok( + false, + "XHR load for " + name + " should be intercepted successfully" + ); + finish(); + }; + + var x = new XMLHttpRequest(); + x.open(method, name, true); + x.onload = function () { + onload(x); + }; + x.onerror = function () { + onerror(x); + }; + headers = headers || []; + headers.forEach(function (header) { + x.setRequestHeader(header[0], header[1]); + }); + x.send(); +} + +var corsServerPath = + "/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs"; +var corsServerURL = "http://example.com" + corsServerPath; + +function redirectURL(hops) { + return ( + hops[0].server + + corsServerPath + + "?hop=1&hops=" + + encodeURIComponent(JSON.stringify(hops)) + ); +} + +function fetchXHR(name, onload, onerror, headers) { + return fetchXHRWithMethod(name, "GET", onload, onerror, headers); +} + +fetchXHR("bare-synthesized.txt", function (xhr) { + my_ok(xhr.status == 200, "load should be successful"); + my_ok( + xhr.responseText == "synthesized response body", + "load should have synthesized response" + ); + finish(); +}); + +fetchXHR("test-respondwith-response.txt", function (xhr) { + my_ok( + xhr.status == 200, + "test-respondwith-response load should be successful" + ); + my_ok( + xhr.responseText == "test-respondwith-response response body", + "load should have response" + ); + finish(); +}); + +fetchXHR("synthesized-404.txt", function (xhr) { + my_ok(xhr.status == 404, "load should 404"); + my_ok( + xhr.responseText == "synthesized response body", + "404 load should have synthesized response" + ); + finish(); +}); + +fetchXHR("synthesized-headers.txt", function (xhr) { + my_ok(xhr.status == 200, "load should be successful"); + my_ok( + xhr.getResponseHeader("X-Custom-Greeting") === "Hello", + "custom header should be set" + ); + my_ok( + xhr.responseText == "synthesized response body", + "custom header load should have synthesized response" + ); + finish(); +}); + +fetchXHR("synthesized-redirect-real-file.txt", function (xhr) { + dump("Got status AARRGH " + xhr.status + " " + xhr.responseText + "\n"); + my_ok(xhr.status == 200, "load should be successful"); + my_ok( + xhr.responseText == "This is a real file.\n", + "Redirect to real file should complete." + ); + finish(); +}); + +fetchXHR("synthesized-redirect-twice-real-file.txt", function (xhr) { + my_ok(xhr.status == 200, "load should be successful"); + my_ok( + xhr.responseText == "This is a real file.\n", + "Redirect to real file (twice) should complete." + ); + finish(); +}); + +fetchXHR("synthesized-redirect-synthesized.txt", function (xhr) { + my_ok(xhr.status == 200, "synth+redirect+synth load should be successful"); + my_ok( + xhr.responseText == "synthesized response body", + "load should have redirected+synthesized response" + ); + finish(); +}); + +fetchXHR("synthesized-redirect-twice-synthesized.txt", function (xhr) { + my_ok( + xhr.status == 200, + "synth+redirect+synth (twice) load should be successful" + ); + my_ok( + xhr.responseText == "synthesized response body", + "load should have redirected+synthesized (twice) response" + ); + finish(); +}); + +fetchXHR("redirect.sjs", function (xhr) { + my_ok(xhr.status == 404, "redirected load should be uninterrupted"); + finish(); +}); + +fetchXHR("ignored.txt", function (xhr) { + my_ok(xhr.status == 404, "load should be uninterrupted"); + finish(); +}); + +fetchXHR("rejected.txt", null, function (xhr) { + my_ok(xhr.status == 0, "load should not complete"); + finish(); +}); + +fetchXHR("nonresponse.txt", null, function (xhr) { + my_ok(xhr.status == 0, "load should not complete"); + finish(); +}); + +fetchXHR("nonresponse2.txt", null, function (xhr) { + my_ok(xhr.status == 0, "load should not complete"); + finish(); +}); + +fetchXHR("nonpromise.txt", null, function (xhr) { + my_ok(xhr.status == 0, "load should not complete"); + finish(); +}); + +fetchXHR( + "headers.txt", + function (xhr) { + my_ok(xhr.status == 200, "load should be successful"); + my_ok(xhr.responseText == "1", "request header checks should have passed"); + finish(); + }, + null, + [ + ["X-Test1", "header1"], + ["X-Test2", "header2"], + ] +); + +fetchXHR("http://user:pass@mochi.test:8888/user-pass", function (xhr) { + my_ok(xhr.status == 200, "load should be successful"); + my_ok( + xhr.responseText == "http://user:pass@mochi.test:8888/user-pass", + "The username and password should be preserved" + ); + finish(); +}); + +fetchXHR("readable-stream.txt", function (xhr) { + my_ok(xhr.status == 200, "loading completed"); + my_ok(xhr.responseText == "Hello!", "The message is correct!"); + finish(); +}); + +fetchXHR( + "readable-stream-locked.txt", + function (xhr) { + my_ok(false, "This should not be called!"); + finish(); + }, + function () { + my_ok(true, "The exception has been correctly handled!"); + finish(); + } +); + +fetchXHR( + "readable-stream-with-exception.txt", + function (xhr) { + my_ok(false, "This should not be called!"); + finish(); + }, + function () { + my_ok(true, "The exception has been correctly handled!"); + finish(); + } +); + +fetchXHR( + "readable-stream-with-exception2.txt", + function (xhr) { + my_ok(false, "This should not be called!"); + finish(); + }, + function () { + my_ok(true, "The exception has been correctly handled!"); + finish(); + } +); + +fetchXHR( + "readable-stream-already-consumed.txt", + function (xhr) { + my_ok(false, "This should not be called!"); + finish(); + }, + function () { + my_ok(true, "The exception has been correctly handled!"); + finish(); + } +); + +var expectedUncompressedResponse = ""; +for (let i = 0; i < 10; ++i) { + expectedUncompressedResponse += "hello"; +} +expectedUncompressedResponse += "\n"; + +// ServiceWorker does not intercept, at which point the network request should +// be correctly decoded. +fetchXHR("deliver-gzip.sjs", function (xhr) { + my_ok(xhr.status == 200, "network gzip load should be successful"); + my_ok( + xhr.responseText == expectedUncompressedResponse, + "network gzip load should have synthesized response." + ); + my_ok( + xhr.getResponseHeader("Content-Encoding") == "gzip", + "network Content-Encoding should be gzip." + ); + my_ok( + xhr.getResponseHeader("Content-Length") == "35", + "network Content-Length should be of original gzipped file." + ); + finish(); +}); + +fetchXHR("hello.gz", function (xhr) { + my_ok(xhr.status == 200, "gzip load should be successful"); + my_ok( + xhr.responseText == expectedUncompressedResponse, + "gzip load should have synthesized response." + ); + my_ok( + xhr.getResponseHeader("Content-Encoding") == "gzip", + "Content-Encoding should be gzip." + ); + my_ok( + xhr.getResponseHeader("Content-Length") == "35", + "Content-Length should be of original gzipped file." + ); + finish(); +}); + +fetchXHR("hello-after-extracting.gz", function (xhr) { + my_ok(xhr.status == 200, "gzip load after extracting should be successful"); + my_ok( + xhr.responseText == expectedUncompressedResponse, + "gzip load after extracting should have synthesized response." + ); + my_ok( + xhr.getResponseHeader("Content-Encoding") == "gzip", + "Content-Encoding after extracting should be gzip." + ); + my_ok( + xhr.getResponseHeader("Content-Length") == "35", + "Content-Length after extracting should be of original gzipped file." + ); + finish(); +}); + +fetchXHR(corsServerURL + "?status=200&allowOrigin=*", function (xhr) { + my_ok( + xhr.status == 200, + "cross origin load with correct headers should be successful" + ); + my_ok( + xhr.getResponseHeader("access-control-allow-origin") == null, + "cors headers should be filtered out" + ); + finish(); +}); + +// Verify origin header is sent properly even when we have a no-intercept SW. +var uriOrigin = encodeURIComponent(origin); +fetchXHR( + "http://example.org" + + corsServerPath + + "?ignore&status=200&origin=" + + uriOrigin + + "&allowOrigin=" + + uriOrigin, + function (xhr) { + my_ok( + xhr.status == 200, + "cross origin load with correct headers should be successful" + ); + my_ok( + xhr.getResponseHeader("access-control-allow-origin") == null, + "cors headers should be filtered out" + ); + finish(); + } +); + +// Verify that XHR is considered CORS tainted even when original URL is same-origin +// redirected to cross-origin. +fetchXHR( + redirectURL([ + { server: origin }, + { server: "http://example.org", allowOrigin: origin }, + ]), + function (xhr) { + my_ok( + xhr.status == 200, + "cross origin load with correct headers should be successful" + ); + my_ok( + xhr.getResponseHeader("access-control-allow-origin") == null, + "cors headers should be filtered out" + ); + finish(); + } +); + +// Test that CORS preflight requests cannot be intercepted. Performs a +// cross-origin XHR that the SW chooses not to intercept. This requires a +// preflight request, which the SW must not be allowed to intercept. +fetchXHR( + corsServerURL + "?status=200&allowOrigin=*", + null, + function (xhr) { + my_ok( + xhr.status == 0, + "cross origin load with incorrect headers should be a failure" + ); + finish(); + }, + [["X-Unsafe", "unsafe"]] +); + +// Test that CORS preflight requests cannot be intercepted. Performs a +// cross-origin XHR that the SW chooses to intercept and respond with a +// cross-origin fetch. This requires a preflight request, which the SW must not +// be allowed to intercept. +fetchXHR( + "http://example.org" + corsServerPath + "?status=200&allowOrigin=*", + null, + function (xhr) { + my_ok( + xhr.status == 0, + "cross origin load with incorrect headers should be a failure" + ); + finish(); + }, + [["X-Unsafe", "unsafe"]] +); + +// Test that when the page fetches a url the controlling SW forces a redirect to +// another location. This other location fetch should also be intercepted by +// the SW. +fetchXHR("something.txt", function (xhr) { + my_ok(xhr.status == 200, "load should be successful"); + my_ok( + xhr.responseText == "something else response body", + "load should have something else" + ); + finish(); +}); + +// Test fetch will internally get it's SkipServiceWorker flag set. The request is +// made from the SW through fetch(). fetch() fetches a server-side JavaScript +// file that force a redirect. The redirect location fetch does not go through +// the SW. +fetchXHR("redirect_serviceworker.sjs", function (xhr) { + my_ok(xhr.status == 200, "load should be successful"); + my_ok( + xhr.responseText == "// empty worker, always succeed!\n", + "load should have redirection content" + ); + finish(); +}); + +fetchXHR( + "empty-header", + function (xhr) { + my_ok(xhr.status == 200, "load should be successful"); + my_ok( + xhr.responseText == "emptyheader", + "load should have the expected content" + ); + finish(); + }, + null, + [["emptyheader", ""]] +); + +expectAsyncResult(); +fetch( + "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*" +).then( + function (res) { + my_ok(res.ok, "Valid CORS request should receive valid response"); + my_ok(res.type == "cors", "Response type should be CORS"); + res.text().then(function (body) { + my_ok( + body === "<res>hello pass</res>\n", + "cors response body should match" + ); + finish(); + }); + }, + function (e) { + my_ok(false, "CORS Fetch failed"); + finish(); + } +); + +expectAsyncResult(); +fetch( + "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200", + { mode: "no-cors" } +).then( + function (res) { + my_ok(res.type == "opaque", "Response type should be opaque"); + my_ok(res.status == 0, "Status should be 0"); + res.text().then(function (body) { + my_ok(body === "", "opaque response body should be empty"); + finish(); + }); + }, + function (e) { + my_ok(false, "no-cors Fetch failed"); + finish(); + } +); + +expectAsyncResult(); +fetch("opaque-on-same-origin").then( + function (res) { + my_ok( + false, + "intercepted opaque response for non no-cors request should fail." + ); + finish(); + }, + function (e) { + my_ok( + true, + "intercepted opaque response for non no-cors request should fail." + ); + finish(); + } +); + +expectAsyncResult(); +fetch("http://example.com/opaque-no-cors", { mode: "no-cors" }).then( + function (res) { + my_ok( + res.type == "opaque", + "intercepted opaque response for no-cors request should have type opaque." + ); + finish(); + }, + function (e) { + my_ok( + false, + "intercepted opaque response for no-cors request should pass." + ); + finish(); + } +); + +expectAsyncResult(); +fetch("http://example.com/cors-for-no-cors", { mode: "no-cors" }).then( + function (res) { + my_ok( + res.type == "cors", + "synthesize CORS response should result in outer CORS response" + ); + finish(); + }, + function (e) { + my_ok(false, "cors-for-no-cors request should not reject"); + finish(); + } +); + +function arrayBufferFromString(str) { + var arr = new Uint8Array(str.length); + for (let i = 0; i < str.length; ++i) { + arr[i] = str.charCodeAt(i); + } + return arr; +} + +expectAsyncResult(); +fetch(new Request("body-simple", { method: "POST", body: "my body" })) + .then(function (res) { + return res.text(); + }) + .then(function (body) { + my_ok( + body == "my bodymy body", + "the body of the intercepted fetch should be visible in the SW" + ); + finish(); + }); + +expectAsyncResult(); +fetch( + new Request("body-arraybufferview", { + method: "POST", + body: arrayBufferFromString("my body"), + }) +) + .then(function (res) { + return res.text(); + }) + .then(function (body) { + my_ok( + body == "my bodymy body", + "the ArrayBufferView body of the intercepted fetch should be visible in the SW" + ); + finish(); + }); + +expectAsyncResult(); +fetch( + new Request("body-arraybuffer", { + method: "POST", + body: arrayBufferFromString("my body").buffer, + }) +) + .then(function (res) { + return res.text(); + }) + .then(function (body) { + my_ok( + body == "my bodymy body", + "the ArrayBuffer body of the intercepted fetch should be visible in the SW" + ); + finish(); + }); + +expectAsyncResult(); +var usp = new URLSearchParams(); +usp.set("foo", "bar"); +usp.set("baz", "qux"); +fetch(new Request("body-urlsearchparams", { method: "POST", body: usp })) + .then(function (res) { + return res.text(); + }) + .then(function (body) { + my_ok( + body == "foo=bar&baz=quxfoo=bar&baz=qux", + "the URLSearchParams body of the intercepted fetch should be visible in the SW" + ); + finish(); + }); + +expectAsyncResult(); +var fd = new FormData(); +fd.set("foo", "bar"); +fd.set("baz", "qux"); +fetch(new Request("body-formdata", { method: "POST", body: fd })) + .then(function (res) { + return res.text(); + }) + .then(function (body) { + my_ok( + body.indexOf('Content-Disposition: form-data; name="foo"\r\n\r\nbar') < + body.indexOf('Content-Disposition: form-data; name="baz"\r\n\r\nqux'), + "the FormData body of the intercepted fetch should be visible in the SW" + ); + finish(); + }); + +expectAsyncResult(); +fetch( + new Request("body-blob", { + method: "POST", + body: new Blob(new String("my body")), + }) +) + .then(function (res) { + return res.text(); + }) + .then(function (body) { + my_ok( + body == "my bodymy body", + "the Blob body of the intercepted fetch should be visible in the SW" + ); + finish(); + }); + +expectAsyncResult(); +fetch("interrupt.sjs").then( + function (res) { + my_ok(true, "interrupted fetch succeeded"); + res.text().then( + function (body) { + my_ok(false, "interrupted fetch shouldn't have complete body"); + finish(); + }, + function () { + my_ok(true, "interrupted fetch shouldn't have complete body"); + finish(); + } + ); + }, + function (e) { + my_ok(false, "interrupted fetch failed"); + finish(); + } +); + +["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"].forEach(function (method) { + fetchXHRWithMethod("xhr-method-test.txt", method, function (xhr) { + my_ok(xhr.status == 200, method + " load should be successful"); + if (method === "HEAD") { + my_ok( + xhr.responseText == "", + method + "load should not have synthesized response" + ); + } else { + my_ok( + xhr.responseText == "intercepted " + method, + method + " load should have synthesized response" + ); + } + finish(); + }); +}); + +expectAsyncResult(); +fetch(new Request("empty-header", { headers: { emptyheader: "" } })) + .then(function (res) { + return res.text(); + }) + .then( + function (body) { + my_ok( + body == "emptyheader", + "The empty header was observed in the fetch event" + ); + finish(); + }, + function (err) { + my_ok(false, "A promise was rejected with " + err); + finish(); + } + ); + +expectAsyncResult(); +fetch("fetchevent-extendable") + .then(function (res) { + return res.text(); + }) + .then( + function (body) { + my_ok(body == "extendable", "FetchEvent inherits from ExtendableEvent"); + finish(); + }, + function (err) { + my_ok(false, "A promise was rejected with " + err); + finish(); + } + ); + +expectAsyncResult(); +fetch("fetchevent-request") + .then(function (res) { + return res.text(); + }) + .then( + function (body) { + my_ok(body == "non-nullable", "FetchEvent.request must be non-nullable"); + finish(); + }, + function (err) { + my_ok(false, "A promise was rejected with " + err); + finish(); + } + ); diff --git a/dom/serviceworkers/test/fetch/fetch_worker_script.js b/dom/serviceworkers/test/fetch/fetch_worker_script.js new file mode 100644 index 0000000000..6eb0b18a77 --- /dev/null +++ b/dom/serviceworkers/test/fetch/fetch_worker_script.js @@ -0,0 +1,28 @@ +function my_ok(v, msg) { + postMessage({ type: "ok", value: v, msg }); +} + +function finish() { + postMessage("finish"); +} + +function expectAsyncResult() { + postMessage("expect"); +} + +expectAsyncResult(); +try { + var success = false; + importScripts("nonexistent_imported_script.js"); +} catch (x) {} + +my_ok(success, "worker imported script should be intercepted"); +finish(); + +function check_intercepted_script() { + success = true; +} + +importScripts("fetch_tests.js"); + +finish(); //corresponds to the gExpected increment before creating this worker diff --git a/dom/serviceworkers/test/fetch/hsts/embedder.html b/dom/serviceworkers/test/fetch/hsts/embedder.html new file mode 100644 index 0000000000..ad44809042 --- /dev/null +++ b/dom/serviceworkers/test/fetch/hsts/embedder.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<script> + window.onmessage = function(e) { + window.parent.postMessage(e.data, "*"); + }; +</script> +<iframe src="http://example.com/tests/dom/serviceworkers/test/fetch/hsts/index.html"></iframe> diff --git a/dom/serviceworkers/test/fetch/hsts/hsts_test.js b/dom/serviceworkers/test/fetch/hsts/hsts_test.js new file mode 100644 index 0000000000..74b9ed23ba --- /dev/null +++ b/dom/serviceworkers/test/fetch/hsts/hsts_test.js @@ -0,0 +1,11 @@ +self.addEventListener("fetch", function (event) { + if (event.request.url.includes("index.html")) { + event.respondWith(fetch("realindex.html")); + } else if (event.request.url.includes("image-20px.png")) { + if (event.request.url.indexOf("https://") == 0) { + event.respondWith(fetch("image-40px.png")); + } else { + event.respondWith(Response.error()); + } + } +}); diff --git a/dom/serviceworkers/test/fetch/hsts/image-20px.png b/dom/serviceworkers/test/fetch/hsts/image-20px.png Binary files differnew file mode 100644 index 0000000000..ae6a8a6b88 --- /dev/null +++ b/dom/serviceworkers/test/fetch/hsts/image-20px.png diff --git a/dom/serviceworkers/test/fetch/hsts/image-40px.png b/dom/serviceworkers/test/fetch/hsts/image-40px.png Binary files differnew file mode 100644 index 0000000000..fe391dc8a2 --- /dev/null +++ b/dom/serviceworkers/test/fetch/hsts/image-40px.png diff --git a/dom/serviceworkers/test/fetch/hsts/image.html b/dom/serviceworkers/test/fetch/hsts/image.html new file mode 100644 index 0000000000..7036ea954e --- /dev/null +++ b/dom/serviceworkers/test/fetch/hsts/image.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<script> +onload=function(){ + var img = new Image(); + img.src = "http://example.com/tests/dom/serviceworkers/test/fetch/hsts/image-20px.png"; + img.onload = function() { + window.parent.postMessage({status: "image", data: img.width}, "*"); + }; + img.onerror = function() { + window.parent.postMessage({status: "image", data: "error"}, "*"); + }; +}; +</script> diff --git a/dom/serviceworkers/test/fetch/hsts/realindex.html b/dom/serviceworkers/test/fetch/hsts/realindex.html new file mode 100644 index 0000000000..e7d282fe83 --- /dev/null +++ b/dom/serviceworkers/test/fetch/hsts/realindex.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<script> + var securityInfoPresent = !!SpecialPowers.wrap(window).docShell.currentDocumentChannel.securityInfo; + window.parent.postMessage({status: "protocol", + data: location.protocol, + securityInfoPresent}, + "*"); +</script> diff --git a/dom/serviceworkers/test/fetch/hsts/register.html b/dom/serviceworkers/test/fetch/hsts/register.html new file mode 100644 index 0000000000..bcdc146aec --- /dev/null +++ b/dom/serviceworkers/test/fetch/hsts/register.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + navigator.serviceWorker.ready.then(done); + navigator.serviceWorker.register("hsts_test.js", {scope: "."}); +</script> diff --git a/dom/serviceworkers/test/fetch/hsts/register.html^headers^ b/dom/serviceworkers/test/fetch/hsts/register.html^headers^ new file mode 100644 index 0000000000..a46bf65bd9 --- /dev/null +++ b/dom/serviceworkers/test/fetch/hsts/register.html^headers^ @@ -0,0 +1,2 @@ +Cache-Control: no-cache +Strict-Transport-Security: max-age=60 diff --git a/dom/serviceworkers/test/fetch/hsts/unregister.html b/dom/serviceworkers/test/fetch/hsts/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/hsts/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script> diff --git a/dom/serviceworkers/test/fetch/https/clonedresponse/https_test.js b/dom/serviceworkers/test/fetch/https/clonedresponse/https_test.js new file mode 100644 index 0000000000..8ab34123af --- /dev/null +++ b/dom/serviceworkers/test/fetch/https/clonedresponse/https_test.js @@ -0,0 +1,19 @@ +self.addEventListener("install", function (event) { + event.waitUntil( + caches.open("cache").then(function (cache) { + return cache.add("index.html"); + }) + ); +}); + +self.addEventListener("fetch", function (event) { + if (event.request.url.includes("index.html")) { + event.respondWith( + new Promise(function (resolve, reject) { + caches.match(event.request).then(function (response) { + resolve(response.clone()); + }); + }) + ); + } +}); diff --git a/dom/serviceworkers/test/fetch/https/clonedresponse/index.html b/dom/serviceworkers/test/fetch/https/clonedresponse/index.html new file mode 100644 index 0000000000..a435548443 --- /dev/null +++ b/dom/serviceworkers/test/fetch/https/clonedresponse/index.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<script> + window.parent.postMessage({status: "done"}, "*"); +</script> diff --git a/dom/serviceworkers/test/fetch/https/clonedresponse/register.html b/dom/serviceworkers/test/fetch/https/clonedresponse/register.html new file mode 100644 index 0000000000..41774f70d1 --- /dev/null +++ b/dom/serviceworkers/test/fetch/https/clonedresponse/register.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + navigator.serviceWorker.ready.then(done); + navigator.serviceWorker.register("https_test.js", {scope: "."}); +</script> diff --git a/dom/serviceworkers/test/fetch/https/clonedresponse/unregister.html b/dom/serviceworkers/test/fetch/https/clonedresponse/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/https/clonedresponse/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script> diff --git a/dom/serviceworkers/test/fetch/https/https_test.js b/dom/serviceworkers/test/fetch/https/https_test.js new file mode 100644 index 0000000000..5f20690bb5 --- /dev/null +++ b/dom/serviceworkers/test/fetch/https/https_test.js @@ -0,0 +1,31 @@ +self.addEventListener("install", function (event) { + event.waitUntil( + caches.open("cache").then(function (cache) { + var synth = new Response( + '<!DOCTYPE html><script>window.parent.postMessage({status: "done-synth-sw"}, "*");</script>', + { headers: { "Content-Type": "text/html" } } + ); + return Promise.all([ + cache.add("index.html"), + cache.put("synth-sw.html", synth), + ]); + }) + ); +}); + +self.addEventListener("fetch", function (event) { + if (event.request.url.includes("index.html")) { + event.respondWith(caches.match(event.request)); + } else if (event.request.url.includes("synth-sw.html")) { + event.respondWith(caches.match(event.request)); + } else if (event.request.url.includes("synth-window.html")) { + event.respondWith(caches.match(event.request)); + } else if (event.request.url.includes("synth.html")) { + event.respondWith( + new Response( + '<!DOCTYPE html><script>window.parent.postMessage({status: "done-synth"}, "*");</script>', + { headers: { "Content-Type": "text/html" } } + ) + ); + } +}); diff --git a/dom/serviceworkers/test/fetch/https/index.html b/dom/serviceworkers/test/fetch/https/index.html new file mode 100644 index 0000000000..a435548443 --- /dev/null +++ b/dom/serviceworkers/test/fetch/https/index.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<script> + window.parent.postMessage({status: "done"}, "*"); +</script> diff --git a/dom/serviceworkers/test/fetch/https/register.html b/dom/serviceworkers/test/fetch/https/register.html new file mode 100644 index 0000000000..fa666fe957 --- /dev/null +++ b/dom/serviceworkers/test/fetch/https/register.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + navigator.serviceWorker.ready.then(reg => { + return window.caches.open("cache").then(function(cache) { + var synth = new Response('<!DOCTYPE html><script>window.parent.postMessage({status: "done-synth-window"}, "*");</scri' + 'pt>', + {headers:{"Content-Type": "text/html"}}); + return cache.put('synth-window.html', synth).then(_ => done(reg)); + }); + }); + navigator.serviceWorker.register("https_test.js", {scope: "."}); +</script> diff --git a/dom/serviceworkers/test/fetch/https/unregister.html b/dom/serviceworkers/test/fetch/https/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/https/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script> diff --git a/dom/serviceworkers/test/fetch/imagecache-maxage/image-20px.png b/dom/serviceworkers/test/fetch/imagecache-maxage/image-20px.png Binary files differnew file mode 100644 index 0000000000..ae6a8a6b88 --- /dev/null +++ b/dom/serviceworkers/test/fetch/imagecache-maxage/image-20px.png diff --git a/dom/serviceworkers/test/fetch/imagecache-maxage/image-40px.png b/dom/serviceworkers/test/fetch/imagecache-maxage/image-40px.png Binary files differnew file mode 100644 index 0000000000..fe391dc8a2 --- /dev/null +++ b/dom/serviceworkers/test/fetch/imagecache-maxage/image-40px.png diff --git a/dom/serviceworkers/test/fetch/imagecache-maxage/index.html b/dom/serviceworkers/test/fetch/imagecache-maxage/index.html new file mode 100644 index 0000000000..0d4c52eedd --- /dev/null +++ b/dom/serviceworkers/test/fetch/imagecache-maxage/index.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<script> +var width, url, width2, url2; +function maybeReport() { + if (width !== undefined && url !== undefined && + width2 !== undefined && url2 !== undefined) { + window.parent.postMessage({status: "result", + width, + width2, + url, + url2}, "*"); + } +} +onload = function() { + width = document.querySelector("img").width; + width2 = document.querySelector("img").width; + maybeReport(); +}; +navigator.serviceWorker.onmessage = function(event) { + if (event.data.suffix == "2") { + url2 = event.data.url; + } else { + url = event.data.url; + } + maybeReport(); +}; +</script> +<img src="image.png"> +<img src="image2.png"> diff --git a/dom/serviceworkers/test/fetch/imagecache-maxage/maxage_test.js b/dom/serviceworkers/test/fetch/imagecache-maxage/maxage_test.js new file mode 100644 index 0000000000..c664e07c28 --- /dev/null +++ b/dom/serviceworkers/test/fetch/imagecache-maxage/maxage_test.js @@ -0,0 +1,45 @@ +function synthesizeImage(suffix) { + // Serve image-20px for the first page, and image-40px for the second page. + return clients + .matchAll() + .then(clients => { + var url = "image-20px.png"; + clients.forEach(client => { + if (client.url.indexOf("?new") > 0) { + url = "image-40px.png"; + } + client.postMessage({ suffix, url }); + }); + return fetch(url); + }) + .then(response => { + return response.arrayBuffer(); + }) + .then(ab => { + var headers; + if (suffix == "") { + headers = { + "Content-Type": "image/png", + Date: "Tue, 1 Jan 1990 01:02:03 GMT", + "Cache-Control": "max-age=1", + }; + } else { + headers = { + "Content-Type": "image/png", + "Cache-Control": "no-cache", + }; + } + return new Response(ab, { + status: 200, + headers, + }); + }); +} + +self.addEventListener("fetch", function (event) { + if (event.request.url.includes("image.png")) { + event.respondWith(synthesizeImage("")); + } else if (event.request.url.includes("image2.png")) { + event.respondWith(synthesizeImage("2")); + } +}); diff --git a/dom/serviceworkers/test/fetch/imagecache-maxage/register.html b/dom/serviceworkers/test/fetch/imagecache-maxage/register.html new file mode 100644 index 0000000000..af4dde2e29 --- /dev/null +++ b/dom/serviceworkers/test/fetch/imagecache-maxage/register.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + navigator.serviceWorker.ready.then(done); + navigator.serviceWorker.register("maxage_test.js", {scope: "."}); +</script> diff --git a/dom/serviceworkers/test/fetch/imagecache-maxage/unregister.html b/dom/serviceworkers/test/fetch/imagecache-maxage/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/imagecache-maxage/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script> diff --git a/dom/serviceworkers/test/fetch/imagecache/image-20px.png b/dom/serviceworkers/test/fetch/imagecache/image-20px.png Binary files differnew file mode 100644 index 0000000000..ae6a8a6b88 --- /dev/null +++ b/dom/serviceworkers/test/fetch/imagecache/image-20px.png diff --git a/dom/serviceworkers/test/fetch/imagecache/image-40px.png b/dom/serviceworkers/test/fetch/imagecache/image-40px.png Binary files differnew file mode 100644 index 0000000000..fe391dc8a2 --- /dev/null +++ b/dom/serviceworkers/test/fetch/imagecache/image-40px.png diff --git a/dom/serviceworkers/test/fetch/imagecache/imagecache_test.js b/dom/serviceworkers/test/fetch/imagecache/imagecache_test.js new file mode 100644 index 0000000000..cd8f522728 --- /dev/null +++ b/dom/serviceworkers/test/fetch/imagecache/imagecache_test.js @@ -0,0 +1,15 @@ +function synthesizeImage() { + return clients.matchAll().then(clients => { + var url = "image-40px.png"; + clients.forEach(client => { + client.postMessage(url); + }); + return fetch(url); + }); +} + +self.addEventListener("fetch", function (event) { + if (event.request.url.includes("image-20px.png")) { + event.respondWith(synthesizeImage()); + } +}); diff --git a/dom/serviceworkers/test/fetch/imagecache/index.html b/dom/serviceworkers/test/fetch/imagecache/index.html new file mode 100644 index 0000000000..f634f68bb7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/imagecache/index.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<script> +var width, url; +function maybeReport() { + if (width !== undefined && url !== undefined) { + window.parent.postMessage({status: "result", + width, + url}, "*"); + } +} +onload = function() { + width = document.querySelector("img").width; + maybeReport(); +}; +navigator.serviceWorker.onmessage = function(event) { + url = event.data; + maybeReport(); +}; +</script> +<img src="image-20px.png"> diff --git a/dom/serviceworkers/test/fetch/imagecache/postmortem.html b/dom/serviceworkers/test/fetch/imagecache/postmortem.html new file mode 100644 index 0000000000..53356cd02c --- /dev/null +++ b/dom/serviceworkers/test/fetch/imagecache/postmortem.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<script> +onload = function() { + var width = document.querySelector("img").width; + window.parent.postMessage({status: "postmortem", + width}, "*"); +}; +</script> +<img src="image-20px.png"> diff --git a/dom/serviceworkers/test/fetch/imagecache/register.html b/dom/serviceworkers/test/fetch/imagecache/register.html new file mode 100644 index 0000000000..f6d1eb382f --- /dev/null +++ b/dom/serviceworkers/test/fetch/imagecache/register.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<!-- Load the image here to put it in the image cache --> +<img src="image-20px.png"> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + navigator.serviceWorker.ready.then(done); + navigator.serviceWorker.register("imagecache_test.js", {scope: "."}); +</script> diff --git a/dom/serviceworkers/test/fetch/imagecache/unregister.html b/dom/serviceworkers/test/fetch/imagecache/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/imagecache/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script> diff --git a/dom/serviceworkers/test/fetch/importscript-mixedcontent/https_test.js b/dom/serviceworkers/test/fetch/importscript-mixedcontent/https_test.js new file mode 100644 index 0000000000..138ca768aa --- /dev/null +++ b/dom/serviceworkers/test/fetch/importscript-mixedcontent/https_test.js @@ -0,0 +1,31 @@ +function sendResponseToParent(response) { + return ` + <!DOCTYPE html> + <script> + window.parent.postMessage({status: "done", data: "${response}"}, "*"); + </script> + `; +} + +self.addEventListener("fetch", function (event) { + if (event.request.url.includes("index.html")) { + var response = "good"; + try { + importScripts("http://example.org/tests/dom/workers/test/foreign.js"); + } catch (e) { + dump("Got error " + e + " when importing the script\n"); + } + if (response === "good") { + try { + importScripts("/tests/dom/workers/test/redirect_to_foreign.sjs"); + } catch (e) { + dump("Got error " + e + " when importing the script\n"); + } + } + event.respondWith( + new Response(sendResponseToParent(response), { + headers: { "Content-Type": "text/html" }, + }) + ); + } +}); diff --git a/dom/serviceworkers/test/fetch/importscript-mixedcontent/register.html b/dom/serviceworkers/test/fetch/importscript-mixedcontent/register.html new file mode 100644 index 0000000000..41774f70d1 --- /dev/null +++ b/dom/serviceworkers/test/fetch/importscript-mixedcontent/register.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + navigator.serviceWorker.ready.then(done); + navigator.serviceWorker.register("https_test.js", {scope: "."}); +</script> diff --git a/dom/serviceworkers/test/fetch/importscript-mixedcontent/unregister.html b/dom/serviceworkers/test/fetch/importscript-mixedcontent/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/importscript-mixedcontent/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script> diff --git a/dom/serviceworkers/test/fetch/index.html b/dom/serviceworkers/test/fetch/index.html new file mode 100644 index 0000000000..693810c6fc --- /dev/null +++ b/dom/serviceworkers/test/fetch/index.html @@ -0,0 +1,191 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 94048 - test install event.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<div id="style-test" style="background-color: white"></div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + function my_ok(result, msg) { + window.opener.postMessage({status: "ok", result, message: msg}, "*"); + } + + function check_intercepted_script() { + document.getElementById('intercepted-script').test_result = + document.currentScript == document.getElementById('intercepted-script'); + } + + function fetchXHR(name, onload, onerror, headers) { + gExpected++; + + onload = onload || function() { + my_ok(false, "load should not complete successfully"); + finish(); + }; + onerror = onerror || function() { + my_ok(false, "load should be intercepted successfully"); + finish(); + }; + + var x = new XMLHttpRequest(); + x.open('GET', name, true); + x.onload = function() { onload(x) }; + x.onerror = function() { onerror(x) }; + headers = headers || []; + headers.forEach(function(header) { + x.setRequestHeader(header[0], header[1]); + }); + x.send(); + } + + var gExpected = 0; + var gEncountered = 0; + function finish() { + gEncountered++; + if (gEncountered == gExpected) { + window.opener.postMessage({status: "done"}, "*"); + } + } + + function test_onload(creator, complete) { + gExpected++; + var elem = creator(); + elem.onload = function() { + complete.call(elem); + finish(); + }; + elem.onerror = function() { + my_ok(false, elem.tagName + " load should complete successfully"); + finish(); + }; + document.body.appendChild(elem); + } + + function expectAsyncResult() { + gExpected++; + } + + my_ok(navigator.serviceWorker.controller != null, "should be controlled"); +</script> +<script src="fetch_tests.js"></script> +<script> + test_onload(function() { + var elem = document.createElement('img'); + elem.src = "nonexistent_image.gifs"; + elem.id = 'intercepted-img'; + return elem; + }, function() { + my_ok(this.complete, "image should be complete"); + my_ok(this.naturalWidth == 1 && this.naturalHeight == 1, "image should be 1x1 gif"); + }); + + test_onload(function() { + var elem = document.createElement('script'); + elem.id = 'intercepted-script'; + elem.src = "nonexistent_script.js"; + return elem; + }, function() { + my_ok(this.test_result, "script load should be intercepted"); + }); + + test_onload(function() { + var elem = document.createElement('link'); + elem.href = "nonexistent_stylesheet.css"; + elem.rel = "stylesheet"; + return elem; + }, function() { + var styled = document.getElementById('style-test'); + my_ok(window.getComputedStyle(styled).backgroundColor == 'rgb(0, 0, 0)', + "stylesheet load should be intercepted"); + }); + + test_onload(function() { + var elem = document.createElement('iframe'); + elem.id = 'intercepted-iframe'; + elem.src = "nonexistent_page.html"; + return elem; + }, function() { + my_ok(this.test_result, "iframe load should be intercepted"); + }); + + test_onload(function() { + var elem = document.createElement('iframe'); + elem.id = 'intercepted-iframe-2'; + elem.src = "navigate.html"; + return elem; + }, function() { + my_ok(this.test_result, "iframe should successfully load"); + }); + + gExpected++; + var xhr = new XMLHttpRequest(); + xhr.addEventListener("load", function(evt) { + my_ok(evt.target.responseXML === null, "Load synthetic cross origin XML Document should be allowed"); + finish(); + }); + xhr.responseType = "document"; + xhr.open("GET", "load_cross_origin_xml_document_synthetic.xml"); + xhr.send(); + + gExpected++; + fetch( + "load_cross_origin_xml_document_cors.xml", + {mode: "same-origin"} + ).then(function(response) { + // issue: https://github.com/whatwg/fetch/issues/629 + my_ok(false, "Load CORS cross origin XML Document should not be allowed"); + finish(); + }, function(error) { + my_ok(true, "Load CORS cross origin XML Document should not be allowed"); + finish(); + }); + + gExpected++; + fetch( + "load_cross_origin_xml_document_opaque.xml", + {mode: "same-origin"} + ).then(function(response) { + my_ok(false, "Load opaque cross origin XML Document should not be allowed"); + finish(); + }, function(error) { + my_ok(true, "Load opaque cross origin XML Document should not be allowed"); + finish(); + }); + + gExpected++; + var worker = new Worker('nonexistent_worker_script.js'); + worker.onmessage = function(e) { + my_ok(e.data == "worker-intercept-success", "worker load intercepted"); + finish(); + }; + worker.onerror = function() { + my_ok(false, "worker load should be intercepted"); + }; + + gExpected++; + var worker = new Worker('fetch_worker_script.js'); + worker.onmessage = function(e) { + if (e.data == "finish") { + finish(); + } else if (e.data == "expect") { + gExpected++; + } else if (e.data.type == "ok") { + my_ok(e.data.value, "Fetch test on worker: " + e.data.msg); + } + }; + worker.onerror = function() { + my_ok(false, "worker should not cause any errors"); + }; +</script> +</pre> +</body> +</html> diff --git a/dom/serviceworkers/test/fetch/interrupt.sjs b/dom/serviceworkers/test/fetch/interrupt.sjs new file mode 100644 index 0000000000..6e5deeb832 --- /dev/null +++ b/dom/serviceworkers/test/fetch/interrupt.sjs @@ -0,0 +1,20 @@ +function handleRequest(request, response) { + var body = "a"; + for (var i = 0; i < 20; i++) { + body += body; + } + + response.seizePower(); + response.write("HTTP/1.1 200 OK\r\n"); + var count = 10; + response.write("Content-Length: " + body.length * count + "\r\n"); + response.write("Content-Type: text/plain; charset=utf-8\r\n"); + response.write("Cache-Control: no-cache, must-revalidate\r\n"); + response.write("\r\n"); + + for (var i = 0; i < count; i++) { + response.write(body); + } + + throw Components.Exception("", Components.results.NS_BINDING_ABORTED); +} diff --git a/dom/serviceworkers/test/fetch/origin/https/index-https.sjs b/dom/serviceworkers/test/fetch/origin/https/index-https.sjs new file mode 100644 index 0000000000..5250467ec7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/origin/https/index-https.sjs @@ -0,0 +1,8 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader( + "Location", + "https://example.org/tests/dom/serviceworkers/test/fetch/origin/https/realindex.html", + false + ); +} diff --git a/dom/serviceworkers/test/fetch/origin/https/origin_test.js b/dom/serviceworkers/test/fetch/origin/https/origin_test.js new file mode 100644 index 0000000000..d148de2d83 --- /dev/null +++ b/dom/serviceworkers/test/fetch/origin/https/origin_test.js @@ -0,0 +1,29 @@ +var prefix = "/tests/dom/serviceworkers/test/fetch/origin/https/"; + +function addOpaqueRedirect(cache, file) { + return fetch(new Request(prefix + file, { redirect: "manual" })).then( + function (response) { + return cache.put(prefix + file, response); + } + ); +} + +self.addEventListener("install", function (event) { + event.waitUntil( + self.caches.open("origin-cache").then(c => { + return addOpaqueRedirect(c, "index-https.sjs"); + }) + ); +}); + +self.addEventListener("fetch", function (event) { + if (event.request.url.includes("index-cached-https.sjs")) { + event.respondWith( + self.caches.open("origin-cache").then(c => { + return c.match(prefix + "index-https.sjs"); + }) + ); + } else { + event.respondWith(fetch(event.request)); + } +}); diff --git a/dom/serviceworkers/test/fetch/origin/https/realindex.html b/dom/serviceworkers/test/fetch/origin/https/realindex.html new file mode 100644 index 0000000000..87f3489455 --- /dev/null +++ b/dom/serviceworkers/test/fetch/origin/https/realindex.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<script> + window.opener.postMessage({status: "domain", data: document.domain}, "*"); + window.opener.postMessage({status: "origin", data: location.origin}, "*"); + window.opener.postMessage({status: "done"}, "*"); +</script> diff --git a/dom/serviceworkers/test/fetch/origin/https/realindex.html^headers^ b/dom/serviceworkers/test/fetch/origin/https/realindex.html^headers^ new file mode 100644 index 0000000000..5ed82fd065 --- /dev/null +++ b/dom/serviceworkers/test/fetch/origin/https/realindex.html^headers^ @@ -0,0 +1 @@ +Access-Control-Allow-Origin: https://example.com diff --git a/dom/serviceworkers/test/fetch/origin/https/register.html b/dom/serviceworkers/test/fetch/origin/https/register.html new file mode 100644 index 0000000000..2e99adba53 --- /dev/null +++ b/dom/serviceworkers/test/fetch/origin/https/register.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + navigator.serviceWorker.ready.then(done); + navigator.serviceWorker.register("origin_test.js", {scope: "."}); +</script> diff --git a/dom/serviceworkers/test/fetch/origin/https/unregister.html b/dom/serviceworkers/test/fetch/origin/https/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/origin/https/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script> diff --git a/dom/serviceworkers/test/fetch/origin/index-to-https.sjs b/dom/serviceworkers/test/fetch/origin/index-to-https.sjs new file mode 100644 index 0000000000..2505c03686 --- /dev/null +++ b/dom/serviceworkers/test/fetch/origin/index-to-https.sjs @@ -0,0 +1,8 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader( + "Location", + "https://example.org/tests/dom/serviceworkers/test/fetch/origin/realindex.html", + false + ); +} diff --git a/dom/serviceworkers/test/fetch/origin/index.sjs b/dom/serviceworkers/test/fetch/origin/index.sjs new file mode 100644 index 0000000000..ca11827792 --- /dev/null +++ b/dom/serviceworkers/test/fetch/origin/index.sjs @@ -0,0 +1,8 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader( + "Location", + "http://example.org/tests/dom/serviceworkers/test/fetch/origin/realindex.html", + false + ); +} diff --git a/dom/serviceworkers/test/fetch/origin/origin_test.js b/dom/serviceworkers/test/fetch/origin/origin_test.js new file mode 100644 index 0000000000..d57f10cc2a --- /dev/null +++ b/dom/serviceworkers/test/fetch/origin/origin_test.js @@ -0,0 +1,38 @@ +var prefix = "/tests/dom/serviceworkers/test/fetch/origin/"; + +function addOpaqueRedirect(cache, file) { + return fetch(new Request(prefix + file, { redirect: "manual" })).then( + function (response) { + return cache.put(prefix + file, response); + } + ); +} + +self.addEventListener("install", function (event) { + event.waitUntil( + self.caches.open("origin-cache").then(c => { + return Promise.all([ + addOpaqueRedirect(c, "index.sjs"), + addOpaqueRedirect(c, "index-to-https.sjs"), + ]); + }) + ); +}); + +self.addEventListener("fetch", function (event) { + if (event.request.url.includes("index-cached.sjs")) { + event.respondWith( + self.caches.open("origin-cache").then(c => { + return c.match(prefix + "index.sjs"); + }) + ); + } else if (event.request.url.includes("index-to-https-cached.sjs")) { + event.respondWith( + self.caches.open("origin-cache").then(c => { + return c.match(prefix + "index-to-https.sjs"); + }) + ); + } else { + event.respondWith(fetch(event.request)); + } +}); diff --git a/dom/serviceworkers/test/fetch/origin/realindex.html b/dom/serviceworkers/test/fetch/origin/realindex.html new file mode 100644 index 0000000000..87f3489455 --- /dev/null +++ b/dom/serviceworkers/test/fetch/origin/realindex.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<script> + window.opener.postMessage({status: "domain", data: document.domain}, "*"); + window.opener.postMessage({status: "origin", data: location.origin}, "*"); + window.opener.postMessage({status: "done"}, "*"); +</script> diff --git a/dom/serviceworkers/test/fetch/origin/realindex.html^headers^ b/dom/serviceworkers/test/fetch/origin/realindex.html^headers^ new file mode 100644 index 0000000000..3a6a85d894 --- /dev/null +++ b/dom/serviceworkers/test/fetch/origin/realindex.html^headers^ @@ -0,0 +1 @@ +Access-Control-Allow-Origin: http://mochi.test:8888 diff --git a/dom/serviceworkers/test/fetch/origin/register.html b/dom/serviceworkers/test/fetch/origin/register.html new file mode 100644 index 0000000000..2e99adba53 --- /dev/null +++ b/dom/serviceworkers/test/fetch/origin/register.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + navigator.serviceWorker.ready.then(done); + navigator.serviceWorker.register("origin_test.js", {scope: "."}); +</script> diff --git a/dom/serviceworkers/test/fetch/origin/unregister.html b/dom/serviceworkers/test/fetch/origin/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/origin/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script> diff --git a/dom/serviceworkers/test/fetch/plugin/plugins.html b/dom/serviceworkers/test/fetch/plugin/plugins.html new file mode 100644 index 0000000000..b268f6d99e --- /dev/null +++ b/dom/serviceworkers/test/fetch/plugin/plugins.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<script> + var obj, embed; + + function ok(v, msg) { + window.opener.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function finish() { + document.documentElement.removeChild(obj); + document.documentElement.removeChild(embed); + window.opener.postMessage({status: "done"}, "*"); + } + + function test_object() { + obj = document.createElement("object"); + obj.setAttribute('data', "object"); + document.documentElement.appendChild(obj); + } + + function test_embed() { + embed = document.createElement("embed"); + embed.setAttribute('src', "embed"); + document.documentElement.appendChild(embed); + } + + navigator.serviceWorker.addEventListener("message", function onMessage(e) { + if (e.data.destination === "object") { + ok(false, "<object> should not be intercepted"); + } else if (e.data.destination === "embed") { + ok(false, "<embed> should not be intercepted"); + } else if (e.data.destination === "" && e.data.resource === "foo.txt") { + navigator.serviceWorker.removeEventListener("message", onMessage); + finish(); + } + }); + + test_object(); + test_embed(); + // SW will definitely intercept fetch API, use this to see if plugins are + // intercepted before fetch(). + fetch("foo.txt"); +</script> diff --git a/dom/serviceworkers/test/fetch/plugin/worker.js b/dom/serviceworkers/test/fetch/plugin/worker.js new file mode 100644 index 0000000000..9849c9e0d5 --- /dev/null +++ b/dom/serviceworkers/test/fetch/plugin/worker.js @@ -0,0 +1,15 @@ +self.addEventListener("fetch", function (event) { + var resource = event.request.url.split("/").pop(); + event.waitUntil( + clients.matchAll().then(clients => { + clients.forEach(client => { + if (client.url.includes("plugins.html")) { + client.postMessage({ + destination: event.request.destination, + resource, + }); + } + }); + }) + ); +}); diff --git a/dom/serviceworkers/test/fetch/real-file.txt b/dom/serviceworkers/test/fetch/real-file.txt new file mode 100644 index 0000000000..3ca2088ec0 --- /dev/null +++ b/dom/serviceworkers/test/fetch/real-file.txt @@ -0,0 +1 @@ +This is a real file. diff --git a/dom/serviceworkers/test/fetch/redirect.sjs b/dom/serviceworkers/test/fetch/redirect.sjs new file mode 100644 index 0000000000..dab558f4a8 --- /dev/null +++ b/dom/serviceworkers/test/fetch/redirect.sjs @@ -0,0 +1,4 @@ +function handleRequest(request, response) { + response.setStatusLine(request.httpVersion, 301, "Moved Permanently"); + response.setHeader("Location", "synthesized-redirect-twice-real-file.txt"); +} diff --git a/dom/serviceworkers/test/fetch/requesturl/index.html b/dom/serviceworkers/test/fetch/requesturl/index.html new file mode 100644 index 0000000000..bc3e400a94 --- /dev/null +++ b/dom/serviceworkers/test/fetch/requesturl/index.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.onmessage = window.onmessage = e => { + window.parent.postMessage(e.data, "*"); + }; +</script> +<iframe src="redirector.html"></iframe> diff --git a/dom/serviceworkers/test/fetch/requesturl/redirect.sjs b/dom/serviceworkers/test/fetch/requesturl/redirect.sjs new file mode 100644 index 0000000000..ae50a78aef --- /dev/null +++ b/dom/serviceworkers/test/fetch/requesturl/redirect.sjs @@ -0,0 +1,8 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader( + "Location", + "http://example.org/tests/dom/serviceworkers/test/fetch/requesturl/secret.html", + false + ); +} diff --git a/dom/serviceworkers/test/fetch/requesturl/redirector.html b/dom/serviceworkers/test/fetch/requesturl/redirector.html new file mode 100644 index 0000000000..0a3afab9ee --- /dev/null +++ b/dom/serviceworkers/test/fetch/requesturl/redirector.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<meta http-equiv="refresh" content="3;URL=/tests/dom/serviceworkers/test/fetch/requesturl/redirect.sjs"> diff --git a/dom/serviceworkers/test/fetch/requesturl/register.html b/dom/serviceworkers/test/fetch/requesturl/register.html new file mode 100644 index 0000000000..19a2e022c2 --- /dev/null +++ b/dom/serviceworkers/test/fetch/requesturl/register.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + navigator.serviceWorker.ready.then(done); + navigator.serviceWorker.register("requesturl_test.js", {scope: "."}); +</script> diff --git a/dom/serviceworkers/test/fetch/requesturl/requesturl_test.js b/dom/serviceworkers/test/fetch/requesturl/requesturl_test.js new file mode 100644 index 0000000000..4d2680538f --- /dev/null +++ b/dom/serviceworkers/test/fetch/requesturl/requesturl_test.js @@ -0,0 +1,21 @@ +addEventListener("fetch", event => { + var url = event.request.url; + var badURL = url.indexOf("secret.html") > -1; + event.respondWith( + new Promise(resolve => { + clients.matchAll().then(clients => { + for (var client of clients) { + if (client.url.indexOf("index.html") > -1) { + client.postMessage({ + status: "ok", + result: !badURL, + message: "Should not find a bad URL (" + url + ")", + }); + break; + } + } + resolve(fetch(event.request)); + }); + }) + ); +}); diff --git a/dom/serviceworkers/test/fetch/requesturl/secret.html b/dom/serviceworkers/test/fetch/requesturl/secret.html new file mode 100644 index 0000000000..694c336355 --- /dev/null +++ b/dom/serviceworkers/test/fetch/requesturl/secret.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +secret stuff +<script> + window.parent.postMessage({status: "done"}, "*"); +</script> diff --git a/dom/serviceworkers/test/fetch/requesturl/unregister.html b/dom/serviceworkers/test/fetch/requesturl/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/requesturl/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script> diff --git a/dom/serviceworkers/test/fetch/sandbox/index.html b/dom/serviceworkers/test/fetch/sandbox/index.html new file mode 100644 index 0000000000..1094a3995d --- /dev/null +++ b/dom/serviceworkers/test/fetch/sandbox/index.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<script> + window.parent.postMessage({status: "ok", result: true, message: "The iframe is not being intercepted"}, "*"); + window.parent.postMessage({status: "done"}, "*"); +</script> diff --git a/dom/serviceworkers/test/fetch/sandbox/intercepted_index.html b/dom/serviceworkers/test/fetch/sandbox/intercepted_index.html new file mode 100644 index 0000000000..87261a495f --- /dev/null +++ b/dom/serviceworkers/test/fetch/sandbox/intercepted_index.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<script> + window.parent.postMessage({status: "ok", result: false, message: "The iframe is being intercepted"}, "*"); + window.parent.postMessage({status: "done"}, "*"); +</script> diff --git a/dom/serviceworkers/test/fetch/sandbox/register.html b/dom/serviceworkers/test/fetch/sandbox/register.html new file mode 100644 index 0000000000..427b1a8da9 --- /dev/null +++ b/dom/serviceworkers/test/fetch/sandbox/register.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + navigator.serviceWorker.ready.then(done); + navigator.serviceWorker.register("sandbox_test.js", {scope: "."}); +</script> diff --git a/dom/serviceworkers/test/fetch/sandbox/sandbox_test.js b/dom/serviceworkers/test/fetch/sandbox/sandbox_test.js new file mode 100644 index 0000000000..310cea0d16 --- /dev/null +++ b/dom/serviceworkers/test/fetch/sandbox/sandbox_test.js @@ -0,0 +1,5 @@ +self.addEventListener("fetch", function (event) { + if (event.request.url.includes("index.html")) { + event.respondWith(fetch("intercepted_index.html")); + } +}); diff --git a/dom/serviceworkers/test/fetch/sandbox/unregister.html b/dom/serviceworkers/test/fetch/sandbox/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/sandbox/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script> diff --git a/dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html b/dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html new file mode 100644 index 0000000000..e99209aa4d --- /dev/null +++ b/dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<script> + window.onmessage = function(e) { + window.parent.postMessage(e.data, "*"); + if (e.data.status == "protocol") { + document.querySelector("iframe").src = "image.html"; + } + }; +</script> +<iframe src="http://example.com/tests/dom/serviceworkers/test/fetch/upgrade-insecure/index.html"></iframe> diff --git a/dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html^headers^ b/dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html^headers^ new file mode 100644 index 0000000000..602d9dc38d --- /dev/null +++ b/dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html^headers^ @@ -0,0 +1 @@ +Content-Security-Policy: upgrade-insecure-requests diff --git a/dom/serviceworkers/test/fetch/upgrade-insecure/image-20px.png b/dom/serviceworkers/test/fetch/upgrade-insecure/image-20px.png Binary files differnew file mode 100644 index 0000000000..ae6a8a6b88 --- /dev/null +++ b/dom/serviceworkers/test/fetch/upgrade-insecure/image-20px.png diff --git a/dom/serviceworkers/test/fetch/upgrade-insecure/image-40px.png b/dom/serviceworkers/test/fetch/upgrade-insecure/image-40px.png Binary files differnew file mode 100644 index 0000000000..fe391dc8a2 --- /dev/null +++ b/dom/serviceworkers/test/fetch/upgrade-insecure/image-40px.png diff --git a/dom/serviceworkers/test/fetch/upgrade-insecure/image.html b/dom/serviceworkers/test/fetch/upgrade-insecure/image.html new file mode 100644 index 0000000000..dfcfd80014 --- /dev/null +++ b/dom/serviceworkers/test/fetch/upgrade-insecure/image.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<script> +onload=function(){ + var img = new Image(); + img.src = "http://example.com/tests/dom/serviceworkers/test/fetch/upgrade-insecure/image-20px.png"; + img.onload = function() { + window.parent.postMessage({status: "image", data: img.width}, "*"); + }; + img.onerror = function() { + window.parent.postMessage({status: "image", data: "error"}, "*"); + }; +}; +</script> diff --git a/dom/serviceworkers/test/fetch/upgrade-insecure/realindex.html b/dom/serviceworkers/test/fetch/upgrade-insecure/realindex.html new file mode 100644 index 0000000000..aaa255aad3 --- /dev/null +++ b/dom/serviceworkers/test/fetch/upgrade-insecure/realindex.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<script> + window.parent.postMessage({status: "protocol", data: location.protocol}, "*"); +</script> diff --git a/dom/serviceworkers/test/fetch/upgrade-insecure/register.html b/dom/serviceworkers/test/fetch/upgrade-insecure/register.html new file mode 100644 index 0000000000..6309b9b218 --- /dev/null +++ b/dom/serviceworkers/test/fetch/upgrade-insecure/register.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + navigator.serviceWorker.ready.then(done); + navigator.serviceWorker.register("upgrade-insecure_test.js", {scope: "."}); +</script> diff --git a/dom/serviceworkers/test/fetch/upgrade-insecure/unregister.html b/dom/serviceworkers/test/fetch/upgrade-insecure/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/serviceworkers/test/fetch/upgrade-insecure/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script> diff --git a/dom/serviceworkers/test/fetch/upgrade-insecure/upgrade-insecure_test.js b/dom/serviceworkers/test/fetch/upgrade-insecure/upgrade-insecure_test.js new file mode 100644 index 0000000000..74b9ed23ba --- /dev/null +++ b/dom/serviceworkers/test/fetch/upgrade-insecure/upgrade-insecure_test.js @@ -0,0 +1,11 @@ +self.addEventListener("fetch", function (event) { + if (event.request.url.includes("index.html")) { + event.respondWith(fetch("realindex.html")); + } else if (event.request.url.includes("image-20px.png")) { + if (event.request.url.indexOf("https://") == 0) { + event.respondWith(fetch("image-40px.png")); + } else { + event.respondWith(Response.error()); + } + } +}); diff --git a/dom/serviceworkers/test/fetch_event_worker.js b/dom/serviceworkers/test/fetch_event_worker.js new file mode 100644 index 0000000000..b022ca4175 --- /dev/null +++ b/dom/serviceworkers/test/fetch_event_worker.js @@ -0,0 +1,364 @@ +// eslint-disable-next-line complexity +onfetch = function (ev) { + if (ev.request.url.includes("ignore")) { + return; + } + + if (ev.request.url.includes("bare-synthesized.txt")) { + ev.respondWith( + Promise.resolve(new Response("synthesized response body", {})) + ); + } else if (ev.request.url.includes("file_CrossSiteXHR_server.sjs")) { + // N.B. this response would break the rules of CORS if it were allowed, but + // this test relies upon the preflight request not being intercepted and + // thus this response should not be used. + if (ev.request.method == "OPTIONS") { + ev.respondWith( + new Response("", { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": "X-Unsafe", + }, + }) + ); + } else if (ev.request.url.includes("example.org")) { + ev.respondWith(fetch(ev.request)); + } + } else if (ev.request.url.includes("synthesized-404.txt")) { + ev.respondWith( + Promise.resolve( + new Response("synthesized response body", { status: 404 }) + ) + ); + } else if (ev.request.url.includes("synthesized-headers.txt")) { + ev.respondWith( + Promise.resolve( + new Response("synthesized response body", { + headers: { + "X-Custom-Greeting": "Hello", + }, + }) + ) + ); + } else if (ev.request.url.includes("test-respondwith-response.txt")) { + ev.respondWith(new Response("test-respondwith-response response body", {})); + } else if (ev.request.url.includes("synthesized-redirect-real-file.txt")) { + ev.respondWith(Promise.resolve(Response.redirect("fetch/real-file.txt"))); + } else if ( + ev.request.url.includes("synthesized-redirect-twice-real-file.txt") + ) { + ev.respondWith( + Promise.resolve(Response.redirect("synthesized-redirect-real-file.txt")) + ); + } else if (ev.request.url.includes("synthesized-redirect-synthesized.txt")) { + ev.respondWith(Promise.resolve(Response.redirect("bare-synthesized.txt"))); + } else if ( + ev.request.url.includes("synthesized-redirect-twice-synthesized.txt") + ) { + ev.respondWith( + Promise.resolve(Response.redirect("synthesized-redirect-synthesized.txt")) + ); + } else if (ev.request.url.includes("rejected.txt")) { + ev.respondWith(Promise.reject()); + } else if (ev.request.url.includes("nonresponse.txt")) { + ev.respondWith(Promise.resolve(5)); + } else if (ev.request.url.includes("nonresponse2.txt")) { + ev.respondWith(Promise.resolve({})); + } else if (ev.request.url.includes("nonpromise.txt")) { + try { + // This should coerce to Promise(5) instead of throwing + ev.respondWith(5); + } catch (e) { + // test is expecting failure, so return a success if we get a thrown + // exception + ev.respondWith(new Response("respondWith(5) threw " + e)); + } + } else if (ev.request.url.includes("headers.txt")) { + var ok = true; + ok &= ev.request.headers.get("X-Test1") == "header1"; + ok &= ev.request.headers.get("X-Test2") == "header2"; + ev.respondWith(Promise.resolve(new Response(ok.toString(), {}))); + } else if (ev.request.url.includes("readable-stream.txt")) { + ev.respondWith( + new Response( + new ReadableStream({ + start(controller) { + controller.enqueue( + new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21]) + ); + controller.close(); + }, + }) + ) + ); + } else if (ev.request.url.includes("readable-stream-locked.txt")) { + let stream = new ReadableStream({ + start(controller) { + controller.enqueue( + new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21]) + ); + controller.close(); + }, + }); + + ev.respondWith(new Response(stream)); + + // This locks the stream. + stream.getReader(); + } else if (ev.request.url.includes("readable-stream-with-exception.txt")) { + ev.respondWith( + new Response( + new ReadableStream({ + start(controller) {}, + pull() { + throw "EXCEPTION!"; + }, + }) + ) + ); + } else if (ev.request.url.includes("readable-stream-with-exception2.txt")) { + ev.respondWith( + new Response( + new ReadableStream({ + _controller: null, + _count: 0, + + start(controller) { + this._controller = controller; + }, + pull() { + if (++this._count == 5) { + throw "EXCEPTION 2!"; + } + this._controller.enqueue(new Uint8Array([this._count])); + }, + }) + ) + ); + } else if (ev.request.url.includes("readable-stream-already-consumed.txt")) { + let r = new Response( + new ReadableStream({ + start(controller) { + controller.enqueue( + new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21]) + ); + controller.close(); + }, + }) + ); + + r.blob(); + + ev.respondWith(r); + } else if (ev.request.url.includes("user-pass")) { + ev.respondWith(new Response(ev.request.url)); + } else if (ev.request.url.includes("nonexistent_image.gif")) { + var imageAsBinaryString = atob( + "R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs" + ); + var imageLength = imageAsBinaryString.length; + + // If we just pass |imageAsBinaryString| to the Response constructor, an + // encoding conversion occurs that corrupts the image. Instead, we need to + // convert it to a typed array. + // typed array. + var imageAsArray = new Uint8Array(imageLength); + for (var i = 0; i < imageLength; ++i) { + imageAsArray[i] = imageAsBinaryString.charCodeAt(i); + } + + ev.respondWith( + Promise.resolve( + new Response(imageAsArray, { headers: { "Content-Type": "image/gif" } }) + ) + ); + } else if (ev.request.url.includes("nonexistent_script.js")) { + ev.respondWith( + Promise.resolve(new Response("check_intercepted_script();", {})) + ); + } else if (ev.request.url.includes("nonexistent_stylesheet.css")) { + ev.respondWith( + Promise.resolve( + new Response("#style-test { background-color: black !important; }", { + headers: { + "Content-Type": "text/css", + }, + }) + ) + ); + } else if (ev.request.url.includes("nonexistent_page.html")) { + ev.respondWith( + Promise.resolve( + new Response( + "<script>window.frameElement.test_result = true;</script>", + { + headers: { + "Content-Type": "text/html", + }, + } + ) + ) + ); + } else if (ev.request.url.includes("navigate.html")) { + var requests = [ + // should not throw + new Request(ev.request), + new Request(ev.request, undefined), + new Request(ev.request, null), + new Request(ev.request, {}), + new Request(ev.request, { someUnrelatedProperty: 42 }), + new Request(ev.request, { method: "GET" }), + ]; + ev.respondWith( + Promise.resolve( + new Response( + "<script>window.frameElement.test_result = true;</script>", + { + headers: { + "Content-Type": "text/html", + }, + } + ) + ) + ); + } else if (ev.request.url.includes("nonexistent_worker_script.js")) { + ev.respondWith( + Promise.resolve( + new Response("postMessage('worker-intercept-success')", { + headers: { "Content-Type": "text/javascript" }, + }) + ) + ); + } else if (ev.request.url.includes("nonexistent_imported_script.js")) { + ev.respondWith( + Promise.resolve( + new Response("check_intercepted_script();", { + headers: { "Content-Type": "text/javascript" }, + }) + ) + ); + } else if (ev.request.url.includes("deliver-gzip")) { + // Don't handle the request, this will make Necko perform a network request, at + // which point SetApplyConversion must be re-enabled, otherwise the request + // will fail. + return; + } else if (ev.request.url.includes("hello.gz")) { + ev.respondWith(fetch("fetch/deliver-gzip.sjs")); + } else if (ev.request.url.includes("hello-after-extracting.gz")) { + ev.respondWith( + fetch("fetch/deliver-gzip.sjs").then(function (res) { + return res.text().then(function (body) { + return new Response(body, { + status: res.status, + statusText: res.statusText, + headers: res.headers, + }); + }); + }) + ); + } else if (ev.request.url.includes("opaque-on-same-origin")) { + var url = + "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200"; + ev.respondWith(fetch(url, { mode: "no-cors" })); + } else if (ev.request.url.includes("opaque-no-cors")) { + if (ev.request.mode != "no-cors") { + ev.respondWith(Promise.reject()); + return; + } + + var url = + "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200"; + ev.respondWith(fetch(url, { mode: ev.request.mode })); + } else if (ev.request.url.includes("cors-for-no-cors")) { + if (ev.request.mode != "no-cors") { + ev.respondWith(Promise.reject()); + return; + } + + var url = + "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*"; + ev.respondWith(fetch(url)); + } else if (ev.request.url.includes("example.com")) { + ev.respondWith(fetch(ev.request)); + } else if (ev.request.url.includes("body-")) { + ev.respondWith( + ev.request.text().then(function (body) { + return new Response(body + body); + }) + ); + } else if (ev.request.url.includes("something.txt")) { + ev.respondWith(Response.redirect("fetch/somethingelse.txt")); + } else if (ev.request.url.includes("somethingelse.txt")) { + ev.respondWith(new Response("something else response body", {})); + } else if (ev.request.url.includes("redirect_serviceworker.sjs")) { + // The redirect_serviceworker.sjs server-side JavaScript file redirects to + // 'http://mochi.test:8888/tests/dom/serviceworkers/test/worker.js' + // The redirected fetch should not go through the SW since the original + // fetch was initiated from a SW. + ev.respondWith(fetch("redirect_serviceworker.sjs")); + } else if ( + ev.request.url.includes("load_cross_origin_xml_document_synthetic.xml") + ) { + ev.respondWith( + Promise.resolve( + new Response("<response>body</response>", { + headers: { "Content-Type": "text/xtml" }, + }) + ) + ); + } else if ( + ev.request.url.includes("load_cross_origin_xml_document_cors.xml") + ) { + if (ev.request.mode != "same-origin") { + ev.respondWith(Promise.reject()); + return; + } + + var url = + "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*"; + ev.respondWith(fetch(url, { mode: "cors" })); + } else if ( + ev.request.url.includes("load_cross_origin_xml_document_opaque.xml") + ) { + if (ev.request.mode != "same-origin") { + Promise.resolve( + new Response("<error>Invalid Request mode</error>", { + headers: { "Content-Type": "text/xtml" }, + }) + ); + return; + } + + var url = + "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200"; + ev.respondWith(fetch(url, { mode: "no-cors" })); + } else if (ev.request.url.includes("xhr-method-test.txt")) { + ev.respondWith(new Response("intercepted " + ev.request.method)); + } else if (ev.request.url.includes("empty-header")) { + if ( + !ev.request.headers.has("emptyheader") || + ev.request.headers.get("emptyheader") !== "" + ) { + ev.respondWith(Promise.reject()); + return; + } + ev.respondWith(new Response("emptyheader")); + } else if (ev.request.url.includes("fetchevent-extendable")) { + if (ev instanceof ExtendableEvent) { + ev.respondWith(new Response("extendable")); + } else { + ev.respondWith(Promise.reject()); + } + } else if (ev.request.url.includes("fetchevent-request")) { + var threw = false; + try { + new FetchEvent("foo"); + } catch (e) { + if (e.name == "TypeError") { + threw = true; + } + } finally { + ev.respondWith(new Response(threw ? "non-nullable" : "nullable")); + } + } +}; |