Bug 1525502 - Part 2: Add an MRU cache for CheckContentBlockingAllowList(); r=baku
authorEhsan Akhgari <ehsan@mozilla.com>
Thu, 07 Feb 2019 15:21:58 +0000
changeset 457625 02e125fcde8ad98bfcf4e7852f7a03798a9b48d4
parent 457624 b9d970f20f18e13bdf99f208c945bd3252c59fc8
child 457626 023ecfc5cf46837719be435324942c5c1532b7a8
push id35516
push userrmaries@mozilla.com
push dateFri, 08 Feb 2019 04:23:26 +0000
treeherdermozilla-central@d599d1a73a3a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1525502
milestone67.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 1525502 - Part 2: Add an MRU cache for CheckContentBlockingAllowList(); r=baku Depends on D18779 Differential Revision: https://phabricator.services.mozilla.com/D18780
toolkit/components/antitracking/AntiTrackingCommon.cpp
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp
+++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp
@@ -4,18 +4,20 @@
  * 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/. */
 
 #include "AntiTrackingCommon.h"
 
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/AbstractThread.h"
+#include "mozilla/HashFunctions.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Logging.h"
+#include "mozilla/MruCache.h"
 #include "mozilla/Pair.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozIThirdPartyUtil.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindowInner.h"
 #include "nsCookiePermission.h"
 #include "nsICookieService.h"
 #include "nsIDocShell.h"
@@ -198,16 +200,80 @@ int32_t CookiesBehavior(nsIPrincipal* aT
           a3rdPartyURI->SchemeIs("moz-extension", &is3rdPartyMozExt)) &&
       is3rdPartyMozExt) {
     return nsICookieService::BEHAVIOR_ACCEPT;
   }
 
   return StaticPrefs::network_cookie_cookieBehavior();
 }
 
