Bug 1151368 - Display blocked requests in Network Monitor. r=ochameau,Honza
authorJ. Ryan Stinnett <jryans@gmail.com>
Fri, 19 Apr 2019 18:25:29 +0000
changeset 470248 6b031fd49d1e292c4033e6f86f909a689a78b176
parent 470247 eaf2a199d4eaa2b991bb319bd6df919d793ccf8f
child 470249 b9a2a553d0c4f8b9bd3f98e9680b925c746c5f81
push id112851
push userrgurzau@mozilla.com
push dateSat, 20 Apr 2019 10:03:47 +0000
treeherdermozilla-inbound@a092972b53f0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau, Honza
bugs1151368
milestone68.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 1151368 - Display blocked requests in Network Monitor. r=ochameau,Honza This updates the request list to indicate a request was blocked by marking the entire request item and also replaces transferred size column with "blocked by DevTools". In the future, we may show other reasons for blocking in this way, such as CORS, etc. Differential Revision: https://phabricator.services.mozilla.com/D26580
devtools/client/locales/en-US/netmonitor.properties
devtools/client/netmonitor/src/assets/styles/RequestList.css
devtools/client/netmonitor/src/components/RequestListColumnTransferredSize.js
devtools/client/netmonitor/src/components/RequestListContent.js
devtools/client/netmonitor/src/components/RequestListItem.js
devtools/client/netmonitor/src/connector/firefox-data-provider.js
devtools/client/netmonitor/src/constants.js
devtools/server/actors/network-event.js
devtools/server/actors/network-monitor/network-observer.js
devtools/shared/fronts/webconsole.js
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -225,16 +225,21 @@ networkMenu.sizeUnavailable.title=Transf
 # cached.
 networkMenu.sizeCached=cached
 
 # LOCALIZATION NOTE (networkMenu.sizeServiceWorker): This is the label displayed
 # in the network menu specifying the transferred of a request computed
 # by a service worker.
 networkMenu.sizeServiceWorker=service worker
 
+# LOCALIZATION NOTE (networkMenu.sizeServiceWorker): This is the label displayed
+# in the network menu specifying the request was blocked by something.
+# %S is replaced by the blocked reason, which could be "DevTools", "CORS", etc.
+networkMenu.blockedBy=blocked by %S
+
 # LOCALIZATION NOTE (networkMenu.totalMS2): This is the label displayed
 # in the network menu specifying the time for a request to finish (in milliseconds).
 networkMenu.totalMS2=%S ms
 
 # This string is used to concatenate tooltips (netmonitor.waterfall.tooltip.*)
 # in the requests waterfall for total time (in milliseconds). \\u0020 represents
 # a whitespace. You can replace this with a different character, e.g. an hyphen
 # or a period, if a comma doesn't work for your language.
--- a/devtools/client/netmonitor/src/assets/styles/RequestList.css
+++ b/devtools/client/netmonitor/src/assets/styles/RequestList.css
@@ -485,16 +485,20 @@
 .request-list-item:not(.selected):hover {
   background-color: var(--table-selection-background-hover);
 }
 
 .request-list-item:not(.selected).fromCache > .requests-list-column:not(.requests-list-waterfall) {
   opacity: 0.7;
 }
 
