window-global.sys.mjs (28184B)
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 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { ContentProcessWatcherRegistry } from "resource://devtools/server/connectors/js-process-actor/ContentProcessWatcherRegistry.sys.mjs"; 6 7 const lazy = {}; 8 ChromeUtils.defineESModuleGetters( 9 lazy, 10 { 11 HTMLSourcesCache: 12 "resource://devtools/server/actors/utils/HTMLSourcesCache.sys.mjs", 13 isWindowGlobalPartOfContext: 14 "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs", 15 WEBEXTENSION_FALLBACK_DOC_URL: 16 "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs", 17 }, 18 { global: "contextual" } 19 ); 20 21 // TargetActorRegistery has to be shared between all devtools instances 22 // and so is loaded into the shared global. 23 ChromeUtils.defineESModuleGetters( 24 lazy, 25 { 26 TargetActorRegistry: 27 "resource://devtools/server/actors/targets/target-actor-registry.sys.mjs", 28 }, 29 { global: "shared" } 30 ); 31 32 function watch() { 33 // Set the following preference in this function, so that we can easily 34 // toggle these preferences on and off from tests and have the new value being picked up. 35 36 // bfcache-in-parent changes significantly how navigation behaves. 37 // We may start reusing previously existing WindowGlobal and so reuse 38 // previous set of JSWindowActor pairs (i.e. DevToolsProcessParent/DevToolsProcessChild). 39 // When enabled, regular navigations may also change and spawn new BrowsingContexts. 40 // If the page we navigate from supports being stored in bfcache, 41 // the navigation will use a new BrowsingContext. And so force spawning 42 // a new top-level target. 43 ChromeUtils.defineLazyGetter( 44 lazy, 45 "isBfcacheInParentEnabled", 46 () => 47 Services.appinfo.sessionHistoryInParent && 48 Services.prefs.getBoolPref("fission.bfcacheInParent", false) 49 ); 50 51 // Observe for all necessary event to track new and destroyed WindowGlobals. 52 Services.obs.addObserver(observe, "content-document-global-created"); 53 Services.obs.addObserver(observe, "chrome-document-global-created"); 54 Services.obs.addObserver(observe, "content-page-shown"); 55 Services.obs.addObserver(observe, "chrome-page-shown"); 56 Services.obs.addObserver(observe, "content-page-hidden"); 57 Services.obs.addObserver(observe, "chrome-page-hidden"); 58 Services.obs.addObserver(observe, "inner-window-destroyed"); 59 Services.obs.addObserver(observe, "initial-document-element-inserted"); 60 } 61 62 function unwatch() { 63 // Observe for all necessary event to track new and destroyed WindowGlobals. 64 Services.obs.removeObserver(observe, "content-document-global-created"); 65 Services.obs.removeObserver(observe, "chrome-document-global-created"); 66 Services.obs.removeObserver(observe, "content-page-shown"); 67 Services.obs.removeObserver(observe, "chrome-page-shown"); 68 Services.obs.removeObserver(observe, "content-page-hidden"); 69 Services.obs.removeObserver(observe, "chrome-page-hidden"); 70 Services.obs.removeObserver(observe, "inner-window-destroyed"); 71 Services.obs.removeObserver(observe, "initial-document-element-inserted"); 72 } 73 74 function createTargetsForWatcher(watcherDataObject, isProcessActorStartup) { 75 const { sessionContext } = watcherDataObject; 76 // Bug 1785266 - For now, in browser, when debugging the parent process (childID == 0), 77 // we spawn only the ParentProcessTargetActor, which will debug all the BrowsingContext running in the process. 78 // So that we have to avoid instantiating any here. 79 if ( 80 sessionContext.type == "all" && 81 ChromeUtils.domProcessChild.childID === 0 82 ) { 83 return; 84 } 85 86 function lookupForTargets(window) { 87 // Do not only track top level BrowsingContext in this content process, 88 // but also any nested iframe which may be running in the same process. 89 for (const browsingContext of window.docShell.browsingContext.getAllBrowsingContextsInSubtree()) { 90 const { currentWindowContext } = browsingContext; 91 // Only consider Window Global which are running in this process 92 if (!currentWindowContext || !currentWindowContext.isInProcess) { 93 continue; 94 } 95 96 // WindowContext's windowGlobalChild should be defined for WindowGlobal running in this process 97 const { windowGlobalChild } = currentWindowContext; 98 99 // getWindowEnumerator will expose somewhat unexpected WindowGlobal when a tab navigated. 100 // This will expose WindowGlobals of past navigations. Document which are in the bfcache 101 // and aren't the current WindowGlobal of their BrowsingContext. 102 if (!windowGlobalChild.isCurrentGlobal) { 103 continue; 104 } 105 106 // Accept the initial about:blank document: 107 // - only from createTargetsForWatcher, when instantiating the target for the already existing WindowGlobals, 108 // - when we do that on toolbox opening, to prevent creating one when the process is starting. 109 // 110 // This is to allow debugging blank tabs, which are on an initial about:blank document. 111 // 112 // We want to avoid creating transient targets for initial about blank when a new WindowGlobal 113 // just get created as it will most likely navigate away just after and confuse the frontend with short lived target. 114 const acceptUncommitedInitialDocument = !isProcessActorStartup; 115 116 if ( 117 lazy.isWindowGlobalPartOfContext(windowGlobalChild, sessionContext, { 118 acceptUncommitedInitialDocument, 119 }) 120 ) { 121 createWindowGlobalTargetActor(watcherDataObject, windowGlobalChild); 122 } else if ( 123 !browsingContext.parent && 124 sessionContext.browserId && 125 browsingContext.browserId == sessionContext.browserId && 126 browsingContext.window.document.isInitialDocument 127 ) { 128 // In order to succesfully get the devtools-html-content event in SourcesManager, 129 // we have to ensure flagging the initial about:blank document... 130 // While we don't create a target for it, we need to set this flag for this event to be emitted. 131 browsingContext.watchedByDevTools = true; 132 } 133 } 134 } 135 136 const topWindows = []; 137 // Do not use a for loop on `windowEnumerator` as underlying call to `getNext` may throw on some windows 138 const windowEnumerator = Services.ww.getWindowEnumerator(); 139 while (windowEnumerator.hasMoreElements()) { 140 try { 141 const window = windowEnumerator.getNext(); 142 topWindows.push(window); 143 } catch (e) { 144 console.error("Failed to process a top level window", e); 145 } 146 } 147 148 // When debugging an extension, we have to ensure create the top level target first. 149 // The top level target is the one for the fallback document. 150 if (sessionContext.type == "webextension") { 151 const fallbackWindowIndex = topWindows.findIndex(window => 152 window.location.href.startsWith(lazy.WEBEXTENSION_FALLBACK_DOC_URL) 153 ); 154 if (fallbackWindowIndex != -1) { 155 const [fallbackWindow] = topWindows.splice(fallbackWindowIndex, 1); 156 topWindows.unshift(fallbackWindow); 157 } 158 } 159 160 for (const window of topWindows) { 161 lookupForTargets(window); 162 163 // `lookupForTargets` uses `getAllBrowsingContextsInSubTree`, but this will ignore browser elements 164 // using type="content". So manually retrieve the windows for these browser elements, 165 // in case we have tabs opened on document loaded in the same process. 166 // This codepath is meant when we are in the parent process, with browser.xhtml having these <browser type="content"> 167 // elements for tabs. 168 for (const browser of window.document.querySelectorAll( 169 `browser[type="content"]` 170 )) { 171 // Bug 1947777 - the browser context may not have an active DOM Window 172 const childWindow = browser.browsingContext?.window; 173 // If the tab isn't on a document loaded in the parent process, 174 // the window will be null. 175 if (childWindow) { 176 lookupForTargets(childWindow); 177 } 178 } 179 } 180 181 // Utility class to watch for HTML Sources text content emitted by the HTML Parser. 182 // As this can come from the previous WindowGlobal, we need this logic to be running outside 183 // of individual WindowGlobal targets. 184 if (sessionContext.type != "all") { 185 lazy.HTMLSourcesCache.watch(sessionContext.browserId); 186 } 187 } 188 189 function destroyTargetsForWatcher(watcherDataObject, options) { 190 // Unregister and destroy the existing target actors for this target type 191 const actorsToDestroy = watcherDataObject.actors.filter( 192 actor => actor.targetType == "frame" 193 ); 194 watcherDataObject.actors = watcherDataObject.actors.filter( 195 actor => actor.targetType != "frame" 196 ); 197 198 for (const actor of actorsToDestroy) { 199 ContentProcessWatcherRegistry.destroyTargetActor( 200 watcherDataObject, 201 actor, 202 options 203 ); 204 } 205 206 if (watcherDataObject.sessionContext.type != "all") { 207 lazy.HTMLSourcesCache.unwatch(watcherDataObject.sessionContext.browserId); 208 } 209 } 210 211 /** 212 * Called whenever a new WindowGlobal is instantiated either: 213 * - when navigating to a new page (DOMWindowCreated) 214 * - by a bfcache navigation (pageshow) 215 * 216 * @param {Window} window 217 * @param {object} options 218 * @param {boolean} options.isBFCache 219 * True, if the request to instantiate a new target comes from a bfcache navigation. 220 * i.e. when we receive a pageshow event with persisted=true. 221 * This will be true regardless of bfcacheInParent being enabled or disabled. 222 * @param {boolean} options.ignoreIfExisting 223 * By default to false. If true is passed, we avoid instantiating a target actor 224 * if one already exists for this windowGlobal. 225 */ 226 function onWindowGlobalCreated( 227 window, 228 { isBFCache = false, ignoreIfExisting = false } = {} 229 ) { 230 try { 231 const windowGlobal = window.windowGlobalChild; 232 233 // For bfcache navigations, we only create new targets when bfcacheInParent is enabled, 234 // as this would be the only case where new DocShells will be created. This requires us to spawn a 235 // new WindowGlobalTargetActor as such actor is bound to a unique DocShell. 236 const forceAcceptTopLevelTarget = 237 isBFCache && lazy.isBfcacheInParentEnabled; 238 239 for (const watcherDataObject of ContentProcessWatcherRegistry.getAllWatchersDataObjects( 240 "frame" 241 )) { 242 const { sessionContext } = watcherDataObject; 243 /* 244 try { 245 windowGlobal.browsingContext.watchedByDevTools = true; 246 } catch (e) {} 247 try { 248 windowGlobal.browsingContext.top.watchedByDevTools = true; 249 } catch (e) {} 250 */ 251 if ( 252 lazy.isWindowGlobalPartOfContext(windowGlobal, sessionContext, { 253 forceAcceptTopLevelTarget, 254 }) 255 ) { 256 // If this was triggered because of a navigation, we want to retrieve the existing 257 // target we were debugging so we can destroy it before creating the new target. 258 // This is important because we had cases where the destruction of an old target 259 // was unsetting a flag on the **new** target document, breaking the toolbox (See Bug 1721398). 260 261 // We're checking for an existing target given a watcherActorID + browserId + browsingContext. 262 // Note that a target switch might create a new browsing context, so we wouldn't 263 // retrieve the existing target here. We are okay with this as: 264 // - this shouldn't happen much 265 // - in such case we weren't seeing the issue of Bug 1721398 (the old target can't access the new document) 266 const existingTarget = findTargetActor({ 267 watcherDataObject, 268 innerWindowId: windowGlobal.innerWindowId, 269 // Also use a loose match per browsing context's ID instead of only window global's innerWindowId 270 // as we would like to destroy eagerly the previous target actor, 271 // which will anyway be wiped by the frontend as soon as we notify it about this new target. 272 // TargetCommand wipes all existing targets as soon as we receive a new Top Level target. 273 // 274 // Matching only per innerWindowId was making browser_webconsole_message_categories.js test to fail 275 // because some very early message resource was attached to this `existingTarget` actor. 276 // The `existingTarget` actor is actually destroyed and their related resource ignored. 277 // Instead we bind these early resources to the new target actor. 278 browsingContextID: windowGlobal.browsingContext.id, 279 }); 280 281 // See comment in `observe()` method and `DOMDocElementInserted` condition to know why we sometime 282 // ignore this method call if a target actor already exists. 283 // It means that we got a previous DOMWindowCreated event, related to a non-about:blank document, 284 // and we should ignore the DOMDocElementInserted. 285 // In any other scenario, destroy the already existing target and re-create a new one. 286 if (existingTarget && ignoreIfExisting) { 287 continue; 288 } 289 290 // Bail if there is already an existing WindowGlobalTargetActor which wasn't 291 // created from a JSWIndowActor. 292 // This means we are reloading or navigating (same-process) a Target 293 // which has not been created using the Watcher, but from the client (most likely 294 // the initial target of a local-tab toolbox). 295 // However, we force overriding the first message manager based target in case of 296 // BFCache navigations. 297 if ( 298 existingTarget && 299 !existingTarget.createdFromJsWindowActor && 300 !isBFCache 301 ) { 302 continue; 303 } 304 305 // If we decide to instantiate a new target and there was one before, 306 // first destroy the previous one. 307 // Otherwise its destroy sequence will be executed *after* the new one 308 // is being initialized and may easily revert changes made against platform API. 309 // (typically toggle platform boolean attributes back to default…) 310 if (existingTarget) { 311 existingTarget.destroy({ isTargetSwitching: true }); 312 } 313 314 // When navigating to another process, the Watcher Actor won't have sent any query 315 // to the new process JS Actor as the debugged tab was on another process before navigation. 316 // But `sharedData` will have data about all the current watchers. 317 // Here we have to ensure calling watchTargetsForWatcher in order to populate #connections 318 // for the currently processed watcher actor and start listening for future targets. 319 if ( 320 !ContentProcessWatcherRegistry.has(watcherDataObject.watcherActorID) 321 ) { 322 throw new Error("Watcher data seems out of sync"); 323 } 324 325 createWindowGlobalTargetActor(watcherDataObject, windowGlobal, true); 326 } 327 } 328 } catch (e) { 329 // Ensure logging exception as they are silently ignore otherwise 330 dump( 331 " Exception while observing a new window: " + e + "\n" + e.stack + "\n" 332 ); 333 } 334 } 335 336 /** 337 * Called whenever a WindowGlobal just got destroyed, when closing the tab, or navigating to another one. 338 * 339 * @param {innerWindowId} innerWindowId 340 * The WindowGlobal's unique identifier. 341 */ 342 function onWindowGlobalDestroyed(innerWindowId) { 343 for (const watcherDataObject of ContentProcessWatcherRegistry.getAllWatchersDataObjects( 344 "frame" 345 )) { 346 const existingTarget = findTargetActor({ 347 watcherDataObject, 348 innerWindowId, 349 }); 350 351 if (!existingTarget) { 352 continue; 353 } 354 355 // Do not do anything if both bfcache in parent and server targets are disabled 356 // As history navigations will be handled within the same DocShell and by the 357 // same WindowGlobalTargetActor. The actor will listen to pageshow/pagehide by itself. 358 // We should not destroy any target. 359 if ( 360 !lazy.isBfcacheInParentEnabled && 361 !watcherDataObject.sessionContext.isServerTargetSwitchingEnabled 362 ) { 363 continue; 364 } 365 // If the target actor isn't in watcher data object, it is a top level actor 366 // instantiated via a Descriptor's getTarget method. It isn't registered into Watcher objects. 367 // But we still want to destroy such target actor, and need to manually emit the targetDestroyed to the parent process. 368 // Hopefully bug 1754452 should allow us to get rid of this workaround by making the top level actor 369 // be created and managed by the watcher universe, like all the others. 370 const isTopLevelActorRegisteredOutsideOfWatcherActor = 371 !watcherDataObject.actors.find( 372 actor => actor.innerWindowId == innerWindowId 373 ); 374 const targetActorForm = isTopLevelActorRegisteredOutsideOfWatcherActor 375 ? existingTarget.form() 376 : null; 377 378 existingTarget.destroy(); 379 380 if (isTopLevelActorRegisteredOutsideOfWatcherActor) { 381 watcherDataObject.jsProcessActor.sendAsyncMessage( 382 "DevToolsProcessChild:targetDestroyed", 383 { 384 actors: [ 385 { 386 watcherActorID: watcherDataObject.watcherActorID, 387 targetActorForm, 388 }, 389 ], 390 options: {}, 391 } 392 ); 393 } 394 } 395 } 396 397 /** 398 * Instantiate a WindowGlobal target actor for a given browsing context 399 * and for a given watcher actor. 400 * 401 * @param {object} watcherDataObject 402 * @param {BrowsingContext} windowGlobalChild 403 * @param {boolean} isDocumentCreation 404 */ 405 function createWindowGlobalTargetActor( 406 watcherDataObject, 407 windowGlobalChild, 408 isDocumentCreation = false 409 ) { 410 // When debugging privileged pages running a the shared system compartment, and we aren't in the browser toolbox (which already uses a distinct loader), 411 // we have to use the distinct loader in order to ensure running DevTools in a distinct compartment than the page we are about to debug 412 // Such page could be about:addons, chrome://browser/content/browser.xhtml,... 413 const { browsingContext } = windowGlobalChild; 414 const useDistinctLoader = 415 browsingContext.associatedWindow.document.nodePrincipal.isSystemPrincipal; 416 const { connection, loader } = 417 ContentProcessWatcherRegistry.getOrCreateConnectionForWatcher( 418 watcherDataObject.watcherActorID, 419 useDistinctLoader 420 ); 421 422 const { WindowGlobalTargetActor } = loader.require( 423 "devtools/server/actors/targets/window-global" 424 ); 425 426 // In the case of the browser toolbox, tab's BrowsingContext don't have 427 // any parent BC and shouldn't be considered as top-level. 428 // This is why we check for browserId's. 429 const { sessionContext } = watcherDataObject; 430 let isTopLevelTarget = 431 !browsingContext.parent && 432 browsingContext.browserId == sessionContext.browserId; 433 434 // Web Extension are many of many "top level" browsing context. 435 // i.e. browsing context with no parent. 436 // The background page and popup top browsing context have no parent. 437 // Given that none of these browsing context are guaranteed to be active, 438 // not guarantee to be always active while debugging an add-on, 439 // the WebExtension Descriptor Actor will spawn an "fallback document", 440 // which is guaranteed to be always active and dedicated to eachd debugged addon. 441 // Thus, allowing us to have an identifier top level document to be provided 442 // to an unique top level target actor. 443 if (sessionContext.type == "webextension") { 444 isTopLevelTarget = browsingContext.window.location.href.startsWith( 445 lazy.WEBEXTENSION_FALLBACK_DOC_URL 446 ); 447 } 448 449 // Create the actual target actor. 450 const targetActor = new WindowGlobalTargetActor(connection, { 451 docShell: browsingContext.docShell, 452 // Targets created from the server side, via Watcher actor and DevToolsProcess JSWindow 453 // actor pairs are following WindowGlobal lifecycle. i.e. will be destroyed on any 454 // type of navigation/reload. 455 followWindowGlobalLifeCycle: true, 456 isTopLevelTarget, 457 ignoreSubFrames: true, 458 sessionContext, 459 }); 460 targetActor.createdFromJsWindowActor = true; 461 462 ContentProcessWatcherRegistry.onNewTargetActor( 463 watcherDataObject, 464 targetActor, 465 isDocumentCreation 466 ); 467 } 468 469 /** 470 * Observer service notification handler. 471 * 472 * @param {DOMWindow|Document} subject 473 * A window for *-document-global-created 474 * A document for *-page-{shown|hide} 475 * @param {string} topic 476 */ 477 function observe(subject, topic) { 478 if ( 479 topic == "content-document-global-created" || 480 topic == "chrome-document-global-created" 481 ) { 482 if (subject.isUncommittedInitialDocument) { 483 // If this is the initial document, it might be a short-lived transient one, and 484 // onWindowGlobalCreated will ignore such documents. If we receive a load 485 // event, the document has been committed to, and we know the initial document 486 // will persist. In that case, we need to call onWindowGlobalCreated again. 487 subject.addEventListener("DOMContentLoaded", handleEvent, { 488 capture: true, 489 once: true, 490 }); 491 } 492 onWindowGlobalCreated(subject); 493 } else if (topic == "inner-window-destroyed") { 494 const innerWindowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data; 495 onWindowGlobalDestroyed(innerWindowId); 496 } else if (topic == "content-page-shown" || topic == "chrome-page-shown") { 497 // The observer service notification doesn't receive the "persisted" DOM Event attribute, 498 // but thanksfully is fired just before the dispatching of that DOM event. 499 subject.defaultView.addEventListener("pageshow", handleEvent, { 500 capture: true, 501 once: true, 502 }); 503 } else if (topic == "content-page-hidden" || topic == "chrome-page-hidden") { 504 // Same as previous elseif branch 505 subject.defaultView.addEventListener("pagehide", handleEvent, { 506 capture: true, 507 once: true, 508 }); 509 } else if (topic == "initial-document-element-inserted") { 510 // We may be notified about SVG documents which we don't care about here. 511 if (!subject.location || !subject.defaultView) { 512 return; 513 } 514 // We might have ignored the DOMWindowCreated event because it was the initial about:blank document. 515 // But when loading same-process iframes, we reuse the WindowGlobal of the previously ignored about:bank document 516 // to load the actual URL loaded in the iframe. This means we won't have a new DOMWindowCreated 517 // for the actual document. But there is a DOMDocElementInserted fired just after, that we are processing here 518 // to create a target for same-process iframes. We only have to tell onWindowGlobalCreated to ignore 519 // the call if a target was created on the DOMWindowCreated event (if that was a non-about:blank document). 520 // 521 // All this means that we still do not create any target for the initial documents. 522 // It is complex to instantiate targets for initial documents because: 523 // - it would mean spawning two targets for the same WindowGlobal and sharing the same innerWindowId 524 // - or have WindowGlobalTargets to handle more than one document (it would mean reusing will-navigate/window-ready events 525 // both on client and server) 526 onWindowGlobalCreated(subject.defaultView, { ignoreIfExisting: true }); 527 } 528 } 529 530 /** 531 * DOM Event handler. 532 * 533 * @param {string} type 534 * DOM event name 535 * @param {boolean} persisted 536 * A flag set to true in cache of BFCache navigation 537 * @param {Document} target 538 * The navigating document 539 */ 540 function handleEvent({ type, persisted, target }) { 541 // If persisted=true, this is a BFCache navigation. 542 // 543 // With Fission enabled and bfcacheInParent, BFCache navigations will spawn a new DocShell 544 // in the same process: 545 // * the previous page won't be destroyed, its JSWindowActor will stay alive (`didDestroy` won't be called) 546 // and a 'pagehide' with persisted=true will be emitted on it. 547 // * the new page page won't emit any DOMWindowCreated, but instead a pageshow with persisted=true 548 // will be emitted. 549 if (type === "pageshow" && persisted) { 550 // Notify all bfcache navigations, even the one for which we don't create a new target 551 // as that's being useful for parent process storage resource watchers. 552 for (const watcherDataObject of ContentProcessWatcherRegistry.getAllWatchersDataObjects()) { 553 watcherDataObject.jsProcessActor.sendAsyncMessage( 554 "DevToolsProcessChild:bf-cache-navigation-pageshow", 555 { 556 browsingContextId: target.defaultView.browsingContext.id, 557 } 558 ); 559 } 560 561 // Here we are going to re-instantiate a target that got destroyed before while processing a pagehide event. 562 // We force instantiating a new top level target, within `instantiate()` even if server targets are disabled. 563 // But we only do that if bfcacheInParent is enabled. Otherwise for same-process, same-docshell bfcache navigation, 564 // we don't want to spawn new targets. 565 onWindowGlobalCreated(target.defaultView, { 566 isBFCache: true, 567 }); 568 } 569 570 if (type === "pagehide" && persisted) { 571 // Notify all bfcache navigations, even the one for which we don't create a new target 572 // as that's being useful for parent process storage resource watchers. 573 for (const watcherDataObject of ContentProcessWatcherRegistry.getAllWatchersDataObjects()) { 574 watcherDataObject.jsProcessActor.sendAsyncMessage( 575 "DevToolsProcessChild:bf-cache-navigation-pagehide", 576 { 577 browsingContextId: target.defaultView.browsingContext.id, 578 } 579 ); 580 } 581 582 // We might navigate away for the first top level target, 583 // which isn't using JSWindowActor (it still uses messages manager and is created by the client, via TabDescriptor.getTarget). 584 // We have to unregister it from the TargetActorRegistry, otherwise, 585 // if we navigate back to it, the next DOMWindowCreated won't create a new target for it. 586 onWindowGlobalDestroyed(target.defaultView.windowGlobalChild.innerWindowId); 587 } 588 589 if (type == "DOMContentLoaded") { 590 if (!target.isInitialDocument) { 591 return; 592 } 593 594 // This is similar to initial-document-element-inserted. onWindowGlobalCreated likely 595 // ignored the earlier call for this document because it was the uncommitted initial one. Now 596 // that we got a load event we know that the document is not transient but the destination of a 597 // load. Its state will have changed and onWindowGlobalCreated won't skip it anymore. 598 onWindowGlobalCreated(target.defaultView, { 599 ignoreIfExisting: true, 600 }); 601 } 602 } 603 604 /** 605 * Return an existing Window Global target for given a WatcherActor 606 * and against a given WindowGlobal. 607 * 608 * @param {object} options 609 * @param {string} options.watcherDataObject 610 * @param {number} options.innerWindowId 611 * The WindowGlobal inner window ID. 612 * 613 * @returns {WindowGlobalTargetActor|null} 614 */ 615 function findTargetActor({ 616 watcherDataObject, 617 innerWindowId, 618 browsingContextID, 619 }) { 620 // First let's check if a target was created for this watcher actor in this specific 621 // DevToolsProcessChild instance. 622 // 623 // And start by checking if there is a perfect match first by doing a WindowGlobal / innerWindowId lookup, 624 // before falling back to a BrowsingContext / browsingContextID lookup. 625 let targetActor = watcherDataObject.actors.find( 626 actor => actor.innerWindowId == innerWindowId 627 ); 628 if (!targetActor && browsingContextID) { 629 targetActor = watcherDataObject.actors.find( 630 actor => actor.browsingContextID == browsingContextID 631 ); 632 } 633 if (targetActor) { 634 return targetActor; 635 } 636 637 // Ensure retrieving the one target actor related to this connection. 638 // This allows to distinguish actors created for various toolboxes. 639 // For ex, regular toolbox versus browser console versus browser toolbox 640 const connectionPrefix = watcherDataObject.watcherActorID.replace( 641 /watcher\d+$/, 642 "" 643 ); 644 const targetActors = lazy.TargetActorRegistry.getTargetActors( 645 watcherDataObject.sessionContext, 646 connectionPrefix 647 ); 648 649 return targetActors.find(actor => actor.innerWindowId == innerWindowId); 650 } 651 652 export const WindowGlobalTargetWatcher = { 653 watch, 654 unwatch, 655 createTargetsForWatcher, 656 destroyTargetsForWatcher, 657 };