Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2013-01-12 20:56:52 -05:00
commit f0eeb8a037
23 changed files with 563 additions and 1459 deletions

View File

@ -10,6 +10,7 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
const NEW_SCRIPT_IGNORED_URLS = ["debugger eval code", "self-hosted"];
const NEW_SCRIPT_DISPLAY_DELAY = 200; // ms
const FETCH_SOURCE_RESPONSE_DELAY = 50; // ms
const FRAME_STEP_CLEAR_DELAY = 100; // ms
@ -1073,8 +1074,8 @@ SourceScripts.prototype = {
* Handler for the debugger client's unsolicited newScript notification.
*/
_onNewScript: function SS__onNewScript(aNotification, aPacket) {
// Ignore scripts generated from 'clientEvaluate' packets.
if (aPacket.url == "debugger eval code") {
// Ignore bogus scripts, e.g. generated from 'clientEvaluate' packets.
if (NEW_SCRIPT_IGNORED_URLS.indexOf(aPacket.url) != -1) {
return;
}
@ -1129,8 +1130,8 @@ SourceScripts.prototype = {
_onScriptsAdded: function SS__onScriptsAdded(aResponse) {
// Add all the sources in the debugger view sources container.
for (let script of aResponse.scripts) {
// Ignore scripts generated from 'clientEvaluate' packets.
if (script.url == "debugger eval code") {
// Ignore bogus scripts, e.g. generated from 'clientEvaluate' packets.
if (NEW_SCRIPT_IGNORED_URLS.indexOf(script.url) != -1) {
continue;
}
this._addSource(script);

View File

@ -56,6 +56,7 @@ ToolSidebar.prototype = {
iframe.setAttribute("src", url);
let tab = this._tabbox.tabs.appendItem();
tab.setAttribute("label", ""); // Avoid showing "undefined" while the tab is loading
let onIFrameLoaded = function() {
tab.setAttribute("label", iframe.contentDocument.title);

View File

@ -130,7 +130,7 @@ this.Toolbox = function Toolbox(target, selectedTool, hostType) {
if (!selectedTool) {
selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
}
let definitions = gDevTools.getToolDefinitions();
let definitions = gDevTools.getToolDefinitionMap();
if (!definitions.get(selectedTool)) {
selectedTool = "webconsole";
}
@ -248,11 +248,6 @@ Toolbox.prototype = {
let domReady = function() {
iframe.removeEventListener("DOMContentLoaded", domReady, true);
let vbox = this.doc.getElementById("toolbox-panel-" + this._currentToolId);
if (vbox) {
this.doc.commandDispatcher.advanceFocusIntoSubtree(vbox);
}
this.isReady = true;
let closeButton = this.doc.getElementById("toolbox-close");
@ -315,7 +310,7 @@ Toolbox.prototype = {
* Add tabs to the toolbox UI for registered tools
*/
_buildTabs: function TBOX_buildTabs() {
for (let [id, definition] of gDevTools.getToolDefinitions()) {
for (let definition of gDevTools.getToolDefinitionArray()) {
this._buildTabForTool(definition);
}
},
@ -347,7 +342,6 @@ Toolbox.prototype = {
* Tool definition of the tool to build a tab for.
*/
_buildTabForTool: function TBOX_buildTabForTool(toolDefinition) {
const MAX_ORDINAL = 99;
if (!toolDefinition.isTargetSupported(this._target)) {
return;
}
@ -367,10 +361,6 @@ Toolbox.prototype = {
radio.setAttribute("src", toolDefinition.icon);
}
let ordinal = (typeof toolDefinition.ordinal == "number") ?
toolDefinition.ordinal : MAX_ORDINAL;
radio.setAttribute("ordinal", ordinal);
radio.addEventListener("command", function(id) {
this.selectTool(id);
}.bind(this, id));
@ -422,7 +412,7 @@ Toolbox.prototype = {
let deck = this.doc.getElementById("toolbox-deck");
deck.selectedIndex = index;
let definition = gDevTools.getToolDefinitions().get(id);
let definition = gDevTools.getToolDefinitionMap().get(id);
this._currentToolId = id;
@ -478,6 +468,10 @@ Toolbox.prototype = {
/**
* Create a host object based on the given host type.
*
* Warning: some hosts require that the toolbox target provides a reference to
* the attached tab. Not all Targets have a tab property - make sure you correctly
* mix and match hosts and targets.
*
* @param {string} hostType
* The host type of the new host object
*
@ -539,7 +533,7 @@ Toolbox.prototype = {
* Id of the tool that was registered
*/
_toolRegistered: function TBOX_toolRegistered(event, toolId) {
let defs = gDevTools.getToolDefinitions();
let defs = gDevTools.getToolDefinitionMap();
let tool = defs.get(toolId);
this._buildTabForTool(tool);

View File

@ -25,8 +25,8 @@ const FORBIDDEN_IDS = new Set("toolbox", "");
* set of tools and keeps track of open toolboxes in the browser.
*/
this.DevTools = function DevTools() {
this._tools = new Map();
this._toolboxes = new Map();
this._tools = new Map(); // Map<toolId, tool>
this._toolboxes = new Map(); // Map<target, toolbox>
// destroy() is an observer's handler so we need to preserve context.
this.destroy = this.destroy.bind(this);
@ -85,11 +85,16 @@ DevTools.prototype = {
*
* @param {string} toolId
* id of the tool to unregister
* @param {boolean} isQuitApplication
* true to indicate that the call is due to app quit, so we should not
* cause a cascade of costly events
*/
unregisterTool: function DT_unregisterTool(toolId) {
unregisterTool: function DT_unregisterTool(toolId, isQuitApplication) {
this._tools.delete(toolId);
this.emit("tool-unregistered", toolId);
if (!isQuitApplication) {
this.emit("tool-unregistered", toolId);
}
},
/**
@ -99,7 +104,7 @@ DevTools.prototype = {
* @return {Map} tools
* A map of the the tool definitions registered in this instance
*/
getToolDefinitions: function DT_getToolDefinitions() {
getToolDefinitionMap: function DT_getToolDefinitionMap() {
let tools = new Map();
for (let [key, value] of this._tools) {
@ -118,6 +123,31 @@ DevTools.prototype = {
return tools;
},
/**
* Tools have an inherent ordering that can't be represented in a Map so
* getToolDefinitionArray provides an alternative representation of the
* definitions sorted by ordinal value.
*
* @return {Array} tools
* A sorted array of the tool definitions registered in this instance
*/
getToolDefinitionArray: function DT_getToolDefinitionArray() {
const MAX_ORDINAL = 99;
let definitions = [];
for (let [id, definition] of this.getToolDefinitionMap()) {
definitions.push(definition);
}
definitions.sort(function(d1, d2) {
let o1 = (typeof d1.ordinal == "number") ? d1.ordinal : MAX_ORDINAL;
let o2 = (typeof d2.ordinal == "number") ? d2.ordinal : MAX_ORDINAL;
return o1 - o2;
});
return definitions;
},
/**
* Show a Toolbox for a target (either by creating a new one, or if a toolbox
* already exists for the target, by bring to the front the existing one)
@ -218,8 +248,13 @@ DevTools.prototype = {
destroy: function() {
Services.obs.removeObserver(this.destroy, "quit-application");
delete this._trackedBrowserWindows;
delete this._toolboxes;
for (let [key, tool] of this._tools) {
this.unregisterTool(key, true);
}
// Cleaning down the toolboxes: i.e.
// for (let [target, toolbox] of this._toolboxes) toolbox.destroy();
// Is taken care of by the gDevToolsBrowser.forgetBrowserWindow
},
};
@ -313,8 +348,43 @@ let gDevToolsBrowser = {
* properties of the tool to add
*/
_addToolToWindows: function DT_addToolToWindows(toolDefinition) {
// We need to insert the new tool in the right place, which means knowing
// the tool that comes before the tool that we're trying to add
let allDefs = gDevTools.getToolDefinitionArray();
let prevDef;
for (let def of allDefs) {
if (def === toolDefinition) {
break;
}
prevDef = def;
}
for (let win of gDevToolsBrowser._trackedBrowserWindows) {
gDevToolsBrowser._addToolToMenu(toolDefinition, win.document);
let doc = win.document;
let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc);
doc.getElementById("mainCommandSet").appendChild(elements.cmd);
if (elements.key) {
doc.getElementById("mainKeyset").appendChild(elements.key);
}
doc.getElementById("mainBroadcasterSet").appendChild(elements.bc);
let amp = doc.getElementById("appmenu_webDeveloper_popup");
if (amp) {
let ref = (prevDef != null) ?
doc.getElementById("appmenuitem_" + prevDef.id).nextSibling :
doc.getElementById("appmenu_devtools_separator");
amp.insertBefore(elements.appmenuitem, ref);
}
let mp = doc.getElementById("menuWebDeveloperPopup");
let ref = (prevDef != null) ?
doc.getElementById("menuitem_" + prevDef.id).nextSibling :
doc.getElementById("menu_devtools_separator");
mp.insertBefore(elements.menuitem, ref);
}
},
@ -331,22 +401,20 @@ let gDevToolsBrowser = {
let fragAppMenuItems = doc.createDocumentFragment();
let fragMenuItems = doc.createDocumentFragment();
for (let [key, toolDefinition] of gDevTools._tools) {
let frags = gDevToolsBrowser._addToolToMenu(toolDefinition, doc, true);
for (let toolDefinition of gDevTools.getToolDefinitionArray()) {
let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc);
if (!frags) {
if (!elements) {
return;
}
let [cmd, key, bc, appmenuitem, menuitem] = frags;
fragCommands.appendChild(cmd);
if (key) {
fragKeys.appendChild(key);
fragCommands.appendChild(elements.cmd);
if (elements.key) {
fragKeys.appendChild(elements.key);
}
fragBroadcasters.appendChild(bc);
fragAppMenuItems.appendChild(appmenuitem);
fragMenuItems.appendChild(menuitem);
fragBroadcasters.appendChild(elements.bc);
fragAppMenuItems.appendChild(elements.appmenuitem);
fragMenuItems.appendChild(elements.menuitem);
}
let mcs = doc.getElementById("mainCommandSet");
@ -376,11 +444,8 @@ let gDevToolsBrowser = {
* Tool definition of the tool to add a menu entry.
* @param {XULDocument} doc
* The document to which the tool menu item is to be added.
* @param {Boolean} [noAppend]
* Return an array of elements instead of appending them to the
* document. Default is false.
*/
_addToolToMenu: function DT_addToolToMenu(toolDefinition, doc, noAppend) {
_createToolMenuElements: function DT_createToolMenuElements(toolDefinition, doc) {
let id = toolDefinition.id;
// Prevent multiple entries for the same tool.
@ -429,30 +494,13 @@ let gDevToolsBrowser = {
menuitem.setAttribute("accesskey", toolDefinition.accesskey);
}
if (noAppend) {
return [cmd, key, bc, appmenuitem, menuitem];
} else {
let mcs = doc.getElementById("mainCommandSet");
mcs.appendChild(cmd);
if (key) {
let mks = doc.getElementById("mainKeyset");
mks.appendChild(key);
}
let mbs = doc.getElementById("mainBroadcasterSet");
mbs.appendChild(bc);
let amp = doc.getElementById("appmenu_webDeveloper_popup");
if (amp) {
let amps = doc.getElementById("appmenu_devtools_separator");
amp.insertBefore(appmenuitem, amps);
}
let mp = doc.getElementById("menuWebDeveloperPopup");
let mps = doc.getElementById("menu_devtools_separator");
mp.insertBefore(menuitem, mps);
}
return {
cmd: cmd,
key: key,
bc: bc,
appmenuitem: appmenuitem,
menuitem: menuitem
};
},
/**
@ -534,10 +582,6 @@ let gDevToolsBrowser = {
* The window containing the menu entry
*/
forgetBrowserWindow: function DT_forgetBrowserWindow(win) {
if (!gDevToolsBrowser._trackedBrowserWindows) {
return;
}
gDevToolsBrowser._trackedBrowserWindows.delete(win);
// Destroy toolboxes for closed window
@ -557,7 +601,6 @@ let gDevToolsBrowser = {
*/
destroy: function() {
Services.obs.removeObserver(gDevToolsBrowser.destroy, "quit-application");
delete gDevToolsBrowser._trackedBrowserWindows;
},
}
this.gDevToolsBrowser = gDevToolsBrowser;

View File

@ -32,11 +32,11 @@ function runTests(aTab) {
};
ok(gDevTools, "gDevTools exists");
is(gDevTools.getToolDefinitions().has(toolId), false,
is(gDevTools.getToolDefinitionMap().has(toolId), false,
"The tool is not registered");
gDevTools.registerTool(toolDefinition);
is(gDevTools.getToolDefinitions().has(toolId), true,
is(gDevTools.getToolDefinitionMap().has(toolId), true,
"The tool is registered");
let target = TargetFactory.forTab(gBrowser.selectedTab);
@ -51,14 +51,14 @@ function continueTests(toolbox, panel) {
ok(toolbox.getCurrentPanel(), "panel value is correct");
is(toolbox.currentToolId, toolId, "toolbox _currentToolId is correct");
let toolDefinitions = gDevTools.getToolDefinitions();
let toolDefinitions = gDevTools.getToolDefinitionMap();
is(toolDefinitions.has(toolId), true, "The tool is in gDevTools");
let toolDefinition = toolDefinitions.get(toolId);
is(toolDefinition.id, toolId, "toolDefinition id is correct");
gDevTools.unregisterTool(toolId);
is(gDevTools.getToolDefinitions().has(toolId), false,
is(gDevTools.getToolDefinitionMap().has(toolId), false,
"The tool is no longer registered");
toolbox.destroy().then(function() {

View File

@ -40,7 +40,7 @@ function toolRegistered(event, toolId)
{
is(toolId, "test-tool", "tool-registered event handler sent tool id");
ok(gDevTools.getToolDefinitions().has(toolId), "tool added to map");
ok(gDevTools.getToolDefinitionMap().has(toolId), "tool added to map");
// test that it appeared in the UI
let doc = toolbox.frame.contentDocument;
@ -81,7 +81,7 @@ function toolUnregistered(event, toolId)
{
is(toolId, "test-tool", "tool-unregistered event handler sent tool id");
ok(!gDevTools.getToolDefinitions().has(toolId), "tool removed from map");
ok(!gDevTools.getToolDefinitionMap().has(toolId), "tool removed from map");
// test that it disappeared from the UI
let doc = toolbox.frame.contentDocument;

View File

@ -276,7 +276,12 @@ function test() {
is(inspector.highlighter.nodeInfo.classesBox.textContent, "",
"No classes in the infobar before edit.");
},
execute: function() {
execute: function(after) {
inspector.once("markupmutation", function() {
// needed because we need to make sure the infobar is updated
// not just the markupview (which happens in this event loop)
executeSoon(after);
});
let editor = markup.getContainer(doc.querySelector("#node18")).editor;
let attr = editor.attrs["id"].querySelector(".editable");
editField(attr, attr.textContent + ' class="newclass" style="color:green"');
@ -310,7 +315,8 @@ function test() {
is(doc.querySelector("#retag-me-2").parentNode, node,
"retag-me-2 should be a child of the old element.");
},
execute: function() {
execute: function(after) {
inspector.once("markupmutation", after);
let node = doc.querySelector("#retag-me");
let editor = markup.getContainer(node).editor;
let field = editor.tag;
@ -367,9 +373,10 @@ function test() {
inspector.selection.once("new-node", function BIMET_testAsyncSetupNewNode() {
test.before();
test.execute();
test.after();
undoRedo(test, callback);
test.execute(function() {
test.after();
undoRedo(test, callback);
});
});
executeSoon(function BIMET_setNode2() {
test.setup();

View File

@ -15,7 +15,6 @@ XPCSHELL_TESTS = unit
MOCHITEST_BROWSER_FILES = \
browser_browser_basic.js \
browser_promise_basic.js \
browser_require_basic.js \
browser_templater_basic.js \
browser_toolbar_basic.js \

View File

@ -1,305 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that our Promise implementation works properly
let tempScope = {};
Cu.import("resource://gre/modules/devtools/_Promise.jsm", tempScope);
let Promise = tempScope.Promise;
function test() {
addTab("about:blank", function() {
info("Starting Promise Tests");
testBasic();
});
}
var postResolution;
function testBasic() {
postResolution = new Promise();
ok(postResolution.isPromise, "We have a promise");
ok(!postResolution.isComplete(), "Promise is initially incomplete");
ok(!postResolution.isResolved(), "Promise is initially unresolved");
ok(!postResolution.isRejected(), "Promise is initially unrejected");
// Test resolve() *after* then() in the same context
var reply = postResolution.then(testPostResolution, fail)
.resolve("postResolution");
is(reply, postResolution, "return this; working ok");
}
var preResolution;
function testPostResolution(data) {
is(data, "postResolution", "data is postResolution");
ok(postResolution.isComplete(), "postResolution Promise is complete");
ok(postResolution.isResolved(), "postResolution Promise is resolved");
ok(!postResolution.isRejected(), "postResolution Promise is unrejected");
try {
info("Expected double resolve error");
postResolution.resolve("double resolve");
ok(false, "double resolve");
}
catch (ex) {
// Expected
}
// Test resolve() *before* then() in the same context
preResolution = new Promise();
var reply = preResolution.resolve("preResolution")
.then(testPreResolution, fail);
is(reply, preResolution, "return this; working ok");
}
var laterResolution;
function testPreResolution(data) {
is(data, "preResolution", "data is preResolution");
ok(preResolution.isComplete(), "preResolution Promise is complete");
ok(preResolution.isResolved(), "preResolution Promise is resolved");
ok(!preResolution.isRejected(), "preResolution Promise is unrejected");
// Test resolve() *after* then() in a later context
laterResolution = new Promise();
laterResolution.then(testLaterResolution, fail);
executeSoon(function() {
laterResolution.resolve("laterResolution");
});
}
var laterRejection;
function testLaterResolution(data) {
is(data, "laterResolution", "data is laterResolution");
ok(laterResolution.isComplete(), "laterResolution Promise is complete");
ok(laterResolution.isResolved(), "laterResolution Promise is resolved");
ok(!laterResolution.isRejected(), "laterResolution Promise is unrejected");
// Test reject() *after* then() in a later context
laterRejection = new Promise().then(fail, testLaterRejection);
executeSoon(function() {
laterRejection.reject("laterRejection");
});
}
function testLaterRejection(data) {
is(data, "laterRejection", "data is laterRejection");
ok(laterRejection.isComplete(), "laterRejection Promise is complete");
ok(!laterRejection.isResolved(), "laterRejection Promise is unresolved");
ok(laterRejection.isRejected(), "laterRejection Promise is rejected");
// Test chaining
var orig = new Promise();
orig.chainPromise(function(data) {
is(data, "origData", "data is origData");
return data.replace(/orig/, "new");
}).then(function(data) {
is(data, "newData", "data is newData");
testChain();
});
orig.resolve("origData");
}
var member1;
var member2;
var member3;
var laterGroup;
function testChain() {
// Test an empty group
var empty1 = Promise.group();
ok(empty1.isComplete(), "empty1 Promise is complete");
ok(empty1.isResolved(), "empty1 Promise is resolved");
ok(!empty1.isRejected(), "empty1 Promise is unrejected");
// Test a group with no members
var empty2 = Promise.group([]);
ok(empty2.isComplete(), "empty2 Promise is complete");
ok(empty2.isResolved(), "empty2 Promise is resolved");
ok(!empty2.isRejected(), "empty2 Promise is unrejected");
// Test grouping using resolve() in a later context
member1 = new Promise();
member2 = new Promise();
member3 = new Promise();
laterGroup = Promise.group(member1, member2, member3);
laterGroup.then(testLaterGroup, fail);
member1.then(function(data) {
is(data, "member1", "member1 is member1");
executeSoon(function() {
member2.resolve("member2");
});
}, fail);
member2.then(function(data) {
is(data, "member2", "member2 is member2");
executeSoon(function() {
member3.resolve("member3");
});
}, fail);
member3.then(function(data) {
is(data, "member3", "member3 is member3");
// The group should now fire
}, fail);
executeSoon(function() {
member1.resolve("member1");
});
}
var tidyGroup;
function testLaterGroup(data) {
is(data[0], "member1", "member1 is member1");
is(data[1], "member2", "member2 is member2");
is(data[2], "member3", "member3 is member3");
is(data.length, 3, "data.length is right");
ok(laterGroup.isComplete(), "laterGroup Promise is complete");
ok(laterGroup.isResolved(), "laterGroup Promise is resolved");
ok(!laterGroup.isRejected(), "laterGroup Promise is unrejected");
// Test grouping resolve() *before* then() in the same context
tidyGroup = Promise.group([
postResolution, preResolution, laterResolution,
member1, member2, member3, laterGroup
]);
tidyGroup.then(testTidyGroup, fail);
}
var failGroup;
function testTidyGroup(data) {
is(data[0], "postResolution", "postResolution is postResolution");
is(data[1], "preResolution", "preResolution is preResolution");
is(data[2], "laterResolution", "laterResolution is laterResolution");
is(data[3], "member1", "member1 is member1");
is(data[6][1], "member2", "laterGroup is laterGroup");
is(data.length, 7, "data.length is right");
ok(tidyGroup.isComplete(), "tidyGroup Promise is complete");
ok(tidyGroup.isResolved(), "tidyGroup Promise is resolved");
ok(!tidyGroup.isRejected(), "tidyGroup Promise is unrejected");
// Test grouping resolve() *before* then() in the same context
failGroup = Promise.group(postResolution, laterRejection);
failGroup.then(fail, testFailGroup);
}
function testFailGroup(data) {
is(data, "laterRejection", "laterRejection is laterRejection");
postResolution = undefined;
preResolution = undefined;
laterResolution = undefined;
member1 = undefined;
member2 = undefined;
member3 = undefined;
laterGroup = undefined;
laterRejection = undefined;
testTrap();
}
function testTrap() {
var p = new Promise();
var message = "Expected exception";
p.chainPromise(
function() {
throw new Error(message);
}).trap(
function(aError) {
is(aError instanceof Error, true, "trap received exception");
is(aError.message, message, "trap received correct exception");
return 1;
}).chainPromise(
function(aResult) {
is(aResult, 1, "trap restored correct result");
testAlways();
});
p.resolve();
}
function testAlways() {
var shouldbeTrue1 = false;
var shouldbeTrue2 = false;
var p = new Promise();
p.chainPromise(
function() {
throw new Error();
}
).chainPromise(// Promise rejected, should not be executed
function() {
ok(false, "This should not be executed");
}
).always(
function(x) {
shouldbeTrue1 = true;
return "random value";
}
).trap(
function(arg) {
ok((arg instanceof Error), "The random value should be ignored");
return 1;// We should still have this result later
}
).trap(
function() {
ok(false, "This should not be executed 2");
}
).always(
function() {
shouldbeTrue2 = true;
}
).then(
function(aResult){
ok(shouldbeTrue1, "First always must be executed");
ok(shouldbeTrue2, "Second always must be executed");
is(aResult, 1, "Result should be unaffected by always");
testComplete();
}
);
p.resolve();
}
function fail() {
gBrowser.removeCurrentTab();
info("Failed Promise Tests");
ok(false, "fail called");
finish();
}
/**
* We wish to launch all tests with several configurations (at the moment,
* non-debug and debug mode).
*
* If 0, we have not completed any test yet.
* If 1, we have completed the tests in non-debug mode.
* If 2, we have also completed the tests in debug mode.
*/
var configurationTestComplete = 0;
function testComplete() {
switch (configurationTestComplete) {
case 0:
info("Finished run in non-debug mode");
configurationTestComplete = 1;
Promise.Debug.setDebug(true);
window.setTimeout(testBasic, 0);
return;
case 1:
info("Finished run in debug mode");
configurationTestComplete = 2;
Promise.Debug.setDebug(false);
window.setTimeout(finished, 0);
return;
default:
ok(false, "Internal error in testComplete "+configurationTestComplete);
return;
}
}
function finished() {
gBrowser.removeCurrentTab();
info("Finishing Promise Tests");
finish();
}

View File

@ -11,7 +11,7 @@
var imports = {};
Cu.import("resource:///modules/devtools/Templater.jsm", imports);
Cu.import("resource://gre/modules/devtools/_Promise.jsm", imports);
Cu.import("resource://gre/modules/commonjs/promise/core.js", imports);
function test() {
addTab("http://example.com/browser/browser/devtools/shared/test/browser_templater_basic.html", function() {
@ -278,9 +278,9 @@ var tests = [
];
function delayReply(data) {
var p = new imports.Promise();
var d = imports.Promise.defer();
executeSoon(function() {
p.resolve(data);
d.resolve(data);
});
return p;
return d.promise;
}

View File

@ -13,56 +13,43 @@ const Cu = Components.utils;
const CONSOLEAPI_CLASS_ID = "{b49c18f8-3379-4fc0-8c90-d7772c1a9ff3}";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/devtools/gDevTools.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
"resource:///modules/devtools/gDevTools.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TargetFactory",
"resource:///modules/devtools/Target.jsm");
"resource:///modules/devtools/Target.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/promise/core.js");
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
this.EXPORTED_SYMBOLS = ["HUDService"];
function LogFactory(aMessagePrefix)
{
function log(aMessage) {
var _msg = aMessagePrefix + " " + aMessage + "\n";
dump(_msg);
}
return log;
}
let log = LogFactory("*** HUDService:");
// The HTML namespace.
const HTML_NS = "http://www.w3.org/1999/xhtml";
// The XUL namespace.
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
///////////////////////////////////////////////////////////////////////////
//// The HUD service
function HUD_SERVICE()
{
// These methods access the "this" object, but they're registered as
// event listeners. So we hammer in the "this" binding.
this.onWindowUnload = this.onWindowUnload.bind(this);
/**
* Keeps a reference for each HeadsUpDisplay that is created
*/
this.hudReferences = {};
};
}
HUD_SERVICE.prototype =
{
/**
* Keeps a reference for each HeadsUpDisplay that is created
* @type object
*/
hudReferences: null,
/**
* getter for UI commands to be used by the frontend
*
@ -72,12 +59,6 @@ HUD_SERVICE.prototype =
return HeadsUpDisplayUICommands;
},
/**
* The sequencer is a generator (after initialization) that returns unique
* integers
*/
sequencer: null,
/**
* Firefox-specific current tab getter
*
@ -88,134 +69,22 @@ HUD_SERVICE.prototype =
},
/**
* Activate a HeadsUpDisplay for the given tab context.
* Open a Web Console for the given target.
*
* @param nsIDOMElement aTab
* The xul:tab element.
* @see devtools/framework/Target.jsm for details about targets.
*
* @param object aTarget
* The target that the web console will connect to.
* @param nsIDOMElement aIframe
* The iframe element into which to place the web console.
* @param RemoteTarget aTarget
* The target that the web console will connect to.
* @return object
* The new HeadsUpDisplay instance.
* A Promise object for the opening of the new WebConsole instance.
*/
activateHUDForContext: function HS_activateHUDForContext(aTab, aIframe,
aTarget)
openWebConsole: function HS_openWebConsole(aTarget, aIframe)
{
let hudId = "hud_" + aTab.linkedPanel;
if (hudId in this.hudReferences) {
return this.hudReferences[hudId];
}
this.wakeup();
let window = aTab.ownerDocument.defaultView;
let gBrowser = window.gBrowser;
window.addEventListener("unload", this.onWindowUnload, false);
let hud = new WebConsole(aTab, aIframe, aTarget);
this.hudReferences[hudId] = hud;
return hud;
},
/**
* Deactivate a HeadsUpDisplay for the given tab context.
*
* @param nsIDOMElement aTab
* The xul:tab element you want to enable the Web Console for.
* @return void
*/
deactivateHUDForContext: function HS_deactivateHUDForContext(aTab)
{
let hudId = "hud_" + aTab.linkedPanel;
if (!(hudId in this.hudReferences)) {
return;
}
let hud = this.getHudReferenceById(hudId);
let document = hud.chromeDocument;
hud.destroy(function() {
let id = WebConsoleUtils.supportsString(hudId);
Services.obs.notifyObservers(id, "web-console-destroyed", null);
});
delete this.hudReferences[hudId];
if (Object.keys(this.hudReferences).length == 0) {
let autocompletePopup = document.
getElementById("webConsole_autocompletePopup");
if (autocompletePopup) {
autocompletePopup.parentNode.removeChild(autocompletePopup);
}
let window = document.defaultView;
window.removeEventListener("unload", this.onWindowUnload, false);
let gBrowser = window.gBrowser;
let tabContainer = gBrowser.tabContainer;
this.suspend();
}
let contentWindow = aTab.linkedBrowser.contentWindow;
contentWindow.focus();
},
/**
* get a unique ID from the sequence generator
*
* @returns integer
*/
sequenceId: function HS_sequencerId()
{
if (!this.sequencer) {
this.sequencer = this.createSequencer(-1);
}
return this.sequencer.next();
},
/**
* "Wake up" the Web Console activity. This is called when the first Web
* Console is open. This method initializes the various observers we have.
*
* @returns void
*/
wakeup: function HS_wakeup()
{
if (Object.keys(this.hudReferences).length > 0) {
return;
}
WebConsoleObserver.init();
},
/**
* Suspend Web Console activity. This is called when all Web Consoles are
* closed.
*
* @returns void
*/
suspend: function HS_suspend()
{
delete this.lastFinishedRequestCallback;
WebConsoleObserver.uninit();
},
/**
* Shutdown all HeadsUpDisplays on quit-application-granted.
*
* @returns void
*/
shutdown: function HS_shutdown()
{
for (let hud of this.hudReferences) {
this.deactivateHUDForContext(hud.tab);
}
let hud = new WebConsole(aTarget, aIframe);
this.hudReferences[hud.hudId] = hud;
return hud.init();
},
/**
@ -226,8 +95,13 @@ HUD_SERVICE.prototype =
*/
getHudByWindow: function HS_getHudByWindow(aContentWindow)
{
let hudId = this.getHudIdByWindow(aContentWindow);
return hudId ? this.hudReferences[hudId] : null;
for each (let hud in this.hudReferences) {
let target = hud.target;
if (target && target.tab && target.window === aContentWindow) {
return hud;
}
}
return null;
},
/**
@ -239,16 +113,8 @@ HUD_SERVICE.prototype =
*/
getHudIdByWindow: function HS_getHudIdByWindow(aContentWindow)
{
let window = this.currentContext();
let index =
window.gBrowser.getBrowserIndexForDocument(aContentWindow.document);
if (index == -1) {
return null;
}
let tab = window.gBrowser.tabs[index];
let hudId = "hud_" + tab.linkedPanel;
return hudId in this.hudReferences ? hudId : null;
let hud = this.getHudByWindow(aContentWindow);
return hud ? hud.hudId : null;
},
/**
@ -270,101 +136,54 @@ HUD_SERVICE.prototype =
* @type function
*/
lastFinishedRequestCallback: null,
/**
* Creates a generator that always returns a unique number for use in the
* indexes
*
* @returns Generator
*/
createSequencer: function HS_createSequencer(aInt)
{
function sequencer(aInt)
{
while(1) {
aInt++;
yield aInt;
}
}
return sequencer(aInt);
},
/**
* Called whenever a browser window closes. Cleans up any consoles still
* around.
*
* @param nsIDOMEvent aEvent
* The dispatched event.
* @returns void
*/
onWindowUnload: function HS_onWindowUnload(aEvent)
{
let window = aEvent.target.defaultView;
window.removeEventListener("unload", this.onWindowUnload, false);
let gBrowser = window.gBrowser;
let tabContainer = gBrowser.tabContainer;
let tab = tabContainer.firstChild;
while (tab != null) {
this.deactivateHUDForContext(tab);
tab = tab.nextSibling;
}
},
};
/**
* A WebConsole instance is an interactive console initialized *per tab*
* A WebConsole instance is an interactive console initialized *per target*
* that displays console log data as well as provides an interactive terminal to
* manipulate the current tab's document content.
* manipulate the target's document content.
*
* This object only wraps the iframe that holds the Web Console UI.
*
* @param nsIDOMElement aTab
* The xul:tab for which you want the WebConsole object.
* @constructor
* @param object aTarget
* The target that the web console will connect to.
* @param nsIDOMElement aIframe
* iframe into which we should create the WebConsole UI.
* @param RemoteTarget aTarget
* The target that the web console will connect to.
*/
function WebConsole(aTab, aIframe, aTarget)
function WebConsole(aTarget, aIframe)
{
this.tab = aTab;
if (this.tab == null) {
throw new Error('Missing tab');
}
this.iframe = aIframe;
if (this.iframe == null) {
console.trace();
throw new Error('Missing iframe');
}
this.chromeDocument = this.tab.ownerDocument;
this.chromeWindow = this.chromeDocument.defaultView;
this.hudId = "hud_" + this.tab.linkedPanel;
this.target = aTarget;
this._onIframeLoad = this._onIframeLoad.bind(this);
this.iframe.className = "web-console-frame";
this.iframe.addEventListener("load", this._onIframeLoad, true);
this.positionConsole();
this.chromeDocument = this.iframe.ownerDocument;
this.chromeWindow = this.chromeDocument.defaultView;
this.hudId = "hud_" + Date.now();
this.target = aTarget;
}
WebConsole.prototype = {
/**
* The xul:tab for which the current Web Console instance was created.
* @type nsIDOMElement
*/
tab: null,
chromeWindow: null,
chromeDocument: null,
hudId: null,
target: null,
iframe: null,
_destroyer: null,
_browserWindow: null,
get browserWindow()
{
if (!this._browserWindow) {
let window = this.chromeWindow.top;
let element = window.document.documentElement;
if (element.getAttribute("windowtype") != "navigator:browser") {
window = HUDService.currentContext();
}
this._browserWindow = window;
}
return this._browserWindow;
},
/**
* Getter for HUDService.lastFinishedRequestCallback.
@ -380,7 +199,7 @@ WebConsole.prototype = {
*/
get mainPopupSet()
{
return this.chromeDocument.getElementById("mainPopupSet");
return this.browserWindow.document.getElementById("mainPopupSet");
},
/**
@ -392,18 +211,48 @@ WebConsole.prototype = {
return this.ui ? this.ui.outputNode : null;
},
get gViewSourceUtils() this.chromeWindow.gViewSourceUtils,
get gViewSourceUtils() this.browserWindow.gViewSourceUtils,
/**
* The "load" event handler for the Web Console iframe.
* @private
* Initialize the Web Console instance.
*
* @return object
* A Promise for the initialization.
*/
_onIframeLoad: function WC__onIframeLoad()
init: function WC_init()
{
this.iframe.removeEventListener("load", this._onIframeLoad, true);
let deferred = Promise.defer();
this.iframeWindow = this.iframe.contentWindow.wrappedJSObject;
this.ui = new this.iframeWindow.WebConsoleFrame(this);
let onIframeLoad = function() {
this.iframe.removeEventListener("load", onIframeLoad, true);
initUI();
}.bind(this);
let initUI = function() {
this.iframeWindow = this.iframe.contentWindow.wrappedJSObject;
this.ui = new this.iframeWindow.WebConsoleFrame(this);
this.ui.init().then(onSuccess, onFailure);
}.bind(this);
let onSuccess = function() {
deferred.resolve(this);
}.bind(this);
let onFailure = function(aReason) {
deferred.reject(aReason);
};
let win, doc;
if ((win = this.iframe.contentWindow) &&
(doc = win.document) &&
doc.readyState == "complete") {
this.iframe.addEventListener("load", onIframeLoad, true);
}
else {
initUI();
}
return deferred.promise;
},
/**
@ -418,50 +267,6 @@ WebConsole.prototype = {
return l10n.getFormatStr("webConsoleWindowTitleAndURL", [url]);
},
consoleWindowUnregisterOnHide: true,
/**
* Position the Web Console UI.
*/
positionConsole: function WC_positionConsole()
{
let lastIndex = -1;
if (this.outputNode && this.outputNode.getIndexOfFirstVisibleRow) {
lastIndex = this.outputNode.getIndexOfFirstVisibleRow() +
this.outputNode.getNumberOfVisibleRows() - 1;
}
this._beforePositionConsole(lastIndex);
},
/**
* Common code that needs to execute before the Web Console is repositioned.
* @private
* @param number aLastIndex
* The last visible message in the console output before repositioning
* occurred.
*/
_beforePositionConsole:
function WC__beforePositionConsole(aLastIndex)
{
if (!this.ui) {
return;
}
let onLoad = function() {
this.iframe.removeEventListener("load", onLoad, true);
this.iframeWindow = this.iframe.contentWindow.wrappedJSObject;
this.ui.positionConsole(this.iframeWindow);
if (aLastIndex > -1 && aLastIndex < this.outputNode.getRowCount()) {
this.outputNode.ensureIndexIsVisible(aLastIndex);
}
}.bind(this);
this.iframe.addEventListener("load", onLoad, true);
},
/**
* The JSTerm object that manages the console's input.
* @see webconsole.js::JSTerm
@ -478,7 +283,9 @@ WebConsole.prototype = {
*/
_onClearButton: function WC__onClearButton()
{
this.chromeWindow.DeveloperToolbar.resetErrorsCount(this.tab);
if (this.target.isLocalTab) {
this.browserWindow.DeveloperToolbar.resetErrorsCount(this.target.tab);
}
},
/**
@ -498,7 +305,7 @@ WebConsole.prototype = {
*/
openLink: function WC_openLink(aLink)
{
this.chromeWindow.openUILinkIn(aLink, "tab");
this.browserWindow.openUILinkIn(aLink, "tab");
},
/**
@ -530,12 +337,13 @@ WebConsole.prototype = {
viewSourceInStyleEditor:
function WC_viewSourceInStyleEditor(aSourceURL, aSourceLine)
{
let styleSheets = this.tab.linkedBrowser.contentWindow.document.styleSheets;
let styleSheets = {};
if (this.target.isLocalTab) {
styleSheets = this.target.window.document.styleSheets;
}
for each (let style in styleSheets) {
if (style.href == aSourceURL) {
let target = TargetFactory.forTab(this.tab);
let gDevTools = this.chromeWindow.gDevTools;
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
gDevTools.showToolbox(this.target, "styleeditor").then(function(toolbox) {
toolbox.getCurrentPanel().selectStyleSheet(style, aSourceLine);
});
return;
@ -549,15 +357,20 @@ WebConsole.prototype = {
* Destroy the object. Call this method to avoid memory leaks when the Web
* Console is closed.
*
* @param function [aOnDestroy]
* Optional function to invoke when the Web Console instance is
* destroyed.
* @return object
* A Promise object that is resolved once the Web Console is closed.
*/
destroy: function WC_destroy(aOnDestroy)
destroy: function WC_destroy()
{
// Make sure that the console panel does not try to call
// deactivateHUDForContext() again.
this.consoleWindowUnregisterOnHide = false;
if (this._destroyer) {
return this._destroyer.promise;
}
delete HUDService.hudReferences[this.hudId];
let tabWindow = this.target.isLocalTab ? this.target.window : null;
this._destroyer = Promise.defer();
let popupset = this.mainPopupSet;
let panels = popupset.querySelectorAll("panel[hudId=" + this.hudId + "]");
@ -566,27 +379,27 @@ WebConsole.prototype = {
}
let onDestroy = function WC_onDestroyUI() {
// Remove the iframe and the consolePanel if the Web Console is inside a
// floating panel.
if (this.consolePanel && this.consolePanel.parentNode) {
this.consolePanel.hidePopup();
this.consolePanel.parentNode.removeChild(this.consolePanel);
this.consolePanel = null;
try {
tabWindow && tabWindow.focus();
}
catch (ex) {
// Tab focus can fail if the tab is closed.
}
if (this.iframe.parentNode) {
this.iframe.parentNode.removeChild(this.iframe);
}
let id = WebConsoleUtils.supportsString(this.hudId);
Services.obs.notifyObservers(id, "web-console-destroyed", null);
aOnDestroy && aOnDestroy();
this._destroyer.resolve(null);
}.bind(this);
if (this.ui) {
this.ui.destroy(onDestroy);
this.ui.destroy().then(onDestroy);
}
else {
onDestroy();
}
return this._destroyer.promise;
},
};
@ -595,9 +408,16 @@ WebConsole.prototype = {
//////////////////////////////////////////////////////////////////////////
var HeadsUpDisplayUICommands = {
toggleHUD: function UIC_toggleHUD(aOptions)
/**
* Toggle the Web Console for the current tab.
*
* @return object
* A Promise for either the opening of the toolbox that holds the Web
* Console, or a Promise for the closing of the toolbox.
*/
toggleHUD: function UIC_toggleHUD()
{
var window = HUDService.currentContext();
let window = HUDService.currentContext();
let target = TargetFactory.forTab(window.gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
@ -606,84 +426,24 @@ var HeadsUpDisplayUICommands = {
gDevTools.showToolbox(target, "webconsole");
},
toggleRemoteHUD: function UIC_toggleRemoteHUD()
{
if (this.getOpenHUD()) {
this.toggleHUD();
return;
}
let host = Services.prefs.getCharPref("devtools.debugger.remote-host");
let port = Services.prefs.getIntPref("devtools.debugger.remote-port");
let check = { value: false };
let input = { value: host + ":" + port };
let result = Services.prompt.prompt(null,
l10n.getStr("remoteWebConsolePromptTitle"),
l10n.getStr("remoteWebConsolePromptMessage"),
input, null, check);
if (!result) {
return;
}
let parts = input.value.split(":");
if (parts.length != 2) {
return;
}
[host, port] = parts;
if (!host.length || !port.length) {
return;
}
Services.prefs.setCharPref("devtools.debugger.remote-host", host);
Services.prefs.setIntPref("devtools.debugger.remote-port", port);
this.toggleHUD({
host: host,
port: port,
});
},
/**
* Find the hudId for the active chrome window.
* @return string|null
* The hudId or null if the active chrome window has no open Web
* Find if there is a Web Console open for the current tab and return the
* instance.
* @return object|null
* The WebConsole object or null if the active tab has no open Web
* Console.
*/
getOpenHUD: function UIC_getOpenHUD() {
let chromeWindow = HUDService.currentContext();
let hudId = "hud_" + chromeWindow.gBrowser.selectedTab.linkedPanel;
return hudId in HUDService.hudReferences ? hudId : null;
},
};
//////////////////////////////////////////////////////////////////////////
// WebConsoleObserver
//////////////////////////////////////////////////////////////////////////
var WebConsoleObserver = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
init: function WCO_init()
getOpenHUD: function UIC_getOpenHUD()
{
Services.obs.addObserver(this, "quit-application-granted", false);
},
observe: function WCO_observe(aSubject, aTopic)
{
if (aTopic == "quit-application-granted") {
HUDService.shutdown();
let tab = HUDService.currentContext().gBrowser.selectedTab;
if (!tab || !TargetFactory.isKnownTab(tab)) {
return null;
}
},
uninit: function WCO_uninit()
{
Services.obs.removeObserver(this, "quit-application-granted");
let target = TargetFactory.forTab(tab);
let toolbox = gDevTools.getToolbox(target);
let panel = toolbox ? toolbox.getPanel("webconsole") : null;
return panel ? panel.hud : null;
},
};
const HUDService = new HUD_SERVICE();

View File

@ -9,12 +9,12 @@ this.EXPORTED_SYMBOLS = [ "WebConsolePanel" ];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/commonjs/promise/core.js");
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "HUDService",
"resource:///modules/HUDService.jsm");
"resource:///modules/HUDService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
"resource:///modules/devtools/EventEmitter.jsm");
/**
* A DevToolPanel that controls the Web Console.
@ -26,32 +26,29 @@ function WebConsolePanel(iframeWindow, toolbox) {
}
WebConsolePanel.prototype = {
hud: null,
/**
* open is effectively an asynchronous constructor
* Open is effectively an asynchronous constructor.
*
* @return object
* A Promise that is resolved when the Web Console completes opening.
*/
open: function StyleEditor_open() {
let parentDoc = this._frameWindow.document.defaultView.parent.document;
open: function WCP_open()
{
let parentDoc = this._toolbox.doc;
let iframe = parentDoc.getElementById("toolbox-panel-iframe-webconsole");
this.hud = HUDService.activateHUDForContext(this.target.tab, iframe,
this._toolbox.target);
let promise = HUDService.openWebConsole(this.target, iframe);
let deferred = Promise.defer();
let hudId = this.hud.hudId;
let onOpen = function _onWebConsoleOpen(aSubject) {
aSubject.QueryInterface(Ci.nsISupportsString);
if (hudId == aSubject.data) {
Services.obs.removeObserver(onOpen, "web-console-created");
this._isReady = true;
this.emit("ready");
deferred.resolve(this);
}
}.bind(this);
Services.obs.addObserver(onOpen, "web-console-created", false);
return deferred.promise;
return promise.then(function onSuccess(aWebConsole) {
this.hud = aWebConsole;
this._isReady = true;
this.emit("ready");
return this;
}.bind(this), function onError(aReason) {
Cu.reportError("WebConsolePanel open failed. " +
aReason.error + ": " + aReason.message);
});
},
get target() this._toolbox.target,
@ -61,28 +58,15 @@ WebConsolePanel.prototype = {
destroy: function WCP_destroy()
{
if (this.destroyer) {
return this.destroyer.promise;
if (this._destroyer) {
return this._destroyer;
}
this.destroyer = Promise.defer();
this._destroyer = this.hud.destroy();
this._destroyer.then(function() {
this.emit("destroyed");
}.bind(this));
let hudId = this.hud.hudId;
let onClose = function _onWebConsoleClose(aSubject)
{
aSubject.QueryInterface(Ci.nsISupportsString);
if (hudId == aSubject.data) {
Services.obs.removeObserver(onClose, "web-console-destroyed");
this.emit("destroyed");
this.destroyer.resolve(null);
}
}.bind(this);
Services.obs.addObserver(onClose, "web-console-destroyed", false);
HUDService.deactivateHUDForContext(this.hud.tab, false);
return this.destroyer.promise;
return this._destroyer;
},
};

View File

@ -28,14 +28,17 @@ function testClosingAfterCompletion(hud) {
// Focus the inputNode and perform the keycombo to close the WebConsole.
inputNode.focus();
EventUtils.synthesizeKey("k", { accelKey: true, shiftKey: true });
// We can't test for errors right away, because the error occurs after a
// setTimeout(..., 0) in the WebConsole code.
executeSoon(function() {
gDevTools.once("toolbox-destroyed", function() {
browser.removeEventListener("error", errorListener, false);
is(errorWhileClosing, false, "no error while closing the WebConsole");
finishTest();
});
if (Services.appinfo.OS == "Darwin") {
EventUtils.synthesizeKey("k", { accelKey: true, altKey: true });
} else {
EventUtils.synthesizeKey("k", { accelKey: true, shiftKey: true });
}
}

View File

@ -57,13 +57,14 @@ function openConsoles() {
let tab = openTabs[i];
openConsole(tab, function(index, hud) {
ok(hud, "HUD is open for tab " + index);
let window = hud.tab.linkedBrowser.contentWindow;
let window = hud.target.tab.linkedBrowser.contentWindow;
window.console.log("message for tab " + index);
consolesOpen++;
}.bind(null, i));
}
waitForSuccess({
timeout: 10000,
name: "4 web consoles opened",
validatorFn: function()
{

View File

@ -67,6 +67,7 @@ function onStyleEditorReady(aEvent, aPanel)
return sheet;
}
}
return null;
}
waitForFocus(function() {

View File

@ -238,9 +238,11 @@ function finishTest()
finish();
return;
}
hud.jsterm.clearOutput(true);
if (hud.jsterm) {
hud.jsterm.clearOutput(true);
}
closeConsole(hud.tab, finish);
closeConsole(hud.target.tab, finish);
hud = null;
}

View File

@ -43,6 +43,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "AutocompletePopup",
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/promise/core.js");
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
@ -168,9 +171,9 @@ const MIN_FONT_SIZE = 10;
const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
/**
* A WebConsoleFrame instance is an interactive console initialized *per tab*
* A WebConsoleFrame instance is an interactive console initialized *per target*
* that displays console log data as well as provides an interactive terminal to
* manipulate the current tab's document content.
* manipulate the target's document content.
*
* The WebConsoleFrame is responsible for the actual Web Console UI
* implementation.
@ -190,18 +193,9 @@ function WebConsoleFrame(aWebConsoleOwner)
this._toggleFilter = this._toggleFilter.bind(this);
this._flushMessageQueue = this._flushMessageQueue.bind(this);
this._connectionTimeout = this._connectionTimeout.bind(this);
this._outputTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._outputTimerInitialized = false;
this._initDefaultFilterPrefs();
this._commandController = new CommandController(this);
this.positionConsole(window);
this.jsterm = new JSTerm(this);
this.jsterm.inputNode.focus();
this._initConnection();
}
WebConsoleFrame.prototype = {
@ -222,13 +216,6 @@ WebConsoleFrame.prototype = {
*/
proxy: null,
/**
* Timer used for the connection.
* @private
* @type object
*/
_connectTimer: null,
/**
* Getter for the xul:popupset that holds any popups we open.
* @type nsIDOMElement
@ -306,7 +293,7 @@ WebConsoleFrame.prototype = {
groupDepth: 0,
/**
* The current tab location.
* The current target location.
* @type string
*/
contentLocation: "",
@ -336,6 +323,8 @@ WebConsoleFrame.prototype = {
*/
get webConsoleClient() this.proxy ? this.proxy.webConsoleClient : null,
_destroyer: null,
_saveRequestAndResponseBodies: false,
/**
@ -364,60 +353,51 @@ WebConsoleFrame.prototype = {
}.bind(this));
},
/**
* Initialize the WebConsoleFrame instance.
* @return object
* A Promise object for the initialization.
*/
init: function WCF_init()
{
this._initUI();
return this._initConnection();
},
/**
* Connect to the server using the remote debugging protocol.
*
* @private
* @return object
* A Promise object that is resolved/reject based on the connection
* result.
*/
_initConnection: function WCF__initConnection()
{
let deferred = Promise.defer();
this.proxy = new WebConsoleConnectionProxy(this, this.owner.target);
let timeout = Services.prefs.getIntPref(PREF_CONNECTION_TIMEOUT);
this._connectTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._connectTimer.initWithCallback(this._connectionTimeout,
timeout, Ci.nsITimer.TYPE_ONE_SHOT);
let onSuccess = function() {
this.saveRequestAndResponseBodies = this._saveRequestAndResponseBodies;
deferred.resolve(this);
}.bind(this);
this.proxy.connect(function() {
// Don't complete connection if the connection timed-out.
if (this._connectTimer) {
this._connectTimer.cancel();
this._connectTimer = null;
this.saveRequestAndResponseBodies = this._saveRequestAndResponseBodies;
this._onInitComplete();
}
}.bind(this));
},
let onFailure = function(aReason) {
let node = this.createMessageNode(CATEGORY_JS, SEVERITY_ERROR,
aReason.error + ": " + aReason.message);
this.outputMessage(CATEGORY_JS, node);
deferred.reject(aReason);
}.bind(this);
/**
* Connection timeout handler. This method simply prints a message informing
* the user that the connection timed-out.
* @private
*/
_connectionTimeout: function WCF__connectionTimeout()
{
this._connectTimer = null;
let sendNotification = function() {
let id = WebConsoleUtils.supportsString(this.hudId);
Services.obs.notifyObservers(id, "web-console-created", null);
}.bind(this);
let node = this.createMessageNode(CATEGORY_JS, SEVERITY_ERROR,
l10n.getStr("connectionTimeout"));
this.outputMessage(CATEGORY_JS, node);
this.proxy.connect().then(onSuccess, onFailure).then(sendNotification);
// Allow initialization to complete.
this._onInitComplete();
},
/**
* Reset the connection timeout timer.
* @private
*/
_resetConnectionTimeout: function WCF__resetConnectionTimeout()
{
let timer = this._connectTimer;
if (timer) {
let timeout = timer.delay;
timer.cancel();
timer.initWithCallback(this._connectionTimeout, timeout,
Ci.nsITimer.TYPE_ONE_SHOT);
}
return deferred.promise;
},
/**
@ -426,6 +406,18 @@ WebConsoleFrame.prototype = {
*/
_initUI: function WCF__initUI()
{
// Remember that this script is loaded in the webconsole.xul context:
// |window| is the iframe global.
this.window = window;
this.document = this.window.document;
this.rootElement = this.document.documentElement;
this._initDefaultFilterPrefs();
// Register the controller to handle "select all" properly.
this._commandController = new CommandController(this);
this.window.controllers.insertControllerAt(0, this._commandController);
let doc = this.document;
this.filterBox = doc.querySelector(".hud-filter-box");
@ -482,6 +474,10 @@ WebConsoleFrame.prototype = {
this.owner._onClearButton();
this.jsterm.clearOutput(true);
}.bind(this));
this.jsterm = new JSTerm(this);
this.jsterm.init();
this.jsterm.inputNode.focus();
},
/**
@ -567,54 +563,6 @@ WebConsoleFrame.prototype = {
}, this);
},
/**
* Callback method for when the Web Console initialization is complete. For
* now this method sends the web-console-created notification using the
* nsIObserverService.
*
* @private
*/
_onInitComplete: function WC__onInitComplete()
{
let id = WebConsoleUtils.supportsString(this.hudId);
Services.obs.notifyObservers(id, "web-console-created", null);
},
/**
* Position the console in a different location.
*
* Note: you do not usually call this method. This is called by the WebConsole
* instance that owns this iframe. You need to call this if you write
* a different owner or you manually reposition the iframe.
*
* @param object aNewWindow
* Repositioning causes the iframe to reload - bug 254144. You need to
* provide the new window object so we can reinitialize the UI as
* needed.
*/
positionConsole: function WCF_positionConsole(aNewWindow)
{
this.window = aNewWindow;
this.document = this.window.document;
this.rootElement = this.document.documentElement;
// register the controller to handle "select all" properly
this.window.controllers.insertControllerAt(0, this._commandController);
let oldOutputNode = this.outputNode;
this._initUI();
this.jsterm && this.jsterm._initUI();
if (oldOutputNode && oldOutputNode.childNodes.length) {
let parentNode = this.outputNode.parentNode;
parentNode.replaceChild(oldOutputNode, this.outputNode);
this.outputNode = oldOutputNode;
}
this.jsterm && this.jsterm.inputNode.focus();
},
/**
* Increase, decrease or reset the font size.
*
@ -2706,15 +2654,21 @@ WebConsoleFrame.prototype = {
},
/**
* Destroy the HUD object. Call this method to avoid memory leaks when the Web
* Console is closed.
* Destroy the WebConsoleFrame object. Call this method to avoid memory leaks
* when the Web Console is closed.
*
* @param function [aOnDestroy]
* Optional function to invoke when the Web Console instance is
* destroyed.
* @return object
* A Promise that is resolved when the WebConsoleFrame instance is
* destroyed.
*/
destroy: function WCF_destroy(aOnDestroy)
destroy: function WCF_destroy()
{
if (this._destroyer) {
return this._destroyer.promise;
}
this._destroyer = Promise.defer();
this._cssNodes = {};
this._outputQueue = [];
this._pruneCategoriesQueue = {};
@ -2726,22 +2680,26 @@ WebConsoleFrame.prototype = {
}
this._outputTimer = null;
if (this._connectTimer) {
this._connectTimer.cancel();
}
this._connectTimer = null;
if (this.proxy) {
this.proxy.disconnect(aOnDestroy);
this.proxy = null;
}
if (this.jsterm) {
this.jsterm.destroy();
this.jsterm = null;
}
this._commandController = null;
let onDestroy = function() {
this._destroyer.resolve(null);
}.bind(this);
if (this.proxy) {
this.proxy.disconnect().then(onDestroy);
this.proxy = null;
}
else {
onDestroy();
}
return this._destroyer.promise;
},
};
@ -2763,12 +2721,8 @@ function JSTerm(aWebConsoleFrame)
this.history = [];
this.historyIndex = 0;
this.historyPlaceHolder = 0; // this.history.length;
this.autocompletePopup = new AutocompletePopup(this.hud.owner.chromeDocument);
this.autocompletePopup.onSelect = this.onAutocompleteSelect.bind(this);
this.autocompletePopup.onClick = this.acceptProposedCompletion.bind(this);
this._keyPress = this.keyPress.bind(this);
this._inputEventHandler = this.inputEventHandler.bind(this);
this._initUI();
}
JSTerm.prototype = {
@ -2790,6 +2744,10 @@ JSTerm.prototype = {
*/
history: null,
autocompletePopup: null,
inputNode: null,
completeNode: null,
/**
* Getter for the element that holds the messages we display.
* @type nsIDOMElement
@ -2808,10 +2766,14 @@ JSTerm.prototype = {
/**
* Initialize the JSTerminal UI.
* @private
*/
_initUI: function JST__initUI()
init: function JST_init()
{
let chromeDocument = this.hud.owner.chromeDocument;
this.autocompletePopup = new AutocompletePopup(chromeDocument);
this.autocompletePopup.onSelect = this.onAutocompleteSelect.bind(this);
this.autocompletePopup.onClick = this.acceptProposedCompletion.bind(this);
let doc = this.hud.document;
this.completeNode = doc.querySelector(".jsterm-complete-node");
this.inputNode = doc.querySelector(".jsterm-input-node");
@ -3819,6 +3781,12 @@ JSTerm.prototype = {
this.autocompletePopup.destroy();
this.autocompletePopup = null;
let popup = this.hud.owner.chromeDocument
.getElementById("webConsole_autocompletePopup");
if (popup) {
popup.parentNode.removeChild(popup);
}
this.inputNode.removeEventListener("keypress", this._keyPress, false);
this.inputNode.removeEventListener("input", this._inputEventHandler, false);
this.inputNode.removeEventListener("keyup", this._inputEventHandler, false);
@ -4047,6 +4015,11 @@ function WebConsoleConnectionProxy(aWebConsole, aTarget)
this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
this._onFileActivity = this._onFileActivity.bind(this);
this._onTabNavigated = this._onTabNavigated.bind(this);
this._onListTabs = this._onListTabs.bind(this);
this._onAttachTab = this._onAttachTab.bind(this);
this._onAttachConsole = this._onAttachConsole.bind(this);
this._onCachedMessages = this._onCachedMessages.bind(this);
this._connectionTimeout = this._connectionTimeout.bind(this);
}
WebConsoleConnectionProxy.prototype = {
@ -4092,6 +4065,16 @@ WebConsoleConnectionProxy.prototype = {
*/
connected: false,
/**
* Timer used for the connection.
* @private
* @type object
*/
_connectTimer: null,
_connectDefer: null,
_disconnecter: null,
/**
* The WebConsoleActor ID.
*
@ -4130,11 +4113,31 @@ WebConsoleConnectionProxy.prototype = {
/**
* Initialize a debugger client and connect it to the debugger server.
*
* @param function [aCallback]
* Optional function to invoke when connection is established.
* @return object
* A Promise object that is resolved/rejected based on the success of
* the connection initialization.
*/
connect: function WCCP_connect(aCallback)
connect: function WCCP_connect()
{
if (this._connectDefer) {
return this._connectDefer.promise;
}
this._connectDefer = Promise.defer();
let timeout = Services.prefs.getIntPref(PREF_CONNECTION_TIMEOUT);
this._connectTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._connectTimer.initWithCallback(this._connectionTimeout,
timeout, Ci.nsITimer.TYPE_ONE_SHOT);
let promise = this._connectDefer.promise;
promise.then(function _onSucess() {
this._connectTimer.cancel();
this._connectTimer = null;
}.bind(this), function _onFailure() {
this._connectTimer = null;
}.bind(this));
// TODO: convert the non-remote path to use the target API as well.
let transport, client;
if (this.target.isRemote) {
@ -4143,7 +4146,6 @@ WebConsoleConnectionProxy.prototype = {
else {
this.initServer();
transport = DebuggerServer.connectPipe();
client = this.client = new DebuggerClient(transport);
}
@ -4157,39 +4159,54 @@ WebConsoleConnectionProxy.prototype = {
if (this.target.isRemote) {
if (!this.target.chrome) {
// target.form is a TabActor grip
this._attachTab(this.target.form, aCallback);
this._attachTab(this.target.form);
}
else {
// target.form is a RootActor grip
this._consoleActor = this.target.form.consoleActor;
this._attachConsole(aCallback);
this._attachConsole();
}
}
else {
client.connect(function(aType, aTraits) {
client.listTabs(this._onListTabs.bind(this, aCallback));
client.listTabs(this._onListTabs);
}.bind(this));
}
return promise;
},
/**
* Connection timeout handler.
* @private
*/
_connectionTimeout: function WCCP__connectionTimeout()
{
let error = {
error: "timeout",
message: l10n.getStr("connectionTimeout"),
};
this._connectDefer.reject(error);
},
/**
* The "listTabs" response handler.
*
* @private
* @param function [aCallback]
* Optional function to invoke once the connection is established.
* @param object aResponse
* The JSON response object received from the server.
*/
_onListTabs: function WCCP__onListTabs(aCallback, aResponse)
_onListTabs: function WCCP__onListTabs(aResponse)
{
if (aResponse.error) {
Cu.reportError("listTabs failed: " + aResponse.error + " " +
aResponse.message);
this._connectDefer.reject(aResponse);
return;
}
this._attachTab(aResponse.tabs[aResponse.selected], aCallback);
this._attachTab(aResponse.tabs[aResponse.selected]);
},
/**
@ -4198,74 +4215,65 @@ WebConsoleConnectionProxy.prototype = {
* @private
* @param object aTab
* Grip for the tab to attach to.
* @param function aCallback
* Function to invoke when the connection is established.
*/
_attachTab: function WCCP__attachTab(aTab, aCallback)
_attachTab: function WCCP__attachTab(aTab)
{
this._consoleActor = aTab.consoleActor;
this._tabActor = aTab.actor;
this.owner.onLocationChange(aTab.url, aTab.title);
this.client.attachTab(this._tabActor,
this._onAttachTab.bind(this, aCallback));
this.client.attachTab(this._tabActor, this._onAttachTab);
},
/**
* The "attachTab" response handler.
*
* @private
* @param function [aCallback]
* Optional function to invoke once the connection is established.
* @param object aResponse
* The JSON response object received from the server.
* @param object aTabClient
* The TabClient instance for the attached tab.
*/
_onAttachTab: function WCCP__onAttachTab(aCallback, aResponse, aTabClient)
_onAttachTab: function WCCP__onAttachTab(aResponse, aTabClient)
{
if (aResponse.error) {
Cu.reportError("attachTab failed: " + aResponse.error + " " +
aResponse.message);
this._connectDefer.reject(aResponse);
return;
}
this.tabClient = aTabClient;
this._attachConsole(aCallback);
this._attachConsole();
},
/**
* Attach to the Web Console actor.
*
* @private
* @param function aCallback
* Function to invoke when the connection is established.
*/
_attachConsole: function WCCP__attachConsole(aCallback)
_attachConsole: function WCCP__attachConsole()
{
let listeners = ["PageError", "ConsoleAPI", "NetworkActivity",
"FileActivity"];
this.client.attachConsole(this._consoleActor, listeners,
this._onAttachConsole.bind(this, aCallback));
this._onAttachConsole);
},
/**
* The "attachConsole" response handler.
*
* @private
* @param function [aCallback]
* Optional function to invoke once the connection is established.
* @param object aResponse
* The JSON response object received from the server.
* @param object aWebConsoleClient
* The WebConsoleClient instance for the attached console, for the
* specific tab we work with.
*/
_onAttachConsole:
function WCCP__onAttachConsole(aCallback, aResponse, aWebConsoleClient)
_onAttachConsole: function WCCP__onAttachConsole(aResponse, aWebConsoleClient)
{
if (aResponse.error) {
Cu.reportError("attachConsole failed: " + aResponse.error + " " +
aResponse.message);
this._connectDefer.reject(aResponse);
return;
}
@ -4274,27 +4282,31 @@ WebConsoleConnectionProxy.prototype = {
this._hasNativeConsoleAPI = aResponse.nativeConsoleAPI;
let msgs = ["PageError", "ConsoleAPI"];
this.webConsoleClient.getCachedMessages(msgs,
this._onCachedMessages.bind(this, aCallback));
this.webConsoleClient.getCachedMessages(msgs, this._onCachedMessages);
},
/**
* The "cachedMessages" response handler.
*
* @private
* @param function [aCallback]
* Optional function to invoke once the connection is established.
* @param object aResponse
* The JSON response object received from the server.
*/
_onCachedMessages: function WCCP__onCachedMessages(aCallback, aResponse)
_onCachedMessages: function WCCP__onCachedMessages(aResponse)
{
if (aResponse.error) {
Cu.reportError("Web Console getCachedMessages error: " + aResponse.error +
" " + aResponse.message);
this._connectDefer.reject(aResponse);
return;
}
if (!this._connectTimer) {
// This happens if the Promise is rejected (eg. a timeout), but the
// connection attempt is successful, nonetheless.
Cu.reportError("Web Console getCachedMessages error: invalid state.");
}
this.owner.displayCachedMessages(aResponse.messages);
if (!this._hasNativeConsoleAPI) {
@ -4302,7 +4314,7 @@ WebConsoleConnectionProxy.prototype = {
}
this.connected = true;
aCallback && aCallback();
this._connectDefer.resolve(this);
},
/**
@ -4432,30 +4444,33 @@ WebConsoleConnectionProxy.prototype = {
/**
* Disconnect the Web Console from the remote server.
*
* @param function [aOnDisconnect]
* Optional function to invoke when the connection is dropped.
* @return object
* A Promise object that is resolved when disconnect completes.
*/
disconnect: function WCCP_disconnect(aOnDisconnect)
disconnect: function WCCP_disconnect()
{
if (this._disconnecter) {
return this._disconnecter.promise;
}
this._disconnecter = Promise.defer();
if (!this.client) {
aOnDisconnect && aOnDisconnect();
return;
this._disconnecter.resolve(null);
return this._disconnecter.promise;
}
let onDisconnect = function() {
if (timer) {
timer.cancel();
timer = null;
this._disconnecter.resolve(null);
}
if (aOnDisconnect) {
aOnDisconnect();
aOnDisconnect = null;
}
};
}.bind(this);
let timer = null;
let remoteTarget = this.target.isRemote;
if (aOnDisconnect && !remoteTarget) {
if (!remoteTarget) {
timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(onDisconnect, 1500, Ci.nsITimer.TYPE_ONE_SHOT);
}
@ -4476,20 +4491,21 @@ WebConsoleConnectionProxy.prototype = {
this.connected = false;
this.owner = null;
try {
if (!remoteTarget) {
if (!remoteTarget) {
try {
client.close(onDisconnect);
}
catch (ex) {
Cu.reportError("Web Console disconnect exception: " + ex);
Cu.reportError(ex.stack);
onDisconnect();
}
}
catch (ex) {
Cu.reportError("Web Console disconnect exception: " + ex);
Cu.reportError(ex.stack);
else {
onDisconnect();
}
if (remoteTarget) {
onDisconnect();
}
return this._disconnecter.promise;
},
};

View File

@ -2297,6 +2297,10 @@ html|*#gcli-output-frame {
display: none;
}
#developer-toolbar-toolbox-button > .toolbarbutton-icon {
-moz-margin-end: 0;
}
.developer-toolbar-button {
-moz-appearance: none;
min-width: 78px;

View File

@ -2981,6 +2981,10 @@ html|*#gcli-output-frame {
display: none;
}
#developer-toolbar-toolbox-button > .toolbarbutton-icon {
-moz-margin-end: 0;
}
.developer-toolbar-button {
-moz-appearance: none;
min-width: 78px;

View File

@ -1,409 +0,0 @@
/*
* Copyright 2009-2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE.txt or:
* http://opensource.org/licenses/BSD-3-Clause
*/
this.EXPORTED_SYMBOLS = [ "Promise" ];
/**
* Create an unfulfilled promise
*
* @param {*=} aTrace A debugging value
*
* @constructor
*/
this.Promise = function Promise(aTrace) {
this._status = Promise.PENDING;
this._value = undefined;
this._onSuccessHandlers = [];
this._onErrorHandlers = [];
this._trace = aTrace;
// Debugging help
if (Promise.Debug._debug) {
this._id = Promise.Debug._nextId++;
Promise.Debug._outstanding[this._id] = this;
}
}
/**
* Debugging options and tools.
*/
Promise.Debug = {
/**
* Set current debugging mode.
*
* @param {boolean} value If |true|, maintain _nextId, _outstanding, _recent.
* Otherwise, cleanup debugging data.
*/
setDebug: function(value) {
Promise.Debug._debug = value;
if (!value) {
Promise.Debug._outstanding = [];
Promise.Debug._recent = [];
}
},
_debug: false,
/**
* We give promises and ID so we can track which are outstanding.
*/
_nextId: 0,
/**
* Outstanding promises. Handy for debugging (only).
*/
_outstanding: [],
/**
* Recently resolved promises. Also for debugging only.
*/
_recent: []
};
/**
* A promise can be in one of 2 states.
* The ERROR and SUCCESS states are terminal, the PENDING state is the only
* start state.
*/
Promise.ERROR = -1;
Promise.PENDING = 0;
Promise.SUCCESS = 1;
/**
* Yeay for RTTI
*/
Promise.prototype.isPromise = true;
/**
* Have we either been resolve()ed or reject()ed?
*/
Promise.prototype.isComplete = function() {
return this._status != Promise.PENDING;
};
/**
* Have we resolve()ed?
*/
Promise.prototype.isResolved = function() {
return this._status == Promise.SUCCESS;
};
/**
* Have we reject()ed?
*/
Promise.prototype.isRejected = function() {
return this._status == Promise.ERROR;
};
/**
* Take the specified action of fulfillment of a promise, and (optionally)
* a different action on promise rejection
*/
Promise.prototype.then = function(onSuccess, onError) {
if (typeof onSuccess === 'function') {
if (this._status === Promise.SUCCESS) {
onSuccess.call(null, this._value);
}
else if (this._status === Promise.PENDING) {
this._onSuccessHandlers.push(onSuccess);
}
}
if (typeof onError === 'function') {
if (this._status === Promise.ERROR) {
onError.call(null, this._value);
}
else if (this._status === Promise.PENDING) {
this._onErrorHandlers.push(onError);
}
}
return this;
};
/**
* Like then() except that rather than returning <tt>this</tt> we return
* a promise which resolves when the original promise resolves
*/
Promise.prototype.chainPromise = function(onSuccess) {
var chain = new Promise();
chain._chainedFrom = this;
this.then(function(data) {
try {
chain.resolve(onSuccess(data));
}
catch (ex) {
chain.reject(ex);
}
}, function(ex) {
chain.reject(ex);
});
return chain;
};
/**
* Supply the fulfillment of a promise
*/
Promise.prototype.resolve = function(data) {
return this._complete(this._onSuccessHandlers,
Promise.SUCCESS, data, 'resolve');
};
/**
* Renege on a promise
*/
Promise.prototype.reject = function(data) {
return this._complete(this._onErrorHandlers, Promise.ERROR, data, 'reject');
};
/**
* Internal method to be called on resolve() or reject()
* @private
*/
Promise.prototype._complete = function(list, status, data, name) {
// Complain if we've already been completed
if (this._status != Promise.PENDING) {
Promise._error("Promise complete.", "Attempted ", name, "() with ", data);
Promise._error("Previous status: ", this._status, ", value =", this._value);
throw new Error('Promise already complete');
}
if (list.length == 0 && status == Promise.ERROR) {
var frame;
var text;
//Complain if a rejection is ignored
//(this is the equivalent of an empty catch-all clause)
Promise._error("Promise rejection ignored and silently dropped", data);
if (data.stack) {// This looks like an exception. Try harder to display it
if (data.fileName && data.lineNumber) {
Promise._error("Error originating at", data.fileName,
", line", data.lineNumber );
}
try {
for (frame = data.stack; frame; frame = frame.caller) {
text += frame + "\n";
}
Promise._error("Attempting to extract exception stack", text);
} catch (x) {
Promise._error("Could not extract exception stack.");
}
} else {
Promise._error("Exception stack not available.");
}
if (Components && Components.stack) {
try {
text = "";
for (frame = Components.stack; frame; frame = frame.caller) {
text += frame + "\n";
}
Promise._error("Attempting to extract current stack", text);
} catch (x) {
Promise._error("Could not extract current stack.");
}
} else {
Promise._error("Current stack not available.");
}
}
this._status = status;
this._value = data;
// Call all the handlers, and then delete them
list.forEach(function(handler) {
handler.call(null, this._value);
}, this);
delete this._onSuccessHandlers;
delete this._onErrorHandlers;
// Remove the given {promise} from the _outstanding list, and add it to the
// _recent list, pruning more than 20 recent promises from that list
delete Promise.Debug._outstanding[this._id];
// The original code includes this very useful debugging aid, however there
// is concern that it will create a memory leak, so we leave it out here.
/*
Promise._recent.push(this);
while (Promise._recent.length > 20) {
Promise._recent.shift();
}
*/
return this;
};
/**
* Log an error on the most appropriate channel.
*
* If the console is available, this method uses |console.warn|. Otherwise,
* this method falls back to |dump|.
*
* @param {...*} items Items to log.
*/
Promise._error = null;
if (typeof console != "undefined" && console.warn) {
Promise._error = function() {
var args = Array.prototype.slice.call(arguments);
args.unshift("Promise");
console.warn.call(console, args);
};
} else {
Promise._error = function() {
var i;
var len = arguments.length;
dump("Promise: ");
for (i = 0; i < len; ++i) {
dump(arguments[i]+" ");
}
dump("\n");
};
}
/**
* Takes an array of promises and returns a promise that that is fulfilled once
* all the promises in the array are fulfilled
* @param promiseList The array of promises
* @return the promise that is fulfilled when all the array is fulfilled
*/
Promise.group = function(promiseList) {
if (!Array.isArray(promiseList)) {
promiseList = Array.prototype.slice.call(arguments);
}
// If the original array has nothing in it, return now to avoid waiting
if (promiseList.length === 0) {
return new Promise().resolve([]);
}
var groupPromise = new Promise();
var results = [];
var fulfilled = 0;
var onSuccessFactory = function(index) {
return function(data) {
results[index] = data;
fulfilled++;
// If the group has already failed, silently drop extra results
if (groupPromise._status !== Promise.ERROR) {
if (fulfilled === promiseList.length) {
groupPromise.resolve(results);
}
}
};
};
promiseList.forEach(function(promise, index) {
var onSuccess = onSuccessFactory(index);
var onError = groupPromise.reject.bind(groupPromise);
promise.then(onSuccess, onError);
});
return groupPromise;
};
/**
* Trap errors.
*
* This function serves as an asynchronous counterpart to |catch|.
*
* Example:
* myPromise.chainPromise(a) //May reject
* .chainPromise(b) //May reject
* .chainPromise(c) //May reject
* .trap(d) //Catch any rejection from a, b or c
* .chainPromise(e) //If either a, b and c or
* //d has resolved, execute
*
* Scenario 1:
* If a, b, c resolve, e is executed as if d had not been added.
*
* Scenario 2:
* If a, b or c rejects, d is executed. If d resolves, we proceed
* with e as if nothing had happened. Otherwise, we proceed with
* the rejection of d.
*
* @param {Function} aTrap Called if |this| promise is rejected,
* with one argument: the rejection.
* @return {Promise} A new promise. This promise resolves if all
* previous promises have resolved or if |aTrap| succeeds.
*/
Promise.prototype.trap = function(aTrap) {
var promise = new Promise();
var resolve = Promise.prototype.resolve.bind(promise);
var reject = function(aRejection) {
try {
//Attempt to handle issue
var result = aTrap.call(aTrap, aRejection);
promise.resolve(result);
} catch (x) {
promise.reject(x);
}
};
this.then(resolve, reject);
return promise;
};
/**
* Execute regardless of errors.
*
* This function serves as an asynchronous counterpart to |finally|.
*
* Example:
* myPromise.chainPromise(a) //May reject
* .chainPromise(b) //May reject
* .chainPromise(c) //May reject
* .always(d) //Executed regardless
* .chainPromise(e)
*
* Whether |a|, |b| or |c| resolve or reject, |d| is executed.
*
* @param {Function} aTrap Called regardless of whether |this|
* succeeds or fails.
* @return {Promise} A new promise. This promise holds the same
* resolution/rejection as |this|.
*/
Promise.prototype.always = function(aTrap) {
var promise = new Promise();
var resolve = function(result) {
try {
aTrap.call(aTrap);
promise.resolve(result);
} catch (x) {
promise.reject(x);
}
};
var reject = function(result) {
try {
aTrap.call(aTrap);
promise.reject(result);
} catch (x) {
promise.reject(result);
}
};
this.then(resolve, reject);
return promise;
};
Promise.prototype.toString = function() {
var status;
switch (this._status) {
case Promise.PENDING:
status = "pending";
break;
case Promise.SUCCESS:
status = "resolved";
break;
case Promise.ERROR:
status = "rejected";
break;
default:
status = "invalid status: "+this._status;
}
return "[Promise " + this._id + " (" + status + ")]";
};

View File

@ -1346,27 +1346,23 @@ SourceActor.prototype = {
* Handler for the "source" packet.
*/
onSource: function SA_onSource(aRequest) {
this
return this
._loadSource()
.chainPromise(function(aSource) {
.then(function(aSource) {
return this._threadActor.createValueGrip(
aSource, this.threadActor.threadLifetimePool);
}.bind(this))
.chainPromise(function (aSourceGrip) {
.then(function (aSourceGrip) {
return {
from: this.actorID,
source: aSourceGrip
};
}.bind(this))
.trap(function (aError) {
}.bind(this), function (aError) {
return {
"from": this.actorID,
"error": "loadSourceError",
"message": "Could not load the source for " + this._script.url + "."
};
}.bind(this))
.chainPromise(function (aPacket) {
this.conn.send(aPacket);
}.bind(this));
},
@ -1404,7 +1400,7 @@ SourceActor.prototype = {
* http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
*/
_loadSource: function SA__loadSource() {
let promise = new Promise();
let deferred = defer();
let url = this._script.url;
let scheme;
try {
@ -1424,16 +1420,16 @@ SourceActor.prototype = {
try {
NetUtil.asyncFetch(url, function onFetch(aStream, aStatus) {
if (!Components.isSuccessCode(aStatus)) {
promise.reject(new Error("Request failed"));
deferred.reject(new Error("Request failed"));
return;
}
let source = NetUtil.readInputStreamToString(aStream, aStream.available());
promise.resolve(this._convertToUnicode(source));
deferred.resolve(this._convertToUnicode(source));
aStream.close();
}.bind(this));
} catch (ex) {
promise.reject(new Error("Request failed"));
deferred.reject(new Error("Request failed"));
}
break;
@ -1451,7 +1447,7 @@ SourceActor.prototype = {
let streamListener = {
onStartRequest: function(aRequest, aContext, aStatusCode) {
if (!Components.isSuccessCode(aStatusCode)) {
promise.reject("Request failed");
deferred.reject("Request failed");
}
},
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
@ -1459,12 +1455,12 @@ SourceActor.prototype = {
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
if (!Components.isSuccessCode(aStatusCode)) {
promise.reject("Request failed");
deferred.reject("Request failed");
return;
}
promise.resolve(this._convertToUnicode(chunks.join(""),
channel.contentCharset));
deferred.resolve(this._convertToUnicode(chunks.join(""),
channel.contentCharset));
}.bind(this)
};
@ -1473,7 +1469,7 @@ SourceActor.prototype = {
break;
}
return promise;
return deferred.promise;
}
};

View File

@ -24,7 +24,8 @@ let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
Cu.import("resource://gre/modules/jsdebugger.jsm");
addDebuggerToGlobal(this);
Cu.import("resource://gre/modules/devtools/_Promise.jsm");
Cu.import("resource://gre/modules/commonjs/promise/core.js");
const { defer, resolve, reject } = Promise;
function dumpn(str) {
if (wantLogging) {
@ -664,16 +665,17 @@ DebuggerServerConnection.prototype = {
}
if (!ret) {
// XXX: The actor wasn't ready to reply yet, don't process new
// requests until it does.
// This should become an error once we've converted every user
// of this to promises in bug 794078.
return;
}
if (!ret.from) {
ret.from = aPacket.to;
}
this.transport.send(ret);
resolve(ret).then(function(returnPacket) {
if (!returnPacket.from) {
returnPacket.from = aPacket.to;
}
this.transport.send(returnPacket);
}.bind(this));
},
/**

View File

@ -183,7 +183,7 @@ WebConsoleActor.prototype =
this.consoleProgressListener.destroy();
this.consoleProgressListener = null;
}
this.conn.removeActorPool(this.actorPool);
this.conn.removeActorPool(this._actorPool);
this._actorPool = null;
this.sandbox = null;
this._sandboxWindowId = 0;