Bug 573103 - Implement WebConsole Network Panel, r=dietrich, a=blocking2.0 (beta5)
authorJulian Viereck <jviereck@mozilla.com>
Sat, 28 Aug 2010 21:31:12 -0700
changeset 51709 e39d03333cdf3085013809182a8a2e4de978049b
parent 51708 1ac4f2b24a913ce3a15d3b928b3a727bb3bc7d61
child 51710 ce7602d990f91b2f0d7da23b09614b1866b816c4
push idunknown
push userunknown
push dateunknown
reviewersdietrich, blocking2.0
bugs573103
milestone2.0b5pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 573103 - Implement WebConsole Network Panel, r=dietrich, a=blocking2.0 (beta5)
toolkit/components/console/hudservice/HUDService.jsm
toolkit/components/console/hudservice/NetworkPanel.xhtml
toolkit/components/console/hudservice/tests/browser/browser_HUDServiceTestsAll.js
toolkit/components/console/hudservice/tests/browser/browser_webconsole_netlogging.js
toolkit/components/console/jar.mn
toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
toolkit/locales/en-US/chrome/global/webConsole.dtd
toolkit/locales/jar.mn
toolkit/themes/gnomestripe/global/jar.mn
toolkit/themes/gnomestripe/global/webConsole_networkPanel.css
toolkit/themes/pinstripe/global/jar.mn
toolkit/themes/pinstripe/global/webConsole_networkPanel.css
toolkit/themes/winstripe/global/jar.mn
toolkit/themes/winstripe/global/webConsole_networkPanel.css
--- a/toolkit/components/console/hudservice/HUDService.jsm
+++ b/toolkit/components/console/hudservice/HUDService.jsm
@@ -236,16 +236,25 @@ ResponseListener.prototype =
     this.originalListener.onStopRequest(aRequest, aContext, aStatusCode);
 
     this.setResponseHeader(aRequest);
     this.httpActivity.response.body = this.receivedData;
 
     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;
   },
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIStreamListener,
     Ci.nsISupports
   ])
 }
