Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | "use strict"; |
michael@0 | 8 | |
michael@0 | 9 | let {Ci,Cu} = require("chrome"); |
michael@0 | 10 | let {createExtraActors, appendExtraActors} = require("devtools/server/actors/common"); |
michael@0 | 11 | let DevToolsUtils = require("devtools/toolkit/DevToolsUtils"); |
michael@0 | 12 | |
michael@0 | 13 | let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); |
michael@0 | 14 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 15 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 16 | |
michael@0 | 17 | XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); |
michael@0 | 18 | |
michael@0 | 19 | // Assumptions on events module: |
michael@0 | 20 | // events needs to be dispatched synchronously, |
michael@0 | 21 | // by calling the listeners in the order or registration. |
michael@0 | 22 | XPCOMUtils.defineLazyGetter(this, "events", () => { |
michael@0 | 23 | return devtools.require("sdk/event/core"); |
michael@0 | 24 | }); |
michael@0 | 25 | |
michael@0 | 26 | // Also depends on following symbols, shared by common scope with main.js: |
michael@0 | 27 | // DebuggerServer, CommonCreateExtraActors, CommonAppendExtraActors, ActorPool, |
michael@0 | 28 | // ThreadActor |
michael@0 | 29 | |
michael@0 | 30 | /** |
michael@0 | 31 | * Browser-specific actors. |
michael@0 | 32 | */ |
michael@0 | 33 | |
michael@0 | 34 | /** |
michael@0 | 35 | * Yield all windows of type |aWindowType|, from the oldest window to the |
michael@0 | 36 | * youngest, using nsIWindowMediator::getEnumerator. We're usually |
michael@0 | 37 | * interested in "navigator:browser" windows. |
michael@0 | 38 | */ |
michael@0 | 39 | function allAppShellDOMWindows(aWindowType) |
michael@0 | 40 | { |
michael@0 | 41 | let e = Services.wm.getEnumerator(aWindowType); |
michael@0 | 42 | while (e.hasMoreElements()) { |
michael@0 | 43 | yield e.getNext(); |
michael@0 | 44 | } |
michael@0 | 45 | } |
michael@0 | 46 | |
michael@0 | 47 | /** |
michael@0 | 48 | * Retrieve the window type of the top-level window |aWindow|. |
michael@0 | 49 | */ |
michael@0 | 50 | function appShellDOMWindowType(aWindow) { |
michael@0 | 51 | /* This is what nsIWindowMediator's enumerator checks. */ |
michael@0 | 52 | return aWindow.document.documentElement.getAttribute('windowtype'); |
michael@0 | 53 | } |
michael@0 | 54 | |
michael@0 | 55 | /** |
michael@0 | 56 | * Send Debugger:Shutdown events to all "navigator:browser" windows. |
michael@0 | 57 | */ |
michael@0 | 58 | function sendShutdownEvent() { |
michael@0 | 59 | for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) { |
michael@0 | 60 | let evt = win.document.createEvent("Event"); |
michael@0 | 61 | evt.initEvent("Debugger:Shutdown", true, false); |
michael@0 | 62 | win.document.documentElement.dispatchEvent(evt); |
michael@0 | 63 | } |
michael@0 | 64 | } |
michael@0 | 65 | |
michael@0 | 66 | /** |
michael@0 | 67 | * Construct a root actor appropriate for use in a server running in a |
michael@0 | 68 | * browser. The returned root actor: |
michael@0 | 69 | * - respects the factories registered with DebuggerServer.addGlobalActor, |
michael@0 | 70 | * - uses a BrowserTabList to supply tab actors, |
michael@0 | 71 | * - sends all navigator:browser window documents a Debugger:Shutdown event |
michael@0 | 72 | * when it exits. |
michael@0 | 73 | * |
michael@0 | 74 | * * @param aConnection DebuggerServerConnection |
michael@0 | 75 | * The conection to the client. |
michael@0 | 76 | */ |
michael@0 | 77 | function createRootActor(aConnection) |
michael@0 | 78 | { |
michael@0 | 79 | return new RootActor(aConnection, |
michael@0 | 80 | { |
michael@0 | 81 | tabList: new BrowserTabList(aConnection), |
michael@0 | 82 | addonList: new BrowserAddonList(aConnection), |
michael@0 | 83 | globalActorFactories: DebuggerServer.globalActorFactories, |
michael@0 | 84 | onShutdown: sendShutdownEvent |
michael@0 | 85 | }); |
michael@0 | 86 | } |
michael@0 | 87 | |
michael@0 | 88 | /** |
michael@0 | 89 | * A live list of BrowserTabActors representing the current browser tabs, |
michael@0 | 90 | * to be provided to the root actor to answer 'listTabs' requests. |
michael@0 | 91 | * |
michael@0 | 92 | * This object also takes care of listening for TabClose events and |
michael@0 | 93 | * onCloseWindow notifications, and exiting the BrowserTabActors concerned. |
michael@0 | 94 | * |
michael@0 | 95 | * (See the documentation for RootActor for the definition of the "live |
michael@0 | 96 | * list" interface.) |
michael@0 | 97 | * |
michael@0 | 98 | * @param aConnection DebuggerServerConnection |
michael@0 | 99 | * The connection in which this list's tab actors may participate. |
michael@0 | 100 | * |
michael@0 | 101 | * Some notes: |
michael@0 | 102 | * |
michael@0 | 103 | * This constructor is specific to the desktop browser environment; it |
michael@0 | 104 | * maintains the tab list by tracking XUL windows and their XUL documents' |
michael@0 | 105 | * "tabbrowser", "tab", and "browser" elements. What's entailed in maintaining |
michael@0 | 106 | * an accurate list of open tabs in this context? |
michael@0 | 107 | * |
michael@0 | 108 | * - Opening and closing XUL windows: |
michael@0 | 109 | * |
michael@0 | 110 | * An nsIWindowMediatorListener is notified when new XUL windows (i.e., desktop |
michael@0 | 111 | * windows) are opened and closed. It is not notified of individual content |
michael@0 | 112 | * browser tabs coming and going within such a XUL window. That seems |
michael@0 | 113 | * reasonable enough; it's concerned with XUL windows, not tab elements in the |
michael@0 | 114 | * window's XUL document. |
michael@0 | 115 | * |
michael@0 | 116 | * However, even if we attach TabOpen and TabClose event listeners to each XUL |
michael@0 | 117 | * window as soon as it is created: |
michael@0 | 118 | * |
michael@0 | 119 | * - we do not receive a TabOpen event for the initial empty tab of a new XUL |
michael@0 | 120 | * window; and |
michael@0 | 121 | * |
michael@0 | 122 | * - we do not receive TabClose events for the tabs of a XUL window that has |
michael@0 | 123 | * been closed. |
michael@0 | 124 | * |
michael@0 | 125 | * This means that TabOpen and TabClose events alone are not sufficient to |
michael@0 | 126 | * maintain an accurate list of live tabs and mark tab actors as closed |
michael@0 | 127 | * promptly. Our nsIWindowMediatorListener onCloseWindow handler must find and |
michael@0 | 128 | * exit all actors for tabs that were in the closing window. |
michael@0 | 129 | * |
michael@0 | 130 | * Since this is a bit hairy, we don't make each individual attached tab actor |
michael@0 | 131 | * responsible for noticing when it has been closed; we watch for that, and |
michael@0 | 132 | * promise to call each actor's 'exit' method when it's closed, regardless of |
michael@0 | 133 | * how we learn the news. |
michael@0 | 134 | * |
michael@0 | 135 | * - nsIWindowMediator locks |
michael@0 | 136 | * |
michael@0 | 137 | * nsIWindowMediator holds a lock protecting its list of top-level windows |
michael@0 | 138 | * while it calls nsIWindowMediatorListener methods. nsIWindowMediator's |
michael@0 | 139 | * GetEnumerator method also tries to acquire that lock. Thus, enumerating |
michael@0 | 140 | * windows from within a listener method deadlocks (bug 873589). Rah. One |
michael@0 | 141 | * can sometimes work around this by leaving the enumeration for a later |
michael@0 | 142 | * tick. |
michael@0 | 143 | * |
michael@0 | 144 | * - Dragging tabs between windows: |
michael@0 | 145 | * |
michael@0 | 146 | * When a tab is dragged from one desktop window to another, we receive a |
michael@0 | 147 | * TabOpen event for the new tab, and a TabClose event for the old tab; tab XUL |
michael@0 | 148 | * elements do not really move from one document to the other (although their |
michael@0 | 149 | * linked browser's content window objects do). |
michael@0 | 150 | * |
michael@0 | 151 | * However, while we could thus assume that each tab stays with the XUL window |
michael@0 | 152 | * it belonged to when it was created, I'm not sure this is behavior one should |
michael@0 | 153 | * rely upon. When a XUL window is closed, we take the less efficient, more |
michael@0 | 154 | * conservative approach of simply searching the entire table for actors that |
michael@0 | 155 | * belong to the closing XUL window, rather than trying to somehow track which |
michael@0 | 156 | * XUL window each tab belongs to. |
michael@0 | 157 | */ |
michael@0 | 158 | function BrowserTabList(aConnection) |
michael@0 | 159 | { |
michael@0 | 160 | this._connection = aConnection; |
michael@0 | 161 | |
michael@0 | 162 | /* |
michael@0 | 163 | * The XUL document of a tabbed browser window has "tab" elements, whose |
michael@0 | 164 | * 'linkedBrowser' JavaScript properties are "browser" elements; those |
michael@0 | 165 | * browsers' 'contentWindow' properties are wrappers on the tabs' content |
michael@0 | 166 | * window objects. |
michael@0 | 167 | * |
michael@0 | 168 | * This map's keys are "browser" XUL elements; it maps each browser element |
michael@0 | 169 | * to the tab actor we've created for its content window, if we've created |
michael@0 | 170 | * one. This map serves several roles: |
michael@0 | 171 | * |
michael@0 | 172 | * - During iteration, we use it to find actors we've created previously. |
michael@0 | 173 | * |
michael@0 | 174 | * - On a TabClose event, we use it to find the tab's actor and exit it. |
michael@0 | 175 | * |
michael@0 | 176 | * - When the onCloseWindow handler is called, we iterate over it to find all |
michael@0 | 177 | * tabs belonging to the closing XUL window, and exit them. |
michael@0 | 178 | * |
michael@0 | 179 | * - When it's empty, and the onListChanged hook is null, we know we can |
michael@0 | 180 | * stop listening for events and notifications. |
michael@0 | 181 | * |
michael@0 | 182 | * We listen for TabClose events and onCloseWindow notifications in order to |
michael@0 | 183 | * send onListChanged notifications, but also to tell actors when their |
michael@0 | 184 | * referent has gone away and remove entries for dead browsers from this map. |
michael@0 | 185 | * If that code is working properly, neither this map nor the actors in it |
michael@0 | 186 | * should ever hold dead tabs alive. |
michael@0 | 187 | */ |
michael@0 | 188 | this._actorByBrowser = new Map(); |
michael@0 | 189 | |
michael@0 | 190 | /* The current onListChanged handler, or null. */ |
michael@0 | 191 | this._onListChanged = null; |
michael@0 | 192 | |
michael@0 | 193 | /* |
michael@0 | 194 | * True if we've been iterated over since we last called our onListChanged |
michael@0 | 195 | * hook. |
michael@0 | 196 | */ |
michael@0 | 197 | this._mustNotify = false; |
michael@0 | 198 | |
michael@0 | 199 | /* True if we're testing, and should throw if consistency checks fail. */ |
michael@0 | 200 | this._testing = false; |
michael@0 | 201 | } |
michael@0 | 202 | |
michael@0 | 203 | BrowserTabList.prototype.constructor = BrowserTabList; |
michael@0 | 204 | |
michael@0 | 205 | |
michael@0 | 206 | /** |
michael@0 | 207 | * Get the selected browser for the given navigator:browser window. |
michael@0 | 208 | * @private |
michael@0 | 209 | * @param aWindow nsIChromeWindow |
michael@0 | 210 | * The navigator:browser window for which you want the selected browser. |
michael@0 | 211 | * @return nsIDOMElement|null |
michael@0 | 212 | * The currently selected xul:browser element, if any. Note that the |
michael@0 | 213 | * browser window might not be loaded yet - the function will return |
michael@0 | 214 | * |null| in such cases. |
michael@0 | 215 | */ |
michael@0 | 216 | BrowserTabList.prototype._getSelectedBrowser = function(aWindow) { |
michael@0 | 217 | return aWindow.gBrowser ? aWindow.gBrowser.selectedBrowser : null; |
michael@0 | 218 | }; |
michael@0 | 219 | |
michael@0 | 220 | BrowserTabList.prototype._getChildren = function(aWindow) { |
michael@0 | 221 | return aWindow.gBrowser.browsers; |
michael@0 | 222 | }; |
michael@0 | 223 | |
michael@0 | 224 | BrowserTabList.prototype.getList = function() { |
michael@0 | 225 | let topXULWindow = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType); |
michael@0 | 226 | |
michael@0 | 227 | // As a sanity check, make sure all the actors presently in our map get |
michael@0 | 228 | // picked up when we iterate over all windows' tabs. |
michael@0 | 229 | let initialMapSize = this._actorByBrowser.size; |
michael@0 | 230 | let foundCount = 0; |
michael@0 | 231 | |
michael@0 | 232 | // To avoid mysterious behavior if tabs are closed or opened mid-iteration, |
michael@0 | 233 | // we update the map first, and then make a second pass over it to yield |
michael@0 | 234 | // the actors. Thus, the sequence yielded is always a snapshot of the |
michael@0 | 235 | // actors that were live when we began the iteration. |
michael@0 | 236 | |
michael@0 | 237 | let actorPromises = []; |
michael@0 | 238 | |
michael@0 | 239 | // Iterate over all navigator:browser XUL windows. |
michael@0 | 240 | for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) { |
michael@0 | 241 | let selectedBrowser = this._getSelectedBrowser(win); |
michael@0 | 242 | if (!selectedBrowser) { |
michael@0 | 243 | continue; |
michael@0 | 244 | } |
michael@0 | 245 | |
michael@0 | 246 | // For each tab in this XUL window, ensure that we have an actor for |
michael@0 | 247 | // it, reusing existing actors where possible. We actually iterate |
michael@0 | 248 | // over 'browser' XUL elements, and BrowserTabActor uses |
michael@0 | 249 | // browser.contentWindow as the debuggee global. |
michael@0 | 250 | for (let browser of this._getChildren(win)) { |
michael@0 | 251 | // Do we have an existing actor for this browser? If not, create one. |
michael@0 | 252 | let actor = this._actorByBrowser.get(browser); |
michael@0 | 253 | if (actor) { |
michael@0 | 254 | actorPromises.push(promise.resolve(actor)); |
michael@0 | 255 | foundCount++; |
michael@0 | 256 | } else if (browser.isRemoteBrowser) { |
michael@0 | 257 | actor = new RemoteBrowserTabActor(this._connection, browser); |
michael@0 | 258 | this._actorByBrowser.set(browser, actor); |
michael@0 | 259 | let promise = actor.connect().then((form) => { |
michael@0 | 260 | actor._form = form; |
michael@0 | 261 | return actor; |
michael@0 | 262 | }); |
michael@0 | 263 | actorPromises.push(promise); |
michael@0 | 264 | } else { |
michael@0 | 265 | actor = new BrowserTabActor(this._connection, browser, win.gBrowser); |
michael@0 | 266 | this._actorByBrowser.set(browser, actor); |
michael@0 | 267 | actorPromises.push(promise.resolve(actor)); |
michael@0 | 268 | } |
michael@0 | 269 | |
michael@0 | 270 | // Set the 'selected' properties on all actors correctly. |
michael@0 | 271 | actor.selected = (win === topXULWindow && browser === selectedBrowser); |
michael@0 | 272 | } |
michael@0 | 273 | } |
michael@0 | 274 | |
michael@0 | 275 | if (this._testing && initialMapSize !== foundCount) |
michael@0 | 276 | throw Error("_actorByBrowser map contained actors for dead tabs"); |
michael@0 | 277 | |
michael@0 | 278 | this._mustNotify = true; |
michael@0 | 279 | this._checkListening(); |
michael@0 | 280 | |
michael@0 | 281 | return promise.all(actorPromises); |
michael@0 | 282 | }; |
michael@0 | 283 | |
michael@0 | 284 | Object.defineProperty(BrowserTabList.prototype, 'onListChanged', { |
michael@0 | 285 | enumerable: true, configurable:true, |
michael@0 | 286 | get: function() { return this._onListChanged; }, |
michael@0 | 287 | set: function(v) { |
michael@0 | 288 | if (v !== null && typeof v !== 'function') { |
michael@0 | 289 | throw Error("onListChanged property may only be set to 'null' or a function"); |
michael@0 | 290 | } |
michael@0 | 291 | this._onListChanged = v; |
michael@0 | 292 | this._checkListening(); |
michael@0 | 293 | } |
michael@0 | 294 | }); |
michael@0 | 295 | |
michael@0 | 296 | /** |
michael@0 | 297 | * The set of tabs has changed somehow. Call our onListChanged handler, if |
michael@0 | 298 | * one is set, and if we haven't already called it since the last iteration. |
michael@0 | 299 | */ |
michael@0 | 300 | BrowserTabList.prototype._notifyListChanged = function() { |
michael@0 | 301 | if (!this._onListChanged) |
michael@0 | 302 | return; |
michael@0 | 303 | if (this._mustNotify) { |
michael@0 | 304 | this._onListChanged(); |
michael@0 | 305 | this._mustNotify = false; |
michael@0 | 306 | } |
michael@0 | 307 | }; |
michael@0 | 308 | |
michael@0 | 309 | /** |
michael@0 | 310 | * Exit |aActor|, belonging to |aBrowser|, and notify the onListChanged |
michael@0 | 311 | * handle if needed. |
michael@0 | 312 | */ |
michael@0 | 313 | BrowserTabList.prototype._handleActorClose = function(aActor, aBrowser) { |
michael@0 | 314 | if (this._testing) { |
michael@0 | 315 | if (this._actorByBrowser.get(aBrowser) !== aActor) { |
michael@0 | 316 | throw Error("BrowserTabActor not stored in map under given browser"); |
michael@0 | 317 | } |
michael@0 | 318 | if (aActor.browser !== aBrowser) { |
michael@0 | 319 | throw Error("actor's browser and map key don't match"); |
michael@0 | 320 | } |
michael@0 | 321 | } |
michael@0 | 322 | |
michael@0 | 323 | this._actorByBrowser.delete(aBrowser); |
michael@0 | 324 | aActor.exit(); |
michael@0 | 325 | |
michael@0 | 326 | this._notifyListChanged(); |
michael@0 | 327 | this._checkListening(); |
michael@0 | 328 | }; |
michael@0 | 329 | |
michael@0 | 330 | /** |
michael@0 | 331 | * Make sure we are listening or not listening for activity elsewhere in |
michael@0 | 332 | * the browser, as appropriate. Other than setting up newly created XUL |
michael@0 | 333 | * windows, all listener / observer connection and disconnection should |
michael@0 | 334 | * happen here. |
michael@0 | 335 | */ |
michael@0 | 336 | BrowserTabList.prototype._checkListening = function() { |
michael@0 | 337 | /* |
michael@0 | 338 | * If we have an onListChanged handler that we haven't sent an announcement |
michael@0 | 339 | * to since the last iteration, we need to watch for tab creation. |
michael@0 | 340 | * |
michael@0 | 341 | * Oddly, we don't need to watch for 'close' events here. If our actor list |
michael@0 | 342 | * is empty, then either it was empty the last time we iterated, and no |
michael@0 | 343 | * close events are possible, or it was not empty the last time we |
michael@0 | 344 | * iterated, but all the actors have since been closed, and we must have |
michael@0 | 345 | * sent a notification already when they closed. |
michael@0 | 346 | */ |
michael@0 | 347 | this._listenForEventsIf(this._onListChanged && this._mustNotify, |
michael@0 | 348 | "_listeningForTabOpen", ["TabOpen", "TabSelect"]); |
michael@0 | 349 | |
michael@0 | 350 | /* If we have live actors, we need to be ready to mark them dead. */ |
michael@0 | 351 | this._listenForEventsIf(this._actorByBrowser.size > 0, |
michael@0 | 352 | "_listeningForTabClose", ["TabClose"]); |
michael@0 | 353 | |
michael@0 | 354 | /* |
michael@0 | 355 | * We must listen to the window mediator in either case, since that's the |
michael@0 | 356 | * only way to find out about tabs that come and go when top-level windows |
michael@0 | 357 | * are opened and closed. |
michael@0 | 358 | */ |
michael@0 | 359 | this._listenToMediatorIf((this._onListChanged && this._mustNotify) || |
michael@0 | 360 | (this._actorByBrowser.size > 0)); |
michael@0 | 361 | }; |
michael@0 | 362 | |
michael@0 | 363 | /* |
michael@0 | 364 | * Add or remove event listeners for all XUL windows. |
michael@0 | 365 | * |
michael@0 | 366 | * @param aShouldListen boolean |
michael@0 | 367 | * True if we should add event handlers; false if we should remove them. |
michael@0 | 368 | * @param aGuard string |
michael@0 | 369 | * The name of a guard property of 'this', indicating whether we're |
michael@0 | 370 | * already listening for those events. |
michael@0 | 371 | * @param aEventNames array of strings |
michael@0 | 372 | * An array of event names. |
michael@0 | 373 | */ |
michael@0 | 374 | BrowserTabList.prototype._listenForEventsIf = function(aShouldListen, aGuard, aEventNames) { |
michael@0 | 375 | if (!aShouldListen !== !this[aGuard]) { |
michael@0 | 376 | let op = aShouldListen ? "addEventListener" : "removeEventListener"; |
michael@0 | 377 | for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) { |
michael@0 | 378 | for (let name of aEventNames) { |
michael@0 | 379 | win[op](name, this, false); |
michael@0 | 380 | } |
michael@0 | 381 | } |
michael@0 | 382 | this[aGuard] = aShouldListen; |
michael@0 | 383 | } |
michael@0 | 384 | }; |
michael@0 | 385 | |
michael@0 | 386 | /** |
michael@0 | 387 | * Implement nsIDOMEventListener. |
michael@0 | 388 | */ |
michael@0 | 389 | BrowserTabList.prototype.handleEvent = DevToolsUtils.makeInfallible(function(aEvent) { |
michael@0 | 390 | switch (aEvent.type) { |
michael@0 | 391 | case "TabOpen": |
michael@0 | 392 | case "TabSelect": |
michael@0 | 393 | /* Don't create a new actor; iterate will take care of that. Just notify. */ |
michael@0 | 394 | this._notifyListChanged(); |
michael@0 | 395 | this._checkListening(); |
michael@0 | 396 | break; |
michael@0 | 397 | case "TabClose": |
michael@0 | 398 | let browser = aEvent.target.linkedBrowser; |
michael@0 | 399 | let actor = this._actorByBrowser.get(browser); |
michael@0 | 400 | if (actor) { |
michael@0 | 401 | this._handleActorClose(actor, browser); |
michael@0 | 402 | } |
michael@0 | 403 | break; |
michael@0 | 404 | } |
michael@0 | 405 | }, "BrowserTabList.prototype.handleEvent"); |
michael@0 | 406 | |
michael@0 | 407 | /* |
michael@0 | 408 | * If |aShouldListen| is true, ensure we've registered a listener with the |
michael@0 | 409 | * window mediator. Otherwise, ensure we haven't registered a listener. |
michael@0 | 410 | */ |
michael@0 | 411 | BrowserTabList.prototype._listenToMediatorIf = function(aShouldListen) { |
michael@0 | 412 | if (!aShouldListen !== !this._listeningToMediator) { |
michael@0 | 413 | let op = aShouldListen ? "addListener" : "removeListener"; |
michael@0 | 414 | Services.wm[op](this); |
michael@0 | 415 | this._listeningToMediator = aShouldListen; |
michael@0 | 416 | } |
michael@0 | 417 | }; |
michael@0 | 418 | |
michael@0 | 419 | /** |
michael@0 | 420 | * nsIWindowMediatorListener implementation. |
michael@0 | 421 | * |
michael@0 | 422 | * See _onTabClosed for explanation of why we needn't actually tweak any |
michael@0 | 423 | * actors or tables here. |
michael@0 | 424 | * |
michael@0 | 425 | * An nsIWindowMediatorListener's methods get passed all sorts of windows; we |
michael@0 | 426 | * only care about the tab containers. Those have 'getBrowser' methods. |
michael@0 | 427 | */ |
michael@0 | 428 | BrowserTabList.prototype.onWindowTitleChange = () => { }; |
michael@0 | 429 | |
michael@0 | 430 | BrowserTabList.prototype.onOpenWindow = DevToolsUtils.makeInfallible(function(aWindow) { |
michael@0 | 431 | let handleLoad = DevToolsUtils.makeInfallible(() => { |
michael@0 | 432 | /* We don't want any further load events from this window. */ |
michael@0 | 433 | aWindow.removeEventListener("load", handleLoad, false); |
michael@0 | 434 | |
michael@0 | 435 | if (appShellDOMWindowType(aWindow) !== DebuggerServer.chromeWindowType) |
michael@0 | 436 | return; |
michael@0 | 437 | |
michael@0 | 438 | // Listen for future tab activity. |
michael@0 | 439 | if (this._listeningForTabOpen) { |
michael@0 | 440 | aWindow.addEventListener("TabOpen", this, false); |
michael@0 | 441 | aWindow.addEventListener("TabSelect", this, false); |
michael@0 | 442 | } |
michael@0 | 443 | if (this._listeningForTabClose) { |
michael@0 | 444 | aWindow.addEventListener("TabClose", this, false); |
michael@0 | 445 | } |
michael@0 | 446 | |
michael@0 | 447 | // As explained above, we will not receive a TabOpen event for this |
michael@0 | 448 | // document's initial tab, so we must notify our client of the new tab |
michael@0 | 449 | // this will have. |
michael@0 | 450 | this._notifyListChanged(); |
michael@0 | 451 | }); |
michael@0 | 452 | |
michael@0 | 453 | /* |
michael@0 | 454 | * You can hardly do anything at all with a XUL window at this point; it |
michael@0 | 455 | * doesn't even have its document yet. Wait until its document has |
michael@0 | 456 | * loaded, and then see what we've got. This also avoids |
michael@0 | 457 | * nsIWindowMediator enumeration from within listeners (bug 873589). |
michael@0 | 458 | */ |
michael@0 | 459 | aWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 460 | .getInterface(Ci.nsIDOMWindow); |
michael@0 | 461 | |
michael@0 | 462 | aWindow.addEventListener("load", handleLoad, false); |
michael@0 | 463 | }, "BrowserTabList.prototype.onOpenWindow"); |
michael@0 | 464 | |
michael@0 | 465 | BrowserTabList.prototype.onCloseWindow = DevToolsUtils.makeInfallible(function(aWindow) { |
michael@0 | 466 | aWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 467 | .getInterface(Ci.nsIDOMWindow); |
michael@0 | 468 | |
michael@0 | 469 | if (appShellDOMWindowType(aWindow) !== DebuggerServer.chromeWindowType) |
michael@0 | 470 | return; |
michael@0 | 471 | |
michael@0 | 472 | /* |
michael@0 | 473 | * nsIWindowMediator deadlocks if you call its GetEnumerator method from |
michael@0 | 474 | * a nsIWindowMediatorListener's onCloseWindow hook (bug 873589), so |
michael@0 | 475 | * handle the close in a different tick. |
michael@0 | 476 | */ |
michael@0 | 477 | Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => { |
michael@0 | 478 | /* |
michael@0 | 479 | * Scan the entire map for actors representing tabs that were in this |
michael@0 | 480 | * top-level window, and exit them. |
michael@0 | 481 | */ |
michael@0 | 482 | for (let [browser, actor] of this._actorByBrowser) { |
michael@0 | 483 | /* The browser document of a closed window has no default view. */ |
michael@0 | 484 | if (!browser.ownerDocument.defaultView) { |
michael@0 | 485 | this._handleActorClose(actor, browser); |
michael@0 | 486 | } |
michael@0 | 487 | } |
michael@0 | 488 | }, "BrowserTabList.prototype.onCloseWindow's delayed body"), 0); |
michael@0 | 489 | }, "BrowserTabList.prototype.onCloseWindow"); |
michael@0 | 490 | |
michael@0 | 491 | /** |
michael@0 | 492 | * Creates a tab actor for handling requests to a browser tab, like |
michael@0 | 493 | * attaching and detaching. TabActor respects the actor factories |
michael@0 | 494 | * registered with DebuggerServer.addTabActor. |
michael@0 | 495 | * |
michael@0 | 496 | * This class is subclassed by BrowserTabActor and |
michael@0 | 497 | * ContentActor. Subclasses are expected to implement a getter |
michael@0 | 498 | * the docShell properties. |
michael@0 | 499 | * |
michael@0 | 500 | * @param aConnection DebuggerServerConnection |
michael@0 | 501 | * The conection to the client. |
michael@0 | 502 | * @param aChromeEventHandler |
michael@0 | 503 | * An object on which listen for DOMWindowCreated and pageshow events. |
michael@0 | 504 | */ |
michael@0 | 505 | function TabActor(aConnection) |
michael@0 | 506 | { |
michael@0 | 507 | this.conn = aConnection; |
michael@0 | 508 | this._tabActorPool = null; |
michael@0 | 509 | // A map of actor names to actor instances provided by extensions. |
michael@0 | 510 | this._extraActors = {}; |
michael@0 | 511 | this._exited = false; |
michael@0 | 512 | |
michael@0 | 513 | this.traits = { reconfigure: true }; |
michael@0 | 514 | } |
michael@0 | 515 | |
michael@0 | 516 | // XXX (bug 710213): TabActor attach/detach/exit/disconnect is a |
michael@0 | 517 | // *complete* mess, needs to be rethought asap. |
michael@0 | 518 | |
michael@0 | 519 | TabActor.prototype = { |
michael@0 | 520 | traits: null, |
michael@0 | 521 | |
michael@0 | 522 | get exited() { return this._exited; }, |
michael@0 | 523 | get attached() { return !!this._attached; }, |
michael@0 | 524 | |
michael@0 | 525 | _tabPool: null, |
michael@0 | 526 | get tabActorPool() { return this._tabPool; }, |
michael@0 | 527 | |
michael@0 | 528 | _contextPool: null, |
michael@0 | 529 | get contextActorPool() { return this._contextPool; }, |
michael@0 | 530 | |
michael@0 | 531 | _pendingNavigation: null, |
michael@0 | 532 | |
michael@0 | 533 | // A constant prefix that will be used to form the actor ID by the server. |
michael@0 | 534 | actorPrefix: "tab", |
michael@0 | 535 | |
michael@0 | 536 | /** |
michael@0 | 537 | * An object on which listen for DOMWindowCreated and pageshow events. |
michael@0 | 538 | */ |
michael@0 | 539 | get chromeEventHandler() { |
michael@0 | 540 | // TODO: bug 992778, fix docShell.chromeEventHandler in child processes |
michael@0 | 541 | return this.docShell.chromeEventHandler || |
michael@0 | 542 | this.docShell.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 543 | .getInterface(Ci.nsIContentFrameMessageManager); |
michael@0 | 544 | }, |
michael@0 | 545 | |
michael@0 | 546 | /** |
michael@0 | 547 | * Getter for the nsIMessageManager associated to the tab. |
michael@0 | 548 | */ |
michael@0 | 549 | get messageManager() { |
michael@0 | 550 | return this.docShell |
michael@0 | 551 | .QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 552 | .getInterface(Ci.nsIContentFrameMessageManager); |
michael@0 | 553 | }, |
michael@0 | 554 | |
michael@0 | 555 | /** |
michael@0 | 556 | * Getter for the tab's doc shell. |
michael@0 | 557 | */ |
michael@0 | 558 | get docShell() { |
michael@0 | 559 | throw "The docShell getter should be implemented by a subclass of TabActor"; |
michael@0 | 560 | }, |
michael@0 | 561 | |
michael@0 | 562 | /** |
michael@0 | 563 | * Getter for the tab content's DOM window. |
michael@0 | 564 | */ |
michael@0 | 565 | get window() { |
michael@0 | 566 | return this.docShell |
michael@0 | 567 | .QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 568 | .getInterface(Ci.nsIDOMWindow); |
michael@0 | 569 | }, |
michael@0 | 570 | |
michael@0 | 571 | /** |
michael@0 | 572 | * Getter for the nsIWebProgress for watching this window. |
michael@0 | 573 | */ |
michael@0 | 574 | get webProgress() { |
michael@0 | 575 | return this.docShell |
michael@0 | 576 | .QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 577 | .getInterface(Ci.nsIWebProgress); |
michael@0 | 578 | }, |
michael@0 | 579 | |
michael@0 | 580 | /** |
michael@0 | 581 | * Getter for the nsIWebNavigation for the tab. |
michael@0 | 582 | */ |
michael@0 | 583 | get webNavigation() { |
michael@0 | 584 | return this.docShell |
michael@0 | 585 | .QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 586 | .getInterface(Ci.nsIWebNavigation); |
michael@0 | 587 | }, |
michael@0 | 588 | |
michael@0 | 589 | /** |
michael@0 | 590 | * Getter for the tab's document. |
michael@0 | 591 | */ |
michael@0 | 592 | get contentDocument() { |
michael@0 | 593 | return this.webNavigation.document; |
michael@0 | 594 | }, |
michael@0 | 595 | |
michael@0 | 596 | /** |
michael@0 | 597 | * Getter for the tab title. |
michael@0 | 598 | * @return string |
michael@0 | 599 | * Tab title. |
michael@0 | 600 | */ |
michael@0 | 601 | get title() { |
michael@0 | 602 | return this.contentDocument.contentTitle; |
michael@0 | 603 | }, |
michael@0 | 604 | |
michael@0 | 605 | /** |
michael@0 | 606 | * Getter for the tab URL. |
michael@0 | 607 | * @return string |
michael@0 | 608 | * Tab URL. |
michael@0 | 609 | */ |
michael@0 | 610 | get url() { |
michael@0 | 611 | if (this.webNavigation.currentURI) { |
michael@0 | 612 | return this.webNavigation.currentURI.spec; |
michael@0 | 613 | } |
michael@0 | 614 | // Abrupt closing of the browser window may leave callbacks without a |
michael@0 | 615 | // currentURI. |
michael@0 | 616 | return null; |
michael@0 | 617 | }, |
michael@0 | 618 | |
michael@0 | 619 | form: function BTA_form() { |
michael@0 | 620 | dbg_assert(!this.exited, |
michael@0 | 621 | "grip() shouldn't be called on exited browser actor."); |
michael@0 | 622 | dbg_assert(this.actorID, |
michael@0 | 623 | "tab should have an actorID."); |
michael@0 | 624 | |
michael@0 | 625 | let windowUtils = this.window |
michael@0 | 626 | .QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 627 | .getInterface(Ci.nsIDOMWindowUtils); |
michael@0 | 628 | |
michael@0 | 629 | let response = { |
michael@0 | 630 | actor: this.actorID, |
michael@0 | 631 | title: this.title, |
michael@0 | 632 | url: this.url, |
michael@0 | 633 | outerWindowID: windowUtils.outerWindowID |
michael@0 | 634 | }; |
michael@0 | 635 | |
michael@0 | 636 | // Walk over tab actors added by extensions and add them to a new ActorPool. |
michael@0 | 637 | let actorPool = new ActorPool(this.conn); |
michael@0 | 638 | this._createExtraActors(DebuggerServer.tabActorFactories, actorPool); |
michael@0 | 639 | if (!actorPool.isEmpty()) { |
michael@0 | 640 | this._tabActorPool = actorPool; |
michael@0 | 641 | this.conn.addActorPool(this._tabActorPool); |
michael@0 | 642 | } |
michael@0 | 643 | |
michael@0 | 644 | this._appendExtraActors(response); |
michael@0 | 645 | return response; |
michael@0 | 646 | }, |
michael@0 | 647 | |
michael@0 | 648 | /** |
michael@0 | 649 | * Called when the actor is removed from the connection. |
michael@0 | 650 | */ |
michael@0 | 651 | disconnect: function BTA_disconnect() { |
michael@0 | 652 | this._detach(); |
michael@0 | 653 | this._extraActors = null; |
michael@0 | 654 | this._exited = true; |
michael@0 | 655 | }, |
michael@0 | 656 | |
michael@0 | 657 | /** |
michael@0 | 658 | * Called by the root actor when the underlying tab is closed. |
michael@0 | 659 | */ |
michael@0 | 660 | exit: function BTA_exit() { |
michael@0 | 661 | if (this.exited) { |
michael@0 | 662 | return; |
michael@0 | 663 | } |
michael@0 | 664 | |
michael@0 | 665 | if (this._detach()) { |
michael@0 | 666 | this.conn.send({ from: this.actorID, |
michael@0 | 667 | type: "tabDetached" }); |
michael@0 | 668 | } |
michael@0 | 669 | |
michael@0 | 670 | this._exited = true; |
michael@0 | 671 | }, |
michael@0 | 672 | |
michael@0 | 673 | /* Support for DebuggerServer.addTabActor. */ |
michael@0 | 674 | _createExtraActors: createExtraActors, |
michael@0 | 675 | _appendExtraActors: appendExtraActors, |
michael@0 | 676 | |
michael@0 | 677 | /** |
michael@0 | 678 | * Does the actual work of attching to a tab. |
michael@0 | 679 | */ |
michael@0 | 680 | _attach: function BTA_attach() { |
michael@0 | 681 | if (this._attached) { |
michael@0 | 682 | return; |
michael@0 | 683 | } |
michael@0 | 684 | |
michael@0 | 685 | // Create a pool for tab-lifetime actors. |
michael@0 | 686 | dbg_assert(!this._tabPool, "Shouldn't have a tab pool if we weren't attached."); |
michael@0 | 687 | this._tabPool = new ActorPool(this.conn); |
michael@0 | 688 | this.conn.addActorPool(this._tabPool); |
michael@0 | 689 | |
michael@0 | 690 | // ... and a pool for context-lifetime actors. |
michael@0 | 691 | this._pushContext(); |
michael@0 | 692 | |
michael@0 | 693 | this._progressListener = new DebuggerProgressListener(this); |
michael@0 | 694 | this._progressListener.watch(this.docShell); |
michael@0 | 695 | |
michael@0 | 696 | this._attached = true; |
michael@0 | 697 | }, |
michael@0 | 698 | |
michael@0 | 699 | /** |
michael@0 | 700 | * Creates a thread actor and a pool for context-lifetime actors. It then sets |
michael@0 | 701 | * up the content window for debugging. |
michael@0 | 702 | */ |
michael@0 | 703 | _pushContext: function BTA_pushContext() { |
michael@0 | 704 | dbg_assert(!this._contextPool, "Can't push multiple contexts"); |
michael@0 | 705 | |
michael@0 | 706 | this._contextPool = new ActorPool(this.conn); |
michael@0 | 707 | this.conn.addActorPool(this._contextPool); |
michael@0 | 708 | |
michael@0 | 709 | this.threadActor = new ThreadActor(this, this.window); |
michael@0 | 710 | this._contextPool.addActor(this.threadActor); |
michael@0 | 711 | }, |
michael@0 | 712 | |
michael@0 | 713 | /** |
michael@0 | 714 | * Exits the current thread actor and removes the context-lifetime actor pool. |
michael@0 | 715 | * The content window is no longer being debugged after this call. |
michael@0 | 716 | */ |
michael@0 | 717 | _popContext: function BTA_popContext() { |
michael@0 | 718 | dbg_assert(!!this._contextPool, "No context to pop."); |
michael@0 | 719 | |
michael@0 | 720 | this.conn.removeActorPool(this._contextPool); |
michael@0 | 721 | this._contextPool = null; |
michael@0 | 722 | this.threadActor.exit(); |
michael@0 | 723 | this.threadActor = null; |
michael@0 | 724 | }, |
michael@0 | 725 | |
michael@0 | 726 | /** |
michael@0 | 727 | * Does the actual work of detaching from a tab. |
michael@0 | 728 | * |
michael@0 | 729 | * @returns false if the tab wasn't attached or true of detahing succeeds. |
michael@0 | 730 | */ |
michael@0 | 731 | _detach: function BTA_detach() { |
michael@0 | 732 | if (!this.attached) { |
michael@0 | 733 | return false; |
michael@0 | 734 | } |
michael@0 | 735 | |
michael@0 | 736 | // Check for docShell availability, as it can be already gone |
michael@0 | 737 | // during Firefox shutdown. |
michael@0 | 738 | if (this.docShell) { |
michael@0 | 739 | this._progressListener.unwatch(this.docShell); |
michael@0 | 740 | } |
michael@0 | 741 | this._progressListener = null; |
michael@0 | 742 | |
michael@0 | 743 | this._popContext(); |
michael@0 | 744 | |
michael@0 | 745 | // Shut down actors that belong to this tab's pool. |
michael@0 | 746 | this.conn.removeActorPool(this._tabPool); |
michael@0 | 747 | this._tabPool = null; |
michael@0 | 748 | if (this._tabActorPool) { |
michael@0 | 749 | this.conn.removeActorPool(this._tabActorPool); |
michael@0 | 750 | this._tabActorPool = null; |
michael@0 | 751 | } |
michael@0 | 752 | |
michael@0 | 753 | this._attached = false; |
michael@0 | 754 | return true; |
michael@0 | 755 | }, |
michael@0 | 756 | |
michael@0 | 757 | // Protocol Request Handlers |
michael@0 | 758 | |
michael@0 | 759 | onAttach: function BTA_onAttach(aRequest) { |
michael@0 | 760 | if (this.exited) { |
michael@0 | 761 | return { type: "exited" }; |
michael@0 | 762 | } |
michael@0 | 763 | |
michael@0 | 764 | this._attach(); |
michael@0 | 765 | |
michael@0 | 766 | return { |
michael@0 | 767 | type: "tabAttached", |
michael@0 | 768 | threadActor: this.threadActor.actorID, |
michael@0 | 769 | cacheEnabled: this._getCacheEnabled(), |
michael@0 | 770 | javascriptEnabled: this._getJavascriptEnabled(), |
michael@0 | 771 | traits: this.traits, |
michael@0 | 772 | }; |
michael@0 | 773 | }, |
michael@0 | 774 | |
michael@0 | 775 | onDetach: function BTA_onDetach(aRequest) { |
michael@0 | 776 | if (!this._detach()) { |
michael@0 | 777 | return { error: "wrongState" }; |
michael@0 | 778 | } |
michael@0 | 779 | |
michael@0 | 780 | return { type: "detached" }; |
michael@0 | 781 | }, |
michael@0 | 782 | |
michael@0 | 783 | /** |
michael@0 | 784 | * Reload the page in this tab. |
michael@0 | 785 | */ |
michael@0 | 786 | onReload: function(aRequest) { |
michael@0 | 787 | // Wait a tick so that the response packet can be dispatched before the |
michael@0 | 788 | // subsequent navigation event packet. |
michael@0 | 789 | Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => { |
michael@0 | 790 | this.window.location.reload(); |
michael@0 | 791 | }, "TabActor.prototype.onReload's delayed body"), 0); |
michael@0 | 792 | return {}; |
michael@0 | 793 | }, |
michael@0 | 794 | |
michael@0 | 795 | /** |
michael@0 | 796 | * Navigate this tab to a new location |
michael@0 | 797 | */ |
michael@0 | 798 | onNavigateTo: function(aRequest) { |
michael@0 | 799 | // Wait a tick so that the response packet can be dispatched before the |
michael@0 | 800 | // subsequent navigation event packet. |
michael@0 | 801 | Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => { |
michael@0 | 802 | this.window.location = aRequest.url; |
michael@0 | 803 | }, "TabActor.prototype.onNavigateTo's delayed body"), 0); |
michael@0 | 804 | return {}; |
michael@0 | 805 | }, |
michael@0 | 806 | |
michael@0 | 807 | /** |
michael@0 | 808 | * Reconfigure options. |
michael@0 | 809 | */ |
michael@0 | 810 | onReconfigure: function (aRequest) { |
michael@0 | 811 | let options = aRequest.options || {}; |
michael@0 | 812 | |
michael@0 | 813 | this._toggleJsOrCache(options); |
michael@0 | 814 | return {}; |
michael@0 | 815 | }, |
michael@0 | 816 | |
michael@0 | 817 | /** |
michael@0 | 818 | * Handle logic to enable/disable JS/cache. |
michael@0 | 819 | */ |
michael@0 | 820 | _toggleJsOrCache: function(options) { |
michael@0 | 821 | // Wait a tick so that the response packet can be dispatched before the |
michael@0 | 822 | // subsequent navigation event packet. |
michael@0 | 823 | let reload = false; |
michael@0 | 824 | |
michael@0 | 825 | if (typeof options.javascriptEnabled !== "undefined" && |
michael@0 | 826 | options.javascriptEnabled !== this._getJavascriptEnabled()) { |
michael@0 | 827 | this._setJavascriptEnabled(options.javascriptEnabled); |
michael@0 | 828 | reload = true; |
michael@0 | 829 | } |
michael@0 | 830 | if (typeof options.cacheEnabled !== "undefined" && |
michael@0 | 831 | options.cacheEnabled !== this._getCacheEnabled()) { |
michael@0 | 832 | this._setCacheEnabled(options.cacheEnabled); |
michael@0 | 833 | reload = true; |
michael@0 | 834 | } |
michael@0 | 835 | |
michael@0 | 836 | // Reload if: |
michael@0 | 837 | // - there's an explicit `performReload` flag and it's true |
michael@0 | 838 | // - there's no `performReload` flag, but it makes sense to do so |
michael@0 | 839 | let hasExplicitReloadFlag = "performReload" in options; |
michael@0 | 840 | if ((hasExplicitReloadFlag && options.performReload) || |
michael@0 | 841 | (!hasExplicitReloadFlag && reload)) { |
michael@0 | 842 | this.onReload(); |
michael@0 | 843 | } |
michael@0 | 844 | }, |
michael@0 | 845 | |
michael@0 | 846 | /** |
michael@0 | 847 | * Disable or enable the cache via docShell. |
michael@0 | 848 | */ |
michael@0 | 849 | _setCacheEnabled: function(allow) { |
michael@0 | 850 | let enable = Ci.nsIRequest.LOAD_NORMAL; |
michael@0 | 851 | let disable = Ci.nsIRequest.LOAD_BYPASS_CACHE | |
michael@0 | 852 | Ci.nsIRequest.INHIBIT_CACHING; |
michael@0 | 853 | if (this.docShell) { |
michael@0 | 854 | this.docShell.defaultLoadFlags = allow ? enable : disable; |
michael@0 | 855 | } |
michael@0 | 856 | }, |
michael@0 | 857 | |
michael@0 | 858 | /** |
michael@0 | 859 | * Disable or enable JS via docShell. |
michael@0 | 860 | */ |
michael@0 | 861 | _setJavascriptEnabled: function(allow) { |
michael@0 | 862 | if (this.docShell) { |
michael@0 | 863 | this.docShell.allowJavascript = allow; |
michael@0 | 864 | } |
michael@0 | 865 | }, |
michael@0 | 866 | |
michael@0 | 867 | /** |
michael@0 | 868 | * Return cache allowed status. |
michael@0 | 869 | */ |
michael@0 | 870 | _getCacheEnabled: function() { |
michael@0 | 871 | if (!this.docShell) { |
michael@0 | 872 | // The tab is already closed. |
michael@0 | 873 | return null; |
michael@0 | 874 | } |
michael@0 | 875 | |
michael@0 | 876 | let disable = Ci.nsIRequest.LOAD_BYPASS_CACHE | |
michael@0 | 877 | Ci.nsIRequest.INHIBIT_CACHING; |
michael@0 | 878 | return this.docShell.defaultLoadFlags !== disable; |
michael@0 | 879 | }, |
michael@0 | 880 | |
michael@0 | 881 | /** |
michael@0 | 882 | * Return JS allowed status. |
michael@0 | 883 | */ |
michael@0 | 884 | _getJavascriptEnabled: function() { |
michael@0 | 885 | if (!this.docShell) { |
michael@0 | 886 | // The tab is already closed. |
michael@0 | 887 | return null; |
michael@0 | 888 | } |
michael@0 | 889 | |
michael@0 | 890 | return this.docShell.allowJavascript; |
michael@0 | 891 | }, |
michael@0 | 892 | |
michael@0 | 893 | /** |
michael@0 | 894 | * Prepare to enter a nested event loop by disabling debuggee events. |
michael@0 | 895 | */ |
michael@0 | 896 | preNest: function BTA_preNest() { |
michael@0 | 897 | if (!this.window) { |
michael@0 | 898 | // The tab is already closed. |
michael@0 | 899 | return; |
michael@0 | 900 | } |
michael@0 | 901 | let windowUtils = this.window |
michael@0 | 902 | .QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 903 | .getInterface(Ci.nsIDOMWindowUtils); |
michael@0 | 904 | windowUtils.suppressEventHandling(true); |
michael@0 | 905 | windowUtils.suspendTimeouts(); |
michael@0 | 906 | }, |
michael@0 | 907 | |
michael@0 | 908 | /** |
michael@0 | 909 | * Prepare to exit a nested event loop by enabling debuggee events. |
michael@0 | 910 | */ |
michael@0 | 911 | postNest: function BTA_postNest(aNestData) { |
michael@0 | 912 | if (!this.window) { |
michael@0 | 913 | // The tab is already closed. |
michael@0 | 914 | return; |
michael@0 | 915 | } |
michael@0 | 916 | let windowUtils = this.window |
michael@0 | 917 | .QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 918 | .getInterface(Ci.nsIDOMWindowUtils); |
michael@0 | 919 | windowUtils.resumeTimeouts(); |
michael@0 | 920 | windowUtils.suppressEventHandling(false); |
michael@0 | 921 | if (this._pendingNavigation) { |
michael@0 | 922 | this._pendingNavigation.resume(); |
michael@0 | 923 | this._pendingNavigation = null; |
michael@0 | 924 | } |
michael@0 | 925 | }, |
michael@0 | 926 | |
michael@0 | 927 | /** |
michael@0 | 928 | * Handle location changes, by clearing the previous debuggees and enabling |
michael@0 | 929 | * debugging, which may have been disabled temporarily by the |
michael@0 | 930 | * DebuggerProgressListener. |
michael@0 | 931 | */ |
michael@0 | 932 | _windowReady: function (window) { |
michael@0 | 933 | let isTopLevel = window == this.window; |
michael@0 | 934 | dumpn("window-ready: " + window.location + " isTopLevel:" + isTopLevel); |
michael@0 | 935 | |
michael@0 | 936 | events.emit(this, "window-ready", { |
michael@0 | 937 | window: window, |
michael@0 | 938 | isTopLevel: isTopLevel |
michael@0 | 939 | }); |
michael@0 | 940 | |
michael@0 | 941 | // TODO bug 997119: move that code to ThreadActor by listening to window-ready |
michael@0 | 942 | let threadActor = this.threadActor; |
michael@0 | 943 | if (isTopLevel) { |
michael@0 | 944 | threadActor.clearDebuggees(); |
michael@0 | 945 | if (threadActor.dbg) { |
michael@0 | 946 | threadActor.dbg.enabled = true; |
michael@0 | 947 | threadActor.global = window; |
michael@0 | 948 | threadActor.maybePauseOnExceptions(); |
michael@0 | 949 | } |
michael@0 | 950 | } |
michael@0 | 951 | |
michael@0 | 952 | // Refresh the debuggee list when a new window object appears (top window or |
michael@0 | 953 | // iframe). |
michael@0 | 954 | if (threadActor.attached) { |
michael@0 | 955 | threadActor.findGlobals(); |
michael@0 | 956 | } |
michael@0 | 957 | }, |
michael@0 | 958 | |
michael@0 | 959 | /** |
michael@0 | 960 | * Start notifying server codebase and client about a new document |
michael@0 | 961 | * being loaded in the currently targeted context. |
michael@0 | 962 | */ |
michael@0 | 963 | _willNavigate: function (window, newURI, request) { |
michael@0 | 964 | let isTopLevel = window == this.window; |
michael@0 | 965 | |
michael@0 | 966 | // will-navigate event needs to be dispatched synchronously, |
michael@0 | 967 | // by calling the listeners in the order or registration. |
michael@0 | 968 | // This event fires once navigation starts, |
michael@0 | 969 | // (all pending user prompts are dealt with), |
michael@0 | 970 | // but before the first request starts. |
michael@0 | 971 | events.emit(this, "will-navigate", { |
michael@0 | 972 | window: window, |
michael@0 | 973 | isTopLevel: isTopLevel, |
michael@0 | 974 | newURI: newURI, |
michael@0 | 975 | request: request |
michael@0 | 976 | }); |
michael@0 | 977 | |
michael@0 | 978 | |
michael@0 | 979 | // We don't do anything for inner frames in TabActor. |
michael@0 | 980 | // (we will only update thread actor on window-ready) |
michael@0 | 981 | if (!isTopLevel) { |
michael@0 | 982 | return; |
michael@0 | 983 | } |
michael@0 | 984 | |
michael@0 | 985 | // Proceed normally only if the debuggee is not paused. |
michael@0 | 986 | // TODO bug 997119: move that code to ThreadActor by listening to will-navigate |
michael@0 | 987 | let threadActor = this.threadActor; |
michael@0 | 988 | if (request && threadActor.state == "paused") { |
michael@0 | 989 | request.suspend(); |
michael@0 | 990 | threadActor.onResume(); |
michael@0 | 991 | threadActor.dbg.enabled = false; |
michael@0 | 992 | this._pendingNavigation = request; |
michael@0 | 993 | } |
michael@0 | 994 | threadActor.disableAllBreakpoints(); |
michael@0 | 995 | |
michael@0 | 996 | this.conn.send({ |
michael@0 | 997 | from: this.actorID, |
michael@0 | 998 | type: "tabNavigated", |
michael@0 | 999 | url: newURI, |
michael@0 | 1000 | nativeConsoleAPI: true, |
michael@0 | 1001 | state: "start" |
michael@0 | 1002 | }); |
michael@0 | 1003 | }, |
michael@0 | 1004 | |
michael@0 | 1005 | /** |
michael@0 | 1006 | * Notify server and client about a new document done loading in the current |
michael@0 | 1007 | * targeted context. |
michael@0 | 1008 | */ |
michael@0 | 1009 | _navigate: function (window) { |
michael@0 | 1010 | let isTopLevel = window == this.window; |
michael@0 | 1011 | |
michael@0 | 1012 | // navigate event needs to be dispatched synchronously, |
michael@0 | 1013 | // by calling the listeners in the order or registration. |
michael@0 | 1014 | // This event is fired once the document is loaded, |
michael@0 | 1015 | // after the load event, it's document ready-state is 'complete'. |
michael@0 | 1016 | events.emit(this, "navigate", { |
michael@0 | 1017 | window: window, |
michael@0 | 1018 | isTopLevel: isTopLevel |
michael@0 | 1019 | }); |
michael@0 | 1020 | |
michael@0 | 1021 | // We don't do anything for inner frames in TabActor. |
michael@0 | 1022 | // (we will only update thread actor on window-ready) |
michael@0 | 1023 | if (!isTopLevel) { |
michael@0 | 1024 | return; |
michael@0 | 1025 | } |
michael@0 | 1026 | |
michael@0 | 1027 | // TODO bug 997119: move that code to ThreadActor by listening to navigate |
michael@0 | 1028 | let threadActor = this.threadActor; |
michael@0 | 1029 | if (threadActor.state == "running") { |
michael@0 | 1030 | threadActor.dbg.enabled = true; |
michael@0 | 1031 | } |
michael@0 | 1032 | |
michael@0 | 1033 | this.conn.send({ |
michael@0 | 1034 | from: this.actorID, |
michael@0 | 1035 | type: "tabNavigated", |
michael@0 | 1036 | url: this.url, |
michael@0 | 1037 | title: this.title, |
michael@0 | 1038 | nativeConsoleAPI: this.hasNativeConsoleAPI(this.window), |
michael@0 | 1039 | state: "stop" |
michael@0 | 1040 | }); |
michael@0 | 1041 | }, |
michael@0 | 1042 | |
michael@0 | 1043 | /** |
michael@0 | 1044 | * Tells if the window.console object is native or overwritten by script in |
michael@0 | 1045 | * the page. |
michael@0 | 1046 | * |
michael@0 | 1047 | * @param nsIDOMWindow aWindow |
michael@0 | 1048 | * The window object you want to check. |
michael@0 | 1049 | * @return boolean |
michael@0 | 1050 | * True if the window.console object is native, or false otherwise. |
michael@0 | 1051 | */ |
michael@0 | 1052 | hasNativeConsoleAPI: function BTA_hasNativeConsoleAPI(aWindow) { |
michael@0 | 1053 | let isNative = false; |
michael@0 | 1054 | try { |
michael@0 | 1055 | // We are very explicitly examining the "console" property of |
michael@0 | 1056 | // the non-Xrayed object here. |
michael@0 | 1057 | let console = aWindow.wrappedJSObject.console; |
michael@0 | 1058 | isNative = console instanceof aWindow.Console; |
michael@0 | 1059 | } |
michael@0 | 1060 | catch (ex) { } |
michael@0 | 1061 | return isNative; |
michael@0 | 1062 | } |
michael@0 | 1063 | }; |
michael@0 | 1064 | |
michael@0 | 1065 | /** |
michael@0 | 1066 | * The request types this actor can handle. |
michael@0 | 1067 | */ |
michael@0 | 1068 | TabActor.prototype.requestTypes = { |
michael@0 | 1069 | "attach": TabActor.prototype.onAttach, |
michael@0 | 1070 | "detach": TabActor.prototype.onDetach, |
michael@0 | 1071 | "reload": TabActor.prototype.onReload, |
michael@0 | 1072 | "navigateTo": TabActor.prototype.onNavigateTo, |
michael@0 | 1073 | "reconfigure": TabActor.prototype.onReconfigure |
michael@0 | 1074 | }; |
michael@0 | 1075 | |
michael@0 | 1076 | /** |
michael@0 | 1077 | * Creates a tab actor for handling requests to a single in-process |
michael@0 | 1078 | * <browser> tab. Most of the implementation comes from TabActor. |
michael@0 | 1079 | * |
michael@0 | 1080 | * @param aConnection DebuggerServerConnection |
michael@0 | 1081 | * The conection to the client. |
michael@0 | 1082 | * @param aBrowser browser |
michael@0 | 1083 | * The browser instance that contains this tab. |
michael@0 | 1084 | * @param aTabBrowser tabbrowser |
michael@0 | 1085 | * The tabbrowser that can receive nsIWebProgressListener events. |
michael@0 | 1086 | */ |
michael@0 | 1087 | function BrowserTabActor(aConnection, aBrowser, aTabBrowser) |
michael@0 | 1088 | { |
michael@0 | 1089 | TabActor.call(this, aConnection, aBrowser); |
michael@0 | 1090 | this._browser = aBrowser; |
michael@0 | 1091 | this._tabbrowser = aTabBrowser; |
michael@0 | 1092 | } |
michael@0 | 1093 | |
michael@0 | 1094 | BrowserTabActor.prototype = Object.create(TabActor.prototype); |
michael@0 | 1095 | |
michael@0 | 1096 | BrowserTabActor.prototype.constructor = BrowserTabActor; |
michael@0 | 1097 | |
michael@0 | 1098 | Object.defineProperty(BrowserTabActor.prototype, "docShell", { |
michael@0 | 1099 | get: function() { |
michael@0 | 1100 | return this._browser.docShell; |
michael@0 | 1101 | }, |
michael@0 | 1102 | enumerable: true, |
michael@0 | 1103 | configurable: false |
michael@0 | 1104 | }); |
michael@0 | 1105 | |
michael@0 | 1106 | Object.defineProperty(BrowserTabActor.prototype, "title", { |
michael@0 | 1107 | get: function() { |
michael@0 | 1108 | let title = this.contentDocument.title || this._browser.contentTitle; |
michael@0 | 1109 | // If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a |
michael@0 | 1110 | // tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label |
michael@0 | 1111 | // as the title. |
michael@0 | 1112 | if (!title && this._tabbrowser) { |
michael@0 | 1113 | let tab = this._tabbrowser._getTabForContentWindow(this.window); |
michael@0 | 1114 | if (tab) { |
michael@0 | 1115 | title = tab.label; |
michael@0 | 1116 | } |
michael@0 | 1117 | } |
michael@0 | 1118 | return title; |
michael@0 | 1119 | }, |
michael@0 | 1120 | enumerable: true, |
michael@0 | 1121 | configurable: false |
michael@0 | 1122 | }); |
michael@0 | 1123 | |
michael@0 | 1124 | Object.defineProperty(BrowserTabActor.prototype, "browser", { |
michael@0 | 1125 | get: function() { |
michael@0 | 1126 | return this._browser; |
michael@0 | 1127 | }, |
michael@0 | 1128 | enumerable: true, |
michael@0 | 1129 | configurable: false |
michael@0 | 1130 | }); |
michael@0 | 1131 | |
michael@0 | 1132 | BrowserTabActor.prototype.disconnect = function() { |
michael@0 | 1133 | TabActor.prototype.disconnect.call(this); |
michael@0 | 1134 | this._browser = null; |
michael@0 | 1135 | this._tabbrowser = null; |
michael@0 | 1136 | }; |
michael@0 | 1137 | |
michael@0 | 1138 | BrowserTabActor.prototype.exit = function() { |
michael@0 | 1139 | TabActor.prototype.exit.call(this); |
michael@0 | 1140 | this._browser = null; |
michael@0 | 1141 | this._tabbrowser = null; |
michael@0 | 1142 | }; |
michael@0 | 1143 | |
michael@0 | 1144 | /** |
michael@0 | 1145 | * This actor is a shim that connects to a ContentActor in a remote |
michael@0 | 1146 | * browser process. All RDP packets get forwarded using the message |
michael@0 | 1147 | * manager. |
michael@0 | 1148 | * |
michael@0 | 1149 | * @param aConnection The main RDP connection. |
michael@0 | 1150 | * @param aBrowser XUL <browser> element to connect to. |
michael@0 | 1151 | */ |
michael@0 | 1152 | function RemoteBrowserTabActor(aConnection, aBrowser) |
michael@0 | 1153 | { |
michael@0 | 1154 | this._conn = aConnection; |
michael@0 | 1155 | this._browser = aBrowser; |
michael@0 | 1156 | this._form = null; |
michael@0 | 1157 | } |
michael@0 | 1158 | |
michael@0 | 1159 | RemoteBrowserTabActor.prototype = { |
michael@0 | 1160 | connect: function() { |
michael@0 | 1161 | return DebuggerServer.connectToChild(this._conn, this._browser); |
michael@0 | 1162 | }, |
michael@0 | 1163 | |
michael@0 | 1164 | form: function() { |
michael@0 | 1165 | return this._form; |
michael@0 | 1166 | }, |
michael@0 | 1167 | |
michael@0 | 1168 | exit: function() { |
michael@0 | 1169 | this._browser = null; |
michael@0 | 1170 | }, |
michael@0 | 1171 | }; |
michael@0 | 1172 | |
michael@0 | 1173 | function BrowserAddonList(aConnection) |
michael@0 | 1174 | { |
michael@0 | 1175 | this._connection = aConnection; |
michael@0 | 1176 | this._actorByAddonId = new Map(); |
michael@0 | 1177 | this._onListChanged = null; |
michael@0 | 1178 | } |
michael@0 | 1179 | |
michael@0 | 1180 | BrowserAddonList.prototype.getList = function() { |
michael@0 | 1181 | var deferred = promise.defer(); |
michael@0 | 1182 | AddonManager.getAllAddons((addons) => { |
michael@0 | 1183 | for (let addon of addons) { |
michael@0 | 1184 | let actor = this._actorByAddonId.get(addon.id); |
michael@0 | 1185 | if (!actor) { |
michael@0 | 1186 | actor = new BrowserAddonActor(this._connection, addon); |
michael@0 | 1187 | this._actorByAddonId.set(addon.id, actor); |
michael@0 | 1188 | } |
michael@0 | 1189 | } |
michael@0 | 1190 | deferred.resolve([actor for ([_, actor] of this._actorByAddonId)]); |
michael@0 | 1191 | }); |
michael@0 | 1192 | return deferred.promise; |
michael@0 | 1193 | } |
michael@0 | 1194 | |
michael@0 | 1195 | Object.defineProperty(BrowserAddonList.prototype, "onListChanged", { |
michael@0 | 1196 | enumerable: true, configurable: true, |
michael@0 | 1197 | get: function() { return this._onListChanged; }, |
michael@0 | 1198 | set: function(v) { |
michael@0 | 1199 | if (v !== null && typeof v != "function") { |
michael@0 | 1200 | throw Error("onListChanged property may only be set to 'null' or a function"); |
michael@0 | 1201 | } |
michael@0 | 1202 | this._onListChanged = v; |
michael@0 | 1203 | if (this._onListChanged) { |
michael@0 | 1204 | AddonManager.addAddonListener(this); |
michael@0 | 1205 | } else { |
michael@0 | 1206 | AddonManager.removeAddonListener(this); |
michael@0 | 1207 | } |
michael@0 | 1208 | } |
michael@0 | 1209 | }); |
michael@0 | 1210 | |
michael@0 | 1211 | BrowserAddonList.prototype.onInstalled = function (aAddon) { |
michael@0 | 1212 | this._onListChanged(); |
michael@0 | 1213 | }; |
michael@0 | 1214 | |
michael@0 | 1215 | BrowserAddonList.prototype.onUninstalled = function (aAddon) { |
michael@0 | 1216 | this._actorByAddonId.delete(aAddon.id); |
michael@0 | 1217 | this._onListChanged(); |
michael@0 | 1218 | }; |
michael@0 | 1219 | |
michael@0 | 1220 | function BrowserAddonActor(aConnection, aAddon) { |
michael@0 | 1221 | this.conn = aConnection; |
michael@0 | 1222 | this._addon = aAddon; |
michael@0 | 1223 | this._contextPool = null; |
michael@0 | 1224 | this._threadActor = null; |
michael@0 | 1225 | this._global = null; |
michael@0 | 1226 | AddonManager.addAddonListener(this); |
michael@0 | 1227 | } |
michael@0 | 1228 | |
michael@0 | 1229 | BrowserAddonActor.prototype = { |
michael@0 | 1230 | actorPrefix: "addon", |
michael@0 | 1231 | |
michael@0 | 1232 | get exited() { |
michael@0 | 1233 | return !this._addon; |
michael@0 | 1234 | }, |
michael@0 | 1235 | |
michael@0 | 1236 | get id() { |
michael@0 | 1237 | return this._addon.id; |
michael@0 | 1238 | }, |
michael@0 | 1239 | |
michael@0 | 1240 | get url() { |
michael@0 | 1241 | return this._addon.sourceURI ? this._addon.sourceURI.spec : undefined; |
michael@0 | 1242 | }, |
michael@0 | 1243 | |
michael@0 | 1244 | get attached() { |
michael@0 | 1245 | return this._threadActor; |
michael@0 | 1246 | }, |
michael@0 | 1247 | |
michael@0 | 1248 | get global() { |
michael@0 | 1249 | return this._global; |
michael@0 | 1250 | }, |
michael@0 | 1251 | |
michael@0 | 1252 | form: function BAA_form() { |
michael@0 | 1253 | dbg_assert(this.actorID, "addon should have an actorID."); |
michael@0 | 1254 | |
michael@0 | 1255 | return { |
michael@0 | 1256 | actor: this.actorID, |
michael@0 | 1257 | id: this.id, |
michael@0 | 1258 | name: this._addon.name, |
michael@0 | 1259 | url: this.url, |
michael@0 | 1260 | debuggable: this._addon.isDebuggable, |
michael@0 | 1261 | }; |
michael@0 | 1262 | }, |
michael@0 | 1263 | |
michael@0 | 1264 | disconnect: function BAA_disconnect() { |
michael@0 | 1265 | this._addon = null; |
michael@0 | 1266 | this._global = null; |
michael@0 | 1267 | AddonManager.removeAddonListener(this); |
michael@0 | 1268 | }, |
michael@0 | 1269 | |
michael@0 | 1270 | setOptions: function BAA_setOptions(aOptions) { |
michael@0 | 1271 | if ("global" in aOptions) { |
michael@0 | 1272 | this._global = aOptions.global; |
michael@0 | 1273 | } |
michael@0 | 1274 | }, |
michael@0 | 1275 | |
michael@0 | 1276 | onDisabled: function BAA_onDisabled(aAddon) { |
michael@0 | 1277 | if (aAddon != this._addon) { |
michael@0 | 1278 | return; |
michael@0 | 1279 | } |
michael@0 | 1280 | |
michael@0 | 1281 | this._global = null; |
michael@0 | 1282 | }, |
michael@0 | 1283 | |
michael@0 | 1284 | onUninstalled: function BAA_onUninstalled(aAddon) { |
michael@0 | 1285 | if (aAddon != this._addon) { |
michael@0 | 1286 | return; |
michael@0 | 1287 | } |
michael@0 | 1288 | |
michael@0 | 1289 | if (this.attached) { |
michael@0 | 1290 | this.onDetach(); |
michael@0 | 1291 | this.conn.send({ from: this.actorID, type: "tabDetached" }); |
michael@0 | 1292 | } |
michael@0 | 1293 | |
michael@0 | 1294 | this.disconnect(); |
michael@0 | 1295 | }, |
michael@0 | 1296 | |
michael@0 | 1297 | onAttach: function BAA_onAttach() { |
michael@0 | 1298 | if (this.exited) { |
michael@0 | 1299 | return { type: "exited" }; |
michael@0 | 1300 | } |
michael@0 | 1301 | |
michael@0 | 1302 | if (!this.attached) { |
michael@0 | 1303 | this._contextPool = new ActorPool(this.conn); |
michael@0 | 1304 | this.conn.addActorPool(this._contextPool); |
michael@0 | 1305 | |
michael@0 | 1306 | this._threadActor = new AddonThreadActor(this.conn, this, |
michael@0 | 1307 | this._addon.id); |
michael@0 | 1308 | this._contextPool.addActor(this._threadActor); |
michael@0 | 1309 | } |
michael@0 | 1310 | |
michael@0 | 1311 | return { type: "tabAttached", threadActor: this._threadActor.actorID }; |
michael@0 | 1312 | }, |
michael@0 | 1313 | |
michael@0 | 1314 | onDetach: function BAA_onDetach() { |
michael@0 | 1315 | if (!this.attached) { |
michael@0 | 1316 | return { error: "wrongState" }; |
michael@0 | 1317 | } |
michael@0 | 1318 | |
michael@0 | 1319 | this.conn.removeActorPool(this._contextPool); |
michael@0 | 1320 | this._contextPool = null; |
michael@0 | 1321 | |
michael@0 | 1322 | this._threadActor = null; |
michael@0 | 1323 | |
michael@0 | 1324 | return { type: "detached" }; |
michael@0 | 1325 | }, |
michael@0 | 1326 | |
michael@0 | 1327 | preNest: function() { |
michael@0 | 1328 | let e = Services.wm.getEnumerator(null); |
michael@0 | 1329 | while (e.hasMoreElements()) { |
michael@0 | 1330 | let win = e.getNext(); |
michael@0 | 1331 | let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 1332 | .getInterface(Ci.nsIDOMWindowUtils); |
michael@0 | 1333 | windowUtils.suppressEventHandling(true); |
michael@0 | 1334 | windowUtils.suspendTimeouts(); |
michael@0 | 1335 | } |
michael@0 | 1336 | }, |
michael@0 | 1337 | |
michael@0 | 1338 | postNest: function() { |
michael@0 | 1339 | let e = Services.wm.getEnumerator(null); |
michael@0 | 1340 | while (e.hasMoreElements()) { |
michael@0 | 1341 | let win = e.getNext(); |
michael@0 | 1342 | let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 1343 | .getInterface(Ci.nsIDOMWindowUtils); |
michael@0 | 1344 | windowUtils.resumeTimeouts(); |
michael@0 | 1345 | windowUtils.suppressEventHandling(false); |
michael@0 | 1346 | } |
michael@0 | 1347 | } |
michael@0 | 1348 | }; |
michael@0 | 1349 | |
michael@0 | 1350 | BrowserAddonActor.prototype.requestTypes = { |
michael@0 | 1351 | "attach": BrowserAddonActor.prototype.onAttach, |
michael@0 | 1352 | "detach": BrowserAddonActor.prototype.onDetach |
michael@0 | 1353 | }; |
michael@0 | 1354 | |
michael@0 | 1355 | /** |
michael@0 | 1356 | * The DebuggerProgressListener object is an nsIWebProgressListener which |
michael@0 | 1357 | * handles onStateChange events for the inspected browser. If the user tries to |
michael@0 | 1358 | * navigate away from a paused page, the listener makes sure that the debuggee |
michael@0 | 1359 | * is resumed before the navigation begins. |
michael@0 | 1360 | * |
michael@0 | 1361 | * @param TabActor aTabActor |
michael@0 | 1362 | * The tab actor associated with this listener. |
michael@0 | 1363 | */ |
michael@0 | 1364 | function DebuggerProgressListener(aTabActor) { |
michael@0 | 1365 | this._tabActor = aTabActor; |
michael@0 | 1366 | this._onWindowCreated = this.onWindowCreated.bind(this); |
michael@0 | 1367 | } |
michael@0 | 1368 | |
michael@0 | 1369 | DebuggerProgressListener.prototype = { |
michael@0 | 1370 | QueryInterface: XPCOMUtils.generateQI([ |
michael@0 | 1371 | Ci.nsIWebProgressListener, |
michael@0 | 1372 | Ci.nsISupportsWeakReference, |
michael@0 | 1373 | Ci.nsISupports, |
michael@0 | 1374 | ]), |
michael@0 | 1375 | |
michael@0 | 1376 | watch: function DPL_watch(docShell) { |
michael@0 | 1377 | let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 1378 | .getInterface(Ci.nsIWebProgress); |
michael@0 | 1379 | webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATUS | |
michael@0 | 1380 | Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | |
michael@0 | 1381 | Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT); |
michael@0 | 1382 | |
michael@0 | 1383 | // TODO: fix docShell.chromeEventHandler in child processes! |
michael@0 | 1384 | let chromeEventHandler = docShell.chromeEventHandler || |
michael@0 | 1385 | docShell.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 1386 | .getInterface(Ci.nsIContentFrameMessageManager); |
michael@0 | 1387 | |
michael@0 | 1388 | // Watch for globals being created in this docshell tree. |
michael@0 | 1389 | chromeEventHandler.addEventListener("DOMWindowCreated", |
michael@0 | 1390 | this._onWindowCreated, true); |
michael@0 | 1391 | chromeEventHandler.addEventListener("pageshow", |
michael@0 | 1392 | this._onWindowCreated, true); |
michael@0 | 1393 | }, |
michael@0 | 1394 | |
michael@0 | 1395 | unwatch: function DPL_unwatch(docShell) { |
michael@0 | 1396 | let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 1397 | .getInterface(Ci.nsIWebProgress); |
michael@0 | 1398 | webProgress.removeProgressListener(this); |
michael@0 | 1399 | |
michael@0 | 1400 | // TODO: fix docShell.chromeEventHandler in child processes! |
michael@0 | 1401 | let chromeEventHandler = docShell.chromeEventHandler || |
michael@0 | 1402 | docShell.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 1403 | .getInterface(Ci.nsIContentFrameMessageManager); |
michael@0 | 1404 | chromeEventHandler.removeEventListener("DOMWindowCreated", |
michael@0 | 1405 | this._onWindowCreated, true); |
michael@0 | 1406 | chromeEventHandler.removeEventListener("pageshow", |
michael@0 | 1407 | this._onWindowCreated, true); |
michael@0 | 1408 | }, |
michael@0 | 1409 | |
michael@0 | 1410 | onWindowCreated: |
michael@0 | 1411 | DevToolsUtils.makeInfallible(function DPL_onWindowCreated(evt) { |
michael@0 | 1412 | // Ignore any event if the tab actor isn't attached. |
michael@0 | 1413 | if (!this._tabActor.attached) { |
michael@0 | 1414 | return; |
michael@0 | 1415 | } |
michael@0 | 1416 | |
michael@0 | 1417 | // pageshow events for non-persisted pages have already been handled by a |
michael@0 | 1418 | // prior DOMWindowCreated event. |
michael@0 | 1419 | if (evt.type == "pageshow" && !evt.persisted) { |
michael@0 | 1420 | return; |
michael@0 | 1421 | } |
michael@0 | 1422 | |
michael@0 | 1423 | let window = evt.target.defaultView; |
michael@0 | 1424 | this._tabActor._windowReady(window); |
michael@0 | 1425 | }, "DebuggerProgressListener.prototype.onWindowCreated"), |
michael@0 | 1426 | |
michael@0 | 1427 | onStateChange: |
michael@0 | 1428 | DevToolsUtils.makeInfallible(function DPL_onStateChange(aProgress, aRequest, aFlag, aStatus) { |
michael@0 | 1429 | // Ignore any event if the tab actor isn't attached. |
michael@0 | 1430 | if (!this._tabActor.attached) { |
michael@0 | 1431 | return; |
michael@0 | 1432 | } |
michael@0 | 1433 | |
michael@0 | 1434 | let isStart = aFlag & Ci.nsIWebProgressListener.STATE_START; |
michael@0 | 1435 | let isStop = aFlag & Ci.nsIWebProgressListener.STATE_STOP; |
michael@0 | 1436 | let isDocument = aFlag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT; |
michael@0 | 1437 | let isWindow = aFlag & Ci.nsIWebProgressListener.STATE_IS_WINDOW; |
michael@0 | 1438 | |
michael@0 | 1439 | let window = aProgress.DOMWindow; |
michael@0 | 1440 | if (isDocument && isStart) { |
michael@0 | 1441 | let newURI = aRequest instanceof Ci.nsIChannel ? aRequest.URI.spec : null; |
michael@0 | 1442 | this._tabActor._willNavigate(window, newURI, aRequest); |
michael@0 | 1443 | } |
michael@0 | 1444 | if (isWindow && isStop) { |
michael@0 | 1445 | this._tabActor._navigate(window); |
michael@0 | 1446 | } |
michael@0 | 1447 | }, "DebuggerProgressListener.prototype.onStateChange") |
michael@0 | 1448 | }; |