Bug 768096 - Web Console remote debugging protocol support - Part 1: page errors; r=past,robcee

--HG--
rename : browser/devtools/webconsole/WebConsoleUtils.jsm => toolkit/devtools/webconsole/WebConsoleUtils.jsm
This commit is contained in:
Mihai Sucan 2012-09-26 18:07:57 +01:00
parent d165fdcaaa
commit 348454ccd7
27 changed files with 911 additions and 115 deletions

View File

@ -11,12 +11,13 @@
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let tempScope = {};
Cu.import("resource://gre/modules/XPCOMUtils.jsm", tempScope);
Cu.import("resource://gre/modules/Services.jsm", tempScope);
Cu.import("resource://gre/modules/ConsoleAPIStorage.jsm", tempScope);
Cu.import("resource:///modules/WebConsoleUtils.jsm", tempScope);
Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tempScope);
Cu.import("resource:///modules/NetworkHelper.jsm", tempScope);
Cu.import("resource://gre/modules/NetUtil.jsm", tempScope);
@ -24,7 +25,7 @@ let XPCOMUtils = tempScope.XPCOMUtils;
let Services = tempScope.Services;
let gConsoleStorage = tempScope.ConsoleAPIStorage;
let WebConsoleUtils = tempScope.WebConsoleUtils;
let l10n = WebConsoleUtils.l10n;
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
let JSPropertyProvider = tempScope.JSPropertyProvider;
let NetworkHelper = tempScope.NetworkHelper;
let NetUtil = tempScope.NetUtil;
@ -185,6 +186,8 @@ let Manager = {
if (aMessage.cachedMessages) {
this._sendCachedMessages(aMessage.cachedMessages);
}
this.sendMessage("WebConsole:Initialized", {});
},
/**

View File

@ -18,12 +18,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource:///modules/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "l10n", function() {
return WebConsoleUtils.l10n;
});
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
var EXPORTED_SYMBOLS = ["HUDService"];
@ -172,7 +170,10 @@ HUD_SERVICE.prototype =
let hud = this.getHudReferenceById(hudId);
let document = hud.chromeDocument;
hud.destroy();
hud.destroy(function() {
let id = WebConsoleUtils.supportsString(hudId);
Services.obs.notifyObservers(id, "web-console-destroyed", null);
});
delete this.hudReferences[hudId];
@ -199,9 +200,6 @@ HUD_SERVICE.prototype =
contentWindow.focus();
HeadsUpDisplayUICommands.refreshCommand();
let id = WebConsoleUtils.supportsString(hudId);
Services.obs.notifyObservers(id, "web-console-destroyed", null);
},
/**
@ -539,7 +537,7 @@ WebConsole.prototype = {
* @type array
*/
_messageListeners: ["JSTerm:EvalObject", "WebConsole:ConsoleAPI",
"WebConsole:CachedMessages", "WebConsole:PageError", "JSTerm:EvalResult",
"WebConsole:CachedMessages", "WebConsole:Initialized", "JSTerm:EvalResult",
"JSTerm:AutocompleteProperties", "JSTerm:ClearOutput",
"JSTerm:InspectObject", "WebConsole:NetworkActivity",
"WebConsole:FileActivity", "WebConsole:LocationChange",
@ -926,8 +924,7 @@ WebConsole.prototype = {
}, this);
let message = {
features: ["ConsoleAPI", "JSTerm", "PageError", "NetworkMonitor",
"LocationChange"],
features: ["ConsoleAPI", "JSTerm", "NetworkMonitor", "LocationChange"],
cachedMessages: ["ConsoleAPI", "PageError"],
NetworkMonitor: { monitorFileActivity: true },
JSTerm: { notifyNonNativeConsoleAPI: true },
@ -940,19 +937,6 @@ WebConsole.prototype = {
this.sendMessageToContent("WebConsole:Init", message);
},
/**
* 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);
},
/**
* Handler for messages that have an associated callback function. The
* this.sendMessageToContent() allows one to provide a function to be invoked
@ -1051,8 +1035,12 @@ 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.
*/
destroy: function WC_destroy()
destroy: function WC_destroy(aOnDestroy)
{
this.sendMessageToContent("WebConsole:Destroy", {});
@ -1072,24 +1060,31 @@ 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;
}
if (this.iframe.parentNode) {
this.iframe.parentNode.removeChild(this.iframe);
}
if (this.splitter.parentNode) {
this.splitter.parentNode.removeChild(this.splitter);
}
aOnDestroy && aOnDestroy();
}.bind(this);
if (this.ui) {
this.ui.destroy();
this.ui.destroy(onDestroy);
}
// 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;
}
if (this.iframe.parentNode) {
this.iframe.parentNode.removeChild(this.iframe);
}
if (this.splitter.parentNode) {
this.splitter.parentNode.removeChild(this.splitter);
else {
onDestroy();
}
},
};

View File

