Skip to content

Commit

Permalink
Clear-Site-Data: clients uncontrolled by service workers after storag…
Browse files Browse the repository at this point in the history
…e directive

Adds a failing test, asserting that pages actively controlled by a service worker become uncontrolled after the Clear-Site-Data storage directive.

We agreed on this behavior in a spec issue and confirmed the need for this test at the TPAC Service Workers F2F.

w3c/ServiceWorker#614
F2F notes: https://docs.google.com/document/d/1_Qfw5m3BJEaL1xIzTJd41HXjgJ9gq7mroBDXqSJIzic/edit
  • Loading branch information
asakusuma committed Sep 27, 2019
1 parent 306955e commit 0954b39
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 2 deletions.
52 changes: 52 additions & 0 deletions clear-site-data/storage.https.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
return datatype.name == "storage";
})[0];

var serviceWorkerTestPageIFrame;

// The tests are set up asynchronously.
setup({"explicit_done": true});

Expand All @@ -22,7 +24,30 @@
test(function() {}, "Populate backends.");

TestUtils.populateStorage()
.then(() => {
return navigator.serviceWorker.getRegistration("support/").then(function(reg) {
return TestUtils.waitForServiceWorkerActive(reg);
});
})
.then(() => {
const serviceWorkerResponseBody = TestUtils.getServiceWorkerControlledResponseBody();

// 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";
document.body.appendChild(serviceWorkerTestPageIFrame);

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 = TestUtils.waitForControllerChangeEvent();
// 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.
Expand All @@ -45,6 +70,33 @@
}, test_name);
});

promise_test(function() {
const serviceWorkerResponseBody = TestUtils.getServiceWorkerControlledResponseBody();

// Tell the iframe to make a request for the URL that the service worker
// responds to
serviceWorkerTestPageIFrame.contentWindow.postMessage(null, "*");

return serviceWorkerResponseBody.then(function(body) {
assert_equals(body, "FROM_NETWORK", "Response should not be 200");
});
}, "Service worker no longer responds to requests");

promise_test(function() {
const halfSecondTimeout = new Promise(function(resolve, reject) {
step_timeout(() => {
reject("controllerchange event was not fired");
}, 500);
});

return Promise.race([
waitForControllerChange,
halfSecondTimeout
]).then(function(hasController) {
assert_false(hasController, "Client should not have a controller");
});
}, "controllerchange event fires and client no longer has controller");

done();
});
});
Expand Down
3 changes: 3 additions & 0 deletions clear-site-data/support/controlled-endpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def main(request, response):
return ([("Content-Type", "text/html")],
"FROM_NETWORK")
38 changes: 38 additions & 0 deletions clear-site-data/support/page_using_service_worker.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<title>Clear-Site-Data + Service Workers Test Page</title>
</head>
<body>
<script>
function beaconResponseBody(body) {
window.top.postMessage({
subject: "service-worker-page-request",
body: body
}, "*");
}
function requestServiceWorkerPage() {
// Make a request to a URL that can be controlled by the service worker
fetch("controlled-endpoint.py").then(function(result) {
if (!result.ok) {
beaconResponseBody();
} else {
result.text().then(function (text) {
beaconResponseBody(text);
});
}
});
}
window.addEventListener("message", requestServiceWorkerPage);

navigator.serviceWorker.addEventListener("controllerchange", function() {
window.top.postMessage({
subject: "service-worker-page-controllerchange",
hasController: !!navigator.serviceWorker.controller
}, "*");
});

requestServiceWorkerPage();
</script>
</body>
</html>
7 changes: 6 additions & 1 deletion clear-site-data/support/service_worker.js
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
/* This file is intentionally left blank. */
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'));
}
});
56 changes: 55 additions & 1 deletion clear-site-data/support/test_utils.sub.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ var TestUtils = (function() {
"add": function() {
return navigator.serviceWorker.register(
"support/service_worker.js",
{ scope: "support/scope-that-does-not-contain-this-test/"});
{ scope: "support/"});
},
"isEmpty": function() {
return new Promise(function(resolve, reject) {
Expand Down Expand Up @@ -245,6 +245,60 @@ var TestUtils = (function() {
*/
TestUtils.populateStorage = populate.bind(this, TestUtils.STORAGE);

/**
* Wait for a postMessage from an iFrame, specifically a message that communicates
* the result of a request that is served by the service worker
* @private
*/
TestUtils.getServiceWorkerControlledResponseBody = function() {
return new Promise(function(resolve) {
const cb = (m) => {
if (m.data && m.data.subject === "service-worker-page-request") {
window.removeEventListener("message", cb);
resolve(m.data.body);
}
};
window.addEventListener("message", cb);
});
}

/**
* Wait for a postMessage from an iFrame, specifically a message that communicates
* that a controllerchange event has happened.
* @private
*/
TestUtils.waitForControllerChangeEvent = function() {
return new Promise(function(resolve) {
const cb = (m) => {
if (m.data && m.data.subject === "service-worker-page-controllerchange") {
window.removeEventListener("message", cb);
resolve(m.data.hasController);
}
};
window.addEventListener("message", cb);
});
}

/**
* Returns a promise that resolves when the registration has an active worker
* @param reg The service worker registration object
* @return a promise that resolves when the worker becomes active
* @private
*/
TestUtils.waitForServiceWorkerActive = function(reg) {
return new Promise((resolve) => {
(function pollForControllingWorker() {
if (reg.active && reg.active.state === 'activated') {
resolve();
} else {
step_timeout(() => {
pollForControllingWorker();
}, 100);
}
})();
});
}

/**
* Get the support server URL that returns a Clear-Site-Data header
* to clear |datatypes|.
Expand Down

0 comments on commit 0954b39

Please sign in to comment.