+struct ContentBlockingAllowListKey {
+  ContentBlockingAllowListKey() : mHash(mozilla::HashGeneric(uintptr_t(0))) {}
+
+  // Ensure that we compute a different hash for window and channel pointers of
+  // the same numeric value, in the off chance that we get unlucky and encounter
+  // a case where the allocator reallocates a window object where a channel used
+  // to live and vice versa.
+  explicit ContentBlockingAllowListKey(nsPIDOMWindowInner* aWindow)
+      : mHash(mozilla::AddToHash(uintptr_t(aWindow),
+                                 mozilla::HashString("window"))) {}
+  explicit ContentBlockingAllowListKey(nsIChannel* aChannel)
+      : mHash(mozilla::AddToHash(uintptr_t(aChannel),
+                                 mozilla::HashString("channel"))) {}
+
+  ContentBlockingAllowListKey(const ContentBlockingAllowListKey& aRHS)
+      : mHash(aRHS.mHash) {}
+
+  bool operator==(const ContentBlockingAllowListKey& aRHS) const {
+    return mHash == aRHS.mHash;
+  }
+
+  HashNumber GetHash() const { return mHash; }
+
+ private:
+  HashNumber mHash;
+};
+
+struct ContentBlockingAllowListEntry {
+  ContentBlockingAllowListEntry() : mResult(false) {}
+  ContentBlockingAllowListEntry(nsPIDOMWindowInner* aWindow, bool aResult)
+      : mKey(aWindow), mResult(aResult) {}
+  ContentBlockingAllowListEntry(nsIChannel* aChannel, bool aResult)
+      : mKey(aChannel), mResult(aResult) {}
+
+  ContentBlockingAllowListKey mKey;
+  bool mResult;
+};
+
+struct ContentBlockingAllowListCache
+    : MruCache<ContentBlockingAllowListKey, ContentBlockingAllowListEntry,
+               ContentBlockingAllowListCache> {
+  static HashNumber Hash(const ContentBlockingAllowListKey& aKey) {
+    return aKey.GetHash();
+  }
+  static bool Match(const ContentBlockingAllowListKey& aKey,
+                    const ContentBlockingAllowListEntry& aValue) {
+    return aValue.mKey == aKey;
+  }
+};
+
+ContentBlockingAllowListCache& GetContentBlockingAllowListCache() {
+  static bool initialized = false;
+  static ContentBlockingAllowListCache cache;
+  if (!initialized) {
+    AntiTrackingCommon::OnAntiTrackingSettingsChanged([&] {
+      // Drop everything in the cache, since the result of content blocking
+      // allow list checks may change past this point.
+      cache.Clear();
+    });
+    initialized = true;
+  }
+  return cache;
+}
+
 bool CheckContentBlockingAllowList(nsIURI* aTopWinURI,
                                    bool aIsPrivateBrowsing) {
   bool isAllowed = false;
   nsresult rv = AntiTrackingCommon::IsOnContentBlockingAllowList(
       aTopWinURI, aIsPrivateBrowsing, AntiTrackingCommon::eStorageChecks,
       isAllowed);
   if (NS_SUCCEEDED(rv) && isAllowed) {
     LOG_SPEC(
@@ -222,45 +288,75 @@ bool CheckContentBlockingAllowList(nsIUR
         ("Checking the content blocking allow list for %s failed with %" PRIx32,
          _spec, static_cast<uint32_t>(rv)),
         aTopWinURI);
   }
   return false;
 }
 
 bool CheckContentBlockingAllowList(nsPIDOMWindowInner* aWindow) {
+  ContentBlockingAllowListKey cacheKey(aWindow);
+  auto entry = GetContentBlockingAllowListCache().Lookup(cacheKey);
+  if (entry) {
+    // We've recently performed a content blocking allow list check for this
+    // window, so let's quickly return the answer instead of continuing with the
+    // rest of this potentially expensive computation.
+    return entry.Data().mResult;
+  }
+
   nsPIDOMWindowOuter* top = aWindow->GetScriptableTop();
   if (top) {
     nsIURI* topWinURI = top->GetDocumentURI();
     Document* doc = top->GetExtantDoc();
     bool isPrivateBrowsing =
         doc ? nsContentUtils::IsInPrivateBrowsing(doc) : false;
-    return CheckContentBlockingAllowList(topWinURI, isPrivateBrowsing);
+
+    const bool result =
+        CheckContentBlockingAllowList(topWinURI, isPrivateBrowsing);
+
+    entry.Set(ContentBlockingAllowListEntry(aWindow, result));
+
+    return result;
   }
 
   LOG(
       ("Could not check the content blocking allow list because the top "
        "window wasn't accessible"));
+  entry.Set(ContentBlockingAllowListEntry(aWindow, false));
   return false;
 }
 
 bool CheckContentBlockingAllowList(nsIHttpChannel* aChannel) {
+  ContentBlockingAllowListKey cacheKey(aChannel);
+  auto entry = GetContentBlockingAllowListCache().Lookup(cacheKey);
+  if (entry) {
+    // We've recently performed a content blocking allow list check for this
+    // channel, so let's quickly return the answer instead of continuing with
+    // the rest of this potentially expensive computation.
+    return entry.Data().mResult;
+  }
+
   nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(aChannel);
   if (chan) {
     nsCOMPtr<nsIURI> topWinURI;
     nsresult rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI));
     if (NS_SUCCEEDED(rv)) {
-      return CheckContentBlockingAllowList(topWinURI,
-                                           NS_UsePrivateBrowsing(aChannel));
+      const bool result = CheckContentBlockingAllowList(
+          topWinURI, NS_UsePrivateBrowsing(aChannel));
+
+      entry.Set(ContentBlockingAllowListEntry(aChannel, result));
+
+      return result;
     }
   }
 
   LOG(
       ("Could not check the content blocking allow list because the top "
        "window wasn't accessible"));
+  entry.Set(ContentBlockingAllowListEntry(aChannel, false));
   return false;
 }
 
 void ReportBlockingToConsole(nsPIDOMWindowOuter* aWindow, nsIURI* aURI,
                              uint32_t aRejectedReason) {
   MOZ_ASSERT(aWindow && aURI);
   MOZ_ASSERT(
       aRejectedReason == 0 ||