@ -16,7 +16,6 @@ EXTRA_JS_MODULES = \
NetworkHelper.jsm \
NetworkPanel.jsm \
AutocompletePopup.jsm \
WebConsoleUtils.jsm \
$(NULL)
TEST_DIRS = test

View File

@ -22,11 +22,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource:///modules/WebConsoleUtils.jsm");
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "l10n", function() {
return WebConsoleUtils.l10n;
});
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
var EXPORTED_SYMBOLS = ["NetworkPanel"];

View File

@ -13,7 +13,7 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource:///modules/WebConsoleUtils.jsm");
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView"];

View File

@ -21,7 +21,7 @@ function test() {
browser.removeEventListener("DOMContentLoaded", testTimestamp, false);
const TEST_TIMESTAMP = 12345678;
let date = new Date(TEST_TIMESTAMP);
let localizedString = WebConsoleUtils.l10n.timestampString(TEST_TIMESTAMP);
let localizedString = WCU_l10n.timestampString(TEST_TIMESTAMP);
isnot(localizedString.indexOf(date.getHours()), -1, "the localized " +
"timestamp contains the hours");
isnot(localizedString.indexOf(date.getMinutes()), -1, "the localized " +

View File

@ -81,7 +81,7 @@ function testContextMenuCopy() {
}
function getExpectedClipboardText(aItem) {
return "[" + WebConsoleUtils.l10n.timestampString(aItem.timestamp) + "] " +
return "[" + WCU_l10n.timestampString(aItem.timestamp) + "] " +
aItem.clipboardText;
}

View File

@ -14,25 +14,35 @@ let tab1, tab2, win1, win2;
let noErrors = true;
function tab1Loaded(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
browser.removeEventListener(aEvent.type, tab1Loaded, true);
win2 = OpenBrowserWindow();
win2.addEventListener("load", win2Loaded, true);
}
function win2Loaded(aEvent) {
win2.removeEventListener(aEvent.type, arguments.callee, true);
win2.removeEventListener(aEvent.type, win2Loaded, true);
tab2 = win2.gBrowser.addTab();
tab2 = win2.gBrowser.addTab(TEST_URI);
win2.gBrowser.selectedTab = tab2;
tab2.linkedBrowser.addEventListener("load", tab2Loaded, true);
tab2.linkedBrowser.contentWindow.location = TEST_URI;
}
function tab2Loaded(aEvent) {
tab2.linkedBrowser.removeEventListener(aEvent.type, arguments.callee, true);
tab2.linkedBrowser.removeEventListener(aEvent.type, tab2Loaded, true);
waitForFocus(function() {
let consolesOpened = 0;
function onWebConsoleOpen() {
consolesOpened++;
if (consolesOpened == 2) {
Services.obs.removeObserver(onWebConsoleOpen, "web-console-created");
executeSoon(closeConsoles);
}
}
Services.obs.addObserver(onWebConsoleOpen, "web-console-created", false);
function openConsoles() {
try {
HUDService.activateHUDForContext(tab1);
}
@ -48,6 +58,20 @@ function tab2Loaded(aEvent) {
ok(false, "HUDService.activateHUDForContext(tab2) exception: " + ex);
noErrors = false;
}
}
let consolesClosed = 0;
function onWebConsoleClose()
{
consolesClosed++;
if (consolesClosed == 2) {
Services.obs.removeObserver(onWebConsoleClose, "web-console-destroyed");
executeSoon(testEnd);
}
}
function closeConsoles() {
Services.obs.addObserver(onWebConsoleClose, "web-console-destroyed", false);
try {
HUDService.deactivateHUDForContext(tab1);
@ -64,20 +88,26 @@ function tab2Loaded(aEvent) {
ok(false, "HUDService.deactivateHUDForContext(tab2) exception: " + ex);
noErrors = false;
}
}
if (noErrors) {
ok(true, "there were no errors");
}
function testEnd() {
ok(noErrors, "there were no errors");
win2.gBrowser.removeTab(tab2);
Array.forEach(win1.gBrowser.tabs, function(aTab) {
win1.gBrowser.removeTab(aTab);
});
Array.forEach(win2.gBrowser.tabs, function(aTab) {
win2.gBrowser.removeTab(aTab);
});
executeSoon(function() {
win2.close();
tab1 = tab2 = win1 = win2 = null;
finishTest();
});
}
}, tab2.linkedBrowser.contentWindow);
waitForFocus(openConsoles, tab2.linkedBrowser.contentWindow);
}
function test() {

View File

@ -16,14 +16,14 @@ const MINIMUM_CONSOLE_HEIGHT = 150;
const MINIMUM_PAGE_HEIGHT = 50;
const HEIGHT_PREF = "devtools.hud.height";
let hud, newHeight, height, innerHeight;
let hud, newHeight, height, innerHeight, testDriver;
function performTests(aWebConsole)
function testGen()
{
hud = aWebConsole.iframe;
height = parseInt(hud.style.height);
toggleConsole();
yield;
is(newHeight, height, "same height after reopening the console");
is(Services.prefs.getIntPref(HEIGHT_PREF), HUDService.lastConsoleHeight,
@ -31,6 +31,7 @@ function performTests(aWebConsole)
setHeight(Math.ceil(innerHeight * 0.5));
toggleConsole();
yield;
is(newHeight, height, "same height after reopening the console");
is(Services.prefs.getIntPref(HEIGHT_PREF), HUDService.lastConsoleHeight,
@ -38,6 +39,7 @@ function performTests(aWebConsole)
setHeight(MINIMUM_CONSOLE_HEIGHT - 1);
toggleConsole();
yield;
is(newHeight, MINIMUM_CONSOLE_HEIGHT, "minimum console height is respected");
is(Services.prefs.getIntPref(HEIGHT_PREF), HUDService.lastConsoleHeight,
@ -45,6 +47,7 @@ function performTests(aWebConsole)
setHeight(innerHeight - MINIMUM_PAGE_HEIGHT + 1);
toggleConsole();
yield;
is(newHeight, innerHeight - MINIMUM_PAGE_HEIGHT,
"minimum page height is respected");
@ -54,6 +57,7 @@ function performTests(aWebConsole)
setHeight(Math.ceil(innerHeight * 0.6));
Services.prefs.setIntPref(HEIGHT_PREF, -1);
toggleConsole();
yield;
is(newHeight, height, "same height after reopening the console");
is(Services.prefs.getIntPref(HEIGHT_PREF), -1, "pref is not updated");
@ -62,17 +66,23 @@ function performTests(aWebConsole)
HUDService.lastConsoleHeight = 0;
Services.prefs.setIntPref(HEIGHT_PREF, 0);
hud = testDriver = null;
executeSoon(finishTest);
yield;
}
function toggleConsole()
{
closeConsole();
openConsole();
closeConsole(null, function() {
openConsole(null, function() {
let hudId = HUDService.getHudIdByWindow(content);
hud = HUDService.hudReferences[hudId].iframe;
newHeight = parseInt(hud.style.height);
let hudId = HUDService.getHudIdByWindow(content);
hud = HUDService.hudReferences[hudId].iframe;
newHeight = parseInt(hud.style.height);
testDriver.next();
});
});
}
function setHeight(aHeight)
@ -87,7 +97,11 @@ function test()
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
innerHeight = content.innerHeight;
openConsole(null, performTests);
openConsole(null, function(aHud) {
hud = aHud.iframe;
testDriver = testGen();
testDriver.next();
});
}, true);
}

View File

@ -80,6 +80,6 @@ function performTest(HUD) {
}
function getExpectedClipboardText(aItem) {
return "[" + WebConsoleUtils.l10n.timestampString(aItem.timestamp) + "] " +
return "[" + WCU_l10n.timestampString(aItem.timestamp) + "] " +
aItem.clipboardText;
}

View File

@ -58,7 +58,7 @@ function getExpectedClipboardText(aItemCount) {
for (let i = 0; i < aItemCount; i++) {
let item = outputNode.getItemAtIndex(i);
expectedClipboardText.push("[" +
WebConsoleUtils.l10n.timestampString(item.timestamp) + "] " +
WCU_l10n.timestampString(item.timestamp) + "] " +
item.clipboardText);
}
return expectedClipboardText.join("\n");

View File

@ -15,7 +15,7 @@ function test() {
function consoleOpened(HUD) {
let tmp = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", tmp);
Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tmp);
let WCU = tmp.WebConsoleUtils;
let JSPropertyProvider = tmp.JSPropertyProvider;
tmp = null;

View File

@ -23,7 +23,7 @@ function consoleOpened(aHud) {
let completeNode = jsterm.completeNode;
let tmp = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", tmp);
Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tmp);
let WCU = tmp.WebConsoleUtils;
tmp = null;

View File

@ -6,7 +6,7 @@
// Tests that code completion works properly.
function test() {
addTab("about:addons");
addTab("about:credits");
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, testChrome);
@ -15,7 +15,7 @@ function test() {
function testChrome(hud) {
ok(hud, "we have a console");
ok(hud.iframe, "we have the console iframe");
let jsterm = hud.jsterm;

View File

@ -52,7 +52,7 @@ function testClipboard() {
for (let i = 0; i < outputNode.itemCount; i++) {
let item = outputNode.getItemAtIndex(i);
clipboardTexts.push("[" +
WebConsoleUtils.l10n.timestampString(item.timestamp) +
WCU_l10n.timestampString(item.timestamp) +
"] " + item.clipboardText);
}

View File

@ -64,11 +64,6 @@ function testGen() {
let hud = HUDService.getHudByWindow(content);
let filterBox = hud.ui.filterBox;
let tempScope = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", tempScope);
let l10n = tempScope.WebConsoleUtils.l10n;
tempScope = null;
let httpActivity = {
meta: {
stages: [],
@ -442,7 +437,7 @@ function testGen() {
});
let responseString =
l10n.getFormatStr("NetworkPanel.responseBodyUnableToDisplay.content",
WCU_l10n.getFormatStr("NetworkPanel.responseBodyUnableToDisplay.content",
["application/x-shockwave-flash"]);
checkNodeContent(networkPanel, "responseBodyUnknownTypeContent", responseString);
networkPanel.panel.hidePopup();

View File

@ -17,7 +17,7 @@ function testPropertyProvider() {
browser.removeEventListener("load", testPropertyProvider, true);
let tmp = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", tmp);
Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tmp);
let JSPropertyProvider = tmp.JSPropertyProvider;
tmp = null;

View File

@ -6,8 +6,10 @@
let tempScope = {};
Cu.import("resource:///modules/HUDService.jsm", tempScope);
let HUDService = tempScope.HUDService;
Cu.import("resource:///modules/WebConsoleUtils.jsm", tempScope);
Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tempScope);
let WebConsoleUtils = tempScope.WebConsoleUtils;
const WEBCONSOLE_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let WCU_l10n = new WebConsoleUtils.l10n(WEBCONSOLE_STRINGS_URI);
function log(aMsg)
{
@ -252,7 +254,7 @@ function tearDown()
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
tab = browser = hudId = hud = filterBox = outputNode = cs = null;
WCU_l10n = tab = browser = hudId = hud = filterBox = outputNode = cs = null;
}
registerCleanupFunction(tearDown);

View File

@ -15,6 +15,12 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
"resource://gre/modules/devtools/dbg-server.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
"resource://gre/modules/devtools/dbg-client.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
@ -32,11 +38,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "AutocompletePopup",
"resource:///modules/AutocompletePopup.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource:///modules/WebConsoleUtils.jsm");
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "l10n", function() {
return WebConsoleUtils.l10n;
});
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
// The XUL namespace.
@ -187,6 +192,8 @@ function WebConsoleFrame(aWebConsoleOwner, aPosition)
this.jsterm = new JSTerm(this);
this.jsterm.inputNode.focus();
this._initConnection();
}
WebConsoleFrame.prototype = {
@ -197,6 +204,23 @@ WebConsoleFrame.prototype = {
*/
owner: null,
/**
* Proxy between the Web Console and the remote Web Console instance. This
* object holds methods used for connecting, listening and disconnecting from
* the remote server, using the remote debugging protocol.
*
* @see WebConsoleConnectionProxy
* @type object
*/
proxy: null,
/**
* Tells if the Web Console initialization via message manager completed.
* @private
* @type boolean
*/
_messageManagerInitComplete: false,
/**
* Getter for the xul:popupset that holds any popups we open.
* @type nsIDOMElement
@ -311,6 +335,21 @@ WebConsoleFrame.prototype = {
this.owner.sendMessageToContent("WebConsole:SetPreferences", message);
},
/**
* Connect to the server using the remote debugging protocol.
* @private
*/
_initConnection: function WCF__initConnection()
{
this.proxy = new WebConsoleConnectionProxy(this);
this.proxy.initServer();
this.proxy.connect(function() {
if (this._messageManagerInitComplete) {
this._onInitComplete();
}
}.bind(this));
},
/**
* Find the Web Console UI elements and setup event listeners as needed.
* @private
@ -486,6 +525,19 @@ 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);
},
/**
* Handle the "command" event for the buttons that allow the user to
* reposition the Web Console UI.
@ -620,16 +672,11 @@ WebConsoleFrame.prototype = {
this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage,
[aMessage.json]);
break;
case "WebConsole:PageError": {
let pageError = aMessage.json.pageError;
let category = Utils.categoryForScriptError(pageError);
this.outputMessage(category, this.reportPageError,
[category, pageError]);
case "WebConsole:Initialized":
this._onMessageManagerInitComplete();
break;
}
case "WebConsole:CachedMessages":
this._displayCachedConsoleMessages(aMessage.json.messages);
this.owner._onInitComplete();
break;
case "WebConsole:NetworkActivity":
this.handleNetworkActivity(aMessage.json);
@ -647,6 +694,20 @@ WebConsoleFrame.prototype = {
}
},
/**
* Callback method used to track the Web Console initialization via message
* manager.
*
* @private
*/
_onMessageManagerInitComplete: function WCF__onMessageManagerInitComplete()
{
this._messageManagerInitComplete = true;
if (this.proxy.connected) {
this._onInitComplete();
}
},
/**
* The event handler that is called whenever a user switches a filter on or
* off.
@ -1226,8 +1287,7 @@ WebConsoleFrame.prototype = {
// Warnings and legacy strict errors become warnings; other types become
// errors.
let severity = SEVERITY_ERROR;
if ((aScriptError.flags & aScriptError.warningFlag) ||
(aScriptError.flags & aScriptError.strictFlag)) {
if (aScriptError.warning || aScriptError.strict) {
severity = SEVERITY_WARNING;
}
@ -1239,6 +1299,19 @@ WebConsoleFrame.prototype = {
return node;
},
/**
* Handle PageError objects received from the server. This method outputs the
* given error.
*
* @param nsIScriptError aPageError
* The error received from the server.
*/
handlePageError: function WCF_handlePageError(aPageError)
{
let category = Utils.categoryForScriptError(aPageError);
this.outputMessage(category, this.reportPageError, [category, aPageError]);
},
/**
* Log network activity.
*
@ -2337,9 +2410,17 @@ WebConsoleFrame.prototype = {
/**
* Destroy the HUD 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.
*/
destroy: function WCF_destroy()
destroy: function WCF_destroy(aOnDestroy)
{
if (this.proxy) {
this.proxy.disconnect(aOnDestroy);
}
if (this.jsterm) {
this.jsterm.destroy();
}
@ -3535,6 +3616,158 @@ CommandController.prototype = {
}
};
///////////////////////////////////////////////////////////////////////////////
// Web Console connection proxy
///////////////////////////////////////////////////////////////////////////////
/**
* The WebConsoleConnectionProxy handles the connection between the Web Console
* and the application we connect to through the remote debug protocol.
*
* @constructor
* @param object aWebConsole
* The Web Console instance that owns this connection proxy.
*/
function WebConsoleConnectionProxy(aWebConsole)
{
this.owner = aWebConsole;
this._onPageError = this._onPageError.bind(this);
}
WebConsoleConnectionProxy.prototype = {
/**
* The owning Web Console instance.
*
* @see WebConsoleFrame
* @type object
*/
owner: null,
/**
* The DebuggerClient object.
*
* @see DebuggerClient
* @type object
*/
client: null,
/**
* Tells if the connection is established.
* @type boolean
*/
connected: false,
/**
* The WebConsoleActor ID.
*
* @private
* @type string
*/
_consoleActor: null,
/**
* Initialize the debugger server.
*/
initServer: function WCCP_initServer()
{
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
},
/**
* Initialize a debugger client and connect it to the debugger server.
*
* @param function [aCallback]
* Optional function to invoke when connection is established.
*/
connect: function WCCP_connect(aCallback)
{
let transport = DebuggerServer.connectPipe();
let client = this.client = new DebuggerClient(transport);
client.addListener("pageError", this._onPageError);
let listeners = ["PageError"];
client.connect(function(aType, aTraits) {
client.listTabs(function(aResponse) {
let tab = aResponse.tabs[aResponse.selected];
this._consoleActor = tab.consoleActor;
client.attachConsole(tab.consoleActor, listeners,
this._onAttachConsole.bind(this, aCallback));
}.bind(this));
}.bind(this));
},
/**
* 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)
{
if (aResponse.error) {
Cu.reportError("attachConsole failed: " + aResponse.error + " " +
aResponse.message);
return;
}
this.webConsoleClient = aWebConsoleClient;
this.connected = true;
aCallback && aCallback();
},
/**
* The "pageError" message type handler. We redirect any page errors to the UI
* for displaying.
*
* @private
* @param string aType
* Message type.
* @param object aPacket
* The message received from the server.
*/
_onPageError: function WCCP__onPageError(aType, aPacket)
{
if (this.owner && aPacket.from == this._consoleActor) {
this.owner.handlePageError(aPacket.pageError);
}
},
/**
* Disconnect the Web Console from the remote server.
*
* @param function [aOnDisconnect]
* Optional function to invoke when the connection is dropped.
*/
disconnect: function WCCP_disconnect(aOnDisconnect)
{
if (!this.client) {
aOnDisconnect && aOnDisconnect();
return;
}
this.client.removeListener("pageError", this._onPageError);
this.client.close(aOnDisconnect);
this.client = null;
this.webConsoleClient = null;
this.connected = false;
},
};
function gSequenceId()
{
return gSequenceId.n++;

View File

@ -12,6 +12,7 @@ include $(topsrcdir)/config/config.mk
PARALLEL_DIRS += \
debugger \
sourcemap \
webconsole \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -22,6 +22,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "socketTransportService",
"@mozilla.org/network/socket-transport-service;1",
"nsISocketTransportService");
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleClient",
"resource://gre/modules/devtools/WebConsoleClient.jsm");
let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
function dumpn(str)
@ -167,6 +170,7 @@ const UnsolicitedNotifications = {
"newScript": "newScript",
"tabDetached": "tabDetached",
"tabNavigated": "tabNavigated",
"pageError": "pageError",
"profilerStateChanged": "profilerStateChanged"
};
@ -194,6 +198,7 @@ function DebuggerClient(aTransport)
this._transport.hooks = this;
this._threadClients = {};
this._tabClients = {};
this._consoleClients = {};
this._pendingRequests = [];
this._activeRequests = {};
@ -249,10 +254,32 @@ DebuggerClient.prototype = {
}
}.bind(this);
if (this.activeThread) {
this.activeThread.detach(detachTab);
} else {
detachTab();
let detachThread = function _detachThread() {
if (this.activeThread) {
this.activeThread.detach(detachTab);
} else {
detachTab();
}
}.bind(this);
let consolesClosed = 0;
let consolesToClose = 0;
let onConsoleClose = function _onConsoleClose() {
consolesClosed++;
if (consolesClosed >= consolesToClose) {
this._consoleClients = {};
detachThread();
}
}.bind(this);
for each (let client in this._consoleClients) {
consolesToClose++;
client.close(onConsoleClose);
}
if (!consolesToClose) {
detachThread();
}
},
@ -282,8 +309,9 @@ DebuggerClient.prototype = {
let self = this;
let packet = { to: aTabActor, type: "attach" };
this.request(packet, function(aResponse) {
let tabClient;
if (!aResponse.error) {
var tabClient = new TabClient(self, aTabActor);
tabClient = new TabClient(self, aTabActor);
self._tabClients[aTabActor] = tabClient;
self.activeTab = tabClient;
}
@ -291,6 +319,36 @@ DebuggerClient.prototype = {
});
},
/**
* Attach to a Web Console actor.
*
* @param string aConsoleActor
* The ID for the console actor to attach to.
* @param array aListeners
* The console listeners you want to start.
* @param function aOnResponse
* Called with the response packet and a WebConsoleClient
* instance (which will be undefined on error).
*/
attachConsole:
function DC_attachConsole(aConsoleActor, aListeners, aOnResponse) {
let self = this;
let packet = {
to: aConsoleActor,
type: "startListeners",
listeners: aListeners,
};
this.request(packet, function(aResponse) {
let consoleClient;
if (!aResponse.error) {
consoleClient = new WebConsoleClient(self, aConsoleActor);
self._consoleClients[aConsoleActor] = consoleClient;
}
aOnResponse(aResponse, consoleClient);
});
},
/**
* Attach to a thread actor.
*

View File

@ -185,6 +185,8 @@ var DebuggerServer = {
*/
addBrowserActors: function DH_addBrowserActors() {
this.addActors("chrome://global/content/devtools/dbg-browser-actors.js");
this.addActors("chrome://global/content/devtools/dbg-webconsole-actors.js");
this.addTabActor(this.WebConsoleActor, "consoleActor");
if ("nsIProfiler" in Ci)
this.addActors("chrome://global/content/devtools/dbg-profiler-actors.js");
},

View File

@ -7,4 +7,5 @@ toolkit.jar:
content/global/devtools/dbg-server.js (debugger/server/dbg-server.js)
content/global/devtools/dbg-script-actors.js (debugger/server/dbg-script-actors.js)
content/global/devtools/dbg-browser-actors.js (debugger/server/dbg-browser-actors.js)
content/global/devtools/dbg-webconsole-actors.js (webconsole/dbg-webconsole-actors.js)
content/global/devtools/dbg-profiler-actors.js (debugger/server/dbg-profiler-actors.js)

View File

@ -0,0 +1,17 @@
# 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/.
DEPTH = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
#TEST_DIRS += tests
include $(topsrcdir)/config/rules.mk
libs::
$(INSTALL) $(IFLAGS1) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools

View File

@ -0,0 +1,103 @@
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* vim: set 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";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
var EXPORTED_SYMBOLS = ["WebConsoleClient"];
/**
* A WebConsoleClient is used as a front end for the WebConsoleActor that is
* created on the server, hiding implementation details.
*
* @param object aDebuggerClient
* The DebuggerClient instance we live for.
* @param string aActor
* The WebConsoleActor ID.
*/
function WebConsoleClient(aDebuggerClient, aActor)
{
this._actor = aActor;
this._client = aDebuggerClient;
}
WebConsoleClient.prototype = {
/**
* Retrieve the cached messages from the server.
*
* @see this.CACHED_MESSAGES
* @param array aTypes
* The array of message types you want from the server. See
* this.CACHED_MESSAGES for known types.
* @param function aOnResponse
* The function invoked when the response is received.
*/
getCachedMessages: function WCC_getCachedMessages(aTypes, aOnResponse)
{
let packet = {
to: this._actor,
type: "getCachedMessages",
messageTypes: aTypes,
};
this._client.request(packet, aOnResponse);
},
/**
* Start the given Web Console listeners.
*
* @see this.LISTENERS
* @param array aListeners
* Array of listeners you want to start. See this.LISTENERS for
* known listeners.
* @param function aOnResponse
* Function to invoke when the server response is received.
*/
startListeners: function WCC_startListeners(aListeners, aOnResponse)
{
let packet = {
to: this._actor,
type: "startListeners",
listeners: aListeners,
};
this._client.request(packet, aOnResponse);
},
/**
* Stop the given Web Console listeners.
*
* @see this.LISTENERS
* @param array aListeners
* Array of listeners you want to stop. See this.LISTENERS for
* known listeners.
* @param function aOnResponse
* Function to invoke when the server response is received.
*/
stopListeners: function WCC_stopListeners(aListeners, aOnResponse)
{
let packet = {
to: this._actor,
type: "stopListeners",
listeners: aListeners,
};
this._client.request(packet, aOnResponse);
},
/**
* Close the WebConsoleClient. This stops all the listeners on the server and
* detaches from the console actor.
*
* @param function aOnResponse
* Function to invoke when the server response is received.
*/
close: function WCC_close(aOnResponse)
{
this.stopListeners(null, aOnResponse);
this._client = null;
},
};

View File

@ -15,9 +15,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
var EXPORTED_SYMBOLS = ["WebConsoleUtils", "JSPropertyProvider"];
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
var EXPORTED_SYMBOLS = ["WebConsoleUtils", "JSPropertyProvider",
"PageErrorListener"];
const TYPES = { OBJECT: 0,
FUNCTION: 1,
@ -645,7 +644,22 @@ var WebConsoleUtils = {
// Localization
//////////////////////////////////////////////////////////////////////////
WebConsoleUtils.l10n = {
WebConsoleUtils.l10n = function WCU_l10n(aBundleURI)
{
this._bundleUri = aBundleURI;
};
WebConsoleUtils.l10n.prototype = {
_stringBundle: null,
get stringBundle()
{
if (!this._stringBundle) {
this._stringBundle = Services.strings.createBundle(this._bundleUri);
}
return this._stringBundle;
},
/**
* Generates a formatted timestamp string for displaying in console messages.
*
@ -710,10 +724,6 @@ WebConsoleUtils.l10n = {
},
};
XPCOMUtils.defineLazyGetter(WebConsoleUtils.l10n, "stringBundle", function() {
return Services.strings.createBundle(STRINGS_URI);
});
//////////////////////////////////////////////////////////////////////////
// JS Completer
@ -1012,3 +1022,144 @@ function getMatchedProps(aObj, aOptions = {matchProp: ""})
return JSPropertyProvider;
})(WebConsoleUtils);
///////////////////////////////////////////////////////////////////////////////
// The page errors listener
///////////////////////////////////////////////////////////////////////////////
/**
* The nsIConsoleService listener. This is used to send all the page errors
* (JavaScript, CSS and more) to the remote Web Console instance.
*
* @constructor
* @param nsIDOMWindow aWindow
* The window object for which we are created.
* @param object aListener
* The listener object must have a method: onPageError. This method is
* invoked with one argument, the nsIScriptError, whenever a relevant
* page error is received.
*/
function PageErrorListener(aWindow, aListener)
{
this.window = aWindow;
this.listener = aListener;
}
PageErrorListener.prototype =
{
QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]),
/**
* The content window for which we listen to page errors.
* @type nsIDOMWindow
*/
window: null,
/**
* The listener object which is notified of page errors. It must have
* a onPageError method which is invoked with one argument: the nsIScriptError.
* @type object
*/
listener: null,
/**
* Initialize the nsIConsoleService listener.
*/
init: function PEL_init()
{
Services.console.registerListener(this);
},
/**
* The nsIConsoleService observer. This method takes all the script error
* messages belonging to the current window and sends them to the remote Web
* Console instance.
*
* @param nsIScriptError aScriptError
* The script error object coming from the nsIConsoleService.
*/
observe: function PEL_observe(aScriptError)
{
if (!this.window || !this.listener ||
!(aScriptError instanceof Ci.nsIScriptError) ||
!aScriptError.outerWindowID) {
return;
}
if (!this.isCategoryAllowed(aScriptError.category)) {
return;
}
let errorWindow =
WebConsoleUtils.getWindowByOuterId(aScriptError.outerWindowID, this.window);
if (!errorWindow || errorWindow.top != this.window) {
return;
}
this.listener.onPageError(aScriptError);
},
/**
* Check if the given script error category is allowed to be tracked or not.
* We ignore chrome-originating errors as we only care about content.
*
* @param string aCategory
* The nsIScriptError category you want to check.
* @return boolean
* True if the category is allowed to be logged, false otherwise.
*/
isCategoryAllowed: function PEL_isCategoryAllowed(aCategory)
{
switch (aCategory) {
case "XPConnect JavaScript":
case "component javascript":
case "chrome javascript":
case "chrome registration":
case "XBL":
case "XBL Prototype Handler":
case "XBL Content Sink":
case "xbl javascript":
return false;
}
return true;
},
/**
* Get the cached page errors for the current inner window.
*
* @return array
* The array of cached messages. Each element is an nsIScriptError
* with an added _type property so the remote Web Console instance can
* tell the difference between various types of cached messages.
*/
getCachedMessages: function PEL_getCachedMessages()
{
let innerWindowId = WebConsoleUtils.getInnerWindowId(this.window);
let result = [];
let errors = {};
Services.console.getMessageArray(errors, {});
(errors.value || []).forEach(function(aError) {
if (!(aError instanceof Ci.nsIScriptError) ||
aError.innerWindowID != innerWindowId ||
!this.isCategoryAllowed(aError.category)) {
return;
}
let remoteMessage = WebConsoleUtils.cloneObject(aError);
result.push(remoteMessage);
}, this);
return result;
},
/**
* Remove the nsIConsoleService listener.
*/
destroy: function PEL_destroy()
{
Services.console.unregisterListener(this);
this.listener = this.window = null;
},
};

View File

@ -0,0 +1,193 @@
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* vim: set 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";
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
/**
* The WebConsoleActor implements capabilities needed for the Web Console
* feature.
*
* @constructor
* @param object aConnection
* The connection to the client, DebuggerServerConnection.
* @param object aTabActor
* The parent tab actor.
*/
function WebConsoleActor(aConnection, aTabActor)
{
this.conn = aConnection;
this._browser = aTabActor.browser;
}
WebConsoleActor.prototype =
{
/**
* The xul:browser we work with.
* @private
* @type nsIDOMElement
*/
_browser: null,
/**
* The debugger server connection instance.
* @type object
*/
conn: null,
/**
* The content window we work with.
* @type nsIDOMWindow
*/
get window() this._browser.contentWindow,
/**
* The PageErrorListener instance.
* @type object
*/
pageErrorListener: null,
actorPrefix: "console",
grip: function WCA_grip()
{
return { actor: this.actorID };
},
/**
* Destroy the current WebConsoleActor instance.
*/
disconnect: function WCA_disconnect()
{
if (this.pageErrorListener) {
this.pageErrorListener.destroy();
this.pageErrorListener = null;
}
this.conn = this._browser = null;
},
/**
* Handler for the "startListeners" request.
*
* @param object aRequest
* The JSON request object received from the Web Console client.
* @return object
* The response object which holds the startedListeners array.
*/
onStartListeners: function WCA_onStartListeners(aRequest)
{
let startedListeners = [];
while (aRequest.listeners.length > 0) {
let listener = aRequest.listeners.shift();
switch (listener) {
case "PageError":
if (!this.pageErrorListener) {
this.pageErrorListener =
new PageErrorListener(this.window, this);
this.pageErrorListener.init();
}
startedListeners.push(listener);
break;
}
}
return { startedListeners: startedListeners };
},
/**
* Handler for the "stopListeners" request.
*
* @param object aRequest
* The JSON request object received from the Web Console client.
* @return object
* The response packet to send to the client: holds the
* stoppedListeners array.
*/
onStopListeners: function WCA_onStopListeners(aRequest)
{
let stoppedListeners = [];
// If no specific listeners are requested to be detached, we stop all
// listeners.
let toDetach = aRequest.listeners || ["PageError"];
while (toDetach.length > 0) {
let listener = toDetach.shift();
switch (listener) {
case "PageError":
if (this.pageErrorListener) {
this.pageErrorListener.destroy();
this.pageErrorListener = null;
}
stoppedListeners.push(listener);
break;
}
}
return { stoppedListeners: stoppedListeners };
},
/**
* Handler for page errors received from the PageErrorListener. This method
* sends the nsIScriptError to the remote Web Console client.
*
* @param nsIScriptError aPageError
* The page error we need to send to the client.
*/
onPageError: function WCA_onPageError(aPageError)
{
let packet = {
from: this.actorID,
type: "pageError",
pageError: this.preparePageErrorForRemote(aPageError),
};
this.conn.send(packet);
},
/**
* Prepare an nsIScriptError to be sent to the client.
*
* @param nsIScriptError aPageError
* The page error we need to send to the client.
* @return object
* The object you can send to the remote client.
*/
preparePageErrorForRemote: function WCA_preparePageErrorForRemote(aPageError)
{
return {
message: aPageError.message,
errorMessage: aPageError.errorMessage,
sourceName: aPageError.sourceName,
lineText: aPageError.sourceLine,
lineNumber: aPageError.lineNumber,
columnNumber: aPageError.columnNumber,
category: aPageError.category,
timeStamp: aPageError.timeStamp,
warning: !!(aPageError.flags & aPageError.warningFlag),
error: !!(aPageError.flags & aPageError.errorFlag),
exception: !!(aPageError.flags & aPageError.exceptionFlag),
strict: !!(aPageError.flags & aPageError.strictFlag),
};
},
};
WebConsoleActor.prototype.requestTypes =
{
startListeners: WebConsoleActor.prototype.onStartListeners,
stopListeners: WebConsoleActor.prototype.onStopListeners,
};