Bug 1143224 - Add new requests to the netmonitor view asynchronously;r=jsantell
authorBrian Grinstead <bgrinstead@mozilla.com>
Fri, 12 Jun 2015 08:35:38 -0700
changeset 279465 92cf636281bb8770fe8b55ce4f12293bc341fcbf
parent 279464 75bc56b719ce5aec5f587a51a71688fe7387e371
child 279466 d6d15087a8cfd1a8ed6c018389b1ed0ce504cb34
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjsantell
bugs1143224
milestone41.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 1143224 - Add new requests to the netmonitor view asynchronously;r=jsantell
browser/devtools/netmonitor/netmonitor-controller.js
browser/devtools/netmonitor/netmonitor-view.js
browser/devtools/netmonitor/test/browser_net_details-no-duplicated-content.js
browser/devtools/netmonitor/test/head.js
browser/devtools/styleeditor/test/browser_styleeditor_fetch-from-cache.js
browser/devtools/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js
browser/devtools/webconsole/test/browser_webconsole_shows_reqs_in_netmonitor.js
--- a/browser/devtools/netmonitor/netmonitor-controller.js
+++ b/browser/devtools/netmonitor/netmonitor-controller.js
@@ -18,16 +18,19 @@ const EVENTS = {
   TARGET_WILL_NAVIGATE: "NetMonitor:TargetWillNavigate",
   TARGET_DID_NAVIGATE: "NetMonitor:TargetNavigate",
 
   // When a network event is received.
   // See https://developer.mozilla.org/docs/Tools/Web_Console/remoting for
   // more information about what each packet is supposed to deliver.
   NETWORK_EVENT: "NetMonitor:NetworkEvent",
 
+  // When a network event is added to the view
+  REQUEST_ADDED: "NetMonitor:RequestAdded",
+
   // When request headers begin and finish receiving.
   UPDATING_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdating:RequestHeaders",
   RECEIVED_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdated:RequestHeaders",
 
   // When request cookies begin and finish receiving.
   UPDATING_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdating:RequestCookies",
   RECEIVED_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdated:RequestCookies",
 
@@ -523,178 +526,184 @@ NetworkEventsHandler.prototype = {
    *        Message type.
    * @param object packet
    *        The message received from the server.
    * @param object networkInfo
    *        The network request information.
    */
   _onNetworkEventUpdate: function(type, { packet, networkInfo }) {
     let { actor, request: { url } } = networkInfo;
-
     switch (packet.updateType) {
       case "requestHeaders":
         this.webConsoleClient.getRequestHeaders(actor, this._onRequestHeaders);
-        window.emit(EVENTS.UPDATING_REQUEST_HEADERS, [actor, url]);
+        window.emit(EVENTS.UPDATING_REQUEST_HEADERS, actor);
         break;
       case "requestCookies":
         this.webConsoleClient.getRequestCookies(actor, this._onRequestCookies);
-        window.emit(EVENTS.UPDATING_REQUEST_COOKIES, [actor, url]);
+        window.emit(EVENTS.UPDATING_REQUEST_COOKIES, actor);
         break;
       case "requestPostData":
         this.webConsoleClient.getRequestPostData(actor, this._onRequestPostData);
-        window.emit(EVENTS.UPDATING_REQUEST_POST_DATA, [actor, url]);
+        window.emit(EVENTS.UPDATING_REQUEST_POST_DATA, actor);
         break;
       case "securityInfo":
         NetMonitorView.RequestsMenu.updateRequest(actor, {
           securityState: networkInfo.securityInfo,
         });
         this.webConsoleClient.getSecurityInfo(actor, this._onSecurityInfo);
-        window.emit(EVENTS.UPDATING_SECURITY_INFO, [actor, url]);
+        window.emit(EVENTS.UPDATING_SECURITY_INFO, actor);
         break;
       case "responseHeaders":
         this.webConsoleClient.getResponseHeaders(actor, this._onResponseHeaders);
-        window.emit(EVENTS.UPDATING_RESPONSE_HEADERS, [actor, url]);
+        window.emit(EVENTS.UPDATING_RESPONSE_HEADERS, actor);
         break;
       case "responseCookies":
         this.webConsoleClient.getResponseCookies(actor, this._onResponseCookies);
-        window.emit(EVENTS.UPDATING_RESPONSE_COOKIES, [actor, url]);
+        window.emit(EVENTS.UPDATING_RESPONSE_COOKIES, actor);
         break;
       case "responseStart":
         NetMonitorView.RequestsMenu.updateRequest(actor, {
           httpVersion: networkInfo.response.httpVersion,
           remoteAddress: networkInfo.response.remoteAddress,
           remotePort: networkInfo.response.remotePort,
           status: networkInfo.response.status,
           statusText: networkInfo.response.statusText,
           headersSize: networkInfo.response.headersSize
         });
-        window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, [actor, url]);
+        window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
         break;
       case "responseContent":
         NetMonitorView.RequestsMenu.updateRequest(actor, {
           contentSize: networkInfo.response.bodySize,
           transferredSize: networkInfo.response.transferredSize,
           mimeType: networkInfo.response.content.mimeType
         });
         this.webConsoleClient.getResponseContent(actor, this._onResponseContent);
-        window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, [actor, url]);
+        window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
         break;
       case "eventTimings":
         NetMonitorView.RequestsMenu.updateRequest(actor, {
           totalTime: networkInfo.totalTime
         });
         this.webConsoleClient.getEventTimings(actor, this._onEventTimings);
-        window.emit(EVENTS.UPDATING_EVENT_TIMINGS, [actor, url]);
+        window.emit(EVENTS.UPDATING_EVENT_TIMINGS, actor);
         break;
     }
   },
 
   /**
    * Handles additional information received for a "requestHeaders" packet.
    *
    * @param object aResponse
    *        The message received from the server.
    */
   _onRequestHeaders: function(aResponse) {
     NetMonitorView.RequestsMenu.updateRequest(aResponse.from, {
       requestHeaders: aResponse
+    }, () => {
+      window.emit(EVENTS.RECEIVED_REQUEST_HEADERS, aResponse.from);
     });
-    window.emit(EVENTS.RECEIVED_REQUEST_HEADERS, aResponse.from);
   },
 
   /**
    * Handles additional information received for a "requestCookies" packet.
    *
    * @param object aResponse
    *        The message received from the server.
    */
   _onRequestCookies: function(aResponse) {
     NetMonitorView.RequestsMenu.updateRequest(aResponse.from, {
       requestCookies: aResponse
+    }, () => {
+      window.emit(EVENTS.RECEIVED_REQUEST_COOKIES, aResponse.from);
     });
-    window.emit(EVENTS.RECEIVED_REQUEST_COOKIES, aResponse.from);
   },
 
   /**
    * Handles additional information received for a "requestPostData" packet.
    *
    * @param object aResponse
    *        The message received from the server.
    */
   _onRequestPostData: function(aResponse) {
     NetMonitorView.RequestsMenu.updateRequest(aResponse.from, {
       requestPostData: aResponse
+    }, () => {
+      window.emit(EVENTS.RECEIVED_REQUEST_POST_DATA, aResponse.from);
     });
-    window.emit(EVENTS.RECEIVED_REQUEST_POST_DATA, aResponse.from);
   },
 
   /**
    * Handles additional information received for a "securityInfo" packet.
    *
    * @param object aResponse
    *        The message received from the server.
    */
    _onSecurityInfo: function(aResponse) {
      NetMonitorView.RequestsMenu.updateRequest(aResponse.from, {
        securityInfo: aResponse.securityInfo
+     }, () => {
+       window.emit(EVENTS.RECEIVED_SECURITY_INFO, aResponse.from);
      });
-
-     window.emit(EVENTS.RECEIVED_SECURITY_INFO, aResponse.from);
    },
 
   /**
    * Handles additional information received for a "responseHeaders" packet.
    *
    * @param object aResponse
    *        The message received from the server.
    */
   _onResponseHeaders: function(aResponse) {
     NetMonitorView.RequestsMenu.updateRequest(aResponse.from, {
       responseHeaders: aResponse
+    }, () => {
+      window.emit(EVENTS.RECEIVED_RESPONSE_HEADERS, aResponse.from);
     });
-    window.emit(EVENTS.RECEIVED_RESPONSE_HEADERS, aResponse.from);
   },
 
   /**
    * Handles additional information received for a "responseCookies" packet.
    *
    * @param object aResponse
    *        The message received from the server.
    */
   _onResponseCookies: function(aResponse) {
     NetMonitorView.RequestsMenu.updateRequest(aResponse.from, {
       responseCookies: aResponse
+    }, () => {
+      window.emit(EVENTS.RECEIVED_RESPONSE_COOKIES, aResponse.from);
     });
-    window.emit(EVENTS.RECEIVED_RESPONSE_COOKIES, aResponse.from);
   },
 
   /**
    * Handles additional information received for a "responseContent" packet.
    *
    * @param object aResponse
    *        The message received from the server.
    */
   _onResponseContent: function(aResponse) {
     NetMonitorView.RequestsMenu.updateRequest(aResponse.from, {
       responseContent: aResponse
+    }, () => {
+      window.emit(EVENTS.RECEIVED_RESPONSE_CONTENT, aResponse.from);
     });
-    window.emit(EVENTS.RECEIVED_RESPONSE_CONTENT, aResponse.from);
   },
 
   /**
    * Handles additional information received for a "eventTimings" packet.
    *
    * @param object aResponse
    *        The message received from the server.
    */
   _onEventTimings: function(aResponse) {
     NetMonitorView.RequestsMenu.updateRequest(aResponse.from, {
       eventTimings: aResponse
+    }, () => {
+      window.emit(EVENTS.RECEIVED_EVENT_TIMINGS, aResponse.from);
     });
-    window.emit(EVENTS.RECEIVED_EVENT_TIMINGS, aResponse.from);
   },
 
   /**
    * Fetches the full text of a LongString.
    *
    * @param object | string aStringGrip
    *        The long string grip containing the corresponding actor.
    *        If you pass in a plain string (by accident or because you're lazy),
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -70,16 +70,18 @@ const GENERIC_VARIABLES_VIEW_SETTINGS = 
   editableNameTooltip: "",
   preventDisableOnChange: true,
   preventDescriptorModifiers: true,
   eval: () => {}
 };
 const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200; // px
 const FREETEXT_FILTER_SEARCH_DELAY = 200; // ms
 
+const {DeferredTask} = Cu.import("resource://gre/modules/DeferredTask.jsm", {});
+
 /**
  * Object defining the network monitor view components.
  */
 let NetMonitorView = {
   /**
    * Initializes the network monitor view.
    */
   initialize: function() {
@@ -90,16 +92,17 @@ let NetMonitorView = {
     this.NetworkDetails.initialize();
     this.CustomRequest.initialize();
   },
 
   /**
    * Destroys the network monitor view.
    */
   destroy: function() {
+    this._isDestroyed = true;
     this.Toolbar.destroy();
     this.RequestsMenu.destroy();
     this.NetworkDetails.destroy();
     this.CustomRequest.destroy();
 
     this._destroyPanes();
   },
 
@@ -389,16 +392,17 @@ RequestsMenuView.prototype = Heritage.ex
     this._onContextNewTabCommand = this.openRequestInTab.bind(this);
     this._onContextCopyUrlCommand = this.copyUrl.bind(this);
     this._onContextCopyImageAsDataUriCommand = this.copyImageAsDataUri.bind(this);
     this._onContextCopyResponseCommand = this.copyResponse.bind(this);
     this._onContextResendCommand = this.cloneSelectedRequest.bind(this);
     this._onContextToggleRawHeadersCommand = this.toggleRawHeaders.bind(this);
     this._onContextPerfCommand = () => NetMonitorView.toggleFrontendMode();
     this._onReloadCommand = () => NetMonitorView.reloadPage();
+    this._flushRequestsTask = new DeferredTask(this._flushRequests, REQUESTS_REFRESH_RATE);
 
     this.sendCustomRequestEvent = this.sendCustomRequest.bind(this);
     this.closeCustomRequestEvent = this.closeCustomRequest.bind(this);
     this.cloneSelectedRequestEvent = this.cloneSelectedRequest.bind(this);
     this.toggleRawHeadersEvent = this.toggleRawHeaders.bind(this);
 
     this.requestsFreetextFilterEvent = this.requestsFreetextFilterEvent.bind(this);
     this.reFilterRequests = this.reFilterRequests.bind(this);
@@ -465,17 +469,20 @@ RequestsMenuView.prototype = Heritage.ex
     this._splitter.removeEventListener("mousemove", this._onResize, false);
     window.removeEventListener("resize", this._onResize, false);
 
     $("#toolbar-labels").removeEventListener("click", this.requestsMenuSortEvent, false);
     $("#requests-menu-footer").removeEventListener("click", this.requestsMenuFilterEvent, false);
     $("#requests-menu-clear-button").removeEventListener("click", this.reqeustsMenuClearEvent, false);
     this.freetextFilterBox.removeEventListener("input", this.requestsFreetextFilterEvent, false);
     this.freetextFilterBox.removeEventListener("command", this.requestsFreetextFilterEvent, false);
+
     this.userInputTimer.cancel();
+    this._flushRequestsTask.disarm();
+
     $("#network-request-popup").removeEventListener("popupshowing", this._onContextShowing, false);
     $("#request-menu-context-newtab").removeEventListener("command", this._onContextNewTabCommand, false);
     $("#request-menu-context-copy-url").removeEventListener("command", this._onContextCopyUrlCommand, false);
     $("#request-menu-context-copy-response").removeEventListener("command", this._onContextCopyResponseCommand, false);
     $("#request-menu-context-copy-image-as-data-uri").removeEventListener("command", this._onContextCopyImageAsDataUriCommand, false);
     $("#request-menu-context-resend").removeEventListener("command", this._onContextResendCommand, false);
     $("#request-menu-context-perf").removeEventListener("command", this._onContextPerfCommand, false);
 
@@ -491,24 +498,37 @@ RequestsMenuView.prototype = Heritage.ex
     $("#toggle-raw-headers").removeEventListener("click", this.toggleRawHeadersEvent, false);
   },
 
   /**
    * Resets this container (removes all the networking information).
    */
   reset: function() {
     this.empty();
+    this._addQueue = [];
+    this._updateQueue = [];
     this._firstRequestStartedMillis = -1;
     this._lastRequestEndedMillis = -1;
   },
 
   /**
    * Specifies if this view may be updated lazily.
    */
-  lazyUpdate: true,
+  _lazyUpdate: true,
+
+  get lazyUpdate() {
+    return this._lazyUpdate;
+  },
+
+  set lazyUpdate(value) {
+    this._lazyUpdate = value;
+    if (!value) {
+      this._flushRequests();
+    }
+  },
 
   /**
    * Adds a network request to this container.
    *
    * @param string aId
    *        An identifier coming from the network monitor controller.
    * @param string aStartedDateTime
    *        A string representation of when the request was started, which
@@ -518,57 +538,24 @@ RequestsMenuView.prototype = Heritage.ex
    * @param string aUrl
    *        Specifies the request's url.
    * @param boolean aIsXHR
    *        True if this request was initiated via XHR.
    * @param boolean aFromCache
    *        Indicates if the result came from the browser cache
    */
   addRequest: function(aId, aStartedDateTime, aMethod, aUrl, aIsXHR, aFromCache) {
-    // Convert the received date/time string to a unix timestamp.
-    let unixTime = Date.parse(aStartedDateTime);
-
-    // Create the element node for the network request item.
-    let menuView = this._createMenuView(aMethod, aUrl);
-
-    // Remember the first and last event boundaries.
-    this._registerFirstRequestStart(unixTime);
-    this._registerLastRequestEnd(unixTime);
-
-    // Append a network request item to this container.
-    let requestItem = this.push([menuView, aId], {
-      attachment: {
-        startedDeltaMillis: unixTime - this._firstRequestStartedMillis,
-        startedMillis: unixTime,
-        method: aMethod,
-        url: aUrl,
-        isXHR: aIsXHR,
-        fromCache: aFromCache
-      }
-    });
-
-    // Create a tooltip for the newly appended network request item.
-    let requestTooltip = requestItem.attachment.tooltip = new Tooltip(document, {
-      closeOnEvents: [{
-        emitter: $("#requests-menu-contents"),
-        event: "scroll",
-        useCapture: true
-      }]
-    });
-
-    $("#details-pane-toggle").disabled = false;
-    $("#requests-menu-empty-notice").hidden = true;
-
-    this.refreshSummary();
-    this.refreshZebra();
-    this.refreshTooltip(requestItem);
-
-    if (aId == this._preferredItemId) {
-      this.selectedItem = requestItem;
+    this._addQueue.push([aId, aStartedDateTime, aMethod, aUrl, aIsXHR, aFromCache]);
+
+    // Lazy updating is disabled in some tests.
+    if (!this.lazyUpdate) {
+      return void this._flushRequests();
     }
+
+    this._flushRequestsTask.arm();
   },
 
   /**
    * Opens selected item in a new tab.
    */
   openRequestInTab: function() {
     let win = Services.wm.getMostRecentWindow("navigator:browser");
     let selected = this.selectedItem.attachment;
@@ -1321,40 +1308,84 @@ RequestsMenuView.prototype = Heritage.ex
   /**
    * Schedules adding additional information to a network request.
    *
    * @param string aId
    *        An identifier coming from the network monitor controller.
    * @param object aData
    *        An object containing several { key: value } tuples of network info.
    *        Supported keys are "httpVersion", "status", "statusText" etc.
+   * @param function aCallback
+   *        A function to call once the request has been updated in the view.
    */
-  updateRequest: function(aId, aData) {
-    // Prevent interference from zombie updates received after target closed.
-    if (NetMonitorView._isDestroyed) {
-      return;
-    }
-    this._updateQueue.push([aId, aData]);
+  updateRequest: function(aId, aData, aCallback) {
+    this._updateQueue.push([aId, aData, aCallback]);
 
     // Lazy updating is disabled in some tests.
     if (!this.lazyUpdate) {
       return void this._flushRequests();
     }
-    // Allow requests to settle down first.
-    setNamedTimeout(
-      "update-requests", REQUESTS_REFRESH_RATE, () => this._flushRequests());
+
+    this._flushRequestsTask.arm();
   },
 
   /**
    * Starts adding all queued additional information about network requests.
    */
   _flushRequests: function() {
+    // Prevent displaying any updates received after the target closed.
+    if (NetMonitorView._isDestroyed) {
+      return;
+    }
+
+    for (let [id, startedDateTime, method, url, isXHR, fromCache] of this._addQueue) {
+      // Convert the received date/time string to a unix timestamp.
+      let unixTime = Date.parse(startedDateTime);
+
+      // Create the element node for the network request item.
+      let menuView = this._createMenuView(method, url);
+
+      // Remember the first and last event boundaries.
+      this._registerFirstRequestStart(unixTime);
+      this._registerLastRequestEnd(unixTime);
+
+      // Append a network request item to this container.
+      let requestItem = this.push([menuView, id], {
+        attachment: {
+          startedDeltaMillis: unixTime - this._firstRequestStartedMillis,
+          startedMillis: unixTime,
+          method: method,
+          url: url,
+          isXHR: isXHR,
+          fromCache: fromCache
+        }
+      });
+
+      // Create a tooltip for the newly appended network request item.
+      let requestTooltip = requestItem.attachment.tooltip = new Tooltip(document, {
+        closeOnEvents: [{
+          emitter: $("#requests-menu-contents"),
+          event: "scroll",
+          useCapture: true
+        }]
+      });
+
+      this.refreshTooltip(requestItem);
+
+      if (id == this._preferredItemId) {
+        this.selectedItem = requestItem;
+      }
+
+      window.emit(EVENTS.REQUEST_ADDED, id);
+    }
+
+
     // For each queued additional information packet, get the corresponding
     // request item in the view and update it based on the specified data.
-    for (let [id, data] of this._updateQueue) {
+    for (let [id, data, callback] of this._updateQueue) {
       let requestItem = this.getItemByValue(id);
       if (!requestItem) {
         // Packet corresponds to a dead request item, target navigated.
         continue;
       }
 
       // Each information packet may contain several { key: value } tuples of
       // network info, so update the view based on each one.
@@ -1477,16 +1508,20 @@ RequestsMenuView.prototype = Heritage.ex
             requestItem.attachment.eventTimings = value;
             this._createWaterfallView(
               requestItem, value.timings, requestItem.attachment.fromCache
             );
             break;
         }
       }
       refreshNetworkDetailsPaneIfNecessary(requestItem);
+
+      if (callback) {
+        callback();
+      }
     }
 
     /**
      * Refreshes the information displayed in the sidebar, in case this update
      * may have additional information about a request which isn't shown yet
      * in the network details pane.
      *
      * @param object aRequestItem
@@ -1497,16 +1532,20 @@ RequestsMenuView.prototype = Heritage.ex
       let selectedItem = NetMonitorView.RequestsMenu.selectedItem;
       if (selectedItem == aRequestItem) {
         NetMonitorView.NetworkDetails.populate(selectedItem.attachment);
       }
     }
 
     // We're done flushing all the requests, clear the update queue.
     this._updateQueue = [];
+    this._addQueue = [];
+
+    $("#details-pane-toggle").disabled = !this.itemCount;
+    $("#requests-menu-empty-notice").hidden = !!this.itemCount;
 
     // Make sure all the requests are sorted and filtered.
     // Freshly added requests may not yet contain all the information required
     // for sorting and filtering predicates, so this is done each time the
     // network requests table is flushed (don't worry, events are drained first
     // so this doesn't happen once per network event update).
     this.sortContents();
     this.filterContents();
@@ -1645,19 +1684,19 @@ RequestsMenuView.prototype = Heritage.ex
         let node = $(".requests-menu-type", target);
         let text = CONTENT_MIME_TYPE_ABBREVIATIONS[type] || type;
         node.setAttribute("value", text);
         node.setAttribute("tooltiptext", aValue);
         break;
       }
       case "responseContent": {
         let { mimeType } = aItem.attachment;
-        let { text, encoding } = aValue.content;
 
         if (mimeType.includes("image/")) {
+          let { text, encoding } = aValue.content;
           let responseBody = yield gNetwork.getString(text);
           let node = $(".requests-menu-icon", aItem.target);
           node.src = "data:" + mimeType + ";" + encoding + "," + responseBody;
           node.setAttribute("type", "thumbnail");
           node.removeAttribute("hidden");
 
           window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
         }
@@ -2153,16 +2192,17 @@ RequestsMenuView.prototype = Heritage.ex
   _splitter: null,
   _summary: null,
   _canvas: null,
   _ctx: null,
   _cachedWaterfallWidth: 0,
   _firstRequestStartedMillis: -1,
   _lastRequestEndedMillis: -1,
   _updateQueue: [],
+  _addQueue: [],
   _updateTimeout: null,
   _resizeTimeout: null,
   _activeFilters: ["all"],
   _currentFreetextFilter: ""
 });
 
 /**
  * Functions handling the sidebar details view.
--- a/browser/devtools/netmonitor/test/browser_net_details-no-duplicated-content.js
+++ b/browser/devtools/netmonitor/test/browser_net_details-no-duplicated-content.js
@@ -90,16 +90,21 @@ let test = Task.async(function* () {
       waitForNetworkEvents(monitor, 0, 1) : waitForNetworkEvents(monitor, 1);
 
     info("Performing a request");
     debuggee.performRequests(1, uri);
 
     info("Waiting for NETWORK_EVENT");
     yield onNetworkEvent;
 
+    if (!RequestsMenu.getItemAtIndex(0)) {
+      info("Waiting for the request to be added to the view")
+      yield monitor.panelWin.once(monitor.panelWin.EVENTS.REQUEST_ADDED);
+    }
+
     ok(true, "Received NETWORK_EVENT. Selecting the item.");
     let item = RequestsMenu.getItemAtIndex(0);
     RequestsMenu.selectedItem = item;
 
     info("Item selected. Waiting for NETWORKDETAILSVIEW_POPULATED");
     yield onDetailsPopulated;
 
     info("Received populated event. Selecting tab at index " + tabIndex);
--- a/browser/devtools/netmonitor/test/head.js
+++ b/browser/devtools/netmonitor/test/head.js
@@ -235,30 +235,34 @@ function waitForNetworkEvents(aMonitor, 
     maybeResolve(event, actor);
   }
 
   function onPostEvent(event, actor) {
     postEvents++;
     maybeResolve(event, actor);
   }
 
-  function maybeResolve(event, [actor, url]) {
+  function maybeResolve(event, actor) {
     info("> Network events progress: " +
       genericEvents + "/" + ((aGetRequests + aPostRequests) * 13) + ", " +
       postEvents + "/" + (aPostRequests * 2) + ", " +
       "got " + event + " for " + actor);
 
+    let networkInfo =
+      panel.NetMonitorController.webConsoleClient.getNetworkRequest(actor)
+    let url = networkInfo.request.url;
     updateProgressForURL(url, event);
+
     info("> Current state: " + JSON.stringify(progress, null, 2));
 
     // There are 15 updates which need to be fired for a request to be
     // considered finished. The "requestPostData" packet isn't fired for
     // non-POST requests.
-    if (genericEvents == (aGetRequests + aPostRequests) * 13 &&
-        postEvents == aPostRequests * 2) {
+    if (genericEvents >= (aGetRequests + aPostRequests) * 13 &&
+        postEvents >= aPostRequests * 2) {
 
       awaitedEventsToListeners.forEach(([e, l]) => panel.off(events[e], l));
       executeSoon(deferred.resolve);
     }
   }
 
   awaitedEventsToListeners.forEach(([e, l]) => panel.on(events[e], l));
   return deferred.promise;
--- a/browser/devtools/styleeditor/test/browser_styleeditor_fetch-from-cache.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_fetch-from-cache.js
@@ -12,16 +12,17 @@ add_task(function*() {
   let isTesting = gDevTools.testing;
   gDevTools.testing = true;
 
   info("Opening netmonitor");
   let tab = yield addTab("about:blank");
   let target = TargetFactory.forTab(tab);
   let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
   let netmonitor = toolbox.getPanel("netmonitor");
+  netmonitor._view.RequestsMenu.lazyUpdate = false;
 
   info("Navigating to test page");
   yield navigateTo(TEST_URL);
 
   info("Opening Style Editor");
   let styleeditor = yield toolbox.selectTool("styleeditor");
 
   info("Waiting for the source to be loaded.");
--- a/browser/devtools/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js
+++ b/browser/devtools/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js
@@ -58,14 +58,16 @@ function loadDocument(browser) {
   content.location = TEST_PATH;
 
   return deferred.promise;
 }
 
 function testNetmonitor(toolbox) {
   let monitor = toolbox.getCurrentPanel();
   let { RequestsMenu } = monitor.panelWin.NetMonitorView;
+  RequestsMenu.lazyUpdate = false;
+
   is(RequestsMenu.itemCount, 1, "Network request appears in the network panel");
 
   let item = RequestsMenu.getItemAtIndex(0);
   is(item.attachment.method, "GET", "The attached method is correct.");
   is(item.attachment.url, TEST_PATH, "The attached url is correct.");
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_shows_reqs_in_netmonitor.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_shows_reqs_in_netmonitor.js
@@ -58,14 +58,15 @@ function loadDocument(browser) {
   content.location = TEST_PATH;
 
   return deferred.promise;
 }
 
 function testNetmonitor(toolbox) {
   let monitor = toolbox.getCurrentPanel();
   let { RequestsMenu } = monitor.panelWin.NetMonitorView;
+  RequestsMenu.lazyUpdate = false;
   is(RequestsMenu.itemCount, 1, "Network request appears in the network panel");
 
   let item = RequestsMenu.getItemAtIndex(0);
   is(item.attachment.method, "GET", "The attached method is correct.");
   is(item.attachment.url, TEST_PATH, "The attached url is correct.");
 }