Bug 871081: Share a common RootActor implementation amongst browser, Fennec, B2G, and xpcshell tests. r=past,mfinkle,fabrice

--HG--
rename : browser/devtools/debugger/test/browser_dbg_listtabs.js => browser/devtools/debugger/test/browser_dbg_listtabs-01.js
This commit is contained in:
Jim Blandy 2013-05-17 15:17:00 +03:00
Родитель a211a7d924
Коммит 3923f3e983
13 изменённых файлов: 1080 добавлений и 516 удалений

Просмотреть файл

@ -6,110 +6,106 @@
'use strict';
/**
* B2G-specific actors that extend BrowserRootActor and BrowserTabActor,
* overriding some of their methods.
* B2G-specific actors.
*/
/**
* The function that creates the root actor. DebuggerServer expects to find this
* function in the loaded actors in order to initialize properly.
* Construct a root actor appropriate for use in a server running in B2G. The
* returned root actor:
* - respects the factories registered with DebuggerServer.addGlobalActor,
* - uses a ContentTabList to supply tab actors,
* - sends all navigator:browser window documents a Debugger:Shutdown event
* when it exits.
*
* * @param connection DebuggerServerConnection
* The conection to the client.
*/
function createRootActor(connection) {
return new DeviceRootActor(connection);
function createRootActor(connection)
{
let parameters = {
#ifndef MOZ_WIDGET_GONK
tabList: new ContentTabList(connection),
#else
tabList: [],
#endif
globalActorFactories: DebuggerServer.globalActorFactories,
onShutdown: sendShutdownEvent
};
let root = new RootActor(connection, parameters);
root.applicationType = "operating-system";
return root;
}
/**
* Creates the root actor that client-server communications always start with.
* The root actor is responsible for the initial 'hello' packet and for
* responding to a 'listTabs' request that produces the list of currently open
* tabs.
* A live list of BrowserTabActors representing the current browser tabs,
* to be provided to the root actor to answer 'listTabs' requests. In B2G,
* only a single tab is ever present.
*
* @param connection DebuggerServerConnection
* The connection in which this list's tab actors may participate.
*
* @see BrowserTabList for more a extensive description of how tab list objects
* work.
*/
function ContentTabList(connection)
{
BrowserTabList.call(this, connection);
}
ContentTabList.prototype = Object.create(BrowserTabList.prototype);
ContentTabList.prototype.constructor = ContentTabList;
ContentTabList.prototype.iterator = function() {
let browser = Services.wm.getMostRecentWindow('navigator:browser');
// Do we have an existing actor for this browser? If not, create one.
let actor = this._actorByBrowser.get(browser);
if (!actor) {
actor = new ContentTabActor(this._connection, browser);
this._actorByBrowser.set(browser, actor);
actor.selected = true;
}
yield actor;
};
ContentTabList.prototype.onCloseWindow = makeInfallible(function(aWindow) {
/*
* nsIWindowMediator deadlocks if you call its GetEnumerator method from
* a nsIWindowMediatorListener's onCloseWindow hook (bug 873589), so
* handle the close in a different tick.
*/
Services.tm.currentThread.dispatch(makeInfallible(() => {
/*
* Scan the entire map for actors representing tabs that were in this
* top-level window, and exit them.
*/
for (let [browser, actor] of this._actorByBrowser) {
this._handleActorClose(actor, browser);
}
}, "ContentTabList.prototype.onCloseWindow's delayed body"), 0);
}, "ContentTabList.prototype.onCloseWindow");
/**
* Creates a tab actor for handling requests to the single tab, like
* attaching and detaching. ContentTabActor respects the actor factories
* registered with DebuggerServer.addTabActor.
*
* @param connection DebuggerServerConnection
* The conection to the client.
*/
function DeviceRootActor(connection) {
BrowserRootActor.call(this, connection);
this.browser = Services.wm.getMostRecentWindow('navigator:browser');
}
DeviceRootActor.prototype = new BrowserRootActor();
/**
* Disconnects the actor from the browser window.
*/
DeviceRootActor.prototype.disconnect = function DRA_disconnect() {
this._extraActors = null;
let actor = this._tabActors.get(this.browser);
if (actor) {
actor.exit();
}
};
/**
* Handles the listTabs request. Builds a list of actors for the single
* tab (window) running in the process. The actors will survive
* until at least the next listTabs request.
*/
DeviceRootActor.prototype.onListTabs = function DRA_onListTabs() {
let actorPool = new ActorPool(this.conn);
#ifndef MOZ_WIDGET_GONK
let actor = this._tabActors.get(this.browser);
if (!actor) {
actor = new DeviceTabActor(this.conn, this.browser);
// this.actorID is set by ActorPool when an actor is put into one.
actor.parentID = this.actorID;
this._tabActors.set(this.browser, actor);
}
actorPool.addActor(actor);
#endif
this._createExtraActors(DebuggerServer.globalActorFactories, actorPool);
// Now drop the old actorID -> actor map. Actors that still mattered were
// added to the new map, others will go away.
if (this._tabActorPool) {
this.conn.removeActorPool(this._tabActorPool);
}
this._tabActorPool = actorPool;
this.conn.addActorPool(this._tabActorPool);
let response = {
'from': 'root',
'selected': 0,
#ifndef MOZ_WIDGET_GONK
'tabs': [actor.grip()]
#else
'tabs': []
#endif
};
this._appendExtraActors(response);
return response;
};
/**
* The request types this actor can handle.
*/
DeviceRootActor.prototype.requestTypes = {
'listTabs': DeviceRootActor.prototype.onListTabs
};
/**
* Creates a tab actor for handling requests to the single tab, like attaching
* and detaching.
*
* @param connection DebuggerServerConnection
* The connection to the client.
* @param browser browser
* The browser instance that contains this tab.
*/
function DeviceTabActor(connection, browser) {
function ContentTabActor(connection, browser)
{
BrowserTabActor.call(this, connection, browser);
}
DeviceTabActor.prototype = new BrowserTabActor();
ContentTabActor.prototype.constructor = ContentTabActor;
Object.defineProperty(DeviceTabActor.prototype, "title", {
ContentTabActor.prototype = Object.create(BrowserTabActor.prototype);
Object.defineProperty(ContentTabActor.prototype, "title", {
get: function() {
return this.browser.title;
},
@ -117,7 +113,7 @@ Object.defineProperty(DeviceTabActor.prototype, "title", {
configurable: false
});
Object.defineProperty(DeviceTabActor.prototype, "url", {
Object.defineProperty(ContentTabActor.prototype, "url", {
get: function() {
return this.browser.document.documentURI;
},
@ -125,7 +121,7 @@ Object.defineProperty(DeviceTabActor.prototype, "url", {
configurable: false
});
Object.defineProperty(DeviceTabActor.prototype, "contentWindow", {
Object.defineProperty(ContentTabActor.prototype, "contentWindow", {
get: function() {
return this.browser;
},

Просмотреть файл

@ -1001,6 +1001,7 @@ let RemoteDebugger = {
DebuggerServer.init(this.prompt.bind(this));
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js");
#ifndef MOZ_WIDGET_GONK
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/script.js");
DebuggerServer.addGlobalActor(DebuggerServer.ChromeDebuggerActor, "chromeDebugger");
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/webconsole.js");
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/gcli.js");

Просмотреть файл

@ -17,7 +17,8 @@ MOCHITEST_BROWSER_TESTS = \
browser_dbg_cmd_break.js \
$(browser_dbg_createRemote.js disabled for intermittent failures, bug 753225) \
browser_dbg_debuggerstatement.js \
browser_dbg_listtabs.js \
browser_dbg_listtabs-01.js \
browser_dbg_listtabs-02.js \
browser_dbg_tabactor-01.js \
browser_dbg_tabactor-02.js \
browser_dbg_globalactor-01.js \

Просмотреть файл

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Make sure the listTabs request works as specified.
var gTab1 = null;
var gTab1Actor = null;

Просмотреть файл

@ -0,0 +1,150 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Make sure the root actor's live tab list implementation works as specified.
let testPage = ("data:text/html;charset=utf-8,"
+ encodeURIComponent("<title>JS Debugger BrowserTabList test page</title>" +
"<body>Yo.</body>"));
// The tablist object whose behavior we observe.
let tabList;
let firstActor, actorA;
let tabA, tabB, tabC;
let newWin;
// Stock onListChanged handler.
let onListChangedCount = 0;
function onListChangedHandler() {
onListChangedCount++;
}
function test() {
tabList = new DebuggerServer.BrowserTabList("fake DebuggerServerConnection");
tabList._testing = true;
tabList.onListChanged = onListChangedHandler;
checkSingleTab();
// Open a new tab. We should be notified.
is(onListChangedCount, 0, "onListChanged handler call count");
tabA = addTab(testPage, onTabA);
}
function checkSingleTab() {
var tabActors = [t for (t of tabList)];
is(tabActors.length, 1, "initial tab list: contains initial tab");
firstActor = tabActors[0];
is(firstActor.url, "about:blank", "initial tab list: initial tab URL is 'about:blank'");
is(firstActor.title, "New Tab", "initial tab list: initial tab title is 'New Tab'");
}
function onTabA() {
is(onListChangedCount, 1, "onListChanged handler call count");
var tabActors = new Set([t for (t of tabList)]);
is(tabActors.size, 2, "tabA opened: two tabs in list");
ok(tabActors.has(firstActor), "tabA opened: initial tab present");
info("actors: " + [a.url for (a of tabActors)]);
actorA = [a for (a of tabActors) if (a !== firstActor)][0];
ok(actorA.url.match(/^data:text\/html;/), "tabA opened: new tab URL");
is(actorA.title, "JS Debugger BrowserTabList test page", "tabA opened: new tab title");
tabB = addTab(testPage, onTabB);
}
function onTabB() {
is(onListChangedCount, 2, "onListChanged handler call count");
var tabActors = new Set([t for (t of tabList)]);
is(tabActors.size, 3, "tabB opened: three tabs in list");
// Test normal close.
gBrowser.tabContainer.addEventListener("TabClose", function onClose(aEvent) {
gBrowser.tabContainer.removeEventListener("TabClose", onClose, false);
ok(!aEvent.detail, "This was a normal tab close");
// Let the actor's TabClose handler finish first.
executeSoon(testTabClose);
}, false);
gBrowser.removeTab(tabA);
}
function testTabClose() {
is(onListChangedCount, 3, "onListChanged handler call count");
var tabActors = new Set([t for (t of tabList)]);
is(tabActors.size, 2, "tabA closed: two tabs in list");
ok(tabActors.has(firstActor), "tabA closed: initial tab present");
info("actors: " + [a.url for (a of tabActors)]);
actorA = [a for (a of tabActors) if (a !== firstActor)][0];
ok(actorA.url.match(/^data:text\/html;/), "tabA closed: new tab URL");
is(actorA.title, "JS Debugger BrowserTabList test page", "tabA closed: new tab title");
// Test tab close by moving tab to a window.
tabC = addTab(testPage, onTabC);
}
function onTabC() {
is(onListChangedCount, 4, "onListChanged handler call count");
var tabActors = new Set([t for (t of tabList)]);
is(tabActors.size, 3, "tabC opened: three tabs in list");
gBrowser.tabContainer.addEventListener("TabClose", function onClose2(aEvent) {
gBrowser.tabContainer.removeEventListener("TabClose", onClose2, false);
ok(aEvent.detail, "This was a tab closed by moving");
// Let the actor's TabClose handler finish first.
executeSoon(testWindowClose);
}, false);
newWin = gBrowser.replaceTabWithWindow(tabC);
}
function testWindowClose() {
is(onListChangedCount, 5, "onListChanged handler call count");
var tabActors = new Set([t for (t of tabList)]);
is(tabActors.size, 3, "tabC closed: three tabs in list");
ok(tabActors.has(firstActor), "tabC closed: initial tab present");
info("actors: " + [a.url for (a of tabActors)]);
actorA = [a for (a of tabActors) if (a !== firstActor)][0];
ok(actorA.url.match(/^data:text\/html;/), "tabC closed: new tab URL");
is(actorA.title, "JS Debugger BrowserTabList test page", "tabC closed: new tab title");
// Cleanup.
newWin.addEventListener("unload", function onUnload(aEvent) {
newWin.removeEventListener("unload", onUnload, false);
ok(!aEvent.detail, "This was a normal window close");
// Let the actor's TabClose handler finish first.
executeSoon(checkWindowClose);
}, false);
newWin.close();
}
function checkWindowClose() {
is(onListChangedCount, 6, "onListChanged handler call count");
// Check that closing a XUL window leaves the other actors intact.
var tabActors = new Set([t for (t of tabList)]);
is(tabActors.size, 2, "newWin closed: two tabs in list");
ok(tabActors.has(firstActor), "newWin closed: initial tab present");
info("actors: " + [a.url for (a of tabActors)]);
actorA = [a for (a of tabActors) if (a !== firstActor)][0];
ok(actorA.url.match(/^data:text\/html;/), "newWin closed: new tab URL");
is(actorA.title, "JS Debugger BrowserTabList test page", "newWin closed: new tab title");
// Test normal close.
gBrowser.tabContainer.addEventListener("TabClose", function onClose(aEvent) {
gBrowser.tabContainer.removeEventListener("TabClose", onClose, false);
ok(!aEvent.detail, "This was a normal tab close");
// Let the actor's TabClose handler finish first.
executeSoon(finishTest);
}, false);
gBrowser.removeTab(tabB);
}
function finishTest() {
checkSingleTab();
finish();
}

Просмотреть файл

@ -5,120 +5,98 @@
"use strict";
/**
* Fennec-specific root actor that extends BrowserRootActor and overrides some
* of its methods.
* Fennec-specific actors.
*/
/**
* The function that creates the root actor. DebuggerServer expects to find this
* function in the loaded actors in order to initialize properly.
*/
function createRootActor(aConnection) {
return new DeviceRootActor(aConnection);
}
/**
* Creates the root actor that client-server communications always start with.
* The root actor is responsible for the initial 'hello' packet and for
* responding to a 'listTabs' request that produces the list of currently open
* tabs.
* Construct a root actor appropriate for use in a server running in a
* browser on Android. The returned root actor:
* - respects the factories registered with DebuggerServer.addGlobalActor,
* - uses a MobileTabList to supply tab actors,
* - sends all navigator:browser window documents a Debugger:Shutdown event
* when it exits.
*
* @param aConnection DebuggerServerConnection
* * @param aConnection DebuggerServerConnection
* The conection to the client.
*/
function DeviceRootActor(aConnection) {
BrowserRootActor.call(this, aConnection);
function createRootActor(aConnection)
{
let parameters = {
tabList: new MobileTabList(aConnection),
globalActorFactories: DebuggerServer.globalActorFactories,
onShutdown: sendShutdownEvent
};
return new RootActor(aConnection, parameters);
}
DeviceRootActor.prototype = new BrowserRootActor();
/**
* Handles the listTabs request. Builds a list of actors
* for the tabs running in the process. The actors will survive
* until at least the next listTabs request.
* A live list of BrowserTabActors representing the current browser tabs,
* to be provided to the root actor to answer 'listTabs' requests.
*
* This object also takes care of listening for TabClose events and
* onCloseWindow notifications, and exiting the BrowserTabActors concerned.
*
* (See the documentation for RootActor for the definition of the "live
* list" interface.)
*
* @param aConnection DebuggerServerConnection
* The connection in which this list's tab actors may participate.
*
* @see BrowserTabList for more a extensive description of how tab list objects
* work.
*/
DeviceRootActor.prototype.onListTabs = function DRA_onListTabs() {
// Get actors for all the currently-running tabs (reusing
// existing actors where applicable), and store them in
// an ActorPool.
function MobileTabList(aConnection)
{
BrowserTabList.call(this, aConnection);
}
let actorPool = new ActorPool(this.conn);
let tabActorList = [];
MobileTabList.prototype = Object.create(BrowserTabList.prototype);
let win = windowMediator.getMostRecentWindow("navigator:browser");
this.browser = win.BrowserApp.selectedBrowser;
MobileTabList.prototype.constructor = MobileTabList;
// Watch the window for tab closes so we can invalidate
// actors as needed.
this.watchWindow(win);
MobileTabList.prototype.iterator = function() {
// As a sanity check, make sure all the actors presently in our map get
// picked up when we iterate over all windows' tabs.
let initialMapSize = this._actorByBrowser.size;
let foundCount = 0;
let tabs = win.BrowserApp.tabs;
let selected;
// To avoid mysterious behavior if tabs are closed or opened mid-iteration,
// we update the map first, and then make a second pass over it to yield
// the actors. Thus, the sequence yielded is always a snapshot of the
// actors that were live when we began the iteration.
for each (let tab in tabs) {
let browser = tab.browser;
// Iterate over all navigator:browser XUL windows.
for (let win of allAppShellDOMWindows("navigator:browser")) {
let selectedTab = win.BrowserApp.selectedBrowser;
if (browser == this.browser) {
selected = tabActorList.length;
// For each tab in this XUL window, ensure that we have an actor for
// it, reusing existing actors where possible. We actually iterate
// over 'browser' XUL elements, and BrowserTabActor uses
// browser.contentWindow.wrappedJSObject as the debuggee global.
for (let tab of win.BrowserApp.tabs) {
let browser = tab.browser;
// Do we have an existing actor for this browser? If not, create one.
let actor = this._actorByBrowser.get(browser);
if (actor) {
foundCount++;
} else {
actor = new BrowserTabActor(this._connection, browser);
this._actorByBrowser.set(browser, actor);
}
// Set the 'selected' properties on all actors correctly.
actor.selected = (browser === selectedTab);
}
let actor = this._tabActors.get(browser);
if (!actor) {
actor = new BrowserTabActor(this.conn, browser);
actor.parentID = this.actorID;
this._tabActors.set(browser, actor);
}
actorPool.addActor(actor);
tabActorList.push(actor);
}
this._createExtraActors(DebuggerServer.globalActorFactories, actorPool);
if (this._testing && initialMapSize !== foundCount)
throw Error("_actorByBrowser map contained actors for dead tabs");
// Now drop the old actorID -> actor map. Actors that still
// mattered were added to the new map, others will go
// away.
if (this._tabActorPool) {
this.conn.removeActorPool(this._tabActorPool);
}
this._mustNotify = true;
this._checkListening();
this._tabActorPool = actorPool;
this.conn.addActorPool(this._tabActorPool);
let response = {
"from": "root",
"selected": selected,
"tabs": [actor.grip() for (actor of tabActorList)]
};
this._appendExtraActors(response);
return response;
};
/**
* Return the tab container for the specified window.
*/
DeviceRootActor.prototype.getTabContainer = function DRA_getTabContainer(aWindow) {
return aWindow.document.getElementById("browsers");
};
/**
* When a tab is closed, exit its tab actor. The actor
* will be dropped at the next listTabs request.
*/
DeviceRootActor.prototype.onTabClosed = function DRA_onTabClosed(aEvent) {
this.exitTabActor(aEvent.target.browser);
};
// nsIWindowMediatorListener
DeviceRootActor.prototype.onCloseWindow = function DRA_onCloseWindow(aWindow) {
if (aWindow.BrowserApp) {
this.unwatchWindow(aWindow);
/* Yield the values. */
for (let [browser, actor] of this._actorByBrowser) {
yield actor;
}
};
/**
* The request types this actor can handle.
*/
DeviceRootActor.prototype.requestTypes = {
"listTabs": DeviceRootActor.prototype.onListTabs
};

Просмотреть файл

@ -182,6 +182,7 @@ const UnsolicitedNotifications = {
"newScript": "newScript",
"newSource": "newSource",
"tabDetached": "tabDetached",
"tabListChanged": "tabListChanged",
"tabNavigated": "tabNavigated",
"pageError": "pageError",
"webappsEvent": "webappsEvent",

Просмотреть файл

@ -0,0 +1,328 @@
/* -*- tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* Root actor for the remote debugging protocol. */
/**
* Methods shared between RootActor and BrowserTabActor.
*/
/**
* Populate |this._extraActors| as specified by |aFactories|, reusing whatever
* actors are already there. Add all actors in the final extra actors table to
* |aPool|.
*
* The root actor and the tab actor use this to instantiate actors that other
* parts of the browser have specified with DebuggerServer.addTabActor antd
* DebuggerServer.addGlobalActor.
*
* @param aFactories
* An object whose own property names are the names of properties to add to
* some reply packet (say, a tab actor grip or the "listTabs" response
* form), and whose own property values are actor constructor functions, as
* documented for addTabActor and addGlobalActor.
*
* @param this
* The BrowserRootActor or BrowserTabActor with which the new actors will
* be associated. It should support whatever API the |aFactories|
* constructor functions might be interested in, as it is passed to them.
* For the sake of CommonCreateExtraActors itself, it should have at least
* the following properties:
*
* - _extraActors
* An object whose own property names are factory table (and packet)
* property names, and whose values are no-argument actor constructors,
* of the sort that one can add to an ActorPool.
*
* - conn
* The DebuggerServerConnection in which the new actors will participate.
*
* - actorID
* The actor's name, for use as the new actors' parentID.
*/
function CommonCreateExtraActors(aFactories, aPool) {
// Walk over global actors added by extensions.
for (let name in aFactories) {
let actor = this._extraActors[name];
if (!actor) {
actor = aFactories[name].bind(null, this.conn, this);
actor.prototype = aFactories[name].prototype;
actor.parentID = this.actorID;
this._extraActors[name] = actor;
}
aPool.addActor(actor);
}
}
/**
* Append the extra actors in |this._extraActors|, constructed by a prior call
* to CommonCreateExtraActors, to |aObject|.
*
* @param aObject
* The object to which the extra actors should be added, under the
* property names given in the |aFactories| table passed to
* CommonCreateExtraActors.
*
* @param this
* The BrowserRootActor or BrowserTabActor whose |_extraActors| table we
* should use; see above.
*/
function CommonAppendExtraActors(aObject) {
for (let name in this._extraActors) {
let actor = this._extraActors[name];
aObject[name] = actor.actorID;
}
}
/**
* Create a remote debugging protocol root actor.
*
* @param aConnection
* The DebuggerServerConnection whose root actor we are constructing.
*
* @param aParameters
* The properties of |aParameters| provide backing objects for the root
* actor's requests; if a given property is omitted from |aParameters|, the
* root actor won't implement the corresponding requests or notifications.
* Supported properties:
*
* - tabList: a live list (see below) of tab actors. If present, the
* new root actor supports the 'listTabs' request, providing the live
* list's elements as its tab actors, and sending 'tabListChanged'
* notifications when the live list's contents change. One actor in
* this list must have a true '.selected' property.
*
* - globalActorFactories: an object |A| describing further actors to
* attach to the 'listTabs' reply. This is the type accumulated by
* DebuggerServer.addGlobalActor. For each own property |P| of |A|,
* the root actor adds a property named |P| to the 'listTabs'
* reply whose value is the name of an actor constructed by
* |A[P]|.
*
* - onShutdown: a function to call when the root actor is disconnected.
*
* Instance properties:
*
* - applicationType: the string the root actor will include as the
* "applicationType" property in the greeting packet. By default, this
* is "browser".
*
* Live lists:
*
* A "live list", as used for the |tabList|, is an object that presents a
* list of actors, and also notifies its clients of changes to the list. A
* live list's interface is two properties:
*
* - iterator: a method that returns an iterator. A for-of loop will call
* this method to obtain an iterator for the loop, so if LL is
* a live list, one can simply write 'for (i of LL) ...'.
*
* - onListChanged: a handler called, with no arguments, when the set of
* values the iterator would produce has changed since the last
* time 'iterator' was called. This may only be set to null or a
* callable value (one for which the typeof operator returns
* 'function'). (Note that the live list will not call the
* onListChanged handler until the list has been iterated over
* once; if nobody's seen the list in the first place, nobody
* should care if its contents have changed!)
*
* When the list changes, the list implementation should ensure that any
* actors yielded in previous iterations whose referents (tabs) still exist
* get yielded again in subsequent iterations. If the underlying referent
* is the same, the same actor should be presented for it.
*
* The root actor registers an 'onListChanged' handler on the appropriate
* list when it may need to send the client 'tabListChanged' notifications,
* and is careful to remove the handler whenever it does not need to send
* such notifications (including when it is disconnected). This means that
* live list implementations can use the state of the handler property (set
* or null) to install and remove observers and event listeners.
*
* Note that, as the only way for the root actor to see the members of the
* live list is to begin an iteration over the list, the live list need not
* actually produce any actors until they are reached in the course of
* iteration: alliterative lazy live lists.
*/
function RootActor(aConnection, aParameters) {
this.conn = aConnection;
this._parameters = aParameters;
this._onTabListChanged = this.onTabListChanged.bind(this);
this._extraActors = {};
}
RootActor.prototype = {
constructor: RootActor,
applicationType: "browser",
/**
* Return a 'hello' packet as specified by the Remote Debugging Protocol.
*/
sayHello: function() {
return {
from: "root",
applicationType: this.applicationType,
/* This is not in the spec, but it's used by tests. */
testConnectionPrefix: this.conn.prefix,
traits: {
sources: true
}
};
},
/**
* Disconnects the actor from the browser window.
*/
disconnect: function() {
/* Tell the live lists we aren't watching any more. */
if (this._parameters.tabList) {
this._parameters.tabList.onListChanged = null;
}
if (typeof this._parameters.onShutdown === 'function') {
this._parameters.onShutdown();
}
this._extraActors = null;
},
/* The 'listTabs' request and the 'tabListChanged' notification. */
/**
* Handles the listTabs request. The actors will survive until at least
* the next listTabs request.
*/
onListTabs: function() {
let tabList = this._parameters.tabList;
if (!tabList) {
return { from: "root", error: "noTabs",
message: "This root actor has no browser tabs." };
}
/*
* Walk the tab list, accumulating the array of tab actors for the
* reply, and moving all the actors to a new ActorPool. We'll
* replace the old tab actor pool with the one we build here, thus
* retiring any actors that didn't get listed again, and preparing any
* new actors to receive packets.
*/
let newActorPool = new ActorPool(this.conn);
let tabActorList = [];
let selected;
for (let tabActor of tabList) {
if (tabActor.selected) {
selected = tabActorList.length;
}
tabActor.parentID = this.actorID;
newActorPool.addActor(tabActor);
tabActorList.push(tabActor);
}
/* DebuggerServer.addGlobalActor support: create actors. */
this._createExtraActors(this._parameters.globalActorFactories, newActorPool);
/*
* Drop the old actorID -> actor map. Actors that still mattered were
* added to the new map; others will go away.
*/
if (this._tabActorPool) {
this.conn.removeActorPool(this._tabActorPool);
}
this._tabActorPool = newActorPool;
this.conn.addActorPool(this._tabActorPool);
let reply = {
"from": "root",
"selected": selected || 0,
"tabs": [actor.grip() for (actor of tabActorList)],
};
/* DebuggerServer.addGlobalActor support: name actors in 'listTabs' reply. */
this._appendExtraActors(reply);
/*
* Now that we're actually going to report the contents of tabList to
* the client, we're responsible for letting the client know if it
* changes.
*/
tabList.onListChanged = this._onTabListChanged;
return reply;
},
onTabListChanged: function () {
this.conn.send({ from:"root", type:"tabListChanged" });
/* It's a one-shot notification; no need to watch any more. */
this._parameters.tabList.onListChanged = null;
},
/* This is not in the spec, but it's used by tests. */
onEcho: (aRequest) => aRequest,
/* Support for DebuggerServer.addGlobalActor. */
_createExtraActors: CommonCreateExtraActors,
_appendExtraActors: CommonAppendExtraActors,
/* ThreadActor hooks. */
/**
* Prepare to enter a nested event loop by disabling debuggee events.
*/
preNest: function() {
// Disable events in all open windows.
let e = windowMediator.getEnumerator(null);
while (e.hasMoreElements()) {
let win = e.getNext();
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.suppressEventHandling(true);
windowUtils.suspendTimeouts();
}
},
/**
* Prepare to exit a nested event loop by enabling debuggee events.
*/
postNest: function(aNestData) {
// Enable events in all open windows.
let e = windowMediator.getEnumerator(null);
while (e.hasMoreElements()) {
let win = e.getNext();
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.resumeTimeouts();
windowUtils.suppressEventHandling(false);
}
},
/* ChromeDebuggerActor hooks. */
/**
* Add the specified actor to the default actor pool connection, in order to
* keep it alive as long as the server is. This is used by breakpoints in the
* thread and chrome debugger actors.
*
* @param actor aActor
* The actor object.
*/
addToParentPool: function(aActor) {
this.conn.addActor(aActor);
},
/**
* Remove the specified actor from the default actor pool.
*
* @param BreakpointActor aActor
* The actor object.
*/
removeFromParentPool: function(aActor) {
this.conn.removeActor(aActor);
}
}
RootActor.prototype.requestTypes = {
"listTabs": RootActor.prototype.onListTabs,
"echo": RootActor.prototype.onEcho
};

Просмотреть файл

@ -10,329 +10,437 @@
*/
/**
* Methods shared between BrowserRootActor and BrowserTabActor.
* Yield all windows of type |aWindowType|, from the oldest window to the
* youngest, using nsIWindowMediator::getEnumerator. We're usually
* interested in "navigator:browser" windows.
*/
/**
* Populate |this._extraActors| as specified by |aFactories|, reusing whatever
* actors are already there. Add all actors in the final extra actors table to
* |aPool|.
*
* The root actor and the tab actor use this to instantiate actors that other
* parts of the browser have specified with DebuggerServer.addTabActor antd
* DebuggerServer.addGlobalActor.
*
* @param aFactories
* An object whose own property names are the names of properties to add to
* some reply packet (say, a tab actor grip or the "listTabs" response
* form), and whose own property values are actor constructor functions, as
* documented for addTabActor and addGlobalActor.
*
* @param this
* The BrowserRootActor or BrowserTabActor with which the new actors will
* be associated. It should support whatever API the |aFactories|
* constructor functions might be interested in, as it is passed to them.
* For the sake of CommonCreateExtraActors itself, it should have at least
* the following properties:
*
* - _extraActors
* An object whose own property names are factory table (and packet)
* property names, and whose values are no-argument actor constructors,
* of the sort that one can add to an ActorPool.
*
* - conn
* The DebuggerServerConnection in which the new actors will participate.
*
* - actorID
* The actor's name, for use as the new actors' parentID.
*/
function CommonCreateExtraActors(aFactories, aPool) {
// Walk over global actors added by extensions.
for (let name in aFactories) {
let actor = this._extraActors[name];
if (!actor) {
actor = aFactories[name].bind(null, this.conn, this);
actor.prototype = aFactories[name].prototype;
actor.parentID = this.actorID;
this._extraActors[name] = actor;
}
aPool.addActor(actor);
function allAppShellDOMWindows(aWindowType)
{
let e = windowMediator.getEnumerator(aWindowType);
while (e.hasMoreElements()) {
yield e.getNext();
}
}
/**
* Append the extra actors in |this._extraActors|, constructed by a prior call
* to CommonCreateExtraActors, to |aObject|.
*
* @param aObject
* The object to which the extra actors should be added, under the
* property names given in the |aFactories| table passed to
* CommonCreateExtraActors.
*
* @param this
* The BrowserRootActor or BrowserTabActor whose |_extraActors| table we
* should use; see above.
* Return true if the top-level window |aWindow| is a "navigator:browser"
* window.
*/
function CommonAppendExtraActors(aObject) {
for (let name in this._extraActors) {
let actor = this._extraActors[name];
aObject[name] = actor.actorID;
function appShellDOMWindowType(aWindow) {
/* This is what nsIWindowMediator's enumerator checks. */
return aWindow.document.documentElement.getAttribute('windowtype');
}
/**
* Send Debugger:Shutdown events to all "navigator:browser" windows.
*/
function sendShutdownEvent() {
for (let win of allAppShellDOMWindows("navigator:browser")) {
let evt = win.document.createEvent("Event");
evt.initEvent("Debugger:Shutdown", true, false);
win.document.documentElement.dispatchEvent(evt);
}
}
/**
* Construct a root actor appropriate for use in a server running in a
* browser. The returned root actor:
* - respects the factories registered with DebuggerServer.addGlobalActor,
* - uses a BrowserTabList to supply tab actors,
* - sends all navigator:browser window documents a Debugger:Shutdown event
* when it exits.
*
* * @param aConnection DebuggerServerConnection
* The conection to the client.
*/
function createRootActor(aConnection)
{
return new RootActor(aConnection,
{
tabList: new BrowserTabList(aConnection),
globalActorFactories: DebuggerServer.globalActorFactories,
onShutdown: sendShutdownEvent
});
}
var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
function createRootActor(aConnection)
{
return new BrowserRootActor(aConnection);
}
.getService(Ci.nsIWindowMediator);
/**
* Creates the root actor that client-server communications always start with.
* The root actor is responsible for the initial 'hello' packet and for
* responding to a 'listTabs' request that produces the list of currently open
* tabs.
* A live list of BrowserTabActors representing the current browser tabs,
* to be provided to the root actor to answer 'listTabs' requests.
*
* This object also takes care of listening for TabClose events and
* onCloseWindow notifications, and exiting the BrowserTabActors concerned.
*
* (See the documentation for RootActor for the definition of the "live
* list" interface.)
*
* @param aConnection DebuggerServerConnection
* The conection to the client.
* The connection in which this list's tab actors may participate.
*
* Some notes:
*
* This constructor is specific to the desktop browser environment; it
* maintains the tab list by tracking XUL windows and their XUL documents'
* "tabbrowser", "tab", and "browser" elements. What's entailed in maintaining
* an accurate list of open tabs in this context?
*
* - Opening and closing XUL windows:
*
* An nsIWindowMediatorListener is notified when new XUL windows (i.e., desktop
* windows) are opened and closed. It is not notified of individual content
* browser tabs coming and going within such a XUL window. That seems
* reasonable enough; it's concerned with XUL windows, not tab elements in the
* window's XUL document.
*
* However, even if we attach TabOpen and TabClose event listeners to each XUL
* window as soon as it is created:
*
* - we do not receive a TabOpen event for the initial empty tab of a new XUL
* window; and
*
* - we do not receive TabClose events for the tabs of a XUL window that has
* been closed.
*
* This means that TabOpen and TabClose events alone are not sufficient to
* maintain an accurate list of live tabs and mark tab actors as closed
* promptly. Our nsIWindowMediatorListener onCloseWindow handler must find and
* exit all actors for tabs that were in the closing window.
*
* Since this is a bit hairy, we don't make each individual attached tab actor
* responsible for noticing when it has been closed; we watch for that, and
* promise to call each actor's 'exit' method when it's closed, regardless of
* how we learn the news.
*
* - nsIWindowMediator locks
*
* nsIWindowMediator holds a lock protecting its list of top-level windows
* while it calls nsIWindowMediatorListener methods. nsIWindowMediator's
* GetEnumerator method also tries to acquire that lock. Thus, enumerating
* windows from within a listener method deadlocks (bug 873589). Rah. One
* can sometimes work around this by leaving the enumeration for a later
* tick.
*
* - Dragging tabs between windows:
*
* When a tab is dragged from one desktop window to another, we receive a
* TabOpen event for the new tab, and a TabClose event for the old tab; tab XUL
* elements do not really move from one document to the other (although their
* linked browser's content window objects do).
*
* However, while we could thus assume that each tab stays with the XUL window
* it belonged to when it was created, I'm not sure this is behavior one should
* rely upon. When a XUL window is closed, we take the less efficient, more
* conservative approach of simply searching the entire table for actors that
* belong to the closing XUL window, rather than trying to somehow track which
* XUL window each tab belongs to.
*/
function BrowserRootActor(aConnection)
function BrowserTabList(aConnection)
{
this.conn = aConnection;
this._tabActors = new WeakMap();
this._tabActorPool = null;
// A map of actor names to actor instances provided by extensions.
this._extraActors = {};
this._connection = aConnection;
this.onTabClosed = this.onTabClosed.bind(this);
windowMediator.addListener(this);
/*
* The XUL document of a tabbed browser window has "tab" elements, whose
* 'linkedBrowser' JavaScript properties are "browser" elements; those
* browsers' 'contentWindow' properties are wrappers on the tabs' content
* window objects.
*
* This map's keys are "browser" XUL elements; it maps each browser element
* to the tab actor we've created for its content window, if we've created
* one. This map serves several roles:
*
* - During iteration, we use it to find actors we've created previously.
*
* - On a TabClose event, we use it to find the tab's actor and exit it.
*
* - When the onCloseWindow handler is called, we iterate over it to find all
* tabs belonging to the closing XUL window, and exit them.
*
* - When it's empty, and the onListChanged hook is null, we know we can
* stop listening for events and notifications.
*
* We listen for TabClose events and onCloseWindow notifications in order to
* send onListChanged notifications, but also to tell actors when their
* referent has gone away and remove entries for dead browsers from this map.
* If that code is working properly, neither this map nor the actors in it
* should ever hold dead tabs alive.
*/
this._actorByBrowser = new Map();
/* The current onListChanged handler, or null. */
this._onListChanged = null;
/*
* True if we've been iterated over since we last called our onListChanged
* hook.
*/
this._mustNotify = false;
/* True if we're testing, and should throw if consistency checks fail. */
this._testing = false;
}
BrowserRootActor.prototype = {
BrowserTabList.prototype.constructor = BrowserTabList;
/**
* Return a 'hello' packet as specified by the Remote Debugging Protocol.
*/
sayHello: function BRA_sayHello() {
return {
from: "root",
applicationType: "browser",
traits: {
sources: true
BrowserTabList.prototype.iterator = function() {
let topXULWindow = windowMediator.getMostRecentWindow("navigator:browser");
// As a sanity check, make sure all the actors presently in our map get
// picked up when we iterate over all windows' tabs.
let initialMapSize = this._actorByBrowser.size;
let foundCount = 0;
// To avoid mysterious behavior if tabs are closed or opened mid-iteration,
// we update the map first, and then make a second pass over it to yield
// the actors. Thus, the sequence yielded is always a snapshot of the
// actors that were live when we began the iteration.
// Iterate over all navigator:browser XUL windows.
for (let win of allAppShellDOMWindows("navigator:browser")) {
let selectedTab = win.gBrowser.selectedBrowser;
// For each tab in this XUL window, ensure that we have an actor for
// it, reusing existing actors where possible. We actually iterate
// over 'browser' XUL elements, and BrowserTabActor uses
// browser.contentWindow.wrappedJSObject as the debuggee global.
for (let browser of win.gBrowser.browsers) {
// Do we have an existing actor for this browser? If not, create one.
let actor = this._actorByBrowser.get(browser);
if (actor) {
foundCount++;
} else {
actor = new BrowserTabActor(this._connection, browser, win.gBrowser);
this._actorByBrowser.set(browser, actor);
}
};
},
/**
* Disconnects the actor from the browser window.
*/
disconnect: function BRA_disconnect() {
windowMediator.removeListener(this);
this._extraActors = null;
// We may have registered event listeners on browser windows to
// watch for tab closes, remove those.
let e = windowMediator.getEnumerator("navigator:browser");
while (e.hasMoreElements()) {
let win = e.getNext();
this.unwatchWindow(win);
// Signal our imminent shutdown.
let evt = win.document.createEvent("Event");
evt.initEvent("Debugger:Shutdown", true, false);
win.document.documentElement.dispatchEvent(evt);
// Set the 'selected' properties on all actors correctly.
actor.selected = (win === topXULWindow && browser === selectedTab);
}
},
}
/**
* Handles the listTabs request. Builds a list of actors for the tabs running
* in the process. The actors will survive until at least the next listTabs
* request.
*/
onListTabs: function BRA_onListTabs() {
// Get actors for all the currently-running tabs (reusing existing actors
// where applicable), and store them in an ActorPool.
if (this._testing && initialMapSize !== foundCount)
throw Error("_actorByBrowser map contained actors for dead tabs");
let actorPool = new ActorPool(this.conn);
let tabActorList = [];
this._mustNotify = true;
this._checkListening();
// Walk over open browser windows.
let e = windowMediator.getEnumerator("navigator:browser");
let top = windowMediator.getMostRecentWindow("navigator:browser");
let selected;
while (e.hasMoreElements()) {
let win = e.getNext();
// Watch the window for tab closes so we can invalidate actors as needed.
this.watchWindow(win);
// List the tabs in this browser.
let selectedBrowser = win.getBrowser().selectedBrowser;
let browsers = win.getBrowser().browsers;
for each (let browser in browsers) {
if (browser == selectedBrowser && win == top) {
selected = tabActorList.length;
}
let actor = this._tabActors.get(browser);
if (!actor) {
actor = new BrowserTabActor(this.conn, browser, win.gBrowser);
actor.parentID = this.actorID;
this._tabActors.set(browser, actor);
}
actorPool.addActor(actor);
tabActorList.push(actor);
}
}
this._createExtraActors(DebuggerServer.globalActorFactories, actorPool);
// Now drop the old actorID -> actor map. Actors that still mattered were
// added to the new map, others will go away.
if (this._tabActorPool) {
this.conn.removeActorPool(this._tabActorPool);
}
this._tabActorPool = actorPool;
this.conn.addActorPool(this._tabActorPool);
let response = {
"from": "root",
"selected": selected,
"tabs": [actor.grip() for (actor of tabActorList)]
};
this._appendExtraActors(response);
return response;
},
/* Support for DebuggerServer.addGlobalActor. */
_createExtraActors: CommonCreateExtraActors,
_appendExtraActors: CommonAppendExtraActors,
/**
* Watch a window that was visited during onListTabs for
* tab closures.
*/
watchWindow: function BRA_watchWindow(aWindow) {
this.getTabContainer(aWindow).addEventListener("TabClose",
this.onTabClosed,
false);
},
/**
* Stop watching a window for tab closes.
*/
unwatchWindow: function BRA_unwatchWindow(aWindow) {
this.getTabContainer(aWindow).removeEventListener("TabClose",
this.onTabClosed);
this.exitTabActor(aWindow);
},
/**
* Return the tab container for the specified window.
*/
getTabContainer: function BRA_getTabContainer(aWindow) {
return aWindow.getBrowser().tabContainer;
},
/**
* When a tab is closed, exit its tab actor. The actor
* will be dropped at the next listTabs request.
*/
onTabClosed:
makeInfallible(function BRA_onTabClosed(aEvent) {
this.exitTabActor(aEvent.target.linkedBrowser);
}, "BrowserRootActor.prototype.onTabClosed"),
/**
* Exit the tab actor of the specified tab.
*/
exitTabActor: function BRA_exitTabActor(aWindow) {
let actor = this._tabActors.get(aWindow);
if (actor) {
this._tabActors.delete(actor.browser);
actor.exit();
}
},
// ChromeDebuggerActor hooks.
/**
* Add the specified actor to the default actor pool connection, in order to
* keep it alive as long as the server is. This is used by breakpoints in the
* thread and chrome debugger actors.
*
* @param actor aActor
* The actor object.
*/
addToParentPool: function BRA_addToParentPool(aActor) {
this.conn.addActor(aActor);
},
/**
* Remove the specified actor from the default actor pool.
*
* @param BreakpointActor aActor
* The actor object.
*/
removeFromParentPool: function BRA_removeFromParentPool(aActor) {
this.conn.removeActor(aActor);
},
/**
* Prepare to enter a nested event loop by disabling debuggee events.
*/
preNest: function BRA_preNest() {
// Disable events in all open windows.
let e = windowMediator.getEnumerator(null);
while (e.hasMoreElements()) {
let win = e.getNext();
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.suppressEventHandling(true);
windowUtils.suspendTimeouts();
}
},
/**
* Prepare to exit a nested event loop by enabling debuggee events.
*/
postNest: function BRA_postNest(aNestData) {
// Enable events in all open windows.
let e = windowMediator.getEnumerator(null);
while (e.hasMoreElements()) {
let win = e.getNext();
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.resumeTimeouts();
windowUtils.suppressEventHandling(false);
}
},
// nsIWindowMediatorListener.
onWindowTitleChange: function BRA_onWindowTitleChange(aWindow, aTitle) { },
onOpenWindow: function BRA_onOpenWindow(aWindow) { },
onCloseWindow:
makeInfallible(function BRA_onCloseWindow(aWindow) {
// An nsIWindowMediatorListener's onCloseWindow method gets passed all
// sorts of windows; we only care about the tab containers. Those have
// 'getBrowser' methods.
if (aWindow.getBrowser) {
this.unwatchWindow(aWindow);
}
}, "BrowserRootActor.prototype.onCloseWindow"),
/* Yield the values. */
for (let [browser, actor] of this._actorByBrowser) {
yield actor;
}
};
Object.defineProperty(BrowserTabList.prototype, 'onListChanged', {
enumerable: true, configurable:true,
get: function() { return this._onListChanged; },
set: function(v) {
if (v !== null && typeof v !== 'function') {
throw Error("onListChanged property may only be set to 'null' or a function");
}
this._onListChanged = v;
this._checkListening();
}
});
/**
* The request types this actor can handle.
* The set of tabs has changed somehow. Call our onListChanged handler, if
* one is set, and if we haven't already called it since the last iteration.
*/
BrowserRootActor.prototype.requestTypes = {
"listTabs": BrowserRootActor.prototype.onListTabs
BrowserTabList.prototype._notifyListChanged = function() {
if (!this._onListChanged)
return;
if (this._mustNotify) {
this._onListChanged();
this._mustNotify = false;
}
};
/**
* Creates a tab actor for handling requests to a browser tab, like attaching
* and detaching.
* Exit |aActor|, belonging to |aBrowser|, and notify the onListChanged
* handle if needed.
*/
BrowserTabList.prototype._handleActorClose = function(aActor, aBrowser) {
if (this._testing) {
if (this._actorByBrowser.get(aBrowser) !== aActor) {
throw Error("BrowserTabActor not stored in map under given browser");
}
if (aActor.browser !== aBrowser) {
throw Error("actor's browser and map key don't match");
}
}
this._actorByBrowser.delete(aBrowser);
aActor.exit();
this._notifyListChanged();
this._checkListening();
};
/**
* Make sure we are listening or not listening for activity elsewhere in
* the browser, as appropriate. Other than setting up newly created XUL
* windows, all listener / observer connection and disconnection should
* happen here.
*/
BrowserTabList.prototype._checkListening = function() {
/*
* If we have an onListChanged handler that we haven't sent an announcement
* to since the last iteration, we need to watch for tab creation.
*
* Oddly, we don't need to watch for 'close' events here. If our actor list
* is empty, then either it was empty the last time we iterated, and no
* close events are possible, or it was not empty the last time we
* iterated, but all the actors have since been closed, and we must have
* sent a notification already when they closed.
*/
this._listenForEventsIf(this._onListChanged && this._mustNotify,
"_listeningForTabOpen", ["TabOpen", "TabSelect"]);
/* If we have live actors, we need to be ready to mark them dead. */
this._listenForEventsIf(this._actorByBrowser.size > 0,
"_listeningForTabClose", ["TabClose"]);
/*
* We must listen to the window mediator in either case, since that's the
* only way to find out about tabs that come and go when top-level windows
* are opened and closed.
*/
this._listenToMediatorIf((this._onListChanged && this._mustNotify) ||
(this._actorByBrowser.size > 0));
};
/*
* Add or remove event listeners for all XUL windows.
*
* @param aShouldListen boolean
* True if we should add event handlers; false if we should remove them.
* @param aGuard string
* The name of a guard property of 'this', indicating whether we're
* already listening for those events.
* @param aEventNames array of strings
* An array of event names.
*/
BrowserTabList.prototype._listenForEventsIf = function(aShouldListen, aGuard, aEventNames) {
if (!aShouldListen !== !this[aGuard]) {
let op = aShouldListen ? "addEventListener" : "removeEventListener";
for (let win of allAppShellDOMWindows("navigator:browser")) {
for (let name of aEventNames) {
win[op](name, this, false);
}
}
this[aGuard] = aShouldListen;
}
};
/**
* Implement nsIDOMEventListener.
*/
BrowserTabList.prototype.handleEvent = makeInfallible(function(aEvent) {
switch (aEvent.type) {
case "TabOpen":
case "TabSelect":
/* Don't create a new actor; iterate will take care of that. Just notify. */
this._notifyListChanged();
this._checkListening();
break;
case "TabClose":
let browser = aEvent.target.linkedBrowser;
let actor = this._actorByBrowser.get(browser);
if (actor) {
this._handleActorClose(actor, browser);
}
break;
}
}, "BrowserTabList.prototype.handleEvent");
/*
* If |aShouldListen| is true, ensure we've registered a listener with the
* window mediator. Otherwise, ensure we haven't registered a listener.
*/
BrowserTabList.prototype._listenToMediatorIf = function(aShouldListen) {
if (!aShouldListen !== !this._listeningToMediator) {
let op = aShouldListen ? "addListener" : "removeListener";
windowMediator[op](this);
this._listeningToMediator = aShouldListen;
}
};
/**
* nsIWindowMediatorListener implementation.
*
* See _onTabClosed for explanation of why we needn't actually tweak any
* actors or tables here.
*
* An nsIWindowMediatorListener's methods get passed all sorts of windows; we
* only care about the tab containers. Those have 'getBrowser' methods.
*/
BrowserTabList.prototype.onWindowTitleChange = () => { };
BrowserTabList.prototype.onOpenWindow = makeInfallible(function(aWindow) {
/*
* You can hardly do anything at all with a XUL window at this point; it
* doesn't even have its document yet. Wait until its document has
* loaded, and then see what we've got. This also avoids
* nsIWindowMediator enumeration from within listeners (bug 873589).
*/
aWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
aWindow.addEventListener("load", makeInfallible(handleLoad.bind(this)), false);
function handleLoad(aEvent) {
/* We don't want any further load events from this window. */
aWindow.removeEventListener("load", handleLoad, false);
if (appShellDOMWindowType(aWindow) !== "navigator:browser")
return;
// Listen for future tab activity.
if (this._listeningForTabOpen) {
aWindow.addEventListener("TabOpen", this, false);
aWindow.addEventListener("TabSelect", this, false);
}
if (this._listeningForTabClose) {
aWindow.addEventListener("TabClose", this, false);
}
// As explained above, we will not receive a TabOpen event for this
// document's initial tab, so we must notify our client of the new tab
// this will have.
this._notifyListChanged();
}
}, "BrowserTabList.prototype.onOpenWindow");
BrowserTabList.prototype.onCloseWindow = makeInfallible(function(aWindow) {
aWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
if (appShellDOMWindowType(aWindow) !== "navigator:browser")
return;
/*
* nsIWindowMediator deadlocks if you call its GetEnumerator method from
* a nsIWindowMediatorListener's onCloseWindow hook (bug 873589), so
* handle the close in a different tick.
*/
Services.tm.currentThread.dispatch(makeInfallible(() => {
/*
* Scan the entire map for actors representing tabs that were in this
* top-level window, and exit them.
*/
for (let [browser, actor] of this._actorByBrowser) {
/* The browser document of a closed window has no default view. */
if (!browser.ownerDocument.defaultView) {
this._handleActorClose(actor, browser);
}
}
}, "BrowserTabList.prototype.onCloseWindow's delayed body"), 0);
}, "BrowserTabList.prototype.onCloseWindow");
/**
* Creates a tab actor for handling requests to a browser tab, like
* attaching and detaching. BrowserTabActor respects the actor factories
* registered with DebuggerServer.addTabActor.
*
* @param aConnection DebuggerServerConnection
* The conection to the client.
@ -360,7 +468,7 @@ BrowserTabActor.prototype = {
get browser() { return this._browser; },
get exited() { return !this.browser; },
get attached() { return !!this._attached },
get attached() { return !!this._attached; },
_tabPool: null,
get tabActorPool() { return this._tabPool; },
@ -439,7 +547,7 @@ BrowserTabActor.prototype = {
let response = {
actor: this.actorID,
title: this.title,
url: this.url,
url: this.url
};
// Walk over tab actors added by extensions and add them to a new ActorPool.
@ -666,7 +774,7 @@ BrowserTabActor.prototype = {
}
catch (ex) { }
return isNative;
},
}
};
/**
@ -722,7 +830,7 @@ DebuggerProgressListener.prototype = {
type: "tabNavigated",
url: aRequest.URI.spec,
nativeConsoleAPI: true,
state: "start",
state: "start"
});
} else if (isStop) {
if (this._tabActor.threadActor.state == "running") {
@ -736,7 +844,7 @@ DebuggerProgressListener.prototype = {
url: this._tabActor.url,
title: this._tabActor.title,
nativeConsoleAPI: this._tabActor.hasNativeConsoleAPI(window),
state: "stop",
state: "stop"
});
}
}, "DebuggerProgressListener.prototype.onStateChange"),

Просмотреть файл

@ -114,7 +114,7 @@ var DebuggerServer = {
this.xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
this.initTransport(aAllowConnectionCallback);
this.addActors("resource://gre/modules/devtools/server/actors/script.js");
this.addActors("resource://gre/modules/devtools/server/actors/root.js");
this._initialized = true;
},
@ -183,6 +183,7 @@ var DebuggerServer = {
*/
addBrowserActors: function DS_addBrowserActors() {
this.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js");
this.addActors("resource://gre/modules/devtools/server/actors/script.js");
this.addGlobalActor(this.ChromeDebuggerActor, "chromeDebugger");
this.addActors("resource://gre/modules/devtools/server/actors/webconsole.js");
this.addActors("resource://gre/modules/devtools/server/actors/gcli.js");
@ -271,7 +272,6 @@ var DebuggerServer = {
return clientTransport;
},
// nsIServerSocketListener implementation
onSocketAccepted:
@ -441,7 +441,7 @@ ActorPool.prototype = {
*
* @param aActor object
* The actor implementation. If the object has a
* 'disconnected' property, it will be called when the actor
* 'disconnect' property, it will be called when the actor
* pool is cleaned up.
*/
addActor: function AP_addActor(aActor) {

Просмотреть файл

@ -11,9 +11,9 @@ relativesrcdir = @relativesrcdir@
include $(DEPTH)/config/autoconf.mk
MOCHITEST_CHROME_FILES = \
test_unsafeDereference.html \
nonchrome_unsafeDereference.html \
MOCHITEST_CHROME_FILES = \
test_unsafeDereference.html \
nonchrome_unsafeDereference.html \
$(NULL)
include $(topsrcdir)/config/rules.mk

Просмотреть файл

@ -156,6 +156,7 @@ function attachTestTabAndResume(aClient, aTitle, aCallback) {
*/
function initTestDebuggerServer()
{
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/script.js");
DebuggerServer.addActors("resource://test/testactors.js");
// Allow incoming connections.
DebuggerServer.init(function () { return true; });
@ -163,7 +164,9 @@ function initTestDebuggerServer()
function initSourcesBackwardsCompatDebuggerServer()
{
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/root.js");
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js");
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/script.js");
DebuggerServer.addActors("resource://test/testcompatactors.js");
DebuggerServer.init(function () { return true; });
}

Просмотреть файл

@ -6,15 +6,15 @@ DebuggerServer.addTestGlobal = function(aGlobal) {
gTestGlobals.push(aGlobal);
};
function createRootActor(aConnection)
{
return new TestRootActor(aConnection);
}
function TestRootActor(aConnection)
{
// A mock tab list, for use by tests. This simply presents each global in
// gTestGlobals as a tab, and the list is fixed: it never calls its
// onListChanged handler.
//
// As implemented now, we consult gTestGlobals when we're constructed, not
// when we're iterated over, so tests have to add their globals before the
// root actor is created.
function TestTabList(aConnection) {
this.conn = aConnection;
this.actorID = "root";
// An array of actors for each global added with
// DebuggerServer.addTestGlobal.
@ -25,37 +25,33 @@ function TestRootActor(aConnection)
for (let global of gTestGlobals) {
let actor = new TestTabActor(aConnection, global);
actor.selected = false;
this._tabActors.push(actor);
this._tabActorPool.addActor(actor);
}
if (this._tabActors.length > 0) {
this._tabActors[0].selected = true;
}
aConnection.addActorPool(this._tabActorPool);
}
TestRootActor.prototype = {
constructor: TestRootActor,
sayHello: function () {
return { from: "root",
applicationType: "xpcshell-tests",
testConnectionPrefix: this.conn.prefix,
traits: {
sources: true
}
};
},
onListTabs: function(aRequest) {
return { tabs:[actor.grip() for (actor of this._tabActors)], selected:0 };
},
onEcho: function(aRequest) { return aRequest; },
TestTabList.prototype = {
constructor: TestTabList,
iterator: function() {
for (let actor of this._tabActors) {
yield actor;
}
}
};
TestRootActor.prototype.requestTypes = {
"listTabs": TestRootActor.prototype.onListTabs,
"echo": TestRootActor.prototype.onEcho
};
function createRootActor(aConnection)
{
let root = new RootActor(aConnection,
{ tabList: new TestTabList(aConnection) });
root.applicationType = "xpcshell-tests";
return root;
}
function TestTabActor(aConnection, aGlobal)
{
@ -68,7 +64,7 @@ function TestTabActor(aConnection, aGlobal)
TestTabActor.prototype = {
constructor: TestTabActor,
actorPrefix:"TestTabActor",
actorPrefix: "TestTabActor",
grip: function() {
return { actor: this.actorID, title: this._global.__name };