Bug 1244227 - Add an API to enable throttling. r=Honza draft
authorTom Tromey <tom@tromey.com>
Tue, 01 Mar 2016 11:13:41 -0700
changeset 399972 ac10142a6019a2d98df8bb749c908dbeecb001fd
parent 399971 48cb737bb212864dfa6905553e19dcefe2ee6dda
child 399973 960ce2eeb520ea0b2c21ef5ae44c4d02640c6c09
push id26051
push userbmo:ttromey@mozilla.com
push dateFri, 12 Aug 2016 14:07:06 +0000
reviewersHonza
bugs1244227
milestone51.0a1
Bug 1244227 - Add an API to enable throttling. r=Honza MozReview-Commit-ID: BirjFHVSZN7
devtools/client/netmonitor/har/test/browser.ini
devtools/client/netmonitor/har/test/browser_net_har_throttle_upload.js
devtools/client/netmonitor/har/test/html_har_post-data-test-page.html
devtools/client/netmonitor/test/browser.ini
devtools/client/netmonitor/test/browser_net_throttle.js
devtools/client/webconsole/webconsole.js
devtools/server/actors/webconsole.js
devtools/shared/webconsole/network-monitor.js
devtools/shared/webconsole/throttle.js
--- a/devtools/client/netmonitor/har/test/browser.ini
+++ b/devtools/client/netmonitor/har/test/browser.ini
@@ -4,8 +4,9 @@ subsuite = clipboard
 support-files =
   head.js
   html_har_post-data-test-page.html
   !/devtools/client/netmonitor/test/head.js
   !/devtools/client/netmonitor/test/html_simple-test-page.html
 
 [browser_net_har_copy_all_as_har.js]
 [browser_net_har_post_data.js]
+[browser_net_har_throttle_upload.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/har/test/browser_net_har_throttle_upload.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test timing of upload when throttling.
+
+"use strict";
+
+add_task(function* () {
+  yield throttleUploadTest(true);
+  yield throttleUploadTest(false);
+});
+
+function* throttleUploadTest(actuallyThrottle) {
+  let [ , debuggee, monitor ] = yield initNetMonitor(
+    HAR_EXAMPLE_URL + "html_har_post-data-test-page.html");
+
+  info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
+
+  let { NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu } = NetMonitorView;
+
+  const size = 4096;
+  const uploadSize = actuallyThrottle ? size / 3 : 0;
+
+  const request = {
+    "NetworkMonitor.throttleData": {
+      roundTripTimeMean: 0,
+      roundTripTimeMax: 0,
+      downloadBPSMean: 200000,
+      downloadBPSMax: 200000,
+      uploadBPSMean: uploadSize,
+      uploadBPSMax: uploadSize,
+    },
+  };
+  let client = monitor._controller.webConsoleClient;
+
+  info("sending throttle request");
+  let deferred = promise.defer();
+  client.setPreferences(request, response => {
+    deferred.resolve(response);
+  });
+  yield deferred.promise;
+
+  RequestsMenu.lazyUpdate = false;
+
+  // Execute one POST request on the page and wait till its done.
+  debuggee.executeTest2(size);
+  yield waitForNetworkEvents(monitor, 0, 1);
+
+  // Copy HAR into the clipboard (asynchronous).
+  let jsonString = yield RequestsMenu.copyAllAsHar();
+  let har = JSON.parse(jsonString);
+
+  // Check out the HAR log.
+  isnot(har.log, null, "The HAR log must exist");
+  is(har.log.pages.length, 1, "There must be one page");
+  is(har.log.entries.length, 1, "There must be one request");
+
+  let entry = har.log.entries[0];
+  is(entry.request.postData.text, "x".repeat(size),
+     "Check post data payload");
+
+  const wasTwoSeconds = entry.timings.send >= 2000;
+  if (actuallyThrottle) {
+    ok(wasTwoSeconds, "upload should have taken more than 2 seconds");
+  } else {
+    ok(!wasTwoSeconds, "upload should not have taken more than 2 seconds");
+  }
+
+  // Clean up
+  yield teardown(monitor);
+}
--- a/devtools/client/netmonitor/har/test/html_har_post-data-test-page.html
+++ b/devtools/client/netmonitor/har/test/html_har_post-data-test-page.html
@@ -22,12 +22,18 @@
         xhr.send(aData);
       }
 
       function executeTest() {
         var url = "html_har_post-data-test-page.html";
         var data = "{'first': 'John', 'last': 'Doe'}";
         post(url, data);
       }
