diff options
Diffstat (limited to 'testing/web-platform/tests/clear-site-data')
14 files changed, 737 insertions, 0 deletions
diff --git a/testing/web-platform/tests/clear-site-data/META.yml b/testing/web-platform/tests/clear-site-data/META.yml new file mode 100644 index 0000000000..65ca96dbb9 --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/META.yml @@ -0,0 +1,4 @@ +spec: https://w3c.github.io/webappsec-clear-site-data/ +suggested_reviewers: + - mikewest + - msramek diff --git a/testing/web-platform/tests/clear-site-data/executionContexts.sub.html b/testing/web-platform/tests/clear-site-data/executionContexts.sub.html new file mode 100644 index 0000000000..f09a3f73b5 --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/executionContexts.sub.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html> + <head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + + <body> + <script> + function createAndLoadIframe() { + return new Promise(resolve => { + addEventListener("message", function() { + assert_true(true, "Iframe loaded"); + resolve(); + }, { once: true }); + + let ifr = document.createElement('iframe'); + document.body.appendChild(ifr); + ifr.src = "https://{{domains[www2]}}:{{ports[https][0]}}/clear-site-data/support/iframe_executionContexts.html"; + }); + } + + function loadClearSiteDataResource(what) { + return new Promise(resolve => { + addEventListener("message", function() { + assert_true(true, "Iframe re-loaded"); + resolve(); + }, { once: true }); + + let image = new Image(); + image.src = "https://{{domains[www2]}}:{{ports[https][0]}}/clear-site-data/support/echo-clear-site-data.py?" + what; + }); + } + + promise_test(function(test) { + return createAndLoadIframe() + .then(() => loadClearSiteDataResource("executionContexts")) + }, "executionContexts triggers the reload of contexts"); + + promise_test(function(test) { + return createAndLoadIframe() + .then(() => loadClearSiteDataResource("*")); + }, "* triggers the reload of contexts"); + </script> + </body> +</html> + diff --git a/testing/web-platform/tests/clear-site-data/navigation-insecure.html b/testing/web-platform/tests/clear-site-data/navigation-insecure.html new file mode 100644 index 0000000000..9ccd712a22 --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/navigation-insecure.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html> + <head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="support/test_utils.sub.js"></script> + </head> + + <body> + <script> + /** + * @param Array.<Array.<Datatype>> combination A combination of datatypes. + * @param Dict.<string, boolean> report A map between a datatype name and + * whether it is empty. + * @return boolean Whether all datatypes are still nonempty. + */ + function verifyDatatypes(report) { + TestUtils.DATATYPES.forEach(function(datatype) { + assert_false( + report[datatype.name], + datatype.name + " should NOT have been cleared."); + }); + } + + TestUtils.COMBINATIONS.forEach(function(combination) { + var test_name = + "Do not clear datatypes on insecure navigation (header: " + + combination.map(function(e) { return e.name; }).join(", ") + + ")"; + + promise_test(function(test) { + return new Promise(function(resolve_test, reject_test) { + TestUtils.populateDatatypes() + .then(function() { + // Navigate to a resource with a Clear-Site-Data header in + // an iframe, then verify that no data have been deleted. + return new Promise(function(resolve, reject) { + window.addEventListener("message", resolve); + var iframe = document.createElement("iframe"); + iframe.src = TestUtils.getClearSiteDataUrl(combination); + document.body.appendChild(iframe); + }).then(function(messageEvent) { + verifyDatatypes(messageEvent.data); + resolve_test(); + }); + }); + }); + }, test_name); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/clear-site-data/navigation.https.html b/testing/web-platform/tests/clear-site-data/navigation.https.html new file mode 100644 index 0000000000..dbddce3c13 --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/navigation.https.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<html> + <head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="support/test_utils.sub.js"></script> + </head> + + <body> + <script> + /** + * @param Array.<Array.<Datatype>> combination A combination of datatypes. + * @param Dict.<string, boolean> report A map between a datatype name and + * whether it is empty. + * @return boolean Whether all datatypes are empty if and only if they are + * included in the |combination|. + */ + function verifyDatatypes(combination, report) { + TestUtils.DATATYPES.forEach(function(datatype) { + if (combination.indexOf(datatype) != -1) { + assert_true( + report[datatype.name], + datatype.name + " should have been cleared."); + } else { + assert_false( + report[datatype.name], + datatype.name + " should NOT have been cleared."); + } + }); + } + + TestUtils.COMBINATIONS.forEach(function(combination) { + var test_name = + "Clear datatypes on navigation: " + + combination.map(function(e) { return e.name; }).join(", "); + + promise_test(function(test) { + return new Promise(function(resolve_test, reject_test) { + TestUtils.populateDatatypes() + .then(function() { + // Navigate to a resource with a Clear-Site-Data header in + // an iframe, then verify that the correct types have been + // deleted. + return new Promise(function(resolve, reject) { + window.addEventListener("message", resolve); + var iframe = document.createElement("iframe"); + iframe.src = TestUtils.getClearSiteDataUrl(combination); + document.body.appendChild(iframe); + }).then(function(messageEvent) { + verifyDatatypes(combination, messageEvent.data); + resolve_test(); + }); + }); + }); + }, test_name); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/clear-site-data/resource.html b/testing/web-platform/tests/clear-site-data/resource.html new file mode 100644 index 0000000000..a966cb95aa --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/resource.html @@ -0,0 +1,74 @@ +<!DOCTYPE html> +<html> + <head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="support/test_utils.sub.js"></script> + </head> + + <body> + <script> + /** + * @typedef{TestCase} + * @type{object} + * @property{string} frame The scheme of the url of the iframe. + * @property{string} resource The scheme of the resource in the iframe. + * @property{boolean} deleted Whether it is expected that Clear-Site-Data + * will be respected when loading |resource| in |frame|. + */ + var TestCase; + + /** Array<TestCase> Test cases. */ + var test_cases = [ + { "frame": "https", "resource": "https", "deleted": true }, + { "frame": "http", "resource": "https", "deleted": true }, + { "frame": "https", "resource": "http", "deleted": false }, + { "frame": "http", "resource": "http", "deleted": false }, + ]; + + /** + * @param TestCase test_case The test case that is tested. + * @param Dict.<string, boolean> report A map between a datatype name and + * whether it is empty. + */ + function verifyDatatypes(test_case, report) { + if (test_case.deleted) { + assert_true( + report["storage"], + "Storage should have been cleared."); + } else { + assert_false( + report["storage"], + "Storage should NOT have been cleared."); + } + } + + test_cases.forEach(function(test_case) { + var test_name = + test_case.resource + " resource on a " + test_case.frame + " page"; + + promise_test(function(test) { + return new Promise(function(resolve_test, reject_test) { + TestUtils.populateDatatypes() + .then(function() { + // Navigate to a page with a resource that is loaded with + // the Clear-Site-Data header, then verify that storage + // has been deleted if and only if the resource was loaded + // via HTTPS. + return new Promise(function(resolve, reject) { + window.addEventListener("message", resolve); + var iframe = document.createElement("iframe"); + iframe.src = TestUtils.getPageWithResourceUrl( + test_case.frame, test_case.resource); + document.body.appendChild(iframe); + }).then(function(messageEvent) { + verifyDatatypes(test_case, messageEvent.data); + resolve_test(); + }); + }); + }); + }, test_name); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/clear-site-data/storage.https.html b/testing/web-platform/tests/clear-site-data/storage.https.html new file mode 100644 index 0000000000..1d6882d7f7 --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/storage.https.html @@ -0,0 +1,110 @@ +<!DOCTYPE html> +<html> + <head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="../service-workers/service-worker/resources/test-helpers.sub.js"></script> + <script src="support/test_utils.sub.js"></script> + </head> + + <body> + <script> + /** @property{Datatype} The storage datatype. */ + var storage = TestUtils.DATATYPES.filter(function(datatype) { + return datatype.name == "storage"; + })[0]; + + var serviceWorkerTestPageIFrame; + function fetchFromIFrame() { + return serviceWorkerTestPageIFrame.contentWindow + .fetch('controlled-endpoint.py') + .then((result) => { + return result.text(); + }); + } + + // The tests are set up asynchronously. + setup({"explicit_done": true}); + + // There must be at least one test added synchronously, otherwise + // testharness will complain. + // TODO(@msramek): Find a way to avoid this dummy test. + test(function() {}, "Populate backends."); + + TestUtils.populateStorage() + .then(() => { + return new Promise(function(resolve, reject) { + promise_test(function(t) { + return navigator.serviceWorker.getRegistration("support/").then(function(reg) { + return wait_for_state(t, reg.installing || reg.waiting || reg.active, 'activated'); + }).then(resolve, reject); + }); + }); + }) + .then(() => { + return new Promise(function (resolve) { + // Create iFrame in the service worker's scope. This page will make a request + // for another page that is only served by the service worker + serviceWorkerTestPageIFrame = document.createElement("iframe"); + serviceWorkerTestPageIFrame.src = "support/page_using_service_worker.html"; + serviceWorkerTestPageIFrame.onload = function() { resolve(); }; + document.body.appendChild(serviceWorkerTestPageIFrame); + }); + }) + .then(() => { + const serviceWorkerResponseBody = fetchFromIFrame(); + + promise_test(function() { + return serviceWorkerResponseBody.then(function(body) { + assert_equals(body, "FROM_SERVICE_WORKER", "Response should be from service worker"); + }); + }, "Baseline: Service worker responds to request"); + + return serviceWorkerResponseBody; + }) + .then(function() { + const waitForControllerChange = new Promise(function(resolve) { + serviceWorkerTestPageIFrame.contentWindow + .navigator.serviceWorker.addEventListener("controllerchange", resolve); + }); + // Navigate to a resource with a Clear-Site-Data header in + // an iframe, then verify that all backends of the "storage" + // datatype have been deleted. + return new Promise(function(resolve, reject) { + window.addEventListener("message", resolve); + var iframe = document.createElement("iframe"); + iframe.src = TestUtils.getClearSiteDataUrl([storage]); + document.body.appendChild(iframe); + }).then(function() { + TestUtils.STORAGE.forEach(function(backend) { + var test_name = + "Clear backend when 'storage' is deleted: " + backend.name; + + promise_test(function() { + return backend.isEmpty().then(function(isEmpty) { + assert_true( + isEmpty, + backend.name + " should have been cleared."); + }); + }, test_name); + }); + + promise_test(function() { + return fetchFromIFrame().then(function(body) { + assert_equals(body, "FROM_NETWORK", "Response should be from network and not from the service worker"); + }); + }, "Service worker no longer responds to requests"); + + promise_test(function() { + return waitForControllerChange.then(function() { + assert_false(!!serviceWorkerTestPageIFrame.contentWindow.navigator.serviceWorker.controller, + "Client should not have a controller"); + }); + }, "controllerchange event fires and client no longer has controller"); + + done(); + }); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/clear-site-data/support/controlled-endpoint.py b/testing/web-platform/tests/clear-site-data/support/controlled-endpoint.py new file mode 100644 index 0000000000..bb4f464088 --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/support/controlled-endpoint.py @@ -0,0 +1,3 @@ +def main(request, response): + return ([(b"Content-Type", b"text/html")], + u"FROM_NETWORK") diff --git a/testing/web-platform/tests/clear-site-data/support/echo-clear-site-data.py b/testing/web-platform/tests/clear-site-data/support/echo-clear-site-data.py new file mode 100644 index 0000000000..6419d5bfad --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/support/echo-clear-site-data.py @@ -0,0 +1,40 @@ +import json + +RESPONSE = u""" +<!DOCTYPE html> +<html> + <head> + <title>Clear-Site-Data</title> + <script src="test_utils.sub.js"></script> + </head> + <body> + <script> + /** + * A map between a datatype name and whether it is empty. + * @property Object.<string, boolean> + */ + var report = {}; + + Promise.all(TestUtils.DATATYPES.map(function(datatype) { + return datatype.isEmpty().then(function(isEmpty) { + report[datatype.name] = isEmpty; + }); + })).then(function() { + window.top.postMessage(report, "*"); + }); + </script> + </body> +</html> +""" + +# A support server that receives a list of datatypes in the GET query +# and returns a Clear-Site-Data header with those datatypes. The content +# of the response is a html site using postMessage to report the status +# of the datatypes, so that if used in an iframe, it can inform the +# embedder whether the data deletion succeeded. +def main(request, response): + types = [key for key in request.GET.keys()] + header = b",".join(b"\"" + type + b"\"" for type in types) + return ([(b"Clear-Site-Data", header), + (b"Content-Type", b"text/html")], + RESPONSE) diff --git a/testing/web-platform/tests/clear-site-data/support/iframe_executionContexts.html b/testing/web-platform/tests/clear-site-data/support/iframe_executionContexts.html new file mode 100644 index 0000000000..9c20c9e0db --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/support/iframe_executionContexts.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> + <body> + <script> + parent.postMessage("Hello world!", "*"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/clear-site-data/support/page_using_service_worker.html b/testing/web-platform/tests/clear-site-data/support/page_using_service_worker.html new file mode 100644 index 0000000000..968a39a132 --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/support/page_using_service_worker.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> + <head> + <title>Clear-Site-Data + Service Workers Test Page</title> + </head> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/clear-site-data/support/page_with_resource.sub.html b/testing/web-platform/tests/clear-site-data/support/page_with_resource.sub.html new file mode 100644 index 0000000000..703519a2f6 --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/support/page_with_resource.sub.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> + <head> + <title>Clear-Site-Data</title> + <script src="test_utils.sub.js"></script> + </head> + <body> + <script> + var scheme = location.search.match("scheme=([^&$]+)")[1]; + + // TODO(@msramek): Move this logic to TestUtils. + var base_url = location.origin + .replace(/https?/, scheme) + .replace(/:[0-9]+/, ":" + (scheme == "https" ? {{ports[https][0]}} + : {{ports[http][0]}})) + + "/clear-site-data/support/"; + + var image = new Image(); + image.onload = image.onerror = function() { + location.href = base_url + "send_report.html"; + } + + // TODO(@msramek): Move this logic to TestUtils. + image.src = base_url + "echo-clear-site-data.py?storage"; + </script> + </body> +</html> diff --git a/testing/web-platform/tests/clear-site-data/support/send_report.html b/testing/web-platform/tests/clear-site-data/support/send_report.html new file mode 100644 index 0000000000..6e90c626ea --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/support/send_report.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html> + <head> + <title>Clear-Site-Data</title> + <script src="test_utils.sub.js"></script> + </head> + <body> + <script> + /** + * A map between a datatype name and whether it is empty. + * @property Object.<string, boolean> + */ + var report = {}; + + Promise.all(TestUtils.DATATYPES.map(function(datatype) { + return datatype.isEmpty().then(function(isEmpty) { + report[datatype.name] = isEmpty; + }); + })).then(function() { + window.top.postMessage(report, "*"); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/clear-site-data/support/service_worker.js b/testing/web-platform/tests/clear-site-data/support/service_worker.js new file mode 100644 index 0000000000..a4e5709ee1 --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/support/service_worker.js @@ -0,0 +1,6 @@ +self.addEventListener('fetch', (e) => { + const url = new URL(e.request.url); + if (url.pathname.match('controlled-endpoint.py')) { + e.respondWith(new Response('FROM_SERVICE_WORKER')); + } +});
\ No newline at end of file diff --git a/testing/web-platform/tests/clear-site-data/support/test_utils.sub.js b/testing/web-platform/tests/clear-site-data/support/test_utils.sub.js new file mode 100644 index 0000000000..0b9ea60d0d --- /dev/null +++ b/testing/web-platform/tests/clear-site-data/support/test_utils.sub.js @@ -0,0 +1,277 @@ +var TestUtils = (function() { + function randomString() { + var result = ""; + for (var i = 0; i < 5; i++) + result += String.fromCharCode(97 + Math.floor(Math.random() * 26)); + return result; + }; + + /** + * Representation of one datatype. + * @typedef Datatype + * @type{object} + * @property{string} name Name of the datatype. + * @property{function():boolean} supported + * Whether this datatype is supported by this user agent. + * @method{function():Void} add A function to add an instance of the datatype. + * @method{function():boolean} isEmpty A function that tests whether + * the datatype's storage backend is empty. + */ + var Datatype; + + var TestUtils = {}; + + /** + * Various storage backends that are part of the 'storage' datatype. + * @param{Array.<Datatype>} + */ + TestUtils.STORAGE = [ + { + "name": "local storage", + "supported": function() { return !!window.localStorage; }, + "add": function() { + return new Promise(function(resolve, reject) { + localStorage.setItem(randomString(), randomString()); + resolve(); + }); + }, + "isEmpty": function() { + return new Promise(function(resolve, reject) { + resolve(!localStorage.length); + }); + } + }, + { + "name": "Indexed DB", + "supported": function() { return !!window.indexedDB; }, + "add": function() { + return new Promise(function(resolve, reject) { + var request = window.indexedDB.open("database"); + request.onupgradeneeded = function() { + request.result.createObjectStore("store"); + }; + request.onsuccess = function() { + request.result.close(); + resolve(); + } + }); + }, + "isEmpty": function() { + return new Promise(function(resolve, reject) { + var request = window.indexedDB.open("database"); + request.onsuccess = function() { + var database = request.result; + try { + var transaction = database.transaction(["store"]); + resolve(false); + } catch(error) { + // The database is empty. However, by testing that, we have also + // created it, which means that |onupgradeneeded| in the "add" + // method will not run the next time. Delete the database before + // reporting that it was empty. + var deletion = window.indexedDB.deleteDatabase("database"); + deletion.onsuccess = resolve.bind(this, true); + } finally { + database.close(); + } + }; + }); + } + }, + { + // TODO(@msramek): We should also test the PERSISTENT filesystem, however, + // that might require storage permissions. + "name": "filesystems", + "supported": function() { + return window.requestFileSystem || window.webkitRequestFileSystem; + }, + "add": function() { + return new Promise(function(resolve, reject) { + var onSuccess = function(fileSystem) { + fileSystem.root.getFile('file', {"create": true}, resolve, resolve); + } + var onFailure = resolve; + + var requestFileSystem = + window.requestFileSystem || window.webkitRequestFileSystem; + requestFileSystem(window.TEMPORARY, 1 /* 1B */, + onSuccess, onFailure); + }); + }, + "isEmpty": function() { + return new Promise(function(resolve, reject) { + var onSuccess = function(fileSystem) { + fileSystem.root.getFile( + 'file', {}, + resolve.bind(this, false) /* opened successfully */, + resolve.bind(this, true) /* failed to open */); + } + var onFailure = resolve.bind(this, true); + + var requestFileSystem = + window.requestFileSystem || window.webkitRequestFileSystem; + requestFileSystem(window.TEMPORARY, 1 /* 1B */, + onSuccess, onFailure); + }); + } + }, + { + "name": "service workers", + "supported": function() { return !!navigator.serviceWorker; }, + "add": function() { + return navigator.serviceWorker.register( + "support/service_worker.js", + { scope: "support/"}); + }, + "isEmpty": function() { + return new Promise(function(resolve, reject) { + navigator.serviceWorker.getRegistrations() + .then(function(registrations) { + resolve(!registrations.length); + }); + }); + } + }, + { + "name": "WebSQL", + "supported": function() { return !!window.openDatabase; }, + "add": function() { + return new Promise(function(resolve, reject) { + var database = window.openDatabase( + "database", "1.0", "database", 1024 /* 1 kB */); + database.transaction(function(context) { + context.executeSql("CREATE TABLE IF NOT EXISTS data (column)"); + context.executeSql( + "INSERT INTO data (column) VALUES (1)", [], resolve); + }); + }); + }, + "isEmpty": function() { + return new Promise(function(resolve, reject) { + var database = window.openDatabase( + "database", "1.0", "database", 1024 /* 1 kB */); + database.transaction(function(context) { + context.executeSql("CREATE TABLE IF NOT EXISTS data (column)"); + context.executeSql( + "SELECT * FROM data", [], + function(transaction, result) { + resolve(!result.rows.length); + }); + }); + }); + } + } + ].filter(function(backend) { return backend.supported(); }); + + /** + * All datatypes supported by Clear-Site-Data. + * @param{Array.<Datatype>} + */ + TestUtils.DATATYPES = [ + { + "name": "cookies", + "supported": function() { return typeof document.cookie == "string"; }, + "add": function() { + return new Promise(function(resolve, reject) { + document.cookie = randomString() + "=" + randomString(); + resolve(); + }); + }, + "isEmpty": function() { + return new Promise(function(resolve, reject) { + resolve(!document.cookie); + }); + } + }, + { + "name": "storage", + "supported": TestUtils.STORAGE[0].supported, + "add": TestUtils.STORAGE[0].add, + "isEmpty": TestUtils.STORAGE[0].isEmpty, + } + ].filter(function(datatype) { return datatype.supported(); }); + + /** + * All possible combinations of datatypes. + * @property {Array.<Array.<Datatype>>} + */ + TestUtils.COMBINATIONS = (function() { + var combinations = []; + for (var mask = 0; mask < (1 << TestUtils.DATATYPES.length); mask++) { + var combination = []; + + for (var datatype = 0; + datatype < TestUtils.DATATYPES.length; datatype++) { + if (mask & (1 << datatype)) + combination.push(TestUtils.DATATYPES[datatype]); + } + + combinations.push(combination); + } + return combinations; + })(); + + /** + * Populates |datatypes| by calling the "add" method on each of them, + * and verifies that they are nonempty. + * @param {Array.<Datatype>} datatypes to be populated. + * @private + */ + function populate(datatypes) { + return Promise.all(datatypes.map(function(datatype) { + return new Promise(function(resolve, reject) { + datatype.add().then(function() { + datatype.isEmpty().then(function(isEmpty) { + assert_false( + isEmpty, + datatype.name + + " has to be nonempty before the test starts."); + resolve(); + }); + }); + }); + })); + }; + + /** + * Ensures that all datatypes are nonempty. Should be called in the test + * setup phase. + */ + TestUtils.populateDatatypes = populate.bind(this, TestUtils.DATATYPES); + + /** + * Ensures that all backends of the "storage" datatype are nonempty. Should + * be called in the test setup phase. + */ + TestUtils.populateStorage = populate.bind(this, TestUtils.STORAGE); + + /** + * Get the support server URL that returns a Clear-Site-Data header + * to clear |datatypes|. + * @param{Array.<Datatype>} datatypes The list of datatypes to be deleted. + * @return string The URL to be queried. + */ + TestUtils.getClearSiteDataUrl = function(datatypes) { + names = datatypes.map(function(e) { return e.name }); + return "support/echo-clear-site-data.py?" + names.join("&"); + } + + /** + * @param{string} page_scheme Scheme of the page. "http" or "https". + * @param{string} resource_scheme Scheme of the resource. "http" or "https". + * @return The URL of a page that contains a resource requesting the deletion + * of storage. + */ + TestUtils.getPageWithResourceUrl = function(page_scheme, resource_scheme) { + if (page_scheme != "https" && page_scheme != "http") + throw "Unsupported scheme: " + page_scheme; + if (resource_scheme != "https" && resource_scheme != "http") + throw "Unsupported scheme: " + resource_scheme; + return page_scheme + "://{{domains[]}}:" + + (page_scheme == "https" ? {{ports[https][0]}} : {{ports[http][0]}}) + + "/clear-site-data/support/page_with_resource.sub.html?scheme=" + + resource_scheme; + } + + return TestUtils; +})(); |