Bug 768096 - Web Console remote debugging protocol support - Part 3: network logging; r=past,robcee

--HG--
rename : browser/devtools/webconsole/NetworkHelper.jsm => toolkit/devtools/webconsole/NetworkHelper.jsm
This commit is contained in:
Mihai Sucan 2012-10-05 14:54:43 +03:00
parent 514a8188b1
commit e13614b31e
17 changed files with 2577 additions and 379 deletions

View File

@ -18,7 +18,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm", tempScope);
Cu.import("resource://gre/modules/Services.jsm", tempScope); Cu.import("resource://gre/modules/Services.jsm", tempScope);
Cu.import("resource://gre/modules/ConsoleAPIStorage.jsm", tempScope); Cu.import("resource://gre/modules/ConsoleAPIStorage.jsm", tempScope);
Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tempScope); Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tempScope);
Cu.import("resource:///modules/NetworkHelper.jsm", tempScope); Cu.import("resource://gre/modules/devtools/NetworkHelper.jsm", tempScope);
Cu.import("resource://gre/modules/NetUtil.jsm", tempScope); Cu.import("resource://gre/modules/NetUtil.jsm", tempScope);
let XPCOMUtils = tempScope.XPCOMUtils; let XPCOMUtils = tempScope.XPCOMUtils;

View File

