Source code
Revision control
Copy as Markdown
Other Tools
/* 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
"use strict";
/*
* Target actor for the entire parent process.
*
* This actor extends WindowGlobalTargetActor.
*
* See devtools/docs/contributor/backend/actor-hierarchy.md for more details about all the targets.
*/
const {
DevToolsServer,
} = require("resource://devtools/server/devtools-server.js");
const {
getChildDocShells,
WindowGlobalTargetActor,
} = require("resource://devtools/server/actors/targets/window-global.js");
const makeDebugger = require("resource://devtools/server/actors/utils/make-debugger.js");
const {
parentProcessTargetSpec,
} = require("resource://devtools/shared/specs/targets/parent-process.js");
class ParentProcessTargetActor extends WindowGlobalTargetActor {
/**
* Creates a target actor for debugging all the chrome content in the parent process.
* Most of the implementation is inherited from WindowGlobalTargetActor.
* ParentProcessTargetActor is a child of RootActor, it can be instantiated via
* RootActor.getProcess request. ParentProcessTargetActor exposes all target-scoped actors
* via its form() request, like WindowGlobalTargetActor.
*
* @param {DevToolsServerConnection} conn
* The connection to the client.
* @param {Boolean} options.isTopLevelTarget
* flag to indicate if this is the top
* level target of the DevTools session
* @param {Object} options.sessionContext
* The Session Context to help know what is debugged.
* See devtools/server/actors/watcher/session-context.js
*/
constructor(conn, { isTopLevelTarget, sessionContext }) {
super(conn, {
isTopLevelTarget,
sessionContext,
customSpec: parentProcessTargetSpec,
});
// This creates a Debugger instance for chrome debugging all globals.
this.makeDebugger = makeDebugger.bind(null, {
findDebuggees: dbg =>
dbg.findAllGlobals().map(g => g.unsafeDereference()),
shouldAddNewGlobalAsDebuggee: () => true,
});
// Ensure catching the creation of any new content docshell
this.watchNewDocShells = true;
this.isRootActor = true;
// Listen for any new/destroyed chrome docshell
Services.obs.addObserver(this, "chrome-webnavigation-create");
Services.obs.addObserver(this, "chrome-webnavigation-destroy");
this.setDocShell(this._getInitialDocShell());
}
// Overload setDocShell in order to observe all the docshells.
// WindowGlobalTargetActor only observes the top level one.
setDocShell(initialDocShell) {
super.setDocShell(initialDocShell);
// Iterate over all top-level windows.
for (const { docShell } of Services.ww.getWindowEnumerator()) {
if (docShell == this.docShell) {
continue;
}
this._progressListener.watch(docShell);
}
}
_getInitialDocShell() {
// Defines the default docshell selected for the target actor
let window = Services.wm.getMostRecentWindow(
DevToolsServer.chromeWindowType
);
// Default to any available top level window if there is no expected window
// eg when running ./mach run --chrome chrome://browser/content/aboutTabCrashed.xhtml --jsdebugger
if (!window) {
// If DevTools is started early enough, this window will be the
// early navigator:blank window created in BrowserGlue.sys.mjs
window = Services.wm.getMostRecentWindow(null);
}
// On Fenix, we may not have any document to inspect when there is no tab
// opened, so return a fake document, just to ensure the ParentProcessWindowGlobalTarget
// actor has a functional document to operate with.
if (!window) {
const browser = Services.appShell.createWindowlessBrowser(false);
// Keep a strong reference to the document to keep it alive
this.headlessBrowser = browser;
// Create a document in order to avoid being on the initial about:blank document
// and have the document be ignored because its `isInitialDocument` attribute being true
const systemPrincipal =
Services.scriptSecurityManager.getSystemPrincipal();
browser.docShell.createAboutBlankDocumentViewer(
systemPrincipal,
systemPrincipal
);
window = browser.docShell.domWindow;
// Set some content to be shown in the inspector
window.document.body.textContent =
"Fake DevTools document, as there is no tab opened yet";
}
return window.docShell;
}
/**
* Getter for the list of all docshells in this targetActor
* @return {Array}
*/
get docShells() {
// Iterate over all top-level windows and all their docshells.
let docShells = [];
for (const { docShell } of Services.ww.getWindowEnumerator()) {
docShells = docShells.concat(getChildDocShells(docShell));
}
return docShells;
}
observe(subject, topic, data) {
super.observe(subject, topic, data);
if (this.isDestroyed()) {
return;
}
subject.QueryInterface(Ci.nsIDocShell);
if (topic == "chrome-webnavigation-create") {
this._onDocShellCreated(subject);
} else if (topic == "chrome-webnavigation-destroy") {
this._onDocShellDestroy(subject);
}
}
_detach() {
if (this.isDestroyed()) {
return false;
}
Services.obs.removeObserver(this, "chrome-webnavigation-create");
Services.obs.removeObserver(this, "chrome-webnavigation-destroy");
// Iterate over all top-level windows.
for (const { docShell } of Services.ww.getWindowEnumerator()) {
if (docShell == this.docShell) {
continue;
}
this._progressListener.unwatch(docShell);
}
this.headlessBrowser = null;
return super._detach();
}
}
exports.ParentProcessTargetActor = ParentProcessTargetActor;