@@ -452,16 +461,481 @@ var NetworkHelper =
     }
 
     return null;
    }
 }
 
 // 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
   if (appName() == "FIREFOX") {
     var mixins = new FirefoxApplicationHooks();
   }
   else {
     throw new Error("Unsupported Application");
@@ -1003,16 +1477,21 @@ HUD_SERVICE.prototype =
   /**
    * When a display is being destroyed, unregister it first
    *
    * @param string aId
    * @returns void
    */
   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);
     var parent = outputNode.parentNode;
     var splitters = parent.querySelectorAll("splitter");
     var len = splitters.length;
     for (var i = 0; i < len; i++) {
       if (splitters[i].getAttribute("class") == "hud-splitter") {
@@ -1362,16 +1841,38 @@ HUD_SERVICE.prototype =
 
   /**
    * Assign a function to this property to listen for finished httpRequests.
    * Used by unit tests.
    */
   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
    * is active for.
    */
   startHTTPObservation: function HS_httpObserverFactory()
   {
     // creates an observer for http traffic
     var self = this;
@@ -1408,16 +1909,17 @@ HUD_SERVICE.prototype =
             // this request and later response.
             let httpActivity = {
               id: self.sequenceId(),
               hudId: hudId,
               url: aChannel.URI.spec,
               method: aChannel.requestMethod,
               channel: aChannel,
 
+              panels: [],
               request: {
                 header: { }
               },
               response: {
                 header: null
               },
               timing: {
                 "REQUEST_HEADER": aTimestamp
@@ -1445,16 +1947,23 @@ HUD_SERVICE.prototype =
               visitHeader: function(aName, aValue) {
                 httpActivity.request.header[aName] = aValue;
               }
             });
 
             // 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
             // be found within them, then exit this function.
             let httpActivity = null;
             for each (var item in self.openRequests) {
               if (item.channel !== aChannel) {
                 continue;
@@ -1462,31 +1971,31 @@ HUD_SERVICE.prototype =
               httpActivity = item;
               break;
             }
 
             if (!httpActivity) {
               return;
             }
 
-            let msgObject;
+            let msgObject, updatePanel = false;
             let data, textNode;
             // Store the time information for this activity subtype.
             httpActivity.timing[transCodes[aActivitySubtype]] = aTimestamp;
 
             switch (aActivitySubtype) {
               case activityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
                 let gBrowser = HUDService.currentContext().gBrowser;
 
                 let sentBody = NetworkHelper.readPostTextFromRequest(
                                 aChannel, gBrowser);
                 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
                   // in the cache of the webpage.
                   if (httpActivity.url == gBrowser.contentWindow.location.href) {
                     sentBody = NetworkHelper.readPostTextFromPage(gBrowser);
                   }
                   if (!sentBody) {
@@ -1544,18 +2053,28 @@ HUD_SERVICE.prototype =
                          requestDuration ];
 
                 msgObject.messageNode.appendChild(
                   msgObject.textFactory(
                     msgObject.prefix +
                     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();
+                }
+              });
+            }
           }
         }
       },
 
       httpTransactionCodes: {
         0x5001: "REQUEST_HEADER",
         0x5002: "REQUEST_BODY_SENT",
         0x5003: "RESPONSE_START",
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/NetworkPanel.xhtml
@@ -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>
--- a/toolkit/components/console/hudservice/tests/browser/browser_HUDServiceTestsAll.js
+++ b/toolkit/components/console/hudservice/tests/browser/browser_HUDServiceTestsAll.js
@@ -65,16 +65,18 @@ const TEST_HTTP_URI = "http://example.co
 const TEST_NETWORK_URI = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-network.html";
 
 const TEST_PROPERTY_PROVIDER_URI = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-property-provider.html";
 
 const TEST_ERROR_URI = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-error.html";
 
 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();
 }
 
 content.location.href = TEST_URI;
 
 function testRegistries() {
   var displaysIdx = HUDService.displaysIndex();
@@ -564,16 +566,264 @@ function testConsoleHistory()
 
   is (input.value, "", "check input is still empty");
 
   let idxLast = executeList.length - 1;
   jsterm.historyPeruse(true);
   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()
 {
   var HUD = HUDService.hudWeakReferences[hudId].get();
   var jsterm = HUD.jsterm;
   var context = jsterm.sandbox.window;
   var completion;
 
@@ -824,17 +1074,17 @@ function testPageReload() {
 
     is(typeof console, "object", "window.console is an object, after page reload");
     is(typeof console.log, "function", "console.log is a function");
     is(typeof console.info, "function", "console.info is a function");
     is(typeof console.warn, "function", "console.warn is a function");
     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();
 }
 
 function testErrorOnPageReload() {
   // see bug 580030: the error handler fails silently after page reload.
   // https://bugzilla.mozilla.org/show_bug.cgi?id=580030
--- a/toolkit/components/console/hudservice/tests/browser/browser_webconsole_netlogging.js
+++ b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_netlogging.js
@@ -12,29 +12,31 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 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" ] }';
 
 var hud;
 var hudId;
 
 function testOpenWebConsole()
 {
   HUDService.activateHUDForContext(gBrowser.selectedTab);
   is(HUDService.displaysIndex().length, 1, "WebConsole was opened");
 
   hudId = HUDService.displaysIndex()[0];
-  hud = HUDService.hudWeakReferences[hudId].get();
+  hud = HUDService.getHeadsUpDisplay(hudId);
 
   testNetworkLogging();
 }
 
 function finishTest() {
   hud = null;
   hudId = null;
 
@@ -140,21 +142,34 @@ function testNetworkLogging()
       "Content-Length: 20"), -1, "Content-length is correct");
     isnot(httpActivity.request.body.indexOf(
       "name=foo+bar&age=144"), -1, "Form data is correct");
     ok(httpActivity.response.body.indexOf("<!DOCTYPE HTML>") == 0,
       "Response body's beginning is okay");
 
     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();
   loggingGen.next();
 }
 
 function test()
 {
--- a/toolkit/components/console/jar.mn
+++ b/toolkit/components/console/jar.mn
@@ -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)
--- a/toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
+++ b/toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
@@ -79,8 +79,23 @@ networkUrlWithStatus=%1$S [%2$S
 #
 # When the HTTP request is finished (the HTTP connection is closed) this string
 # replaces the former `networkUrlWithStatus` string in the WebConsole.
 #
 # %1$S = URL of network request
 # %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
new file mode 100644
--- /dev/null
+++ b/toolkit/locales/en-US/chrome/global/webConsole.dtd
@@ -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">
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -32,16 +32,17 @@
   locale/@AB_CD@/global/filepicker.dtd                  (%chrome/global/filepicker.dtd)
   locale/@AB_CD@/global/filepicker.properties           (%chrome/global/filepicker.properties)
   locale/@AB_CD@/global/findbar.dtd                     (%chrome/global/findbar.dtd)
   locale/@AB_CD@/global/findbar.properties              (%chrome/global/findbar.properties)
 + locale/@AB_CD@/global/finddialog.dtd                  (%chrome/global/finddialog.dtd)
 + 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)
 + locale/@AB_CD@/global/languageNames.properties        (%chrome/global/languageNames.properties)
   locale/@AB_CD@/global/mozilla.dtd                     (%chrome/global/mozilla.dtd)
   locale/@AB_CD@/global/notification.dtd                (%chrome/global/notification.dtd)
   locale/@AB_CD@/global/preferences.dtd                 (%chrome/global/preferences.dtd)
 + locale/@AB_CD@/global/printdialog.dtd                 (%chrome/global/printdialog.dtd)
--- a/toolkit/themes/gnomestripe/global/jar.mn
+++ b/toolkit/themes/gnomestripe/global/jar.mn
@@ -23,16 +23,17 @@ toolkit.jar:
 +  skin/classic/global/scrollbox.css
 +  skin/classic/global/splitter.css
 +  skin/classic/global/tabbox.css
 +  skin/classic/global/textbox.css
 +  skin/classic/global/toolbar.css
 +  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)
 +  skin/classic/global/console/console-toolbar.png             (console/console-toolbar.png)
 +  skin/classic/global/dirListing/remote.png                   (dirListing/remote.png)
 +  skin/classic/global/icons/Authentication.png                (icons/Authentication.png)
 +  skin/classic/global/icons/autoscroll.png                    (icons/autoscroll.png)
 +  skin/classic/global/icons/blacklist_favicon.png             (icons/blacklist_favicon.png)
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/gnomestripe/global/webConsole_networkPanel.css
@@ -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;
+}
--- a/toolkit/themes/pinstripe/global/jar.mn
+++ b/toolkit/themes/pinstripe/global/jar.mn
@@ -45,16 +45,17 @@ toolkit.jar:
   skin/classic/global/tabbox.css
   skin/classic/global/textbox.css
   skin/classic/global/datetimepicker.css
   skin/classic/global/toolbar.css
   skin/classic/global/toolbarbutton.css
   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)
   skin/classic/global/arrow/arrow-dn-sharp.gif                       (arrow/arrow-dn-sharp.gif)
   skin/classic/global/arrow/arrow-dn.gif                             (arrow/arrow-dn.gif)
   skin/classic/global/arrow/arrow-dn.png                             (arrow/arrow-dn.png)
   skin/classic/global/arrow/arrow-lft-dis.gif                        (arrow/arrow-lft-dis.gif)
   skin/classic/global/arrow/arrow-lft-hov.gif                        (arrow/arrow-lft-hov.gif)
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/pinstripe/global/webConsole_networkPanel.css
@@ -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;
+}
--- a/toolkit/themes/winstripe/global/jar.mn
+++ b/toolkit/themes/winstripe/global/jar.mn
@@ -49,16 +49,17 @@ toolkit.jar:
         skin/classic/global/spinbuttons.css
         skin/classic/global/splitter.css
         skin/classic/global/tabbox.css
         skin/classic/global/textbox.css
 *       skin/classic/global/toolbar.css
         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)
         skin/classic/global/arrow/arrow-dn-dis.gif               (arrow/arrow-dn-dis.gif)
         skin/classic/global/arrow/arrow-dn-hov.gif               (arrow/arrow-dn-hov.gif)
         skin/classic/global/arrow/arrow-dn-sharp.gif             (arrow/arrow-dn-sharp.gif)
         skin/classic/global/arrow/arrow-down.png                 (arrow/arrow-down.png)
         skin/classic/global/arrow/arrow-lft.gif                  (arrow/arrow-lft.gif)
@@ -217,16 +218,17 @@ toolkit.jar:
         skin/classic/aero/global/spinbuttons.css
         skin/classic/aero/global/splitter.css
         skin/classic/aero/global/tabbox.css
 *       skin/classic/aero/global/textbox.css                             (textbox-aero.css)
 *       skin/classic/aero/global/toolbar.css
 *       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)
         skin/classic/aero/global/arrow/arrow-dn-dis.gif                  (arrow/arrow-dn-dis.gif)
         skin/classic/aero/global/arrow/arrow-dn-hov.gif                  (arrow/arrow-dn-hov.gif)
         skin/classic/aero/global/arrow/arrow-dn-sharp.gif                (arrow/arrow-dn-sharp.gif)
         skin/classic/aero/global/arrow/arrow-down.png                    (arrow/arrow-down.png)
         skin/classic/aero/global/arrow/arrow-lft.gif                     (arrow/arrow-lft.gif)
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/winstripe/global/webConsole_networkPanel.css
@@ -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;
+}