copy from devtools/server/actors/webbrowser.js
copy to devtools/server/actors/tab.js
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/tab.js
@@ -3,38 +3,36 @@
/* 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";
/* global XPCNativeWrapper */
+// For performance matters, this file should only be loaded in the targeted
+// document 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 { Ci, Cu, Cr } = require("chrome");
var Services = require("Services");
var { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
var promise = require("promise");
var {
ActorPool, createExtraActors, appendExtraActors, GeneratedLocation
} = require("devtools/server/actors/common");
var { DebuggerServer } = require("devtools/server/main");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { assert } = DevToolsUtils;
var { TabSources } = require("./utils/TabSources");
var makeDebugger = require("./utils/make-debugger");
-loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true);
loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
-loader.lazyRequireGetter(this, "BrowserAddonActor", "devtools/server/actors/addon", true);
-loader.lazyRequireGetter(this, "WebExtensionActor", "devtools/server/actors/webextension", true);
loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker", true);
-loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActorList", "devtools/server/actors/worker", true);
-loader.lazyRequireGetter(this, "ProcessActorList", "devtools/server/actors/process", true);
-loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
loader.lazyImporter(this, "ExtensionContent", "resource://gre/modules/ExtensionContent.jsm");
// Assumptions on events module:
// events needs to be dispatched synchronously,
// by calling the listeners in the order or registration.
loader.lazyRequireGetter(this, "events", "sdk/event/core");
loader.lazyRequireGetter(this, "StyleSheetActor", "devtools/server/actors/stylesheets", true);
@@ -83,668 +81,16 @@ exports.getChildDocShells = getChildDocS
*/
function getInnerId(window) {
return window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
}
/**
- * Yield all windows of type |windowType|, from the oldest window to the
- * youngest, using nsIWindowMediator::getEnumerator. We're usually
- * interested in "navigator:browser" windows.
- */
-function* allAppShellDOMWindows(windowType) {
- let e = Services.wm.getEnumerator(windowType);
- while (e.hasMoreElements()) {
- yield e.getNext();
- }
-}
-
-exports.allAppShellDOMWindows = allAppShellDOMWindows;
-
-/**
- * Retrieve the window type of the top-level window |window|.
- */
-function appShellDOMWindowType(window) {
- /* This is what nsIWindowMediator's enumerator checks. */
- return window.document.documentElement.getAttribute("windowtype");
-}
-
-/**
- * Send Debugger:Shutdown events to all "navigator:browser" windows.
- */
-function sendShutdownEvent() {
- for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) {
- let evt = win.document.createEvent("Event");
- evt.initEvent("Debugger:Shutdown", true, false);
- win.document.documentElement.dispatchEvent(evt);
- }
-}
-
-exports.sendShutdownEvent = sendShutdownEvent;
-
-/**
- * Construct a root actor appropriate for use in a server running in a
- * browser. The returned root actor:
- * - respects the factories registered with DebuggerServer.addGlobalActor,
- * - uses a BrowserTabList to supply tab actors,
- * - sends all navigator:browser window documents a Debugger:Shutdown event
- * when it exits.
- *
- * * @param connection DebuggerServerConnection
- * The conection to the client.
- */
-function createRootActor(connection) {
- return new RootActor(connection, {
- tabList: new BrowserTabList(connection),
- addonList: new BrowserAddonList(connection),
- workerList: new WorkerActorList(connection, {}),
- serviceWorkerRegistrationList:
- new ServiceWorkerRegistrationActorList(connection),
- processList: new ProcessActorList(),
- globalActorFactories: DebuggerServer.globalActorFactories,
- onShutdown: sendShutdownEvent
- });
-}
-
-/**
- * A live list of BrowserTabActors representing the current browser tabs,
- * to be provided to the root actor to answer 'listTabs' requests.
- *
- * This object also takes care of listening for TabClose events and
- * onCloseWindow notifications, and exiting the BrowserTabActors concerned.
- *
- * (See the documentation for RootActor for the definition of the "live
- * list" interface.)
- *
- * @param connection DebuggerServerConnection
- * The connection in which this list's tab actors may participate.
- *
- * Some notes:
- *
- * This constructor is specific to the desktop browser environment; it
- * maintains the tab list by tracking XUL windows and their XUL documents'
- * "tabbrowser", "tab", and "browser" elements. What's entailed in maintaining
- * an accurate list of open tabs in this context?
- *
- * - Opening and closing XUL windows:
- *
- * An nsIWindowMediatorListener is notified when new XUL windows (i.e., desktop
- * windows) are opened and closed. It is not notified of individual content
- * browser tabs coming and going within such a XUL window. That seems
- * reasonable enough; it's concerned with XUL windows, not tab elements in the
- * window's XUL document.
- *
- * However, even if we attach TabOpen and TabClose event listeners to each XUL
- * window as soon as it is created:
- *
- * - we do not receive a TabOpen event for the initial empty tab of a new XUL
- * window; and
- *
- * - we do not receive TabClose events for the tabs of a XUL window that has
- * been closed.
- *
- * This means that TabOpen and TabClose events alone are not sufficient to
- * maintain an accurate list of live tabs and mark tab actors as closed
- * promptly. Our nsIWindowMediatorListener onCloseWindow handler must find and
- * exit all actors for tabs that were in the closing window.
- *
- * Since this is a bit hairy, we don't make each individual attached tab actor
- * responsible for noticing when it has been closed; we watch for that, and
- * promise to call each actor's 'exit' method when it's closed, regardless of
- * how we learn the news.
- *
- * - nsIWindowMediator locks
- *
- * nsIWindowMediator holds a lock protecting its list of top-level windows
- * while it calls nsIWindowMediatorListener methods. nsIWindowMediator's
- * GetEnumerator method also tries to acquire that lock. Thus, enumerating
- * windows from within a listener method deadlocks (bug 873589). Rah. One
- * can sometimes work around this by leaving the enumeration for a later
- * tick.
- *
- * - Dragging tabs between windows:
- *
- * When a tab is dragged from one desktop window to another, we receive a
- * TabOpen event for the new tab, and a TabClose event for the old tab; tab XUL
- * elements do not really move from one document to the other (although their
- * linked browser's content window objects do).
- *
- * However, while we could thus assume that each tab stays with the XUL window
- * it belonged to when it was created, I'm not sure this is behavior one should
- * rely upon. When a XUL window is closed, we take the less efficient, more
- * conservative approach of simply searching the entire table for actors that
- * belong to the closing XUL window, rather than trying to somehow track which
- * XUL window each tab belongs to.
- *
- * - Title changes:
- *
- * For tabs living in the child process, we listen for DOMTitleChange message
- * via the top-level window's message manager. Doing this also allows listening
- * for title changes on Fennec.
- * But as these messages aren't sent for tabs loaded in the parent process,
- * we also listen for TabAttrModified event, which is fired only on Firefox
- * desktop.
- */
-function BrowserTabList(connection) {
- this._connection = connection;
-
- /*
- * The XUL document of a tabbed browser window has "tab" elements, whose
- * 'linkedBrowser' JavaScript properties are "browser" elements; those
- * browsers' 'contentWindow' properties are wrappers on the tabs' content
- * window objects.
- *
- * This map's keys are "browser" XUL elements; it maps each browser element
- * to the tab actor we've created for its content window, if we've created
- * one. This map serves several roles:
- *
- * - During iteration, we use it to find actors we've created previously.
- *
- * - On a TabClose event, we use it to find the tab's actor and exit it.
- *
- * - When the onCloseWindow handler is called, we iterate over it to find all
- * tabs belonging to the closing XUL window, and exit them.
- *
- * - When it's empty, and the onListChanged hook is null, we know we can
- * stop listening for events and notifications.
- *
- * We listen for TabClose events and onCloseWindow notifications in order to
- * send onListChanged notifications, but also to tell actors when their
- * referent has gone away and remove entries for dead browsers from this map.
- * If that code is working properly, neither this map nor the actors in it
- * should ever hold dead tabs alive.
- */
- this._actorByBrowser = new Map();
-
- /* The current onListChanged handler, or null. */
- this._onListChanged = null;
-
- /*
- * True if we've been iterated over since we last called our onListChanged
- * hook.
- */
- this._mustNotify = false;
-
- /* True if we're testing, and should throw if consistency checks fail. */
- this._testing = false;
-}
-
-BrowserTabList.prototype.constructor = BrowserTabList;
-
-/**
- * Get the selected browser for the given navigator:browser window.
- * @private
- * @param window nsIChromeWindow
- * The navigator:browser window for which you want the selected browser.
- * @return nsIDOMElement|null
- * The currently selected xul:browser element, if any. Note that the
- * browser window might not be loaded yet - the function will return
- * |null| in such cases.
- */
-BrowserTabList.prototype._getSelectedBrowser = function (window) {
- return window.gBrowser ? window.gBrowser.selectedBrowser : null;
-};
-
-/**
- * Produces an iterable (in this case a generator) to enumerate all available
- * browser tabs.
- */
-BrowserTabList.prototype._getBrowsers = function* () {
- // Iterate over all navigator:browser XUL windows.
- for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) {
- // For each tab in this XUL window, ensure that we have an actor for
- // it, reusing existing actors where possible. We actually iterate
- // over 'browser' XUL elements, and BrowserTabActor uses
- // browser.contentWindow as the debuggee global.
- for (let browser of this._getChildren(win)) {
- yield browser;
- }
- }
-};
-
-BrowserTabList.prototype._getChildren = function (window) {
- if (!window.gBrowser) {
- return [];
- }
- let { gBrowser } = window;
- if (!gBrowser.browsers) {
- return [];
- }
- return gBrowser.browsers.filter(browser => {
- // Filter tabs that are closing. listTabs calls made right after TabClose
- // events still list tabs in process of being closed.
- let tab = gBrowser.getTabForBrowser(browser);
- return !tab.closing;
- });
-};
-
-BrowserTabList.prototype.getList = function () {
- let topXULWindow = Services.wm.getMostRecentWindow(
- DebuggerServer.chromeWindowType);
- let selectedBrowser = null;
- if (topXULWindow) {
- selectedBrowser = this._getSelectedBrowser(topXULWindow);
- }
-
- // As a sanity check, make sure all the actors presently in our map get
- // picked up when we iterate over all windows' tabs.
- let initialMapSize = this._actorByBrowser.size;
- this._foundCount = 0;
-
- // To avoid mysterious behavior if tabs are closed or opened mid-iteration,
- // we update the map first, and then make a second pass over it to yield
- // the actors. Thus, the sequence yielded is always a snapshot of the
- // actors that were live when we began the iteration.
-
- let actorPromises = [];
-
- for (let browser of this._getBrowsers()) {
- let selected = browser === selectedBrowser;
- actorPromises.push(
- this._getActorForBrowser(browser)
- .then(actor => {
- // Set the 'selected' properties on all actors correctly.
- actor.selected = selected;
- return actor;
- })
- );
- }
-
- if (this._testing && initialMapSize !== this._foundCount) {
- throw new Error("_actorByBrowser map contained actors for dead tabs");
- }
-
- this._mustNotify = true;
- this._checkListening();
-
- return promise.all(actorPromises);
-};
-
-BrowserTabList.prototype._getActorForBrowser = function (browser) {
- // Do we have an existing actor for this browser? If not, create one.
- let actor = this._actorByBrowser.get(browser);
- if (actor) {
- this._foundCount++;
- return actor.update();
- }
-
- actor = new BrowserTabActor(this._connection, browser);
- this._actorByBrowser.set(browser, actor);
- this._checkListening();
- return actor.connect();
-};
-
-BrowserTabList.prototype.getTab = function ({ outerWindowID, tabId }) {
- if (typeof outerWindowID == "number") {
- // First look for in-process frames with this ID
- let window = Services.wm.getOuterWindowWithId(outerWindowID);
- // Safety check to prevent debugging top level window via getTab
- if (window instanceof Ci.nsIDOMChromeWindow) {
- return promise.reject({
- error: "forbidden",
- message: "Window with outerWindowID '" + outerWindowID + "' is chrome"
- });
- }
- if (window) {
- let iframe = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils)
- .containerElement;
- if (iframe) {
- return this._getActorForBrowser(iframe);
- }
- }
- // Then also look on registered <xul:browsers> when using outerWindowID for
- // OOP tabs
- for (let browser of this._getBrowsers()) {
- if (browser.outerWindowID == outerWindowID) {
- return this._getActorForBrowser(browser);
- }
- }
- return promise.reject({
- error: "noTab",
- message: "Unable to find tab with outerWindowID '" + outerWindowID + "'"
- });
- } else if (typeof tabId == "number") {
- // Tabs OOP
- for (let browser of this._getBrowsers()) {
- if (browser.frameLoader.tabParent &&
- browser.frameLoader.tabParent.tabId === tabId) {
- return this._getActorForBrowser(browser);
- }
- }
- return promise.reject({
- error: "noTab",
- message: "Unable to find tab with tabId '" + tabId + "'"
- });
- }
-
- let topXULWindow = Services.wm.getMostRecentWindow(
- DebuggerServer.chromeWindowType);
- if (topXULWindow) {
- let selectedBrowser = this._getSelectedBrowser(topXULWindow);
- return this._getActorForBrowser(selectedBrowser);
- }
- return promise.reject({
- error: "noTab",
- message: "Unable to find any selected browser"
- });
-};
-
-Object.defineProperty(BrowserTabList.prototype, "onListChanged", {
- enumerable: true,
- configurable: true,
- get() {
- return this._onListChanged;
- },
- set(v) {
- if (v !== null && typeof v !== "function") {
- throw new Error(
- "onListChanged property may only be set to 'null' or a function");
- }
- this._onListChanged = v;
- this._checkListening();
- }
-});
-
-/**
- * The set of tabs has changed somehow. Call our onListChanged handler, if
- * one is set, and if we haven't already called it since the last iteration.
- */
-BrowserTabList.prototype._notifyListChanged = function () {
- if (!this._onListChanged) {
- return;
- }
- if (this._mustNotify) {
- this._onListChanged();
- this._mustNotify = false;
- }
-};
-
-/**
- * Exit |actor|, belonging to |browser|, and notify the onListChanged
- * handle if needed.
- */
-BrowserTabList.prototype._handleActorClose = function (actor, browser) {
- if (this._testing) {
- if (this._actorByBrowser.get(browser) !== actor) {
- throw new Error("BrowserTabActor not stored in map under given browser");
- }
- if (actor.browser !== browser) {
- throw new Error("actor's browser and map key don't match");
- }
- }
-
- this._actorByBrowser.delete(browser);
- actor.exit();
-
- this._notifyListChanged();
- this._checkListening();
-};
-
-/**
- * Make sure we are listening or not listening for activity elsewhere in
- * the browser, as appropriate. Other than setting up newly created XUL
- * windows, all listener / observer management should happen here.
- */
-BrowserTabList.prototype._checkListening = function () {
- /*
- * If we have an onListChanged handler that we haven't sent an announcement
- * to since the last iteration, we need to watch for tab creation as well as
- * change of the currently selected tab and tab title changes of tabs in
- * parent process via TabAttrModified (tabs oop uses DOMTitleChanges).
- *
- * Oddly, we don't need to watch for 'close' events here. If our actor list
- * is empty, then either it was empty the last time we iterated, and no
- * close events are possible, or it was not empty the last time we
- * iterated, but all the actors have since been closed, and we must have
- * sent a notification already when they closed.
- */
- this._listenForEventsIf(this._onListChanged && this._mustNotify,
- "_listeningForTabOpen",
- ["TabOpen", "TabSelect", "TabAttrModified"]);
-
- /* If we have live actors, we need to be ready to mark them dead. */
- this._listenForEventsIf(this._actorByBrowser.size > 0,
- "_listeningForTabClose",
- ["TabClose", "TabRemotenessChange"]);
-
- /*
- * We must listen to the window mediator in either case, since that's the
- * only way to find out about tabs that come and go when top-level windows
- * are opened and closed.
- */
- this._listenToMediatorIf((this._onListChanged && this._mustNotify) ||
- (this._actorByBrowser.size > 0));
-
- /*
- * We also listen for title changed from the child process.
- * This allows listening for title changes from Fennec and OOP tabs in Fx.
- */
- this._listenForMessagesIf(this._onListChanged && this._mustNotify,
- "_listeningForTitleChange",
- ["DOMTitleChanged"]);
-};
-
-/*
- * Add or remove event listeners for all XUL windows.
- *
- * @param shouldListen boolean
- * True if we should add event handlers; false if we should remove them.
- * @param guard string
- * The name of a guard property of 'this', indicating whether we're
- * already listening for those events.
- * @param eventNames array of strings
- * An array of event names.
- */
-BrowserTabList.prototype._listenForEventsIf =
- function (shouldListen, guard, eventNames) {
- if (!shouldListen !== !this[guard]) {
- let op = shouldListen ? "addEventListener" : "removeEventListener";
- for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) {
- for (let name of eventNames) {
- win[op](name, this, false);
- }
- }
- this[guard] = shouldListen;
- }
- };
-
-/*
- * Add or remove message listeners for all XUL windows.
- *
- * @param aShouldListen boolean
- * True if we should add message listeners; false if we should remove them.
- * @param aGuard string
- * The name of a guard property of 'this', indicating whether we're
- * already listening for those messages.
- * @param aMessageNames array of strings
- * An array of message names.
- */
-BrowserTabList.prototype._listenForMessagesIf =
- function (shouldListen, guard, messageNames) {
- if (!shouldListen !== !this[guard]) {
- let op = shouldListen ? "addMessageListener" : "removeMessageListener";
- for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) {
- for (let name of messageNames) {
- win.messageManager[op](name, this);
- }
- }
- this[guard] = shouldListen;
- }
- };
-
-/**
- * Implement nsIMessageListener.
- */
-BrowserTabList.prototype.receiveMessage = DevToolsUtils.makeInfallible(
- function (message) {
- let browser = message.target;
- switch (message.name) {
- case "DOMTitleChanged": {
- let actor = this._actorByBrowser.get(browser);
- if (actor) {
- this._notifyListChanged();
- this._checkListening();
- }
- break;
- }
- }
- });
-
-/**
- * Implement nsIDOMEventListener.
- */
-BrowserTabList.prototype.handleEvent =
-DevToolsUtils.makeInfallible(function (event) {
- let browser = event.target.linkedBrowser;
- switch (event.type) {
- case "TabOpen":
- case "TabSelect": {
- /* Don't create a new actor; iterate will take care of that. Just notify. */
- this._notifyListChanged();
- this._checkListening();
- break;
- }
- case "TabClose": {
- let actor = this._actorByBrowser.get(browser);
- if (actor) {
- this._handleActorClose(actor, browser);
- }
- break;
- }
- case "TabRemotenessChange": {
- // We have to remove the cached actor as we have to create a new instance.
- let actor = this._actorByBrowser.get(browser);
- if (actor) {
- this._actorByBrowser.delete(browser);
- // Don't create a new actor; iterate will take care of that. Just notify.
- this._notifyListChanged();
- this._checkListening();
- }
- break;
- }
- case "TabAttrModified": {
- // Remote <browser> title changes are handled via DOMTitleChange message
- // TabAttrModified is only here for browsers in parent process which
- // don't send this message.
- if (browser.isRemoteBrowser) {
- break;
- }
- let actor = this._actorByBrowser.get(browser);
- if (actor) {
- // TabAttrModified is fired in various cases, here only care about title
- // changes
- if (event.detail.changed.includes("label")) {
- this._notifyListChanged();
- this._checkListening();
- }
- }
- break;
- }
- }
-}, "BrowserTabList.prototype.handleEvent");
-
-/*
- * If |shouldListen| is true, ensure we've registered a listener with the
- * window mediator. Otherwise, ensure we haven't registered a listener.
- */
-BrowserTabList.prototype._listenToMediatorIf = function (shouldListen) {
- if (!shouldListen !== !this._listeningToMediator) {
- let op = shouldListen ? "addListener" : "removeListener";
- Services.wm[op](this);
- this._listeningToMediator = shouldListen;
- }
-};
-
-/**
- * nsIWindowMediatorListener implementation.
- *
- * See _onTabClosed for explanation of why we needn't actually tweak any
- * actors or tables here.
- *
- * An nsIWindowMediatorListener's methods get passed all sorts of windows; we
- * only care about the tab containers. Those have 'getBrowser' methods.
- */
-BrowserTabList.prototype.onWindowTitleChange = () => { };
-
-BrowserTabList.prototype.onOpenWindow =
-DevToolsUtils.makeInfallible(function (window) {
- let handleLoad = DevToolsUtils.makeInfallible(() => {
- /* We don't want any further load events from this window. */
- window.removeEventListener("load", handleLoad, false);
-
- if (appShellDOMWindowType(window) !== DebuggerServer.chromeWindowType) {
- return;
- }
-
- // Listen for future tab activity.
- if (this._listeningForTabOpen) {
- window.addEventListener("TabOpen", this, false);
- window.addEventListener("TabSelect", this, false);
- window.addEventListener("TabAttrModified", this, false);
- }
- if (this._listeningForTabClose) {
- window.addEventListener("TabClose", this, false);
- window.addEventListener("TabRemotenessChange", this, false);
- }
- if (this._listeningForTitleChange) {
- window.messageManager.addMessageListener("DOMTitleChanged", this);
- }
-
- // As explained above, we will not receive a TabOpen event for this
- // document's initial tab, so we must notify our client of the new tab
- // this will have.
- this._notifyListChanged();
- });
-
- /*
- * You can hardly do anything at all with a XUL window at this point; it
- * doesn't even have its document yet. Wait until its document has
- * loaded, and then see what we've got. This also avoids
- * nsIWindowMediator enumeration from within listeners (bug 873589).
- */
- window = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow);
-
- window.addEventListener("load", handleLoad, false);
-}, "BrowserTabList.prototype.onOpenWindow");
-
-BrowserTabList.prototype.onCloseWindow =
-DevToolsUtils.makeInfallible(function (window) {
- window = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow);
-
- if (appShellDOMWindowType(window) !== DebuggerServer.chromeWindowType) {
- return;
- }
-
- /*
- * nsIWindowMediator deadlocks if you call its GetEnumerator method from
- * a nsIWindowMediatorListener's onCloseWindow hook (bug 873589), so
- * handle the close in a different tick.
- */
- Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => {
- /*
- * Scan the entire map for actors representing tabs that were in this
- * top-level window, and exit them.
- */
- for (let [browser, actor] of this._actorByBrowser) {
- /* The browser document of a closed window has no default view. */
- if (!browser.ownerDocument.defaultView) {
- this._handleActorClose(actor, browser);
- }
- }
- }, "BrowserTabList.prototype.onCloseWindow's delayed body"), 0);
-}, "BrowserTabList.prototype.onCloseWindow");
-
-exports.BrowserTabList = BrowserTabList;
-
-/**
* Creates a TabActor whose main goal is to manage lifetime and
* expose the tab actors being registered via DebuggerServer.registerModule.
* But also track the lifetime of the document being tracked.
*
* ### Main requests:
*
* `attach`/`detach` requests:
* - start/stop document watching:
@@ -2115,213 +1461,16 @@ TabActor.prototype.requestTypes = {
"listFrames": TabActor.prototype.onListFrames,
"listWorkers": TabActor.prototype.onListWorkers,
"resolveLocation": TabActor.prototype.onResolveLocation
};
exports.TabActor = TabActor;
/**
- * Creates a tab actor for handling requests to a single browser frame.
- * Both <xul:browser> and <iframe mozbrowser> are supported.
- * This actor is a shim that connects to a ContentActor in a remote browser process.
- * All RDP packets get forwarded using the message manager.
- *
- * @param connection The main RDP connection.
- * @param browser <xul:browser> or <iframe mozbrowser> element to connect to.
- */
-function BrowserTabActor(connection, browser) {
- this._conn = connection;
- this._browser = browser;
- this._form = null;
-}
-
-BrowserTabActor.prototype = {
- connect() {
- let onDestroy = () => {
- this._form = null;
- };
- let connect = DebuggerServer.connectToChild(this._conn, this._browser, onDestroy);
- return connect.then(form => {
- this._form = form;
- return this;
- });
- },
-
- get _tabbrowser() {
- if (typeof this._browser.getTabBrowser == "function") {
- return this._browser.getTabBrowser();
- }
- return null;
- },
-
- get _mm() {
- // Get messageManager from XUL browser (which might be a specialized tunnel for RDM)
- // or else fallback to asking the frameLoader itself.
- return this._browser.messageManager ||
- this._browser.frameLoader.messageManager;
- },
-
- update() {
- // If the child happens to be crashed/close/detach, it won't have _form set,
- // so only request form update if some code is still listening on the other
- // side.
- if (this._form) {
- let deferred = promise.defer();
- let onFormUpdate = msg => {
- // There may be more than just one childtab.js up and running
- if (this._form.actor != msg.json.actor) {
- return;
- }
- this._mm.removeMessageListener("debug:form", onFormUpdate);
- this._form = msg.json;
- deferred.resolve(this);
- };
- this._mm.addMessageListener("debug:form", onFormUpdate);
- this._mm.sendAsyncMessage("debug:form");
- return deferred.promise;
- }
-
- return this.connect();
- },
-
- /**
- * If we don't have a title from the content side because it's a zombie tab, try to find
- * it on the chrome side.
- */
- get title() {
- // On Fennec, we can check the session store data for zombie tabs
- if (this._browser.__SS_restore) {
- let sessionStore = this._browser.__SS_data;
- // Get the last selected entry
- let entry = sessionStore.entries[sessionStore.index - 1];
- return entry.title;
- }
- // If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a
- // tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label
- // as the title.
- if (this._tabbrowser) {
- let tab = this._tabbrowser.getTabForBrowser(this._browser);
- if (tab) {
- return tab.label;
- }
- }
- return "";
- },
-
- /**
- * If we don't have a url from the content side because it's a zombie tab, try to find
- * it on the chrome side.
- */
- get url() {
- // On Fennec, we can check the session store data for zombie tabs
- if (this._browser.__SS_restore) {
- let sessionStore = this._browser.__SS_data;
- // Get the last selected entry
- let entry = sessionStore.entries[sessionStore.index - 1];
- return entry.url;
- }
- return null;
- },
-
- form() {
- let form = Object.assign({}, this._form);
- // In some cases, the title and url fields might be empty. Zombie tabs (not yet
- // restored) are a good example. In such cases, try to look up values for these
- // fields using other data in the parent process.
- if (!form.title) {
- form.title = this.title;
- }
- if (!form.url) {
- form.url = this.url;
- }
- return form;
- },
-
- exit() {
- this._browser = null;
- },
-};
-
-exports.BrowserTabActor = BrowserTabActor;
-
-function BrowserAddonList(connection) {
- this._connection = connection;
- this._actorByAddonId = new Map();
- this._onListChanged = null;
-}
-
-BrowserAddonList.prototype.getList = function () {
- let deferred = promise.defer();
- AddonManager.getAllAddons((addons) => {
- for (let addon of addons) {
- let actor = this._actorByAddonId.get(addon.id);
- if (!actor) {
- if (addon.isWebExtension) {
- actor = new WebExtensionActor(this._connection, addon);
- } else {
- actor = new BrowserAddonActor(this._connection, addon);
- }
-
- this._actorByAddonId.set(addon.id, actor);
- }
- }
- deferred.resolve([...this._actorByAddonId].map(([_, actor]) => actor));
- });
- return deferred.promise;
-};
-
-Object.defineProperty(BrowserAddonList.prototype, "onListChanged", {
- enumerable: true,
- configurable: true,
- get() {
- return this._onListChanged;
- },
- set(v) {
- if (v !== null && typeof v != "function") {
- throw new Error(
- "onListChanged property may only be set to 'null' or a function");
- }
- this._onListChanged = v;
- this._adjustListener();
- }
-});
-
-BrowserAddonList.prototype.onInstalled = function (addon) {
- this._notifyListChanged();
- this._adjustListener();
-};
-
-BrowserAddonList.prototype.onUninstalled = function (addon) {
- this._actorByAddonId.delete(addon.id);
- this._notifyListChanged();
- this._adjustListener();
-};
-
-BrowserAddonList.prototype._notifyListChanged = function () {
- if (this._onListChanged) {
- this._onListChanged();
- }
-};
-
-BrowserAddonList.prototype._adjustListener = function () {
- if (this._onListChanged) {
- // As long as the callback exists, we need to listen for changes
- // so we can notify about add-on changes.
- AddonManager.addAddonListener(this);
- } else if (this._actorByAddonId.size === 0) {
- // When the callback does not exist, we only need to keep listening
- // if the actor cache will need adjusting when add-ons change.
- AddonManager.removeAddonListener(this);
- }
-};
-
-exports.BrowserAddonList = BrowserAddonList;
-
-/**
* The DebuggerProgressListener object is an nsIWebProgressListener which
* handles onStateChange events for the inspected browser. If the user tries to
* navigate away from a paused page, the listener makes sure that the debuggee
* is resumed before the navigation begins.
*
* @param TabActor aTabActor
* The tab actor associated with this listener.
*/
@@ -2513,16 +1662,8 @@ DebuggerProgressListener.prototype = {
} else {
// Somewhat equivalent of load event.
// (window.document.readyState == complete)
this._tabActor._navigate(window);
}
}
}, "DebuggerProgressListener.prototype.onStateChange")
};
-
-exports.register = function (handle) {
- handle.setRootActor(createRootActor);
-};
-
-exports.unregister = function (handle) {
- handle.setRootActor(null);
-};
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/webbrowser.js
@@ -1,97 +1,34 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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";
-/* global XPCNativeWrapper */
-
-var { Ci, Cu, Cr } = require("chrome");
+var { Ci } = require("chrome");
var Services = require("Services");
-var { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
var promise = require("promise");
-var {
- ActorPool, createExtraActors, appendExtraActors, GeneratedLocation
-} = require("devtools/server/actors/common");
var { DebuggerServer } = require("devtools/server/main");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
-var { assert } = DevToolsUtils;
-var { TabSources } = require("./utils/TabSources");
-var makeDebugger = require("./utils/make-debugger");
loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true);
-loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/script", true);
-loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "BrowserAddonActor", "devtools/server/actors/addon", true);
loader.lazyRequireGetter(this, "WebExtensionActor", "devtools/server/actors/webextension", true);
loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker", true);
loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActorList", "devtools/server/actors/worker", true);
loader.lazyRequireGetter(this, "ProcessActorList", "devtools/server/actors/process", true);
loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
-loader.lazyImporter(this, "ExtensionContent", "resource://gre/modules/ExtensionContent.jsm");
-
-// Assumptions on events module:
-// events needs to be dispatched synchronously,
-// by calling the listeners in the order or registration.
-loader.lazyRequireGetter(this, "events", "sdk/event/core");
-
-loader.lazyRequireGetter(this, "StyleSheetActor", "devtools/server/actors/stylesheets", true);
-
-function getWindowID(window) {
- return window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils)
- .currentInnerWindowID;
-}
-
-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.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow);
- } catch (e) {
- // ignore
- }
- }
- return handler;
-}
-
-function getChildDocShells(parentDocShell) {
- let docShellsEnum = parentDocShell.getDocShellEnumerator(
- Ci.nsIDocShellTreeItem.typeAll,
- Ci.nsIDocShell.ENUMERATE_FORWARDS
- );
-
- let docShells = [];
- while (docShellsEnum.hasMoreElements()) {
- let docShell = docShellsEnum.getNext();
- docShell.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebProgress);
- docShells.push(docShell);
- }
- return docShells;
-}
-
-exports.getChildDocShells = getChildDocShells;
/**
* Browser-specific actors.
*/
-function getInnerId(window) {
- return window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
-}
-
/**
* Yield all windows of type |windowType|, from the oldest window to the
* youngest, using nsIWindowMediator::getEnumerator. We're usually
* interested in "navigator:browser" windows.
*/
function* allAppShellDOMWindows(windowType) {
let e = Services.wm.getEnumerator(windowType);
while (e.hasMoreElements()) {
@@ -735,1396 +672,16 @@ DevToolsUtils.makeInfallible(function (w
}
}
}, "BrowserTabList.prototype.onCloseWindow's delayed body"), 0);
}, "BrowserTabList.prototype.onCloseWindow");
exports.BrowserTabList = BrowserTabList;
/**
- * Creates a TabActor whose main goal is to manage lifetime and
- * expose the tab actors being registered via DebuggerServer.registerModule.
- * But also track the lifetime of the document being tracked.
- *
- * ### Main requests:
- *
- * `attach`/`detach` requests:
- * - start/stop document watching:
- * Starts watching for new documents and emits `tabNavigated` and
- * `frameUpdate` over RDP.
- * - retrieve the thread actor:
- * Instantiates a ThreadActor that can be later attached to in order to
- * debug JS sources in the document.
- * `switchToFrame`:
- * Change the targeted document of the whole TabActor, and its child tab actors
- * to an iframe or back to its original document.
- *
- * Most of the TabActor properties (like `chromeEventHandler` or `docShells`)
- * are meant to be used by the various child tab actors.
- *
- * ### RDP events:
- *
- * - `tabNavigated`:
- * Sent when the tab 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.
- * * nativeConsoleAPI (boolean) `false` if the console API of the page has
- * been overridden (e.g. by Firebug),
- * `true` if the Gecko implementation is used.
- * * 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 TabActor 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 tab'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 TabActor to a specific frame:
- * { selected: #id }
- *
- * ### Internal, non-rdp events:
- * Various events are also dispatched on the TabActor itself that are not
- * related to RDP, so, not sent to the client. They all relate to the documents
- * tracked by the TabActor (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 on three 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.
- * - 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 tab 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 TabActor targeted document
- * to one of its iframes, or back to its original top document.
- * It is dispatched between window-destroyed and window-ready.
- * - stylesheet-added
- * This event is fired when a StyleSheetActor is created.
- * It contains the following attribute :
- * * actor (StyleSheetActor) The created actor.
- *
- * Note that *all* these events are dispatched in the following order
- * when we switch the context of the TabActor to a given iframe:
- * - will-navigate
- * - window-destroyed
- * - changed-toplevel-document
- * - window-ready
- * - navigate
- *
- * This class is subclassed by ContentActor and others.
- * Subclasses are expected to implement a getter for the docShell property.
- *
- * @param connection DebuggerServerConnection
- * The conection to the client.
- */
-function TabActor(connection) {
- this.conn = connection;
- this._tabActorPool = null;
- // A map of actor names to actor instances provided by extensions.
- this._extraActors = {};
- this._exited = false;
- this._sources = null;
-
- // Map of DOM stylesheets to StyleSheetActors
- this._styleSheetActors = new Map();
-
- this._shouldAddNewGlobalAsDebuggee =
- this._shouldAddNewGlobalAsDebuggee.bind(this);
-
- this.makeDebugger = makeDebugger.bind(null, {
- findDebuggees: () => {
- return this.windows.concat(this.webextensionsContentScriptGlobals);
- },
- shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee
- });
-
- // Flag eventually overloaded by sub classes in order to watch new docshells
- // Used by the ChromeActor to list all frames in the Browser Toolbox
- this.listenForNewDocShells = false;
-
- this.traits = {
- reconfigure: true,
- // Supports frame listing via `listFrames` request and `frameUpdate` events
- // as well as frame switching via `switchToFrame` request
- frames: true,
- // Do not require to send reconfigure request to reset the document state
- // to what it was before using the TabActor
- noTabReconfigureOnClose: true
- };
-
- this._workerActorList = null;
- this._workerActorPool = null;
- this._onWorkerActorListChanged = this._onWorkerActorListChanged.bind(this);
-}
-
-// XXX (bug 710213): TabActor attach/detach/exit/destroy is a
-// *complete* mess, needs to be rethought asap.
-
-TabActor.prototype = {
- traits: null,
-
- // Optional console API listener options (e.g. used by the WebExtensionActor to
- // filter console messages by addonID), set to an empty (no options) object by default.
- consoleAPIListenerOptions: {},
-
- // Optional TabSources filter function (e.g. used by the WebExtensionActor to filter
- // sources by addonID), allow all sources by default.
- _allowSource() {
- return true;
- },
-
- get exited() {
- return this._exited;
- },
-
- get attached() {
- return !!this._attached;
- },
-
- _tabPool: null,
- get tabActorPool() {
- return this._tabPool;
- },
-
- _contextPool: null,
- get contextActorPool() {
- return this._contextPool;
- },
-
- // A constant prefix that will be used to form the actor ID by the server.
- actorPrefix: "tab",
-
- /**
- * An object on which listen for DOMWindowCreated and pageshow events.
- */
- get chromeEventHandler() {
- return getDocShellChromeEventHandler(this.docShell);
- },
-
- /**
- * Getter for the nsIMessageManager associated to the tab.
- */
- get messageManager() {
- try {
- return this.docShell
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIContentFrameMessageManager);
- } catch (e) {
- return null;
- }
- },
-
- /**
- * Getter for the tab's doc shell.
- */
- get docShell() {
- throw new Error(
- "The docShell getter should be implemented by a subclass of TabActor");
- },
-
- /**
- * Getter for the list of all docshell in this tabActor
- * @return {Array}
- */
- get docShells() {
- return getChildDocShells(this.docShell);
- },
-
- /**
- * Getter for the tab content's DOM window.
- */
- get window() {
- // On xpcshell, there is no document
- if (this.docShell) {
- return this.docShell
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow);
- }
- return null;
- },
-
- get outerWindowID() {
- if (this.window) {
- return this.window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils)
- .outerWindowID;
- }
- return null;
- },
-
- /**
- * Getter for the WebExtensions ContentScript globals related to the
- * current tab content's DOM window.
- */
- get webextensionsContentScriptGlobals() {
- // Ignore xpcshell runtime which spawn TabActors without a window.
- if (this.window) {
- return ExtensionContent.getContentScriptGlobalsForWindow(this.window);
- }
-
- return [];
- },
-
- /**
- * Getter for the list of all content DOM windows in this tabActor
- * @return {Array}
- */
- get windows() {
- return this.docShells.map(docShell => {
- return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow);
- });
- },
-
- /**
- * Getter for the original docShell the tabActor got attached to in the first
- * place.
- * Note that your actor should normally *not* rely on this top level docShell
- * if you want it to show information relative to the iframe that's currently
- * being inspected in the toolbox.
- */
- get originalDocShell() {
- if (!this._originalWindow) {
- return this.docShell;
- }
-
- return this._originalWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell);
- },
-
- /**
- * Getter for the original window the tabActor got attached to in the first
- * place.
- * Note that your actor should normally *not* rely on this top level window if
- * you want it to show information relative to the iframe that's currently
- * being inspected in the toolbox.
- */
- get originalWindow() {
- return this._originalWindow || this.window;
- },
-
- /**
- * Getter for the nsIWebProgress for watching this window.
- */
- get webProgress() {
- return this.docShell
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebProgress);
- },
-
- /**
- * Getter for the nsIWebNavigation for the tab.
- */
- get webNavigation() {
- return this.docShell
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation);
- },
-
- /**
- * Getter for the tab's document.
- */
- get contentDocument() {
- return this.webNavigation.document;
- },
-
- /**
- * Getter for the tab title.
- * @return string
- * Tab title.
- */
- get title() {
- return this.contentDocument.contentTitle;
- },
-
- /**
- * Getter for the tab URL.
- * @return string
- * Tab URL.
- */
- get url() {
- if (this.webNavigation.currentURI) {
- return this.webNavigation.currentURI.spec;
- }
- // Abrupt closing of the browser window may leave callbacks without a
- // currentURI.
- return null;
- },
-
- get sources() {
- if (!this._sources) {
- this._sources = new TabSources(this.threadActor, this._allowSource);
- }
- return this._sources;
- },
-
- /**
- * This is called by BrowserTabList.getList for existing tab actors prior to
- * calling |form| below. It can be used to do any async work that may be
- * needed to assemble the form.
- */
- update() {
- return promise.resolve(this);
- },
-
- form() {
- assert(!this.exited,
- "form() shouldn't be called on exited browser actor.");
- assert(this.actorID,
- "tab should have an actorID.");
-
- let response = {
- actor: this.actorID
- };
-
- // We may try to access window while the document is closing, then
- // accessing window throws. Also on xpcshell we are using tabactor even if
- // there is no valid document.
- if (this.docShell && !this.docShell.isBeingDestroyed()) {
- response.title = this.title;
- response.url = this.url;
- response.outerWindowID = this.outerWindowID;
- }
-
- // Always use the same ActorPool, so existing actor instances
- // (created in createExtraActors) are not lost.
- if (!this._tabActorPool) {
- this._tabActorPool = new ActorPool(this.conn);
- this.conn.addActorPool(this._tabActorPool);
- }
-
- // Walk over tab actor factories and make sure they are all
- // instantiated and added into the ActorPool. Note that some
- // factories can be added dynamically by extensions.
- this._createExtraActors(DebuggerServer.tabActorFactories,
- this._tabActorPool);
-
- this._appendExtraActors(response);
- return response;
- },
-
- /**
- * Called when the actor is removed from the connection.
- */
- destroy() {
- this.exit();
- },
-
- /**
- * Called by the root actor when the underlying tab is closed.
- */
- exit() {
- if (this.exited) {
- return;
- }
-
- // Tell the thread actor that the tab is closed, so that it may terminate
- // instead of resuming the debuggee script.
- if (this._attached) {
- this.threadActor._tabClosed = true;
- }
-
- this._detach();
-
- Object.defineProperty(this, "docShell", {
- value: null,
- configurable: true
- });
-
- this._extraActors = null;
-
- this._exited = true;
- },
-
- /**
- * Return true if the given global is associated with this tab and should be
- * added as a debuggee, false otherwise.
- */
- _shouldAddNewGlobalAsDebuggee(wrappedGlobal) {
- if (wrappedGlobal.hostAnnotations &&
- wrappedGlobal.hostAnnotations.type == "document" &&
- wrappedGlobal.hostAnnotations.element === this.window) {
- return true;
- }
-
- let global = unwrapDebuggerObjectGlobal(wrappedGlobal);
- if (!global) {
- return false;
- }
-
- // Check if the global is a sdk page-mod sandbox.
- let metadata = {};
- let id = "";
- try {
- id = getInnerId(this.window);
- metadata = Cu.getSandboxMetadata(global);
- } catch (e) {
- // ignore
- }
- if (metadata
- && metadata["inner-window-id"]
- && metadata["inner-window-id"] == id) {
- return true;
- }
-
- return false;
- },
-
- /* Support for DebuggerServer.addTabActor. */
- _createExtraActors: createExtraActors,
- _appendExtraActors: appendExtraActors,
-
- /**
- * Does the actual work of attaching to a tab.
- */
- _attach() {
- if (this._attached) {
- return;
- }
-
- // Create a pool for tab-lifetime actors.
- assert(!this._tabPool, "Shouldn't have a tab pool if we weren't attached.");
- this._tabPool = new ActorPool(this.conn);
- this.conn.addActorPool(this._tabPool);
-
- // ... and a pool for context-lifetime actors.
- this._pushContext();
-
- // on xpcshell, there is no document
- if (this.window) {
- this._progressListener = new DebuggerProgressListener(this);
-
- // Save references to the original document we attached to
- this._originalWindow = this.window;
-
- // Ensure replying to attach() request first
- // before notifying about new docshells.
- DevToolsUtils.executeSoon(() => this._watchDocshells());
- }
-
- this._attached = true;
- },
-
- _watchDocshells() {
- // In child processes, we watch all docshells living in the process.
- if (this.listenForNewDocShells) {
- Services.obs.addObserver(this, "webnavigation-create", false);
- }
- Services.obs.addObserver(this, "webnavigation-destroy", false);
-
- // We watch for all child docshells under the current document,
- this._progressListener.watch(this.docShell);
-
- // And list all already existing ones.
- this._updateChildDocShells();
- },
-
- onSwitchToFrame(request) {
- let windowId = request.windowId;
- let win;
-
- try {
- win = Services.wm.getOuterWindowWithId(windowId);
- } catch (e) {
- // ignore
- }
- if (!win) {
- return { error: "noWindow",
- message: "The related docshell is destroyed or not found" };
- } else if (win == this.window) {
- return {};
- }
-
- // Reply first before changing the document
- DevToolsUtils.executeSoon(() => this._changeTopLevelDocument(win));
-
- return {};
- },
-
- onListFrames(request) {
- let windows = this._docShellsToWindows(this.docShells);
- return { frames: windows };
- },
-
- onListWorkers(request) {
- if (!this.attached) {
- return { error: "wrongState" };
- }
-
- if (this._workerActorList === null) {
- this._workerActorList = new WorkerActorList(this.conn, {
- type: Ci.nsIWorkerDebugger.TYPE_DEDICATED,
- window: this.window
- });
- }
-
- return this._workerActorList.getList().then((actors) => {
- let pool = new ActorPool(this.conn);
- for (let actor of actors) {
- pool.addActor(actor);
- }
-
- this.conn.removeActorPool(this._workerActorPool);
- this._workerActorPool = pool;
- this.conn.addActorPool(this._workerActorPool);
-
- this._workerActorList.onListChanged = this._onWorkerActorListChanged;
-
- return {
- "from": this.actorID,
- "workers": actors.map((actor) => actor.form())
- };
- });
- },
-
- _onWorkerActorListChanged() {
- this._workerActorList.onListChanged = null;
- this.conn.sendActorEvent(this.actorID, "workerListChanged");
- },
-
- observe(subject, topic, data) {
- // Ignore any event that comes before/after the tab actor is attached
- // That typically happens during firefox shutdown.
- if (!this.attached) {
- return;
- }
- if (topic == "webnavigation-create") {
- subject.QueryInterface(Ci.nsIDocShell);
- this._onDocShellCreated(subject);
- } else if (topic == "webnavigation-destroy") {
- this._onDocShellDestroy(subject);
- }
- },
-
- _onDocShellCreated(docShell) {
- // (chrome-)webnavigation-create is fired very early during docshell
- // construction. In new root docshells within child processes, involving
- // TabChild, this event is from within this call:
- // http://hg.mozilla.org/mozilla-central/annotate/74d7fb43bb44/dom/ipc/TabChild.cpp#l912
- // whereas the chromeEventHandler (and most likely other stuff) is set
- // later:
- // http://hg.mozilla.org/mozilla-central/annotate/74d7fb43bb44/dom/ipc/TabChild.cpp#l944
- // So wait a tick before watching it:
- DevToolsUtils.executeSoon(() => {
- // Bug 1142752: sometimes, the docshell appears to be immediately
- // destroyed, bailout early to prevent random exceptions.
- if (docShell.isBeingDestroyed()) {
- return;
- }
-
- // In child processes, we have new root docshells,
- // let's watch them and all their child docshells.
- if (this._isRootDocShell(docShell)) {
- this._progressListener.watch(docShell);
- }
- this._notifyDocShellsUpdate([docShell]);
- });
- },
-
- _onDocShellDestroy(docShell) {
- let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebProgress);
- this._notifyDocShellDestroy(webProgress);
- },
-
- _isRootDocShell(docShell) {
- // Should report as root docshell:
- // - New top level window's docshells, when using ChromeActor against a
- // process. It allows tracking iframes of the newly opened windows
- // like Browser console or new browser windows.
- // - MozActivities or window.open frames on B2G, where a new root docshell
- // is spawn in the child process of the app.
- return !docShell.parent;
- },
-
- // Convert docShell list to windows objects list being sent to the client
- _docShellsToWindows(docshells) {
- return docshells.map(docShell => {
- let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebProgress);
- let window = webProgress.DOMWindow;
- let id = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils)
- .outerWindowID;
- let parentID = undefined;
- // Ignore the parent of the original document on non-e10s firefox,
- // as we get the xul window as parent and don't care about it.
- if (window.parent && window != this._originalWindow) {
- parentID = window.parent
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils)
- .outerWindowID;
- }
-
- // Collect the addonID from the document origin attributes.
- let addonID = window.document.nodePrincipal.originAttributes.addonId;
-
- return {
- id,
- parentID,
- addonID,
- url: window.location.href,
- title: window.document.title,
- };
- });
- },
-
- _notifyDocShellsUpdate(docshells) {
- let windows = this._docShellsToWindows(docshells);
-
- // Do not send the `frameUpdate` event if the windows array is empty.
- if (windows.length == 0) {
- return;
- }
-
- this.conn.send({
- from: this.actorID,
- type: "frameUpdate",
- frames: windows
- });
- },
-
- _updateChildDocShells() {
- this._notifyDocShellsUpdate(this.docShells);
- },
-
- _notifyDocShellDestroy(webProgress) {
- webProgress = webProgress.QueryInterface(Ci.nsIWebProgress);
- let id = webProgress.DOMWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils)
- .outerWindowID;
- this.conn.send({
- from: this.actorID,
- type: "frameUpdate",
- frames: [{
- id,
- destroy: true
- }]
- });
-
- // Stop watching this docshell (the unwatch() method will check if we
- // started watching it before).
- webProgress.QueryInterface(Ci.nsIDocShell);
- this._progressListener.unwatch(webProgress);
-
- if (webProgress.DOMWindow == this._originalWindow) {
- // If the original top level document we connected to is removed,
- // we try to switch to any other top level document
- let rootDocShells = this.docShells
- .filter(d => {
- return d != this.docShell &&
- this._isRootDocShell(d);
- });
- if (rootDocShells.length > 0) {
- let newRoot = rootDocShells[0];
- this._originalWindow = newRoot.DOMWindow;
- this._changeTopLevelDocument(this._originalWindow);
- } else {
- // If for some reason (typically during Firefox shutdown), the original
- // document is destroyed, and there is no other top level docshell,
- // we detach the tab actor to unregister all listeners and prevent any
- // exception
- this.exit();
- }
- return;
- }
-
- // If the currently targeted context is destroyed,
- // and we aren't on the top-level document,
- // we have to switch to the top-level one.
- if (webProgress.DOMWindow == this.window &&
- this.window != this._originalWindow) {
- this._changeTopLevelDocument(this._originalWindow);
- }
- },
-
- _notifyDocShellDestroyAll() {
- this.conn.send({
- from: this.actorID,
- type: "frameUpdate",
- destroyAll: true
- });
- },
-
- /**
- * Creates a thread actor and a pool for context-lifetime actors. It then sets
- * up the content window for debugging.
- */
- _pushContext() {
- assert(!this._contextPool, "Can't push multiple contexts");
-
- this._contextPool = new ActorPool(this.conn);
- this.conn.addActorPool(this._contextPool);
-
- this.threadActor = new ThreadActor(this, this.window);
- this._contextPool.addActor(this.threadActor);
- },
-
- /**
- * Exits the current thread actor and removes the context-lifetime actor pool.
- * The content window is no longer being debugged after this call.
- */
- _popContext() {
- assert(!!this._contextPool, "No context to pop.");
-
- this.conn.removeActorPool(this._contextPool);
- this._contextPool = null;
- this.threadActor.exit();
- this.threadActor = null;
- this._sources = null;
- },
-
- /**
- * Does the actual work of detaching from a tab.
- *
- * @returns false if the tab wasn't attached or true of detaching succeeds.
- */
- _detach() {
- if (!this.attached) {
- return false;
- }
-
- // Check for docShell availability, as it can be already gone
- // during Firefox shutdown.
- if (this.docShell) {
- this._progressListener.unwatch(this.docShell);
- this._restoreDocumentSettings();
- }
- if (this._progressListener) {
- this._progressListener.destroy();
- this._progressListener = null;
- this._originalWindow = null;
-
- // Removes the observers being set in _watchDocShells
- if (this.listenForNewDocShells) {
- Services.obs.removeObserver(this, "webnavigation-create");
- }
- Services.obs.removeObserver(this, "webnavigation-destroy");
- }
-
- this._popContext();
-
- // Shut down actors that belong to this tab's pool.
- for (let sheetActor of this._styleSheetActors.values()) {
- this._tabPool.removeActor(sheetActor);
- }
- this._styleSheetActors.clear();
- this.conn.removeActorPool(this._tabPool);
- this._tabPool = null;
- if (this._tabActorPool) {
- this.conn.removeActorPool(this._tabActorPool);
- this._tabActorPool = null;
- }
-
- // Make sure that no more workerListChanged notifications are sent.
- if (this._workerActorList !== null) {
- this._workerActorList.onListChanged = null;
- this._workerActorList = null;
- }
-
- if (this._workerActorPool !== null) {
- this.conn.removeActorPool(this._workerActorPool);
- this._workerActorPool = null;
- }
-
- this._attached = false;
-
- this.conn.send({ from: this.actorID,
- type: "tabDetached" });
-
- return true;
- },
-
- // Protocol Request Handlers
-
- onAttach(request) {
- if (this.exited) {
- return { type: "exited" };
- }
-
- this._attach();
-
- return {
- type: "tabAttached",
- threadActor: this.threadActor.actorID,
- cacheDisabled: this._getCacheDisabled(),
- javascriptEnabled: this._getJavascriptEnabled(),
- traits: this.traits,
- };
- },
-
- onDetach(request) {
- if (!this._detach()) {
- return { error: "wrongState" };
- }
-
- return { type: "detached" };
- },
-
- /**
- * Bring the tab's window to front.
- */
- onFocus() {
- if (this.window) {
- this.window.focus();
- }
- return {};
- },
-
- /**
- * Reload the page in this tab.
- */
- onReload(request) {
- let force = request && request.options && request.options.force;
- // Wait a tick so that the response packet can be dispatched before the
- // subsequent navigation event packet.
- Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => {
- // This won't work while the browser is shutting down and we don't really
- // care.
- if (Services.startup.shuttingDown) {
- return;
- }
- this.webNavigation.reload(force ?
- Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE :
- Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
- }, "TabActor.prototype.onReload's delayed body"), 0);
- return {};
- },
-
- /**
- * Navigate this tab to a new location
- */
- onNavigateTo(request) {
- // Wait a tick so that the response packet can be dispatched before the
- // subsequent navigation event packet.
- Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => {
- this.window.location = request.url;
- }, "TabActor.prototype.onNavigateTo's delayed body"), 0);
- return {};
- },
-
- /**
- * Reconfigure options.
- */
- onReconfigure(request) {
- let options = request.options || {};
-
- if (!this.docShell) {
- // The tab is already closed.
- return {};
- }
- this._toggleDevToolsSettings(options);
-
- return {};
- },
-
- /**
- * Handle logic to enable/disable JS/cache/Service Worker testing.
- */
- _toggleDevToolsSettings(options) {
- // Wait a tick so that the response packet can be dispatched before the
- // subsequent navigation event packet.
- let reload = false;
-
- if (typeof options.javascriptEnabled !== "undefined" &&
- options.javascriptEnabled !== this._getJavascriptEnabled()) {
- this._setJavascriptEnabled(options.javascriptEnabled);
- reload = true;
- }
- if (typeof options.cacheDisabled !== "undefined" &&
- options.cacheDisabled !== this._getCacheDisabled()) {
- this._setCacheDisabled(options.cacheDisabled);
- }
- if ((typeof options.serviceWorkersTestingEnabled !== "undefined") &&
- (options.serviceWorkersTestingEnabled !==
- this._getServiceWorkersTestingEnabled())) {
- this._setServiceWorkersTestingEnabled(
- options.serviceWorkersTestingEnabled
- );
- }
-
- // Reload if:
- // - there's an explicit `performReload` flag and it's true
- // - there's no `performReload` flag, but it makes sense to do so
- let hasExplicitReloadFlag = "performReload" in options;
- if ((hasExplicitReloadFlag && options.performReload) ||
- (!hasExplicitReloadFlag && reload)) {
- this.onReload();
- }
- },
-
- /**
- * Opposite of the _toggleDevToolsSettings method, that reset document state
- * when closing the toolbox.
- */
- _restoreDocumentSettings() {
- this._restoreJavascript();
- this._setCacheDisabled(false);
- this._setServiceWorkersTestingEnabled(false);
- },
-
- /**
- * Disable or enable the cache via docShell.
- */
- _setCacheDisabled(disabled) {
- let enable = Ci.nsIRequest.LOAD_NORMAL;
- let disable = Ci.nsIRequest.LOAD_BYPASS_CACHE |
- Ci.nsIRequest.INHIBIT_CACHING;
-
- this.docShell.defaultLoadFlags = disabled ? disable : enable;
- },
-
- /**
- * Disable or enable JS via docShell.
- */
- _wasJavascriptEnabled: null,
- _setJavascriptEnabled(allow) {
- if (this._wasJavascriptEnabled === null) {
- this._wasJavascriptEnabled = this.docShell.allowJavascript;
- }
- this.docShell.allowJavascript = allow;
- },
-
- /**
- * Restore JS state, before the actor modified it.
- */
- _restoreJavascript() {
- if (this._wasJavascriptEnabled !== null) {
- this._setJavascriptEnabled(this._wasJavascriptEnabled);
- this._wasJavascriptEnabled = null;
- }
- },
-
- /**
- * Return JS allowed status.
- */
- _getJavascriptEnabled() {
- if (!this.docShell) {
- // The tab is already closed.
- return null;
- }
-
- return this.docShell.allowJavascript;
- },
-
- /**
- * Disable or enable the service workers testing features.
- */
- _setServiceWorkersTestingEnabled(enabled) {
- let windowUtils = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- windowUtils.serviceWorkersTestingEnabled = enabled;
- },
-
- /**
- * Return cache allowed status.
- */
- _getCacheDisabled() {
- if (!this.docShell) {
- // The tab is already closed.
- return null;
- }
-
- let disable = Ci.nsIRequest.LOAD_BYPASS_CACHE |
- Ci.nsIRequest.INHIBIT_CACHING;
- return this.docShell.defaultLoadFlags === disable;
- },
-
- /**
- * Return service workers testing allowed status.
- */
- _getServiceWorkersTestingEnabled() {
- if (!this.docShell) {
- // The tab is already closed.
- return null;
- }
-
- let windowUtils = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- return windowUtils.serviceWorkersTestingEnabled;
- },
-
- /**
- * Prepare to enter a nested event loop by disabling debuggee events.
- */
- preNest() {
- if (!this.window) {
- // The tab is already closed.
- return;
- }
- let windowUtils = this.window
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- windowUtils.suppressEventHandling(true);
- windowUtils.suspendTimeouts();
- },
-
- /**
- * Prepare to exit a nested event loop by enabling debuggee events.
- */
- postNest(nestData) {
- if (!this.window) {
- // The tab is already closed.
- return;
- }
- let windowUtils = this.window
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- windowUtils.resumeTimeouts();
- windowUtils.suppressEventHandling(false);
- },
-
- _changeTopLevelDocument(window) {
- // Fake a will-navigate on the previous document
- // to let a chance to unregister it
- this._willNavigate(this.window, window.location.href, null, true);
-
- this._windowDestroyed(this.window, null, true);
-
- // Immediately change the window as this window, if in process of unload
- // may already be non working on the next cycle and start throwing
- this._setWindow(window);
-
- DevToolsUtils.executeSoon(() => {
- // Then fake window-ready and navigate on the given document
- this._windowReady(window, true);
- DevToolsUtils.executeSoon(() => {
- this._navigate(window, true);
- });
- });
- },
-
- _setWindow(window) {
- let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell);
- // Here is the very important call where we switch the currently
- // targeted context (it will indirectly update this.window and
- // many other attributes defined from docShell).
- Object.defineProperty(this, "docShell", {
- value: docShell,
- enumerable: true,
- configurable: true
- });
- events.emit(this, "changed-toplevel-document");
- this.conn.send({
- from: this.actorID,
- type: "frameUpdate",
- selected: this.outerWindowID
- });
- },
-
- /**
- * Handle location changes, by clearing the previous debuggees and enabling
- * debugging, which may have been disabled temporarily by the
- * DebuggerProgressListener.
- */
- _windowReady(window, isFrameSwitching = false) {
- let isTopLevel = window == this.window;
-
- // We just reset iframe list on WillNavigate, so we now list all existing
- // frames when we load a new document in the original window
- if (window == this._originalWindow && !isFrameSwitching) {
- this._updateChildDocShells();
- }
-
- events.emit(this, "window-ready", {
- window: window,
- isTopLevel: isTopLevel,
- id: getWindowID(window)
- });
-
- // TODO bug 997119: move that code to ThreadActor by listening to
- // window-ready
- let threadActor = this.threadActor;
- if (isTopLevel && threadActor.state != "detached") {
- this.sources.reset({ sourceMaps: true });
- threadActor.clearDebuggees();
- threadActor.dbg.enabled = true;
- threadActor.maybePauseOnExceptions();
- // Update the global no matter if the debugger is on or off,
- // otherwise the global will be wrong when enabled later.
- threadActor.global = window;
- }
-
- // Refresh the debuggee list when a new window object appears (top window or
- // iframe).
- if (threadActor.attached) {
- threadActor.dbg.addDebuggees();
- }
- },
-
- _windowDestroyed(window, id = null, isFrozen = false) {
- events.emit(this, "window-destroyed", {
- window: window,
- isTopLevel: window == this.window,
- id: id || getWindowID(window),
- isFrozen: isFrozen
- });
- },
-
- /**
- * Start notifying server and client about a new document
- * being loaded in the currently targeted context.
- */
- _willNavigate(window, newURI, request, isFrameSwitching = false) {
- let isTopLevel = window == this.window;
- let reset = false;
-
- if (window == this._originalWindow && !isFrameSwitching) {
- // Clear the iframe list if the original top-level document changes.
- this._notifyDocShellDestroyAll();
-
- // If the top level document changes and we are targeting
- // an iframe, we need to reset to the upcoming new top level document.
- // But for this will-navigate event, we will dispatch on the old window.
- // (The inspector codebase expect to receive will-navigate for the
- // currently displayed document in order to cleanup the markup view)
- if (this.window != this._originalWindow) {
- reset = true;
- window = this.window;
- isTopLevel = true;
- }
- }
-
- // will-navigate event needs to be dispatched synchronously,
- // by calling the listeners in the order or registration.
- // This event fires once navigation starts,
- // (all pending user prompts are dealt with),
- // but before the first request starts.
- events.emit(this, "will-navigate", {
- window: window,
- isTopLevel: isTopLevel,
- newURI: newURI,
- request: request
- });
-
- // We don't do anything for inner frames in TabActor.
- // (we will only update thread actor on window-ready)
- if (!isTopLevel) {
- return;
- }
-
- // Proceed normally only if the debuggee is not paused.
- // TODO bug 997119: move that code to ThreadActor by listening to
- // will-navigate
- let threadActor = this.threadActor;
- if (threadActor.state == "paused") {
- this.conn.send(
- threadActor.unsafeSynchronize(Promise.resolve(threadActor.onResume())));
- threadActor.dbg.enabled = false;
- }
- threadActor.disableAllBreakpoints();
-
- this.conn.send({
- from: this.actorID,
- type: "tabNavigated",
- url: newURI,
- nativeConsoleAPI: true,
- state: "start",
- isFrameSwitching: isFrameSwitching
- });
-
- if (reset) {
- this._setWindow(this._originalWindow);
- }
- },
-
- /**
- * Notify server and client about a new document done loading in the current
- * targeted context.
- */
- _navigate(window, isFrameSwitching = false) {
- let isTopLevel = window == this.window;
-
- // navigate event needs to be dispatched synchronously,
- // by calling the listeners in the order or registration.
- // This event is fired once the document is loaded,
- // after the load event, it's document ready-state is 'complete'.
- events.emit(this, "navigate", {
- window: window,
- isTopLevel: isTopLevel
- });
-
- // We don't do anything for inner frames in TabActor.
- // (we will only update thread actor on window-ready)
- if (!isTopLevel) {
- return;
- }
-
- // TODO bug 997119: move that code to ThreadActor by listening to navigate
- let threadActor = this.threadActor;
- if (threadActor.state == "running") {
- threadActor.dbg.enabled = true;
- }
-
- this.conn.send({
- from: this.actorID,
- type: "tabNavigated",
- url: this.url,
- title: this.title,
- nativeConsoleAPI: this.hasNativeConsoleAPI(this.window),
- state: "stop",
- isFrameSwitching: isFrameSwitching
- });
- },
-
- /**
- * Tells if the window.console object is native or overwritten by script in
- * the page.
- *
- * @param nsIDOMWindow window
- * The window object you want to check.
- * @return boolean
- * True if the window.console object is native, or false otherwise.
- */
- hasNativeConsoleAPI(window) {
- let isNative = false;
- try {
- // We are very explicitly examining the "console" property of
- // the non-Xrayed object here.
- let console = window.wrappedJSObject.console;
- isNative = new XPCNativeWrapper(console).IS_NATIVE_CONSOLE;
- } catch (ex) {
- // ignore
- }
- return isNative;
- },
-
- /**
- * Create or return the StyleSheetActor for a style sheet. This method
- * is here because the Style Editor and Inspector share style sheet actors.
- *
- * @param DOMStyleSheet styleSheet
- * The style sheet to create an actor for.
- * @return StyleSheetActor actor
- * The actor for this style sheet.
- *
- */
- createStyleSheetActor(styleSheet) {
- if (this._styleSheetActors.has(styleSheet)) {
- return this._styleSheetActors.get(styleSheet);
- }
- let actor = new StyleSheetActor(styleSheet, this);
- this._styleSheetActors.set(styleSheet, actor);
-
- this._tabPool.addActor(actor);
- events.emit(this, "stylesheet-added", actor);
-
- return actor;
- },
-
- removeActorByName(name) {
- if (name in this._extraActors) {
- const actor = this._extraActors[name];
- if (this._tabActorPool.has(actor)) {
- this._tabActorPool.removeActor(actor);
- }
- delete this._extraActors[name];
- }
- },
-
- /**
- * Takes a packet containing a url, line and column and returns
- * the updated url, line and column based on the current source mapping
- * (source mapped files, pretty prints).
- *
- * @param {String} request.url
- * @param {Number} request.line
- * @param {Number?} request.column
- * @return {Promise<Object>}
- */
- onResolveLocation(request) {
- let { url, line } = request;
- let column = request.column || 0;
- const scripts = this.threadActor.dbg.findScripts({ url });
-
- if (!scripts[0] || !scripts[0].source) {
- return promise.resolve({
- from: this.actorID,
- type: "resolveLocation",
- error: "SOURCE_NOT_FOUND"
- });
- }
- const source = scripts[0].source;
- const generatedActor = this.sources.createNonSourceMappedActor(source);
- let generatedLocation = new GeneratedLocation(
- generatedActor, line, column);
- return this.sources.getOriginalLocation(generatedLocation).then(loc => {
- // If no map found, return this packet
- if (loc.originalLine == null) {
- return {
- type: "resolveLocation",
- error: "MAP_NOT_FOUND"
- };
- }
-
- loc = loc.toJSON();
- return {
- from: this.actorID,
- url: loc.source.url,
- column: loc.column,
- line: loc.line
- };
- });
- },
-};
-
-/**
- * The request types this actor can handle.
- */
-TabActor.prototype.requestTypes = {
- "attach": TabActor.prototype.onAttach,
- "detach": TabActor.prototype.onDetach,
- "focus": TabActor.prototype.onFocus,
- "reload": TabActor.prototype.onReload,
- "navigateTo": TabActor.prototype.onNavigateTo,
- "reconfigure": TabActor.prototype.onReconfigure,
- "switchToFrame": TabActor.prototype.onSwitchToFrame,
- "listFrames": TabActor.prototype.onListFrames,
- "listWorkers": TabActor.prototype.onListWorkers,
- "resolveLocation": TabActor.prototype.onResolveLocation
-};
-
-exports.TabActor = TabActor;
-
-/**
* Creates a tab actor for handling requests to a single browser frame.
* Both <xul:browser> and <iframe mozbrowser> are supported.
* This actor is a shim that connects to a ContentActor in a remote browser process.
* All RDP packets get forwarded using the message manager.
*
* @param connection The main RDP connection.
* @param browser <xul:browser> or <iframe mozbrowser> element to connect to.
*/
@@ -2311,218 +868,15 @@ BrowserAddonList.prototype._adjustListen
// When the callback does not exist, we only need to keep listening
// if the actor cache will need adjusting when add-ons change.
AddonManager.removeAddonListener(this);
}
};
exports.BrowserAddonList = BrowserAddonList;
-/**
- * The DebuggerProgressListener object is an nsIWebProgressListener which
- * handles onStateChange events for the inspected browser. If the user tries to
- * navigate away from a paused page, the listener makes sure that the debuggee
- * is resumed before the navigation begins.
- *
- * @param TabActor aTabActor
- * The tab actor associated with this listener.
- */
-function DebuggerProgressListener(tabActor) {
- this._tabActor = tabActor;
- this._onWindowCreated = this.onWindowCreated.bind(this);
- this._onWindowHidden = this.onWindowHidden.bind(this);
-
- // Watch for windows destroyed (global observer that will need filtering)
- Services.obs.addObserver(this, "inner-window-destroyed", false);
-
- // XXX: for now we maintain the list of windows we know about in this instance
- // so that we can discriminate windows we care about when observing
- // inner-window-destroyed events. Bug 1016952 would remove the need for this.
- this._knownWindowIDs = new Map();
-
- this._watchedDocShells = new WeakSet();
-}
-
-DebuggerProgressListener.prototype = {
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsIWebProgressListener,
- Ci.nsISupportsWeakReference,
- Ci.nsISupports,
- ]),
-
- destroy() {
- Services.obs.removeObserver(this, "inner-window-destroyed", false);
- this._knownWindowIDs.clear();
- this._knownWindowIDs = null;
- },
-
- watch(docShell) {
- // Add the docshell to the watched set. We're actually adding the window,
- // because docShell objects are not wrappercached and would be rejected
- // by the WeakSet.
- let docShellWindow = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow);
- this._watchedDocShells.add(docShellWindow);
-
- let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebProgress);
- webProgress.addProgressListener(this,
- Ci.nsIWebProgress.NOTIFY_STATUS |
- Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
- Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
-
- let handler = getDocShellChromeEventHandler(docShell);
- handler.addEventListener("DOMWindowCreated", this._onWindowCreated, true);
- handler.addEventListener("pageshow", this._onWindowCreated, true);
- handler.addEventListener("pagehide", this._onWindowHidden, true);
-
- // Dispatch the _windowReady event on the tabActor for pre-existing windows
- for (let win of this._getWindowsInDocShell(docShell)) {
- this._tabActor._windowReady(win);
- this._knownWindowIDs.set(getWindowID(win), win);
- }
- },
-
- unwatch(docShell) {
- let docShellWindow = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow);
- if (!this._watchedDocShells.has(docShellWindow)) {
- return;
- }
-
- let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebProgress);
- // During process shutdown, the docshell may already be cleaned up and throw
- try {
- webProgress.removeProgressListener(this);
- } catch (e) {
- // ignore
- }
-
- let handler = getDocShellChromeEventHandler(docShell);
- handler.removeEventListener("DOMWindowCreated",
- this._onWindowCreated, true);
- handler.removeEventListener("pageshow", this._onWindowCreated, true);
- handler.removeEventListener("pagehide", this._onWindowHidden, true);
-
- for (let win of this._getWindowsInDocShell(docShell)) {
- this._knownWindowIDs.delete(getWindowID(win));
- }
- },
-
- _getWindowsInDocShell(docShell) {
- return getChildDocShells(docShell).map(d => {
- return d.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow);
- });
- },
-
- onWindowCreated: DevToolsUtils.makeInfallible(function (evt) {
- if (!this._tabActor.attached) {
- return;
- }
-
- // pageshow events for non-persisted pages have already been handled by a
- // prior DOMWindowCreated event. For persisted pages, act as if the window
- // had just been created since it's been unfrozen from bfcache.
- if (evt.type == "pageshow" && !evt.persisted) {
- return;
- }
-
- let window = evt.target.defaultView;
- this._tabActor._windowReady(window);
-
- if (evt.type !== "pageshow") {
- this._knownWindowIDs.set(getWindowID(window), window);
- }
- }, "DebuggerProgressListener.prototype.onWindowCreated"),
-
- onWindowHidden: DevToolsUtils.makeInfallible(function (evt) {
- if (!this._tabActor.attached) {
- return;
- }
-
- // Only act as if the window has been destroyed if the 'pagehide' event
- // was sent for a persisted window (persisted is set when the page is put
- // and frozen in the bfcache). If the page isn't persisted, the observer's
- // inner-window-destroyed event will handle it.
- if (!evt.persisted) {
- return;
- }
-
- let window = evt.target.defaultView;
- this._tabActor._windowDestroyed(window, null, true);
- }, "DebuggerProgressListener.prototype.onWindowHidden"),
-
- observe: DevToolsUtils.makeInfallible(function (subject, topic) {
- if (!this._tabActor.attached) {
- return;
- }
-
- // Because this observer will be called for all inner-window-destroyed in
- // the application, we need to filter out events for windows we are not
- // watching
- let innerID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
- let window = this._knownWindowIDs.get(innerID);
- if (window) {
- this._knownWindowIDs.delete(innerID);
- this._tabActor._windowDestroyed(window, innerID);
- }
- }, "DebuggerProgressListener.prototype.observe"),
-
- onStateChange:
- DevToolsUtils.makeInfallible(function (progress, request, flag, status) {
- if (!this._tabActor.attached) {
- return;
- }
-
- let isStart = flag & Ci.nsIWebProgressListener.STATE_START;
- let isStop = flag & Ci.nsIWebProgressListener.STATE_STOP;
- let isDocument = flag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
- let isWindow = flag & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
-
- // Catch any iframe location change
- if (isDocument && isStop) {
- // Watch document stop to ensure having the new iframe url.
- progress.QueryInterface(Ci.nsIDocShell);
- this._tabActor._notifyDocShellsUpdate([progress]);
- }
-
- let window = progress.DOMWindow;
- if (isDocument && isStart) {
- // One of the earliest events that tells us a new URI
- // is being loaded in this window.
- let newURI = request instanceof Ci.nsIChannel ? request.URI.spec : null;
- this._tabActor._willNavigate(window, newURI, request);
- }
- if (isWindow && isStop) {
- // Don't dispatch "navigate" event just yet when there is a redirect to
- // about:neterror page.
- if (request.status != Cr.NS_OK) {
- // Instead, listen for DOMContentLoaded as about:neterror is loaded
- // with LOAD_BACKGROUND flags and never dispatches load event.
- // That may be the same reason why there is no onStateChange event
- // for about:neterror loads.
- let handler = getDocShellChromeEventHandler(progress);
- let onLoad = evt => {
- // Ignore events from iframes
- if (evt.target == window.document) {
- handler.removeEventListener("DOMContentLoaded", onLoad, true);
- this._tabActor._navigate(window);
- }
- };
- handler.addEventListener("DOMContentLoaded", onLoad, true);
- } else {
- // Somewhat equivalent of load event.
- // (window.document.readyState == complete)
- this._tabActor._navigate(window);
- }
- }
- }, "DebuggerProgressListener.prototype.onStateChange")
-};
-
exports.register = function (handle) {
handle.setRootActor(createRootActor);
};
exports.unregister = function (handle) {
handle.setRootActor(null);
};