+.request-list-item.blocked {
+  color: var(--timing-blocked-color);
+}
+
 /* Responsive web design support */
 
 @media (max-width: 700px) {
   .requests-list-status-code {
     width: auto;
   }
 
   .requests-list-size {
--- a/devtools/client/netmonitor/src/components/RequestListColumnTransferredSize.js
+++ b/devtools/client/netmonitor/src/components/RequestListColumnTransferredSize.js
@@ -28,20 +28,28 @@ class RequestListColumnTransferredSize e
     };
   }
 
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_TRANSFERRED_PROPS, this.props.item, nextProps.item);
   }
 
   render() {
-    const { fromCache, fromServiceWorker, status, transferredSize } = this.props.item;
+    const {
+      blockedReason,
+      fromCache,
+      fromServiceWorker,
+      status,
+      transferredSize,
+    } = this.props.item;
     let text;
 
-    if (fromCache || status === "304") {
+    if (blockedReason) {
+      text = L10N.getFormatStr("networkMenu.blockedBy", blockedReason);
+    } else if (fromCache || status === "304") {
       text = SIZE_CACHED;
     } else if (fromServiceWorker) {
       text = SIZE_SERVICE_WORKER;
     } else if (typeof transferredSize == "number") {
       text = getFormattedSize(transferredSize);
     } else if (transferredSize === null) {
       text = SIZE_UNAVAILABLE;
     }
--- a/devtools/client/netmonitor/src/components/RequestListContent.js
+++ b/devtools/client/netmonitor/src/components/RequestListContent.js
@@ -320,16 +320,17 @@ class RequestListContent extends Compone
           dom.tbody({
             ref: "rowGroupEl",
             className: "requests-list-row-group",
             tabIndex: 0,
             onKeyDown: this.onKeyDown,
             style: { "--timings-scale": scale, "--timings-rev-scale": 1 / scale },
           },
             displayedRequests.map((item, index) => RequestListItem({
+              blocked: !!item.blockedReason,
               firstRequestStartedMillis,
               fromCache: item.status === "304" || item.fromCache,
               connector,
               columns,
               item,
               index,
               isSelected: item.id === (selectedRequest && selectedRequest.id),
               key: item.id,
--- a/devtools/client/netmonitor/src/components/RequestListItem.js
+++ b/devtools/client/netmonitor/src/components/RequestListItem.js
@@ -119,16 +119,17 @@ const UPDATED_REQ_PROPS = [
 ];
 
 /**
  * Render one row in the request list.
  */
 class RequestListItem extends Component {
   static get propTypes() {
     return {
+      blocked: PropTypes.bool,
       connector: PropTypes.object.isRequired,
       columns: PropTypes.object.isRequired,
       item: PropTypes.object.isRequired,
       index: PropTypes.number.isRequired,
       isSelected: PropTypes.bool.isRequired,
       firstRequestStartedMillis: PropTypes.number.isRequired,
       fromCache: PropTypes.bool,
       onCauseBadgeMouseDown: PropTypes.func.isRequired,
@@ -181,16 +182,17 @@ class RequestListItem extends Component 
       if (this.props.onFocusedNodeChange) {
         this.props.onFocusedNodeChange();
       }
     }
   }
 
   render() {
     const {
+      blocked,
       connector,
       columns,
       item,
       index,
       isSelected,
       firstRequestStartedMillis,
       fromCache,
       onDoubleClick,
@@ -199,16 +201,17 @@ class RequestListItem extends Component 
       onCauseBadgeMouseDown,
       onSecurityIconMouseDown,
       onWaterfallMouseDown,
     } = this.props;
 
     const classList = ["request-list-item", index % 2 ? "odd" : "even"];
     isSelected && classList.push("selected");
     fromCache && classList.push("fromCache");
+    blocked && classList.push("blocked");
 
     return (
       dom.tr({
         ref: "listItem",
         className: classList.join(" "),
         "data-id": item.id,
         tabIndex: 0,
         onContextMenu,
--- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js
+++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js
@@ -68,18 +68,25 @@ class FirefoxDataProvider {
       url,
       isXHR,
       cause,
       startedDateTime,
       fromCache,
       fromServiceWorker,
       isThirdPartyTrackingResource,
       referrerPolicy,
+      blockedReason,
     } = data;
 
+    // Insert blocked reason in the payload queue as well, as we'll need it later
+    // when deciding if the request is complete.
+    this.pushRequestToQueue(id, {
+      blockedReason,
+    });
+
     if (this.actionsEnabled && this.actions.addRequest) {
       await this.actions.addRequest(id, {
         // Convert the received date/time string to a unix timestamp.
         startedMillis: Date.parse(startedDateTime),
         method,
         url,
         isXHR,
         cause,
@@ -88,16 +95,17 @@ class FirefoxDataProvider {
         // send stack-trace immediately on networkEvent message.
         // FF59+ supports fetching the traces lazily via requestData.
         stacktrace: cause.stacktrace,
 
         fromCache,
         fromServiceWorker,
         isThirdPartyTrackingResource,
         referrerPolicy,
+        blockedReason,
       }, true);
     }
 
     this.emit(EVENTS.REQUEST_ADDED, id);
   }
 
   /**
    * Update a network request if it already exists in application state.
@@ -324,28 +332,30 @@ class FirefoxDataProvider {
       isXHR,
       request: {
         method,
         url,
       },
       startedDateTime,
       isThirdPartyTrackingResource,
       referrerPolicy,
+      blockedReason,
     } = networkInfo;
 
     await this.addRequest(actor, {
       cause,
       fromCache,
       fromServiceWorker,
       isXHR,
       method,
       startedDateTime,
       url,
       isThirdPartyTrackingResource,
       referrerPolicy,
+      blockedReason,
     });
 
     this.emit(EVENTS.NETWORK_EVENT, actor);
   }
 
   /**
    * The "networkEventUpdate" message type handler.
    *
@@ -406,18 +416,26 @@ class FirefoxDataProvider {
    * messages contain initial network info for each updateType and then we can invoke
    * requestData to fetch its corresponded data lazily.
    * Once all updateTypes of networkEventUpdate message are arrived, we flush merged
    * request payload from pending queue and then update component.
    */
   async onPayloadDataReceived(actor) {
     const payload = this.payloadQueue.get(actor) || {};
 
-    if (!payload.requestHeadersAvailable || !payload.requestCookiesAvailable ||
-        !payload.eventTimingsAvailable || !payload.responseContentAvailable) {
+    // For blocked requests, we should only expect the request portions and not
+    // the response portions to be available.
+    if (!payload.requestHeadersAvailable || !payload.requestCookiesAvailable) {
+      return;
+    }
+    // For unblocked requests, we should wait for all major portions to be available.
+    if (
+      !payload.blockedReason &&
+      (!payload.eventTimingsAvailable || !payload.responseContentAvailable)
+    ) {
       return;
     }
 
     this.payloadQueue.delete(actor);
 
     if (this.actionsEnabled && this.actions.updateRequest) {
       await this.actions.updateRequest(actor, payload, true);
     }
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -144,16 +144,17 @@ const UPDATE_PROPS = [
   "responseContent",
   "responseContentAvailable",
   "responseCache",
   "responseCacheAvailable",
   "formDataSections",
   "stacktrace",
   "isThirdPartyTrackingResource",
   "referrerPolicy",
+  "blockedReason",
 ];
 
 const PANELS = {
   COOKIES: "cookies",
   HEADERS: "headers",
   PARAMS: "params",
   RESPONSE: "response",
   CACHE: "cache",
--- a/devtools/server/actors/network-event.js
+++ b/devtools/server/actors/network-event.js
@@ -62,16 +62,17 @@ const NetworkEventActor = protocol.Actor
       method: this._request.method,
       isXHR: this._isXHR,
       cause: this._cause,
       fromCache: this._fromCache,
       fromServiceWorker: this._fromServiceWorker,
       private: this._private,
       isThirdPartyTrackingResource: this._isThirdPartyTrackingResource,
       referrerPolicy: this._referrerPolicy,
+      blockedReason: this._blockedReason,
     };
   },
 
   /**
    * Releases this actor from the pool.
    */
   destroy(conn) {
     if (!this.netMonitorActor) {
@@ -122,16 +123,18 @@ const NetworkEventActor = protocol.Actor
     for (const prop of ["method", "url", "httpVersion", "headersSize"]) {
       this._request[prop] = networkEvent[prop];
     }
 
     // Consider as not discarded if networkEvent.discard*Body is undefined
     this._discardRequestBody = !!networkEvent.discardRequestBody;
     this._discardResponseBody = !!networkEvent.discardResponseBody;
 
+    this._blockedReason = networkEvent.blockedReason;
+
     this._truncated = false;
     this._private = networkEvent.private;
   },
 
   /**
    * The "getRequestHeaders" packet type handler.
    *
    * @return object
--- a/devtools/server/actors/network-monitor/network-observer.js
+++ b/devtools/server/actors/network-monitor/network-observer.js
@@ -553,19 +553,28 @@ NetworkObserver.prototype = {
         headers.push({ name: name, value: value });
       },
     });
 
     if (cookieHeader) {
       cookies = NetworkHelper.parseCookieHeader(cookieHeader);
     }
 
+    // Check the request URL with ones manually blocked by the user in DevTools.
+    // If it's meant to be blocked, we cancel the request and annotate the event.
+    if (this.blockedURLs.has(httpActivity.url)) {
+      channel.cancel(Cr.NS_BINDING_ABORTED);
+      event.blockedReason = "DevTools";
+    }
+
     httpActivity.owner = this.owner.onNetworkEvent(event);
 
-    this._setupResponseListener(httpActivity, fromCache);
+    if (!event.blockedReason) {
+      this._setupResponseListener(httpActivity, fromCache);
+    }
 
     httpActivity.owner.addRequestHeaders(headers, extraStringData);
     httpActivity.owner.addRequestCookies(cookies);
 
     return httpActivity;
   },
 
   /**
@@ -668,21 +677,16 @@ NetworkObserver.prototype = {
    * @private
    * @param object httpActivity
    *        The HTTP activity object we are tracking.
    */
   _setupResponseListener: function(httpActivity, fromCache) {
     const channel = httpActivity.channel;
     channel.QueryInterface(Ci.nsITraceableChannel);
 
-    if (this.blockedURLs.has(httpActivity.url)) {
-      channel.cancel(Cr.NS_BINDING_ABORTED);
-      return;
-    }
-
     if (!fromCache) {
       const throttler = this._getThrottler();
       if (throttler) {
         httpActivity.downloadThrottle = throttler.manage(channel);
       }
     }
 
     // The response will be written into the outputStream of this pipe.
--- a/devtools/shared/fronts/webconsole.js
+++ b/devtools/shared/fronts/webconsole.js
@@ -91,16 +91,17 @@ class WebConsoleFront extends FrontClass
       timings: {},
       // track the list of network event updates
       updates: [],
       private: actor.private,
       fromCache: actor.fromCache,
       fromServiceWorker: actor.fromServiceWorker,
       isThirdPartyTrackingResource: actor.isThirdPartyTrackingResource,
       referrerPolicy: actor.referrerPolicy,
+      blockedReason: actor.blockedReason,
     };
     this._networkRequests.set(actor.actor, networkInfo);
 
     this.emit("networkEvent", networkInfo);
   }
 
   /**
    * The "networkEventUpdate" message type handler. We redirect any message to