/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; // protocol.js uses objects as exceptions in order to define // error packets. /* eslint-disable no-throw-literal */ /* * WindowGlobalTargetActor is an abstract class used by target actors that hold * documents, such as frames, chrome windows, etc. * * This class is extended by ParentProcessTargetActor, itself being extented by WebExtensionTargetActor. * * See devtools/docs/backend/actor-hierarchy.md for more details. * * For performance matters, this file should only be loaded in the targeted context's * process. For example, it shouldn't be evaluated in the parent process until we try to * debug a document living in the parent process. */ var { ActorRegistry, } = require("resource://devtools/server/actors/utils/actor-registry.js"); var DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js"); var { assert } = DevToolsUtils; var { SourcesManager, } = require("resource://devtools/server/actors/utils/sources-manager.js"); var makeDebugger = require("resource://devtools/server/actors/utils/make-debugger.js"); const Targets = require("resource://devtools/server/actors/targets/index.js"); const { TargetActorRegistry } = ChromeUtils.importESModule( "resource://devtools/server/actors/targets/target-actor-registry.sys.mjs", { loadInDevToolsLoader: false, } ); const { PrivateBrowsingUtils } = ChromeUtils.importESModule( "resource://gre/modules/PrivateBrowsingUtils.sys.mjs" ); const EXTENSION_CONTENT_SYS_MJS = "resource://gre/modules/ExtensionContent.sys.mjs"; const { Pool } = require("resource://devtools/shared/protocol.js"); const { LazyPool, createExtraActors, } = require("resource://devtools/shared/protocol/lazy-pool.js"); const { windowGlobalTargetSpec, } = require("resource://devtools/shared/specs/targets/window-global.js"); const Resources = require("resource://devtools/server/actors/resources/index.js"); const { BaseTargetActor, } = require("resource://devtools/server/actors/targets/base-target-actor.js"); loader.lazyRequireGetter( this, ["ThreadActor", "unwrapDebuggerObjectGlobal"], "resource://devtools/server/actors/thread.js", true ); loader.lazyRequireGetter( this, "WorkerDescriptorActorList", "resource://devtools/server/actors/worker/worker-descriptor-actor-list.js", true ); loader.lazyRequireGetter( this, "StyleSheetsManager", "resource://devtools/server/actors/utils/stylesheets-manager.js", true ); const lazy = {}; loader.lazyGetter(lazy, "ExtensionContent", () => { return ChromeUtils.importESModule(EXTENSION_CONTENT_SYS_MJS, { // ExtensionContent.sys.mjs is a singleton and must be loaded through the // main loader. Note that the user of lazy.ExtensionContent elsewhere in // this file (at webextensionsContentScriptGlobals) looks up the module // via Cu.isESModuleLoaded, which also uses the main loader as desired. loadInDevToolsLoader: false, }).ExtensionContent; }); loader.lazyRequireGetter( this, "TouchSimulator", "resource://devtools/server/actors/emulation/touch-simulator.js", true ); function getWindowID(window) { return window.windowGlobalChild.innerWindowId; } function getDocShellChromeEventHandler(docShell) { let handler = docShell.chromeEventHandler; if (!handler) { try { // Toplevel xul window's docshell doesn't have chromeEventHandler // attribute. The chrome event handler is just the global window object. handler = docShell.domWindow; } catch (e) { // ignore } } return handler; } /** * Helper to retrieve all children docshells of a given docshell. * * Given that docshell interfaces can only be used within the same process, * this only returns docshells for children documents that runs in the same process * as the given docshell. */ function getChildDocShells(parentDocShell) { return parentDocShell.browsingContext .getAllBrowsingContextsInSubtree() .filter(browsingContext => { // Filter out browsingContext which don't expose any docshell (e.g. remote frame) return browsingContext.docShell; }) .map(browsingContext => { // Map BrowsingContext to DocShell return browsingContext.docShell; }); } exports.getChildDocShells = getChildDocShells; /** * Browser-specific actors. */ function getInnerId(window) { return window.windowGlobalChild.innerWindowId; } class WindowGlobalTargetActor extends BaseTargetActor { /** * WindowGlobalTargetActor is the target actor to debug (HTML) documents. * * WindowGlobal's are the Gecko representation for a given document's window object. * It relates to a given nsGlobalWindowInner instance. * * The main goal of this class is to expose the target-scoped actors being registered * via `ActorRegistry.registerModule` and manage their lifetimes. In addition, this * class also tracks the lifetime of the targeted window global. * * ### Main requests: * * `detach`: * Stop document watching and cleanup everything that the target and its children actors created. * It ultimately lead to destroy the target actor. * `switchToFrame`: * Change the targeted document of the whole actor, and its child target-scoped actors * to an iframe or back to its original document. * * Most properties (like `chromeEventHandler` or `docShells`) are meant to be * used by the various child target actors. * * ### RDP events: * * - `tabNavigated`: * Sent when the window global is about to navigate or has just navigated * to a different document. * This event contains the following attributes: * * url (string) * The new URI being loaded. * * state (string) * `start` if we just start requesting the new URL * `stop` if the new URL is done loading * * isFrameSwitching (boolean) * Indicates the event is dispatched when switching the actor context to a * different frame. When we switch to an iframe, there is no document * load. The targeted document is most likely going to be already done * loading. * * title (string) * The document title being loaded. (sent only on state=stop) * * - `frameUpdate`: * Sent when there was a change in the child frames contained in the document * or when the actor's context was switched to another frame. * This event can have four different forms depending on the type of change: * * One or many frames are updated: * { frames: [{ id, url, title, parentID }, ...] } * * One frame got destroyed: * { frames: [{ id, destroy: true }]} * * All frames got destroyed: * { destroyAll: true } * * We switched the context of the actor to a specific frame: * { selected: #id } * * ### Internal, non-rdp events: * * Various events are also dispatched on the actor itself without being sent to * the client. They all relate to the documents tracked by this target actor * (its main targeted document, but also any of its iframes): * - will-navigate * This event fires once navigation starts. All pending user prompts are * dealt with, but it is fired before the first request starts. * - navigate * This event is fired once the document's readyState is "complete". * - window-ready * This event is fired in various distinct scenarios: * * When a new Window object is crafted, equivalent of `DOMWindowCreated`. * It is dispatched before any page script is executed. * * We will have already received a window-ready event for this window * when it was created, but we received a window-destroyed event when * it was frozen into the bfcache, and now the user navigated back to * this page, so it's now live again and we should resume handling it. * * For each existing document, when an `attach` request is received. * At this point scripts in the page will be already loaded. * * When `swapFrameLoaders` is used, such as with moving window globals * between windows or toggling Responsive Design Mode. * - window-destroyed * This event is fired in two cases: * * When the window object is destroyed, i.e. when the related document * is garbage collected. This can happen when the window global is * closed or the iframe is removed from the DOM. * It is equivalent of `inner-window-destroyed` event. * * When the page goes into the bfcache and gets frozen. * The equivalent of `pagehide`. * - changed-toplevel-document * This event fires when we switch the actor's targeted document * to one of its iframes, or back to its original top document. * It is dispatched between window-destroyed and window-ready. * * Note that *all* these events are dispatched in the following order * when we switch the context of the actor to a given iframe: * - will-navigate * - window-destroyed * - changed-toplevel-document * - window-ready * - navigate * * This class is subclassed by ParentProcessTargetActor and others. * Subclasses are expected to implement a getter for the docShell property. * * @param conn DevToolsServerConnection * The conection to the client. * @param options Object * Object with following attributes: * - docShell nsIDocShell * The |docShell| for the debugged frame. * - followWindowGlobalLifeCycle Boolean * If true, the target actor will only inspect the current WindowGlobal (and its children windows). * But won't inspect next document loaded in the same BrowsingContext. * The actor will behave more like a WindowGlobalTarget rather than a BrowsingContextTarget. * This is always true for Tab debugging, but not yet for parent process/web extension. * - isTopLevelTarget Boolean * Should be set to true for all top-level targets. A top level target * is the topmost target of a DevTools "session". For instance for a local * tab toolbox, the WindowGlobalTargetActor for the content page is the top level target. * For the Multiprocess Browser Toolbox, the parent process target is the top level * target. * At the moment this only impacts the WindowGlobalTarget `reconfigure` * implementation. But for server-side target switching this flag will be exposed * to the client and should be available for all target actor classes. It will be * used to detect target switching. (Bug 1644397) * - ignoreSubFrames Boolean * If true, the actor will only focus on the passed docShell and not on the whole * docShell tree. This should be enabled when we have targets for all documents. * - sessionContext Object * The Session Context to help know what is debugged. * See devtools/server/actors/watcher/session-context.js */ constructor( conn, { docShell, followWindowGlobalLifeCycle, isTopLevelTarget, ignoreSubFrames, sessionContext, customSpec = windowGlobalTargetSpec, } ) { super(conn, Targets.TYPES.FRAME, customSpec); this.followWindowGlobalLifeCycle = followWindowGlobalLifeCycle; this.isTopLevelTarget = !!isTopLevelTarget; this.ignoreSubFrames = ignoreSubFrames; this.sessionContext = sessionContext; // A map of actor names to actor instances provided by extensions. this._extraActors = {}; this._sourcesManager = null; this._shouldAddNewGlobalAsDebuggee = this._shouldAddNewGlobalAsDebuggee.bind(this); this.makeDebugger = makeDebugger.bind(null, { findDebuggees: () => { const result = []; const inspectUAWidgets = Services.prefs.getBoolPref( "devtools.inspector.showAllAnonymousContent", false ); for (const win of this.windows) { result.push(win); // Only expose User Agent internal (like