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