Bug 731318 - Show transferred size in Net Monitor. r=vporof
☠☠ backed out by 22eb2228358f ☠ ☠
authorJ. Ryan Stinnett <jryans@gmail.com>
Tue, 09 Dec 2014 14:35:13 -0600
changeset 218963 09bd629ead0ff6be46c29d12d8a10945ba0a2f92
parent 218962 28be6b157ca4ba7eed9cb96bdbe7020bead04244
child 218964 9bbaa27f0975b196ed9f42a7c226917e137682fe
push id27950
push usercbook@mozilla.com
push dateWed, 10 Dec 2014 10:58:50 +0000
treeherderautoland@5b01216f97f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvporof
bugs731318
milestone37.0a1
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 731318 - Show transferred size in Net Monitor. r=vporof
browser/devtools/netmonitor/netmonitor-controller.js
browser/devtools/netmonitor/netmonitor-view.js
browser/devtools/netmonitor/netmonitor.xul
browser/devtools/netmonitor/test/browser_net_simple-request-data.js
browser/devtools/netmonitor/test/browser_net_sort-01.js
browser/devtools/netmonitor/test/browser_net_sort-02.js
browser/devtools/netmonitor/test/browser_net_sort-03.js
browser/devtools/netmonitor/test/browser_net_timeline_ticks.js
browser/devtools/netmonitor/test/head.js
browser/locales/en-US/chrome/browser/devtools/netmonitor.dtd
browser/locales/en-US/chrome/browser/devtools/netmonitor.properties
browser/themes/shared/devtools/netmonitor.inc.css
toolkit/devtools/server/actors/webconsole.js
toolkit/devtools/webconsole/network-monitor.js
--- a/browser/devtools/netmonitor/netmonitor-controller.js
+++ b/browser/devtools/netmonitor/netmonitor-controller.js
@@ -387,16 +387,26 @@ let NetMonitorController = {
    */
   get supportsCustomRequest() {
     return this.webConsoleClient &&
            (this.webConsoleClient.traits.customNetworkRequest ||
             !this._target.isApp);
   },
 
   /**
+   * Getter that tells if the server includes the transferred (compressed /
+   * encoded) response size.
+   * @type boolean
+   */
+  get supportsTransferredResponseSize() {
+    return this.webConsoleClient &&
+           this.webConsoleClient.traits.transferredResponseSize;
+  },
+
+  /**
    * Getter that tells if the server can do network performance statistics.
    * @type boolean
    */
   get supportsPerfStats() {
     return this.tabClient &&
            (this.tabClient.traits.reconfigure || !this._target.isApp);
   },
 
@@ -585,16 +595,17 @@ NetworkEventsHandler.prototype = {
           statusText: aPacket.response.statusText,
           headersSize: aPacket.response.headersSize
         });
         window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
         break;
       case "responseContent":
         NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
           contentSize: aPacket.contentSize,
+          transferredSize: aPacket.transferredSize,
           mimeType: aPacket.mimeType
         });
         this.webConsoleClient.getResponseContent(actor, this._onResponseContent);
         window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
         break;
       case "eventTimings":
         NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
           totalTime: aPacket.totalTime
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -411,16 +411,21 @@ RequestsMenuView.prototype = Heritage.ex
       $("#requests-menu-network-summary-label").addEventListener("click", this._onContextPerfCommand, false);
       $("#network-statistics-back-button").addEventListener("command", this._onContextPerfCommand, false);
     } else {
       $("#notice-perf-message").hidden = true;
       $("#request-menu-context-perf").hidden = true;
       $("#requests-menu-network-summary-button").hidden = true;
       $("#requests-menu-network-summary-label").hidden = true;
     }
