This commit is contained in:
Ms2ger 2013-06-02 17:38:01 +02:00
Родитель 30ddbf4bfc 134b84c4e7
Коммит b7fa0c2492
54 изменённых файлов: 2197 добавлений и 676 удалений

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

@ -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;
}
},
/**