browser_NavigationManager.js (12942B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const { NavigableManager } = ChromeUtils.importESModule( 6 "chrome://remote/content/shared/NavigableManager.sys.mjs" 7 ); 8 9 const FIRST_URL = "https://example.com/document-builder.sjs?html=first"; 10 const SECOND_URL = "https://example.com/document-builder.sjs?html=second"; 11 const THIRD_URL = "https://example.com/document-builder.sjs?html=third"; 12 13 const FIRST_COOP_URL = 14 "https://example.com/document-builder.sjs?headers=Cross-Origin-Opener-Policy:same-origin&html=first_coop"; 15 const SECOND_COOP_URL = 16 "https://example.net/document-builder.sjs?headers=Cross-Origin-Opener-Policy:same-origin&html=second_coop"; 17 18 add_task(async function test_simpleNavigation() { 19 const events = []; 20 const onEvent = (name, data) => events.push({ name, data }); 21 22 const navigationManager = new NavigationManager(); 23 navigationManager.on("navigation-started", onEvent); 24 navigationManager.on("navigation-stopped", onEvent); 25 26 const tab = await addTabAndWaitForNavigated(gBrowser, FIRST_URL); 27 const browser = tab.linkedBrowser; 28 29 const navigableId = NavigableManager.getIdForBrowser(browser); 30 31 navigationManager.startMonitoring(); 32 is( 33 navigationManager.getNavigationForBrowsingContext(browser.browsingContext), 34 null, 35 "No navigation recorded yet" 36 ); 37 is(events.length, 0, "No event recorded"); 38 39 await loadURL(browser, SECOND_URL); 40 await BrowserTestUtils.waitForCondition(() => events.length === 2); 41 42 const firstNavigation = navigationManager.getNavigationForBrowsingContext( 43 browser.browsingContext 44 ); 45 assertNavigation(firstNavigation, SECOND_URL); 46 47 is(events.length, 2, "Two events recorded"); 48 assertNavigationEvents( 49 events, 50 SECOND_URL, 51 firstNavigation.navigationId, 52 navigableId 53 ); 54 55 await loadURL(browser, THIRD_URL); 56 await BrowserTestUtils.waitForCondition(() => events.length === 4); 57 58 const secondNavigation = navigationManager.getNavigationForBrowsingContext( 59 browser.browsingContext 60 ); 61 assertNavigation(secondNavigation, THIRD_URL); 62 assertUniqueNavigationIds(firstNavigation, secondNavigation); 63 64 is(events.length, 4, "Two new events recorded"); 65 assertNavigationEvents( 66 events, 67 THIRD_URL, 68 secondNavigation.navigationId, 69 navigableId 70 ); 71 72 navigationManager.stopMonitoring(); 73 74 // Navigate again to the first URL 75 await loadURL(browser, FIRST_URL); 76 is(events.length, 4, "No new event recorded"); 77 is( 78 navigationManager.getNavigationForBrowsingContext(browser.browsingContext), 79 null, 80 "No navigation recorded" 81 ); 82 83 navigationManager.off("navigation-started", onEvent); 84 navigationManager.off("navigation-stopped", onEvent); 85 }); 86 87 add_task(async function test_loadTwoTabsSimultaneously() { 88 const events = []; 89 const onEvent = (name, data) => events.push({ name, data }); 90 91 const navigationManager = new NavigationManager(); 92 navigationManager.on("navigation-started", onEvent); 93 navigationManager.on("navigation-stopped", onEvent); 94 95 navigationManager.startMonitoring(); 96 97 info("Add two tabs simultaneously"); 98 const tab1 = addTab(gBrowser, FIRST_URL); 99 const browser1 = tab1.linkedBrowser; 100 const navigableId1 = NavigableManager.getIdForBrowser(browser1); 101 const onLoad1 = BrowserTestUtils.browserLoaded(browser1, false, FIRST_URL); 102 103 const tab2 = addTab(gBrowser, SECOND_URL); 104 const browser2 = tab2.linkedBrowser; 105 const navigableId2 = NavigableManager.getIdForBrowser(browser2); 106 const onLoad2 = BrowserTestUtils.browserLoaded(browser2, false, SECOND_URL); 107 108 info("Wait for the tabs to load"); 109 await Promise.all([onLoad1, onLoad2]); 110 await BrowserTestUtils.waitForCondition(() => events.length === 4); 111 112 is(events.length, 4, "Recorded 4 navigation events"); 113 114 info("Check navigation monitored for tab1"); 115 const nav1 = navigationManager.getNavigationForBrowsingContext( 116 browser1.browsingContext 117 ); 118 assertNavigation(nav1, FIRST_URL); 119 assertNavigationEvents(events, FIRST_URL, nav1.navigationId, navigableId1); 120 121 info("Check navigation monitored for tab2"); 122 const nav2 = navigationManager.getNavigationForBrowsingContext( 123 browser2.browsingContext 124 ); 125 assertNavigation(nav2, SECOND_URL); 126 assertNavigationEvents(events, SECOND_URL, nav2.navigationId, navigableId2); 127 assertUniqueNavigationIds(nav1, nav2); 128 129 info("Reload the two tabs simultaneously"); 130 await Promise.all([ 131 BrowserTestUtils.reloadTab(tab1), 132 BrowserTestUtils.reloadTab(tab2), 133 ]); 134 await BrowserTestUtils.waitForCondition(() => events.length === 8); 135 136 is(events.length, 8, "Recorded 8 navigation events"); 137 138 info("Check the second navigation for tab1"); 139 const nav3 = navigationManager.getNavigationForBrowsingContext( 140 browser1.browsingContext 141 ); 142 assertNavigation(nav3, FIRST_URL); 143 assertNavigationEvents(events, FIRST_URL, nav3.navigationId, navigableId1); 144 145 info("Check the second navigation monitored for tab2"); 146 const nav4 = navigationManager.getNavigationForBrowsingContext( 147 browser2.browsingContext 148 ); 149 assertNavigation(nav4, SECOND_URL); 150 assertNavigationEvents(events, SECOND_URL, nav4.navigationId, navigableId2); 151 assertUniqueNavigationIds(nav1, nav2, nav3, nav4); 152 153 navigationManager.off("navigation-started", onEvent); 154 navigationManager.off("navigation-stopped", onEvent); 155 navigationManager.stopMonitoring(); 156 }); 157 158 add_task(async function test_loadPageWithIframes() { 159 const events = []; 160 const onEvent = (name, data) => events.push({ name, data }); 161 162 const navigationManager = new NavigationManager(); 163 navigationManager.on("navigation-started", onEvent); 164 navigationManager.on("navigation-stopped", onEvent); 165 166 navigationManager.startMonitoring(); 167 168 info("Add a tab with iframes"); 169 const testUrl = createTestPageWithFrames(); 170 const tab = addTab(gBrowser, testUrl); 171 const browser = tab.linkedBrowser; 172 await BrowserTestUtils.browserLoaded(browser, false, testUrl); 173 await BrowserTestUtils.waitForCondition(() => events.length === 8); 174 175 is(events.length, 8, "Recorded 8 navigation events"); 176 const contexts = browser.browsingContext.getAllBrowsingContextsInSubtree(); 177 178 const navigations = []; 179 for (const context of contexts) { 180 const navigation = 181 navigationManager.getNavigationForBrowsingContext(context); 182 const navigable = NavigableManager.getIdForBrowsingContext(context); 183 184 const url = context.currentWindowGlobal.documentURI.spec; 185 assertNavigation(navigation, url); 186 assertNavigationEvents(events, url, navigation.navigationId, navigable); 187 navigations.push(navigation); 188 } 189 assertUniqueNavigationIds(...navigations); 190 191 await BrowserTestUtils.reloadTab(tab); 192 await BrowserTestUtils.waitForCondition(() => events.length === 16); 193 194 is(events.length, 16, "Recorded 8 additional navigation events"); 195 const newContexts = browser.browsingContext.getAllBrowsingContextsInSubtree(); 196 197 for (const context of newContexts) { 198 const navigation = 199 navigationManager.getNavigationForBrowsingContext(context); 200 const navigable = NavigableManager.getIdForBrowsingContext(context); 201 202 const url = context.currentWindowGlobal.documentURI.spec; 203 assertNavigation(navigation, url); 204 assertNavigationEvents(events, url, navigation.navigationId, navigable); 205 navigations.push(navigation); 206 } 207 assertUniqueNavigationIds(...navigations); 208 209 navigationManager.off("navigation-started", onEvent); 210 navigationManager.off("navigation-stopped", onEvent); 211 navigationManager.stopMonitoring(); 212 }); 213 214 add_task(async function test_loadPageWithCoop() { 215 const tab = await addTabAndWaitForNavigated(gBrowser, FIRST_COOP_URL); 216 const browser = tab.linkedBrowser; 217 218 const events = []; 219 const onEvent = (name, data) => events.push({ name, data }); 220 221 const navigationManager = new NavigationManager(); 222 navigationManager.on("navigation-started", onEvent); 223 navigationManager.on("navigation-stopped", onEvent); 224 225 navigationManager.startMonitoring(); 226 227 const navigableId = NavigableManager.getIdForBrowser(browser); 228 await loadURL(browser, SECOND_COOP_URL); 229 await BrowserTestUtils.waitForCondition(() => events.length === 2); 230 231 const coopNavigation = navigationManager.getNavigationForBrowsingContext( 232 browser.browsingContext 233 ); 234 assertNavigation(coopNavigation, SECOND_COOP_URL); 235 236 is(events.length, 2, "Two events recorded"); 237 assertNavigationEvents( 238 events, 239 SECOND_COOP_URL, 240 coopNavigation.navigationId, 241 navigableId 242 ); 243 244 navigationManager.off("navigation-started", onEvent); 245 navigationManager.off("navigation-stopped", onEvent); 246 navigationManager.stopMonitoring(); 247 }); 248 249 add_task(async function test_sameDocumentNavigation() { 250 const events = []; 251 const onEvent = (name, data) => events.push({ name, data }); 252 253 const navigationManager = new NavigationManager(); 254 navigationManager.on("fragment-navigated", onEvent); 255 navigationManager.on("navigation-started", onEvent); 256 navigationManager.on("navigation-stopped", onEvent); 257 navigationManager.on("same-document-changed", onEvent); 258 259 const url = "https://example.com/document-builder.sjs?html=test"; 260 const tab = await addTabAndWaitForNavigated(gBrowser, url); 261 const browser = tab.linkedBrowser; 262 263 navigationManager.startMonitoring(); 264 const navigableId = NavigableManager.getIdForBrowser(browser); 265 266 is(events.length, 0, "No event recorded"); 267 268 info("Perform a same-document navigation"); 269 let onFragmentNavigated = navigationManager.once("fragment-navigated"); 270 BrowserTestUtils.startLoadingURIString(browser, url + "#hash"); 271 await onFragmentNavigated; 272 273 const hashNavigation = navigationManager.getNavigationForBrowsingContext( 274 browser.browsingContext 275 ); 276 is(events.length, 1, "Recorded 1 navigation event"); 277 assertNavigationEvents( 278 events, 279 url + "#hash", 280 hashNavigation.navigationId, 281 navigableId, 282 true 283 ); 284 285 // Navigate from `url + "#hash"` to `url`, this will trigger a regular 286 // navigation and we can use `loadURL` to properly wait for the navigation to 287 // complete. 288 info("Perform a regular navigation"); 289 await loadURL(browser, url); 290 await BrowserTestUtils.waitForCondition(() => events.length === 3); 291 292 const regularNavigation = navigationManager.getNavigationForBrowsingContext( 293 browser.browsingContext 294 ); 295 is(events.length, 3, "Recorded 2 additional navigation events"); 296 assertNavigationEvents( 297 events, 298 url, 299 regularNavigation.navigationId, 300 navigableId 301 ); 302 303 info("Perform another hash navigation"); 304 onFragmentNavigated = navigationManager.once("fragment-navigated"); 305 BrowserTestUtils.startLoadingURIString(browser, url + "#foo"); 306 await onFragmentNavigated; 307 308 const otherHashNavigation = navigationManager.getNavigationForBrowsingContext( 309 browser.browsingContext 310 ); 311 312 is(events.length, 4, "Recorded 1 additional navigation event"); 313 314 info("Perform a same-hash navigation"); 315 onFragmentNavigated = navigationManager.once("fragment-navigated"); 316 BrowserTestUtils.startLoadingURIString(browser, url + "#foo"); 317 await onFragmentNavigated; 318 319 const sameHashNavigation = navigationManager.getNavigationForBrowsingContext( 320 browser.browsingContext 321 ); 322 323 is(events.length, 5, "Recorded 1 additional navigation event"); 324 assertNavigationEvents( 325 events, 326 url + "#foo", 327 sameHashNavigation.navigationId, 328 navigableId, 329 true 330 ); 331 332 assertUniqueNavigationIds([ 333 hashNavigation, 334 regularNavigation, 335 otherHashNavigation, 336 sameHashNavigation, 337 ]); 338 339 navigationManager.off("fragment-navigated", onEvent); 340 navigationManager.off("navigation-started", onEvent); 341 navigationManager.off("navigation-stopped", onEvent); 342 navigationManager.off("same-document-changed", onEvent); 343 344 navigationManager.stopMonitoring(); 345 }); 346 347 add_task(async function test_startNavigationAndCloseTab() { 348 const events = []; 349 const onEvent = (name, data) => events.push({ name, data }); 350 351 const navigationManager = new NavigationManager(); 352 navigationManager.on("navigation-started", onEvent); 353 navigationManager.on("navigation-stopped", onEvent); 354 355 const tab = await addTabAndWaitForNavigated(gBrowser, FIRST_URL); 356 const browser = tab.linkedBrowser; 357 358 navigationManager.startMonitoring(); 359 loadURL(browser, SECOND_URL); 360 gBrowser.removeTab(tab); 361 362 // On top of the assertions below, the test also validates that there is no 363 // unhandled promise rejection related to handling the navigation-started event 364 // for the destroyed browsing context. 365 is(events.length, 0, "No event was received"); 366 is( 367 navigationManager.getNavigationForBrowsingContext(browser.browsingContext), 368 null, 369 "No navigation was recorded for the destroyed tab" 370 ); 371 navigationManager.stopMonitoring(); 372 373 navigationManager.off("navigation-started", onEvent); 374 navigationManager.off("navigation-stopped", onEvent); 375 });