toolkit/devtools/server/actors/webbrowser.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial