summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/clear-site-data
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/clear-site-data
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/clear-site-data')
-rw-r--r--testing/web-platform/tests/clear-site-data/META.yml4
-rw-r--r--testing/web-platform/tests/clear-site-data/executionContexts.sub.html47
-rw-r--r--testing/web-platform/tests/clear-site-data/navigation-insecure.html52
-rw-r--r--testing/web-platform/tests/clear-site-data/navigation.https.html59
-rw-r--r--testing/web-platform/tests/clear-site-data/resource.html74
-rw-r--r--testing/web-platform/tests/clear-site-data/storage.https.html110
-rw-r--r--testing/web-platform/tests/clear-site-data/support/controlled-endpoint.py3
-rw-r--r--testing/web-platform/tests/clear-site-data/support/echo-clear-site-data.py40
-rw-r--r--testing/web-platform/tests/clear-site-data/support/iframe_executionContexts.html8
-rw-r--r--testing/web-platform/tests/clear-site-data/support/page_using_service_worker.html6
-rw-r--r--testing/web-platform/tests/clear-site-data/support/page_with_resource.sub.html27
-rw-r--r--testing/web-platform/tests/clear-site-data/support/send_report.html24
-rw-r--r--testing/web-platform/tests/clear-site-data/support/service_worker.js6
-rw-r--r--testing/web-platform/tests/clear-site-data/support/test_utils.sub.js277
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;
+})();