+
+      function executeTest2(size) {
+        var url = "html_har_post-data-test-page.html";
+        var data = "x".repeat(size);
+        post(url, data);
+      }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -133,11 +133,12 @@ skip-if = true # Bug 1258809
 skip-if = (e10s && debug && os == 'mac') # Bug 1253037
 [browser_net_sort-02.js]
 [browser_net_sort-03.js]
 [browser_net_statistics-01.js]
 [browser_net_statistics-02.js]
 [browser_net_statistics-03.js]
 [browser_net_status-codes.js]
 [browser_net_streaming-response.js]
+[browser_net_throttle.js]
 [browser_net_timeline_ticks.js]
 [browser_net_timing-division.js]
 [browser_net_persistent_logs.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_throttle.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Network throttling integration test.
+
+"use strict";
+
+add_task(function* () {
+  yield throttleTest(true);
+  yield throttleTest(false);
+});
+
+function* throttleTest(actuallyThrottle) {
+  requestLongerTimeout(2);
+
+  let [, , monitor] = yield initNetMonitor(SIMPLE_URL);
+  const {ACTIVITY_TYPE, NetMonitorController, NetMonitorView} =
+        monitor.panelWin;
+
+  info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
+
+  // When throttling, must be smaller than the length of the content
+  // of SIMPLE_URL in bytes.
+  const size = actuallyThrottle ? 200 : 0;
+
+  const request = {
+    "NetworkMonitor.throttleData": {
+      roundTripTimeMean: 0,
+      roundTripTimeMax: 0,
+      downloadBPSMean: size,
+      downloadBPSMax: size,
+      uploadBPSMean: 10000,
+      uploadBPSMax: 10000,
+    },
+  };
+  let client = monitor._controller.webConsoleClient;
+
+  info("sending throttle request");
+  let deferred = promise.defer();
+  client.setPreferences(request, response => {
+    deferred.resolve(response);
+  });
+  yield deferred.promise;
+
+  const startTime = Date.now();
+  let eventPromise =
+      monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_EVENT_TIMINGS);
+  yield NetMonitorController
+    .triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED);
+  const endTime = Date.now();
+  const wasOneSecond = endTime - startTime > 1000;
+  if (actuallyThrottle) {
+    ok(wasOneSecond, "download took more than one second");
+  } else {
+    ok(!wasOneSecond, "download took less than one second");
+  }
+
+  yield eventPromise;
+  let requestItem = NetMonitorView.RequestsMenu.getItemAtIndex(0);
+  const reportedOneSecond = requestItem.attachment.eventTimings.timings.receive > 1000;
+  if (actuallyThrottle) {
+    ok(reportedOneSecond, "download reported as taking more than one second");
+  } else {
+    ok(!reportedOneSecond, "download reported as taking less than one second");
+  }
+
+  yield teardown(monitor);
+}
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -382,16 +382,17 @@ WebConsoleFrame.prototype = {
    */
   get webConsoleClient() {
     return this.proxy ? this.proxy.webConsoleClient : null;
   },
 
   _destroyer: null,
 
   _saveRequestAndResponseBodies: true,
+  _throttleData: null,
 
   // Chevron width at the starting of Web Console's input box.
   _chevronWidth: 0,
   // Width of the monospace characters in Web Console's input box.
   _inputCharWidth: 0,
 
   /**
    * Setter for saving of network request and response bodies.
@@ -420,16 +421,46 @@ WebConsoleFrame.prototype = {
         deferred.reject(response.error);
       }
     });
 
     return deferred.promise;
   },
 
   /**
+   * Setter for throttling data.
+   *
+   * @param boolean value
+   *        The new value you want to set; @see NetworkThrottleManager.
+   */
+  setThrottleData: function(value) {
+    if (!this.webConsoleClient) {
+      // Don't continue if the webconsole disconnected.
+      return promise.resolve(null);
+    }
+
+    let deferred = promise.defer();
+    let toSet = {
+      "NetworkMonitor.throttleData": value,
+    };
+
+    // Make sure the web console client connection is established first.
+    this.webConsoleClient.setPreferences(toSet, response => {
+      if (!response.error) {
+        this._throttleData = value;
+        deferred.resolve(response);
+      } else {
+        deferred.reject(response.error);
+      }
+    });
+
+    return deferred.promise;
+  },
+
+  /**
    * Getter for the persistent logging preference.
    * @type boolean
    */
   get persistLog() {
     // For the browser console, we receive tab navigation
     // when the original top level window we attached to is closed,
     // but we don't want to reset console history and just switch to
     // the next available window.
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -1050,21 +1050,28 @@ WebConsoleActor.prototype =
    * @param object aRequest
    *        The request message - which preferences need to be updated.
    */
   onSetPreferences: function WCA_onSetPreferences(aRequest)
   {
     for (let key in aRequest.preferences) {
       this._prefs[key] = aRequest.preferences[key];
 
-      if (key == "NetworkMonitor.saveRequestAndResponseBodies" &&
-          this.networkMonitor) {
-        this.networkMonitor.saveRequestAndResponseBodies = this._prefs[key];
-        if (this.networkMonitorChild) {
-          this.networkMonitorChild.saveRequestAndResponseBodies = this._prefs[key];
+      if (this.networkMonitor) {
+        if (key == "NetworkMonitor.saveRequestAndResponseBodies") {
+          this.networkMonitor.saveRequestAndResponseBodies = this._prefs[key];
+          if (this.networkMonitorChild) {
+            this.networkMonitorChild.saveRequestAndResponseBodies =
+              this._prefs[key];
+          }
+        } else if (key == "NetworkMonitor.throttleData") {
+          this.networkMonitor.throttleData = this._prefs[key];
+          if (this.networkMonitorChild) {
+            this.networkMonitorChild.throttleData = this._prefs[key];
+          }
         }
       }
     }
     return { updated: Object.keys(aRequest.preferences) };
   },
 
   // ////////////////
   // End of request handlers.
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/shared/webconsole/network-monitor.js
@@ -1472,32 +1472,49 @@ function NetworkMonitorChild(appId, mess
 
 exports.NetworkMonitorChild = NetworkMonitorChild;
 
 NetworkMonitorChild.prototype = {
   appId: null,
   owner: null,
   _netEvents: null,
   _saveRequestAndResponseBodies: true,
+  _throttleData: null,
 
   get saveRequestAndResponseBodies() {
     return this._saveRequestAndResponseBodies;
   },
 
   set saveRequestAndResponseBodies(val) {
     this._saveRequestAndResponseBodies = val;
 
     this._messageManager.sendAsyncMessage("debug:netmonitor:" + this.connID, {
       action: "setPreferences",
       preferences: {
         saveRequestAndResponseBodies: this._saveRequestAndResponseBodies,
       },
     });
   },
 
+  get throttleData() {
+    return this._throttleData;
+  },
+
+  set throttleData(val) {
+    this._throttleData = val;
+
+    this._messageManager.sendAsyncMessage("debug:netmonitor:" + this.connID, {
+      appId: this.appId,
+      action: "setPreferences",
+      preferences: {
+        throttleData: this._throttleData,
+      },
+    });
+  },
+
   init: function () {
     let mm = this._messageManager;
     mm.addMessageListener("debug:netmonitor:" + this.connID + ":newEvent",
                           this._onNewEvent);
     mm.addMessageListener("debug:netmonitor:" + this.connID + ":updateEvent",
                           this._onUpdateEvent);
     mm.sendAsyncMessage("debug:netmonitor:" + this.connID, {
       appId: this.appId,
@@ -1672,18 +1689,19 @@ NetworkMonitorManager.prototype = {
             appId: appId,
           }, this);
           this.netMonitor.init();
         }
         break;
       case "setPreferences": {
         let {preferences} = msg.json;
         for (let key of Object.keys(preferences)) {
-          if (key == "saveRequestAndResponseBodies" && this.netMonitor) {
-            this.netMonitor.saveRequestAndResponseBodies = preferences[key];
+          if ((key == "saveRequestAndResponseBodies" ||
+               key == "throttleData") && this.netMonitor) {
+            this.netMonitor[key] = preferences[key];
           }
         }
         break;
       }
 
       case "stop":
         if (this.netMonitor) {
           this.netMonitor.destroy();
--- a/devtools/shared/webconsole/throttle.js
+++ b/devtools/shared/webconsole/throttle.js
@@ -355,46 +355,64 @@ NetworkThrottleQueue.prototype = {
  *
  * @param {Object} An object with the following attributes:
  * roundTripTimeMean {Number} Mean round trip time in milliseconds.
  * roundTripTimeMax {Number} Maximum round trip time in milliseconds.
  * downloadBPSMean {Number} Mean bytes per second for downloads.
  * downloadBPSMax {Number} Maximum bytes per second for downloads.
  * uploadBPSMean {Number} Mean bytes per second for uploads.
  * uploadBPSMax {Number} Maximum bytes per second for uploads.
+ *
+ * Download throttling will not be done if downloadBPSMean and
+ * downloadBPSMax are <= 0.  Upload throttling will not be done if
+ * uploadBPSMean and uploadBPSMax are <= 0.
  */
 function NetworkThrottleManager({roundTripTimeMean, roundTripTimeMax,
                                  downloadBPSMean, downloadBPSMax,
                                  uploadBPSMean, uploadBPSMax}) {
-  this.downloadQueue =
-    new NetworkThrottleQueue(downloadBPSMean, downloadBPSMax,
-                             roundTripTimeMean, roundTripTimeMax);
-  this.uploadQueue = Cc["@mozilla.org/network/throttlequeue;1"]
-    .createInstance(Ci.nsIInputChannelThrottleQueue);
-  this.uploadQueue.init(uploadBPSMean, uploadBPSMax);
+  if (downloadBPSMax <= 0 && downloadBPSMean <= 0) {
+    this.downloadQueue = null;
+  } else {
+    this.downloadQueue =
+      new NetworkThrottleQueue(downloadBPSMean, downloadBPSMax,
+                               roundTripTimeMean, roundTripTimeMax);
+  }
+  if (uploadBPSMax <= 0 && uploadBPSMean <= 0) {
+    this.uploadQueue = null;
+  } else {
+    this.uploadQueue = Cc["@mozilla.org/network/throttlequeue;1"]
+      .createInstance(Ci.nsIInputChannelThrottleQueue);
+    this.uploadQueue.init(uploadBPSMean, uploadBPSMax);
+  }
 }
 exports.NetworkThrottleManager = NetworkThrottleManager;
 
 NetworkThrottleManager.prototype = {
   /**
    * Create a new NetworkThrottleListener for a given channel and
    * install it using |setNewListener|.
    *
    * @param {nsITraceableChannel} channel the channel to manage
-   * @return {NetworkThrottleListener} the new listener
+   * @return {NetworkThrottleListener} the new listener, or null if
+   *         download throttling is not being done.
    */
   manage: function (channel) {
-    let listener = new NetworkThrottleListener(this.downloadQueue);
-    let originalListener = channel.setNewListener(listener);
-    listener.setOriginalListener(originalListener);
-    return listener;
+    if (this.downloadQueue) {
+      let listener = new NetworkThrottleListener(this.downloadQueue);
+      let originalListener = channel.setNewListener(listener);
+      listener.setOriginalListener(originalListener);
+      return listener;
+    }
+    return null;
   },
 
   /**
    * Throttle uploads taking place on the given channel.
    *
    * @param {nsITraceableChannel} channel the channel to manage
    */
   manageUpload: function (channel) {
-    channel = channel.QueryInterface(Ci.nsIThrottledInputChannel);
-    channel.throttleQueue = this.uploadQueue;
+    if (this.uploadQueue) {
+      channel = channel.QueryInterface(Ci.nsIThrottledInputChannel);
+      channel.throttleQueue = this.uploadQueue;
+    }
   },
 };