зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to m-i.
This commit is contained in:
Коммит
b7fa0c2492
|
@ -6,120 +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
|
||||
// Get the chrome debugger actor.
|
||||
let actor = this._chromeDebugger;
|
||||
if (!actor) {
|
||||
actor = new ChromeDebuggerActor(this);
|
||||
actor.parentID = this.actorID;
|
||||
this._chromeDebugger = actor;
|
||||
actorPool.addActor(actor);
|
||||
}
|
||||
|
||||
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()],
|
||||
"chromeDebugger": this._chromeDebugger.actorID
|
||||
#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;
|
||||
},
|
||||
|
@ -127,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;
|
||||
},
|
||||
|
@ -135,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,8 @@ 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");
|
||||
#endif
|
||||
|
|
|
@ -129,12 +129,18 @@ function setLinks(aLinks) {
|
|||
});
|
||||
}
|
||||
|
||||
clearHistory(function () {
|
||||
fillHistory(links, function () {
|
||||
NewTabUtils.links.populateCache(function () {
|
||||
NewTabUtils.allPages.update();
|
||||
TestRunner.next();
|
||||
}, true);
|
||||
// Call populateCache() once to make sure that all link fetching that is
|
||||
// currently in progress has ended. We clear the history, fill it with the
|
||||
// given entries and call populateCache() now again to make sure the cache
|
||||
// has the desired contents.
|
||||
NewTabUtils.links.populateCache(function () {
|
||||
clearHistory(function () {
|
||||
fillHistory(links, function () {
|
||||
NewTabUtils.links.populateCache(function () {
|
||||
NewTabUtils.allPages.update();
|
||||
TestRunner.next();
|
||||
}, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -28,6 +28,9 @@ tests.gatTest = function(options) {
|
|||
hints: '',
|
||||
markup: 'VVVVVVVVVVVVVVVVVVVVV',
|
||||
status: 'VALID'
|
||||
},
|
||||
exec: {
|
||||
output: 'There are no add-ons of that type installed.'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -37,6 +40,9 @@ tests.gatTest = function(options) {
|
|||
hints: '',
|
||||
markup: 'VVVVVVVVVVVVVVVVVVVV',
|
||||
status: 'VALID'
|
||||
},
|
||||
exec: {
|
||||
output: [/The following/, /Mochitest/, /Special Powers/]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -46,6 +52,9 @@ tests.gatTest = function(options) {
|
|||
hints: '',
|
||||
markup: 'VVVVVVVVVVVVVVVVV',
|
||||
status: 'VALID'
|
||||
},
|
||||
exec: {
|
||||
output: 'There are no add-ons of that type installed.'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -55,6 +64,9 @@ tests.gatTest = function(options) {
|
|||
hints: '',
|
||||
markup: 'VVVVVVVVVVVVVVVVV',
|
||||
status: 'VALID'
|
||||
},
|
||||
exec: {
|
||||
output: [/Test Plug-in/, /Second Test Plug-in/]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -64,6 +76,9 @@ tests.gatTest = function(options) {
|
|||
hints: '',
|
||||
markup: 'VVVVVVVVVVVVVVVV',
|
||||
status: 'VALID'
|
||||
},
|
||||
exec: {
|
||||
output: [/following themes/, /Default/]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -73,6 +88,10 @@ tests.gatTest = function(options) {
|
|||
hints: '',
|
||||
markup: 'VVVVVVVVVVVVVV',
|
||||
status: 'VALID'
|
||||
},
|
||||
exec: {
|
||||
output: [/The following/, /Default/, /Mochitest/, /Test Plug-in/,
|
||||
/Second Test Plug-in/, /Special Powers/]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -82,6 +101,9 @@ tests.gatTest = function(options) {
|
|||
hints: '',
|
||||
markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
|
||||
status: 'VALID'
|
||||
},
|
||||
exec: {
|
||||
output: 'Test Plug-in 1.0.0.0 disabled.'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -106,7 +128,7 @@ tests.gatTest = function(options) {
|
|||
}
|
||||
},
|
||||
exec: {
|
||||
completed: false
|
||||
output: 'Test Plug-in 1.0.0.0 enabled.'
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
|
||||
const NEW_SOURCE_IGNORED_URLS = ["debugger eval code", "self-hosted"];
|
||||
const NEW_SOURCE_IGNORED_URLS = ["debugger eval code", "self-hosted", "XStringBundle"];
|
||||
const NEW_SOURCE_DISPLAY_DELAY = 200; // ms
|
||||
const FETCH_SOURCE_RESPONSE_DELAY = 50; // ms
|
||||
const FRAME_STEP_CLEAR_DELAY = 100; // ms
|
||||
|
|
|
@ -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 \
|
||||
|
@ -33,6 +34,7 @@ MOCHITEST_BROWSER_TESTS = \
|
|||
browser_dbg_propertyview-09.js \
|
||||
browser_dbg_propertyview-10.js \
|
||||
browser_dbg_propertyview-11.js \
|
||||
browser_dbg_propertyview-12.js \
|
||||
browser_dbg_propertyview-edit-value.js \
|
||||
browser_dbg_propertyview-edit-watch.js \
|
||||
browser_dbg_propertyview-data-big.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();
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// This test checks that we properly set the frozen, sealed, and non-extensbile
|
||||
// attributes on variables so that the F/S/N is shown in the variables view.
|
||||
|
||||
var gPane = null;
|
||||
var gTab = null;
|
||||
var gDebuggee = null;
|
||||
var gDebugger = null;
|
||||
|
||||
function test() {
|
||||
debug_tab_pane(STACK_URL, function(aTab, aDebuggee, aPane) {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPane = aPane;
|
||||
gDebugger = gPane.panelWin;
|
||||
|
||||
testFSN();
|
||||
});
|
||||
}
|
||||
|
||||
function testFSN() {
|
||||
gDebugger.addEventListener("Debugger:FetchedVariables", function _onFetchedVariables() {
|
||||
gDebugger.removeEventListener("Debugger:FetchedVariables", _onFetchedVariables, false);
|
||||
runTest();
|
||||
}, false);
|
||||
|
||||
gDebuggee.eval("(" + function () {
|
||||
var frozen = Object.freeze({});
|
||||
var sealed = Object.seal({});
|
||||
var nonExtensible = Object.preventExtensions({});
|
||||
var extensible = {};
|
||||
var string = "foo bar baz";
|
||||
|
||||
debugger;
|
||||
} + "())");
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
let hasNoneTester = function (aVariable) {
|
||||
ok(!aVariable.hasAttribute("frozen"),
|
||||
"The variable should not be frozen");
|
||||
ok(!aVariable.hasAttribute("sealed"),
|
||||
"The variable should not be sealed");
|
||||
ok(!aVariable.hasAttribute("non-extensible"),
|
||||
"The variable should be extensible");
|
||||
};
|
||||
|
||||
let testers = {
|
||||
frozen: function (aVariable) {
|
||||
ok(aVariable.hasAttribute("frozen"),
|
||||
"The variable should be frozen")
|
||||
},
|
||||
sealed: function (aVariable) {
|
||||
ok(aVariable.hasAttribute("sealed"),
|
||||
"The variable should be sealed")
|
||||
},
|
||||
nonExtensible: function (aVariable) {
|
||||
ok(aVariable.hasAttribute("non-extensible"),
|
||||
"The variable should be non-extensible")
|
||||
},
|
||||
extensible: hasNoneTester,
|
||||
string: hasNoneTester,
|
||||
arguments: hasNoneTester,
|
||||
this: hasNoneTester
|
||||
};
|
||||
|
||||
let variables = gDebugger.DebuggerView.Variables._parent
|
||||
.querySelectorAll(".variable-or-property");
|
||||
|
||||
for (let v of variables) {
|
||||
let name = v.querySelector(".name").getAttribute("value");
|
||||
let tester = testers[name];
|
||||
delete testers[name];
|
||||
ok(tester, "We should have a tester for the '" + name + "' variable.");
|
||||
tester(v);
|
||||
}
|
||||
|
||||
is(Object.keys(testers).length, 0,
|
||||
"We should have run and removed all the testers.");
|
||||
|
||||
closeDebuggerAndFinish();
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
removeTab(gTab);
|
||||
gPane = null;
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gDebugger = null;
|
||||
});
|
|
@ -225,5 +225,7 @@ window.setPanel = function(panel) {
|
|||
}
|
||||
|
||||
window.onunload = function() {
|
||||
window.fontInspector.destroy();
|
||||
if (window.fontInspector) {
|
||||
window.fontInspector.destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,15 +7,6 @@
|
|||
const {Cc, Ci, Cu} = require("chrome");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
// Add a couple of globals that we use all over this package.
|
||||
let loaderOptions = require("@loader/options")
|
||||
|
||||
loaderOptions.globals.loader = {
|
||||
lazyGetter: XPCOMUtils.defineLazyGetter.bind(XPCOMUtils),
|
||||
lazyImporter: XPCOMUtils.defineLazyModuleGetter.bind(XPCOMUtils)
|
||||
};
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
|
||||
|
|
|
@ -1066,7 +1066,7 @@ var Scratchpad = {
|
|||
*/
|
||||
openErrorConsole: function SP_openErrorConsole()
|
||||
{
|
||||
this.browserWindow.toJavaScriptConsole();
|
||||
this.browserWindow.HUDConsoleUI.toggleBrowserConsole();
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -2364,21 +2364,15 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
|
|||
|
||||
let tooltip = document.createElement("tooltip");
|
||||
tooltip.id = "tooltip-" + this._idString;
|
||||
|
||||
let configurableLabel = document.createElement("label");
|
||||
let enumerableLabel = document.createElement("label");
|
||||
let writableLabel = document.createElement("label");
|
||||
let safeGetterLabel = document.createElement("label");
|
||||
configurableLabel.setAttribute("value", "configurable");
|
||||
enumerableLabel.setAttribute("value", "enumerable");
|
||||
writableLabel.setAttribute("value", "writable");
|
||||
safeGetterLabel.setAttribute("value", "native-getter");
|
||||
|
||||
tooltip.setAttribute("orient", "horizontal");
|
||||
tooltip.appendChild(configurableLabel);
|
||||
tooltip.appendChild(enumerableLabel);
|
||||
tooltip.appendChild(writableLabel);
|
||||
tooltip.appendChild(safeGetterLabel);
|
||||
|
||||
let labels = ["configurable", "enumerable", "writable", "native-getter",
|
||||
"frozen", "sealed", "non-extensible"];
|
||||
for (let label of labels) {
|
||||
let labelElement = document.createElement("label");
|
||||
labelElement.setAttribute("value", label);
|
||||
tooltip.appendChild(labelElement);
|
||||
}
|
||||
|
||||
this._target.appendChild(tooltip);
|
||||
this._target.setAttribute("tooltip", tooltip.id);
|
||||
|
@ -2408,14 +2402,27 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
|
|||
if (this.ownerView.eval) {
|
||||
this._target.setAttribute("editable", "");
|
||||
}
|
||||
if (!descriptor.null && !descriptor.configurable) {
|
||||
this._target.setAttribute("non-configurable", "");
|
||||
}
|
||||
if (!descriptor.null && !descriptor.enumerable) {
|
||||
this._target.setAttribute("non-enumerable", "");
|
||||
}
|
||||
if (!descriptor.null && !descriptor.writable && !this.ownerView.getter && !this.ownerView.setter) {
|
||||
this._target.setAttribute("non-writable", "");
|
||||
if (!descriptor.null) {
|
||||
if (!descriptor.configurable) {
|
||||
this._target.setAttribute("non-configurable", "");
|
||||
}
|
||||
if (!descriptor.enumerable) {
|
||||
this._target.setAttribute("non-enumerable", "");
|
||||
}
|
||||
if (!descriptor.writable && !this.ownerView.getter && !this.ownerView.setter) {
|
||||
this._target.setAttribute("non-writable", "");
|
||||
}
|
||||
if (descriptor.value && typeof descriptor.value == "object") {
|
||||
if (descriptor.value.frozen) {
|
||||
this._target.setAttribute("frozen", "");
|
||||
}
|
||||
if (descriptor.value.sealed) {
|
||||
this._target.setAttribute("sealed", "");
|
||||
}
|
||||
if (!descriptor.value.extensible) {
|
||||
this._target.setAttribute("non-extensible", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (descriptor && "getterValue" in descriptor) {
|
||||
this._target.setAttribute("safe-getter", "");
|
||||
|
|
|
@ -52,9 +52,17 @@ function runTests()
|
|||
let rulesTable = propView.matchedSelectorsContainer;
|
||||
let matchedExpander = propView.matchedExpander;
|
||||
|
||||
info("Adding focus event handler to search filter");
|
||||
searchbar.addEventListener("focus", function searchbarFocused() {
|
||||
searchbar.removeEventListener("focus", searchbarFocused);
|
||||
info("search filter is focused");
|
||||
info("tabbing to property expander node");
|
||||
EventUtils.synthesizeKey("VK_TAB", {}, iframe.contentWindow);
|
||||
});
|
||||
|
||||
info("Adding focus event handler to property expander");
|
||||
matchedExpander.addEventListener("focus", function expanderFocused() {
|
||||
this.removeEventListener("focus", expanderFocused);
|
||||
matchedExpander.removeEventListener("focus", expanderFocused);
|
||||
info("property expander is focused");
|
||||
info("checking expand / collapse");
|
||||
testKey(iframe.contentWindow, "VK_SPACE", rulesTable);
|
||||
|
@ -65,14 +73,6 @@ function runTests()
|
|||
finishUp();
|
||||
});
|
||||
|
||||
info("Adding focus event handler to search filter");
|
||||
searchbar.addEventListener("focus", function searchbarFocused() {
|
||||
this.removeEventListener("focus", searchbarFocused);
|
||||
info("search filter is focused");
|
||||
info("tabbing to property expander node");
|
||||
EventUtils.synthesizeKey("VK_TAB", {}, iframe.contentWindow);
|
||||
});
|
||||
|
||||
info("Making sure that the style inspector panel is focused");
|
||||
SimpleTest.waitForFocus(function windowFocused() {
|
||||
info("window is focused");
|
||||
|
|
|
@ -338,6 +338,12 @@ WebConsole.prototype = {
|
|||
viewSourceInStyleEditor:
|
||||
function WC_viewSourceInStyleEditor(aSourceURL, aSourceLine)
|
||||
{
|
||||
let toolbox = gDevTools.getToolbox(this.target);
|
||||
if (!toolbox) {
|
||||
this.viewSource(aSourceURL, aSourceLine);
|
||||
return;
|
||||
}
|
||||
|
||||
gDevTools.showToolbox(this.target, "styleeditor").then(function(toolbox) {
|
||||
try {
|
||||
toolbox.getCurrentPanel().selectStyleSheet(aSourceURL, aSourceLine);
|
||||
|
|
|
@ -132,6 +132,9 @@ MOCHITEST_BROWSER_FILES = \
|
|||
browser_bug_871156_ctrlw_close_tab.js \
|
||||
browser_console_private_browsing.js \
|
||||
browser_console_nsiconsolemessage.js \
|
||||
browser_webconsole_bug_817834_add_edited_input_to_history.js \
|
||||
browser_console_addonsdk_loader_exception.js \
|
||||
browser_console_error_source_click.js \
|
||||
head.js \
|
||||
$(NULL)
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// Check that exceptions from scripts loaded with the addon-sdk loader are
|
||||
// opened correctly in View Source from the Browser Console.
|
||||
// See bug 866950.
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf8,<p>hello world from bug 866950";
|
||||
|
||||
function test()
|
||||
{
|
||||
let webconsole, browserconsole;
|
||||
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
|
||||
openConsole(null, consoleOpened);
|
||||
}, true);
|
||||
|
||||
function consoleOpened(hud)
|
||||
{
|
||||
ok(hud, "web console opened");
|
||||
webconsole = hud;
|
||||
HUDConsoleUI.toggleBrowserConsole().then(browserConsoleOpened);
|
||||
}
|
||||
|
||||
function browserConsoleOpened(hud)
|
||||
{
|
||||
ok(hud, "browser console opened");
|
||||
browserconsole = hud;
|
||||
|
||||
// Cause an exception in a script loaded with the addon-sdk loader.
|
||||
let toolbox = gDevTools.getToolbox(webconsole.target);
|
||||
let oldPanels = toolbox._toolPanels;
|
||||
toolbox._toolPanels = null;
|
||||
function fixToolbox()
|
||||
{
|
||||
toolbox._toolPanels = oldPanels;
|
||||
}
|
||||
|
||||
info("generate exception and wait for message");
|
||||
|
||||
executeSoon(() => {
|
||||
executeSoon(fixToolbox);
|
||||
expectUncaughtException();
|
||||
toolbox.getToolPanels();
|
||||
});
|
||||
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
text: "TypeError: this._toolPanels is null",
|
||||
category: CATEGORY_JS,
|
||||
severity: SEVERITY_ERROR,
|
||||
},
|
||||
],
|
||||
}).then((results) => {
|
||||
fixToolbox();
|
||||
onMessageFound(results);
|
||||
});
|
||||
}
|
||||
|
||||
function onMessageFound(results)
|
||||
{
|
||||
let msg = [...results[0].matched][0];
|
||||
ok(msg, "message element found");
|
||||
let locationNode = msg.querySelector(".webconsole-location");
|
||||
ok(locationNode, "message location element found");
|
||||
|
||||
let title = locationNode.getAttribute("title");
|
||||
info("location node title: " + title);
|
||||
isnot(title.indexOf(" -> "), -1, "error comes from a subscript");
|
||||
|
||||
let viewSource = browserconsole.viewSource;
|
||||
let URL = null;
|
||||
browserconsole.viewSource = (aURL) => URL = aURL;
|
||||
|
||||
EventUtils.synthesizeMouse(locationNode, 2, 2, {},
|
||||
browserconsole.iframeWindow);
|
||||
|
||||
info("view-source url: " + URL);
|
||||
isnot(URL.indexOf("toolbox.js"), -1, "expected view source URL");
|
||||
is(URL.indexOf("->"), -1, "no -> in the URL given to view-source");
|
||||
|
||||
browserconsole.viewSource = viewSource;
|
||||
|
||||
finishTest();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// Check that JS errors and CSS warnings open view source when their source link
|
||||
// is clicked in the Browser Console. See bug 877778.
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf8,<p>hello world from bug 877778 " +
|
||||
"<button onclick='foobar.explode()' " +
|
||||
"style='test-color: green-please'>click!</button>";
|
||||
function test()
|
||||
{
|
||||
let hud;
|
||||
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
HUDConsoleUI.toggleBrowserConsole().then(browserConsoleOpened);
|
||||
}, true);
|
||||
|
||||
function browserConsoleOpened(aHud)
|
||||
{
|
||||
hud = aHud;
|
||||
ok(hud, "browser console opened");
|
||||
|
||||
let button = content.document.querySelector("button");
|
||||
ok(button, "button element found");
|
||||
|
||||
info("generate exception and wait for the message");
|
||||
executeSoon(() => {
|
||||
expectUncaughtException();
|
||||
button.click();
|
||||
});
|
||||
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
text: "ReferenceError: foobar is not defined",
|
||||
category: CATEGORY_JS,
|
||||
severity: SEVERITY_ERROR,
|
||||
},
|
||||
{
|
||||
text: "Unknown property 'test-color'",
|
||||
category: CATEGORY_CSS,
|
||||
severity: SEVERITY_WARNING,
|
||||
},
|
||||
],
|
||||
}).then(onMessageFound);
|
||||
}
|
||||
|
||||
function onMessageFound(results)
|
||||
{
|
||||
let viewSource = hud.viewSource;
|
||||
let viewSourceCalled = false;
|
||||
hud.viewSource = () => viewSourceCalled = true;
|
||||
|
||||
for (let result of results) {
|
||||
viewSourceCalled = false;
|
||||
|
||||
let msg = [...results[0].matched][0];
|
||||
ok(msg, "message element found for: " + result.text);
|
||||
let locationNode = msg.querySelector(".webconsole-location");
|
||||
ok(locationNode, "message location element found");
|
||||
|
||||
EventUtils.synthesizeMouse(locationNode, 2, 2, {}, hud.iframeWindow);
|
||||
|
||||
ok(viewSourceCalled, "view source opened");
|
||||
}
|
||||
|
||||
hud.viewSource = viewSource;
|
||||
finishTest();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*
|
||||
* Contributor(s):
|
||||
* zmgmoz <zmgmoz@gmail.com>
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
// Test that user input that is not submitted in the command line input is not
|
||||
// lost after navigating in history.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=817834
|
||||
|
||||
function test() {
|
||||
addTab("data:text/html;charset=utf-8,Web Console test for bug 817834");
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, testEditedInputHistory);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function testEditedInputHistory(HUD) {
|
||||
let jsterm = HUD.jsterm;
|
||||
let inputNode = jsterm.inputNode;
|
||||
ok(!inputNode.value, "inputNode.value is empty");
|
||||
is(inputNode.selectionStart, 0);
|
||||
is(inputNode.selectionEnd, 0);
|
||||
|
||||
jsterm.setInputValue('"first item"');
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(inputNode.value, '"first item"', "null test history up");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(inputNode.value, '"first item"', "null test history down");
|
||||
|
||||
jsterm.execute();
|
||||
is(inputNode.value, "", "cleared input line after submit");
|
||||
|
||||
jsterm.setInputValue('"editing input 1"');
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(inputNode.value, '"first item"', "test history up");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(inputNode.value, '"editing input 1"',
|
||||
"test history down restores in-progress input");
|
||||
|
||||
jsterm.setInputValue('"second item"');
|
||||
jsterm.execute();
|
||||
jsterm.setInputValue('"editing input 2"');
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(inputNode.value, '"second item"', "test history up");
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(inputNode.value, '"first item"', "test history up");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(inputNode.value, '"second item"', "test history down");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(inputNode.value, '"editing input 2"',
|
||||
"test history down restores new in-progress input again");
|
||||
|
||||
executeSoon(finishTest);
|
||||
}
|
|
@ -2373,21 +2373,24 @@ WebConsoleFrame.prototype = {
|
|||
|
||||
// Create the text, which consists of an abbreviated version of the URL
|
||||
// plus an optional line number. Scratchpad URLs should not be abbreviated.
|
||||
let text;
|
||||
let displayLocation;
|
||||
let fullURL;
|
||||
|
||||
if (/^Scratchpad\/\d+$/.test(aSourceURL)) {
|
||||
text = aSourceURL;
|
||||
displayLocation = aSourceURL;
|
||||
fullURL = aSourceURL;
|
||||
}
|
||||
else {
|
||||
text = WebConsoleUtils.abbreviateSourceURL(aSourceURL);
|
||||
fullURL = aSourceURL.split(" -> ").pop();
|
||||
displayLocation = WebConsoleUtils.abbreviateSourceURL(fullURL);
|
||||
}
|
||||
|
||||
if (aSourceLine) {
|
||||
text += ":" + aSourceLine;
|
||||
displayLocation += ":" + aSourceLine;
|
||||
locationNode.sourceLine = aSourceLine;
|
||||
}
|
||||
|
||||
locationNode.setAttribute("value", text);
|
||||
locationNode.setAttribute("value", displayLocation);
|
||||
|
||||
// Style appropriately.
|
||||
locationNode.setAttribute("crop", "center");
|
||||
|
@ -2397,7 +2400,7 @@ WebConsoleFrame.prototype = {
|
|||
locationNode.classList.add("text-link");
|
||||
|
||||
// Make the location clickable.
|
||||
locationNode.addEventListener("click", function() {
|
||||
locationNode.addEventListener("click", () => {
|
||||
if (/^Scratchpad\/\d+$/.test(aSourceURL)) {
|
||||
let wins = Services.wm.getEnumerator("devtools:scratchpad");
|
||||
|
||||
|
@ -2411,16 +2414,16 @@ WebConsoleFrame.prototype = {
|
|||
}
|
||||
}
|
||||
else if (locationNode.parentNode.category == CATEGORY_CSS) {
|
||||
this.owner.viewSourceInStyleEditor(aSourceURL, aSourceLine);
|
||||
this.owner.viewSourceInStyleEditor(fullURL, aSourceLine);
|
||||
}
|
||||
else if (locationNode.parentNode.category == CATEGORY_JS ||
|
||||
locationNode.parentNode.category == CATEGORY_WEBDEV) {
|
||||
this.owner.viewSourceInDebugger(aSourceURL, aSourceLine);
|
||||
this.owner.viewSourceInDebugger(fullURL, aSourceLine);
|
||||
}
|
||||
else {
|
||||
this.owner.viewSource(aSourceURL, aSourceLine);
|
||||
this.owner.viewSource(fullURL, aSourceLine);
|
||||
}
|
||||
}.bind(this), true);
|
||||
}, true);
|
||||
|
||||
return locationNode;
|
||||
},
|
||||
|
@ -2683,8 +2686,14 @@ function JSTerm(aWebConsoleFrame)
|
|||
|
||||
this.lastCompletion = { value: null };
|
||||
this.history = [];
|
||||
this.historyIndex = 0;
|
||||
this.historyPlaceHolder = 0; // this.history.length;
|
||||
|
||||
// Holds the number of entries in history. This value is incremented in
|
||||
// this.execute().
|
||||
this.historyIndex = 0; // incremented on this.execute()
|
||||
|
||||
// Holds the index of the history entry that the user is currently viewing.
|
||||
// This is reset to this.history.length when this.execute() is invoked.
|
||||
this.historyPlaceHolder = 0;
|
||||
this._objectActorsInVariablesViews = new Map();
|
||||
|
||||
this._keyPress = this.keyPress.bind(this);
|
||||
|
@ -2954,8 +2963,10 @@ JSTerm.prototype = {
|
|||
let options = { frame: this.SELECTED_FRAME };
|
||||
this.requestEvaluation(aExecuteString, options).then(onResult, onResult);
|
||||
|
||||
this.history.push(aExecuteString);
|
||||
this.historyIndex++;
|
||||
// Append a new value in the history of executed code, or overwrite the most
|
||||
// recent entry. The most recent entry may contain the last edited input
|
||||
// value that was not evaluated yet.
|
||||
this.history[this.historyIndex++] = aExecuteString;
|
||||
this.historyPlaceHolder = this.history.length;
|
||||
this.setInputValue("");
|
||||
this.clearCompletion();
|
||||
|
@ -3937,27 +3948,26 @@ JSTerm.prototype = {
|
|||
if (this.historyPlaceHolder <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let inputVal = this.history[--this.historyPlaceHolder];
|
||||
if (inputVal){
|
||||
this.setInputValue(inputVal);
|
||||
|
||||
// Save the current input value as the latest entry in history, only if
|
||||
// the user is already at the last entry.
|
||||
// Note: this code does not store changes to items that are already in
|
||||
// history.
|
||||
if (this.historyPlaceHolder+1 == this.historyIndex) {
|
||||
this.history[this.historyIndex] = this.inputNode.value || "";
|
||||
}
|
||||
|
||||
this.setInputValue(inputVal);
|
||||
}
|
||||
// Down Arrow key
|
||||
else if (aDirection == HISTORY_FORWARD) {
|
||||
if (this.historyPlaceHolder == this.history.length - 1) {
|
||||
this.historyPlaceHolder ++;
|
||||
this.setInputValue("");
|
||||
}
|
||||
else if (this.historyPlaceHolder >= (this.history.length)) {
|
||||
if (this.historyPlaceHolder >= (this.history.length-1)) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
let inputVal = this.history[++this.historyPlaceHolder];
|
||||
if (inputVal){
|
||||
this.setInputValue(inputVal);
|
||||
}
|
||||
}
|
||||
|
||||
let inputVal = this.history[++this.historyPlaceHolder];
|
||||
this.setInputValue(inputVal);
|
||||
}
|
||||
else {
|
||||
throw new Error("Invalid argument 0");
|
||||
|
|
|
@ -109,8 +109,11 @@ networkMenu.sortedDesc=Sorted descending
|
|||
# in the network table footer when there are no requests available.
|
||||
networkMenu.empty=No requests
|
||||
|
||||
# LOCALIZATION NOTE (networkMenu.summary): This is the label displayed
|
||||
# in the network table footer providing concise information about all requests.
|
||||
# LOCALIZATION NOTE (networkMenu.summary): Semi-colon list of plural forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# This label is displayed in the network table footer providing concise
|
||||
# information about all requests. Parameters: #1 is the number of requests,
|
||||
# #2 is the size, #3 is the number of seconds.
|
||||
networkMenu.summary=One request, #2 KB, #3 s;#1 requests, #2 KB, #3 s
|
||||
|
||||
# LOCALIZATION NOTE (networkMenu.sizeKB): This is the label displayed
|
||||
|
|
|
@ -531,6 +531,21 @@
|
|||
text-shadow: 0 0 8px #fcc;
|
||||
}
|
||||
|
||||
.variable-or-property[non-extensible]:not([non-writable]) > .title:after {
|
||||
content: "N";
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.variable-or-property[sealed]:not([non-writable]) > .title:after {
|
||||
content: "S";
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.variable-or-property[frozen]:not([non-writable]) > .title:after {
|
||||
content: "F";
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Variables and properties tooltips */
|
||||
|
||||
.variable-or-property > tooltip > label {
|
||||
|
@ -543,7 +558,10 @@
|
|||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.variable-or-property:not([safe-getter]) > tooltip > label[value=native-getter] {
|
||||
.variable-or-property:not([safe-getter]) > tooltip > label[value=native-getter],
|
||||
.variable-or-property:not([non-extensible]) > tooltip > label[value=non-extensible],
|
||||
.variable-or-property:not([frozen]) > tooltip > label[value=frozen],
|
||||
.variable-or-property:not([sealed]) > tooltip > label[value=sealed] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -531,6 +531,21 @@
|
|||
text-shadow: 0 0 8px #fcc;
|
||||
}
|
||||
|
||||
.variable-or-property[non-extensible]:not([non-writable]) > .title:after {
|
||||
content: "N";
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.variable-or-property[sealed]:not([non-writable]) > .title:after {
|
||||
content: "S";
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.variable-or-property[frozen]:not([non-writable]) > .title:after {
|
||||
content: "F";
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Variables and properties tooltips */
|
||||
|
||||
.variable-or-property > tooltip > label {
|
||||
|
@ -543,7 +558,10 @@
|
|||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.variable-or-property:not([safe-getter]) > tooltip > label[value=native-getter] {
|
||||
.variable-or-property:not([safe-getter]) > tooltip > label[value=native-getter],
|
||||
.variable-or-property:not([non-extensible]) > tooltip > label[value=non-extensible],
|
||||
.variable-or-property:not([frozen]) > tooltip > label[value=frozen],
|
||||
.variable-or-property:not([sealed]) > tooltip > label[value=sealed] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -534,6 +534,21 @@
|
|||
text-shadow: 0 0 8px #fcc;
|
||||
}
|
||||
|
||||
.variable-or-property[non-extensible]:not([non-writable]) > .title:after {
|
||||
content: "N";
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.variable-or-property[sealed]:not([non-writable]) > .title:after {
|
||||
content: "S";
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.variable-or-property[frozen]:not([non-writable]) > .title:after {
|
||||
content: "F";
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Variables and properties tooltips */
|
||||
|
||||
.variable-or-property > tooltip > label {
|
||||
|
@ -546,7 +561,10 @@
|
|||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.variable-or-property:not([safe-getter]) > tooltip > label[value=native-getter] {
|
||||
.variable-or-property:not([safe-getter]) > tooltip > label[value=native-getter],
|
||||
.variable-or-property:not([non-extensible]) > tooltip > label[value=non-extensible],
|
||||
.variable-or-property:not([frozen]) > tooltip > label[value=frozen],
|
||||
.variable-or-property:not([sealed]) > tooltip > label[value=sealed] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ XPIDL_SOURCES += [
|
|||
'nsILoadContext.idl',
|
||||
'nsIMarkupDocumentViewer.idl',
|
||||
'nsIPrivacyTransitionObserver.idl',
|
||||
'nsIReflowObserver.idl',
|
||||
'nsIRefreshURI.idl',
|
||||
'nsIScrollable.idl',
|
||||
'nsITextScroll.idl',
|
||||
|
|
|
@ -83,6 +83,7 @@
|
|||
#include "nsIOfflineCacheUpdate.h"
|
||||
#include "nsITimedChannel.h"
|
||||
#include "nsIPrivacyTransitionObserver.h"
|
||||
#include "nsIReflowObserver.h"
|
||||
#include "nsCPrefetchService.h"
|
||||
#include "nsJSON.h"
|
||||
#include "nsIDocShellTreeItem.h"
|
||||
|
@ -2205,6 +2206,43 @@ nsDocShell::AddWeakPrivacyTransitionObserver(nsIPrivacyTransitionObserver* aObse
|
|||
return mPrivacyObservers.AppendElement(weakObs) ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocShell::AddWeakReflowObserver(nsIReflowObserver* aObserver)
|
||||
{
|
||||
nsWeakPtr weakObs = do_GetWeakReference(aObserver);
|
||||
if (!weakObs) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return mReflowObservers.AppendElement(weakObs) ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocShell::RemoveWeakReflowObserver(nsIReflowObserver* aObserver)
|
||||
{
|
||||
nsWeakPtr obs = do_GetWeakReference(aObserver);
|
||||
return mReflowObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocShell::NotifyReflowObservers(bool aInterruptible,
|
||||
DOMHighResTimeStamp aStart,
|
||||
DOMHighResTimeStamp aEnd)
|
||||
{
|
||||
nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mReflowObservers);
|
||||
while (iter.HasMore()) {
|
||||
nsWeakPtr ref = iter.GetNext();
|
||||
nsCOMPtr<nsIReflowObserver> obs = do_QueryReferent(ref);
|
||||
if (!obs) {
|
||||
mReflowObservers.RemoveElement(ref);
|
||||
} else if (aInterruptible) {
|
||||
obs->ReflowInterruptible(aStart, aEnd);
|
||||
} else {
|
||||
obs->Reflow(aStart, aEnd);
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsDocShell::GetAllowMetaRedirects(bool * aReturn)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aReturn);
|
||||
|
|
|
@ -872,6 +872,7 @@ private:
|
|||
nsCString mForcedCharset;
|
||||
nsCString mParentCharset;
|
||||
nsTObserverArray<nsWeakPtr> mPrivacyObservers;
|
||||
nsTObserverArray<nsWeakPtr> mReflowObservers;
|
||||
int32_t mParentCharsetSource;
|
||||
nsCString mOriginalUriString;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* 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/. */
|
||||
|
||||
#include "domstubs.idl"
|
||||
#include "nsIDocShellTreeItem.idl"
|
||||
#include "nsIAtom.idl"
|
||||
|
||||
|
@ -39,8 +40,9 @@ interface nsIPrincipal;
|
|||
interface nsIWebBrowserPrint;
|
||||
interface nsIVariant;
|
||||
interface nsIPrivacyTransitionObserver;
|
||||
interface nsIReflowObserver;
|
||||
|
||||
[scriptable, builtinclass, uuid(2b192c9c-dea4-4696-a445-1bef7bc0db6d)]
|
||||
[scriptable, builtinclass, uuid(d15b07e0-c604-11e2-8bdc-651679957a39)]
|
||||
interface nsIDocShell : nsIDocShellTreeItem
|
||||
{
|
||||
/**
|
||||
|
@ -616,6 +618,30 @@ interface nsIDocShell : nsIDocShellTreeItem
|
|||
*/
|
||||
void addWeakPrivacyTransitionObserver(in nsIPrivacyTransitionObserver obs);
|
||||
|
||||
/**
|
||||
* Add an observer to the list of parties to be notified when reflows are
|
||||
* occurring. |obs| must support weak references.
|
||||
*/
|
||||
void addWeakReflowObserver(in nsIReflowObserver obs);
|
||||
|
||||
/**
|
||||
* Remove an observer from the list of parties to be notified about reflows.
|
||||
*/
|
||||
void removeWeakReflowObserver(in nsIReflowObserver obs);
|
||||
|
||||
/**
|
||||
* Notify all attached observers that a reflow has just occurred.
|
||||
*
|
||||
* @param interruptible if true, the reflow was interruptible.
|
||||
* @param start timestamp when reflow started, in milliseconds since
|
||||
* navigationStart (accurate to 1/1000 of a ms)
|
||||
* @param end timestamp when reflow ended, in milliseconds since
|
||||
* navigationStart (accurate to 1/1000 of a ms)
|
||||
*/
|
||||
[noscript] void notifyReflowObservers(in bool interruptible,
|
||||
in DOMHighResTimeStamp start,
|
||||
in DOMHighResTimeStamp end);
|
||||
|
||||
/**
|
||||
* Returns true if this docshell corresponds to an <iframe mozbrowser>.
|
||||
* (<iframe mozapp mozbrowser> is not considered a browser.)
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/* 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/. */
|
||||
|
||||
#include "domstubs.idl"
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(832e692c-c4a6-11e2-8fd1-dce678957a39)]
|
||||
interface nsIReflowObserver : nsISupports
|
||||
{
|
||||
/**
|
||||
* Called when an uninterruptible reflow has occurred.
|
||||
*
|
||||
* @param start timestamp when reflow ended, in milliseconds since
|
||||
* navigationStart (accurate to 1/1000 of a ms)
|
||||
* @param end timestamp when reflow ended, in milliseconds since
|
||||
* navigationStart (accurate to 1/1000 of a ms)
|
||||
*/
|
||||
void reflow(in DOMHighResTimeStamp start,
|
||||
in DOMHighResTimeStamp end);
|
||||
|
||||
/**
|
||||
* Called when an interruptible reflow has occurred.
|
||||
*
|
||||
* @param start timestamp when reflow ended, in milliseconds since
|
||||
* navigationStart (accurate to 1/1000 of a ms)
|
||||
* @param end timestamp when reflow ended, in milliseconds since
|
||||
* navigationStart (accurate to 1/1000 of a ms)
|
||||
*/
|
||||
void reflowInterruptible(in DOMHighResTimeStamp start,
|
||||
in DOMHighResTimeStamp end);
|
||||
};
|
|
@ -80,6 +80,7 @@ MOCHITEST_CHROME_FILES = \
|
|||
test_bug449780.xul \
|
||||
bug449780_window.xul \
|
||||
bug454235-subframe.xul \
|
||||
test_bug453650.xul \
|
||||
test_bug456980.xul \
|
||||
test_bug662200.xul \
|
||||
bug662200_window.xul \
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
|
||||
<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=453650
|
||||
-->
|
||||
<window title="Mozilla Bug 453650"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<title>Test for Bug 453650</title>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
|
||||
<!-- test code goes here -->
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
|
||||
/** Test for Bug 453650 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var Ci = Components.interfaces;
|
||||
var Cr = Components.results;
|
||||
|
||||
var iter = runTests();
|
||||
nextTest();
|
||||
|
||||
function runTests() {
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.style.width = "300px";
|
||||
iframe.style.height = "300px";
|
||||
iframe.setAttribute("src", "data:text/html,<h1 id='h'>hello</h1>");
|
||||
|
||||
document.documentElement.appendChild(iframe);
|
||||
yield whenLoaded(iframe);
|
||||
info("iframe loaded");
|
||||
|
||||
var h1 = iframe.contentDocument.getElementById("h");
|
||||
h1.style.width = "400px";
|
||||
yield waitForInterruptibleReflow(iframe.docShell);
|
||||
|
||||
h1.style.width = "300px";
|
||||
waitForReflow(iframe.docShell);
|
||||
yield is(300, h1.offsetWidth, "h1 has correct width");
|
||||
}
|
||||
|
||||
function waitForInterruptibleReflow(docShell) {
|
||||
waitForReflow(docShell, true);
|
||||
}
|
||||
|
||||
function waitForReflow(docShell, interruptible = false) {
|
||||
function done() {
|
||||
docShell.removeWeakReflowObserver(observer);
|
||||
SimpleTest.executeSoon(nextTest);
|
||||
}
|
||||
|
||||
var observer = {
|
||||
reflow: function (start, end) {
|
||||
if (interruptible) {
|
||||
ok(false, "expected interruptible reflow");
|
||||
} else {
|
||||
ok(true, "observed uninterruptible reflow");
|
||||
}
|
||||
|
||||
info("times: " + start + ", " + end);
|
||||
ok(start < end, "reflow start time lower than end time");
|
||||
done();
|
||||
},
|
||||
|
||||
reflowInterruptible: function (start, end) {
|
||||
if (!interruptible) {
|
||||
ok(false, "expected uninterruptible reflow");
|
||||
} else {
|
||||
ok(true, "observed interruptible reflow");
|
||||
}
|
||||
|
||||
info("times: " + start + ", " + end);
|
||||
ok(start < end, "reflow start time lower than end time");
|
||||
done();
|
||||
},
|
||||
|
||||
QueryInterface: function (iid) {
|
||||
if (Ci.nsIReflowObserver.equals(iid) ||
|
||||
Ci.nsISupportsWeakReference.equals(iid) ||
|
||||
Ci.nsISupports.equals(iid))
|
||||
return this;
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
};
|
||||
|
||||
docShell.addWeakReflowObserver(observer);
|
||||
}
|
||||
|
||||
function whenLoaded(iframe) {
|
||||
iframe.addEventListener("load", function onLoad() {
|
||||
iframe.removeEventListener("load", onLoad);
|
||||
SimpleTest.executeSoon(nextTest);
|
||||
});
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
try {
|
||||
iter.next();
|
||||
} catch (e if e instanceof StopIteration) {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
]]>
|
||||
</script>
|
||||
|
||||
<!-- test results are displayed in the html:body -->
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=453650"
|
||||
target="_blank">Mozilla Bug 453650</a>
|
||||
</body>
|
||||
</window>
|
|
@ -58,7 +58,7 @@ namespace {
|
|||
|
||||
// Wait for 2 seconds for Dialer processing event 'BLDN'. '2' seconds is a
|
||||
// magic number. The mechanism should be revised once we can get call history.
|
||||
static int sWaitingForProcessingBLDNInterval = 2000; //unit: ms
|
||||
static int sWaitingForDialingInterval = 2000; //unit: ms
|
||||
} // anonymous namespace
|
||||
|
||||
/* CallState for sCINDItems[CINDType::CALL].value
|
||||
|
@ -276,8 +276,8 @@ private:
|
|||
{
|
||||
MOZ_ASSERT(gBluetoothHfpManager);
|
||||
|
||||
if (!gBluetoothHfpManager->mBLDNProcessed) {
|
||||
gBluetoothHfpManager->mBLDNProcessed = true;
|
||||
if (!gBluetoothHfpManager->mDialingRequestProcessed) {
|
||||
gBluetoothHfpManager->mDialingRequestProcessed = true;
|
||||
gBluetoothHfpManager->SendLine("ERROR");
|
||||
}
|
||||
}
|
||||
|
@ -374,7 +374,7 @@ BluetoothHfpManager::Reset()
|
|||
mCMEE = false;
|
||||
mCMER = false;
|
||||
mReceiveVgsFlag = false;
|
||||
mBLDNProcessed = true;
|
||||
mDialingRequestProcessed = true;
|
||||
|
||||
ResetCallArray();
|
||||
}
|
||||
|
@ -881,15 +881,19 @@ BluetoothHfpManager::ReceiveSocketData(BluetoothSocket* aSocket,
|
|||
os->NotifyObservers(nullptr, "bluetooth-volume-change", data.get());
|
||||
} else if ((msg.Find("AT+BLDN") != -1) || (msg.Find("ATD>") != -1)) {
|
||||
// Dialer app of FFOS v1 does not have plan to support Memory Dailing.
|
||||
// However, in order to pass Bluetooth HFP certification, we have to
|
||||
// make a call when we receive AT command 'ATD>n'. The solution here
|
||||
// is firing a 'BLDN' event to Dialer to do 'Last Number Redial'.
|
||||
mBLDNProcessed = false;
|
||||
NotifyDialer(NS_LITERAL_STRING("BLDN"));
|
||||
// However, in order to pass Bluetooth HFP certification, we still have to
|
||||
// make a call when we receive AT command 'ATD>n'.
|
||||
mDialingRequestProcessed = false;
|
||||
|
||||
if (msg.Find("AT+BLDN") != -1) {
|
||||
NotifyDialer(NS_LITERAL_STRING("BLDN"));
|
||||
} else {
|
||||
NotifyDialer(NS_ConvertUTF8toUTF16(msg));
|
||||
}
|
||||
|
||||
MessageLoop::current()->
|
||||
PostDelayedTask(FROM_HERE, new RespondToBLDNTask(),
|
||||
sWaitingForProcessingBLDNInterval);
|
||||
sWaitingForDialingInterval);
|
||||
|
||||
// Don't send response 'OK' here because we'll respond later in either
|
||||
// RespondToBLDNTask or HandleCallStateChanged()
|
||||
|
@ -1309,9 +1313,9 @@ BluetoothHfpManager::HandleCallStateChanged(uint32_t aCallIndex,
|
|||
}
|
||||
break;
|
||||
case nsITelephonyProvider::CALL_STATE_DIALING:
|
||||
if (!mBLDNProcessed) {
|
||||
if (!mDialingRequestProcessed) {
|
||||
SendLine("OK");
|
||||
mBLDNProcessed = true;
|
||||
mDialingRequestProcessed = true;
|
||||
}
|
||||
|
||||
UpdateCIND(CINDType::CALLSETUP, CallSetupState::OUTGOING, aSend);
|
||||
|
|
|
@ -132,7 +132,7 @@ private:
|
|||
bool mFirstCKPD;
|
||||
int mNetworkSelectionMode;
|
||||
bool mReceiveVgsFlag;
|
||||
bool mBLDNProcessed;
|
||||
bool mDialingRequestProcessed;
|
||||
bool mIsHandsfree;
|
||||
bool mNeedsUpdatingSdpRecords;
|
||||
nsString mDeviceAddress;
|
||||
|
|
|
@ -12,6 +12,7 @@ class nsWrapperCache;
|
|||
[ptr] native nsWrapperCachePtr(nsWrapperCache);
|
||||
|
||||
typedef unsigned long long DOMTimeStamp;
|
||||
typedef double DOMHighResTimeStamp;
|
||||
|
||||
// Core
|
||||
interface nsIDOMAttr;
|
||||
|
|
|
@ -123,7 +123,9 @@
|
|||
#include "nsSVGEffects.h"
|
||||
#include "SVGFragmentIdentifier.h"
|
||||
|
||||
#include "nsPerformance.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
#include "nsDOMNavigationTiming.h"
|
||||
|
||||
// Drag & Drop, Clipboard
|
||||
#include "nsWidgetsCID.h"
|
||||
|
@ -7651,6 +7653,8 @@ PresShell::WillDoReflow()
|
|||
mPresContext->FlushUserFontSet();
|
||||
|
||||
mFrameConstructor->BeginUpdate();
|
||||
|
||||
mLastReflowStart = GetPerformanceNow();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -7659,6 +7663,16 @@ PresShell::DidDoReflow(bool aInterruptible, bool aWasInterrupted)
|
|||
mFrameConstructor->EndUpdate();
|
||||
|
||||
HandlePostedReflowCallbacks(aInterruptible);
|
||||
|
||||
nsCOMPtr<nsISupports> container = mPresContext->GetContainer();
|
||||
if (container) {
|
||||
nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(container);
|
||||
if (docShell) {
|
||||
DOMHighResTimeStamp now = GetPerformanceNow();
|
||||
docShell->NotifyReflowObservers(aInterruptible, mLastReflowStart, now);
|
||||
}
|
||||
}
|
||||
|
||||
if (sSynthMouseMove) {
|
||||
SynthesizeMouseMove(false);
|
||||
}
|
||||
|
@ -7674,6 +7688,23 @@ PresShell::DidDoReflow(bool aInterruptible, bool aWasInterrupted)
|
|||
}
|
||||
}
|
||||
|
||||
DOMHighResTimeStamp
|
||||
PresShell::GetPerformanceNow()
|
||||
{
|
||||
DOMHighResTimeStamp now = 0;
|
||||
nsPIDOMWindow* window = mDocument->GetInnerWindow();
|
||||
|
||||
if (window) {
|
||||
nsPerformance* perf = window->GetPerformance();
|
||||
|
||||
if (perf) {
|
||||
now = perf->Now();
|
||||
}
|
||||
}
|
||||
|
||||
return now;
|
||||
}
|
||||
|
||||
static PLDHashOperator
|
||||
MarkFramesDirtyToRoot(nsPtrHashKey<nsIFrame>* p, void* closure)
|
||||
{
|
||||
|
|
|
@ -701,6 +701,8 @@ protected:
|
|||
|
||||
nscolor GetDefaultBackgroundColorToDraw();
|
||||
|
||||
DOMHighResTimeStamp GetPerformanceNow();
|
||||
|
||||
// The callback for the mPaintSuppressionTimer timer.
|
||||
static void sPaintSuppressionCallback(nsITimer* aTimer, void* aPresShell);
|
||||
|
||||
|
@ -783,6 +785,9 @@ protected:
|
|||
// moving/sizing loop is running, see bug 491700 for details.
|
||||
nsCOMPtr<nsITimer> mReflowContinueTimer;
|
||||
|
||||
// The `performance.now()` value when we last started to process reflows.
|
||||
DOMHighResTimeStamp mLastReflowStart;
|
||||
|
||||
// Information needed to properly handle scrolling content into view if the
|
||||
// pre-scroll reflow flush can be interrupted. mContentToScrollTo is
|
||||
// non-null between the initial scroll attempt and the first time we finish
|
||||
|
|
|
@ -5,130 +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);
|
||||
|
||||
// Get the chrome debugger actor.
|
||||
let actor = this._chromeDebugger;
|
||||
if (!actor) {
|
||||
actor = new ChromeDebuggerActor(this);
|
||||
actor.parentID = this.actorID;
|
||||
this._chromeDebugger = actor;
|
||||
actorPool.addActor(actor);
|
||||
}
|
||||
MobileTabList.prototype.constructor = MobileTabList;
|
||||
|
||||
let win = windowMediator.getMostRecentWindow("navigator:browser");
|
||||
this.browser = win.BrowserApp.selectedBrowser;
|
||||
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;
|
||||
|
||||
// Watch the window for tab closes so we can invalidate
|
||||
// actors as needed.
|
||||
this.watchWindow(win);
|
||||
// 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.
|
||||
|
||||
let tabs = win.BrowserApp.tabs;
|
||||
let selected;
|
||||
// Iterate over all navigator:browser XUL windows.
|
||||
for (let win of allAppShellDOMWindows("navigator:browser")) {
|
||||
let selectedTab = win.BrowserApp.selectedBrowser;
|
||||
|
||||
for each (let tab in tabs) {
|
||||
let browser = tab.browser;
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (browser == this.browser) {
|
||||
selected = tabActorList.length;
|
||||
// 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)],
|
||||
"chromeDebugger": this._chromeDebugger.actorID
|
||||
};
|
||||
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
|
||||
};
|
||||
|
|
|
@ -44,7 +44,8 @@ let gTimerRegistry = new Map();
|
|||
* @param {object} aOptions (optional)
|
||||
* An object allowing format customization. The only customization
|
||||
* allowed currently is 'truncate' which can take the value "start" to
|
||||
* truncate strings from the start as opposed to the end.
|
||||
* truncate strings from the start as opposed to the end or "center" to
|
||||
* truncate strings in the center
|
||||
* @return {string}
|
||||
* The original string formatted to fit the specified lengths
|
||||
*/
|
||||
|
@ -59,6 +60,12 @@ function fmt(aStr, aMaxLen, aMinLen, aOptions) {
|
|||
if (aOptions && aOptions.truncate == "start") {
|
||||
return "_" + aStr.substring(aStr.length - aMaxLen + 1);
|
||||
}
|
||||
else if (aOptions && aOptions.truncate == "center") {
|
||||
let start = aStr.substring(0, (aMaxLen / 2));
|
||||
|
||||
let end = aStr.substring((aStr.length - (aMaxLen / 2)) + 1);
|
||||
return start + "_" + end;
|
||||
}
|
||||
else {
|
||||
return aStr.substring(0, aMaxLen - 1) + "_";
|
||||
}
|
||||
|
@ -125,15 +132,15 @@ function stringify(aThing) {
|
|||
// Can't use a real ellipsis here, because cmd.exe isn't unicode-enabled
|
||||
json = "{" + Object.keys(aThing).join(":..,") + ":.., " + "}";
|
||||
}
|
||||
return type + fmt(json, 50, 0);
|
||||
return type + json;
|
||||
}
|
||||
|
||||
if (typeof aThing == "function") {
|
||||
return fmt(aThing.toString().replace(/\s+/g, " "), 80, 0);
|
||||
return aThing.toString().replace(/\s+/g, " ");
|
||||
}
|
||||
|
||||
let str = aThing.toString().replace(/\n/g, "|");
|
||||
return fmt(str, 80, 0);
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -334,7 +341,7 @@ function formatTrace(aTrace) {
|
|||
aTrace.forEach(function(frame) {
|
||||
reply += fmt(frame.filename, 20, 20, { truncate: "start" }) + " " +
|
||||
fmt(frame.lineNumber, 5, 5) + " " +
|
||||
fmt(frame.functionName, 75, 75) + "\n";
|
||||
fmt(frame.functionName, 75, 75, { truncate: "center" }) + "\n";
|
||||
});
|
||||
return reply;
|
||||
}
|
||||
|
@ -484,8 +491,7 @@ function sendConsoleAPIMessage(aLevel, aFrame, aArgs, aOptions = {})
|
|||
|
||||
/**
|
||||
* This creates a console object that somewhat replicates Firebug's console
|
||||
* object. It currently writes to dump(), but should write to the web
|
||||
* console's chrome error section (when it has one)
|
||||
* object.
|
||||
*/
|
||||
this.console = {
|
||||
debug: createMultiLineDumper("debug"),
|
||||
|
@ -493,6 +499,7 @@ this.console = {
|
|||
info: createDumper("info"),
|
||||
warn: createDumper("warn"),
|
||||
error: createMultiLineDumper("error"),
|
||||
exception: createMultiLineDumper("error"),
|
||||
|
||||
trace: function Console_trace() {
|
||||
let args = Array.prototype.slice.call(arguments, 0);
|
||||
|
|
|
@ -29,13 +29,20 @@ this.EXPORTED_SYMBOLS = ["devtools"];
|
|||
|
||||
let loaderGlobals = {
|
||||
console: console,
|
||||
_Iterator: Iterator
|
||||
_Iterator: Iterator,
|
||||
loader: {
|
||||
lazyGetter: XPCOMUtils.defineLazyGetter.bind(XPCOMUtils),
|
||||
lazyImporter: XPCOMUtils.defineLazyModuleGetter.bind(XPCOMUtils)
|
||||
}
|
||||
}
|
||||
|
||||
// Used when the tools should be loaded from the Firefox package itself (the default)
|
||||
var BuiltinProvider = {
|
||||
load: function(done) {
|
||||
this.loader = new loader.Loader({
|
||||
modules: {
|
||||
"toolkit/loader": loader
|
||||
},
|
||||
paths: {
|
||||
"": "resource://gre/modules/commonjs/",
|
||||
"main": "resource:///modules/devtools/main.js",
|
||||
|
@ -73,6 +80,9 @@ var SrcdirProvider = {
|
|||
let serverURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools", "server"));
|
||||
let mainURI = this.fileURI(OS.Path.join(srcdir, "browser", "devtools", "main.js"));
|
||||
this.loader = new loader.Loader({
|
||||
modules: {
|
||||
"toolkit/loader": loader
|
||||
},
|
||||
paths: {
|
||||
"": "resource://gre/modules/commonjs/",
|
||||
"devtools/server": serverURI,
|
||||
|
|
|
@ -182,6 +182,7 @@ const UnsolicitedNotifications = {
|
|||
"newScript": "newScript",
|
||||
"newSource": "newSource",
|
||||
"tabDetached": "tabDetached",
|
||||
"tabListChanged": "tabListChanged",
|
||||
"tabNavigated": "tabNavigated",
|
||||
"pageError": "pageError",
|
||||
"webappsEvent": "webappsEvent",
|
||||
|
@ -1437,6 +1438,10 @@ GripClient.prototype = {
|
|||
|
||||
valid: true,
|
||||
|
||||
get isFrozen() this._grip.frozen,
|
||||
get isSealed() this._grip.sealed,
|
||||
get isExtensible() this._grip.extensible,
|
||||
|
||||
/**
|
||||
* Request the names of a function's formal parameters.
|
||||
*
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var connCount = 0;
|
||||
var startedProfilers = 0;
|
||||
var startTime = 0;
|
||||
|
||||
function getCurrentTime() {
|
||||
|
@ -20,7 +20,6 @@ function ProfilerActor(aConnection)
|
|||
this._profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
this._started = false;
|
||||
this._observedEvents = [];
|
||||
connCount += 1;
|
||||
}
|
||||
|
||||
ProfilerActor.prototype = {
|
||||
|
@ -31,30 +30,36 @@ ProfilerActor.prototype = {
|
|||
Services.obs.removeObserver(this, event);
|
||||
}
|
||||
|
||||
// We stop the profiler only after the last client is
|
||||
// disconnected. Otherwise there's a problem where
|
||||
this.stopProfiler();
|
||||
this._profiler = null;
|
||||
},
|
||||
|
||||
stopProfiler: function() {
|
||||
// We stop the profiler only after the last client has
|
||||
// stopped profiling. Otherwise there's a problem where
|
||||
// we stop the profiler as soon as you close the devtools
|
||||
// panel in one tab even though there might be other
|
||||
// profiler instances running in other tabs.
|
||||
|
||||
connCount -= 1;
|
||||
if (connCount <= 0 && this._profiler && this._started) {
|
||||
if (!this._started) {
|
||||
return;
|
||||
}
|
||||
this._started = false;
|
||||
startedProfilers -= 1;
|
||||
if (startedProfilers <= 0) {
|
||||
this._profiler.StopProfiler();
|
||||
}
|
||||
|
||||
this._profiler = null;
|
||||
},
|
||||
|
||||
onStartProfiler: function(aRequest) {
|
||||
this._profiler.StartProfiler(aRequest.entries, aRequest.interval,
|
||||
aRequest.features, aRequest.features.length);
|
||||
this._started = true;
|
||||
startedProfilers += 1;
|
||||
startTime = (new Date()).getTime();
|
||||
return { "msg": "profiler started" }
|
||||
},
|
||||
onStopProfiler: function(aRequest) {
|
||||
this._profiler.StopProfiler();
|
||||
this._started = false;
|
||||
this.stopProfiler();
|
||||
return { "msg": "profiler stopped" }
|
||||
},
|
||||
onGetProfileStr: function(aRequest) {
|
||||
|
|
|
@ -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
|
||||
};
|
|
@ -1481,9 +1481,14 @@ ObjectActor.prototype = {
|
|||
* Returns a grip for this actor for returning in a protocol message.
|
||||
*/
|
||||
grip: function OA_grip() {
|
||||
let g = { "type": "object",
|
||||
"class": this.obj.class,
|
||||
"actor": this.actorID };
|
||||
let g = {
|
||||
"type": "object",
|
||||
"class": this.obj.class,
|
||||
"actor": this.actorID,
|
||||
"extensible": this.obj.isExtensible(),
|
||||
"frozen": this.obj.isFrozen(),
|
||||
"sealed": this.obj.isSealed()
|
||||
};
|
||||
|
||||
// Add additional properties for functions.
|
||||
if (this.obj.class === "Function") {
|
||||
|
@ -1780,7 +1785,7 @@ ObjectActor.prototype.requestTypes = {
|
|||
|
||||
|
||||
/**
|
||||
* Creates a pause-scoped actor for the specified object.
|
||||
* Creates a pause-scoped actor for the specified object.
|
||||
* @see ObjectActor
|
||||
*/
|
||||
function PauseScopedObjectActor()
|
||||
|
@ -2366,13 +2371,18 @@ Object.defineProperty(Debugger.Frame.prototype, "line", {
|
|||
* Creates an actor for handling chrome debugging. ChromeDebuggerActor is a
|
||||
* thin wrapper over ThreadActor, slightly changing some of its behavior.
|
||||
*
|
||||
* @param aConnection object
|
||||
* The DebuggerServerConnection with which this ChromeDebuggerActor
|
||||
* is associated. (Currently unused, but required to make this
|
||||
* constructor usable with addGlobalActor.)
|
||||
*
|
||||
* @param aHooks object
|
||||
* An object with preNest and postNest methods for calling when entering
|
||||
* and exiting a nested event loop and also addToParentPool and
|
||||
* removeFromParentPool methods for handling the lifetime of actors that
|
||||
* will outlive the thread, like breakpoints.
|
||||
*/
|
||||
function ChromeDebuggerActor(aHooks)
|
||||
function ChromeDebuggerActor(aConnection, aHooks)
|
||||
{
|
||||
ThreadActor.call(this, aHooks);
|
||||
}
|
||||
|
|
|
@ -10,339 +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();
|
||||
|
||||
// Get the chrome debugger actor.
|
||||
let actor = this._chromeDebugger;
|
||||
if (!actor) {
|
||||
actor = new ChromeDebuggerActor(this);
|
||||
actor.parentID = this.actorID;
|
||||
this._chromeDebugger = actor;
|
||||
actorPool.addActor(actor);
|
||||
}
|
||||
|
||||
// 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)],
|
||||
"chromeDebugger": this._chromeDebugger.actorID
|
||||
};
|
||||
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.
|
||||
|
@ -370,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; },
|
||||
|
@ -449,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.
|
||||
|
@ -676,7 +774,7 @@ BrowserTabActor.prototype = {
|
|||
}
|
||||
catch (ex) { }
|
||||
return isNative;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -732,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") {
|
||||
|
@ -746,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;
|
||||
},
|
||||
|
@ -150,15 +150,20 @@ var DebuggerServer = {
|
|||
* method returns, the debugger server must be initialized again before use.
|
||||
*/
|
||||
destroy: function DS_destroy() {
|
||||
if (Object.keys(this._connections).length == 0) {
|
||||
this.closeListener();
|
||||
this.globalActorFactories = {};
|
||||
this.tabActorFactories = {};
|
||||
delete this._allowConnection;
|
||||
this._transportInitialized = false;
|
||||
this._initialized = false;
|
||||
dumpn("Debugger server is shut down.");
|
||||
if (!this._initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let connID of Object.getOwnPropertyNames(this._connections)) {
|
||||
this._connections[connID].close();
|
||||
}
|
||||
this.closeListener();
|
||||
this.globalActorFactories = {};
|
||||
this.tabActorFactories = {};
|
||||
delete this._allowConnection;
|
||||
this._transportInitialized = false;
|
||||
this._initialized = false;
|
||||
dumpn("Debugger server is shut down.");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -178,6 +183,8 @@ 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");
|
||||
if ("nsIProfiler" in Ci)
|
||||
|
@ -265,7 +272,6 @@ var DebuggerServer = {
|
|||
return clientTransport;
|
||||
},
|
||||
|
||||
|
||||
// nsIServerSocketListener implementation
|
||||
|
||||
onSocketAccepted:
|
||||
|
@ -435,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) {
|
||||
|
@ -524,6 +530,10 @@ DebuggerServerConnection.prototype = {
|
|||
_transport: null,
|
||||
get transport() { return this._transport },
|
||||
|
||||
close: function() {
|
||||
this._transport.close();
|
||||
},
|
||||
|
||||
send: function DSC_send(aPacket) {
|
||||
this.transport.send(aPacket);
|
||||
},
|
||||
|
|
|
@ -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; });
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* This test checks that frozen objects report themselves as frozen in their
|
||||
* grip.
|
||||
*/
|
||||
|
||||
var gDebuggee;
|
||||
var gClient;
|
||||
var gThreadClient;
|
||||
|
||||
function run_test()
|
||||
{
|
||||
initTestDebuggerServer();
|
||||
gDebuggee = addTestGlobal("test-grips");
|
||||
gDebuggee.eval(function stopMe(arg1, arg2) {
|
||||
debugger;
|
||||
}.toString());
|
||||
|
||||
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
gClient.connect(function() {
|
||||
attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) {
|
||||
gThreadClient = aThreadClient;
|
||||
test_object_grip();
|
||||
});
|
||||
});
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_object_grip()
|
||||
{
|
||||
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||
let obj1 = aPacket.frame.arguments[0];
|
||||
do_check_true(obj1.frozen);
|
||||
|
||||
let obj1Client = gThreadClient.pauseGrip(obj1);
|
||||
do_check_true(obj1Client.isFrozen);
|
||||
|
||||
let obj2 = aPacket.frame.arguments[1];
|
||||
do_check_false(obj2.frozen);
|
||||
|
||||
let obj2Client = gThreadClient.pauseGrip(obj2);
|
||||
do_check_false(obj2Client.isFrozen);
|
||||
|
||||
gThreadClient.resume(_ => {
|
||||
finishClient(gClient);
|
||||
});
|
||||
});
|
||||
|
||||
gDebuggee.eval("(" + function () {
|
||||
let obj1 = {};
|
||||
Object.freeze(obj1);
|
||||
stopMe(obj1, {});
|
||||
} + "())");
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* This test checks that sealed objects report themselves as sealed in their
|
||||
* grip.
|
||||
*/
|
||||
|
||||
var gDebuggee;
|
||||
var gClient;
|
||||
var gThreadClient;
|
||||
|
||||
function run_test()
|
||||
{
|
||||
initTestDebuggerServer();
|
||||
gDebuggee = addTestGlobal("test-grips");
|
||||
gDebuggee.eval(function stopMe(arg1, arg2) {
|
||||
debugger;
|
||||
}.toString());
|
||||
|
||||
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
gClient.connect(function() {
|
||||
attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) {
|
||||
gThreadClient = aThreadClient;
|
||||
test_object_grip();
|
||||
});
|
||||
});
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_object_grip()
|
||||
{
|
||||
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||
let obj1 = aPacket.frame.arguments[0];
|
||||
do_check_true(obj1.sealed);
|
||||
|
||||
let obj1Client = gThreadClient.pauseGrip(obj1);
|
||||
do_check_true(obj1Client.isSealed);
|
||||
|
||||
let obj2 = aPacket.frame.arguments[1];
|
||||
do_check_false(obj2.sealed);
|
||||
|
||||
let obj2Client = gThreadClient.pauseGrip(obj2);
|
||||
do_check_false(obj2Client.isSealed);
|
||||
|
||||
gThreadClient.resume(_ => {
|
||||
finishClient(gClient);
|
||||
});
|
||||
});
|
||||
|
||||
gDebuggee.eval("(" + function () {
|
||||
let obj1 = {};
|
||||
Object.seal(obj1);
|
||||
stopMe(obj1, {});
|
||||
} + "())");
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* This test checks that objects which are not extensible report themselves as
|
||||
* such.
|
||||
*/
|
||||
|
||||
var gDebuggee;
|
||||
var gClient;
|
||||
var gThreadClient;
|
||||
|
||||
function run_test()
|
||||
{
|
||||
initTestDebuggerServer();
|
||||
gDebuggee = addTestGlobal("test-grips");
|
||||
gDebuggee.eval(function stopMe(arg1, arg2, arg3, arg4) {
|
||||
debugger;
|
||||
}.toString());
|
||||
|
||||
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
gClient.connect(function() {
|
||||
attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) {
|
||||
gThreadClient = aThreadClient;
|
||||
test_object_grip();
|
||||
});
|
||||
});
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_object_grip()
|
||||
{
|
||||
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||
let [f, s, ne, e] = aPacket.frame.arguments;
|
||||
let [fClient, sClient, neClient, eClient] = aPacket.frame.arguments.map(
|
||||
a => gThreadClient.pauseGrip(a));
|
||||
|
||||
do_check_false(f.extensible);
|
||||
do_check_false(fClient.isExtensible);
|
||||
|
||||
do_check_false(s.extensible);
|
||||
do_check_false(sClient.isExtensible);
|
||||
|
||||
do_check_false(ne.extensible);
|
||||
do_check_false(neClient.isExtensible);
|
||||
|
||||
do_check_true(e.extensible);
|
||||
do_check_true(eClient.isExtensible);
|
||||
|
||||
gThreadClient.resume(_ => {
|
||||
finishClient(gClient);
|
||||
});
|
||||
});
|
||||
|
||||
gDebuggee.eval("(" + function () {
|
||||
let f = {};
|
||||
Object.freeze(f);
|
||||
let s = {};
|
||||
Object.seal(s);
|
||||
let ne = {};
|
||||
Object.preventExtensions(ne);
|
||||
stopMe(f, s, ne, {});
|
||||
} + "())");
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
|
||||
function connectClient(callback) {
|
||||
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
client.connect(function () {
|
||||
client.listTabs(function(response) {
|
||||
callback(client, response.profilerActor);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function run_test()
|
||||
{
|
||||
// Ensure the profiler is not running when the test starts (it could
|
||||
// happen if the MOZ_PROFILER_STARTUP environment variable is set)
|
||||
Profiler.StopProfiler();
|
||||
|
||||
DebuggerServer.init(function () { return true; });
|
||||
DebuggerServer.addBrowserActors();
|
||||
|
||||
connectClient((client1, actor1) => {
|
||||
connectClient((client2, actor2) => {
|
||||
activate_first(client1, actor1, client2, actor2);
|
||||
});
|
||||
})
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function activate_first(client1, actor1, client2, actor2) {
|
||||
// Start the profiler on the first connection....
|
||||
client1.request({ to: actor1, type: "startProfiler", features: ['js']}, startResponse => {
|
||||
// Profiler should be active now.
|
||||
do_check_true(Profiler.IsActive());
|
||||
|
||||
// But on the next connection just make sure the actor has been
|
||||
// instantiated.
|
||||
client2.request({ to: actor2, type: "getFeatures" }, featureResponse => {
|
||||
|
||||
let connectionClosed = DebuggerServer._connectionClosed;
|
||||
DebuggerServer._connectionClosed = function(conn) {
|
||||
connectionClosed.call(this, conn);
|
||||
|
||||
// Client1 is the only actor that started the profiler,
|
||||
// it shouldn't be active anymore.
|
||||
do_check_false(Profiler.IsActive());
|
||||
|
||||
DebuggerServer._connectionClosed = function(conn) {
|
||||
connectionClosed.call(this, conn);
|
||||
|
||||
// Now there are no open clients at all, it should *definitely*
|
||||
// be deactivated by now.
|
||||
do_check_false(Profiler.IsActive());
|
||||
do_test_finished();
|
||||
}
|
||||
client2.close();
|
||||
};
|
||||
client1.close();
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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 };
|
||||
|
|
|
@ -95,6 +95,9 @@ reason = bug 820380
|
|||
[test_objectgrips-02.js]
|
||||
[test_objectgrips-03.js]
|
||||
[test_objectgrips-04.js]
|
||||
[test_objectgrips-05.js]
|
||||
[test_objectgrips-06.js]
|
||||
[test_objectgrips-07.js]
|
||||
[test_interrupt.js]
|
||||
[test_stepping-01.js]
|
||||
[test_stepping-02.js]
|
||||
|
@ -121,6 +124,7 @@ skip-if = toolkit == "gonk"
|
|||
reason = bug 820380
|
||||
[test_breakpointstore.js]
|
||||
[test_profiler_actor.js]
|
||||
[test_profiler_activation.js]
|
||||
skip-if = toolkit == "gonk"
|
||||
reason = bug 820380
|
||||
[test_unsafeDereference.js]
|
||||
|
|
|
@ -173,7 +173,10 @@ DebuggerTransport.prototype = {
|
|||
onStopRequest:
|
||||
makeInfallible(function DT_onStopRequest(aRequest, aContext, aStatus) {
|
||||
this.close();
|
||||
this.hooks.onClosed(aStatus);
|
||||
if (this.hooks) {
|
||||
this.hooks.onClosed(aStatus);
|
||||
this.hooks = null;
|
||||
}
|
||||
}, "DebuggerTransport.prototype.onStopRequest"),
|
||||
|
||||
onDataAvailable:
|
||||
|
@ -292,7 +295,10 @@ LocalDebuggerTransport.prototype = {
|
|||
delete this.other;
|
||||
other.close();
|
||||
}
|
||||
this.hooks.onClosed();
|
||||
if (this.hooks) {
|
||||
this.hooks.onClosed();
|
||||
this.hooks = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче