1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
|
<!DOCTYPE html>
<title>Service Worker: about:blank replacement handling</title>
<meta name=timeout content=long>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/test-helpers.sub.js"></script>
<body>
<script>
// This test attempts to verify various initial about:blank document
// creation is accurately reflected via the Clients API. The goal is
// for Clients API to reflect what the browser actually does and not
// to make special cases for the API.
//
// If your browser does not create an about:blank document in certain
// cases then please just mark the test expected fail for now. The
// reuse of globals from about:blank documents to the final load document
// has particularly bad interop at the moment. Hopefully we can evolve
// tests like this to eventually align browsers.
const worker = 'resources/about-blank-replacement-worker.js';
// Helper routine that creates an iframe that internally has some kind
// of nested window. The nested window could be another iframe or
// it could be a popup window.
function createFrameWithNestedWindow(url) {
return new Promise((resolve, reject) => {
let frame = document.createElement('iframe');
frame.src = url;
document.body.appendChild(frame);
window.addEventListener('message', function onMsg(evt) {
if (evt.data.type !== 'NESTED_LOADED') {
return;
}
window.removeEventListener('message', onMsg);
if (evt.data.result && evt.data.result.startsWith('failure:')) {
reject(evt.data.result);
return;
}
resolve(frame);
});
});
}
// Helper routine to request the given worker find the client with
// the specified URL using the clients.matchAll() API.
function getClientIdByURL(worker, url) {
return new Promise(resolve => {
navigator.serviceWorker.addEventListener('message', function onMsg(evt) {
if (evt.data.type !== 'GET_CLIENT_ID') {
return;
}
navigator.serviceWorker.removeEventListener('message', onMsg);
resolve(evt.data.result);
});
worker.postMessage({ type: 'GET_CLIENT_ID', url: url.toString() });
});
}
async function doAsyncTest(t, scope) {
let reg = await service_worker_unregister_and_register(t, worker, scope);
t.add_cleanup(() => service_worker_unregister(t, scope));
await wait_for_state(t, reg.installing, 'activated');
// Load the scope as a frame. We expect this in turn to have a nested
// iframe. The service worker will intercept the load of the nested
// iframe and populate its body with the client ID of the initial
// about:blank document it sees via clients.matchAll().
let frame = await createFrameWithNestedWindow(scope);
let initialResult = frame.contentWindow.nested().document.body.textContent;
assert_false(initialResult.startsWith('failure:'), `result: ${initialResult}`);
assert_equals(frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
frame.contentWindow.nested().navigator.serviceWorker.controller.scriptURL,
'nested about:blank should have same controlling service worker');
// Next, ask the service worker to find the final client ID for the fully
// loaded nested frame.
let nestedURL = new URL(frame.contentWindow.nested().location);
let finalResult = await getClientIdByURL(reg.active, nestedURL);
assert_false(finalResult.startsWith('failure:'), `result: ${finalResult}`);
// If the nested frame doesn't have a URL to load, then there is no fetch
// event and the body should be empty. We can't verify the final client ID
// against anything.
if (nestedURL.href === 'about:blank' ||
nestedURL.href === 'about:srcdoc') {
assert_equals('', initialResult, 'about:blank text content should be blank');
}
// If the nested URL is not about:blank, though, then the fetch event handler
// should have populated the body with the client id of the initial about:blank.
// Verify the final client id matches.
else {
assert_equals(initialResult, finalResult, 'client ID values should match');
}
frame.remove();
}
promise_test(async function(t) {
// Execute a test where the nested frame is simply loaded normally.
await doAsyncTest(t, 'resources/about-blank-replacement-frame.py');
}, 'Initial about:blank is controlled, exposed to clients.matchAll(), and ' +
'matches final Client.');
promise_test(async function(t) {
// Execute a test where the nested frame is modified immediately by
// its parent. In this case we add a message listener so the service
// worker can ping the client to verify its existence. This ping-pong
// check is performed during the initial load and when verifying the
// final loaded client.
await doAsyncTest(t, 'resources/about-blank-replacement-ping-frame.py');
}, 'Initial about:blank modified by parent is controlled, exposed to ' +
'clients.matchAll(), and matches final Client.');
promise_test(async function(t) {
// Execute a test where the nested window is a popup window instead of
// an iframe. This should behave the same as the simple iframe case.
await doAsyncTest(t, 'resources/about-blank-replacement-popup-frame.py');
}, 'Popup initial about:blank is controlled, exposed to clients.matchAll(), and ' +
'matches final Client.');
promise_test(async function(t) {
const scope = 'resources/about-blank-replacement-uncontrolled-nested-frame.html';
let reg = await service_worker_unregister_and_register(t, worker, scope);
t.add_cleanup(() => service_worker_unregister(t, scope));
await wait_for_state(t, reg.installing, 'activated');
// Load the scope as a frame. We expect this in turn to have a nested
// iframe. Unlike the other tests in this file the nested iframe URL
// is not covered by a service worker scope. It should end up as
// uncontrolled even though its initial about:blank is controlled.
let frame = await createFrameWithNestedWindow(scope);
let nested = frame.contentWindow.nested();
let initialResult = nested.document.body.textContent;
// The nested iframe should not have been intercepted by the service
// worker. The empty.html nested frame has "hello world" for its body.
assert_equals(initialResult.trim(), 'hello world', `result: ${initialResult}`);
assert_not_equals(frame.contentWindow.navigator.serviceWorker.controller, null,
'outer frame should be controlled');
assert_equals(nested.navigator.serviceWorker.controller, null,
'nested frame should not be controlled');
frame.remove();
}, 'Initial about:blank is controlled, exposed to clients.matchAll(), and ' +
'final Client is not controlled by a service worker.');
promise_test(async function(t) {
// Execute a test where the nested frame is an iframe without a src
// attribute. This simple nested about:blank should still inherit the
// controller and be visible to clients.matchAll().
await doAsyncTest(t, 'resources/about-blank-replacement-blank-nested-frame.html');
}, 'Simple about:blank is controlled and is exposed to clients.matchAll().');
promise_test(async function(t) {
// Execute a test where the nested frame is an iframe using a non-empty
// srcdoc containing only a tag pair so its textContent is still empty.
// This nested iframe should still inherit the controller and be visible
// to clients.matchAll().
await doAsyncTest(t, 'resources/about-blank-replacement-srcdoc-nested-frame.html');
}, 'Nested about:srcdoc is controlled and is exposed to clients.matchAll().');
promise_test(async function(t) {
// Execute a test where the nested frame is dynamically added without a src
// attribute. This simple nested about:blank should still inherit the
// controller and be visible to clients.matchAll().
await doAsyncTest(t, 'resources/about-blank-replacement-blank-dynamic-nested-frame.html');
}, 'Dynamic about:blank is controlled and is exposed to clients.matchAll().');
</script>
</body>
|