+
+    if (!NetMonitorController.supportsTransferredResponseSize) {
+      $("#requests-menu-transferred-header-box").hidden = true;
+      $("#requests-menu-item-template .requests-menu-transferred").hidden = true;
+    }
   },
 
   /**
    * Destruction function, called when the network monitor is closed.
    */
   destroy: function() {
     dumpn("Destroying the SourcesView");
 
@@ -794,18 +799,18 @@ RequestsMenuView.prototype = Heritage.ex
     flash: this.isFlash,
     other: this.isOther
   }),
 
   /**
    * Sorts all network requests in this container by a specified detail.
    *
    * @param string aType
-   *        Either "status", "method", "file", "domain", "type", "size" or
-   *        "waterfall".
+   *        Either "status", "method", "file", "domain", "type", "transferred",
+   *        "size" or "waterfall".
    */
   sortBy: function(aType = "waterfall") {
     let target = $("#requests-menu-" + aType + "-button");
     let headers = document.querySelectorAll(".requests-menu-header-button");
 
     for (let header of headers) {
       if (header != target) {
         header.removeAttribute("sorted");
@@ -856,16 +861,23 @@ RequestsMenuView.prototype = Heritage.ex
         break;
       case "type":
         if (direction == "ascending") {
           this.sortContents(this._byType);
         } else {
           this.sortContents((a, b) => !this._byType(a, b));
         }
         break;
+      case "transferred":
+        if (direction == "ascending") {
+          this.sortContents(this._byTransferred);
+        } else {
+          this.sortContents((a, b) => !this._byTransferred(a, b));
+        }
+        break;
       case "size":
         if (direction == "ascending") {
           this.sortContents(this._bySize);
         } else {
           this.sortContents((a, b) => !this._bySize(a, b));
         }
         break;
       case "waterfall":
@@ -988,18 +1000,23 @@ RequestsMenuView.prototype = Heritage.ex
   _byType: function({ attachment: first }, { attachment: second }) {
     let firstType = this._getAbbreviatedMimeType(first.mimeType).toLowerCase();
     let secondType = this._getAbbreviatedMimeType(second.mimeType).toLowerCase();
     return firstType == secondType
       ? first.startedMillis > second.startedMillis
       : firstType > secondType;
   },
 
-  _bySize: function({ attachment: first }, { attachment: second })
-    first.contentSize > second.contentSize,
+  _byTransferred: function({ attachment: first }, { attachment: second }) {
+    return first.transferredSize > second.transferredSize;
+  },
+
+  _bySize: function({ attachment: first }, { attachment: second }) {
+    return first.contentSize > second.contentSize;
+  },
 
   /**
    * Refreshes the status displayed in this container's footer, providing
    * concise information about all requests.
    */
   refreshSummary: function() {
     let visibleItems = this.visibleItems;
     let visibleRequestsCount = visibleItems.length;
@@ -1154,16 +1171,20 @@ RequestsMenuView.prototype = Heritage.ex
             break;
           case "headersSize":
             requestItem.attachment.headersSize = value;
             break;
           case "contentSize":
             requestItem.attachment.contentSize = value;
             this.updateMenuView(requestItem, key, value);
             break;
+          case "transferredSize":
+            requestItem.attachment.transferredSize = value;
+            this.updateMenuView(requestItem, key, value);
+            break;
           case "mimeType":
             requestItem.attachment.mimeType = value;
             this.updateMenuView(requestItem, key, value);
             break;
           case "responseContent":
             // If there's no mime type available when the response content
             // is received, assume text/plain as a fallback.
             if (!requestItem.attachment.mimeType) {
@@ -1302,16 +1323,30 @@ RequestsMenuView.prototype = Heritage.ex
         let kb = aValue / 1024;
         let size = L10N.numberWithDecimals(kb, CONTENT_SIZE_DECIMALS);
         let node = $(".requests-menu-size", target);
         let text = L10N.getFormatStr("networkMenu.sizeKB", size);
         node.setAttribute("value", text);
         node.setAttribute("tooltiptext", text);
         break;
       }
+      case "transferredSize": {
+        let text;
+        if (aValue === null) {
+          text = L10N.getStr("networkMenu.sizeUnavailable");
+        } else {
+          let kb = aValue / 1024;
+          let size = L10N.numberWithDecimals(kb, CONTENT_SIZE_DECIMALS);
+          text = L10N.getFormatStr("networkMenu.sizeKB", size);
+        }
+        let node = $(".requests-menu-transferred", target);
+        node.setAttribute("value", text);
+        node.setAttribute("tooltiptext", text);
+        break;
+      }
       case "mimeType": {
         let type = this._getAbbreviatedMimeType(aValue);
         let node = $(".requests-menu-type", target);
         let text = CONTENT_MIME_TYPE_ABBREVIATIONS[type] || type;
         node.setAttribute("value", text);
         node.setAttribute("tooltiptext", aValue);
         break;
       }
--- a/browser/devtools/netmonitor/netmonitor.xul
+++ b/browser/devtools/netmonitor/netmonitor.xul
@@ -96,16 +96,26 @@
                     align="center">
                 <button id="requests-menu-type-button"
                         class="requests-menu-header-button requests-menu-type"
                         data-key="type"
                         label="&netmonitorUI.toolbar.type;"
                         flex="1">
                 </button>
               </hbox>
+              <hbox id="requests-menu-transferred-header-box"
+                    class="requests-menu-header requests-menu-transferred"
+                    align="center">
+                <button id="requests-menu-transferred-button"
+                        class="requests-menu-header-button requests-menu-transferred"
+                        data-key="transferred"
+                        label="&netmonitorUI.toolbar.transferred;"
+                        flex="1">
+                </button>
+              </hbox>
               <hbox id="requests-menu-size-header-box"
                     class="requests-menu-header requests-menu-size"
                     align="center">
                 <button id="requests-menu-size-button"
                         class="requests-menu-header-button requests-menu-size"
                         data-key="size"
                         label="&netmonitorUI.toolbar.size;"
                         flex="1">
@@ -169,16 +179,18 @@
                 <label class="plain requests-menu-file"
                        crop="end"
                        flex="1"/>
               </hbox>
               <label class="plain requests-menu-subitem requests-menu-domain"
                      crop="end"/>
               <label class="plain requests-menu-subitem requests-menu-type"
                      crop="end"/>
+              <label class="plain requests-menu-subitem requests-menu-transferred"
+                     crop="end"/>
               <label class="plain requests-menu-subitem requests-menu-size"
                      crop="end"/>
               <hbox class="requests-menu-subitem requests-menu-waterfall"
                     align="center"
                     flex="1">
                 <hbox class="requests-menu-timings"
                       align="center">
                   <label class="plain requests-menu-timings-total"/>
--- a/browser/devtools/netmonitor/test/browser_net_simple-request-data.js
+++ b/browser/devtools/netmonitor/test/browser_net_simple-request-data.js
@@ -60,16 +60,18 @@ function test() {
         "The httpVersion should not yet be set.");
       is(requestItem.attachment.status, undefined,
         "The status should not yet be set.");
       is(requestItem.attachment.statusText, undefined,
         "The statusText should not yet be set.");
 
       is(requestItem.attachment.headersSize, undefined,
         "The headersSize should not yet be set.");
+      is(requestItem.attachment.transferredSize, undefined,
+        "The transferredSize should not yet be set.");
       is(requestItem.attachment.contentSize, undefined,
         "The contentSize should not yet be set.");
 
       is(requestItem.attachment.mimeType, undefined,
         "The mimeType should not yet be set.");
       is(requestItem.attachment.responseContent, undefined,
         "The responseContent should not yet be set.");
 
@@ -151,24 +153,27 @@ function test() {
         status: "200",
         statusText: "Och Aye"
       });
     });
 
     aMonitor.panelWin.once(aMonitor.panelWin.EVENTS.UPDATING_RESPONSE_CONTENT, () => {
       let requestItem = RequestsMenu.getItemAtIndex(0);
 
+      is(requestItem.attachment.transferredSize, "12",
+        "The transferredSize attachment has an incorrect value.");
       is(requestItem.attachment.contentSize, "12",
         "The contentSize attachment has an incorrect value.");
       is(requestItem.attachment.mimeType, "text/plain; charset=utf-8",
         "The mimeType attachment has an incorrect value.");
 
       verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
         type: "plain",
         fullMimeType: "text/plain; charset=utf-8",
+        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
         size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
       });
     });
 
     aMonitor.panelWin.once(aMonitor.panelWin.EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
       let requestItem = RequestsMenu.getItemAtIndex(0);
 
       ok(requestItem.attachment.responseContent,
@@ -178,16 +183,17 @@ function test() {
       is(requestItem.attachment.responseContent.content.text, "Hello world!",
         "The responseContent attachment has an incorrect |content.text| property.");
       is(requestItem.attachment.responseContent.content.size, 12,
         "The responseContent attachment has an incorrect |content.size| property.");
 
       verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
         type: "plain",
         fullMimeType: "text/plain; charset=utf-8",
+        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
         size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
       });
     });
 
     aMonitor.panelWin.once(aMonitor.panelWin.EVENTS.UPDATING_EVENT_TIMINGS, () => {
       let requestItem = RequestsMenu.getItemAtIndex(0);
 
       is(typeof requestItem.attachment.totalTime, "number",
--- a/browser/devtools/netmonitor/test/browser_net_sort-01.js
+++ b/browser/devtools/netmonitor/test/browser_net_sort-01.js
@@ -197,52 +197,57 @@ function test() {
         "The requests menu items aren't ordered correctly. Fifth item is misplaced.");
 
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(a),
         "GET", STATUS_CODES_SJS + "?sts=100", {
           status: 101,
           statusText: "Switching Protocols",
           type: "plain",
           fullMimeType: "text/plain; charset=utf-8",
+          transferred: L10N.getStr("networkMenu.sizeUnavailable"),
           size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(b),
         "GET", STATUS_CODES_SJS + "?sts=200", {
           status: 202,
           statusText: "Created",
           type: "plain",
           fullMimeType: "text/plain; charset=utf-8",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
           size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(c),
         "GET", STATUS_CODES_SJS + "?sts=300", {
           status: 303,
           statusText: "See Other",
           type: "plain",
           fullMimeType: "text/plain; charset=utf-8",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
           size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(d),
         "GET", STATUS_CODES_SJS + "?sts=400", {
           status: 404,
           statusText: "Not Found",
           type: "plain",
           fullMimeType: "text/plain; charset=utf-8",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
           size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(e),
         "GET", STATUS_CODES_SJS + "?sts=500", {
           status: 501,
           statusText: "Not Implemented",
           type: "plain",
           fullMimeType: "text/plain; charset=utf-8",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
           size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
           time: true
         });
 
       return promise.resolve(null);
     }
 
     aDebuggee.performRequests();
--- a/browser/devtools/netmonitor/test/browser_net_sort-02.js
+++ b/browser/devtools/netmonitor/test/browser_net_sort-02.js
@@ -98,16 +98,34 @@ function test() {
         })
         .then(() => {
           info("Testing type sort, ascending. Checking sort loops correctly.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-type-button"));
           testHeaders("type", "ascending");
           return testContents([0, 1, 2, 3, 4]);
         })
         .then(() => {
+          info("Testing transferred sort, ascending.");
+          EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-transferred-button"));
+          testHeaders("transferred", "ascending");
+          return testContents([0, 1, 2, 3, 4]);
+        })
+        .then(() => {
+          info("Testing transferred sort, descending.");
+          EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-transferred-button"));
+          testHeaders("transferred", "descending");
+          return testContents([4, 3, 2, 1, 0]);
+        })
+        .then(() => {
+          info("Testing transferred sort, ascending. Checking sort loops correctly.");
+          EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-transferred-button"));
+          testHeaders("transferred", "ascending");
+          return testContents([0, 1, 2, 3, 4]);
+        })
+        .then(() => {
           info("Testing size sort, ascending.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
           testHeaders("size", "ascending");
           return testContents([0, 1, 2, 3, 4]);
         })
         .then(() => {
           info("Testing size sort, descending.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
@@ -194,56 +212,61 @@ function test() {
 
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(a),
         "GET1", SORTING_SJS + "?index=1", {
           fuzzyUrl: true,
           status: 101,
           statusText: "Meh",
           type: "1",
           fullMimeType: "text/1",
+          transferred: L10N.getStr("networkMenu.sizeUnavailable"),
           size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(b),
         "GET2", SORTING_SJS + "?index=2", {
           fuzzyUrl: true,
           status: 200,
           statusText: "Meh",
           type: "2",
           fullMimeType: "text/2",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
           size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(c),
         "GET3", SORTING_SJS + "?index=3", {
           fuzzyUrl: true,
           status: 300,
           statusText: "Meh",
           type: "3",
           fullMimeType: "text/3",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
           size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(d),
         "GET4", SORTING_SJS + "?index=4", {
           fuzzyUrl: true,
           status: 400,
           statusText: "Meh",
           type: "4",
           fullMimeType: "text/4",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.03),
           size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.03),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(e),
         "GET5", SORTING_SJS + "?index=5", {
           fuzzyUrl: true,
           status: 500,
           statusText: "Meh",
           type: "5",
           fullMimeType: "text/5",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
           size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
           time: true
         });
 
       return promise.resolve(null);
     }
 
     aDebuggee.performRequests();
--- a/browser/devtools/netmonitor/test/browser_net_sort-03.js
+++ b/browser/devtools/netmonitor/test/browser_net_sort-03.js
@@ -125,64 +125,69 @@ function test() {
       for (let i = 0, len = aOrder.length / 5; i < len; i++) {
         verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i]),
           "GET1", SORTING_SJS + "?index=1", {
             fuzzyUrl: true,
             status: 101,
             statusText: "Meh",
             type: "1",
             fullMimeType: "text/1",
+            transferred: L10N.getStr("networkMenu.sizeUnavailable"),
             size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0),
             time: true
           });
       }
       for (let i = 0, len = aOrder.length / 5; i < len; i++) {
         verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len]),
           "GET2", SORTING_SJS + "?index=2", {
             fuzzyUrl: true,
             status: 200,
             statusText: "Meh",
             type: "2",
             fullMimeType: "text/2",
+            transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
             size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
             time: true
           });
       }
       for (let i = 0, len = aOrder.length / 5; i < len; i++) {
         verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 2]),
           "GET3", SORTING_SJS + "?index=3", {
             fuzzyUrl: true,
             status: 300,
             statusText: "Meh",
             type: "3",
             fullMimeType: "text/3",
+            transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
             size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
             time: true
           });
       }
       for (let i = 0, len = aOrder.length / 5; i < len; i++) {
         verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 3]),
           "GET4", SORTING_SJS + "?index=4", {
             fuzzyUrl: true,
             status: 400,
             statusText: "Meh",
             type: "4",
             fullMimeType: "text/4",
+            transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.03),
             size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.03),
             time: true
           });
       }
       for (let i = 0, len = aOrder.length / 5; i < len; i++) {
         verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 4]),
           "GET5", SORTING_SJS + "?index=5", {
             fuzzyUrl: true,
             status: 500,
             statusText: "Meh",
             type: "5",
             fullMimeType: "text/5",
+            transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
             size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
             time: true
           });
       }
 
       return promise.resolve(null);
     }
 
--- a/browser/devtools/netmonitor/test/browser_net_timeline_ticks.js
+++ b/browser/devtools/netmonitor/test/browser_net_timeline_ticks.js
@@ -7,16 +7,22 @@
 
 function test() {
   initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
     info("Starting test... ");
 
     let { document, L10N, NetMonitorView } = aMonitor.panelWin;
     let { RequestsMenu } = NetMonitorView;
 
+    // Disable transferred size column support for this test.
+    // Without this, the waterfall only has enough room for one division, which
+    // would remove most of the value of this test.
+    document.querySelector("#requests-menu-transferred-header-box").hidden = true;
+    document.querySelector("#requests-menu-item-template .requests-menu-transferred").hidden = true;
+
     RequestsMenu.lazyUpdate = false;
 
     ok(document.querySelector("#requests-menu-waterfall-label"),
       "An timeline label should be displayed when the frontend is opened.");
     ok(document.querySelectorAll(".requests-menu-timings-division").length == 0,
       "No tick labels should be displayed when the frontend is opened.");
 
     ok(!RequestsMenu._canvas,
--- a/browser/devtools/netmonitor/test/head.js
+++ b/browser/devtools/netmonitor/test/head.js
@@ -259,17 +259,17 @@ function verifyRequestItemTarget(aReques
 
   let requestsMenu = aRequestItem.ownerView;
   let widgetIndex = requestsMenu.indexOfItem(aRequestItem);
   let visibleIndex = requestsMenu.visibleItems.indexOf(aRequestItem);
 
   info("Widget index of item: " + widgetIndex);
   info("Visible index of item: " + visibleIndex);
 
-  let { fuzzyUrl, status, statusText, type, fullMimeType, size, time } = aData;
+  let { fuzzyUrl, status, statusText, type, fullMimeType, transferred, size, time } = aData;
   let { attachment, target } = aRequestItem
 
   let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
   let name = uri.fileName || "/";
   let query = uri.query;
   let hostPort = uri.hostPort;
 
   if (fuzzyUrl) {
@@ -314,16 +314,24 @@ function verifyRequestItemTarget(aReques
   if (type !== undefined) {
     let value = target.querySelector(".requests-menu-type").getAttribute("value");
     let tooltip = target.querySelector(".requests-menu-type").getAttribute("tooltiptext");
     info("Displayed type: " + value);
     info("Tooltip type: " + tooltip);
     is(value, type, "The displayed type is incorrect.");
     is(tooltip, fullMimeType, "The tooltip type is incorrect.");
   }
+  if (transferred !== undefined) {
+    let value = target.querySelector(".requests-menu-transferred").getAttribute("value");
+    let tooltip = target.querySelector(".requests-menu-transferred").getAttribute("tooltiptext");
+    info("Displayed transferred size: " + value);
+    info("Tooltip transferred size: " + tooltip);
+    is(value, transferred, "The displayed transferred size is incorrect.");
+    is(tooltip, transferred, "The tooltip transferred size is incorrect.");
+  }
   if (size !== undefined) {
     let value = target.querySelector(".requests-menu-size").getAttribute("value");
     let tooltip = target.querySelector(".requests-menu-size").getAttribute("tooltiptext");
     info("Displayed size: " + value);
     info("Tooltip size: " + tooltip);
     is(value, size, "The displayed size is incorrect.");
     is(tooltip, size, "The tooltip size is incorrect.");
   }
--- a/browser/locales/en-US/chrome/browser/devtools/netmonitor.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/netmonitor.dtd
@@ -37,18 +37,24 @@
 <!-- LOCALIZATION NOTE (netmonitorUI.toolbar.domain): This is the label displayed
   -  in the network table toolbar, above the "domain" column. -->
 <!ENTITY netmonitorUI.toolbar.domain      "Domain">
 
 <!-- LOCALIZATION NOTE (netmonitorUI.toolbar.type): This is the label displayed
   -  in the network table toolbar, above the "type" column. -->
 <!ENTITY netmonitorUI.toolbar.type        "Type">
 
+<!-- LOCALIZATION NOTE (netmonitorUI.toolbar.transferred): This is the label displayed
+  -  in the network table toolbar, above the "transferred" column, which is the
+  -  compressed / encoded size. -->
+<!ENTITY netmonitorUI.toolbar.transferred "Transferred">
+
 <!-- LOCALIZATION NOTE (netmonitorUI.toolbar.size): This is the label displayed
-  -  in the network table toolbar, above the "size" column. -->
+  -  in the network table toolbar, above the "size" column, which is the
+  -  uncompressed / decoded size. -->
 <!ENTITY netmonitorUI.toolbar.size        "Size">
 
 <!-- LOCALIZATION NOTE (netmonitorUI.toolbar.waterfall): This is the label displayed
   -  in the network table toolbar, above the "waterfall" column. -->
 <!ENTITY netmonitorUI.toolbar.waterfall   "Timeline">
 
 <!-- LOCALIZATION NOTE (debuggerUI.tab.headers): This is the label displayed
   -  in the network details pane identifying the headers tab. -->
--- a/browser/locales/en-US/chrome/browser/devtools/netmonitor.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/netmonitor.properties
@@ -124,16 +124,21 @@ networkMenu.empty=No requests
 # information about all requests. Parameters: #1 is the number of requests,
 # #2 is the size, #3 is the number of seconds.
 networkMenu.summary=One request, #2 KB, #3 s;#1 requests, #2 KB, #3 s
 
 # LOCALIZATION NOTE (networkMenu.sizeKB): This is the label displayed
 # in the network menu specifying the size of a request (in kilobytes).
 networkMenu.sizeKB=%S KB
 
+# LOCALIZATION NOTE (networkMenu.sizeUnavailable): This is the label displayed
+# in the network menu specifying the transferred size of a request is
+# unavailable.
+networkMenu.sizeUnavailable=—
+
 # LOCALIZATION NOTE (networkMenu.totalMS): This is the label displayed
 # in the network menu specifying the time for a request to finish (in milliseconds).
 networkMenu.totalMS=→ %S ms
 
 # LOCALIZATION NOTE (networkMenu.millisecond): This is the label displayed
 # in the network menu specifying timing interval divisions (in milliseconds).
 networkMenu.millisecond=%S ms
 
--- a/browser/themes/shared/devtools/netmonitor.inc.css
+++ b/browser/themes/shared/devtools/netmonitor.inc.css
@@ -161,16 +161,21 @@
   width: 4em;
 }
 
 .requests-menu-size {
   text-align: center;
   width: 8em;
 }
 
+.requests-menu-transferred {
+  text-align: center;
+  width: 8em;
+}
+
 /* Network requests table: status codes */
 
 box.requests-menu-status {
   background: #fff;
   width: 10px;
   -moz-margin-start: 5px;
   -moz-margin-end: 5px;
   border-radius: 10px;
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -78,16 +78,17 @@ function WebConsoleActor(aConnection, aP
   this._onObserverNotification = this._onObserverNotification.bind(this);
   if (this.parentActor.isRootActor) {
     Services.obs.addObserver(this._onObserverNotification,
                              "last-pb-context-exited", false);
   }
 
   this.traits = {
     customNetworkRequest: !this._parentIsContentActor,
+    transferredResponseSize: true
   };
 }
 
 WebConsoleActor.l10n = new WebConsoleUtils.l10n("chrome://global/locale/console.properties");
 
 WebConsoleActor.prototype =
 {
   /**
@@ -1935,16 +1936,17 @@ NetworkEventActor.prototype =
     }
 
     let packet = {
       from: this.actorID,
       type: "networkEventUpdate",
       updateType: "responseContent",
       mimeType: aContent.mimeType,
       contentSize: aContent.text.length,
+      transferredSize: aContent.transferredSize,
       discardResponseBody: aDiscardedResponseBody,
     };
 
     this.conn.send(packet);
   },
 
   /**
    * Add network event timing information.
--- a/toolkit/devtools/webconsole/network-monitor.js
+++ b/toolkit/devtools/webconsole/network-monitor.js
@@ -58,17 +58,19 @@ function NetworkResponseListener(aOwner,
   this.httpActivity = aHttpActivity;
   this.bodySize = 0;
 }
 exports.NetworkResponseListener = NetworkResponseListener;
 
 NetworkResponseListener.prototype = {
   QueryInterface:
     XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInputStreamCallback,
-                           Ci.nsIRequestObserver, Ci.nsISupports]),
+                           Ci.nsIRequestObserver, Ci.nsIInterfaceRequestor,
+                           Ci.nsISupports]),
+  getInterface: XPCOMUtils.generateQI([Ci.nsIProgressEventSink]),
 
   /**
    * This NetworkResponseListener tracks the NetworkMonitor.openResponses object
    * to find the associated uncached headers.
    * @private
    */
   _foundOpenResponse: false,
 
@@ -89,21 +91,26 @@ NetworkResponseListener.prototype = {
   httpActivity: null,
 
   /**
    * Stores the received data as a string.
    */
   receivedData: null,
 
   /**
-   * The network response body size.
+   * The uncompressed, decoded response body size.
    */
   bodySize: null,
 
   /**
+   * Response body size on the wire, potentially compressed / encoded.
+   */
+  transferredSize: null,
+
+  /**
    * The nsIRequest we are started for.
    */
   request: null,
 
   /**
    * Set the async listener for the given nsIAsyncInputStream. This allows us to
    * wait asynchronously for any data coming from the stream.
    *
@@ -172,16 +179,28 @@ NetworkResponseListener.prototype = {
    * https://developer.mozilla.org/En/NsIRequestObserver
    */
   onStopRequest: function NRL_onStopRequest()
   {
     this._findOpenResponse();
     this.sink.outputStream.close();
   },
 
+  // nsIProgressEventSink implementation
+
+  /**
+   * Handle progress event as data is transferred.  This is used to record the
+   * size on the wire, which may be compressed / encoded.
+   */
+  onProgress: function(request, context, progress, progressMax) {
+    this.transferredSize = progress;
+  },
+
+  onStatus: function () {},
+
   /**
    * Find the open response object associated to the current request. The
    * NetworkMonitor._httpResponseExaminer() method saves the response headers in
    * NetworkMonitor.openResponses. This method takes the data from the open
    * response object and puts it into the HTTP activity object, then sends it to
    * the remote Web Console instance.
    *
    * @private
@@ -254,16 +273,17 @@ NetworkResponseListener.prototype = {
   _onComplete: function NRL__onComplete(aData)
   {
     let response = {
       mimeType: "",
       text: aData || "",
     };
 
     response.size = response.text.length;
+    response.transferredSize = this.transferredSize;
 
     try {
       response.mimeType = this.request.contentType;
     }
     catch (ex) { }
 
     if (!response.mimeType || !NetworkHelper.isTextMimeType(response.mimeType)) {
       response.encoding = "base64";
@@ -274,16 +294,17 @@ NetworkResponseListener.prototype = {
       response.mimeType += "; charset=" + this.request.contentCharset;
     }
 
     this.receivedData = "";
 
     this.httpActivity.owner.
       addResponseContent(response, this.httpActivity.discardResponseBody);
 
+    this.httpActivity.channel.notificationCallbacks = null;
     this.httpActivity.channel = null;
     this.httpActivity.owner = null;
     this.httpActivity = null;
     this.sink = null;
     this.inputStream = null;
     this.request = null;
     this.owner = null;
   },
@@ -775,16 +796,18 @@ NetworkMonitor.prototype = {
     newListener.sink = sink;
 
     let tee = Cc["@mozilla.org/network/stream-listener-tee;1"].
               createInstance(Ci.nsIStreamListenerTee);
 
     let originalListener = channel.setNewListener(tee);
 
     tee.init(originalListener, sink.outputStream, newListener);
+
+    channel.notificationCallbacks = newListener;
   },
 
   /**
    * Handler for ACTIVITY_SUBTYPE_REQUEST_BODY_SENT. The request body is logged
    * here.
    *
    * @private
    * @param object aHttpActivity