@ -529,28 +529,12 @@ WebConsole.prototype = {
*/ */
_asyncRequests: null, _asyncRequests: null,
/**
* Message names that the HUD listens for. These messages come from the remote
* Web Console content script.
*
* @private
* @type array
*/
_messageListeners: ["WebConsole:Initialized", "WebConsole:NetworkActivity",
"WebConsole:FileActivity", "WebConsole:LocationChange"],
/** /**
* The xul:panel that holds the Web Console when it is positioned as a window. * The xul:panel that holds the Web Console when it is positioned as a window.
* @type nsIDOMElement * @type nsIDOMElement
*/ */
consolePanel: null, consolePanel: null,
/**
* The current tab location.
* @type string
*/
contentLocation: "",
/** /**
* Getter for the xul:popupset that holds any popups we open. * Getter for the xul:popupset that holds any popups we open.
* @type nsIDOMElement * @type nsIDOMElement
@ -621,7 +605,6 @@ WebConsole.prototype = {
this.iframeWindow = this.iframe.contentWindow.wrappedJSObject; this.iframeWindow = this.iframe.contentWindow.wrappedJSObject;
this.ui = new this.iframeWindow.WebConsoleFrame(this, position); this.ui = new this.iframeWindow.WebConsoleFrame(this, position);
this._setupMessageManager();
}, },
/** /**
@ -766,8 +749,8 @@ WebConsole.prototype = {
*/ */
getPanelTitle: function WC_getPanelTitle() getPanelTitle: function WC_getPanelTitle()
{ {
return l10n.getFormatStr("webConsoleWindowTitleAndURL", let url = this.ui ? this.ui.contentLocation : "";
[this.contentLocation]); return l10n.getFormatStr("webConsoleWindowTitleAndURL", [url]);
}, },
positions: { positions: {
@ -991,16 +974,16 @@ WebConsole.prototype = {
}, },
/** /**
* Handler for the "WebConsole:LocationChange" message. If the Web Console is * Handler for page location changes. If the Web Console is
* opened in a panel the panel title is updated. * opened in a panel the panel title is updated.
* *
* @param object aMessage * @param string aURI
* The message received from the content script. It needs to hold two * New page location.
* properties: location and title. * @param string aTitle
* New page title.
*/ */
onLocationChange: function WC_onLocationChange(aMessage) onLocationChange: function WC_onLocationChange(aURI, aTitle)
{ {
this.contentLocation = aMessage.location;
if (this.consolePanel) { if (this.consolePanel) {
this.consolePanel.label = this.getPanelTitle(); this.consolePanel.label = this.getPanelTitle();
} }
@ -1036,12 +1019,6 @@ WebConsole.prototype = {
*/ */
destroy: function WC_destroy(aOnDestroy) destroy: function WC_destroy(aOnDestroy)
{ {
this.sendMessageToContent("WebConsole:Destroy", {});
this._messageListeners.forEach(function(aName) {
this.messageManager.removeMessageListener(aName, this.ui);
}, this);
// Make sure that the console panel does not try to call // Make sure that the console panel does not try to call
// deactivateHUDForContext() again. // deactivateHUDForContext() again.
this.consoleWindowUnregisterOnHide = false; this.consoleWindowUnregisterOnHide = false;

View File

@ -13,7 +13,6 @@ include $(DEPTH)/config/autoconf.mk
EXTRA_JS_MODULES = \ EXTRA_JS_MODULES = \
HUDService.jsm \ HUDService.jsm \
PropertyPanel.jsm \ PropertyPanel.jsm \
NetworkHelper.jsm \
NetworkPanel.jsm \ NetworkPanel.jsm \
AutocompletePopup.jsm \ AutocompletePopup.jsm \
$(NULL) $(NULL)

View File

@ -16,7 +16,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "mimeService", "@mozilla.org/mime;1",
"nsIMIMEService"); "nsIMIMEService");
XPCOMUtils.defineLazyModuleGetter(this, "NetworkHelper", XPCOMUtils.defineLazyModuleGetter(this, "NetworkHelper",
"resource:///modules/NetworkHelper.jsm"); "resource://gre/modules/devtools/NetworkHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm"); "resource://gre/modules/NetUtil.jsm");
@ -66,7 +66,6 @@ function NetworkPanel(aParent, aHttpActivity)
self.panel.parentNode.removeChild(self.panel); self.panel.parentNode.removeChild(self.panel);
self.panel = null; self.panel = null;
self.iframe = null; self.iframe = null;
self.document = null;
self.httpActivity = null; self.httpActivity = null;
if (self.linkNode) { if (self.linkNode) {
@ -76,9 +75,17 @@ function NetworkPanel(aParent, aHttpActivity)
}, false); }, false);
// Set the document object and update the content once the panel is loaded. // Set the document object and update the content once the panel is loaded.
this.panel.addEventListener("load", function onLoad() { this.iframe.addEventListener("load", function onLoad() {
self.panel.removeEventListener("load", onLoad, true); if (!self.iframe) {
self.document = self.iframe.contentWindow.document; return;
}
self.iframe.removeEventListener("load", onLoad, true);
self.update();
}, true);
this.panel.addEventListener("popupshown", function onPopupShown() {
self.panel.removeEventListener("popupshown", onPopupShown, true);
self.update(); self.update();
}, true); }, true);
@ -94,12 +101,6 @@ function NetworkPanel(aParent, aHttpActivity)
NetworkPanel.prototype = NetworkPanel.prototype =
{ {
/**
* Callback is called once the NetworkPanel is processed completely. Used by
* unit tests.
*/
isDoneCallback: null,
/** /**
* The current state of the output. * The current state of the output.
*/ */
@ -118,6 +119,20 @@ NetworkPanel.prototype =
_contentType: null, _contentType: null,
/**
* Function callback invoked whenever the panel content is updated. This is
* used only by tests.
*
* @private
* @type function
*/
_onUpdate: null,
get document() {
return this.iframe && this.iframe.contentWindow ?
this.iframe.contentWindow.document : null;
},
/** /**
* Small helper function that is nearly equal to l10n.getFormatStr * Small helper function that is nearly equal to l10n.getFormatStr
* except that it prefixes aName with "NetworkPanel.". * except that it prefixes aName with "NetworkPanel.".
@ -150,9 +165,8 @@ NetworkPanel.prototype =
return this._contentType; return this._contentType;
} }
let entry = this.httpActivity.log.entries[0]; let request = this.httpActivity.request;
let request = entry.request; let response = this.httpActivity.response;
let response = entry.response;
let contentType = ""; let contentType = "";
let types = response.content ? let types = response.content ?
@ -236,7 +250,7 @@ NetworkPanel.prototype =
*/ */
get _isResponseCached() get _isResponseCached()
{ {
return this.httpActivity.log.entries[0].response.status == 304; return this.httpActivity.response.status == 304;
}, },
/** /**
@ -247,7 +261,7 @@ NetworkPanel.prototype =
*/ */
get _isRequestBodyFormData() get _isRequestBodyFormData()
{ {
let requestBody = this.httpActivity.log.entries[0].request.postData.text; let requestBody = this.httpActivity.request.postData.text;
return this._fromDataRegExp.test(requestBody); return this._fromDataRegExp.test(requestBody);
}, },
@ -341,9 +355,8 @@ NetworkPanel.prototype =
*/ */
_displayRequestHeader: function NP__displayRequestHeader() _displayRequestHeader: function NP__displayRequestHeader()
{ {
let entry = this.httpActivity.log.entries[0]; let request = this.httpActivity.request;
let request = entry.request; let requestTime = new Date(this.httpActivity.startedDateTime);
let requestTime = new Date(entry.startedDateTime);
this._appendTextNode("headUrl", request.url); this._appendTextNode("headUrl", request.url);
this._appendTextNode("headMethod", request.method); this._appendTextNode("headMethod", request.method);
@ -364,8 +377,9 @@ NetworkPanel.prototype =
* *
* @returns void * @returns void
*/ */
_displayRequestBody: function NP__displayRequestBody() { _displayRequestBody: function NP__displayRequestBody()
let postData = this.httpActivity.log.entries[0].request.postData; {
let postData = this.httpActivity.request.postData;
this._displayNode("requestBody"); this._displayNode("requestBody");
this._appendTextNode("requestBodyContent", postData.text); this._appendTextNode("requestBodyContent", postData.text);
}, },
@ -376,8 +390,9 @@ NetworkPanel.prototype =
* *
* @returns void * @returns void
*/ */
_displayRequestForm: function NP__processRequestForm() { _displayRequestForm: function NP__processRequestForm()
let postData = this.httpActivity.log.entries[0].request.postData.text; {
let postData = this.httpActivity.request.postData.text;
let requestBodyLines = postData.split("\n"); let requestBodyLines = postData.split("\n");
let formData = requestBodyLines[requestBodyLines.length - 1]. let formData = requestBodyLines[requestBodyLines.length - 1].
replace(/\+/g, " ").split("&"); replace(/\+/g, " ").split("&");
@ -417,9 +432,8 @@ NetworkPanel.prototype =
*/ */
_displayResponseHeader: function NP__displayResponseHeader() _displayResponseHeader: function NP__displayResponseHeader()
{ {
let entry = this.httpActivity.log.entries[0]; let timing = this.httpActivity.timings;
let timing = entry.timings; let response = this.httpActivity.response;
let response = entry.response;
this._appendTextNode("headStatus", this._appendTextNode("headStatus",
[response.httpVersion, response.status, [response.httpVersion, response.status,
@ -453,16 +467,16 @@ NetworkPanel.prototype =
_displayResponseImage: function NP__displayResponseImage() _displayResponseImage: function NP__displayResponseImage()
{ {
let self = this; let self = this;
let entry = this.httpActivity.log.entries[0]; let timing = this.httpActivity.timings;
let timing = entry.timings; let request = this.httpActivity.request;
let request = entry.request;
let cached = ""; let cached = "";
if (this._isResponseCached) { if (this._isResponseCached) {
cached = "Cached"; cached = "Cached";
} }
let imageNode = this.document.getElementById("responseImage" + cached +"Node"); let imageNode = this.document.getElementById("responseImage" +
cached + "Node");
imageNode.setAttribute("src", request.url); imageNode.setAttribute("src", request.url);
// This function is called to set the imageInfo. // This function is called to set the imageInfo.
@ -498,9 +512,8 @@ NetworkPanel.prototype =
*/ */
_displayResponseBody: function NP__displayResponseBody() _displayResponseBody: function NP__displayResponseBody()
{ {
let entry = this.httpActivity.log.entries[0]; let timing = this.httpActivity.timings;
let timing = entry.timings; let response = this.httpActivity.response;
let response = entry.response;
let cached = this._isResponseCached ? "Cached" : ""; let cached = this._isResponseCached ? "Cached" : "";
this._appendTextNode("responseBody" + cached + "Info", this._appendTextNode("responseBody" + cached + "Info",
@ -519,7 +532,7 @@ NetworkPanel.prototype =
*/ */
_displayResponseBodyUnknownType: function NP__displayResponseBodyUnknownType() _displayResponseBodyUnknownType: function NP__displayResponseBodyUnknownType()
{ {
let timing = this.httpActivity.log.entries[0].timings; let timing = this.httpActivity.timings;
this._displayNode("responseBodyUnknownType"); this._displayNode("responseBodyUnknownType");
this._appendTextNode("responseBodyUnknownTypeInfo", this._appendTextNode("responseBodyUnknownTypeInfo",
@ -537,7 +550,7 @@ NetworkPanel.prototype =
*/ */
_displayNoResponseBody: function NP_displayNoResponseBody() _displayNoResponseBody: function NP_displayNoResponseBody()
{ {
let timing = this.httpActivity.log.entries[0].timings; let timing = this.httpActivity.timings;
this._displayNode("responseNoBody"); this._displayNode("responseNoBody");
this._appendTextNode("responseNoBodyInfo", this._appendTextNode("responseNoBodyInfo",
@ -553,15 +566,14 @@ NetworkPanel.prototype =
{ {
// After the iframe's contentWindow is ready, the document object is set. // After the iframe's contentWindow is ready, the document object is set.
// If the document object is not available yet nothing needs to be updated. // If the document object is not available yet nothing needs to be updated.
if (!this.document) { if (!this.document || !this.document.getElementById("headUrl")) {
return; return;
} }
let stages = this.httpActivity.meta.stages; let updates = this.httpActivity.updates;
let entry = this.httpActivity.log.entries[0]; let timing = this.httpActivity.timings;
let timing = entry.timings; let request = this.httpActivity.request;
let request = entry.request; let response = this.httpActivity.response;
let response = entry.response;
switch (this._state) { switch (this._state) {
case this._INIT: case this._INIT:
@ -571,7 +583,7 @@ NetworkPanel.prototype =
case this._DISPLAYED_REQUEST_HEADER: case this._DISPLAYED_REQUEST_HEADER:
// Process the request body if there is one. // Process the request body if there is one.
if (!this.httpActivity.meta.discardRequestBody && request.postData) { if (!this.httpActivity.discardRequestBody && request.postData.text) {
// Check if we send some form data. If so, display the form data special. // Check if we send some form data. If so, display the form data special.
if (this._isRequestBodyFormData) { if (this._isRequestBodyFormData) {
this._displayRequestForm(); this._displayRequestForm();
@ -584,9 +596,6 @@ NetworkPanel.prototype =
// FALL THROUGH // FALL THROUGH
case this._DISPLAYED_REQUEST_BODY: case this._DISPLAYED_REQUEST_BODY:
// There is always a response header. Therefore we can skip here if
// we don't have a response header yet and don't have to try updating
// anything else in the NetworkPanel.
if (!response.headers.length || !Object.keys(timing).length) { if (!response.headers.length || !Object.keys(timing).length) {
break; break;
} }
@ -595,13 +604,13 @@ NetworkPanel.prototype =
// FALL THROUGH // FALL THROUGH
case this._DISPLAYED_RESPONSE_HEADER: case this._DISPLAYED_RESPONSE_HEADER:
if (stages.indexOf("REQUEST_STOP") == -1 || if (updates.indexOf("responseContent") == -1 ||
stages.indexOf("TRANSACTION_CLOSE") == -1) { updates.indexOf("eventTimings") == -1) {
break; break;
} }
this._state = this._TRANSITION_CLOSED; this._state = this._TRANSITION_CLOSED;
if (this.httpActivity.meta.discardResponseBody) { if (this.httpActivity.discardResponseBody) {
break; break;
} }
@ -617,9 +626,12 @@ NetworkPanel.prototype =
else if (response.content.text) { else if (response.content.text) {
this._displayResponseBody(); this._displayResponseBody();
} }
break; break;
} }
if (this._onUpdate) {
this._onUpdate();
}
} }
} }

View File

@ -10,10 +10,12 @@
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-599725-response-headers.sjs"; const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-599725-response-headers.sjs";
function performTest(lastFinishedRequest) function performTest(lastFinishedRequest, aConsole)
{ {
ok(lastFinishedRequest, "page load was logged"); ok(lastFinishedRequest, "page load was logged");
let headers = null;
function readHeader(aName) function readHeader(aName)
{ {
for (let header of headers) { for (let header of headers) {
@ -24,13 +26,16 @@ function performTest(lastFinishedRequest)
return null; return null;
} }
let headers = lastFinishedRequest.log.entries[0].response.headers; aConsole.webConsoleClient.getResponseHeaders(lastFinishedRequest.actor,
ok(headers, "we have the response headers"); function (aResponse) {
ok(!readHeader("Content-Type"), "we do not have the Content-Type header"); headers = aResponse.headers;
isnot(readHeader("Content-Length"), 60, "Content-Length != 60"); ok(headers, "we have the response headers");
ok(!readHeader("Content-Type"), "we do not have the Content-Type header");
isnot(readHeader("Content-Length"), 60, "Content-Length != 60");
executeSoon(finishTest);
});
HUDService.lastFinishedRequestCallback = null; HUDService.lastFinishedRequestCallback = null;
executeSoon(finishTest);
} }
function test() function test()

View File

@ -10,39 +10,49 @@
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-600183-charset.html"; const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-600183-charset.html";
function performTest(lastFinishedRequest) function performTest(lastFinishedRequest, aConsole)
{ {
ok(lastFinishedRequest, "charset test page was loaded and logged"); ok(lastFinishedRequest, "charset test page was loaded and logged");
let body = lastFinishedRequest.log.entries[0].response.content.text; aConsole.webConsoleClient.getResponseContent(lastFinishedRequest.actor,
ok(body, "we have the response body"); function (aResponse) {
ok(!aResponse.contentDiscarded, "response body was not discarded");
let chars = "\u7684\u95ee\u5019!"; // 的问候! let body = aResponse.content.text;
isnot(body.indexOf("<p>" + chars + "</p>"), -1, ok(body, "we have the response body");
"found the chinese simplified string");
let chars = "\u7684\u95ee\u5019!"; // 的问候!
isnot(body.indexOf("<p>" + chars + "</p>"), -1,
"found the chinese simplified string");
executeSoon(finishTest);
});
HUDService.lastFinishedRequestCallback = null; HUDService.lastFinishedRequestCallback = null;
executeSoon(finishTest);
} }
function test() function test()
{ {
addTab("data:text/html;charset=utf-8,Web Console - bug 600183 test"); addTab("data:text/html;charset=utf-8,Web Console - bug 600183 test");
let initialLoad = true;
browser.addEventListener("load", function onLoad() { browser.addEventListener("load", function onLoad() {
if (initialLoad) { browser.removeEventListener("load", onLoad, true);
openConsole(null, function(hud) {
hud.ui.saveRequestAndResponseBodies = true; openConsole(null, function(hud) {
HUDService.lastFinishedRequestCallback = performTest; hud.ui.saveRequestAndResponseBodies = true;
content.location = TEST_URI; waitForSuccess({
name: "saveRequestAndResponseBodies update",
validatorFn: function()
{
return hud.ui.saveRequestAndResponseBodies;
},
successFn: function()
{
HUDService.lastFinishedRequestCallback = performTest;
content.location = TEST_URI;
},
failureFn: finishTest,
}); });
initialLoad = false; });
} else {
browser.removeEventListener("load", onLoad, true);
}
}, true); }, true);
} }

View File

@ -86,8 +86,17 @@ function onpopupshown2(aEvent)
}); });
}, false); }, false);
executeSoon(function() { waitForSuccess({
menupopups[1].hidePopup(); name: "saveRequestAndResponseBodies update",
validatorFn: function()
{
return huds[1].ui.saveRequestAndResponseBodies;
},
successFn: function()
{
menupopups[1].hidePopup();
},
failureFn: finishTest,
}); });
} }
@ -147,8 +156,17 @@ function onpopupshown1(aEvent)
}, tabs[runCount*2 + 1].linkedBrowser.contentWindow); }, tabs[runCount*2 + 1].linkedBrowser.contentWindow);
}, false); }, false);
executeSoon(function() { waitForSuccess({
menupopups[0].hidePopup(); name: "saveRequestAndResponseBodies update",
validatorFn: function()
{
return huds[0].ui.saveRequestAndResponseBodies;
},
successFn: function()
{
menupopups[0].hidePopup();
},
failureFn: finishTest,
}); });
} }

View File

@ -10,20 +10,86 @@
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-630733-response-redirect-headers.sjs"; const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-630733-response-redirect-headers.sjs";
let lastFinishedRequests = {}; let lastFinishedRequests = {};
let webConsoleClient;
function requestDoneCallback(aHttpRequest) function requestDoneCallback(aHttpRequest )
{ {
let status = aHttpRequest.log.entries[0].response.status; let status = aHttpRequest.response.status;
lastFinishedRequests[status] = aHttpRequest; lastFinishedRequests[status] = aHttpRequest;
} }
function performTest(aEvent) function consoleOpened(hud)
{
webConsoleClient = hud.ui.webConsoleClient;
hud.ui.saveRequestAndResponseBodies = true;
waitForSuccess({
name: "saveRequestAndResponseBodies update",
validatorFn: function()
{
return hud.ui.saveRequestAndResponseBodies;
},
successFn: function()
{
HUDService.lastFinishedRequestCallback = requestDoneCallback;
waitForSuccess(waitForResponses);
content.location = TEST_URI;
},
failureFn: finishTest,
});
let waitForResponses = {
name: "301 and 404 responses",
validatorFn: function()
{
return "301" in lastFinishedRequests &&
"404" in lastFinishedRequests;
},
successFn: getHeaders,
failureFn: finishTest,
};
}
function getHeaders()
{ {
HUDService.lastFinishedRequestCallback = null; HUDService.lastFinishedRequestCallback = null;
ok("301" in lastFinishedRequests, "request 1: 301 Moved Permanently"); ok("301" in lastFinishedRequests, "request 1: 301 Moved Permanently");
ok("404" in lastFinishedRequests, "request 2: 404 Not found"); ok("404" in lastFinishedRequests, "request 2: 404 Not found");
webConsoleClient.getResponseHeaders(lastFinishedRequests["301"].actor,
function (aResponse) {
lastFinishedRequests["301"].response.headers = aResponse.headers;
webConsoleClient.getResponseHeaders(lastFinishedRequests["404"].actor,
function (aResponse) {
lastFinishedRequests["404"].response.headers = aResponse.headers;
executeSoon(getContent);
});
});
}
function getContent()
{
webConsoleClient.getResponseContent(lastFinishedRequests["301"].actor,
function (aResponse) {
lastFinishedRequests["301"].response.content = aResponse.content;
lastFinishedRequests["301"].discardResponseBody = aResponse.contentDiscarded;
webConsoleClient.getResponseContent(lastFinishedRequests["404"].actor,
function (aResponse) {
lastFinishedRequests["404"].response.content = aResponse.content;
lastFinishedRequests["404"].discardResponseBody =
aResponse.contentDiscarded;
webConsoleClient = null;
executeSoon(performTest);
});
});
}
function performTest()
{
function readHeader(aName) function readHeader(aName)
{ {
for (let header of headers) { for (let header of headers) {
@ -34,7 +100,7 @@ function performTest(aEvent)
return null; return null;
} }
let headers = lastFinishedRequests["301"].log.entries[0].response.headers; let headers = lastFinishedRequests["301"].response.headers;
is(readHeader("Content-Type"), "text/html", is(readHeader("Content-Type"), "text/html",
"we do have the Content-Type header"); "we do have the Content-Type header");
is(readHeader("Content-Length"), 71, "Content-Length is correct"); is(readHeader("Content-Length"), 71, "Content-Length is correct");
@ -42,14 +108,17 @@ function performTest(aEvent)
"Content-Length is correct"); "Content-Length is correct");
is(readHeader("x-foobar-bug630733"), "bazbaz", is(readHeader("x-foobar-bug630733"), "bazbaz",
"X-Foobar-bug630733 is correct"); "X-Foobar-bug630733 is correct");
let body = lastFinishedRequests["301"].log.entries[0].response.content;
ok(!body.text, "body discarded for request 1");
headers = lastFinishedRequests["404"].log.entries[0].response.headers; let body = lastFinishedRequests["301"].response.content;
ok(!body.text, "body discarded for request 1");
ok(lastFinishedRequests["301"].discardResponseBody,
"body discarded for request 1 (confirmed)");
headers = lastFinishedRequests["404"].response.headers;
ok(!readHeader("Location"), "no Location header"); ok(!readHeader("Location"), "no Location header");
ok(!readHeader("x-foobar-bug630733"), "no X-Foobar-bug630733 header"); ok(!readHeader("x-foobar-bug630733"), "no X-Foobar-bug630733 header");
body = lastFinishedRequests["404"].log.entries[0].response.content.text; body = lastFinishedRequests["404"].response.content.text;
isnot(body.indexOf("404"), -1, isnot(body.indexOf("404"), -1,
"body is correct for request 2"); "body is correct for request 2");
@ -61,19 +130,8 @@ function test()
{ {
addTab("data:text/html;charset=utf-8,<p>Web Console test for bug 630733"); addTab("data:text/html;charset=utf-8,<p>Web Console test for bug 630733");
browser.addEventListener("load", function onLoad1(aEvent) { browser.addEventListener("load", function onLoad(aEvent) {
browser.removeEventListener(aEvent.type, onLoad1, true); browser.removeEventListener(aEvent.type, onLoad, true);
openConsole(null, consoleOpened);
openConsole(null, function(hud) {
hud.ui.saveRequestAndResponseBodies = true;
HUDService.lastFinishedRequestCallback = requestDoneCallback;
browser.addEventListener("load", function onLoad2(aEvent) {
browser.removeEventListener(aEvent.type, onLoad2, true);
executeSoon(performTest);
}, true);
content.location = TEST_URI;
});
}, true); }, true);
} }

View File

@ -25,7 +25,7 @@ function test()
hud = aHud; hud = aHud;
HUDService.lastFinishedRequestCallback = function(aRequest) { HUDService.lastFinishedRequestCallback = function(aRequest) {
lastRequest = aRequest.log.entries[0]; lastRequest = aRequest;
if (requestCallback) { if (requestCallback) {
requestCallback(); requestCallback();
} }

View File

@ -21,7 +21,6 @@ const TEST_DATA_JSON_CONTENT =
let lastRequest = null; let lastRequest = null;
let requestCallback = null; let requestCallback = null;
let lastActivity = null;
function test() function test()
{ {
@ -33,19 +32,34 @@ function test()
openConsole(null, function(aHud) { openConsole(null, function(aHud) {
hud = aHud; hud = aHud;
HUDService.lastFinishedRequestCallback = function(aRequest) { HUDService.lastFinishedRequestCallback = requestCallbackWrapper;
lastRequest = aRequest.log.entries[0];
lastActivity = aRequest;
if (requestCallback) {
requestCallback();
}
};
executeSoon(testPageLoad); executeSoon(testPageLoad);
}); });
}, true); }, true);
} }
function requestCallbackWrapper(aRequest)
{
lastRequest = aRequest;
hud.ui.webConsoleClient.getResponseContent(lastRequest.actor,
function(aResponse) {
lastRequest.response.content = aResponse.content;
lastRequest.discardResponseBody = aResponse.contentDiscarded;
hud.ui.webConsoleClient.getRequestPostData(lastRequest.actor,
function(aResponse) {
lastRequest.request.postData = aResponse.postData;
lastRequest.discardRequestBody = aResponse.postDataDiscarded;
if (requestCallback) {
requestCallback();
}
});
});
}
function testPageLoad() function testPageLoad()
{ {
requestCallback = function() { requestCallback = function() {
@ -55,8 +69,10 @@ function testPageLoad()
is(lastRequest.request.url, TEST_NETWORK_REQUEST_URI, is(lastRequest.request.url, TEST_NETWORK_REQUEST_URI,
"Logged network entry is page load"); "Logged network entry is page load");
is(lastRequest.request.method, "GET", "Method is correct"); is(lastRequest.request.method, "GET", "Method is correct");
ok(!lastRequest.request.postData, "No request body was stored"); ok(!lastRequest.request.postData.text, "No request body was stored");
ok(lastRequest.discardRequestBody, "Request body was discarded");
ok(!lastRequest.response.content.text, "No response body was stored"); ok(!lastRequest.response.content.text, "No response body was stored");
ok(lastRequest.discardResponseBody, "Response body was discarded");
lastRequest = null; lastRequest = null;
requestCallback = null; requestCallback = null;
@ -67,14 +83,29 @@ function testPageLoad()
} }
function testPageLoadBody() function testPageLoadBody()
{
// Turn on logging of request bodies and check again.
hud.ui.saveRequestAndResponseBodies = true;
waitForSuccess({
name: "saveRequestAndResponseBodies update",
validatorFn: function()
{
return hud.ui.saveRequestAndResponseBodies;
},
successFn: testPageLoadBodyAfterSettingUpdate,
failureFn: finishTest,
});
}
function testPageLoadBodyAfterSettingUpdate()
{ {
let loaded = false; let loaded = false;
let requestCallbackInvoked = false; let requestCallbackInvoked = false;
// Turn on logging of request bodies and check again.
hud.ui.saveRequestAndResponseBodies = true;
requestCallback = function() { requestCallback = function() {
ok(lastRequest, "Page load was logged again"); ok(lastRequest, "Page load was logged again");
ok(!lastRequest.discardResponseBody, "Response body was not discarded");
is(lastRequest.response.content.text.indexOf("<!DOCTYPE HTML>"), 0, is(lastRequest.response.content.text.indexOf("<!DOCTYPE HTML>"), 0,
"Response body's beginning is okay"); "Response body's beginning is okay");
@ -104,7 +135,8 @@ function testXhrGet()
requestCallback = function() { requestCallback = function() {
ok(lastRequest, "testXhrGet() was logged"); ok(lastRequest, "testXhrGet() was logged");
is(lastRequest.request.method, "GET", "Method is correct"); is(lastRequest.request.method, "GET", "Method is correct");
ok(!lastRequest.request.postData, "No request body was sent"); ok(!lastRequest.request.postData.text, "No request body was sent");
ok(!lastRequest.discardRequestBody, "Request body was not discarded");
is(lastRequest.response.content.text, TEST_DATA_JSON_CONTENT, is(lastRequest.response.content.text, TEST_DATA_JSON_CONTENT,
"Response is correct"); "Response is correct");
@ -165,19 +197,18 @@ function testNetworkPanel()
{ {
// Open the NetworkPanel. The functionality of the NetworkPanel is tested // Open the NetworkPanel. The functionality of the NetworkPanel is tested
// within separate test files. // within separate test files.
let networkPanel = hud.ui.openNetworkPanel(hud.ui.filterBox, lastActivity); let networkPanel = hud.ui.openNetworkPanel(hud.ui.filterBox, lastRequest);
is(networkPanel, hud.ui.filterBox._netPanel,
"Network panel stored on anchor node");
networkPanel.panel.addEventListener("load", function onLoad(aEvent) { networkPanel.panel.addEventListener("popupshown", function onPopupShown() {
networkPanel.panel.removeEventListener(aEvent.type, onLoad, true); networkPanel.panel.removeEventListener("popupshown", onPopupShown, true);
is(hud.ui.filterBox._netPanel, networkPanel,
"Network panel stored on anchor node");
ok(true, "NetworkPanel was opened"); ok(true, "NetworkPanel was opened");
// All tests are done. Shutdown. // All tests are done. Shutdown.
networkPanel.panel.hidePopup(); networkPanel.panel.hidePopup();
lastRequest = null; lastRequest = null;
lastActivity = null;
HUDService.lastFinishedRequestCallback = null; HUDService.lastFinishedRequestCallback = null;
executeSoon(finishTest); executeSoon(finishTest);
}, true); }, true);

View File

@ -65,42 +65,36 @@ function testGen() {
let filterBox = hud.ui.filterBox; let filterBox = hud.ui.filterBox;
let httpActivity = { let httpActivity = {
meta: { updates: [],
stages: [], discardRequestBody: true,
discardRequestBody: true, discardResponseBody: true,
discardResponseBody: true, startedDateTime: (new Date()).toISOString(),
request: {
url: "http://www.testpage.com",
method: "GET",
cookies: [],
headers: [
{ name: "foo", value: "bar" },
],
}, },
log: { response: {
entries: [{ headers: [],
startedDateTime: (new Date()).toISOString(), content: {},
request: {
url: "http://www.testpage.com",
method: "GET",
cookies: [],
headers: [
{ name: "foo", value: "bar" },
],
},
response: {
headers: [],
content: {},
},
timings: {},
}],
}, },
timings: {},
}; };
let entry = httpActivity.log.entries[0];
let networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); let networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
is(filterBox._netPanel, networkPanel, is(filterBox._netPanel, networkPanel,
"Network panel stored on the anchor object"); "Network panel stored on the anchor object");
networkPanel.panel.addEventListener("load", function onLoad() { networkPanel._onUpdate = function() {
networkPanel.panel.removeEventListener("load", onLoad, true); networkPanel._onUpdate = null;
testDriver.next(); executeSoon(function() {
}, true); testDriver.next();
});
};
yield; yield;
@ -123,8 +117,8 @@ function testGen() {
// Test request body. // Test request body.
info("test 2: request body"); info("test 2: request body");
httpActivity.meta.discardRequestBody = false; httpActivity.discardRequestBody = false;
entry.request.postData = { text: "hello world" }; httpActivity.request.postData = { text: "hello world" };
networkPanel.update(); networkPanel.update();
checkIsVisible(networkPanel, { checkIsVisible(networkPanel, {
@ -141,12 +135,12 @@ function testGen() {
// Test response header. // Test response header.
info("test 3: response header"); info("test 3: response header");
entry.timings.wait = 10; httpActivity.timings.wait = 10;
entry.response.httpVersion = "HTTP/3.14"; httpActivity.response.httpVersion = "HTTP/3.14";
entry.response.status = 999; httpActivity.response.status = 999;
entry.response.statusText = "earthquake win"; httpActivity.response.statusText = "earthquake win";
entry.response.content.mimeType = "text/html"; httpActivity.response.content.mimeType = "text/html";
entry.response.headers.push( httpActivity.response.headers.push(
{ name: "Content-Type", value: "text/html" }, { name: "Content-Type", value: "text/html" },
{ name: "leaveHouses", value: "true" } { name: "leaveHouses", value: "true" }
); );
@ -170,8 +164,8 @@ function testGen() {
info("test 4"); info("test 4");
httpActivity.meta.discardResponseBody = false; httpActivity.discardResponseBody = false;
entry.timings.receive = 2; httpActivity.timings.receive = 2;
networkPanel.update(); networkPanel.update();
checkIsVisible(networkPanel, { checkIsVisible(networkPanel, {
@ -187,7 +181,7 @@ function testGen() {
info("test 5"); info("test 5");
httpActivity.meta.stages.push("REQUEST_STOP", "TRANSACTION_CLOSE"); httpActivity.updates.push("responseContent", "eventTimings");
networkPanel.update(); networkPanel.update();
checkNodeContent(networkPanel, "responseNoBodyInfo", "2ms"); checkNodeContent(networkPanel, "responseNoBodyInfo", "2ms");
@ -205,20 +199,22 @@ function testGen() {
// Second run: Test for cookies and response body. // Second run: Test for cookies and response body.
info("test 6: cookies and response body"); info("test 6: cookies and response body");
entry.request.cookies.push( httpActivity.request.cookies.push(
{ name: "foo", value: "bar" }, { name: "foo", value: "bar" },
{ name: "hello", value: "world" } { name: "hello", value: "world" }
); );
entry.response.content.text = "get out here"; httpActivity.response.content.text = "get out here";
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
is(filterBox._netPanel, networkPanel, is(filterBox._netPanel, networkPanel,
"Network panel stored on httpActivity object"); "Network panel stored on httpActivity object");
networkPanel.panel.addEventListener("load", function onLoad() { networkPanel._onUpdate = function() {
networkPanel.panel.removeEventListener("load", onLoad, true); networkPanel._onUpdate = null;
testDriver.next(); executeSoon(function() {
}, true); testDriver.next();
});
};
yield; yield;
@ -242,15 +238,17 @@ function testGen() {
// Check image request. // Check image request.
info("test 7: image request"); info("test 7: image request");
entry.response.headers[1].value = "image/png"; httpActivity.response.headers[1].value = "image/png";
entry.response.content.mimeType = "image/png"; httpActivity.response.content.mimeType = "image/png";
entry.request.url = TEST_IMG; httpActivity.request.url = TEST_IMG;
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() { networkPanel._onUpdate = function() {
networkPanel.panel.removeEventListener("load", onLoad, true); networkPanel._onUpdate = null;
testDriver.next(); executeSoon(function() {
}, true); testDriver.next();
});
};
yield; yield;
@ -291,15 +289,17 @@ function testGen() {
// Check cached image request. // Check cached image request.
info("test 8: cached image request"); info("test 8: cached image request");
entry.response.httpVersion = "HTTP/1.1"; httpActivity.response.httpVersion = "HTTP/1.1";
entry.response.status = 304; httpActivity.response.status = 304;
entry.response.statusText = "Not Modified"; httpActivity.response.statusText = "Not Modified";
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() { networkPanel._onUpdate = function() {
networkPanel.panel.removeEventListener("load", onLoad, true); networkPanel._onUpdate = null;
testDriver.next(); executeSoon(function() {
}, true); testDriver.next();
});
};
yield; yield;
@ -321,17 +321,19 @@ function testGen() {
// Test sent form data. // Test sent form data.
info("test 9: sent form data"); info("test 9: sent form data");
entry.request.postData.text = [ httpActivity.request.postData.text = [
"Content-Type: application/x-www-form-urlencoded", "Content-Type: application/x-www-form-urlencoded",
"Content-Length: 59", "Content-Length: 59",
"name=rob&age=20" "name=rob&age=20"
].join("\n"); ].join("\n");
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() { networkPanel._onUpdate = function() {
networkPanel.panel.removeEventListener("load", onLoad, true); networkPanel._onUpdate = null;
testDriver.next(); executeSoon(function() {
}, true); testDriver.next();
});
};
yield; yield;
@ -352,13 +354,15 @@ function testGen() {
// Test no space after Content-Type: // Test no space after Content-Type:
info("test 10: no space after Content-Type header in post data"); info("test 10: no space after Content-Type header in post data");
entry.request.postData.text = "Content-Type:application/x-www-form-urlencoded\n"; httpActivity.request.postData.text = "Content-Type:application/x-www-form-urlencoded\n";
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() { networkPanel._onUpdate = function() {
networkPanel.panel.removeEventListener("load", onLoad, true); networkPanel._onUpdate = null;
testDriver.next(); executeSoon(function() {
}, true); testDriver.next();
});
};
yield; yield;
@ -379,16 +383,18 @@ function testGen() {
info("test 11: cached data"); info("test 11: cached data");
entry.request.url = TEST_ENCODING_ISO_8859_1; httpActivity.request.url = TEST_ENCODING_ISO_8859_1;
entry.response.headers[1].value = "application/json"; httpActivity.response.headers[1].value = "application/json";
entry.response.content.mimeType = "application/json"; httpActivity.response.content.mimeType = "application/json";
entry.response.content.text = "my cached data is here!"; httpActivity.response.content.text = "my cached data is here!";
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() { networkPanel._onUpdate = function() {
networkPanel.panel.removeEventListener("load", onLoad, true); networkPanel._onUpdate = null;
testDriver.next(); executeSoon(function() {
}, true); testDriver.next();
});
};
yield; yield;
@ -412,14 +418,16 @@ function testGen() {
// Test a response with a content type that can't be displayed in the // Test a response with a content type that can't be displayed in the
// NetworkPanel. // NetworkPanel.
info("test 12: unknown content type"); info("test 12: unknown content type");
entry.response.headers[1].value = "application/x-shockwave-flash"; httpActivity.response.headers[1].value = "application/x-shockwave-flash";
entry.response.content.mimeType = "application/x-shockwave-flash"; httpActivity.response.content.mimeType = "application/x-shockwave-flash";
networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity); networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() { networkPanel._onUpdate = function() {
networkPanel.panel.removeEventListener("load", onLoad, true); networkPanel._onUpdate = null;
testDriver.next(); executeSoon(function() {
}, true); testDriver.next();
});
};
yield; yield;

View File

@ -216,13 +216,6 @@ WebConsoleFrame.prototype = {
*/ */
proxy: null, 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. * Getter for the xul:popupset that holds any popups we open.
* @type nsIDOMElement * @type nsIDOMElement
@ -290,6 +283,12 @@ WebConsoleFrame.prototype = {
*/ */
groupDepth: 0, groupDepth: 0,
/**
* The current tab location.
* @type string
*/
contentLocation: "",
/** /**
* The JSTerm object that manage the console's input. * The JSTerm object that manage the console's input.
* @see JSTerm * @see JSTerm
@ -331,16 +330,16 @@ WebConsoleFrame.prototype = {
* The new value you want to set. * The new value you want to set.
*/ */
set saveRequestAndResponseBodies(aValue) { set saveRequestAndResponseBodies(aValue) {
this._saveRequestAndResponseBodies = aValue; let newValue = !!aValue;
let preferences = {
let message = { "NetworkMonitor.saveRequestAndResponseBodies": newValue,
preferences: {
"NetworkMonitor.saveRequestAndResponseBodies":
this._saveRequestAndResponseBodies,
},
}; };
this.owner.sendMessageToContent("WebConsole:SetPreferences", message); this.webConsoleClient.setPreferences(preferences, function(aResponse) {
if (!aResponse.error) {
this._saveRequestAndResponseBodies = newValue;
}
}.bind(this));
}, },
/** /**
@ -352,9 +351,8 @@ WebConsoleFrame.prototype = {
this.proxy = new WebConsoleConnectionProxy(this); this.proxy = new WebConsoleConnectionProxy(this);
this.proxy.initServer(); this.proxy.initServer();
this.proxy.connect(function() { this.proxy.connect(function() {
if (this._messageManagerInitComplete) { this.saveRequestAndResponseBodies = this._saveRequestAndResponseBodies;
this._onInitComplete(); this._onInitComplete();
}
}.bind(this)); }.bind(this));
}, },
@ -653,50 +651,6 @@ WebConsoleFrame.prototype = {
} }
}, },
/**
* Handler for all of the messages coming from the Web Console content script.
*
* @private
* @param object aMessage
* A MessageManager object that holds the remote message.
*/
receiveMessage: function WCF_receiveMessage(aMessage)
{
if (!aMessage.json || aMessage.json.hudId != this.hudId) {
return;
}
switch (aMessage.name) {
case "WebConsole:Initialized":
this._onMessageManagerInitComplete();
break;
case "WebConsole:NetworkActivity":
this.handleNetworkActivity(aMessage.json);
break;
case "WebConsole:FileActivity":
this.outputMessage(CATEGORY_NETWORK, this.logFileActivity,
[aMessage.json.uri]);
break;
case "WebConsole:LocationChange":
this.owner.onLocationChange(aMessage.json);
break;
}
},
/**
* 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 * The event handler that is called whenever a user switches a filter on or
* off. * off.
@ -1304,22 +1258,21 @@ WebConsoleFrame.prototype = {
}, },
/** /**
* Log network activity. * Log network event.
* *
* @param object aHttpActivity * @param object aActorId
* The HTTP activity to log. * The network event actor ID to log.
* @return nsIDOMElement|undefined * @return nsIDOMElement|undefined
* The message element to display in the Web Console output. * The message element to display in the Web Console output.
*/ */
logNetActivity: function WCF_logNetActivity(aConnectionId) logNetEvent: function WCF_logNetEvent(aActorId)
{ {
let networkInfo = this._networkRequests[aConnectionId]; let networkInfo = this._networkRequests[aActorId];
if (!networkInfo) { if (!networkInfo) {
return; return;
} }
let entry = networkInfo.httpActivity.log.entries[0]; let request = networkInfo.request;
let request = entry.request;
let msgNode = this.document.createElementNS(XUL_NS, "hbox"); let msgNode = this.document.createElementNS(XUL_NS, "hbox");
@ -1347,8 +1300,7 @@ WebConsoleFrame.prototype = {
let severity = SEVERITY_LOG; let severity = SEVERITY_LOG;
let mixedRequest = let mixedRequest =
WebConsoleUtils.isMixedHTTPSRequest(request.url, WebConsoleUtils.isMixedHTTPSRequest(request.url, this.contentLocation);
this.owner.contentLocation);
if (mixedRequest) { if (mixedRequest) {
urlNode.classList.add("webconsole-mixed-content"); urlNode.classList.add("webconsole-mixed-content");
this.makeMixedContentNode(linkNode); this.makeMixedContentNode(linkNode);
@ -1369,18 +1321,18 @@ WebConsoleFrame.prototype = {
let messageNode = this.createMessageNode(CATEGORY_NETWORK, severity, let messageNode = this.createMessageNode(CATEGORY_NETWORK, severity,
msgNode, null, null, clipboardText); msgNode, null, null, clipboardText);
messageNode._connectionId = entry.connection; messageNode._connectionId = aActorId;
messageNode.url = request.url; messageNode.url = request.url;
this.makeOutputMessageLink(messageNode, function WCF_net_message_link() { this.makeOutputMessageLink(messageNode, function WCF_net_message_link() {
if (!messageNode._panelOpen) { if (!messageNode._panelOpen) {
this.openNetworkPanel(messageNode, networkInfo.httpActivity); this.openNetworkPanel(messageNode, networkInfo);
} }
}.bind(this)); }.bind(this));
networkInfo.node = messageNode; networkInfo.node = messageNode;
this._updateNetMessage(entry.connection); this._updateNetMessage(aActorId);
return messageNode; return messageNode;
}, },
@ -1441,6 +1393,17 @@ WebConsoleFrame.prototype = {
return outputNode; return outputNode;
}, },
/**
* Handle the file activity messages coming from the remote Web Console.
*
* @param string aFileURI
* The file URI that was requested.
*/
handleFileActivity: function WCF_handleFileActivity(aFileURI)
{
this.outputMessage(CATEGORY_NETWORK, this.logFileActivity, [aFileURI]);
},
/** /**
* Inform user that the Web Console API has been replaced by a script * Inform user that the Web Console API has been replaced by a script
* in a content page. * in a content page.
@ -1453,86 +1416,122 @@ WebConsoleFrame.prototype = {
}, },
/** /**
* Handle the "WebConsole:NetworkActivity" message coming from the remote Web * Handle the network events coming from the remote Web Console.
* Console.
* *
* @param object aMessage * @param object aActor
* The HTTP activity object. This object needs to hold two properties: * The NetworkEventActor grip.
* - meta - some metadata about the request log:
* - stages - the stages the network request went through.
* - discardRequestBody and discardResponseBody - booleans that tell
* if the network request/response body was discarded or not.
* - log - the request and response information. This is a HAR-like
* object. See HUDService-content.js
* NetworkMonitor.createActivityObject().
*/ */
handleNetworkActivity: function WCF_handleNetworkActivity(aMessage) handleNetworkEvent: function WCF_handleNetworkEvent(aActor)
{ {
let stage = aMessage.meta.stages[aMessage.meta.stages.length - 1]; let networkInfo = {
let entry = aMessage.log.entries[0]; node: null,
actor: aActor.actor,
discardRequestBody: true,
discardResponseBody: true,
startedDateTime: aActor.startedDateTime,
request: {
url: aActor.url,
method: aActor.method,
},
response: {},
timings: {},
updates: [], // track the list of network event updates
};
if (stage == "REQUEST_HEADER") { this._networkRequests[aActor.actor] = networkInfo;
let networkInfo = { this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [aActor.actor]);
node: null, },
httpActivity: aMessage,
};
this._networkRequests[entry.connection] = networkInfo; /**
this.outputMessage(CATEGORY_NETWORK, this.logNetActivity, * Handle network event updates coming from the server.
[entry.connection]); *
return; * @param string aActorId
} * The network event actor ID.
else if (!(entry.connection in this._networkRequests)) { * @param string aType
* Update type.
* @param object aPacket
* Update details.
*/
handleNetworkEventUpdate:
function WCF_handleNetworkEventUpdate(aActorId, aType, aPacket)
{
let networkInfo = this._networkRequests[aActorId];
if (!networkInfo) {
return; return;
} }
let networkInfo = this._networkRequests[entry.connection]; networkInfo.updates.push(aType);
networkInfo.httpActivity = aMessage;
switch (aType) {
case "requestHeaders":
networkInfo.request.headersSize = aPacket.headersSize;
break;
case "requestPostData":
networkInfo.discardRequestBody = aPacket.discardRequestBody;
networkInfo.request.bodySize = aPacket.dataSize;
break;
case "responseStart":
networkInfo.response.httpVersion = aPacket.response.httpVersion;
networkInfo.response.status = aPacket.response.status;
networkInfo.response.statusText = aPacket.response.statusText;
networkInfo.response.headersSize = aPacket.response.headersSize;
networkInfo.discardResponseBody = aPacket.response.discardResponseBody;
break;
case "responseContent":
networkInfo.response.content = {
mimeType: aPacket.mimeType,
};
networkInfo.response.bodySize = aPacket.contentSize;
networkInfo.discardResponseBody = aPacket.discardResponseBody;
break;
case "eventTimings":
networkInfo.totalTime = aPacket.totalTime;
break;
}
if (networkInfo.node) { if (networkInfo.node) {
this._updateNetMessage(entry.connection); this._updateNetMessage(aActorId);
} }
// For unit tests we pass the HTTP activity object to the test callback, // For unit tests we pass the HTTP activity object to the test callback,
// once requests complete. // once requests complete.
if (this.owner.lastFinishedRequestCallback && if (this.owner.lastFinishedRequestCallback &&
aMessage.meta.stages.indexOf("REQUEST_STOP") > -1 && networkInfo.updates.indexOf("responseContent") > -1 &&
aMessage.meta.stages.indexOf("TRANSACTION_CLOSE") > -1) { networkInfo.updates.indexOf("eventTimings") > -1) {
this.owner.lastFinishedRequestCallback(aMessage); this.owner.lastFinishedRequestCallback(networkInfo, this);
} }
}, },
/** /**
* Update an output message to reflect the latest state of a network request, * Update an output message to reflect the latest state of a network request,
* given a network connection ID. * given a network event actor ID.
* *
* @private * @private
* @param string aConnectionId * @param string aActorId
* The connection ID to update. * The network event actor ID for which you want to update the message.
*/ */
_updateNetMessage: function WCF__updateNetMessage(aConnectionId) _updateNetMessage: function WCF__updateNetMessage(aActorId)
{ {
let networkInfo = this._networkRequests[aConnectionId]; let networkInfo = this._networkRequests[aActorId];
if (!networkInfo || !networkInfo.node) { if (!networkInfo || !networkInfo.node) {
return; return;
} }
let messageNode = networkInfo.node; let messageNode = networkInfo.node;
let httpActivity = networkInfo.httpActivity; let updates = networkInfo.updates;
let stages = httpActivity.meta.stages; let hasEventTimings = updates.indexOf("eventTimings") > -1;
let hasTransactionClose = stages.indexOf("TRANSACTION_CLOSE") > -1; let hasResponseStart = updates.indexOf("responseStart") > -1;
let hasResponseHeader = stages.indexOf("RESPONSE_HEADER") > -1; let request = networkInfo.request;
let entry = httpActivity.log.entries[0]; let response = networkInfo.response;
let request = entry.request;
let response = entry.response;
if (hasTransactionClose || hasResponseHeader) { if (hasEventTimings || hasResponseStart) {
let status = []; let status = [];
if (response.httpVersion && response.status) { if (response.httpVersion && response.status) {
status = [response.httpVersion, response.status, response.statusText]; status = [response.httpVersion, response.status, response.statusText];
} }
if (hasTransactionClose) { if (hasEventTimings) {
status.push(l10n.getFormatStr("NetworkPanel.durationMS", [entry.time])); status.push(l10n.getFormatStr("NetworkPanel.durationMS",
[networkInfo.totalTime]));
} }
let statusText = "[" + status.join(" ") + "]"; let statusText = "[" + status.join(" ") + "]";
@ -1543,7 +1542,7 @@ WebConsoleFrame.prototype = {
messageNode.clipboardText = [request.method, request.url, statusText] messageNode.clipboardText = [request.method, request.url, statusText]
.join(" "); .join(" ");
if (hasResponseHeader && response.status >= MIN_HTTP_ERROR_CODE && if (hasResponseStart && response.status >= MIN_HTTP_ERROR_CODE &&
response.status <= MAX_HTTP_ERROR_CODE) { response.status <= MAX_HTTP_ERROR_CODE) {
this.setMessageType(messageNode, CATEGORY_NETWORK, SEVERITY_ERROR); this.setMessageType(messageNode, CATEGORY_NETWORK, SEVERITY_ERROR);
} }
@ -1567,27 +1566,138 @@ WebConsoleFrame.prototype = {
*/ */
openNetworkPanel: function WCF_openNetworkPanel(aNode, aHttpActivity) openNetworkPanel: function WCF_openNetworkPanel(aNode, aHttpActivity)
{ {
let actor = aHttpActivity.actor;
if (actor) {
this.webConsoleClient.getRequestHeaders(actor, function(aResponse) {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getRequestHeaders:" +
aResponse.error);
return;
}
aHttpActivity.request.headers = aResponse.headers;
this.webConsoleClient.getRequestCookies(actor, onRequestCookies);
}.bind(this));
}
let onRequestCookies = function(aResponse) {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getRequestCookies:" +
aResponse.error);
return;
}
aHttpActivity.request.cookies = aResponse.cookies;
this.webConsoleClient.getResponseHeaders(actor, onResponseHeaders);
}.bind(this);
let onResponseHeaders = function(aResponse) {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getResponseHeaders:" +
aResponse.error);
return;
}
aHttpActivity.response.headers = aResponse.headers;
this.webConsoleClient.getResponseCookies(actor, onResponseCookies);
}.bind(this);
let onResponseCookies = function(aResponse) {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getResponseCookies:" +
aResponse.error);
return;
}
aHttpActivity.response.cookies = aResponse.cookies;
this.webConsoleClient.getRequestPostData(actor, onRequestPostData);
}.bind(this);
let onRequestPostData = function(aResponse) {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getRequestPostData:" +
aResponse.error);
return;
}
aHttpActivity.request.postData = aResponse.postData;
aHttpActivity.discardRequestBody = aResponse.postDataDiscarded;
this.webConsoleClient.getResponseContent(actor, onResponseContent);
}.bind(this);
let onResponseContent = function(aResponse) {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getResponseContent:" +
aResponse.error);
return;
}
aHttpActivity.response.content = aResponse.content;
aHttpActivity.discardResponseBody = aResponse.contentDiscarded;
this.webConsoleClient.getEventTimings(actor, onEventTimings);
}.bind(this);
let onEventTimings = function(aResponse) {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getEventTimings:" +
aResponse.error);
return;
}
aHttpActivity.timings = aResponse.timings;
openPanel();
}.bind(this);
let openPanel = function() {
aNode._netPanel = netPanel;
let panel = netPanel.panel;
panel.openPopup(aNode, "after_pointer", 0, 0, false, false);
panel.sizeTo(450, 500);
panel.setAttribute("hudId", this.hudId);
panel.addEventListener("popuphiding", function WCF_netPanel_onHide() {
panel.removeEventListener("popuphiding", WCF_netPanel_onHide);
aNode._panelOpen = false;
aNode._netPanel = null;
});
aNode._panelOpen = true;
}.bind(this);
let netPanel = new NetworkPanel(this.popupset, aHttpActivity); let netPanel = new NetworkPanel(this.popupset, aHttpActivity);
netPanel.linkNode = aNode; netPanel.linkNode = aNode;
aNode._netPanel = netPanel;
let panel = netPanel.panel; if (!actor) {
panel.openPopup(aNode, "after_pointer", 0, 0, false, false); openPanel();
panel.sizeTo(450, 500); }
panel.setAttribute("hudId", aHttpActivity.hudId);
panel.addEventListener("popuphiding", function WCF_netPanel_onHide() {
panel.removeEventListener("popuphiding", WCF_netPanel_onHide);
aNode._panelOpen = false;
aNode._netPanel = null;
});
aNode._panelOpen = true;
return netPanel; return netPanel;
}, },
/**
* Handler for page location changes.
*
* @param string aURI
* New page location.
* @param string aTitle
* New page title.
*/
onLocationChange: function WCF_onLocationChange(aURI, aTitle)
{
this.contentLocation = aURI;
this.owner.onLocationChange(aURI, aTitle);
},
/** /**
* Output a message node. This filters a node appropriately, then sends it to * Output a message node. This filters a node appropriately, then sends it to
* the output, regrouping and pruning output as necessary. * the output, regrouping and pruning output as necessary.
@ -1847,7 +1957,7 @@ WebConsoleFrame.prototype = {
if (category == CATEGORY_NETWORK) { if (category == CATEGORY_NETWORK) {
let connectionId = null; let connectionId = null;
if (methodOrNode == this.logNetActivity) { if (methodOrNode == this.logNetEvent) {
connectionId = args[0]; connectionId = args[0];
} }
else if (typeof methodOrNode != "function") { else if (typeof methodOrNode != "function") {
@ -1855,6 +1965,7 @@ WebConsoleFrame.prototype = {
} }
if (connectionId && connectionId in this._networkRequests) { if (connectionId && connectionId in this._networkRequests) {
delete this._networkRequests[connectionId]; delete this._networkRequests[connectionId];
this._releaseObject(connectionId);
} }
} }
else if (category == CATEGORY_WEBDEV && else if (category == CATEGORY_WEBDEV &&
@ -1950,8 +2061,10 @@ WebConsoleFrame.prototype = {
} }
delete this._cssNodes[desc + location]; delete this._cssNodes[desc + location];
} }
else if (aNode.classList.contains("webconsole-msg-network")) { else if (aNode._connectionId &&
aNode.classList.contains("webconsole-msg-network")) {
delete this._networkRequests[aNode._connectionId]; delete this._networkRequests[aNode._connectionId];
this._releaseObject(aNode._connectionId);
} }
else if (aNode.classList.contains("webconsole-msg-inspector")) { else if (aNode.classList.contains("webconsole-msg-inspector")) {
this.pruneConsoleDirNode(aNode); this.pruneConsoleDirNode(aNode);
@ -2424,11 +2537,11 @@ WebConsoleFrame.prototype = {
}, },
/** /**
* Release an object actor. * Release an actor.
* *
* @private * @private
* @param string aActor * @param string aActor
* The object actor ID you want to release. * The actor ID you want to release.
*/ */
_releaseObject: function WCF__releaseObject(aActor) _releaseObject: function WCF__releaseObject(aActor)
{ {
@ -3701,6 +3814,10 @@ function WebConsoleConnectionProxy(aWebConsole)
this._onPageError = this._onPageError.bind(this); this._onPageError = this._onPageError.bind(this);
this._onConsoleAPICall = this._onConsoleAPICall.bind(this); this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
this._onNetworkEvent = this._onNetworkEvent.bind(this);
this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
this._onFileActivity = this._onFileActivity.bind(this);
this._onLocationChange = this._onLocationChange.bind(this);
} }
WebConsoleConnectionProxy.prototype = { WebConsoleConnectionProxy.prototype = {
@ -3766,13 +3883,19 @@ WebConsoleConnectionProxy.prototype = {
client.addListener("pageError", this._onPageError); client.addListener("pageError", this._onPageError);
client.addListener("consoleAPICall", this._onConsoleAPICall); client.addListener("consoleAPICall", this._onConsoleAPICall);
client.addListener("networkEvent", this._onNetworkEvent);
client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
client.addListener("fileActivity", this._onFileActivity);
client.addListener("locationChange", this._onLocationChange);
let listeners = ["PageError", "ConsoleAPI"]; let listeners = ["PageError", "ConsoleAPI", "NetworkActivity",
"FileActivity", "LocationChange"];
client.connect(function(aType, aTraits) { client.connect(function(aType, aTraits) {
client.listTabs(function(aResponse) { client.listTabs(function(aResponse) {
let tab = aResponse.tabs[aResponse.selected]; let tab = aResponse.tabs[aResponse.selected];
this._consoleActor = tab.consoleActor; this._consoleActor = tab.consoleActor;
this.owner.onLocationChange(tab.url, tab.title);
client.attachConsole(tab.consoleActor, listeners, client.attachConsole(tab.consoleActor, listeners,
this._onAttachConsole.bind(this, aCallback)); this._onAttachConsole.bind(this, aCallback));
}.bind(this)); }.bind(this));
@ -3870,6 +3993,80 @@ WebConsoleConnectionProxy.prototype = {
} }
}, },
/**
* The "networkEvent" 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.
*/
_onNetworkEvent: function WCCP__onNetworkEvent(aType, aPacket)
{
if (this.owner && aPacket.from == this._consoleActor) {
this.owner.handleNetworkEvent(aPacket.eventActor);
}
},
/**
* The "networkEventUpdate" 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.
*/
_onNetworkEventUpdate: function WCCP__onNetworkEvenUpdatet(aType, aPacket)
{
if (this.owner) {
this.owner.handleNetworkEventUpdate(aPacket.from, aPacket.updateType,
aPacket);
}
},
/**
* The "fileActivity" 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.
*/
_onFileActivity: function WCCP__onFileActivity(aType, aPacket)
{
if (this.owner && aPacket.from == this._consoleActor) {
this.owner.handleFileActivity(aPacket.uri);
}
},
/**
* The "locationChange" 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.
*/
_onLocationChange: function WCCP__onLocationChange(aType, aPacket)
{
if (!this.owner || aPacket.from != this._consoleActor) {
return;
}
this.owner.onLocationChange(aPacket.uri, aPacket.title);
if (aPacket.state == "stop" && !aPacket.nativeConsoleAPI) {
this.owner.logWarningAboutReplacedAPI();
}
},
/** /**
* Release an object actor. * Release an object actor.
* *
@ -3898,6 +4095,10 @@ WebConsoleConnectionProxy.prototype = {
this.client.removeListener("pageError", this._onPageError); this.client.removeListener("pageError", this._onPageError);
this.client.removeListener("consoleAPICall", this._onConsoleAPICall); this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
this.client.removeListener("networkEvent", this._onNetworkEvent);
this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
this.client.removeListener("fileActivity", this._onFileActivity);
this.client.removeListener("locationChange", this._onLocationChange);
this.client.close(aOnDisconnect); this.client.close(aOnDisconnect);
this.client = null; this.client = null;

View File

@ -168,6 +168,10 @@ const ThreadStateTypes = {
*/ */
const UnsolicitedNotifications = { const UnsolicitedNotifications = {
"consoleAPICall": "consoleAPICall", "consoleAPICall": "consoleAPICall",
"fileActivity": "fileActivity",
"locationChange": "locationChange",
"networkEvent": "networkEvent",
"networkEventUpdate": "networkEventUpdate",
"newScript": "newScript", "newScript": "newScript",
"tabDetached": "tabDetached", "tabDetached": "tabDetached",
"tabNavigated": "tabNavigated", "tabNavigated": "tabNavigated",

View File

@ -99,8 +99,6 @@ var NetworkHelper =
return conv.ConvertToUnicode(aText); return conv.ConvertToUnicode(aText);
} }
catch (ex) { catch (ex) {
Cu.reportError("NH_convertToUnicode(aText, '" +
aCharset + "') exception: " + ex);
return aText; return aText;
} }
}, },
@ -177,8 +175,24 @@ var NetworkHelper =
readPostTextFromPage: function NH_readPostTextFromPage(aDocShell, aCharset) readPostTextFromPage: function NH_readPostTextFromPage(aDocShell, aCharset)
{ {
let webNav = aDocShell.QueryInterface(Ci.nsIWebNavigation); let webNav = aDocShell.QueryInterface(Ci.nsIWebNavigation);
if (webNav instanceof Ci.nsIWebPageDescriptor) { return this.readPostTextFromPageViaWebNav(webNav, aCharset);
let descriptor = webNav.currentDescriptor; },
/**
* Reads the posted text from the page's cache, given an nsIWebNavigation
* object.
*
* @param nsIWebNavigation aWebNav
* @param string aCharset
* @returns string or null
* Returns the posted string if it was possible to read from
* aWebNav, otherwise null.
*/
readPostTextFromPageViaWebNav:
function NH_readPostTextFromPageViaWebNav(aWebNav, aCharset)
{
if (aWebNav instanceof Ci.nsIWebPageDescriptor) {
let descriptor = aWebNav.currentDescriptor;
if (descriptor instanceof Ci.nsISHEntry && descriptor.postData && if (descriptor instanceof Ci.nsISHEntry && descriptor.postData &&
descriptor instanceof Ci.nsISeekableStream) { descriptor instanceof Ci.nsISeekableStream) {

View File

@ -117,6 +117,143 @@ WebConsoleClient.prototype = {
this._client.request(packet); this._client.request(packet);
}, },
/**
* Set Web Console-related preferences on the server.
*
* @param object aPreferences
* An object with the preferences you want to change.
* @param function [aOnResponse]
* Optional function to invoke when the response is received.
*/
setPreferences: function WCC_setPreferences(aPreferences, aOnResponse)
{
let packet = {
to: this._actor,
type: "setPreferences",
preferences: aPreferences,
};
this._client.request(packet, aOnResponse);
},
/**
* Retrieve the request headers from the given NetworkEventActor.
*
* @param string aActor
* The NetworkEventActor ID.
* @param function aOnResponse
* The function invoked when the response is received.
*/
getRequestHeaders: function WCC_getRequestHeaders(aActor, aOnResponse)
{
let packet = {
to: aActor,
type: "getRequestHeaders",
};
this._client.request(packet, aOnResponse);
},
/**
* Retrieve the request cookies from the given NetworkEventActor.
*
* @param string aActor
* The NetworkEventActor ID.
* @param function aOnResponse
* The function invoked when the response is received.
*/
getRequestCookies: function WCC_getRequestCookies(aActor, aOnResponse)
{
let packet = {
to: aActor,
type: "getRequestCookies",
};
this._client.request(packet, aOnResponse);
},
/**
* Retrieve the request post data from the given NetworkEventActor.
*
* @param string aActor
* The NetworkEventActor ID.
* @param function aOnResponse
* The function invoked when the response is received.
*/
getRequestPostData: function WCC_getRequestPostData(aActor, aOnResponse)
{
let packet = {
to: aActor,
type: "getRequestPostData",
};
this._client.request(packet, aOnResponse);
},
/**
* Retrieve the response headers from the given NetworkEventActor.
*
* @param string aActor
* The NetworkEventActor ID.
* @param function aOnResponse
* The function invoked when the response is received.
*/
getResponseHeaders: function WCC_getResponseHeaders(aActor, aOnResponse)
{
let packet = {
to: aActor,
type: "getResponseHeaders",
};
this._client.request(packet, aOnResponse);
},
/**
* Retrieve the response cookies from the given NetworkEventActor.
*
* @param string aActor
* The NetworkEventActor ID.
* @param function aOnResponse
* The function invoked when the response is received.
*/
getResponseCookies: function WCC_getResponseCookies(aActor, aOnResponse)
{
let packet = {
to: aActor,
type: "getResponseCookies",
};
this._client.request(packet, aOnResponse);
},
/**
* Retrieve the response content from the given NetworkEventActor.
*
* @param string aActor
* The NetworkEventActor ID.
* @param function aOnResponse
* The function invoked when the response is received.
*/
getResponseContent: function WCC_getResponseContent(aActor, aOnResponse)
{
let packet = {
to: aActor,
type: "getResponseContent",
};
this._client.request(packet, aOnResponse);
},
/**
* Retrieve the timing information for the given NetworkEventActor.
*
* @param string aActor
* The NetworkEventActor ID.
* @param function aOnResponse
* The function invoked when the response is received.
*/
getEventTimings: function WCC_getEventTimings(aActor, aOnResponse)
{
let packet = {
to: aActor,
type: "getEventTimings",
};
this._client.request(packet, aOnResponse);
},
/** /**
* Start the given Web Console listeners. * Start the given Web Console listeners.
* *

File diff suppressed because it is too large Load Diff

View File

@ -24,12 +24,18 @@ XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener",
XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPIListener", XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPIListener",
"resource://gre/modules/devtools/WebConsoleUtils.jsm"); "resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ConsoleProgressListener",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "JSTermHelpers", XPCOMUtils.defineLazyModuleGetter(this, "JSTermHelpers",
"resource://gre/modules/devtools/WebConsoleUtils.jsm"); "resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "JSPropertyProvider", XPCOMUtils.defineLazyModuleGetter(this, "JSPropertyProvider",
"resource://gre/modules/devtools/WebConsoleUtils.jsm"); "resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetworkMonitor",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPIStorage", XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPIStorage",
"resource://gre/modules/ConsoleAPIStorage.jsm"); "resource://gre/modules/ConsoleAPIStorage.jsm");
@ -51,6 +57,11 @@ function WebConsoleActor(aConnection, aTabActor)
this._objectActorsPool = new ActorPool(this.conn); this._objectActorsPool = new ActorPool(this.conn);
this.conn.addActorPool(this._objectActorsPool); this.conn.addActorPool(this._objectActorsPool);
this._networkEventActorsPool = new ActorPool(this.conn);
this.conn.addActorPool(this._networkEventActorsPool);
this._prefs = {};
} }
WebConsoleActor.prototype = WebConsoleActor.prototype =
@ -67,10 +78,26 @@ WebConsoleActor.prototype =
* @private * @private
* @type object * @type object
* @see ActorPool * @see ActorPool
* @see WebConsoleObjectActor
* @see this.objectGrip() * @see this.objectGrip()
*/ */
_objectActorsPool: null, _objectActorsPool: null,
/**
* Actor pool for all of the network event actors.
* @private
* @type object
* @see NetworkEventActor
*/
_networkEventActorsPool: null,
/**
* Web Console-related preferences.
* @private
* @type object
*/
_prefs: null,
/** /**
* Tells the current page location associated to the sandbox. When the page * Tells the current page location associated to the sandbox. When the page
* location is changed, we recreate the sandbox. * location is changed, we recreate the sandbox.
@ -108,6 +135,23 @@ WebConsoleActor.prototype =
*/ */
consoleAPIListener: null, consoleAPIListener: null,
/**
* The NetworkMonitor instance.
*/
networkMonitor: null,
/**
* The ConsoleProgressListener instance.
*/
consoleProgressListener: null,
/**
* Getter for the NetworkMonitor.saveRequestAndResponseBodies preference.
* @type boolean
*/
get saveRequestAndResponseBodies()
this._prefs["NetworkMonitor.saveRequestAndResponseBodies"],
actorPrefix: "console", actorPrefix: "console",
grip: function WCA_grip() grip: function WCA_grip()
@ -146,8 +190,18 @@ WebConsoleActor.prototype =
this.consoleAPIListener.destroy(); this.consoleAPIListener.destroy();
this.consoleAPIListener = null; this.consoleAPIListener = null;
} }
if (this.networkMonitor) {
this.networkMonitor.destroy();
this.networkMonitor = null;
}
if (this.consoleProgressListener) {
this.consoleProgressListener.destroy();
this.consoleProgressListener = null;
}
this.conn.removeActorPool(this._objectActorsPool); this.conn.removeActorPool(this._objectActorsPool);
this.conn.removeActorPool(this._networkEventActorsPool);
this._objectActorsPool = null; this._objectActorsPool = null;
this._networkEventActorsPool = null;
this._sandboxLocation = this.sandbox = null; this._sandboxLocation = this.sandbox = null;
this.conn = this._browser = null; this.conn = this._browser = null;
}, },
@ -205,6 +259,21 @@ WebConsoleActor.prototype =
this._objectActorsPool.removeActor(aActor.actorID); this._objectActorsPool.removeActor(aActor.actorID);
}, },
/**
* Release a network event actor.
*
* @param object aActor
* The NetworkEventActor instance you want to release.
*/
releaseNetworkEvent: function WCA_releaseNetworkEvent(aActor)
{
this._networkEventActorsPool.removeActor(aActor.actorID);
},
//////////////////
// Request handlers for known packet types.
//////////////////
/** /**
* Handler for the "startListeners" request. * Handler for the "startListeners" request.
* *
@ -236,6 +305,30 @@ WebConsoleActor.prototype =
} }
startedListeners.push(listener); startedListeners.push(listener);
break; break;
case "NetworkActivity":
if (!this.networkMonitor) {
this.networkMonitor =
new NetworkMonitor(this.window, this);
this.networkMonitor.init();
}
startedListeners.push(listener);
break;
case "FileActivity":
if (!this.consoleProgressListener) {
this.consoleProgressListener =
new ConsoleProgressListener(this._browser, this);
}
this.consoleProgressListener.startMonitor(this.consoleProgressListener.
MONITOR_FILE_ACTIVITY);
startedListeners.push(listener);
break;
case "LocationChange":
if (!this.consoleProgressListener) {
this.consoleProgressListener =
new ConsoleProgressListener(this._browser, this);
}
this.consoleProgressListener.startMonitor(this.consoleProgressListener.
MONITOR_LOCATION_CHANGE);
} }
} }
return { return {
@ -259,7 +352,9 @@ WebConsoleActor.prototype =
// If no specific listeners are requested to be detached, we stop all // If no specific listeners are requested to be detached, we stop all
// listeners. // listeners.
let toDetach = aRequest.listeners || ["PageError", "ConsoleAPI"]; let toDetach = aRequest.listeners ||
["PageError", "ConsoleAPI", "NetworkActivity",
"FileActivity", "LocationChange"];
while (toDetach.length > 0) { while (toDetach.length > 0) {
let listener = toDetach.shift(); let listener = toDetach.shift();
@ -278,6 +373,27 @@ WebConsoleActor.prototype =
} }
stoppedListeners.push(listener); stoppedListeners.push(listener);
break; break;
case "NetworkActivity":
if (this.networkMonitor) {
this.networkMonitor.destroy();
this.networkMonitor = null;
}
stoppedListeners.push(listener);
break;
case "FileActivity":
if (this.consoleProgressListener) {
this.consoleProgressListener.stopMonitor(this.consoleProgressListener.
MONITOR_FILE_ACTIVITY);
}
stoppedListeners.push(listener);
break;
case "LocationChange":
if (this.consoleProgressListener) {
this.consoleProgressListener.stopMonitor(this.consoleProgressListener.
MONITOR_LOCATION_CHANGE);
}
stoppedListeners.push(listener);
break;
} }
} }
@ -409,6 +525,24 @@ WebConsoleActor.prototype =
return {}; return {};
}, },
/**
* The "setPreferences" request handler.
*
* @param object aRequest
* The request message - which preferences need to be updated.
*/
onSetPreferences: function WCA_onSetPreferences(aRequest)
{
for (let key in aRequest.preferences) {
this._prefs[key] = aRequest.preferences[key];
}
return { updated: Object.keys(aRequest.preferences) };
},
//////////////////
// End of request handlers.
//////////////////
/** /**
* Create the JavaScript sandbox where user input is evaluated. * Create the JavaScript sandbox where user input is evaluated.
* @private * @private
@ -474,6 +608,10 @@ WebConsoleActor.prototype =
return result; return result;
}, },
//////////////////
// Event handlers for various listeners.
//////////////////
/** /**
* Handler for page errors received from the PageErrorListener. This method * Handler for page errors received from the PageErrorListener. This method
* sends the nsIScriptError to the remote Web Console client. * sends the nsIScriptError to the remote Web Console client.
@ -521,6 +659,7 @@ WebConsoleActor.prototype =
* Handler for window.console API calls received from the ConsoleAPIListener. * Handler for window.console API calls received from the ConsoleAPIListener.
* This method sends the object to the remote Web Console client. * This method sends the object to the remote Web Console client.
* *
* @see ConsoleAPIListener
* @param object aMessage * @param object aMessage
* The console API call we need to send to the remote client. * The console API call we need to send to the remote client.
*/ */
@ -534,6 +673,85 @@ WebConsoleActor.prototype =
this.conn.send(packet); this.conn.send(packet);
}, },
/**
* Handler for network events. This method is invoked when a new network event
* is about to be recorded.
*
* @see NetworkEventActor
* @see NetworkMonitor from WebConsoleUtils.jsm
*
* @param object aEvent
* The initial network request event information.
* @return object
* A new NetworkEventActor is returned. This is used for tracking the
* network request and response.
*/
onNetworkEvent: function WCA_onNetworkEvent(aEvent)
{
let actor = new NetworkEventActor(aEvent, this);
this._networkEventActorsPool.addActor(actor);
let packet = {
from: this.actorID,
type: "networkEvent",
eventActor: actor.grip(),
};
this.conn.send(packet);
return actor;
},
/**
* Handler for file activity. This method sends the file request information
* to the remote Web Console client.
*
* @see ConsoleProgressListener
* @param string aFileURI
* The requested file URI.
*/
onFileActivity: function WCA_onFileActivity(aFileURI)
{
let packet = {
from: this.actorID,
type: "fileActivity",
uri: aFileURI,
};
this.conn.send(packet);
},
/**
* Handler for location changes. This method sends the new browser location
* to the remote Web Console client.
*
* @see ConsoleProgressListener
* @param string aState
* Tells the location change state:
* - "start" means a load has begun.
* - "stop" means load completed.
* @param string aURI
* The new browser URI.
* @param string aTitle
* The new page title URI.
*/
onLocationChange: function WCA_onLocationChange(aState, aURI, aTitle)
{
// TODO: Bug 792062 - Make the tabNavigated notification reusable by the Web Console
let packet = {
from: this.actorID,
type: "locationChange",
uri: aURI,
title: aTitle,
state: aState,
nativeConsoleAPI: this.hasNativeConsoleAPI(),
};
this.conn.send(packet);
},
//////////////////
// End of event handlers for various listeners.
//////////////////
/** /**
* Prepare a message from the console API to be sent to the remote Web Console * Prepare a message from the console API to be sent to the remote Web Console
* instance. * instance.
@ -604,6 +822,7 @@ WebConsoleActor.prototype.requestTypes =
evaluateJS: WebConsoleActor.prototype.onEvaluateJS, evaluateJS: WebConsoleActor.prototype.onEvaluateJS,
autocomplete: WebConsoleActor.prototype.onAutocomplete, autocomplete: WebConsoleActor.prototype.onAutocomplete,
clearMessagesCache: WebConsoleActor.prototype.onClearMessagesCache, clearMessagesCache: WebConsoleActor.prototype.onClearMessagesCache,
setPreferences: WebConsoleActor.prototype.onSetPreferences,
}; };
/** /**
@ -678,3 +897,375 @@ WebConsoleObjectActor.prototype.requestTypes =
"release": WebConsoleObjectActor.prototype.onRelease, "release": WebConsoleObjectActor.prototype.onRelease,
}; };
/**
* Creates an actor for a network event.
*
* @constructor
* @param object aNetworkEvent
* The network event you want to use the actor for.
* @param object aWebConsoleActor
* The parent WebConsoleActor instance for this object.
*/
function NetworkEventActor(aNetworkEvent, aWebConsoleActor)
{
this.parent = aWebConsoleActor;
this.conn = this.parent.conn;
this._startedDateTime = aNetworkEvent.startedDateTime;
this._request = {
method: aNetworkEvent.method,
url: aNetworkEvent.url,
httpVersion: aNetworkEvent.httpVersion,
headers: [],
cookies: [],
headersSize: aNetworkEvent.headersSize,
postData: {},
};
this._response = {
headers: [],
cookies: [],
content: {},
};
this._timings = {};
this._discardRequestBody = aNetworkEvent.discardRequestBody;
this._discardResponseBody = aNetworkEvent.discardResponseBody;
}
NetworkEventActor.prototype =
{
_request: null,
_response: null,
_timings: null,
actorPrefix: "netEvent",
/**
* Returns a grip for this actor for returning in a protocol message.
*/
grip: function NEA_grip()
{
return {
actor: this.actorID,
startedDateTime: this._startedDateTime,
url: this._request.url,
method: this._request.method,
};
},
/**
* Releases this actor from the pool.
*/
release: function NEA_release()
{
this.parent.releaseNetworkEvent(this);
},
/**
* Handle a protocol request to release a grip.
*/
onRelease: function NEA_onRelease()
{
this.release();
return {};
},
/**
* The "getRequestHeaders" packet type handler.
*
* @return object
* The response packet - network request headers.
*/
onGetRequestHeaders: function NEA_onGetRequestHeaders()
{
return {
from: this.actorID,
headers: this._request.headers,
headersSize: this._request.headersSize,
};
},
/**
* The "getRequestCookies" packet type handler.
*
* @return object
* The response packet - network request cookies.
*/
onGetRequestCookies: function NEA_onGetRequestCookies()
{
return {
from: this.actorID,
cookies: this._request.cookies,
};
},
/**
* The "getRequestPostData" packet type handler.
*
* @return object
* The response packet - network POST data.
*/
onGetRequestPostData: function NEA_onGetRequestPostData()
{
return {
from: this.actorID,
postData: this._request.postData,
postDataDiscarded: this._discardRequestBody,
};
},
/**
* The "getResponseHeaders" packet type handler.
*
* @return object
* The response packet - network response headers.
*/
onGetResponseHeaders: function NEA_onGetResponseHeaders()
{
return {
from: this.actorID,
headers: this._response.headers,
headersSize: this._response.headersSize,
};
},
/**
* The "getResponseCookies" packet type handler.
*
* @return object
* The response packet - network response cookies.
*/
onGetResponseCookies: function NEA_onGetResponseCookies()
{
return {
from: this.actorID,
cookies: this._response.cookies,
};
},
/**
* The "getResponseContent" packet type handler.
*
* @return object
* The response packet - network response content.
*/
onGetResponseContent: function NEA_onGetResponseContent()
{
return {
from: this.actorID,
content: this._response.content,
contentDiscarded: this._discardResponseBody,
};
},
/**
* The "getEventTimings" packet type handler.
*
* @return object
* The response packet - network event timings.
*/
onGetEventTimings: function NEA_onGetEventTimings()
{
return {
from: this.actorID,
timings: this._timings,
totalTime: this._totalTime,
};
},
/******************************************************************
* Listeners for new network event data coming from NetworkMonitor.
******************************************************************/
/**
* Add network request headers.
*
* @param array aHeaders
* The request headers array.
*/
addRequestHeaders: function NEA_addRequestHeaders(aHeaders)
{
this._request.headers = aHeaders;
let packet = {
from: this.actorID,
type: "networkEventUpdate",
updateType: "requestHeaders",
headers: aHeaders.length,
headersSize: this._request.headersSize,
};
this.conn.send(packet);
},
/**
* Add network request cookies.
*
* @param array aCookies
* The request cookies array.
*/
addRequestCookies: function NEA_addRequestCookies(aCookies)
{
this._request.cookies = aCookies;
let packet = {
from: this.actorID,
type: "networkEventUpdate",
updateType: "requestCookies",
cookies: aCookies.length,
};
this.conn.send(packet);
},
/**
* Add network request POST data.
*
* @param object aPostData
* The request POST data.
*/
addRequestPostData: function NEA_addRequestPostData(aPostData)
{
this._request.postData = aPostData;
let packet = {
from: this.actorID,
type: "networkEventUpdate",
updateType: "requestPostData",
dataSize: aPostData.text.length,
discardRequestBody: this._discardRequestBody,
};
this.conn.send(packet);
},
/**
* Add the initial network response information.
*
* @param object aInfo
* The response information.
*/
addResponseStart: function NEA_addResponseStart(aInfo)
{
this._response.httpVersion = aInfo.httpVersion;
this._response.status = aInfo.status;
this._response.statusText = aInfo.statusText;
this._response.headersSize = aInfo.headersSize;
this._discardResponseBody = aInfo.discardResponseBody;
let packet = {
from: this.actorID,
type: "networkEventUpdate",
updateType: "responseStart",
response: aInfo,
};
this.conn.send(packet);
},
/**
* Add network response headers.
*
* @param array aHeaders
* The response headers array.
*/
addResponseHeaders: function NEA_addResponseHeaders(aHeaders)
{
this._response.headers = aHeaders;
let packet = {
from: this.actorID,
type: "networkEventUpdate",
updateType: "responseHeaders",
headers: aHeaders.length,
headersSize: this._response.headersSize,
};
this.conn.send(packet);
},
/**
* Add network response cookies.
*
* @param array aCookies
* The response cookies array.
*/
addResponseCookies: function NEA_addResponseCookies(aCookies)
{
this._response.cookies = aCookies;
let packet = {
from: this.actorID,
type: "networkEventUpdate",
updateType: "responseCookies",
cookies: aCookies.length,
};
this.conn.send(packet);
},
/**
* Add network response content.
*
* @param object aContent
* The response content.
* @param boolean aDiscardedResponseBody
* Tells if the response content was recorded or not.
*/
addResponseContent:
function NEA_addResponseContent(aContent, aDiscardedResponseBody)
{
this._response.content = aContent;
let packet = {
from: this.actorID,
type: "networkEventUpdate",
updateType: "responseContent",
mimeType: aContent.mimeType,
contentSize: aContent.text.length,
discardResponseBody: aDiscardedResponseBody,
};
this.conn.send(packet);
},
/**
* Add network event timing information.
*
* @param number aTotal
* The total time of the network event.
* @param object aTimings
* Timing details about the network event.
*/
addEventTimings: function NEA_addEventTimings(aTotal, aTimings)
{
this._totalTime = aTotal;
this._timings = aTimings;
let packet = {
from: this.actorID,
type: "networkEventUpdate",
updateType: "eventTimings",
totalTime: aTotal,
};
this.conn.send(packet);
},
};
NetworkEventActor.prototype.requestTypes =
{
"release": NetworkEventActor.prototype.onRelease,
"getRequestHeaders": NetworkEventActor.prototype.onGetRequestHeaders,
"getRequestCookies": NetworkEventActor.prototype.onGetRequestCookies,
"getRequestPostData": NetworkEventActor.prototype.onGetRequestPostData,
"getResponseHeaders": NetworkEventActor.prototype.onGetResponseHeaders,
"getResponseCookies": NetworkEventActor.prototype.onGetResponseCookies,
"getResponseContent": NetworkEventActor.prototype.onGetResponseContent,
"getEventTimings": NetworkEventActor.prototype.onGetEventTimings,
};