Bug 573103 - Implement WebConsole Network Panel, r=dietrich, a=blocking2.0 (beta5)

This commit is contained in:
Julian Viereck 2010-08-28 21:31:12 -07:00
parent ec21282fad
commit 48694b9903
14 changed files with 1271 additions and 9 deletions

View File

@ -241,6 +241,15 @@ ResponseListener.prototype =
if (HUDService.lastFinishedRequestCallback) {
HUDService.lastFinishedRequestCallback(this.httpActivity);
}
// Call update on all panels.
this.httpActivity.panels.forEach(function(weakRef) {
let panel = weakRef.get();
if (panel) {
panel.update();
}
});
this.httpActivity.response.isDone = true;
this.httpActivity = null;
},
@ -457,6 +466,471 @@ var NetworkHelper =
// FIREBUG CODE END.
///////////////////////////////////////////////////////////////////////////
//// Helper for creating the network panel.
/**
* Creates a DOMNode and sets all the attributes of aAttributes on the created
* element.
*
* @param nsIDOMDocument aDocument
* Document to create the new DOMNode.
* @param string aTag
* Name of the tag for the DOMNode.
* @param object aAttributes
* Attributes set on the created DOMNode.
*
* @returns nsIDOMNode
*/
function createElement(aDocument, aTag, aAttributes)
{
let node = aDocument.createElement(aTag);
for (var attr in aAttributes) {
node.setAttribute(attr, aAttributes[attr]);
}
return node;
}
/**
* Creates a new DOMNode and appends it to aParent.
*
* @param nsIDOMNode aParent
* A parent node to append the created element.
* @param string aTag
* Name of the tag for the DOMNode.
* @param object aAttributes
* Attributes set on the created DOMNode.
*
* @returns nsIDOMNode
*/
function createAndAppendElement(aParent, aTag, aAttributes)
{
let node = createElement(aParent.ownerDocument, aTag, aAttributes);
aParent.appendChild(node);
return node;
}
///////////////////////////////////////////////////////////////////////////
//// NetworkPanel
/**
* Creates a new NetworkPanel.
*
* @param nsIDOMNode aParent
* Parent node to append the created panel to.
* @param object aHttpActivity
* HttpActivity to display in the panel.
*/
function NetworkPanel(aParent, aHttpActivity)
{
let doc = aParent.ownerDocument;
this.httpActivity = aHttpActivity;
// Create the underlaying panel
this.panel = createElement(doc, "panel", {
label: HUDService.getStr("NetworkPanel.label"),
titlebar: "normal",
noautofocus: "true",
noautohide: "true",
close: "true"
});
// Create the browser that displays the NetworkPanel XHTML.
this.browser = createAndAppendElement(this.panel, "browser", {
src: "chrome://global/content/NetworkPanel.xhtml",
disablehistory: "true",
flex: "1"
});
// Destroy the panel when it's closed.
this.panel.addEventListener("popuphidden", function onPopupHide() {
self.panel.removeEventListener("popuphidden", onPopupHide, false);
self.panel.parentNode.removeChild(self.panel);
self.panel = null;
self.browser = null;
self.document = null;
self.httpActivity = null;
}, false);
// Set the document object and update the content once the panel is loaded.
let self = this;
this.panel.addEventListener("load", function onLoad() {
self.panel.removeEventListener("load", onLoad, true)
self.document = self.browser.contentWindow.document;
self.update();
}, true);
// Create the footer.
let footer = createElement(doc, "hbox", { align: "end" });
createAndAppendElement(footer, "spacer", { flex: 1 });
createAndAppendElement(footer, "resizer", { dir: "bottomend" });
this.panel.appendChild(footer);
aParent.appendChild(this.panel);
}
NetworkPanel.prototype =
{
/**
* The current state of the output.
*/
_state: 0,
/**
* State variables.
*/
_INIT: 0,
_DISPLAYED_REQUEST_HEADER: 1,
_DISPLAYED_REQUEST_BODY: 2,
_DISPLAYED_RESPONSE_HEADER: 3,
_TRANSITION_CLOSED: 4,
/**
* Small helper function that is nearly equal to HUDService.getFormatStr
* except that it prefixes aName with "NetworkPanel.".
*
* @param string aName
* The name of an i10n string to format. This string is prefixed with
* "NetworkPanel." before calling the HUDService.getFormatStr function.
* @param array aArray
* Values used as placeholder for the i10n string.
* @returns string
* The i10n formated string.
*/
_format: function NP_format(aName, aArray)
{
return HUDService.getFormatStr("NetworkPanel." + aName, aArray);
},
/**
*
* @returns boolean
* True if the response is an image, false otherwise.
*/
get _responseIsImage()
{
let response = this.httpActivity.response;
if (!response || !response.header || !response.header["Content-Type"]) {
let request = this.httpActivity.request;
if (request.header["Accept"] &&
request.header["Accept"].indexOf("image/") != -1) {
return true;
}
else {
return false;
}
}
return response.header["Content-Type"].indexOf("image/") != -1;
},
/**
*
* @returns boolean
* Returns true if the server responded that the request is already
* in the browser's cache, false otherwise.
*/
get _isResponseCached()
{
return this.httpActivity.response.status.indexOf("304") != -1;
},
/**
* Appends the node with id=aId by the text aValue.
*
* @param string aId
* @param string aValue
* @returns void
*/
_appendTextNode: function NP_appendTextNode(aId, aValue)
{
let textNode = this.document.createTextNode(aValue);
this.document.getElementById(aId).appendChild(textNode);
},
/**
* Generates some HTML to display the key-value pair of the aList data. The
* generated HTML is added to node with id=aParentId.
*
* @param string aParentId
* Id of the parent node to append the list to.
* @oaram object aList
* Object that holds the key-value information to display in aParentId.
* @param boolean aIgnoreCookie
* If true, the key-value named "Cookie" is not added to the list.
* @returns void
*/
_appendList: function NP_appendList(aParentId, aList, aIgnoreCookie)
{
let parent = this.document.getElementById(aParentId);
let doc = this.document;
let sortedList = {};
Object.keys(aList).sort().forEach(function(aKey) {
sortedList[aKey] = aList[aKey];
});
for (let key in sortedList) {
if (aIgnoreCookie && key == "Cookie") {
continue;
}
/**
* The following code creates the HTML:
*
* <span class="property-name">${line}:</span>
* <span class="property-value">${aList[line]}</span><br>
*
* and adds it to parent.
*/
let textNode = doc.createTextNode(key + ":");
let span = doc.createElement("span");
span.setAttribute("class", "property-name");
span.appendChild(textNode);
parent.appendChild(span);
textNode = doc.createTextNode(sortedList[key]);
span = doc.createElement("span");
span.setAttribute("class", "property-value");
span.appendChild(textNode);
parent.appendChild(span);
parent.appendChild(doc.createElement("br"));
}
},
/**
* Displays the node with id=aId.
*
* @param string aId
* @returns void
*/
_displayNode: function NP_displayNode(aId)
{
this.document.getElementById(aId).style.display = "block";
},
/**
* Sets the request URL, request method, the timing information when the
* request started and the request header content on the NetworkPanel.
* If the request header contains cookie data, a list of sent cookies is
* generated and a special sent cookie section is displayed + the cookie list
* added to it.
*
* @returns void
*/
_displayRequestHeader: function NP_displayRequestHeader()
{
let timing = this.httpActivity.timing;
let request = this.httpActivity.request;
this._appendTextNode("headUrl", this.httpActivity.url);
this._appendTextNode("headMethod", this.httpActivity.method);
this._appendTextNode("requestHeadersInfo",
ConsoleUtils.timestampString(timing.REQUEST_HEADER/1000));
this._appendList("requestHeadersContent", request.header, true);
if ("Cookie" in request.header) {
this._displayNode("requestCookie");
let cookies = request.header.Cookie.split(";");
let cookieList = {};
let cookieListSorted = {};
cookies.forEach(function(cookie) {
let name, value;
[name, value] = cookie.trim().split("=");
cookieList[name] = value;
});
this._appendList("requestCookieContent", cookieList);
}
},
/**
* Displays the request body section of the NetworkPanel and set the request
* body content on the NetworkPanel.
*
* @returns void
*/
_displayRequestBody: function NP_displayRequestBody() {
this._displayNode("requestBody");
this._appendTextNode("requestBodyContent", this.httpActivity.request.body);
},
/**
* Displays the response section of the NetworkPanel, sets the response status,
* the duration between the start of the request and the receiving of the
* response header as well as the response header content on the the NetworkPanel.
*
* @returns void
*/
_displayResponseHeader: function NP_displayResponseHeader()
{
let timing = this.httpActivity.timing;
let response = this.httpActivity.response;
this._appendTextNode("headStatus", response.status);
let deltaDuration =
Math.round((timing.RESPONSE_HEADER - timing.REQUEST_HEADER) / 1000);
this._appendTextNode("responseHeadersInfo",
this._format("durationMS", [deltaDuration]));
this._displayNode("responseContainer");
this._appendList("responseHeadersContent", response.header);
},
/**
* Displays the respones image section, sets the source of the image displayed
* in the image response section to the request URL and the duration between
* the receiving of the response header and the end of the request. Once the
* image is loaded, the size of the requested image is set.
*
* @returns void
*/
_displayResponseImage: function NP_displayResponseImage()
{
let self = this;
let timing = this.httpActivity.timing;
let response = this.httpActivity.response;
let cached = "";
if (this._isResponseCached) {
cached = "Cached";
}
let imageNode = this.document.getElementById("responseImage" + cached +"Node");
imageNode.setAttribute("src", this.httpActivity.url);
// This function is called to set the imageInfo.
function setImageInfo() {
let deltaDuration =
Math.round((timing.RESPONSE_COMPLETE - timing.RESPONSE_HEADER) / 1000);
self._appendTextNode("responseImage" + cached + "Info",
self._format("imageSizeDeltaDurationMS", [
imageNode.width, imageNode.height, deltaDuration
]
));
}
// Check if the image is already loaded.
if (imageNode.width != 0) {
setImageInfo();
}
else {
// Image is not loaded yet therefore add a load event.
imageNode.addEventListener("load", function imageNodeLoad() {
imageNode.removeEventListener("load", imageNodeLoad, false);
setImageInfo();
}, false);
}
this._displayNode("responseImage" + cached);
},
/**
* Displays the response body section, sets the the duration between
* the receiving of the response header and the end of the request as well as
* the content of the response body on the NetworkPanel.
*
* @returns void
*/
_displayResponseBody: function NP_displayResponseBody()
{
let timing = this.httpActivity.timing;
let response = this.httpActivity.response;
let deltaDuration =
Math.round((timing.RESPONSE_COMPLETE - timing.RESPONSE_HEADER) / 1000);
this._appendTextNode("responseBodyInfo",
this._format("durationMS", [deltaDuration]));
this._displayNode("responseBody");
this._appendTextNode("responseBodyContent", response.body);
},
/**
* Displays the `no response body` section and sets the the duration between
* the receiving of the response header and the end of the request.
*
* @returns void
*/
_displayNoResponseBody: function NP_displayNoResponseBody()
{
let timing = this.httpActivity.timing;
this._displayNode("responseNoBody");
let deltaDuration =
Math.round((timing.RESPONSE_COMPLETE - timing.RESPONSE_HEADER) / 1000);
this._appendTextNode("responseNoBodyInfo",
this._format("durationMS", [deltaDuration]));
},
/**
* Updates the content of the NetworkPanel's browser.
*
* @returns void
*/
update: function NP_update()
{
/**
* After the browser contentWindow is ready, the document object is set.
* If the document object isn't set yet, then the page is loaded and nothing
* can be updated.
*/
if (!this.document) {
return;
}
let timing = this.httpActivity.timing;
let request = this.httpActivity.request;
let response = this.httpActivity.response;
switch (this._state) {
case this._INIT:
this._displayRequestHeader();
this._state = this._DISPLAYED_REQUEST_HEADER;
// FALL THROUGH
case this._DISPLAYED_REQUEST_HEADER:
// Process the request body if there is one.
if (request.body) {
this._displayRequestBody();
this._state = this._DISPLAYED_REQUEST_BODY;
}
// FALL THROUGH
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.header) {
break
}
this._displayResponseHeader();
this._state = this._DISPLAYED_RESPONSE_HEADER;
// FALL THROUGH
case this._DISPLAYED_RESPONSE_HEADER:
// Check if the transition is done.
if (timing.TRANSACTION_CLOSE && response.isDone) {
if (this._responseIsImage) {
this._displayResponseImage();
}
else if (response.body) {
this._displayResponseBody();
}
else {
this._displayNoResponseBody();
}
this._state = this._TRANSITION_CLOSED;
}
break;
}
}
}
function HUD_SERVICE()
{
// TODO: provide mixins for FENNEC: bug 568621
@ -1008,6 +1482,11 @@ HUD_SERVICE.prototype =
*/
unregisterDisplay: function HS_unregisterDisplay(aId)
{
// Remove children from the output. If the output is not cleared, there can
// be leaks as some nodes has node.onclick = function; set and GC can't
// remove the nodes then.
HUDService.clearDisplay(aId);
// remove HUD DOM node and
// remove display references from local registries get the outputNode
var outputNode = this.mixins.getOutputNodeById(aId);
@ -1366,6 +1845,28 @@ HUD_SERVICE.prototype =
*/
lastFinishedRequestCallback: null,
/**
* Opens a NetworkPanel.
*
* @param nsIDOMNode aNode
* DOMNode to display the panel next to.
* @param object aHttpActivity
* httpActivity object. The data of this object is displayed in the
* NetworkPanel.
* @returns NetworkPanel
*/
openNetworkPanel: function (aNode, aHttpActivity) {
let doc = aNode.ownerDocument;
let parent = doc.getElementById("mainPopupSet");
let netPanel = new NetworkPanel(parent, aHttpActivity);
let panel = netPanel.panel;
panel.openPopup(aNode, "after_pointer", 0, 0, false, false);
panel.sizeTo(350, 400);
aHttpActivity.panels.push(Cu.getWeakReference(netPanel));
return netPanel;
},
/**
* Begin observing HTTP traffic that we care about,
* namely traffic that originates inside any context that a Heads Up Display
@ -1413,6 +1914,7 @@ HUD_SERVICE.prototype =
method: aChannel.requestMethod,
channel: aChannel,
panels: [],
request: {
header: { }
},
@ -1450,6 +1952,13 @@ HUD_SERVICE.prototype =
// Store the loggedNode and the httpActivity object for later reuse.
httpActivity.messageObject = loggedNode;
self.openRequests[httpActivity.id] = httpActivity;
// Make the network span clickable.
let linkNode = loggedNode.messageNode;
linkNode.setAttribute("aria-haspopup", "true");
linkNode.onclick = function() {
self.openNetworkPanel(linkNode, httpActivity);
}
}
else {
// Iterate over all currently ongoing requests. If aChannel can't
@ -1467,7 +1976,7 @@ HUD_SERVICE.prototype =
return;
}
let msgObject;
let msgObject, updatePanel = false;
let data, textNode;
// Store the time information for this activity subtype.
httpActivity.timing[transCodes[aActivitySubtype]] = aTimestamp;
@ -1481,7 +1990,7 @@ HUD_SERVICE.prototype =
if (!sentBody) {
// If the request URL is the same as the current page url, then
// we can try to get the posted text from the page directly.
// This is necessary as otherwise the
// This check is necessary as otherwise the
// NetworkHelper.readPostTextFromPage
// function is called for image requests as well but these
// are not web pages and as such don't store the posted text
@ -1549,8 +2058,18 @@ HUD_SERVICE.prototype =
self.getFormatStr("networkUrlWithStatusAndDuration", data)));
delete self.openRequests[item.id];
updatePanel = true;
break;
}
if (updatePanel) {
httpActivity.panels.forEach(function(weakRef) {
let panel = weakRef.get();
if (panel) {
panel.update();
}
});
}
}
}
},

View File

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
<!ENTITY % webConsoleDTD SYSTEM "chrome://global/locale/webConsole.dtd" >
%webConsoleDTD;
]>
<!-- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is DevTools WebConsole Code.
-
- The Initial Developer of the Original Code is
- Mozilla Foundation.
- Portions created by the Initial Developer are Copyright (C) 2010
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- Joe Walker <jwalker@mozilla.com>
- Julian Viereck <jviereck@mozilla.com>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the LGPL or the GPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="chrome://global/skin/webConsole_networkPanel.css" type="text/css"/>
</head>
<body role="application">
<div id="header">
<span class="property-name">&networkPanel.requestURL;:</span>
<span class="property-value" id="headUrl"></span><br />
<span class="property-name">&networkPanel.requestMethod;:</span>
<span class="property-value" id="headMethod"></span><br />
<span class="property-name">&networkPanel.statusCode;:</span>
<span class="property-value" id="headStatus"></span>
</div>
<div class="group">
<h1>
&networkPanel.requestHeaders;
<span id="requestHeadersInfo" class="info"></span>
</h1>
<div class="property-header" id="requestHeadersContent"></div>
<div id="requestCookie" style="display:none">
<h1>&networkPanel.requestCookie;</h1>
<div class="property-header" id="requestCookieContent"></div>
</div>
<div id="requestBody" style="display:none">
<h1>&networkPanel.requestBody;</h1>
<div class="property-header" id="requestBodyContent"></div>
</div>
</div>
<div class="group" id="responseContainer" style="display:none">
<h1>
&networkPanel.responseHeaders;
<span id="responseHeadersInfo" class="info">&Delta;</span>
</h1>
<div class="property-header" id="responseHeadersContent"></div>
<div id="responseBody" style="display:none">
<h1>
&networkPanel.responseBody;
<span class="info" id="responseBodyInfo">&Delta;</span>
</h1>
<div class="property-header" id="responseBodyContent"></div>
</div>
<div id="responseNoBody" style="display:none">
<h1>
&networkPanel.responseNoBody;
<span id="responseNoBodyInfo" class="info">&Delta;</span>
</h1>
</div>
<div id="responseImage" style="display:none">
<h1>
&networkPanel.responseImage;
<span id="responseImageInfo" class="info"></span>
</h1>
<div id="responseImageNodeDiv">
<img id="responseImageNode" />
</div>
</div>
<div id="responseImageCached" style="display:none">
<h1>
&networkPanel.responseImageCached;
<span id="responseImageCachedInfo" class="info"></span>
</h1>
<div id="responseImageNodeDiv">
<img id="responseImageCachedNode" />
</div>
</div>
</div>
</body>
</html>

View File

@ -70,6 +70,8 @@ const TEST_ERROR_URI = "http://example.com/browser/toolkit/components/console/hu
const TEST_DUPLICATE_ERROR_URI = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-duplicate-error.html";
const TEST_IMG = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-image.png";
function noCacheUriSpec(aUriSpec) {
return aUriSpec + "?_=" + Date.now();
}
@ -569,6 +571,254 @@ function testConsoleHistory()
is (input.value, executeList[idxLast], "check history next idx:" + idxLast);
}
function testNetworkPanel()
{
function checkIsVisible(aPanel, aList) {
for (let id in aList) {
let node = aPanel.document.getElementById(id);
let isVisible = aList[id];
is(node.style.display, (isVisible ? "block" : "none"), id + " isVisible=" + isVisible);
}
}
function checkNodeContent(aPanel, aId, aContent) {
let node = aPanel.document.getElementById(aId);
if (node == null) {
ok(false, "Tried to access node " + aId + " that doesn't exist!");
}
else if (node.textContent.indexOf(aContent) != -1) {
ok(true, "checking content of " + aId);
}
else {
ok(false, "Got false value for " + aId + ": " + node.textContent + " doesn't have " + aContent);
}
}
function checkNodeKeyValue(aPanel, aId, aKey, aValue) {
let node = aPanel.document.getElementById(aId);
let testHTML = '<span xmlns="http://www.w3.org/1999/xhtml" class="property-name">' + aKey + ':</span>';
testHTML += '<span xmlns="http://www.w3.org/1999/xhtml" class="property-value">' + aValue + '</span>';
isnot(node.innerHTML.indexOf(testHTML), -1, "checking content of " + aId);
}
let testDriver;
function testGen() {
var httpActivity = {
url: "http://www.testpage.com",
method: "GET",
panels: [],
request: {
header: {
foo: "bar"
}
},
response: { },
timing: {
"REQUEST_HEADER": 0
}
};
let networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity);
is (networkPanel, httpActivity.panels[0].get(), "Network panel stored on httpActivity object");
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}, true);
yield;
checkIsVisible(networkPanel, {
requestCookie: false,
requestBody: false,
responseContainer: false,
responseBody: false,
responseNoBody: false,
responseImage: false,
responseImageCached: false
});
checkNodeContent(networkPanel, "header", "http://www.testpage.com");
checkNodeContent(networkPanel, "header", "GET");
checkNodeKeyValue(networkPanel, "requestHeadersContent", "foo", "bar");
// Test request body.
httpActivity.request.body = "hello world";
networkPanel.update();
checkIsVisible(networkPanel, {
requestBody: true,
requestCookie: false,
responseContainer: false,
responseBody: false,
responseNoBody: false,
responseImage: false,
responseImageCached: false
});
checkNodeContent(networkPanel, "requestBodyContent", "hello world");
// Test response header.
httpActivity.timing.RESPONSE_HEADER = 1000;
httpActivity.response.status = "999 earthquake win";
httpActivity.response.header = {
leaveHouses: "true"
}
networkPanel.update();
checkIsVisible(networkPanel, {
requestBody: true,
requestCookie: false,
responseContainer: true,
responseBody: false,
responseNoBody: false,
responseImage: false,
responseImageCached: false
});
checkNodeContent(networkPanel, "header", "999 earthquake win");
checkNodeKeyValue(networkPanel, "responseHeadersContent", "leaveHouses", "true");
checkNodeContent(networkPanel, "responseHeadersInfo", "1ms");
httpActivity.timing.RESPONSE_COMPLETE = 2500;
// This is necessary to show that the request is done.
httpActivity.timing.TRANSACTION_CLOSE = 2500;
networkPanel.update();
checkIsVisible(networkPanel, {
requestBody: true,
requestCookie: false,
responseContainer: true,
responseBody: false,
responseNoBody: false,
responseImage: false,
responseImageCached: false
});
httpActivity.response.isDone = true;
networkPanel.update();
checkNodeContent(networkPanel, "responseNoBodyInfo", "2ms");
checkIsVisible(networkPanel, {
requestBody: true,
requestCookie: false,
responseContainer: true,
responseBody: false,
responseNoBody: true,
responseImage: false,
responseImageCached: false
});
networkPanel.panel.hidePopup();
// Second run: Test for cookies and response body.
httpActivity.request.header.Cookie = "foo=bar; hello=world";
httpActivity.response.body = "get out here";
networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity);
is (networkPanel, httpActivity.panels[1].get(), "Network panel stored on httpActivity object");
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}, true);
yield;
checkIsVisible(networkPanel, {
requestBody: true,
requestCookie: true,
responseContainer: true,
responseBody: true,
responseNoBody: false,
responseImage: false,
responseImageCached: false
});
checkNodeKeyValue(networkPanel, "requestCookieContent", "foo", "bar");
checkNodeKeyValue(networkPanel, "requestCookieContent", "hello", "world");
checkNodeContent(networkPanel, "responseBodyContent", "get out here");
checkNodeContent(networkPanel, "responseBodyInfo", "2ms");
networkPanel.panel.hidePopup();
// Check image request.
httpActivity.response.header["Content-Type"] = "image/png";
httpActivity.url = TEST_IMG;
networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}, true);
yield;
checkIsVisible(networkPanel, {
requestBody: true,
requestCookie: true,
responseContainer: true,
responseBody: false,
responseNoBody: false,
responseImage: true,
responseImageCached: false
});
let imgNode = networkPanel.document.getElementById("responseImageNode");
is(imgNode.getAttribute("src"), TEST_IMG, "Displayed image is correct");
function checkImageResponseInfo() {
checkNodeContent(networkPanel, "responseImageInfo", "2ms");
checkNodeContent(networkPanel, "responseImageInfo", "16x16px");
}
// Check if the image is loaded already.
if (imgNode.width == 0) {
imgNode.addEventListener("load", function onLoad() {
imgNode.removeEventListener("load", onLoad, false);
checkImageResponseInfo();
networkPanel.panel.hidePopup();
testDriver.next();
}, false);
// Wait until the image is loaded.
yield;
}
else {
checkImageResponseInfo();
networkPanel.panel.hidePopup();
}
// Check cached image request.
httpActivity.response.status = "HTTP/1.1 304 Not Modified";
networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}, true);
yield;
checkIsVisible(networkPanel, {
requestBody: true,
requestCookie: true,
responseContainer: true,
responseBody: false,
responseNoBody: false,
responseImage: false,
responseImageCached: true
});
let imgNode = networkPanel.document.getElementById("responseImageCachedNode");
is(imgNode.getAttribute("src"), TEST_IMG, "Displayed image is correct");
networkPanel.panel.hidePopup();
// Run the next test.
testErrorOnPageReload();
yield;
};
testDriver = testGen();
testDriver.next();
}
// test property provider
function testPropertyProvider()
{
@ -829,7 +1079,7 @@ function testPageReload() {
is(typeof console.error, "function", "console.error is a function");
is(typeof console.exception, "function", "console.exception is a function");
testErrorOnPageReload();
testNetworkPanel();
}, false);
content.location.reload();

View File

@ -17,6 +17,8 @@ Cu.import("resource://gre/modules/HUDService.jsm");
const TEST_NETWORK_REQUEST_URI = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-network-request.html";
const TEST_IMG = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-image.png";
const TEST_DATA_JSON_CONTENT =
'{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] }';
@ -29,7 +31,7 @@ function testOpenWebConsole()
is(HUDService.displaysIndex().length, 1, "WebConsole was opened");
hudId = HUDService.displaysIndex()[0];
hud = HUDService.hudWeakReferences[hudId].get();
hud = HUDService.getHeadsUpDisplay(hudId);
testNetworkLogging();
}
@ -145,11 +147,24 @@ function testNetworkLogging()
lastFinishedRequest = null
// All tests are done. Shutdown.
browser = null;
lastFinishedRequest = null;
HUDService.lastFinishedRequestCallback = null;
finishTest();
// Open the NetworkPanel. The functionality of the NetworkPanel is tested
// within the testNetworkPanel() function.
let filterBox = hud.querySelectorAll(".hud-filter-box")[0];
let networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity);
is (networkPanel, httpActivity.panels[0].get(), "Network panel stored on httpActivity object");
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
ok(true, "NetworkPanel was opened");
networkPanel.panel.hidePopup();
// All tests are done. Shutdown.
browser = null;
lastFinishedRequest = null;
HUDService.lastFinishedRequestCallback = null;
finishTest();
}, true);
}
loggingGen = loggingGeneratorFunc();

View File

@ -1,5 +1,6 @@
toolkit.jar:
*+ content/global/console.js (content/console.js)
*+ content/global/console.xul (content/console.xul)
+ content/global/NetworkPanel.xhtml (hudservice/NetworkPanel.xhtml)
+ content/global/console.css (content/console.css)
+ content/global/consoleBindings.xml (content/consoleBindings.xml)

View File

@ -84,3 +84,18 @@ networkUrlWithStatus=%1$S [%2$S
# %2$S = response status code from the server (e.g. `HTTP/1.1 200 OK`)
# %3$S = duration for the complete network request in milliseconds
networkUrlWithStatusAndDuration=%1$S [%2$S %3$Sms]
NetworkPanel.label=Inspect Network Request
# LOCALIZATION NOTE (NetworkPanel.deltaDurationMS):
#
# This string is used to show the duration between two network events (e.g
# request and respones header or response header and response body).
NetworkPanel.durationMS=%Sms
# LOCALIZATION NOTE (NetworkPanel.imageSizeDeltaDurationMS):
# This string is used to show the duration between the response header and the
# response body event. It also shows the size of the received or cached image.
#
# The first %S is replace by the width of the inspected image.
# The second %S is replaced by the height of the inspected image.
# The third %S is replaced by the duration between the response header and the
# response body event.
NetworkPanel.imageSizeDeltaDurationMS=%Sx%Spx, Δ%Sms

View File

@ -0,0 +1,12 @@
<!ENTITY networkPanel.requestURL "Request URL">
<!ENTITY networkPanel.requestMethod "Request Method">
<!ENTITY networkPanel.statusCode "Status Code">
<!ENTITY networkPanel.requestHeaders "Request Headers">
<!ENTITY networkPanel.requestCookie "Sent Cookie">
<!ENTITY networkPanel.requestBody "Request Body">
<!ENTITY networkPanel.responseHeaders "Response Headers">
<!ENTITY networkPanel.responseBody "Response Body">
<!ENTITY networkPanel.responseNoBody "No Response Body">
<!ENTITY networkPanel.responseImage "Received Image">
<!ENTITY networkPanel.responseImageCached "Cached Image">

View File

@ -37,6 +37,7 @@
+ locale/@AB_CD@/global/finddialog.properties (%chrome/global/finddialog.properties)
locale/@AB_CD@/global/globalKeys.dtd (%chrome/global/globalKeys.dtd)
+ locale/@AB_CD@/global/headsUpDisplay.properties (%chrome/global/headsUpDisplay.properties)
+ locale/@AB_CD@/global/webConsole.dtd (%chrome/global/webConsole.dtd)
+ locale/@AB_CD@/global/intl.css (%chrome/global/intl.css)
+ locale/@AB_CD@/global/intl.properties (%chrome/global/intl.properties)
+ locale/@AB_CD@/global/keys.properties (%chrome/global/keys.properties)

View File

@ -28,6 +28,7 @@ toolkit.jar:
+ skin/classic/global/toolbarbutton.css
+ skin/classic/global/tree.css
+ skin/classic/global/webConsole.css
+ skin/classic/global/webConsole_networkPanel.css
+ skin/classic/global/alerts/alert.css (alerts/alert.css)
+ skin/classic/global/console/console.css (console/console.css)
+ skin/classic/global/console/console.png (console/console.png)

View File

@ -0,0 +1,108 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is DevTools code
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Joe Walker <jwalker@mozilla.com>
* Julian Viereck <jviereck@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
body {
font-family: Lucida Grande, sans-serif;
font-size: 11px;
background: #EEE;
}
div#header {
padding: 5px;
overflow-x:auto;
}
h1 {
font-size: 13px;
padding: 2px 10px;
margin: 0px;
background: -moz-linear-gradient(top, #BBB, #999);
-moz-border-radius: 2px;
text-shadow: #FFF 0px 1px 0px;
}
h1 .info {
font-size: 11px;
float: right;
color: #333;
padding-right: 3px;
}
div.property-header {
padding: 2px 5px;
background: -moz-linear-gradient(top, #FFF, #F8F8F8);
color: #333;
max-height: 330px;
overflow-y: auto;
overflow-x: auto;
white-space: pre-wrap;
}
span.property-name {
font-size: 11px;
font-weight: bold;
padding-right: 4px;
color: #000;
}
span.property-value {
padding-right: 5px;
font-size: 11px;
}
div.group {
margin-top: 10px;
}
div.group, div#header {
background: #FFF;
border-color: #E1E1E1;
border-style: solid;
border-width: 1px;
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
-moz-border-radius: 4px 4px 4px 4px;
}
img#responseImageNode {
-moz-box-shadow: rgba(0,0,0,0.2) 0px 3px 5px;
max-width: 100%;
}
#responseImageNodeDiv {
padding: 5px;
}

View File

@ -50,6 +50,7 @@ toolkit.jar:
skin/classic/global/tree.css
* skin/classic/global/viewbuttons.css
* skin/classic/global/webConsole.css
* skin/classic/global/webConsole_networkPanel.css
skin/classic/global/wizard.css
skin/classic/global/arrow/arrow-dn-dis.gif (arrow/arrow-dn-dis.gif)
skin/classic/global/arrow/arrow-dn-dis.png (arrow/arrow-dn-dis.png)

View File

@ -0,0 +1,108 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is DevTools code
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Joe Walker <jwalker@mozilla.com>
* Julian Viereck <jviereck@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
body {
font-family: Lucida Grande, sans-serif;
font-size: 11px;
background: #EEE;
}
div#header {
padding: 5px;
overflow-x:auto;
}
h1 {
font-size: 13px;
padding: 2px 10px;
margin: 0px;
background: -moz-linear-gradient(top, #BBB, #999);
-moz-border-radius: 2px;
text-shadow: #FFF 0px 1px 0px;
}
h1 .info {
font-size: 11px;
float: right;
color: #333;
padding-right: 3px;
}
div.property-header {
padding: 2px 5px;
background: -moz-linear-gradient(top, #FFF, #F8F8F8);
color: #333;
max-height: 330px;
overflow-y: auto;
overflow-x: auto;
white-space: pre-wrap;
}
span.property-name {
font-size: 11px;
font-weight: bold;
padding-right: 4px;
color: #000;
}
span.property-value {
padding-right: 5px;
font-size: 11px;
}
div.group {
margin-top: 10px;
}
div.group, div#header {
background: #FFF;
border-color: #E1E1E1;
border-style: solid;
border-width: 1px;
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
-moz-border-radius: 4px 4px 4px 4px;
}
img#responseImageNode {
-moz-box-shadow: rgba(0,0,0,0.2) 0px 3px 5px;
max-width: 100%;
}
#responseImageNodeDiv {
padding: 5px;
}

View File

@ -54,6 +54,7 @@ toolkit.jar:
skin/classic/global/toolbarbutton.css
skin/classic/global/tree.css
* skin/classic/global/webConsole.css
* skin/classic/global/webConsole_networkPanel.css
skin/classic/global/wizard.css
skin/classic/global/alerts/alert.css (alerts/alert.css)
skin/classic/global/arrow/arrow-dn.gif (arrow/arrow-dn.gif)
@ -222,6 +223,7 @@ toolkit.jar:
* skin/classic/aero/global/toolbarbutton.css (toolbarbutton-aero.css)
* skin/classic/aero/global/tree.css (tree-aero.css)
* skin/classic/aero/global/webConsole.css
* skin/classic/global/webConsole_networkPanel.css
skin/classic/aero/global/wizard.css
skin/classic/aero/global/alerts/alert.css (alerts/alert.css)
skin/classic/aero/global/arrow/arrow-dn.gif (arrow/arrow-dn.gif)

View File

@ -0,0 +1,108 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is DevTools code
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Joe Walker <jwalker@mozilla.com>
* Julian Viereck <jviereck@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
body {
font-family: Lucida Grande, sans-serif;
font-size: 11px;
background: #EEE;
}
div#header {
padding: 5px;
overflow-x:auto;
}
h1 {
font-size: 13px;
padding: 2px 10px;
margin: 0px;
background: -moz-linear-gradient(top, #BBB, #999);
-moz-border-radius: 2px;
text-shadow: #FFF 0px 1px 0px;
}
h1 .info {
font-size: 11px;
float: right;
color: #333;
padding-right: 3px;
}
div.property-header {
padding: 2px 5px;
background: -moz-linear-gradient(top, #FFF, #F8F8F8);
color: #333;
max-height: 330px;
overflow-y: auto;
overflow-x: auto;
white-space: pre-wrap;
}
span.property-name {
font-size: 11px;
font-weight: bold;
padding-right: 4px;
color: #000;
}
span.property-value {
padding-right: 5px;
font-size: 11px;
}
div.group {
margin-top: 10px;
}
div.group, div#header {
background: #FFF;
border-color: #E1E1E1;
border-style: solid;
border-width: 1px;
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
-moz-border-radius: 4px 4px 4px 4px;
}
img#responseImageNode {
-moz-box-shadow: rgba(0,0,0,0.2) 0px 3px 5px;
max-width: 100%;
}
#responseImageNodeDiv {
padding: 5px;
}