Bug 1644275 - Add a new Channel map r=Honza
authorHubert Boma Manilla <hmanilla@mozilla.com>
Thu, 18 Jun 2020 14:44:12 +0000
changeset 536330 615cd7d5434a6a4e6c325a3a07ceb437a6dbf8bf
parent 536329 a744ed2bcb3d484b3b32ad77a9d9883c206bfca4
child 536331 5744f613b84c4da504663acea05986beb0cced86
push id119392
push userhmanilla@mozilla.com
push dateThu, 18 Jun 2020 14:49:45 +0000
treeherderautoland@615cd7d5434a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza
bugs1644275
milestone79.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 1644275 - Add a new Channel map r=Honza Differential Revision: https://phabricator.services.mozilla.com/D79137
devtools/server/actors/network-monitor/network-observer.js
devtools/server/actors/network-monitor/network-response-listener.js
devtools/server/actors/network-monitor/utils/channel-map.js
devtools/server/actors/network-monitor/utils/moz.build
--- a/devtools/server/actors/network-monitor/network-observer.js
+++ b/devtools/server/actors/network-monitor/network-observer.js
@@ -5,16 +5,22 @@
 "use strict";
 
 const { Cc, Ci, Cr, Cu } = require("chrome");
 const Services = require("Services");
 const flags = require("devtools/shared/flags");
 const {
   wildcardToRegExp,
 } = require("devtools/server/actors/network-monitor/utils/wildcard-to-regexp");
+loader.lazyRequireGetter(
+  this,
+  "ChannelMap",
+  "devtools/server/actors/network-monitor/utils/channel-map",
+  true
+);
 
 loader.lazyRequireGetter(
   this,
   "NetworkHelper",
   "devtools/shared/webconsole/network-helper"
 );
 loader.lazyRequireGetter(
   this,
@@ -152,18 +158,18 @@ exports.matchRequest = matchRequest;
  *          given the initial network request information as an argument.
  *          onNetworkEvent() must return an object which holds several add*()
  *          methods which are used to add further network request/response information.
  */
 function NetworkObserver(filters, owner) {
   this.filters = filters;
   this.owner = owner;
 
-  this.openRequests = new WeakMap();
-  this.openResponses = new WeakMap();
+  this.openRequests = new ChannelMap();
+  this.openResponses = new ChannelMap();
 
   this.blockedURLs = [];
 
   this._httpResponseExaminer = DevToolsUtils.makeInfallible(
     this._httpResponseExaminer
   ).bind(this);
   this._httpModifyExaminer = DevToolsUtils.makeInfallible(
     this._httpModifyExaminer
@@ -463,20 +469,23 @@ NetworkObserver.prototype = {
       // Service worker requests emits cached-response notification on non-e10s,
       // and we fake one on e10s.
       const fromServiceWorker = this.interceptedChannels.has(channel);
       this.interceptedChannels.delete(channel);
 
       // If this is a cached response, there never was a request event
       // so we need to construct one here so the frontend gets all the
       // expected events.
-      const httpActivity = this._createNetworkEvent(channel, {
-        fromCache: !fromServiceWorker,
-        fromServiceWorker: fromServiceWorker,
-      });
+      let httpActivity = this.createOrGetActivityObject(channel);
+      if (!httpActivity.owner) {
+        httpActivity = this._createNetworkEvent(channel, {
+          fromCache: !fromServiceWorker,
+          fromServiceWorker: fromServiceWorker,
+        });
+      }
       httpActivity.owner.addResponseStart(
         {
           httpVersion: response.httpVersion,
           remoteAddress: "",
           remotePort: "",
           status: response.status,
           statusText: response.statusText,
           headersSize: 0,
@@ -857,17 +866,17 @@ NetworkObserver.prototype = {
    * Find an HTTP activity object for the channel.
    *
    * @param nsIHttpChannel channel
    *        The HTTP channel whose activity object we want to find.
    * @return object
    *        The HTTP activity object, or null if it is not found.
    */
   _findActivityObject: function(channel) {
-    return this.openRequests.get(channel) || null;
+    return this.openRequests.getChannelById(channel.channelId);
   },
 
   /**
    * Find an existing HTTP activity object, or create a new one. This
    * object is used for storing all the request and response
    * information.
    *
    * This is a HAR-like object. Conformance to the spec is not guaranteed at
--- a/devtools/server/actors/network-monitor/network-response-listener.js
+++ b/devtools/server/actors/network-monitor/network-response-listener.js
@@ -405,20 +405,24 @@ NetworkResponseListener.prototype = {
    * @private
    */
   _findOpenResponse: function() {
     if (!this.owner || this._foundOpenResponse) {
       return;
     }
 
     const channel = this.httpActivity.channel;
-    const openResponse = this.owner.openResponses.get(channel);
+    const openResponse = this.owner.openResponses.getChannelById(
+      channel.channelId
+    );
+
     if (!openResponse) {
       return;
     }
+
     this._foundOpenResponse = true;
     this.owner.openResponses.delete(channel);
 
     this.httpActivity.owner.addResponseHeaders(openResponse.headers);
     this.httpActivity.owner.addResponseCookies(openResponse.cookies);
   },
 
   /**
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/network-monitor/utils/channel-map.js
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* global FinalizationRegistry, WeakRef */
+
+/**
+ * This object implements iterable weak map for HTTP channels tracked by
+ * the network observer.
+ *
+ * We can't use Map() for storing HTTP channel references since we don't
+ * know when we should remove the entry in it (it's wrong to do it in
+ * 'onTransactionClose' since it doesn't have to be the last platform
+ * notification for a given channel). We want the map to auto update
+ * when the channel is garbage collected.
+ *
+ * We can't use WeakMap() since searching for a value by the channel object
+ * isn't reliable (there might be different objects representing the same
+ * channel). We need to search by channel ID, but ID can't be used as key
+ * in WeakMap().
+ *
+ * So, this custom map solves aforementioned issues.
+ */
+class ChannelMap {
+  constructor() {
+    this.weakMap = new WeakMap();
+    this.refSet = new Set();
+    this.finalizationGroup = new FinalizationRegistry(ChannelMap.cleanup);
+  }
+
+  static cleanup({ set, ref }) {
+    set.delete(ref);
+  }
+
+  set(channel, value) {
+    const ref = new WeakRef(channel);
+    this.weakMap.set(channel, { value, ref });
+    this.refSet.add(ref);
+    this.finalizationGroup.register(
+      channel,
+      {
+        set: this.refSet,
+        ref,
+      },
+      ref
+    );
+  }
+
+  getChannelById(channelId) {
+    for (const ref of this.refSet) {
+      const key = ref.deref();
+      if (!key) {
+        continue;
+      }
+      const { value } = this.weakMap.get(key);
+      if (value.channel.channelId === channelId) {
+        return value;
+      }
+    }
+    return null;
+  }
+
+  delete(channel) {
+    const entry = this.weakMap.get(channel);
+    if (!entry) {
+      return false;
+    }
+
+    this.weakMap.delete(channel);
+    this.refSet.delete(entry.ref);
+    this.finalizationGroup.unregister(entry.ref);
+    return true;
+  }
+}
+
+exports.ChannelMap = ChannelMap;
--- a/devtools/server/actors/network-monitor/utils/moz.build
+++ b/devtools/server/actors/network-monitor/utils/moz.build
@@ -1,9 +1,10 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
+    'channel-map.js',
     'wildcard-to-regexp.js'
 )