зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1168872 - Support for server side logging; r=panos, ochameau
This commit is contained in:
Родитель
a627185642
Коммит
4cf5098ba9
|
@ -1562,6 +1562,10 @@ pref("devtools.webconsole.filter.secwarn", true);
|
|||
pref("devtools.webconsole.filter.serviceworkers", false);
|
||||
pref("devtools.webconsole.filter.sharedworkers", false);
|
||||
pref("devtools.webconsole.filter.windowlessworkers", false);
|
||||
pref("devtools.webconsole.filter.servererror", false);
|
||||
pref("devtools.webconsole.filter.serverwarn", false);
|
||||
pref("devtools.webconsole.filter.serverinfo", false);
|
||||
pref("devtools.webconsole.filter.serverlog", false);
|
||||
|
||||
// Remember the Browser Console filters
|
||||
pref("devtools.browserconsole.filter.network", true);
|
||||
|
@ -1583,6 +1587,10 @@ pref("devtools.browserconsole.filter.secwarn", true);
|
|||
pref("devtools.browserconsole.filter.serviceworkers", true);
|
||||
pref("devtools.browserconsole.filter.sharedworkers", true);
|
||||
pref("devtools.browserconsole.filter.windowlessworkers", true);
|
||||
pref("devtools.browserconsole.filter.servererror", false);
|
||||
pref("devtools.browserconsole.filter.serverwarn", false);
|
||||
pref("devtools.browserconsole.filter.serverinfo", false);
|
||||
pref("devtools.browserconsole.filter.serverlog", false);
|
||||
|
||||
// Text size in the Web Console. Use 0 for the system default size.
|
||||
pref("devtools.webconsole.fontSize", 0);
|
||||
|
|
|
@ -44,6 +44,7 @@ const COMPAT = {
|
|||
INPUT: 4,
|
||||
OUTPUT: 5,
|
||||
SECURITY: 6,
|
||||
SERVER: 7,
|
||||
},
|
||||
|
||||
// The possible message severities.
|
||||
|
@ -68,11 +69,12 @@ const COMPAT = {
|
|||
[ null, null, null, null, ], // Input
|
||||
[ null, null, null, null, ], // Output
|
||||
[ "secerror", "secwarn", null, null, ], // Security
|
||||
[ "servererror", "serverwarn", "serverinfo", "serverlog", ], // Server Logging
|
||||
],
|
||||
|
||||
// The fragment of a CSS class name that identifies each category.
|
||||
CATEGORY_CLASS_FRAGMENTS: [ "network", "cssparser", "exception", "console",
|
||||
"input", "output", "security" ],
|
||||
"input", "output", "security", "server" ],
|
||||
|
||||
// The fragment of a CSS class name that identifies each severity.
|
||||
SEVERITY_CLASS_FRAGMENTS: [ "error", "warn", "info", "log" ],
|
||||
|
@ -1300,7 +1302,7 @@ Messages.ConsoleGeneric = function(packet)
|
|||
let options = {
|
||||
className: "cm-s-mozilla",
|
||||
timestamp: packet.timeStamp,
|
||||
category: "webdev",
|
||||
category: packet.category || "webdev",
|
||||
severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level],
|
||||
prefix: packet.prefix,
|
||||
private: packet.private,
|
||||
|
@ -1571,7 +1573,7 @@ Messages.ConsoleTrace = function(packet)
|
|||
let options = {
|
||||
className: "cm-s-mozilla",
|
||||
timestamp: packet.timeStamp,
|
||||
category: "webdev",
|
||||
category: packet.category || "webdev",
|
||||
severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level],
|
||||
private: packet.private,
|
||||
filterDuplicates: true,
|
||||
|
@ -1708,7 +1710,7 @@ Messages.ConsoleTable = function(packet)
|
|||
let options = {
|
||||
className: "cm-s-mozilla",
|
||||
timestamp: packet.timeStamp,
|
||||
category: "webdev",
|
||||
category: packet.category || "webdev",
|
||||
severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level],
|
||||
private: packet.private,
|
||||
filterDuplicates: false,
|
||||
|
|
|
@ -73,6 +73,7 @@ support-files =
|
|||
test-console-count-external-file.js
|
||||
test-console-extras.html
|
||||
test-console-replaced-api.html
|
||||
test-console-server-logging.sjs
|
||||
test-console.html
|
||||
test-console-workers.html
|
||||
test-console-table.html
|
||||
|
@ -172,6 +173,7 @@ skip-if = buildapp == 'mulet'
|
|||
skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
|
||||
[browser_console_private_browsing.js]
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug 1042253 - webconsole e10s tests
|
||||
[browser_console_server_logging.js]
|
||||
[browser_console_variables_view.js]
|
||||
skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
|
||||
[browser_console_variables_view_filter.js]
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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";
|
||||
|
||||
// Check that server log appears in the console panel - bug 1168872
|
||||
let test = asyncTest(function* () {
|
||||
const PREF = "devtools.webconsole.filter.serverlog";
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-server-logging.sjs";
|
||||
|
||||
Services.prefs.setBoolPref(PREF, true);
|
||||
registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
|
||||
|
||||
yield loadTab(TEST_URI);
|
||||
|
||||
let hud = yield openConsole();
|
||||
|
||||
BrowserReload();
|
||||
|
||||
// Note that the test is also checking out the (printf like)
|
||||
// formatters and encoding of UTF8 characters (see the one at the end).
|
||||
let text = "values: string Object { a: 10 } 123 1.12 \u2713";
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: text,
|
||||
category: CATEGORY_SERVER,
|
||||
severity: SEVERITY_LOG,
|
||||
}],
|
||||
})
|
||||
});
|
|
@ -27,12 +27,14 @@ function testFilterButtons() {
|
|||
testMenuFilterButton("js");
|
||||
testMenuFilterButton("logging");
|
||||
testMenuFilterButton("security");
|
||||
testMenuFilterButton("server");
|
||||
|
||||
testIsolateFilterButton("net");
|
||||
testIsolateFilterButton("css");
|
||||
testIsolateFilterButton("js");
|
||||
testIsolateFilterButton("logging");
|
||||
testIsolateFilterButton("security");
|
||||
testIsolateFilterButton("server");
|
||||
}
|
||||
|
||||
function testMenuFilterButton(category) {
|
||||
|
|
|
@ -28,6 +28,7 @@ const CATEGORY_WEBDEV = 3;
|
|||
const CATEGORY_INPUT = 4;
|
||||
const CATEGORY_OUTPUT = 5;
|
||||
const CATEGORY_SECURITY = 6;
|
||||
const CATEGORY_SERVER = 7;
|
||||
|
||||
// The possible message severities.
|
||||
const SEVERITY_ERROR = 0;
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
function handleRequest(request, response)
|
||||
{
|
||||
var page = "<!DOCTYPE html><html>" +
|
||||
"<head><meta charset='utf-8'></head>" +
|
||||
"<body><p>hello world!</p></body>" +
|
||||
"</html>";
|
||||
|
||||
var data = {
|
||||
"version": "4.1.0",
|
||||
"columns": ["log", "backtrace", "type"],
|
||||
"rows": [[
|
||||
["values: %s %o %i %f %s","string",{"a":10,"___class_name":"Object"},123,1.12, "\u2713"],
|
||||
"C:\\src\\www\\serverlogging\\test7.php:4:1",
|
||||
""
|
||||
]]
|
||||
};
|
||||
|
||||
// Put log into headers.
|
||||
var value = b64EncodeUnicode(JSON.stringify(data));
|
||||
response.setHeader("X-ChromeLogger-Data", value, false);
|
||||
|
||||
response.write(page);
|
||||
}
|
||||
|
||||
function b64EncodeUnicode(str) {
|
||||
return btoa(unescape(encodeURIComponent(str)));
|
||||
}
|
|
@ -34,6 +34,7 @@ loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/Variabl
|
|||
loader.lazyImporter(this, "VariablesViewController", "resource:///modules/devtools/VariablesViewController.jsm");
|
||||
loader.lazyImporter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
|
||||
loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
|
||||
loader.lazyGetter(this, "Timers", () => require("sdk/timers"));
|
||||
|
||||
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
|
||||
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
|
||||
|
@ -76,6 +77,7 @@ const CATEGORY_WEBDEV = 3;
|
|||
const CATEGORY_INPUT = 4; // always on
|
||||
const CATEGORY_OUTPUT = 5; // always on
|
||||
const CATEGORY_SECURITY = 6;
|
||||
const CATEGORY_SERVER = 7;
|
||||
|
||||
// The possible message severities. As before, we start at zero so we can use
|
||||
// these as indexes into MESSAGE_PREFERENCE_KEYS.
|
||||
|
@ -93,6 +95,7 @@ const CATEGORY_CLASS_FRAGMENTS = [
|
|||
"input",
|
||||
"output",
|
||||
"security",
|
||||
"server",
|
||||
];
|
||||
|
||||
// The fragment of a CSS class name that identifies each severity.
|
||||
|
@ -109,14 +112,15 @@ const SEVERITY_CLASS_FRAGMENTS = [
|
|||
// Most of these rather idiosyncratic names are historical and predate the
|
||||
// division of message type into "category" and "severity".
|
||||
const MESSAGE_PREFERENCE_KEYS = [
|
||||
// Error Warning Info Log
|
||||
[ "network", "netwarn", "netxhr", "networkinfo", ], // Network
|
||||
[ "csserror", "cssparser", null, "csslog", ], // CSS
|
||||
[ "exception", "jswarn", null, "jslog", ], // JS
|
||||
[ "error", "warn", "info", "log", ], // Web Developer
|
||||
[ null, null, null, null, ], // Input
|
||||
[ null, null, null, null, ], // Output
|
||||
[ "secerror", "secwarn", null, null, ], // Security
|
||||
// Error Warning Info Log
|
||||
[ "network", "netwarn", "netxhr", "networkinfo", ], // Network
|
||||
[ "csserror", "cssparser", null, "csslog", ], // CSS
|
||||
[ "exception", "jswarn", null, "jslog", ], // JS
|
||||
[ "error", "warn", "info", "log", ], // Web Developer
|
||||
[ null, null, null, null, ], // Input
|
||||
[ null, null, null, null, ], // Output
|
||||
[ "secerror", "secwarn", null, null, ], // Security
|
||||
[ "servererror", "serverwarn", "serverinfo", "serverlog", ], // Server Logging
|
||||
];
|
||||
|
||||
// A mapping from the console API log event levels to the Web Console
|
||||
|
@ -217,6 +221,7 @@ function WebConsoleFrame(aWebConsoleOwner)
|
|||
this._onPanelSelected = this._onPanelSelected.bind(this);
|
||||
this._flushMessageQueue = this._flushMessageQueue.bind(this);
|
||||
this._onToolboxPrefChanged = this._onToolboxPrefChanged.bind(this);
|
||||
this._onUpdateListeners = this._onUpdateListeners.bind(this);
|
||||
|
||||
this._outputTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._outputTimerInitialized = false;
|
||||
|
@ -652,10 +657,12 @@ WebConsoleFrame.prototype = {
|
|||
let prefs = ["network", "networkinfo", "csserror", "cssparser", "csslog",
|
||||
"exception", "jswarn", "jslog", "error", "info", "warn", "log",
|
||||
"secerror", "secwarn", "netwarn", "netxhr", "sharedworkers",
|
||||
"serviceworkers", "windowlessworkers"];
|
||||
"serviceworkers", "windowlessworkers", "servererror",
|
||||
"serverwarn", "serverinfo", "serverlog"];
|
||||
|
||||
for (let pref of prefs) {
|
||||
this.filterPrefs[pref] = Services.prefs
|
||||
.getBoolPref(this._filterPrefsPrefix + pref);
|
||||
this.filterPrefs[pref] = Services.prefs.getBoolPref(
|
||||
this._filterPrefsPrefix + pref);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -666,7 +673,6 @@ WebConsoleFrame.prototype = {
|
|||
* @param function [aCallback=null]
|
||||
* Optional function to invoke when the listener has been
|
||||
* added/removed.
|
||||
*
|
||||
*/
|
||||
_updateReflowActivityListener:
|
||||
function WCF__updateReflowActivityListener(aCallback)
|
||||
|
@ -681,6 +687,38 @@ WebConsoleFrame.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Attach / detach server logging listener depending on the filter
|
||||
* preferences. If the user isn't interested in the server logs at
|
||||
* all the listener is not registered.
|
||||
*
|
||||
* @param function [aCallback=null]
|
||||
* Optional function to invoke when the listener has been
|
||||
* added/removed.
|
||||
*/
|
||||
_updateServerLoggingListener:
|
||||
function WCF__updateServerLoggingListener(aCallback)
|
||||
{
|
||||
if (!this.webConsoleClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
let startListener = false;
|
||||
let prefs = ["servererror", "serverwarn", "serverinfo", "serverlog"];
|
||||
for (let i = 0; i < prefs.length; i++) {
|
||||
if (this.filterPrefs[prefs[i]]) {
|
||||
startListener = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startListener) {
|
||||
this.webConsoleClient.startListeners(["ServerLogging"], aCallback);
|
||||
} else {
|
||||
this.webConsoleClient.stopListeners(["ServerLogging"], aCallback);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the events for the filter input field.
|
||||
* @private
|
||||
|
@ -753,6 +791,9 @@ WebConsoleFrame.prototype = {
|
|||
|
||||
let logging = this.document.querySelector("toolbarbutton[category=logging]");
|
||||
logging.removeAttribute("accesskey");
|
||||
|
||||
let serverLogging = this.document.querySelector("toolbarbutton[category=server]");
|
||||
serverLogging.removeAttribute("accesskey");
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -970,8 +1011,15 @@ WebConsoleFrame.prototype = {
|
|||
{
|
||||
this.filterPrefs[aToggleType] = aState;
|
||||
this.adjustVisibilityForMessageType(aToggleType, aState);
|
||||
|
||||
Services.prefs.setBoolPref(this._filterPrefsPrefix + aToggleType, aState);
|
||||
this._updateReflowActivityListener();
|
||||
|
||||
if (this._updateListenersTimeout) {
|
||||
Timers.clearTimeout(this._updateListenersTimeout);
|
||||
}
|
||||
|
||||
this._updateListenersTimeout = Timers.setTimeout(
|
||||
this._onUpdateListeners, 200);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -985,6 +1033,15 @@ WebConsoleFrame.prototype = {
|
|||
return this.filterPrefs[aToggleType];
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when a logging filter changes. Allows to stop/start
|
||||
* listeners according to the current filter state.
|
||||
*/
|
||||
_onUpdateListeners: function() {
|
||||
this._updateReflowActivityListener();
|
||||
this._updateServerLoggingListener();
|
||||
},
|
||||
|
||||
/**
|
||||
* Check that the passed string matches the filter arguments.
|
||||
*
|
||||
|
@ -4982,6 +5039,7 @@ function WebConsoleConnectionProxy(aWebConsole, aTarget)
|
|||
this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
|
||||
this._onFileActivity = this._onFileActivity.bind(this);
|
||||
this._onReflowActivity = this._onReflowActivity.bind(this);
|
||||
this._onServerLogCall = this._onServerLogCall.bind(this);
|
||||
this._onTabNavigated = this._onTabNavigated.bind(this);
|
||||
this._onAttachConsole = this._onAttachConsole.bind(this);
|
||||
this._onCachedMessages = this._onCachedMessages.bind(this);
|
||||
|
@ -5087,6 +5145,7 @@ WebConsoleConnectionProxy.prototype = {
|
|||
client.addListener("consoleAPICall", this._onConsoleAPICall);
|
||||
client.addListener("fileActivity", this._onFileActivity);
|
||||
client.addListener("reflowActivity", this._onReflowActivity);
|
||||
client.addListener("serverLogCall", this._onServerLogCall);
|
||||
client.addListener("lastPrivateContextExited", this._onLastPrivateContextExited);
|
||||
this.target.on("will-navigate", this._onTabNavigated);
|
||||
this.target.on("navigate", this._onTabNavigated);
|
||||
|
@ -5155,7 +5214,7 @@ WebConsoleConnectionProxy.prototype = {
|
|||
let msgs = ["PageError", "ConsoleAPI"];
|
||||
this.webConsoleClient.getCachedMessages(msgs, this._onCachedMessages);
|
||||
|
||||
this.owner._updateReflowActivityListener();
|
||||
this.owner._onUpdateListeners();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -5304,6 +5363,23 @@ WebConsoleConnectionProxy.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The "serverLogCall" message type handler. We redirect any message to
|
||||
* the UI for displaying.
|
||||
*
|
||||
* @private
|
||||
* @param string aType
|
||||
* Message type.
|
||||
* @param object aPacket
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onServerLogCall: function WCCP__onServerLogCall(aType, aPacket)
|
||||
{
|
||||
if (this.owner && aPacket.from == this._consoleActor) {
|
||||
this.owner.handleConsoleAPICall(aPacket.message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The "lastPrivateContextExited" message type handler. When this message is
|
||||
* received the Web Console UI is cleared.
|
||||
|
@ -5378,6 +5454,7 @@ WebConsoleConnectionProxy.prototype = {
|
|||
this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
|
||||
this.client.removeListener("fileActivity", this._onFileActivity);
|
||||
this.client.removeListener("reflowActivity", this._onReflowActivity);
|
||||
this.client.removeListener("serverLogCall", this._onServerLogCall);
|
||||
this.client.removeListener("lastPrivateContextExited", this._onLastPrivateContextExited);
|
||||
this.webConsoleClient.off("networkEvent", this._onNetworkEvent);
|
||||
this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
|
@ -5474,6 +5551,9 @@ ConsoleContextMenu.prototype = {
|
|||
case CATEGORY_WEBDEV:
|
||||
selection.add("webdev");
|
||||
break;
|
||||
case CATEGORY_SERVER:
|
||||
selection.add("server");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -172,11 +172,27 @@ function goUpdateConsoleCommands() {
|
|||
autocheck="false" prefKey="windowlessworkers"/>
|
||||
</menupopup>
|
||||
</toolbarbutton>
|
||||
<toolbarbutton label="&btnServerLogging.label;" type="menu-button"
|
||||
category="server" class="devtools-toolbarbutton webconsole-filter-button"
|
||||
tooltiptext="&btnServerLogging.tooltip;"
|
||||
accesskey="&btnServerLogging.accesskey;"
|
||||
tabindex="8">
|
||||
<menupopup id="server-logging-contextmenu">
|
||||
<menuitem label="&btnServerErrors;" type="checkbox"
|
||||
autocheck="false" prefKey="servererror"/>
|
||||
<menuitem label="&btnServerWarnings;" type="checkbox"
|
||||
autocheck="false" prefKey="serverwarn"/>
|
||||
<menuitem label="&btnServerInfo;" type="checkbox" autocheck="false"
|
||||
prefKey="serverinfo"/>
|
||||
<menuitem label="&btnServerLog;" type="checkbox" autocheck="false"
|
||||
prefKey="serverlog"/>
|
||||
</menupopup>
|
||||
</toolbarbutton>
|
||||
</hbox>
|
||||
<toolbarbutton class="webconsole-clear-console-button devtools-toolbarbutton"
|
||||
label="&btnClear.label;" tooltiptext="&btnClear.tooltip;"
|
||||
accesskey="&btnClear.accesskey;"
|
||||
tabindex="8"/>
|
||||
tabindex="9"/>
|
||||
|
||||
<spacer flex="1"/>
|
||||
|
||||
|
|
|
@ -76,6 +76,17 @@
|
|||
<!ENTITY btnConsoleXhr "XHR">
|
||||
<!ENTITY btnConsoleReflows "Reflows">
|
||||
|
||||
<!-- LOCALIZATION NOTE (btnServerLogging): This is used as the text of the
|
||||
- the toolbar. It shows or hides messages that the web developer inserted on
|
||||
- the page for debugging purposes, using calls on the HTTP server. -->
|
||||
<!ENTITY btnServerLogging.label "Server">
|
||||
<!ENTITY btnServerLogging.tooltip "Log messages received from a web server">
|
||||
<!ENTITY btnServerLogging.accesskey "S">
|
||||
<!ENTITY btnServerErrors "Errors">
|
||||
<!ENTITY btnServerInfo "Info">
|
||||
<!ENTITY btnServerWarnings "Warnings">
|
||||
<!ENTITY btnServerLog "Log">
|
||||
|
||||
<!-- LOCALIZATION NODE (btnConsoleSharedWorkers) the term "Shared Workers"
|
||||
- should not be translated. -->
|
||||
<!ENTITY btnConsoleSharedWorkers "Shared Workers">
|
||||
|
|
|
@ -319,18 +319,32 @@ a {
|
|||
}
|
||||
|
||||
.message[category=console][severity=error] > .icon::before,
|
||||
.message[category=output][severity=error] > .icon::before {
|
||||
.message[category=output][severity=error] > .icon::before,
|
||||
.message[category=server][severity=error] > .icon::before {
|
||||
background-position: -12px -36px;
|
||||
}
|
||||
|
||||
.message[category=console][severity=warn] > .icon::before {
|
||||
.message[category=console][severity=warn] > .icon::before,
|
||||
.message[category=server][severity=warn] > .icon::before {
|
||||
background-position: -24px -36px;
|
||||
}
|
||||
|
||||
.message[category=console][severity=info] > .icon::before {
|
||||
.message[category=console][severity=info] > .icon::before,
|
||||
.message[category=server][severity=info] > .icon::before {
|
||||
background-position: -36px -36px;
|
||||
}
|
||||
|
||||
/* Server Logging Styles */
|
||||
|
||||
.webconsole-filter-button[category="server"] > .toolbarbutton-menubutton-button:before {
|
||||
background-image: linear-gradient(rgb(144, 176, 144), rgb(99, 151, 99));
|
||||
border-color: rgb(76, 143, 76);
|
||||
}
|
||||
|
||||
.message[category=server] > .indent {
|
||||
-moz-border-end: solid #90B090 6px;
|
||||
}
|
||||
|
||||
/* Input and output styles */
|
||||
.message[category=input] > .indent,
|
||||
.message[category=output] > .indent {
|
||||
|
|
|
@ -31,6 +31,10 @@ XPCOMUtils.defineLazyGetter(this, "ConsoleProgressListener", () => {
|
|||
XPCOMUtils.defineLazyGetter(this, "events", () => {
|
||||
return require("sdk/event/core");
|
||||
});
|
||||
XPCOMUtils.defineLazyGetter(this, "ServerLoggingListener", () => {
|
||||
return require("devtools/toolkit/webconsole/server-logger")
|
||||
.ServerLoggingListener;
|
||||
});
|
||||
|
||||
for (let name of ["WebConsoleUtils", "ConsoleServiceListener",
|
||||
"ConsoleAPIListener", "addWebConsoleCommands", "JSPropertyProvider",
|
||||
|
@ -354,14 +358,22 @@ WebConsoleActor.prototype =
|
|||
this.consoleReflowListener.destroy();
|
||||
this.consoleReflowListener = null;
|
||||
}
|
||||
events.off(this.parentActor, "changed-toplevel-document", this._onChangedToplevelDocument);
|
||||
if (this.serverLoggingListener) {
|
||||
this.serverLoggingListener.destroy();
|
||||
this.serverLoggingListener = null;
|
||||
}
|
||||
|
||||
events.off(this.parentActor, "changed-toplevel-document",
|
||||
this._onChangedToplevelDocument);
|
||||
|
||||
this.conn.removeActorPool(this._actorPool);
|
||||
|
||||
if (this.parentActor.isRootActor) {
|
||||
Services.obs.removeObserver(this._onObserverNotification,
|
||||
"last-pb-context-exited");
|
||||
}
|
||||
this._actorPool = null;
|
||||
|
||||
this._actorPool = null;
|
||||
this._webConsoleCommandsCache = null;
|
||||
this._lastConsoleInputEvaluation = null;
|
||||
this._evalWindow = null;
|
||||
|
@ -604,6 +616,13 @@ WebConsoleActor.prototype =
|
|||
}
|
||||
startedListeners.push(listener);
|
||||
break;
|
||||
case "ServerLogging":
|
||||
if (!this.serverLoggingListener) {
|
||||
this.serverLoggingListener =
|
||||
new ServerLoggingListener(this.window, this);
|
||||
}
|
||||
startedListeners.push(listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -634,7 +653,7 @@ WebConsoleActor.prototype =
|
|||
// listeners.
|
||||
let toDetach = aRequest.listeners ||
|
||||
["PageError", "ConsoleAPI", "NetworkActivity",
|
||||
"FileActivity"];
|
||||
"FileActivity", "ServerLogging"];
|
||||
|
||||
while (toDetach.length > 0) {
|
||||
let listener = toDetach.shift();
|
||||
|
@ -675,6 +694,13 @@ WebConsoleActor.prototype =
|
|||
}
|
||||
stoppedListeners.push(listener);
|
||||
break;
|
||||
case "ServerLogging":
|
||||
if (this.serverLoggingListener) {
|
||||
this.serverLoggingListener.destroy();
|
||||
this.serverLoggingListener = null;
|
||||
}
|
||||
stoppedListeners.push(listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1427,6 +1453,40 @@ WebConsoleActor.prototype =
|
|||
this.conn.send(packet);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for server logging. This method forwards log events to the
|
||||
* remote Web Console client.
|
||||
*
|
||||
* @see ServerLoggingListener
|
||||
* @param object aMessage
|
||||
* The console API call on the server we need to send to the remote client.
|
||||
*/
|
||||
onServerLogCall: function WCA_onServerLogCall(aMessage)
|
||||
{
|
||||
// Clone all data into the content scope (that's where
|
||||
// passed arguments comes from).
|
||||
let msg = Cu.cloneInto(aMessage, this.window);
|
||||
|
||||
// All arguments within the message need to be converted into
|
||||
// debuggees to properly send it to the client side.
|
||||
// Use the default target: this.window as the global object
|
||||
// since that's the correct scope for data in the message.
|
||||
// The 'false' argument passed into prepareConsoleMessageForRemote()
|
||||
// ensures that makeDebuggeeValue uses content debuggee.
|
||||
// See also:
|
||||
// * makeDebuggeeValue()
|
||||
// * prepareConsoleMessageForRemote()
|
||||
msg = this.prepareConsoleMessageForRemote(msg, false);
|
||||
|
||||
let packet = {
|
||||
from: this.actorID,
|
||||
type: "serverLogCall",
|
||||
message: msg,
|
||||
};
|
||||
|
||||
this.conn.send(packet);
|
||||
},
|
||||
|
||||
//////////////////
|
||||
// End of event handlers for various listeners.
|
||||
//////////////////
|
||||
|
@ -1437,11 +1497,14 @@ WebConsoleActor.prototype =
|
|||
*
|
||||
* @param object aMessage
|
||||
* The original message received from console-api-log-event.
|
||||
* @param boolean aUseObjectGlobal
|
||||
* If |true| the object global is determined and added as a debuggee,
|
||||
* otherwise |this.window| is used when makeDebuggeeValue() is invoked.
|
||||
* @return object
|
||||
* The object that can be sent to the remote client.
|
||||
*/
|
||||
prepareConsoleMessageForRemote:
|
||||
function WCA_prepareConsoleMessageForRemote(aMessage)
|
||||
function WCA_prepareConsoleMessageForRemote(aMessage, aUseObjectGlobal = true)
|
||||
{
|
||||
let result = WebConsoleUtils.cloneObject(aMessage);
|
||||
|
||||
|
@ -1454,7 +1517,7 @@ WebConsoleActor.prototype =
|
|||
delete result.consoleID;
|
||||
|
||||
result.arguments = Array.map(aMessage.arguments || [], (aObj) => {
|
||||
let dbgObj = this.makeDebuggeeValue(aObj, true);
|
||||
let dbgObj = this.makeDebuggeeValue(aObj, aUseObjectGlobal);
|
||||
return this.createValueGrip(dbgObj);
|
||||
});
|
||||
|
||||
|
@ -1462,6 +1525,8 @@ WebConsoleActor.prototype =
|
|||
return this.createValueGrip(aString);
|
||||
});
|
||||
|
||||
result.category = aMessage.category || "webdev";
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
|
|
|
@ -12,5 +12,7 @@ EXTRA_JS_MODULES.devtools.toolkit.webconsole += [
|
|||
'client.js',
|
||||
'network-helper.js',
|
||||
'network-monitor.js',
|
||||
'server-logger-monitor.js',
|
||||
'server-logger.js',
|
||||
'utils.js',
|
||||
]
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
/* -*- js-indent-level: 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 {Ci} = require("chrome");
|
||||
const Services = require("Services");
|
||||
|
||||
const {DebuggerServer} = require("devtools/server/main");
|
||||
const {makeInfallible} = require("devtools/toolkit/DevToolsUtils");
|
||||
|
||||
loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper"));
|
||||
|
||||
// Helper tracer. Should be generic sharable by other modules (bug 1171927)
|
||||
const trace = {
|
||||
log: function(...args) {
|
||||
}
|
||||
}
|
||||
|
||||
const acceptableHeaders = ["x-chromelogger-data"];
|
||||
|
||||
/**
|
||||
* This object represents HTTP events observer. It's intended to be
|
||||
* used in e10s enabled browser only.
|
||||
*
|
||||
* Since child processes can't register HTTP event observer they use
|
||||
* this module to do the observing in the parent process. This monitor
|
||||
* is loaded through DebuggerServerConnection.setupInParent() that is executed
|
||||
* from within the child process. The execution is done by {@ServerLoggingListener}.
|
||||
* The monitor listens to HTTP events and forwards it into the right child process.
|
||||
*
|
||||
* Read more about the architecture:
|
||||
* https://github.com/mozilla/gecko-dev/blob/fx-team/toolkit/devtools/server/docs/actor-e10s-handling.md
|
||||
*/
|
||||
var ServerLoggerMonitor = {
|
||||
// Initialization
|
||||
|
||||
initialize: function() {
|
||||
this.onChildMessage = this.onChildMessage.bind(this);
|
||||
this.onDisconnectChild = this.onDisconnectChild.bind(this);
|
||||
this.onExamineResponse = this.onExamineResponse.bind(this);
|
||||
|
||||
// Set of tracked message managers.
|
||||
this.messageManagers = new Set();
|
||||
|
||||
// Set of registered child frames (loggers).
|
||||
this.targets = new Set();
|
||||
},
|
||||
|
||||
// Parent Child Relationship
|
||||
|
||||
attach: makeInfallible(function({mm, prefix}) {
|
||||
let size = this.messageManagers.size;
|
||||
|
||||
trace.log("ServerLoggerMonitor.attach; ", size, arguments);
|
||||
|
||||
if (this.messageManagers.has(mm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageManagers.add(mm);
|
||||
|
||||
// Start listening for messages from the {@ServerLogger} actor
|
||||
// living in the child process.
|
||||
mm.addMessageListener("debug:server-logger", this.onChildMessage);
|
||||
|
||||
// Listen to the disconnection message to clean-up.
|
||||
DebuggerServer.once("disconnected-from-child:" + prefix,
|
||||
this.onDisconnectChild);
|
||||
}),
|
||||
|
||||
detach: function(mm) {
|
||||
let size = this.messageManagers.size;
|
||||
|
||||
trace.log("ServerLoggerMonitor.detach; ", size);
|
||||
|
||||
// Unregister message listeners
|
||||
mm.removeMessageListener("debug:server-logger", this.onChildMessage);
|
||||
},
|
||||
|
||||
onDisconnectChild: function(event, mm) {
|
||||
let size = this.messageManagers.size;
|
||||
|
||||
trace.log("ServerLoggerMonitor.onDisconnectChild; ",
|
||||
size, arguments);
|
||||
|
||||
if (!this.messageManagers.has(mm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.detach(mm);
|
||||
|
||||
this.messageManagers.delete(mm);
|
||||
},
|
||||
|
||||
// Child Message Handling
|
||||
|
||||
onChildMessage: function(msg) {
|
||||
let method = msg.data.method;
|
||||
|
||||
trace.log("ServerLoggerMonitor.onChildMessage; ", method, msg);
|
||||
|
||||
switch (method) {
|
||||
case "attachChild":
|
||||
return this.onAttachChild(msg);
|
||||
case "detachChild":
|
||||
return this.onDetachChild(msg);
|
||||
default:
|
||||
trace.log("Unknown method name: ", method);
|
||||
}
|
||||
},
|
||||
|
||||
onAttachChild: function(event) {
|
||||
let target = event.target;
|
||||
let size = this.targets.size;
|
||||
|
||||
trace.log("ServerLoggerMonitor.onAttachChild; size: ", size, target);
|
||||
|
||||
// If this is the first child attached, register global HTTP observer.
|
||||
if (!size) {
|
||||
trace.log("ServerLoggerMonitor.onAttatchChild; Add HTTP Observer");
|
||||
Services.obs.addObserver(this.onExamineResponse,
|
||||
"http-on-examine-response", false);
|
||||
}
|
||||
|
||||
// Collect child loggers. The frame element where the
|
||||
// window/document lives.
|
||||
this.targets.add(target);
|
||||
},
|
||||
|
||||
onDetachChild: function(event) {
|
||||
let target = event.target;
|
||||
this.targets.delete(target);
|
||||
|
||||
let size = this.targets.size;
|
||||
trace.log("ServerLoggerMonitor.onDetachChild; size: ", size, target);
|
||||
|
||||
// If this is the last child process attached, unregister
|
||||
// the global HTTP observer.
|
||||
if (!size) {
|
||||
trace.log("ServerLoggerMonitor.onDetachChild; Remove HTTP Observer");
|
||||
Services.obs.removeObserver(this.onExamineResponse,
|
||||
"http-on-examine-response", false);
|
||||
}
|
||||
},
|
||||
|
||||
// HTTP Observer
|
||||
|
||||
onExamineResponse: makeInfallible(function(subject, topic) {
|
||||
let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
|
||||
|
||||
trace.log("ServerLoggerMonitor.onExamineResponse; ", httpChannel.name,
|
||||
this.targets);
|
||||
|
||||
// Ignore requests from chrome or add-on code when we are monitoring
|
||||
// content.
|
||||
if (!httpChannel.loadInfo &&
|
||||
httpChannel.loadInfo.loadingDocument === null &&
|
||||
httpChannel.loadInfo.loadingPrincipal === Services.scriptSecurityManager.getSystemPrincipal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let requestFrame = NetworkHelper.getTopFrameForRequest(httpChannel);
|
||||
if (!requestFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore requests from parent frames that aren't registered.
|
||||
if (!this.targets.has(requestFrame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let headers = [];
|
||||
|
||||
httpChannel.visitResponseHeaders((header, value) => {
|
||||
header = header.toLowerCase();
|
||||
if (acceptableHeaders.indexOf(header) !== -1) {
|
||||
headers.push({header: header, value: value});
|
||||
}
|
||||
});
|
||||
|
||||
if (!headers.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { messageManager } = requestFrame;
|
||||
messageManager.sendAsyncMessage("debug:server-logger", {
|
||||
method: "examineHeaders",
|
||||
headers: headers,
|
||||
});
|
||||
|
||||
trace.log("ServerLoggerMonitor.onExamineResponse; headers ",
|
||||
headers.length, ", ", headers);
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* Executed automatically by the framework.
|
||||
*/
|
||||
function setupParentProcess(event) {
|
||||
ServerLoggerMonitor.attach(event);
|
||||
}
|
||||
|
||||
// Monitor initialization.
|
||||
ServerLoggerMonitor.initialize();
|
||||
|
||||
// Exports from this module
|
||||
exports.setupParentProcess = setupParentProcess;
|
|
@ -0,0 +1,531 @@
|
|||
/* -*- js-indent-level: 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 {Cu, Ci} = require("chrome");
|
||||
const {Class} = require("sdk/core/heritage");
|
||||
const Services = require("Services");
|
||||
|
||||
const {DebuggerServer} = require("devtools/server/main");
|
||||
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
|
||||
Cu.importGlobalProperties(["atob"]);
|
||||
|
||||
loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper"));
|
||||
|
||||
// Helper tracer. Should be generic sharable by other modules (bug 1171927)
|
||||
const trace = {
|
||||
log: function(...args) {
|
||||
}
|
||||
}
|
||||
|
||||
// Constants
|
||||
const makeInfallible = DevToolsUtils.makeInfallible;
|
||||
const acceptableHeaders = ["x-chromelogger-data"];
|
||||
|
||||
/**
|
||||
* The listener is responsible for detecting server side logs
|
||||
* within HTTP headers and sending them to the client.
|
||||
*
|
||||
* The logic is based on "http-on-examine-response" event that is
|
||||
* sent when a response from the server is received. Consequently HTTP
|
||||
* headers are parsed to find server side logs.
|
||||
*
|
||||
* A listeners for "http-on-examine-response" is registered when
|
||||
* the listener starts and removed when destroy is executed.
|
||||
*/
|
||||
var ServerLoggingListener = Class({
|
||||
/**
|
||||
* Initialization of the listener. The main step during the initialization
|
||||
* process is registering a listener for "http-on-examine-response" event.
|
||||
*
|
||||
* @param {Object} win (nsIDOMWindow):
|
||||
* filter network requests by the associated window object.
|
||||
* If null (i.e. in the browser context) log everything
|
||||
* @param {Object} owner
|
||||
* The {@WebConsoleActor} instance
|
||||
*/
|
||||
initialize: function(win, owner) {
|
||||
trace.log("ServerLoggingListener.initialize; ", owner.actorID,
|
||||
", child process: ", DebuggerServer.isInChildProcess);
|
||||
|
||||
this.owner = owner;
|
||||
this.window = win;
|
||||
|
||||
this.onExamineResponse = this.onExamineResponse.bind(this);
|
||||
this.onExamineHeaders = this.onExamineHeaders.bind(this);
|
||||
this.onParentMessage = this.onParentMessage.bind(this);
|
||||
|
||||
this.attach();
|
||||
},
|
||||
|
||||
/**
|
||||
* The destroy is called by the parent WebConsoleActor actor.
|
||||
*/
|
||||
destroy: function() {
|
||||
trace.log("ServerLoggingListener.destroy; ", this.owner.actorID,
|
||||
", child process: ", DebuggerServer.isInChildProcess);
|
||||
|
||||
this.detach();
|
||||
},
|
||||
|
||||
/**
|
||||
* The main responsibility of this method is registering a listener for
|
||||
* "http-on-examine-response" events.
|
||||
*/
|
||||
attach: makeInfallible(function() {
|
||||
trace.log("ServerLoggingListener.attach; child process: ",
|
||||
DebuggerServer.isInChildProcess);
|
||||
|
||||
// Setup the child <-> parent communication if this actor module
|
||||
// is running in a child process. If e10s is disabled (this actor
|
||||
// running in the same process as everything else) register observer
|
||||
// listener just like in good old pre e10s days.
|
||||
if (DebuggerServer.isInChildProcess) {
|
||||
this.attachParentProcess();
|
||||
} else {
|
||||
Services.obs.addObserver(this.onExamineResponse,
|
||||
"http-on-examine-response", false);
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Remove the "http-on-examine-response" listener.
|
||||
*/
|
||||
detach: makeInfallible(function() {
|
||||
trace.log("ServerLoggingListener.detach; ", this.owner.actorID);
|
||||
|
||||
if (DebuggerServer.isInChildProcess) {
|
||||
this.detachParentProcess();
|
||||
} else {
|
||||
Services.obs.removeObserver(this.onExamineResponse,
|
||||
"http-on-examine-response", false);
|
||||
}
|
||||
}),
|
||||
|
||||
// Parent Child Relationship
|
||||
|
||||
attachParentProcess: function() {
|
||||
trace.log("ServerLoggingListener.attachParentProcess;");
|
||||
|
||||
this.owner.conn.setupInParent({
|
||||
module: "devtools/toolkit/webconsole/server-logger-monitor",
|
||||
setupParent: "setupParentProcess"
|
||||
});
|
||||
|
||||
let mm = this.owner.conn.parentMessageManager;
|
||||
let { addMessageListener, sendSyncMessage } = mm;
|
||||
|
||||
// It isn't possible to register HTTP-* event observer inside
|
||||
// a child process (in case of e10s), so listen for messages
|
||||
// coming from the {@ServerLoggerMonitor} that lives inside
|
||||
// the parent process.
|
||||
addMessageListener("debug:server-logger", this.onParentMessage);
|
||||
|
||||
// Attach to the {@ServerLoggerMonitor} object to subscribe events.
|
||||
sendSyncMessage("debug:server-logger", {
|
||||
method: "attachChild"
|
||||
});
|
||||
},
|
||||
|
||||
detachParentProcess: makeInfallible(function() {
|
||||
trace.log("ServerLoggingListener.detachParentProcess;");
|
||||
|
||||
let mm = this.owner.conn.parentMessageManager;
|
||||
let { removeMessageListener, sendSyncMessage } = mm;
|
||||
|
||||
sendSyncMessage("debug:server-logger", {
|
||||
method: "detachChild",
|
||||
});
|
||||
|
||||
removeMessageListener("debug:server-logger", this.onParentMessage);
|
||||
}),
|
||||
|
||||
onParentMessage: makeInfallible(function(msg) {
|
||||
if (!msg.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
let method = msg.data.method;
|
||||
trace.log("ServerLogger.onParentMessage; ", method, msg.data);
|
||||
|
||||
switch (method) {
|
||||
case "examineHeaders":
|
||||
return this.onExamineHeaders(msg);
|
||||
default:
|
||||
trace.log("Unknown method name: ", method);
|
||||
}
|
||||
}),
|
||||
|
||||
// HTTP Observer
|
||||
|
||||
onExamineHeaders: function(event) {
|
||||
let headers = event.data.headers;
|
||||
|
||||
trace.log("ServerLoggingListener.onExamineHeaders;", headers);
|
||||
|
||||
let parsedMessages = [];
|
||||
|
||||
for (let item of headers) {
|
||||
let header = item.header;
|
||||
let value = item.value;
|
||||
|
||||
let messages = this.parse(header, value);
|
||||
if (messages) {
|
||||
parsedMessages.push(...messages);
|
||||
}
|
||||
}
|
||||
|
||||
if (!parsedMessages.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let message of parsedMessages) {
|
||||
this.sendMessage(message);
|
||||
}
|
||||
},
|
||||
|
||||
onExamineResponse: makeInfallible(function(subject) {
|
||||
let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
|
||||
|
||||
trace.log("ServerLoggingListener.onExamineResponse; ", httpChannel.name,
|
||||
", ", this.owner.actorID, httpChannel);
|
||||
|
||||
if (!this._matchRequest(httpChannel)) {
|
||||
trace.log("ServerLoggerMonitor.onExamineResponse; No matching request!");
|
||||
return;
|
||||
}
|
||||
|
||||
let headers = [];
|
||||
|
||||
httpChannel.visitResponseHeaders((header, value) => {
|
||||
header = header.toLowerCase();
|
||||
if (acceptableHeaders.indexOf(header) !== -1) {
|
||||
headers.push({header: header, value: value});
|
||||
}
|
||||
});
|
||||
|
||||
this.onExamineHeaders({
|
||||
data: {
|
||||
headers: headers,
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* Check if a given network request should be logged by this network monitor
|
||||
* instance based on the current filters.
|
||||
*
|
||||
* @private
|
||||
* @param nsIHttpChannel aChannel
|
||||
* Request to check.
|
||||
* @return boolean
|
||||
* True if the network request should be logged, false otherwise.
|
||||
*/
|
||||
_matchRequest: function(aChannel) {
|
||||
trace.log("_matchRequest ", this.window, ", ", this.topFrame);
|
||||
|
||||
// Log everything if the window is null (it's null in the browser context)
|
||||
if (!this.window) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ignore requests from chrome or add-on code when we are monitoring
|
||||
// content.
|
||||
if (!aChannel.loadInfo &&
|
||||
aChannel.loadInfo.loadingDocument === null &&
|
||||
aChannel.loadInfo.loadingPrincipal === Services.scriptSecurityManager.getSystemPrincipal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Since frames support, this.window may not be the top level content
|
||||
// frame, so that we can't only compare with win.top.
|
||||
let win = NetworkHelper.getWindowForRequest(aChannel);
|
||||
while(win) {
|
||||
if (win == this.window) {
|
||||
return true;
|
||||
}
|
||||
if (win.parent == win) {
|
||||
break;
|
||||
}
|
||||
win = win.parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
// Server Logs
|
||||
|
||||
/**
|
||||
* Search through HTTP headers to catch all server side logs.
|
||||
* Learn more about the data structure:
|
||||
* https://craig.is/writing/chrome-logger/techspecs
|
||||
*/
|
||||
parse: function(header, value) {
|
||||
let data;
|
||||
|
||||
try {
|
||||
let result = decodeURIComponent(escape(atob(value)));
|
||||
data = JSON.parse(result);
|
||||
} catch (err) {
|
||||
Cu.reportError("Failed to parse HTTP log data! " + err);
|
||||
return;
|
||||
}
|
||||
|
||||
let parsedMessage = [];
|
||||
let columnMap = this.getColumnMap(data);
|
||||
|
||||
trace.log("ServerLoggingListener.parse; ColumnMap", columnMap);
|
||||
trace.log("ServerLoggingListener.parse; data", data);
|
||||
|
||||
let lastLocation;
|
||||
|
||||
for (let row of data.rows) {
|
||||
let backtrace = row[columnMap.get("backtrace")];
|
||||
let rawLogs = row[columnMap.get("log")];
|
||||
let type = row[columnMap.get("type")] || "log";
|
||||
|
||||
// Old version of the protocol includes a label.
|
||||
// If this is the old version do some converting.
|
||||
if (data.columns.indexOf("label") != -1) {
|
||||
let label = row[columnMap.get("label")];
|
||||
let showLabel = label && typeof label === "string";
|
||||
|
||||
rawLogs = [rawLogs];
|
||||
|
||||
if (showLabel) {
|
||||
rawLogs.unshift(label);
|
||||
}
|
||||
}
|
||||
|
||||
// If multiple logs come from the same line only the first log
|
||||
// has info about the backtrace. So, remember the last valid
|
||||
// location and use it for those that not set.
|
||||
let location = this.parseBacktrace(backtrace);
|
||||
if (location) {
|
||||
lastLocation = location;
|
||||
} else {
|
||||
location = lastLocation;
|
||||
}
|
||||
|
||||
parsedMessage.push({
|
||||
logs: rawLogs,
|
||||
location: location,
|
||||
type: type
|
||||
});
|
||||
}
|
||||
|
||||
return parsedMessage;
|
||||
},
|
||||
|
||||
parseBacktrace: function(backtrace) {
|
||||
if (!backtrace) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let result = backtrace.match(/\s*(\d+)$/);
|
||||
if (!result || result.length < 2) {
|
||||
return backtrace;
|
||||
}
|
||||
|
||||
return {
|
||||
url: backtrace.slice(0, -result[0].length),
|
||||
line: result[1]
|
||||
};
|
||||
},
|
||||
|
||||
getColumnMap: function(data) {
|
||||
let columnMap = new Map();
|
||||
let columnName;
|
||||
|
||||
for (let key in data.columns) {
|
||||
columnName = data.columns[key];
|
||||
columnMap.set(columnName, key);
|
||||
}
|
||||
|
||||
return columnMap;
|
||||
},
|
||||
|
||||
sendMessage: function(msg) {
|
||||
trace.log("ServerLoggingListener.sendMessage; message", msg);
|
||||
|
||||
let formatted = format(msg);
|
||||
trace.log("ServerLoggingListener.sendMessage; formatted", formatted);
|
||||
|
||||
let win = this.window;
|
||||
let innerID = win ? getInnerId(win) : null;
|
||||
let location = msg.location;
|
||||
|
||||
let message = {
|
||||
category: "server",
|
||||
innerID: innerID,
|
||||
level: msg.type,
|
||||
filename: location ? location.url : null,
|
||||
lineNumber: location ? location.line : null,
|
||||
columnNumber: 0,
|
||||
private: false,
|
||||
timeStamp: Date.now(),
|
||||
arguments: formatted ? formatted.logs : null,
|
||||
styles: formatted ? formatted.styles : null,
|
||||
};
|
||||
|
||||
// Make sure to set the group name.
|
||||
if (msg.type == "group" && formatted && formatted.logs) {
|
||||
message.groupName = formatted ? formatted.logs[0] : null;
|
||||
}
|
||||
|
||||
// A message for console.table() method (passed in as the first
|
||||
// argument) isn't supported. But, it's passed in by some server
|
||||
// side libraries that implement console.* API - let's just remove it.
|
||||
let args = message.arguments;
|
||||
if (msg.type == "table" && args) {
|
||||
if (typeof args[0] == "string") {
|
||||
args.shift();
|
||||
}
|
||||
}
|
||||
|
||||
trace.log("ServerLoggingListener.sendMessage; raw: ",
|
||||
msg.logs.join(", "), message);
|
||||
|
||||
this.owner.onServerLogCall(message);
|
||||
},
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* Parse printf-like specifiers ("%f", "%d", ...) and
|
||||
* format the logs according to them.
|
||||
*/
|
||||
function format(msg) {
|
||||
if (!msg.logs || !msg.logs[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the styles array (used for the "%c" specifier).
|
||||
msg.styles = [];
|
||||
|
||||
// Remove and get the first log (in which the specifiers are).
|
||||
let firstString = msg.logs.shift();
|
||||
// Contains all the strings split by the specifiers
|
||||
// (i.e. "a %f b" => ["a ", " b"]).
|
||||
let splitLog = [];
|
||||
// All the specifiers present in the first string.
|
||||
let specifiers = [];
|
||||
let specifierIndex = -1;
|
||||
let splitLogRegExp = /(.*?)(%[oOcsdif]|$)/g;
|
||||
let splitLogRegExpRes;
|
||||
|
||||
// Get the strings before the specifiers (or the last chunk before the end
|
||||
// of the string).
|
||||
while ((splitLogRegExpRes = splitLogRegExp.exec(firstString)) !== null) {
|
||||
let [_, log, specifier] = splitLogRegExpRes;
|
||||
|
||||
// We can add an empty string if there is a specifier after (which
|
||||
// means we haven't reached the end of the string). This empty string is
|
||||
// necessary when rebuilding the logs after the formatting (we should ensure
|
||||
// to alternate a log + a specifier to replace to make this loop work).
|
||||
//
|
||||
// Example: "%ctest" => first iteration: log = "", specifier = "%c".
|
||||
// => second iteration: log = "test", specifier = "".
|
||||
if (log || specifier) {
|
||||
splitLog.push(log);
|
||||
}
|
||||
|
||||
// Break now if there is no specifier anymore
|
||||
// (means that we have reached the end of the string).
|
||||
if (!specifier) {
|
||||
break;
|
||||
}
|
||||
|
||||
specifiers.push(specifier);
|
||||
}
|
||||
|
||||
// This array represents the string of the log, in which the specifiers
|
||||
// are replaced. It alternates strings and objects (%o;%O).
|
||||
let rebuiltLogArray = [];
|
||||
let concatString = "";
|
||||
let pushConcatString = () => {
|
||||
if (concatString) {
|
||||
rebuiltLogArray.push(concatString);
|
||||
}
|
||||
concatString = "";
|
||||
};
|
||||
|
||||
// Merge the split first string and the values associated to the specifiers.
|
||||
splitLog.forEach((string, index) => {
|
||||
// Concatenate the string in any case.
|
||||
concatString += string;
|
||||
if (specifiers.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let argument = msg.logs.shift();
|
||||
switch (specifiers[index]) {
|
||||
case "%i":
|
||||
case "%d":
|
||||
// Parse into integer.
|
||||
argument |= 0;
|
||||
concatString += argument;
|
||||
break;
|
||||
case "%f":
|
||||
// Parse into float.
|
||||
argument =+ argument;
|
||||
concatString += argument;
|
||||
break;
|
||||
case "%o":
|
||||
case "%O":
|
||||
// Push the concatenated string and reinitialize concatString.
|
||||
pushConcatString();
|
||||
// Push the object.
|
||||
rebuiltLogArray.push(argument);
|
||||
break;
|
||||
case "%s":
|
||||
concatString += argument;
|
||||
break;
|
||||
case "%c":
|
||||
pushConcatString();
|
||||
for (let j = msg.styles.length; j < rebuiltLogArray.length; j++) {
|
||||
msg.styles.push(null);
|
||||
}
|
||||
msg.styles.push(argument);
|
||||
break;
|
||||
default:
|
||||
// Should never happen.
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (concatString) {
|
||||
rebuiltLogArray.push(concatString);
|
||||
}
|
||||
|
||||
// Append the rest of arguments that don't have corresponding
|
||||
// specifiers to the message logs.
|
||||
msg.logs = rebuiltLogArray.concat(msg.logs);
|
||||
|
||||
// Remove special ___class_name property that isn't supported
|
||||
// by the current implementation. This property represents object class
|
||||
// allowing custom rendering in the console panel.
|
||||
for (let log of msg.logs) {
|
||||
if (typeof log == "object") {
|
||||
delete log.___class_name;
|
||||
}
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
// These helper are cloned from SDK to avoid loading to
|
||||
// much SDK modules just because of two functions.
|
||||
function getInnerId(win) {
|
||||
return win.QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
exports.ServerLoggingListener = ServerLoggingListener;
|
Загрузка…
Ссылка в новой задаче