1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/devtools/server/actors/webbrowser.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1448 @@ 1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +"use strict"; 1.11 + 1.12 +let {Ci,Cu} = require("chrome"); 1.13 +let {createExtraActors, appendExtraActors} = require("devtools/server/actors/common"); 1.14 +let DevToolsUtils = require("devtools/toolkit/DevToolsUtils"); 1.15 + 1.16 +let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); 1.17 +Cu.import("resource://gre/modules/Services.jsm"); 1.18 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.19 + 1.20 +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); 1.21 + 1.22 +// Assumptions on events module: 1.23 +// events needs to be dispatched synchronously, 1.24 +// by calling the listeners in the order or registration. 1.25 +XPCOMUtils.defineLazyGetter(this, "events", () => { 1.26 + return devtools.require("sdk/event/core"); 1.27 +}); 1.28 + 1.29 +// Also depends on following symbols, shared by common scope with main.js: 1.30 +// DebuggerServer, CommonCreateExtraActors, CommonAppendExtraActors, ActorPool, 1.31 +// ThreadActor 1.32 + 1.33 +/** 1.34 + * Browser-specific actors. 1.35 + */ 1.36 + 1.37 +/** 1.38 + * Yield all windows of type |aWindowType|, from the oldest window to the 1.39 + * youngest, using nsIWindowMediator::getEnumerator. We're usually 1.40 + * interested in "navigator:browser" windows. 1.41 + */ 1.42 +function allAppShellDOMWindows(aWindowType) 1.43 +{ 1.44 + let e = Services.wm.getEnumerator(aWindowType); 1.45 + while (e.hasMoreElements()) { 1.46 + yield e.getNext(); 1.47 + } 1.48 +} 1.49 + 1.50 +/** 1.51 + * Retrieve the window type of the top-level window |aWindow|. 1.52 + */ 1.53 +function appShellDOMWindowType(aWindow) { 1.54 + /* This is what nsIWindowMediator's enumerator checks. */ 1.55 + return aWindow.document.documentElement.getAttribute('windowtype'); 1.56 +} 1.57 + 1.58 +/** 1.59 + * Send Debugger:Shutdown events to all "navigator:browser" windows. 1.60 + */ 1.61 +function sendShutdownEvent() { 1.62 + for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) { 1.63 + let evt = win.document.createEvent("Event"); 1.64 + evt.initEvent("Debugger:Shutdown", true, false); 1.65 + win.document.documentElement.dispatchEvent(evt); 1.66 + } 1.67 +} 1.68 + 1.69 +/** 1.70 + * Construct a root actor appropriate for use in a server running in a 1.71 + * browser. The returned root actor: 1.72 + * - respects the factories registered with DebuggerServer.addGlobalActor, 1.73 + * - uses a BrowserTabList to supply tab actors, 1.74 + * - sends all navigator:browser window documents a Debugger:Shutdown event 1.75 + * when it exits. 1.76 + * 1.77 + * * @param aConnection DebuggerServerConnection 1.78 + * The conection to the client. 1.79 + */ 1.80 +function createRootActor(aConnection) 1.81 +{ 1.82 + return new RootActor(aConnection, 1.83 + { 1.84 + tabList: new BrowserTabList(aConnection), 1.85 + addonList: new BrowserAddonList(aConnection), 1.86 + globalActorFactories: DebuggerServer.globalActorFactories, 1.87 + onShutdown: sendShutdownEvent 1.88 + }); 1.89 +} 1.90 + 1.91 +/** 1.92 + * A live list of BrowserTabActors representing the current browser tabs, 1.93 + * to be provided to the root actor to answer 'listTabs' requests. 1.94 + * 1.95 + * This object also takes care of listening for TabClose events and 1.96 + * onCloseWindow notifications, and exiting the BrowserTabActors concerned. 1.97 + * 1.98 + * (See the documentation for RootActor for the definition of the "live 1.99 + * list" interface.) 1.100 + * 1.101 + * @param aConnection DebuggerServerConnection 1.102 + * The connection in which this list's tab actors may participate. 1.103 + * 1.104 + * Some notes: 1.105 + * 1.106 + * This constructor is specific to the desktop browser environment; it 1.107 + * maintains the tab list by tracking XUL windows and their XUL documents' 1.108 + * "tabbrowser", "tab", and "browser" elements. What's entailed in maintaining 1.109 + * an accurate list of open tabs in this context? 1.110 + * 1.111 + * - Opening and closing XUL windows: 1.112 + * 1.113 + * An nsIWindowMediatorListener is notified when new XUL windows (i.e., desktop 1.114 + * windows) are opened and closed. It is not notified of individual content 1.115 + * browser tabs coming and going within such a XUL window. That seems 1.116 + * reasonable enough; it's concerned with XUL windows, not tab elements in the 1.117 + * window's XUL document. 1.118 + * 1.119 + * However, even if we attach TabOpen and TabClose event listeners to each XUL 1.120 + * window as soon as it is created: 1.121 + * 1.122 + * - we do not receive a TabOpen event for the initial empty tab of a new XUL 1.123 + * window; and 1.124 + * 1.125 + * - we do not receive TabClose events for the tabs of a XUL window that has 1.126 + * been closed. 1.127 + * 1.128 + * This means that TabOpen and TabClose events alone are not sufficient to 1.129 + * maintain an accurate list of live tabs and mark tab actors as closed 1.130 + * promptly. Our nsIWindowMediatorListener onCloseWindow handler must find and 1.131 + * exit all actors for tabs that were in the closing window. 1.132 + * 1.133 + * Since this is a bit hairy, we don't make each individual attached tab actor 1.134 + * responsible for noticing when it has been closed; we watch for that, and 1.135 + * promise to call each actor's 'exit' method when it's closed, regardless of 1.136 + * how we learn the news. 1.137 + * 1.138 + * - nsIWindowMediator locks 1.139 + * 1.140 + * nsIWindowMediator holds a lock protecting its list of top-level windows 1.141 + * while it calls nsIWindowMediatorListener methods. nsIWindowMediator's 1.142 + * GetEnumerator method also tries to acquire that lock. Thus, enumerating 1.143 + * windows from within a listener method deadlocks (bug 873589). Rah. One 1.144 + * can sometimes work around this by leaving the enumeration for a later 1.145 + * tick. 1.146 + * 1.147 + * - Dragging tabs between windows: 1.148 + * 1.149 + * When a tab is dragged from one desktop window to another, we receive a 1.150 + * TabOpen event for the new tab, and a TabClose event for the old tab; tab XUL 1.151 + * elements do not really move from one document to the other (although their 1.152 + * linked browser's content window objects do). 1.153 + * 1.154 + * However, while we could thus assume that each tab stays with the XUL window 1.155 + * it belonged to when it was created, I'm not sure this is behavior one should 1.156 + * rely upon. When a XUL window is closed, we take the less efficient, more 1.157 + * conservative approach of simply searching the entire table for actors that 1.158 + * belong to the closing XUL window, rather than trying to somehow track which 1.159 + * XUL window each tab belongs to. 1.160 + */ 1.161 +function BrowserTabList(aConnection) 1.162 +{ 1.163 + this._connection = aConnection; 1.164 + 1.165 + /* 1.166 + * The XUL document of a tabbed browser window has "tab" elements, whose 1.167 + * 'linkedBrowser' JavaScript properties are "browser" elements; those 1.168 + * browsers' 'contentWindow' properties are wrappers on the tabs' content 1.169 + * window objects. 1.170 + * 1.171 + * This map's keys are "browser" XUL elements; it maps each browser element 1.172 + * to the tab actor we've created for its content window, if we've created 1.173 + * one. This map serves several roles: 1.174 + * 1.175 + * - During iteration, we use it to find actors we've created previously. 1.176 + * 1.177 + * - On a TabClose event, we use it to find the tab's actor and exit it. 1.178 + * 1.179 + * - When the onCloseWindow handler is called, we iterate over it to find all 1.180 + * tabs belonging to the closing XUL window, and exit them. 1.181 + * 1.182 + * - When it's empty, and the onListChanged hook is null, we know we can 1.183 + * stop listening for events and notifications. 1.184 + * 1.185 + * We listen for TabClose events and onCloseWindow notifications in order to 1.186 + * send onListChanged notifications, but also to tell actors when their 1.187 + * referent has gone away and remove entries for dead browsers from this map. 1.188 + * If that code is working properly, neither this map nor the actors in it 1.189 + * should ever hold dead tabs alive. 1.190 + */ 1.191 + this._actorByBrowser = new Map(); 1.192 + 1.193 + /* The current onListChanged handler, or null. */ 1.194 + this._onListChanged = null; 1.195 + 1.196 + /* 1.197 + * True if we've been iterated over since we last called our onListChanged 1.198 + * hook. 1.199 + */ 1.200 + this._mustNotify = false; 1.201 + 1.202 + /* True if we're testing, and should throw if consistency checks fail. */ 1.203 + this._testing = false; 1.204 +} 1.205 + 1.206 +BrowserTabList.prototype.constructor = BrowserTabList; 1.207 + 1.208 + 1.209 +/** 1.210 + * Get the selected browser for the given navigator:browser window. 1.211 + * @private 1.212 + * @param aWindow nsIChromeWindow 1.213 + * The navigator:browser window for which you want the selected browser. 1.214 + * @return nsIDOMElement|null 1.215 + * The currently selected xul:browser element, if any. Note that the 1.216 + * browser window might not be loaded yet - the function will return 1.217 + * |null| in such cases. 1.218 + */ 1.219 +BrowserTabList.prototype._getSelectedBrowser = function(aWindow) { 1.220 + return aWindow.gBrowser ? aWindow.gBrowser.selectedBrowser : null; 1.221 +}; 1.222 + 1.223 +BrowserTabList.prototype._getChildren = function(aWindow) { 1.224 + return aWindow.gBrowser.browsers; 1.225 +}; 1.226 + 1.227 +BrowserTabList.prototype.getList = function() { 1.228 + let topXULWindow = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType); 1.229 + 1.230 + // As a sanity check, make sure all the actors presently in our map get 1.231 + // picked up when we iterate over all windows' tabs. 1.232 + let initialMapSize = this._actorByBrowser.size; 1.233 + let foundCount = 0; 1.234 + 1.235 + // To avoid mysterious behavior if tabs are closed or opened mid-iteration, 1.236 + // we update the map first, and then make a second pass over it to yield 1.237 + // the actors. Thus, the sequence yielded is always a snapshot of the 1.238 + // actors that were live when we began the iteration. 1.239 + 1.240 + let actorPromises = []; 1.241 + 1.242 + // Iterate over all navigator:browser XUL windows. 1.243 + for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) { 1.244 + let selectedBrowser = this._getSelectedBrowser(win); 1.245 + if (!selectedBrowser) { 1.246 + continue; 1.247 + } 1.248 + 1.249 + // For each tab in this XUL window, ensure that we have an actor for 1.250 + // it, reusing existing actors where possible. We actually iterate 1.251 + // over 'browser' XUL elements, and BrowserTabActor uses 1.252 + // browser.contentWindow as the debuggee global. 1.253 + for (let browser of this._getChildren(win)) { 1.254 + // Do we have an existing actor for this browser? If not, create one. 1.255 + let actor = this._actorByBrowser.get(browser); 1.256 + if (actor) { 1.257 + actorPromises.push(promise.resolve(actor)); 1.258 + foundCount++; 1.259 + } else if (browser.isRemoteBrowser) { 1.260 + actor = new RemoteBrowserTabActor(this._connection, browser); 1.261 + this._actorByBrowser.set(browser, actor); 1.262 + let promise = actor.connect().then((form) => { 1.263 + actor._form = form; 1.264 + return actor; 1.265 + }); 1.266 + actorPromises.push(promise); 1.267 + } else { 1.268 + actor = new BrowserTabActor(this._connection, browser, win.gBrowser); 1.269 + this._actorByBrowser.set(browser, actor); 1.270 + actorPromises.push(promise.resolve(actor)); 1.271 + } 1.272 + 1.273 + // Set the 'selected' properties on all actors correctly. 1.274 + actor.selected = (win === topXULWindow && browser === selectedBrowser); 1.275 + } 1.276 + } 1.277 + 1.278 + if (this._testing && initialMapSize !== foundCount) 1.279 + throw Error("_actorByBrowser map contained actors for dead tabs"); 1.280 + 1.281 + this._mustNotify = true; 1.282 + this._checkListening(); 1.283 + 1.284 + return promise.all(actorPromises); 1.285 +}; 1.286 + 1.287 +Object.defineProperty(BrowserTabList.prototype, 'onListChanged', { 1.288 + enumerable: true, configurable:true, 1.289 + get: function() { return this._onListChanged; }, 1.290 + set: function(v) { 1.291 + if (v !== null && typeof v !== 'function') { 1.292 + throw Error("onListChanged property may only be set to 'null' or a function"); 1.293 + } 1.294 + this._onListChanged = v; 1.295 + this._checkListening(); 1.296 + } 1.297 +}); 1.298 + 1.299 +/** 1.300 + * The set of tabs has changed somehow. Call our onListChanged handler, if 1.301 + * one is set, and if we haven't already called it since the last iteration. 1.302 + */ 1.303 +BrowserTabList.prototype._notifyListChanged = function() { 1.304 + if (!this._onListChanged) 1.305 + return; 1.306 + if (this._mustNotify) { 1.307 + this._onListChanged(); 1.308 + this._mustNotify = false; 1.309 + } 1.310 +}; 1.311 + 1.312 +/** 1.313 + * Exit |aActor|, belonging to |aBrowser|, and notify the onListChanged 1.314 + * handle if needed. 1.315 + */ 1.316 +BrowserTabList.prototype._handleActorClose = function(aActor, aBrowser) { 1.317 + if (this._testing) { 1.318 + if (this._actorByBrowser.get(aBrowser) !== aActor) { 1.319 + throw Error("BrowserTabActor not stored in map under given browser"); 1.320 + } 1.321 + if (aActor.browser !== aBrowser) { 1.322 + throw Error("actor's browser and map key don't match"); 1.323 + } 1.324 + } 1.325 + 1.326 + this._actorByBrowser.delete(aBrowser); 1.327 + aActor.exit(); 1.328 + 1.329 + this._notifyListChanged(); 1.330 + this._checkListening(); 1.331 +}; 1.332 + 1.333 +/** 1.334 + * Make sure we are listening or not listening for activity elsewhere in 1.335 + * the browser, as appropriate. Other than setting up newly created XUL 1.336 + * windows, all listener / observer connection and disconnection should 1.337 + * happen here. 1.338 + */ 1.339 +BrowserTabList.prototype._checkListening = function() { 1.340 + /* 1.341 + * If we have an onListChanged handler that we haven't sent an announcement 1.342 + * to since the last iteration, we need to watch for tab creation. 1.343 + * 1.344 + * Oddly, we don't need to watch for 'close' events here. If our actor list 1.345 + * is empty, then either it was empty the last time we iterated, and no 1.346 + * close events are possible, or it was not empty the last time we 1.347 + * iterated, but all the actors have since been closed, and we must have 1.348 + * sent a notification already when they closed. 1.349 + */ 1.350 + this._listenForEventsIf(this._onListChanged && this._mustNotify, 1.351 + "_listeningForTabOpen", ["TabOpen", "TabSelect"]); 1.352 + 1.353 + /* If we have live actors, we need to be ready to mark them dead. */ 1.354 + this._listenForEventsIf(this._actorByBrowser.size > 0, 1.355 + "_listeningForTabClose", ["TabClose"]); 1.356 + 1.357 + /* 1.358 + * We must listen to the window mediator in either case, since that's the 1.359 + * only way to find out about tabs that come and go when top-level windows 1.360 + * are opened and closed. 1.361 + */ 1.362 + this._listenToMediatorIf((this._onListChanged && this._mustNotify) || 1.363 + (this._actorByBrowser.size > 0)); 1.364 +}; 1.365 + 1.366 +/* 1.367 + * Add or remove event listeners for all XUL windows. 1.368 + * 1.369 + * @param aShouldListen boolean 1.370 + * True if we should add event handlers; false if we should remove them. 1.371 + * @param aGuard string 1.372 + * The name of a guard property of 'this', indicating whether we're 1.373 + * already listening for those events. 1.374 + * @param aEventNames array of strings 1.375 + * An array of event names. 1.376 + */ 1.377 +BrowserTabList.prototype._listenForEventsIf = function(aShouldListen, aGuard, aEventNames) { 1.378 + if (!aShouldListen !== !this[aGuard]) { 1.379 + let op = aShouldListen ? "addEventListener" : "removeEventListener"; 1.380 + for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) { 1.381 + for (let name of aEventNames) { 1.382 + win[op](name, this, false); 1.383 + } 1.384 + } 1.385 + this[aGuard] = aShouldListen; 1.386 + } 1.387 +}; 1.388 + 1.389 +/** 1.390 + * Implement nsIDOMEventListener. 1.391 + */ 1.392 +BrowserTabList.prototype.handleEvent = DevToolsUtils.makeInfallible(function(aEvent) { 1.393 + switch (aEvent.type) { 1.394 + case "TabOpen": 1.395 + case "TabSelect": 1.396 + /* Don't create a new actor; iterate will take care of that. Just notify. */ 1.397 + this._notifyListChanged(); 1.398 + this._checkListening(); 1.399 + break; 1.400 + case "TabClose": 1.401 + let browser = aEvent.target.linkedBrowser; 1.402 + let actor = this._actorByBrowser.get(browser); 1.403 + if (actor) { 1.404 + this._handleActorClose(actor, browser); 1.405 + } 1.406 + break; 1.407 + } 1.408 +}, "BrowserTabList.prototype.handleEvent"); 1.409 + 1.410 +/* 1.411 + * If |aShouldListen| is true, ensure we've registered a listener with the 1.412 + * window mediator. Otherwise, ensure we haven't registered a listener. 1.413 + */ 1.414 +BrowserTabList.prototype._listenToMediatorIf = function(aShouldListen) { 1.415 + if (!aShouldListen !== !this._listeningToMediator) { 1.416 + let op = aShouldListen ? "addListener" : "removeListener"; 1.417 + Services.wm[op](this); 1.418 + this._listeningToMediator = aShouldListen; 1.419 + } 1.420 +}; 1.421 + 1.422 +/** 1.423 + * nsIWindowMediatorListener implementation. 1.424 + * 1.425 + * See _onTabClosed for explanation of why we needn't actually tweak any 1.426 + * actors or tables here. 1.427 + * 1.428 + * An nsIWindowMediatorListener's methods get passed all sorts of windows; we 1.429 + * only care about the tab containers. Those have 'getBrowser' methods. 1.430 + */ 1.431 +BrowserTabList.prototype.onWindowTitleChange = () => { }; 1.432 + 1.433 +BrowserTabList.prototype.onOpenWindow = DevToolsUtils.makeInfallible(function(aWindow) { 1.434 + let handleLoad = DevToolsUtils.makeInfallible(() => { 1.435 + /* We don't want any further load events from this window. */ 1.436 + aWindow.removeEventListener("load", handleLoad, false); 1.437 + 1.438 + if (appShellDOMWindowType(aWindow) !== DebuggerServer.chromeWindowType) 1.439 + return; 1.440 + 1.441 + // Listen for future tab activity. 1.442 + if (this._listeningForTabOpen) { 1.443 + aWindow.addEventListener("TabOpen", this, false); 1.444 + aWindow.addEventListener("TabSelect", this, false); 1.445 + } 1.446 + if (this._listeningForTabClose) { 1.447 + aWindow.addEventListener("TabClose", this, false); 1.448 + } 1.449 + 1.450 + // As explained above, we will not receive a TabOpen event for this 1.451 + // document's initial tab, so we must notify our client of the new tab 1.452 + // this will have. 1.453 + this._notifyListChanged(); 1.454 + }); 1.455 + 1.456 + /* 1.457 + * You can hardly do anything at all with a XUL window at this point; it 1.458 + * doesn't even have its document yet. Wait until its document has 1.459 + * loaded, and then see what we've got. This also avoids 1.460 + * nsIWindowMediator enumeration from within listeners (bug 873589). 1.461 + */ 1.462 + aWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.463 + .getInterface(Ci.nsIDOMWindow); 1.464 + 1.465 + aWindow.addEventListener("load", handleLoad, false); 1.466 +}, "BrowserTabList.prototype.onOpenWindow"); 1.467 + 1.468 +BrowserTabList.prototype.onCloseWindow = DevToolsUtils.makeInfallible(function(aWindow) { 1.469 + aWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.470 + .getInterface(Ci.nsIDOMWindow); 1.471 + 1.472 + if (appShellDOMWindowType(aWindow) !== DebuggerServer.chromeWindowType) 1.473 + return; 1.474 + 1.475 + /* 1.476 + * nsIWindowMediator deadlocks if you call its GetEnumerator method from 1.477 + * a nsIWindowMediatorListener's onCloseWindow hook (bug 873589), so 1.478 + * handle the close in a different tick. 1.479 + */ 1.480 + Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => { 1.481 + /* 1.482 + * Scan the entire map for actors representing tabs that were in this 1.483 + * top-level window, and exit them. 1.484 + */ 1.485 + for (let [browser, actor] of this._actorByBrowser) { 1.486 + /* The browser document of a closed window has no default view. */ 1.487 + if (!browser.ownerDocument.defaultView) { 1.488 + this._handleActorClose(actor, browser); 1.489 + } 1.490 + } 1.491 + }, "BrowserTabList.prototype.onCloseWindow's delayed body"), 0); 1.492 +}, "BrowserTabList.prototype.onCloseWindow"); 1.493 + 1.494 +/** 1.495 + * Creates a tab actor for handling requests to a browser tab, like 1.496 + * attaching and detaching. TabActor respects the actor factories 1.497 + * registered with DebuggerServer.addTabActor. 1.498 + * 1.499 + * This class is subclassed by BrowserTabActor and 1.500 + * ContentActor. Subclasses are expected to implement a getter 1.501 + * the docShell properties. 1.502 + * 1.503 + * @param aConnection DebuggerServerConnection 1.504 + * The conection to the client. 1.505 + * @param aChromeEventHandler 1.506 + * An object on which listen for DOMWindowCreated and pageshow events. 1.507 + */ 1.508 +function TabActor(aConnection) 1.509 +{ 1.510 + this.conn = aConnection; 1.511 + this._tabActorPool = null; 1.512 + // A map of actor names to actor instances provided by extensions. 1.513 + this._extraActors = {}; 1.514 + this._exited = false; 1.515 + 1.516 + this.traits = { reconfigure: true }; 1.517 +} 1.518 + 1.519 +// XXX (bug 710213): TabActor attach/detach/exit/disconnect is a 1.520 +// *complete* mess, needs to be rethought asap. 1.521 + 1.522 +TabActor.prototype = { 1.523 + traits: null, 1.524 + 1.525 + get exited() { return this._exited; }, 1.526 + get attached() { return !!this._attached; }, 1.527 + 1.528 + _tabPool: null, 1.529 + get tabActorPool() { return this._tabPool; }, 1.530 + 1.531 + _contextPool: null, 1.532 + get contextActorPool() { return this._contextPool; }, 1.533 + 1.534 + _pendingNavigation: null, 1.535 + 1.536 + // A constant prefix that will be used to form the actor ID by the server. 1.537 + actorPrefix: "tab", 1.538 + 1.539 + /** 1.540 + * An object on which listen for DOMWindowCreated and pageshow events. 1.541 + */ 1.542 + get chromeEventHandler() { 1.543 + // TODO: bug 992778, fix docShell.chromeEventHandler in child processes 1.544 + return this.docShell.chromeEventHandler || 1.545 + this.docShell.QueryInterface(Ci.nsIInterfaceRequestor) 1.546 + .getInterface(Ci.nsIContentFrameMessageManager); 1.547 + }, 1.548 + 1.549 + /** 1.550 + * Getter for the nsIMessageManager associated to the tab. 1.551 + */ 1.552 + get messageManager() { 1.553 + return this.docShell 1.554 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.555 + .getInterface(Ci.nsIContentFrameMessageManager); 1.556 + }, 1.557 + 1.558 + /** 1.559 + * Getter for the tab's doc shell. 1.560 + */ 1.561 + get docShell() { 1.562 + throw "The docShell getter should be implemented by a subclass of TabActor"; 1.563 + }, 1.564 + 1.565 + /** 1.566 + * Getter for the tab content's DOM window. 1.567 + */ 1.568 + get window() { 1.569 + return this.docShell 1.570 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.571 + .getInterface(Ci.nsIDOMWindow); 1.572 + }, 1.573 + 1.574 + /** 1.575 + * Getter for the nsIWebProgress for watching this window. 1.576 + */ 1.577 + get webProgress() { 1.578 + return this.docShell 1.579 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.580 + .getInterface(Ci.nsIWebProgress); 1.581 + }, 1.582 + 1.583 + /** 1.584 + * Getter for the nsIWebNavigation for the tab. 1.585 + */ 1.586 + get webNavigation() { 1.587 + return this.docShell 1.588 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.589 + .getInterface(Ci.nsIWebNavigation); 1.590 + }, 1.591 + 1.592 + /** 1.593 + * Getter for the tab's document. 1.594 + */ 1.595 + get contentDocument() { 1.596 + return this.webNavigation.document; 1.597 + }, 1.598 + 1.599 + /** 1.600 + * Getter for the tab title. 1.601 + * @return string 1.602 + * Tab title. 1.603 + */ 1.604 + get title() { 1.605 + return this.contentDocument.contentTitle; 1.606 + }, 1.607 + 1.608 + /** 1.609 + * Getter for the tab URL. 1.610 + * @return string 1.611 + * Tab URL. 1.612 + */ 1.613 + get url() { 1.614 + if (this.webNavigation.currentURI) { 1.615 + return this.webNavigation.currentURI.spec; 1.616 + } 1.617 + // Abrupt closing of the browser window may leave callbacks without a 1.618 + // currentURI. 1.619 + return null; 1.620 + }, 1.621 + 1.622 + form: function BTA_form() { 1.623 + dbg_assert(!this.exited, 1.624 + "grip() shouldn't be called on exited browser actor."); 1.625 + dbg_assert(this.actorID, 1.626 + "tab should have an actorID."); 1.627 + 1.628 + let windowUtils = this.window 1.629 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.630 + .getInterface(Ci.nsIDOMWindowUtils); 1.631 + 1.632 + let response = { 1.633 + actor: this.actorID, 1.634 + title: this.title, 1.635 + url: this.url, 1.636 + outerWindowID: windowUtils.outerWindowID 1.637 + }; 1.638 + 1.639 + // Walk over tab actors added by extensions and add them to a new ActorPool. 1.640 + let actorPool = new ActorPool(this.conn); 1.641 + this._createExtraActors(DebuggerServer.tabActorFactories, actorPool); 1.642 + if (!actorPool.isEmpty()) { 1.643 + this._tabActorPool = actorPool; 1.644 + this.conn.addActorPool(this._tabActorPool); 1.645 + } 1.646 + 1.647 + this._appendExtraActors(response); 1.648 + return response; 1.649 + }, 1.650 + 1.651 + /** 1.652 + * Called when the actor is removed from the connection. 1.653 + */ 1.654 + disconnect: function BTA_disconnect() { 1.655 + this._detach(); 1.656 + this._extraActors = null; 1.657 + this._exited = true; 1.658 + }, 1.659 + 1.660 + /** 1.661 + * Called by the root actor when the underlying tab is closed. 1.662 + */ 1.663 + exit: function BTA_exit() { 1.664 + if (this.exited) { 1.665 + return; 1.666 + } 1.667 + 1.668 + if (this._detach()) { 1.669 + this.conn.send({ from: this.actorID, 1.670 + type: "tabDetached" }); 1.671 + } 1.672 + 1.673 + this._exited = true; 1.674 + }, 1.675 + 1.676 + /* Support for DebuggerServer.addTabActor. */ 1.677 + _createExtraActors: createExtraActors, 1.678 + _appendExtraActors: appendExtraActors, 1.679 + 1.680 + /** 1.681 + * Does the actual work of attching to a tab. 1.682 + */ 1.683 + _attach: function BTA_attach() { 1.684 + if (this._attached) { 1.685 + return; 1.686 + } 1.687 + 1.688 + // Create a pool for tab-lifetime actors. 1.689 + dbg_assert(!this._tabPool, "Shouldn't have a tab pool if we weren't attached."); 1.690 + this._tabPool = new ActorPool(this.conn); 1.691 + this.conn.addActorPool(this._tabPool); 1.692 + 1.693 + // ... and a pool for context-lifetime actors. 1.694 + this._pushContext(); 1.695 + 1.696 + this._progressListener = new DebuggerProgressListener(this); 1.697 + this._progressListener.watch(this.docShell); 1.698 + 1.699 + this._attached = true; 1.700 + }, 1.701 + 1.702 + /** 1.703 + * Creates a thread actor and a pool for context-lifetime actors. It then sets 1.704 + * up the content window for debugging. 1.705 + */ 1.706 + _pushContext: function BTA_pushContext() { 1.707 + dbg_assert(!this._contextPool, "Can't push multiple contexts"); 1.708 + 1.709 + this._contextPool = new ActorPool(this.conn); 1.710 + this.conn.addActorPool(this._contextPool); 1.711 + 1.712 + this.threadActor = new ThreadActor(this, this.window); 1.713 + this._contextPool.addActor(this.threadActor); 1.714 + }, 1.715 + 1.716 + /** 1.717 + * Exits the current thread actor and removes the context-lifetime actor pool. 1.718 + * The content window is no longer being debugged after this call. 1.719 + */ 1.720 + _popContext: function BTA_popContext() { 1.721 + dbg_assert(!!this._contextPool, "No context to pop."); 1.722 + 1.723 + this.conn.removeActorPool(this._contextPool); 1.724 + this._contextPool = null; 1.725 + this.threadActor.exit(); 1.726 + this.threadActor = null; 1.727 + }, 1.728 + 1.729 + /** 1.730 + * Does the actual work of detaching from a tab. 1.731 + * 1.732 + * @returns false if the tab wasn't attached or true of detahing succeeds. 1.733 + */ 1.734 + _detach: function BTA_detach() { 1.735 + if (!this.attached) { 1.736 + return false; 1.737 + } 1.738 + 1.739 + // Check for docShell availability, as it can be already gone 1.740 + // during Firefox shutdown. 1.741 + if (this.docShell) { 1.742 + this._progressListener.unwatch(this.docShell); 1.743 + } 1.744 + this._progressListener = null; 1.745 + 1.746 + this._popContext(); 1.747 + 1.748 + // Shut down actors that belong to this tab's pool. 1.749 + this.conn.removeActorPool(this._tabPool); 1.750 + this._tabPool = null; 1.751 + if (this._tabActorPool) { 1.752 + this.conn.removeActorPool(this._tabActorPool); 1.753 + this._tabActorPool = null; 1.754 + } 1.755 + 1.756 + this._attached = false; 1.757 + return true; 1.758 + }, 1.759 + 1.760 + // Protocol Request Handlers 1.761 + 1.762 + onAttach: function BTA_onAttach(aRequest) { 1.763 + if (this.exited) { 1.764 + return { type: "exited" }; 1.765 + } 1.766 + 1.767 + this._attach(); 1.768 + 1.769 + return { 1.770 + type: "tabAttached", 1.771 + threadActor: this.threadActor.actorID, 1.772 + cacheEnabled: this._getCacheEnabled(), 1.773 + javascriptEnabled: this._getJavascriptEnabled(), 1.774 + traits: this.traits, 1.775 + }; 1.776 + }, 1.777 + 1.778 + onDetach: function BTA_onDetach(aRequest) { 1.779 + if (!this._detach()) { 1.780 + return { error: "wrongState" }; 1.781 + } 1.782 + 1.783 + return { type: "detached" }; 1.784 + }, 1.785 + 1.786 + /** 1.787 + * Reload the page in this tab. 1.788 + */ 1.789 + onReload: function(aRequest) { 1.790 + // Wait a tick so that the response packet can be dispatched before the 1.791 + // subsequent navigation event packet. 1.792 + Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => { 1.793 + this.window.location.reload(); 1.794 + }, "TabActor.prototype.onReload's delayed body"), 0); 1.795 + return {}; 1.796 + }, 1.797 + 1.798 + /** 1.799 + * Navigate this tab to a new location 1.800 + */ 1.801 + onNavigateTo: function(aRequest) { 1.802 + // Wait a tick so that the response packet can be dispatched before the 1.803 + // subsequent navigation event packet. 1.804 + Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => { 1.805 + this.window.location = aRequest.url; 1.806 + }, "TabActor.prototype.onNavigateTo's delayed body"), 0); 1.807 + return {}; 1.808 + }, 1.809 + 1.810 + /** 1.811 + * Reconfigure options. 1.812 + */ 1.813 + onReconfigure: function (aRequest) { 1.814 + let options = aRequest.options || {}; 1.815 + 1.816 + this._toggleJsOrCache(options); 1.817 + return {}; 1.818 + }, 1.819 + 1.820 + /** 1.821 + * Handle logic to enable/disable JS/cache. 1.822 + */ 1.823 + _toggleJsOrCache: function(options) { 1.824 + // Wait a tick so that the response packet can be dispatched before the 1.825 + // subsequent navigation event packet. 1.826 + let reload = false; 1.827 + 1.828 + if (typeof options.javascriptEnabled !== "undefined" && 1.829 + options.javascriptEnabled !== this._getJavascriptEnabled()) { 1.830 + this._setJavascriptEnabled(options.javascriptEnabled); 1.831 + reload = true; 1.832 + } 1.833 + if (typeof options.cacheEnabled !== "undefined" && 1.834 + options.cacheEnabled !== this._getCacheEnabled()) { 1.835 + this._setCacheEnabled(options.cacheEnabled); 1.836 + reload = true; 1.837 + } 1.838 + 1.839 + // Reload if: 1.840 + // - there's an explicit `performReload` flag and it's true 1.841 + // - there's no `performReload` flag, but it makes sense to do so 1.842 + let hasExplicitReloadFlag = "performReload" in options; 1.843 + if ((hasExplicitReloadFlag && options.performReload) || 1.844 + (!hasExplicitReloadFlag && reload)) { 1.845 + this.onReload(); 1.846 + } 1.847 + }, 1.848 + 1.849 + /** 1.850 + * Disable or enable the cache via docShell. 1.851 + */ 1.852 + _setCacheEnabled: function(allow) { 1.853 + let enable = Ci.nsIRequest.LOAD_NORMAL; 1.854 + let disable = Ci.nsIRequest.LOAD_BYPASS_CACHE | 1.855 + Ci.nsIRequest.INHIBIT_CACHING; 1.856 + if (this.docShell) { 1.857 + this.docShell.defaultLoadFlags = allow ? enable : disable; 1.858 + } 1.859 + }, 1.860 + 1.861 + /** 1.862 + * Disable or enable JS via docShell. 1.863 + */ 1.864 + _setJavascriptEnabled: function(allow) { 1.865 + if (this.docShell) { 1.866 + this.docShell.allowJavascript = allow; 1.867 + } 1.868 + }, 1.869 + 1.870 + /** 1.871 + * Return cache allowed status. 1.872 + */ 1.873 + _getCacheEnabled: function() { 1.874 + if (!this.docShell) { 1.875 + // The tab is already closed. 1.876 + return null; 1.877 + } 1.878 + 1.879 + let disable = Ci.nsIRequest.LOAD_BYPASS_CACHE | 1.880 + Ci.nsIRequest.INHIBIT_CACHING; 1.881 + return this.docShell.defaultLoadFlags !== disable; 1.882 + }, 1.883 + 1.884 + /** 1.885 + * Return JS allowed status. 1.886 + */ 1.887 + _getJavascriptEnabled: function() { 1.888 + if (!this.docShell) { 1.889 + // The tab is already closed. 1.890 + return null; 1.891 + } 1.892 + 1.893 + return this.docShell.allowJavascript; 1.894 + }, 1.895 + 1.896 + /** 1.897 + * Prepare to enter a nested event loop by disabling debuggee events. 1.898 + */ 1.899 + preNest: function BTA_preNest() { 1.900 + if (!this.window) { 1.901 + // The tab is already closed. 1.902 + return; 1.903 + } 1.904 + let windowUtils = this.window 1.905 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.906 + .getInterface(Ci.nsIDOMWindowUtils); 1.907 + windowUtils.suppressEventHandling(true); 1.908 + windowUtils.suspendTimeouts(); 1.909 + }, 1.910 + 1.911 + /** 1.912 + * Prepare to exit a nested event loop by enabling debuggee events. 1.913 + */ 1.914 + postNest: function BTA_postNest(aNestData) { 1.915 + if (!this.window) { 1.916 + // The tab is already closed. 1.917 + return; 1.918 + } 1.919 + let windowUtils = this.window 1.920 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.921 + .getInterface(Ci.nsIDOMWindowUtils); 1.922 + windowUtils.resumeTimeouts(); 1.923 + windowUtils.suppressEventHandling(false); 1.924 + if (this._pendingNavigation) { 1.925 + this._pendingNavigation.resume(); 1.926 + this._pendingNavigation = null; 1.927 + } 1.928 + }, 1.929 + 1.930 + /** 1.931 + * Handle location changes, by clearing the previous debuggees and enabling 1.932 + * debugging, which may have been disabled temporarily by the 1.933 + * DebuggerProgressListener. 1.934 + */ 1.935 + _windowReady: function (window) { 1.936 + let isTopLevel = window == this.window; 1.937 + dumpn("window-ready: " + window.location + " isTopLevel:" + isTopLevel); 1.938 + 1.939 + events.emit(this, "window-ready", { 1.940 + window: window, 1.941 + isTopLevel: isTopLevel 1.942 + }); 1.943 + 1.944 + // TODO bug 997119: move that code to ThreadActor by listening to window-ready 1.945 + let threadActor = this.threadActor; 1.946 + if (isTopLevel) { 1.947 + threadActor.clearDebuggees(); 1.948 + if (threadActor.dbg) { 1.949 + threadActor.dbg.enabled = true; 1.950 + threadActor.global = window; 1.951 + threadActor.maybePauseOnExceptions(); 1.952 + } 1.953 + } 1.954 + 1.955 + // Refresh the debuggee list when a new window object appears (top window or 1.956 + // iframe). 1.957 + if (threadActor.attached) { 1.958 + threadActor.findGlobals(); 1.959 + } 1.960 + }, 1.961 + 1.962 + /** 1.963 + * Start notifying server codebase and client about a new document 1.964 + * being loaded in the currently targeted context. 1.965 + */ 1.966 + _willNavigate: function (window, newURI, request) { 1.967 + let isTopLevel = window == this.window; 1.968 + 1.969 + // will-navigate event needs to be dispatched synchronously, 1.970 + // by calling the listeners in the order or registration. 1.971 + // This event fires once navigation starts, 1.972 + // (all pending user prompts are dealt with), 1.973 + // but before the first request starts. 1.974 + events.emit(this, "will-navigate", { 1.975 + window: window, 1.976 + isTopLevel: isTopLevel, 1.977 + newURI: newURI, 1.978 + request: request 1.979 + }); 1.980 + 1.981 + 1.982 + // We don't do anything for inner frames in TabActor. 1.983 + // (we will only update thread actor on window-ready) 1.984 + if (!isTopLevel) { 1.985 + return; 1.986 + } 1.987 + 1.988 + // Proceed normally only if the debuggee is not paused. 1.989 + // TODO bug 997119: move that code to ThreadActor by listening to will-navigate 1.990 + let threadActor = this.threadActor; 1.991 + if (request && threadActor.state == "paused") { 1.992 + request.suspend(); 1.993 + threadActor.onResume(); 1.994 + threadActor.dbg.enabled = false; 1.995 + this._pendingNavigation = request; 1.996 + } 1.997 + threadActor.disableAllBreakpoints(); 1.998 + 1.999 + this.conn.send({ 1.1000 + from: this.actorID, 1.1001 + type: "tabNavigated", 1.1002 + url: newURI, 1.1003 + nativeConsoleAPI: true, 1.1004 + state: "start" 1.1005 + }); 1.1006 + }, 1.1007 + 1.1008 + /** 1.1009 + * Notify server and client about a new document done loading in the current 1.1010 + * targeted context. 1.1011 + */ 1.1012 + _navigate: function (window) { 1.1013 + let isTopLevel = window == this.window; 1.1014 + 1.1015 + // navigate event needs to be dispatched synchronously, 1.1016 + // by calling the listeners in the order or registration. 1.1017 + // This event is fired once the document is loaded, 1.1018 + // after the load event, it's document ready-state is 'complete'. 1.1019 + events.emit(this, "navigate", { 1.1020 + window: window, 1.1021 + isTopLevel: isTopLevel 1.1022 + }); 1.1023 + 1.1024 + // We don't do anything for inner frames in TabActor. 1.1025 + // (we will only update thread actor on window-ready) 1.1026 + if (!isTopLevel) { 1.1027 + return; 1.1028 + } 1.1029 + 1.1030 + // TODO bug 997119: move that code to ThreadActor by listening to navigate 1.1031 + let threadActor = this.threadActor; 1.1032 + if (threadActor.state == "running") { 1.1033 + threadActor.dbg.enabled = true; 1.1034 + } 1.1035 + 1.1036 + this.conn.send({ 1.1037 + from: this.actorID, 1.1038 + type: "tabNavigated", 1.1039 + url: this.url, 1.1040 + title: this.title, 1.1041 + nativeConsoleAPI: this.hasNativeConsoleAPI(this.window), 1.1042 + state: "stop" 1.1043 + }); 1.1044 + }, 1.1045 + 1.1046 + /** 1.1047 + * Tells if the window.console object is native or overwritten by script in 1.1048 + * the page. 1.1049 + * 1.1050 + * @param nsIDOMWindow aWindow 1.1051 + * The window object you want to check. 1.1052 + * @return boolean 1.1053 + * True if the window.console object is native, or false otherwise. 1.1054 + */ 1.1055 + hasNativeConsoleAPI: function BTA_hasNativeConsoleAPI(aWindow) { 1.1056 + let isNative = false; 1.1057 + try { 1.1058 + // We are very explicitly examining the "console" property of 1.1059 + // the non-Xrayed object here. 1.1060 + let console = aWindow.wrappedJSObject.console; 1.1061 + isNative = console instanceof aWindow.Console; 1.1062 + } 1.1063 + catch (ex) { } 1.1064 + return isNative; 1.1065 + } 1.1066 +}; 1.1067 + 1.1068 +/** 1.1069 + * The request types this actor can handle. 1.1070 + */ 1.1071 +TabActor.prototype.requestTypes = { 1.1072 + "attach": TabActor.prototype.onAttach, 1.1073 + "detach": TabActor.prototype.onDetach, 1.1074 + "reload": TabActor.prototype.onReload, 1.1075 + "navigateTo": TabActor.prototype.onNavigateTo, 1.1076 + "reconfigure": TabActor.prototype.onReconfigure 1.1077 +}; 1.1078 + 1.1079 +/** 1.1080 + * Creates a tab actor for handling requests to a single in-process 1.1081 + * <browser> tab. Most of the implementation comes from TabActor. 1.1082 + * 1.1083 + * @param aConnection DebuggerServerConnection 1.1084 + * The conection to the client. 1.1085 + * @param aBrowser browser 1.1086 + * The browser instance that contains this tab. 1.1087 + * @param aTabBrowser tabbrowser 1.1088 + * The tabbrowser that can receive nsIWebProgressListener events. 1.1089 + */ 1.1090 +function BrowserTabActor(aConnection, aBrowser, aTabBrowser) 1.1091 +{ 1.1092 + TabActor.call(this, aConnection, aBrowser); 1.1093 + this._browser = aBrowser; 1.1094 + this._tabbrowser = aTabBrowser; 1.1095 +} 1.1096 + 1.1097 +BrowserTabActor.prototype = Object.create(TabActor.prototype); 1.1098 + 1.1099 +BrowserTabActor.prototype.constructor = BrowserTabActor; 1.1100 + 1.1101 +Object.defineProperty(BrowserTabActor.prototype, "docShell", { 1.1102 + get: function() { 1.1103 + return this._browser.docShell; 1.1104 + }, 1.1105 + enumerable: true, 1.1106 + configurable: false 1.1107 +}); 1.1108 + 1.1109 +Object.defineProperty(BrowserTabActor.prototype, "title", { 1.1110 + get: function() { 1.1111 + let title = this.contentDocument.title || this._browser.contentTitle; 1.1112 + // If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a 1.1113 + // tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label 1.1114 + // as the title. 1.1115 + if (!title && this._tabbrowser) { 1.1116 + let tab = this._tabbrowser._getTabForContentWindow(this.window); 1.1117 + if (tab) { 1.1118 + title = tab.label; 1.1119 + } 1.1120 + } 1.1121 + return title; 1.1122 + }, 1.1123 + enumerable: true, 1.1124 + configurable: false 1.1125 +}); 1.1126 + 1.1127 +Object.defineProperty(BrowserTabActor.prototype, "browser", { 1.1128 + get: function() { 1.1129 + return this._browser; 1.1130 + }, 1.1131 + enumerable: true, 1.1132 + configurable: false 1.1133 +}); 1.1134 + 1.1135 +BrowserTabActor.prototype.disconnect = function() { 1.1136 + TabActor.prototype.disconnect.call(this); 1.1137 + this._browser = null; 1.1138 + this._tabbrowser = null; 1.1139 +}; 1.1140 + 1.1141 +BrowserTabActor.prototype.exit = function() { 1.1142 + TabActor.prototype.exit.call(this); 1.1143 + this._browser = null; 1.1144 + this._tabbrowser = null; 1.1145 +}; 1.1146 + 1.1147 +/** 1.1148 + * This actor is a shim that connects to a ContentActor in a remote 1.1149 + * browser process. All RDP packets get forwarded using the message 1.1150 + * manager. 1.1151 + * 1.1152 + * @param aConnection The main RDP connection. 1.1153 + * @param aBrowser XUL <browser> element to connect to. 1.1154 + */ 1.1155 +function RemoteBrowserTabActor(aConnection, aBrowser) 1.1156 +{ 1.1157 + this._conn = aConnection; 1.1158 + this._browser = aBrowser; 1.1159 + this._form = null; 1.1160 +} 1.1161 + 1.1162 +RemoteBrowserTabActor.prototype = { 1.1163 + connect: function() { 1.1164 + return DebuggerServer.connectToChild(this._conn, this._browser); 1.1165 + }, 1.1166 + 1.1167 + form: function() { 1.1168 + return this._form; 1.1169 + }, 1.1170 + 1.1171 + exit: function() { 1.1172 + this._browser = null; 1.1173 + }, 1.1174 +}; 1.1175 + 1.1176 +function BrowserAddonList(aConnection) 1.1177 +{ 1.1178 + this._connection = aConnection; 1.1179 + this._actorByAddonId = new Map(); 1.1180 + this._onListChanged = null; 1.1181 +} 1.1182 + 1.1183 +BrowserAddonList.prototype.getList = function() { 1.1184 + var deferred = promise.defer(); 1.1185 + AddonManager.getAllAddons((addons) => { 1.1186 + for (let addon of addons) { 1.1187 + let actor = this._actorByAddonId.get(addon.id); 1.1188 + if (!actor) { 1.1189 + actor = new BrowserAddonActor(this._connection, addon); 1.1190 + this._actorByAddonId.set(addon.id, actor); 1.1191 + } 1.1192 + } 1.1193 + deferred.resolve([actor for ([_, actor] of this._actorByAddonId)]); 1.1194 + }); 1.1195 + return deferred.promise; 1.1196 +} 1.1197 + 1.1198 +Object.defineProperty(BrowserAddonList.prototype, "onListChanged", { 1.1199 + enumerable: true, configurable: true, 1.1200 + get: function() { return this._onListChanged; }, 1.1201 + set: function(v) { 1.1202 + if (v !== null && typeof v != "function") { 1.1203 + throw Error("onListChanged property may only be set to 'null' or a function"); 1.1204 + } 1.1205 + this._onListChanged = v; 1.1206 + if (this._onListChanged) { 1.1207 + AddonManager.addAddonListener(this); 1.1208 + } else { 1.1209 + AddonManager.removeAddonListener(this); 1.1210 + } 1.1211 + } 1.1212 +}); 1.1213 + 1.1214 +BrowserAddonList.prototype.onInstalled = function (aAddon) { 1.1215 + this._onListChanged(); 1.1216 +}; 1.1217 + 1.1218 +BrowserAddonList.prototype.onUninstalled = function (aAddon) { 1.1219 + this._actorByAddonId.delete(aAddon.id); 1.1220 + this._onListChanged(); 1.1221 +}; 1.1222 + 1.1223 +function BrowserAddonActor(aConnection, aAddon) { 1.1224 + this.conn = aConnection; 1.1225 + this._addon = aAddon; 1.1226 + this._contextPool = null; 1.1227 + this._threadActor = null; 1.1228 + this._global = null; 1.1229 + AddonManager.addAddonListener(this); 1.1230 +} 1.1231 + 1.1232 +BrowserAddonActor.prototype = { 1.1233 + actorPrefix: "addon", 1.1234 + 1.1235 + get exited() { 1.1236 + return !this._addon; 1.1237 + }, 1.1238 + 1.1239 + get id() { 1.1240 + return this._addon.id; 1.1241 + }, 1.1242 + 1.1243 + get url() { 1.1244 + return this._addon.sourceURI ? this._addon.sourceURI.spec : undefined; 1.1245 + }, 1.1246 + 1.1247 + get attached() { 1.1248 + return this._threadActor; 1.1249 + }, 1.1250 + 1.1251 + get global() { 1.1252 + return this._global; 1.1253 + }, 1.1254 + 1.1255 + form: function BAA_form() { 1.1256 + dbg_assert(this.actorID, "addon should have an actorID."); 1.1257 + 1.1258 + return { 1.1259 + actor: this.actorID, 1.1260 + id: this.id, 1.1261 + name: this._addon.name, 1.1262 + url: this.url, 1.1263 + debuggable: this._addon.isDebuggable, 1.1264 + }; 1.1265 + }, 1.1266 + 1.1267 + disconnect: function BAA_disconnect() { 1.1268 + this._addon = null; 1.1269 + this._global = null; 1.1270 + AddonManager.removeAddonListener(this); 1.1271 + }, 1.1272 + 1.1273 + setOptions: function BAA_setOptions(aOptions) { 1.1274 + if ("global" in aOptions) { 1.1275 + this._global = aOptions.global; 1.1276 + } 1.1277 + }, 1.1278 + 1.1279 + onDisabled: function BAA_onDisabled(aAddon) { 1.1280 + if (aAddon != this._addon) { 1.1281 + return; 1.1282 + } 1.1283 + 1.1284 + this._global = null; 1.1285 + }, 1.1286 + 1.1287 + onUninstalled: function BAA_onUninstalled(aAddon) { 1.1288 + if (aAddon != this._addon) { 1.1289 + return; 1.1290 + } 1.1291 + 1.1292 + if (this.attached) { 1.1293 + this.onDetach(); 1.1294 + this.conn.send({ from: this.actorID, type: "tabDetached" }); 1.1295 + } 1.1296 + 1.1297 + this.disconnect(); 1.1298 + }, 1.1299 + 1.1300 + onAttach: function BAA_onAttach() { 1.1301 + if (this.exited) { 1.1302 + return { type: "exited" }; 1.1303 + } 1.1304 + 1.1305 + if (!this.attached) { 1.1306 + this._contextPool = new ActorPool(this.conn); 1.1307 + this.conn.addActorPool(this._contextPool); 1.1308 + 1.1309 + this._threadActor = new AddonThreadActor(this.conn, this, 1.1310 + this._addon.id); 1.1311 + this._contextPool.addActor(this._threadActor); 1.1312 + } 1.1313 + 1.1314 + return { type: "tabAttached", threadActor: this._threadActor.actorID }; 1.1315 + }, 1.1316 + 1.1317 + onDetach: function BAA_onDetach() { 1.1318 + if (!this.attached) { 1.1319 + return { error: "wrongState" }; 1.1320 + } 1.1321 + 1.1322 + this.conn.removeActorPool(this._contextPool); 1.1323 + this._contextPool = null; 1.1324 + 1.1325 + this._threadActor = null; 1.1326 + 1.1327 + return { type: "detached" }; 1.1328 + }, 1.1329 + 1.1330 + preNest: function() { 1.1331 + let e = Services.wm.getEnumerator(null); 1.1332 + while (e.hasMoreElements()) { 1.1333 + let win = e.getNext(); 1.1334 + let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) 1.1335 + .getInterface(Ci.nsIDOMWindowUtils); 1.1336 + windowUtils.suppressEventHandling(true); 1.1337 + windowUtils.suspendTimeouts(); 1.1338 + } 1.1339 + }, 1.1340 + 1.1341 + postNest: function() { 1.1342 + let e = Services.wm.getEnumerator(null); 1.1343 + while (e.hasMoreElements()) { 1.1344 + let win = e.getNext(); 1.1345 + let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) 1.1346 + .getInterface(Ci.nsIDOMWindowUtils); 1.1347 + windowUtils.resumeTimeouts(); 1.1348 + windowUtils.suppressEventHandling(false); 1.1349 + } 1.1350 + } 1.1351 +}; 1.1352 + 1.1353 +BrowserAddonActor.prototype.requestTypes = { 1.1354 + "attach": BrowserAddonActor.prototype.onAttach, 1.1355 + "detach": BrowserAddonActor.prototype.onDetach 1.1356 +}; 1.1357 + 1.1358 +/** 1.1359 + * The DebuggerProgressListener object is an nsIWebProgressListener which 1.1360 + * handles onStateChange events for the inspected browser. If the user tries to 1.1361 + * navigate away from a paused page, the listener makes sure that the debuggee 1.1362 + * is resumed before the navigation begins. 1.1363 + * 1.1364 + * @param TabActor aTabActor 1.1365 + * The tab actor associated with this listener. 1.1366 + */ 1.1367 +function DebuggerProgressListener(aTabActor) { 1.1368 + this._tabActor = aTabActor; 1.1369 + this._onWindowCreated = this.onWindowCreated.bind(this); 1.1370 +} 1.1371 + 1.1372 +DebuggerProgressListener.prototype = { 1.1373 + QueryInterface: XPCOMUtils.generateQI([ 1.1374 + Ci.nsIWebProgressListener, 1.1375 + Ci.nsISupportsWeakReference, 1.1376 + Ci.nsISupports, 1.1377 + ]), 1.1378 + 1.1379 + watch: function DPL_watch(docShell) { 1.1380 + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) 1.1381 + .getInterface(Ci.nsIWebProgress); 1.1382 + webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATUS | 1.1383 + Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | 1.1384 + Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT); 1.1385 + 1.1386 + // TODO: fix docShell.chromeEventHandler in child processes! 1.1387 + let chromeEventHandler = docShell.chromeEventHandler || 1.1388 + docShell.QueryInterface(Ci.nsIInterfaceRequestor) 1.1389 + .getInterface(Ci.nsIContentFrameMessageManager); 1.1390 + 1.1391 + // Watch for globals being created in this docshell tree. 1.1392 + chromeEventHandler.addEventListener("DOMWindowCreated", 1.1393 + this._onWindowCreated, true); 1.1394 + chromeEventHandler.addEventListener("pageshow", 1.1395 + this._onWindowCreated, true); 1.1396 + }, 1.1397 + 1.1398 + unwatch: function DPL_unwatch(docShell) { 1.1399 + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) 1.1400 + .getInterface(Ci.nsIWebProgress); 1.1401 + webProgress.removeProgressListener(this); 1.1402 + 1.1403 + // TODO: fix docShell.chromeEventHandler in child processes! 1.1404 + let chromeEventHandler = docShell.chromeEventHandler || 1.1405 + docShell.QueryInterface(Ci.nsIInterfaceRequestor) 1.1406 + .getInterface(Ci.nsIContentFrameMessageManager); 1.1407 + chromeEventHandler.removeEventListener("DOMWindowCreated", 1.1408 + this._onWindowCreated, true); 1.1409 + chromeEventHandler.removeEventListener("pageshow", 1.1410 + this._onWindowCreated, true); 1.1411 + }, 1.1412 + 1.1413 + onWindowCreated: 1.1414 + DevToolsUtils.makeInfallible(function DPL_onWindowCreated(evt) { 1.1415 + // Ignore any event if the tab actor isn't attached. 1.1416 + if (!this._tabActor.attached) { 1.1417 + return; 1.1418 + } 1.1419 + 1.1420 + // pageshow events for non-persisted pages have already been handled by a 1.1421 + // prior DOMWindowCreated event. 1.1422 + if (evt.type == "pageshow" && !evt.persisted) { 1.1423 + return; 1.1424 + } 1.1425 + 1.1426 + let window = evt.target.defaultView; 1.1427 + this._tabActor._windowReady(window); 1.1428 + }, "DebuggerProgressListener.prototype.onWindowCreated"), 1.1429 + 1.1430 + onStateChange: 1.1431 + DevToolsUtils.makeInfallible(function DPL_onStateChange(aProgress, aRequest, aFlag, aStatus) { 1.1432 + // Ignore any event if the tab actor isn't attached. 1.1433 + if (!this._tabActor.attached) { 1.1434 + return; 1.1435 + } 1.1436 + 1.1437 + let isStart = aFlag & Ci.nsIWebProgressListener.STATE_START; 1.1438 + let isStop = aFlag & Ci.nsIWebProgressListener.STATE_STOP; 1.1439 + let isDocument = aFlag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT; 1.1440 + let isWindow = aFlag & Ci.nsIWebProgressListener.STATE_IS_WINDOW; 1.1441 + 1.1442 + let window = aProgress.DOMWindow; 1.1443 + if (isDocument && isStart) { 1.1444 + let newURI = aRequest instanceof Ci.nsIChannel ? aRequest.URI.spec : null; 1.1445 + this._tabActor._willNavigate(window, newURI, aRequest); 1.1446 + } 1.1447 + if (isWindow && isStop) { 1.1448 + this._tabActor._navigate(window); 1.1449 + } 1.1450 + }, "DebuggerProgressListener.prototype.onStateChange") 1.1451 +};