зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
a211a7d924
Коммит
3923f3e983
|
@ -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 };
|
||||
|
|
Загрузка…
Ссылка в новой задаче