helper.sub.js (8117B)
1 // Helpers called on the main test HTMLs. 2 // Functions in `RemoteContext.execute_script()`'s 1st argument are evaluated 3 // on the executors (`executor.html`), and helpers available on the executors 4 // are defined in `executor.html`. 5 6 const originSameOrigin = 7 location.protocol === 'http:' ? 8 'http://{{host}}:{{ports[http][0]}}' : 9 'https://{{host}}:{{ports[https][0]}}'; 10 const originSameSite = 11 location.protocol === 'http:' ? 12 'http://{{host}}:{{ports[http][1]}}' : 13 'https://{{host}}:{{ports[https][1]}}'; 14 const originCrossSite = 15 location.protocol === 'http:' ? 16 'http://{{hosts[alt][www]}}:{{ports[http][0]}}' : 17 'https://{{hosts[alt][www]}}:{{ports[https][0]}}'; 18 19 const executorPath = 20 '/html/browsers/browsing-the-web/back-forward-cache/resources/executor.html?uuid='; 21 22 // Asserts that the executor `target` is (or isn't, respectively) 23 // restored from BFCache. These should be used in the following fashion: 24 // 1. Call prepareNavigation() on the executor `target`. 25 // 2. Navigate the executor to another page. 26 // 3. Navigate back to the executor `target`. 27 // 4. Call assert_bfcached() or assert_not_bfcached() on the main test HTML. 28 async function assert_bfcached(target) { 29 const status = await getBFCachedStatus(target); 30 assert_implements_optional(status === 'BFCached', 31 "Could have been BFCached but actually wasn't"); 32 } 33 34 async function assert_not_bfcached(target) { 35 const status = await getBFCachedStatus(target); 36 assert_implements(status !== 'BFCached', 37 'Should not be BFCached but actually was'); 38 } 39 40 async function getBFCachedStatus(target) { 41 const [loadCount, isPageshowFired, isPageshowPersisted] = 42 await target.execute_script(() => [ 43 window.loadCount, window.isPageshowFired, window.isPageshowPersisted]); 44 45 if (loadCount === 1 && isPageshowFired === true && 46 isPageshowPersisted === true) { 47 return 'BFCached'; 48 } else if (loadCount === 2 && isPageshowFired === false) { 49 return 'Not BFCached'; 50 } else { 51 // This can occur for example when this is called before first navigating 52 // away (loadCount = 1, isPageshowFired = false), e.g. when 53 // 1. sending a script for navigation and then 54 // 2. calling getBFCachedStatus() without waiting for the completion of 55 // the script on the `target` page. 56 assert_unreached( 57 `Got unexpected BFCache status: loadCount = ${loadCount}, ` + 58 `isPageshowFired = ${isPageshowFired}, ` + 59 `isPageshowPersisted = ${isPageshowPersisted}`); 60 } 61 } 62 63 // Always call `await remoteContext.execute_script(waitForPageShow);` after 64 // triggering to navigation to the page, to wait for pageshow event on the 65 // remote context. 66 const waitForPageShow = () => window.pageShowPromise; 67 68 // Run a test that navigates A->B->A: 69 // 1. Page A is opened by `params.openFunc(url)`. 70 // 2. `params.funcBeforeNavigation(params.argsBeforeNavigation)` is executed 71 // on page A. 72 // 3. The window is navigated to page B on `params.targetOrigin`. 73 // 4. The window is back navigated to page A (expecting BFCached). 74 // 75 // Events `params.events` (an array of strings) are observed on page A and 76 // `params.expectedEvents` (an array of strings) is expected to be recorded. 77 // See `event-recorder.js` for event recording. 78 // 79 // Parameters can be omitted. See `defaultParams` below for default. 80 function runEventTest(params, description) { 81 const defaultParams = { 82 openFunc(url) { 83 window.open( 84 `${url}&events=${this.events.join(',')}`, 85 '_blank', 86 'noopener' 87 ) 88 }, 89 events: ['pagehide', 'pageshow', 'load'], 90 expectedEvents: [ 91 'window.load', 92 'window.pageshow', 93 'window.pagehide.persisted', 94 'window.pageshow.persisted' 95 ], 96 async funcAfterAssertion(pageA) { 97 assert_array_equals( 98 await pageA.execute_script(() => getRecordedEvents()), 99 this.expectedEvents); 100 } 101 } 102 // Apply defaults. 103 params = { ...defaultParams, ...params }; 104 105 runBfcacheTest(params, description); 106 } 107 108 async function navigateAndThenBack(pageA, pageB, urlB, scripts = [], 109 funcBeforeBackNavigation, 110 argsBeforeBackNavigation) { 111 await pageA.execute_script( 112 (url) => { 113 prepareNavigation(() => { 114 location.href = url; 115 }); 116 }, 117 [urlB] 118 ); 119 120 await pageB.execute_script(waitForPageShow); 121 122 for (const src of scripts) { 123 await pageB.execute_script((src) => { 124 const script = document.createElement("script"); 125 script.src = src; 126 document.head.append(script); 127 return new Promise(resolve => script.onload = resolve); 128 }, [src]); 129 } 130 131 if (funcBeforeBackNavigation) { 132 await pageB.execute_script(funcBeforeBackNavigation, 133 argsBeforeBackNavigation); 134 } 135 await pageB.execute_script( 136 () => { 137 prepareNavigation(() => { history.back(); }); 138 } 139 ); 140 141 await pageA.execute_script(waitForPageShow); 142 } 143 144 function runBfcacheTest(params, description) { 145 const defaultParams = { 146 openFunc: url => window.open(url, '_blank', 'noopener'), 147 scripts: [], 148 funcBeforeNavigation: () => {}, 149 argsBeforeNavigation: [], 150 targetOrigin: originCrossSite, 151 funcBeforeBackNavigation: () => {}, 152 argsBeforeBackNavigation: [], 153 shouldBeCached: true, 154 funcAfterAssertion: () => {}, 155 } 156 // Apply defaults. 157 params = {...defaultParams, ...params }; 158 159 promise_test(async t => { 160 const pageA = new RemoteContext(token()); 161 const pageB = new RemoteContext(token()); 162 163 const urlA = executorPath + pageA.context_id; 164 const urlB = params.targetOrigin + executorPath + pageB.context_id; 165 166 // So that tests can refer to these URLs for assertions if necessary. 167 pageA.url = originSameOrigin + urlA; 168 pageB.url = urlB; 169 170 params.openFunc(urlA); 171 172 await pageA.execute_script(waitForPageShow); 173 174 for (const src of params.scripts) { 175 await pageA.execute_script((src) => { 176 const script = document.createElement("script"); 177 script.src = src; 178 document.head.append(script); 179 return new Promise(resolve => script.onload = resolve); 180 }, [src]); 181 } 182 183 await pageA.execute_script(params.funcBeforeNavigation, 184 params.argsBeforeNavigation); 185 await navigateAndThenBack(pageA, pageB, urlB, params.scripts, 186 params.funcBeforeBackNavigation, 187 params.argsBeforeBackNavigation); 188 189 if (params.shouldBeCached) { 190 await assert_bfcached(pageA); 191 } else { 192 await assert_not_bfcached(pageA); 193 } 194 195 if (params.funcAfterAssertion) { 196 await params.funcAfterAssertion(pageA, pageB, t); 197 } 198 }, description); 199 } 200 201 // Call clients.claim() on the service worker 202 async function claim(t, worker) { 203 const channel = new MessageChannel(); 204 const sawMessage = new Promise(function(resolve) { 205 channel.port1.onmessage = t.step_func(function(e) { 206 assert_equals(e.data, 'PASS', 'Worker call to claim() should fulfill.'); 207 resolve(); 208 }); 209 }); 210 worker.postMessage({type: "claim", port: channel.port2}, [channel.port2]); 211 await sawMessage; 212 } 213 214 // Assigns the current client to a local variable on the service worker. 215 async function storeClients(t, worker) { 216 const channel = new MessageChannel(); 217 const sawMessage = new Promise(function(resolve) { 218 channel.port1.onmessage = t.step_func(function(e) { 219 assert_equals(e.data, 'PASS', 'storeClients'); 220 resolve(); 221 }); 222 }); 223 worker.postMessage({type: "storeClients", port: channel.port2}, [channel.port2]); 224 await sawMessage; 225 } 226 227 // Call storedClients.postMessage("") on the service worker 228 async function postMessageToStoredClients(t, worker) { 229 const channel = new MessageChannel(); 230 const sawMessage = new Promise(function(resolve) { 231 channel.port1.onmessage = t.step_func(function(e) { 232 assert_equals(e.data, 'PASS', 'postMessageToStoredClients'); 233 resolve(); 234 }); 235 }); 236 worker.postMessage({type: "postMessageToStoredClients", 237 port: channel.port2}, [channel.port2]); 238 await sawMessage; 239 }