Bug 1522630 - Report content blocking status notifications from the idle event queue; r=baku
☠☠ backed out by e38ad4c324cf ☠ ☠
authorEhsan Akhgari <ehsan@mozilla.com>
Thu, 24 Jan 2019 16:21:26 -0500
changeset 515465 4f5bfb9b1383cbf8485b97d3f72f7d1607016d11
parent 515464 efd6f48633c20ab86e2b019dfbe2cf5f6bd222ee
child 515466 e38ad4c324cfe4418845286b0e38aaec26532f5c
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1522630
milestone66.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 1522630 - Report content blocking status notifications from the idle event queue; r=baku Differential Revision: https://phabricator.services.mozilla.com/D17554
browser/base/content/test/trackingUI/browser_trackingUI_animation.js
browser/base/content/test/trackingUI/browser_trackingUI_pbmode_exceptions.js
browser/base/content/test/trackingUI/browser_trackingUI_state.js
browser/base/content/test/trackingUI/browser_trackingUI_state_all_disabled.js
dom/base/nsGlobalWindowOuter.cpp
--- a/browser/base/content/test/trackingUI/browser_trackingUI_animation.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_animation.js
@@ -15,26 +15,28 @@ add_task(async function testShieldAnimat
   Services.prefs.setBoolPref(TP_PREF, true);
 
   let tab = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
 
   let animationIcon = document.getElementById("tracking-protection-icon-animatable-image");
   let noAnimationIcon = document.getElementById("tracking-protection-icon");
 
   Services.prefs.setBoolPref(ANIMATIONS_PREF, true);
-  await promiseTabLoadEvent(tab, TRACKING_PAGE);
+  await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
+                     waitForContentBlockingEvent(2, tab.linkedBrowser.ownerGlobal)]);
   ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden when animations are enabled");
   ok(BrowserTestUtils.is_visible(animationIcon), "the animated icon is shown when animations are enabled");
 
   await promiseTabLoadEvent(tab, BENIGN_PAGE);
   ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden");
   ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden");
 
   Services.prefs.setBoolPref(ANIMATIONS_PREF, false);
-  await promiseTabLoadEvent(tab, TRACKING_PAGE);
+  await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
+                     waitForContentBlockingEvent(2, tab.linkedBrowser.ownerGlobal)]);
   ok(BrowserTestUtils.is_visible(noAnimationIcon), "the default icon is shown when animations are disabled");
   ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden when animations are disabled");
 
   gBrowser.removeCurrentTab();
   Services.prefs.clearUserPref(ANIMATIONS_PREF);
   Services.prefs.clearUserPref(TP_PREF);
   UrlClassifierTestUtils.cleanupTestTrackers();
 });
--- a/browser/base/content/test/trackingUI/browser_trackingUI_pbmode_exceptions.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_pbmode_exceptions.js
@@ -87,17 +87,18 @@ add_task(async function testExceptionAdd
   ok(ContentBlocking, "CB is attached to the private window");
   TrackingProtection = browser.ownerGlobal.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the private window");
 
   Services.prefs.setBoolPref(TP_PB_PREF, true);
   ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
 
   info("Load a test page containing tracking elements");
-  await promiseTabLoadEvent(tab, TRACKING_PAGE);
+  await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
+                     waitForContentBlockingEvent(2, tab.ownerGlobal)]);
 
   testTrackingPage(tab.ownerGlobal);
 
   info("Disable TP for the page (which reloads the page)");
   let tabReloadPromise = promiseTabLoadEvent(tab);
   clickButton("#tracking-action-unblock");
   is(identityPopupState(), "closed", "Identity popup is closed");
 
@@ -123,22 +124,24 @@ add_task(async function testExceptionPer
   ContentBlocking = browser.ownerGlobal.ContentBlocking;
   ok(ContentBlocking, "CB is attached to the private window");
   TrackingProtection = browser.ownerGlobal.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the private window");
 
   ok(TrackingProtection.enabled, "TP is still enabled");
 
   info("Load a test page containing tracking elements");
-  await promiseTabLoadEvent(tab, TRACKING_PAGE);
+  await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
+                     waitForContentBlockingEvent(2, tab.ownerGlobal)]);
 
   testTrackingPage(tab.ownerGlobal);
 
   info("Disable TP for the page (which reloads the page)");
   let tabReloadPromise = promiseTabLoadEvent(tab);
   clickButton("#tracking-action-unblock");
   is(identityPopupState(), "closed", "Identity popup is closed");
 
-  await tabReloadPromise;
+  await Promise.all([tabReloadPromise,
+                     waitForContentBlockingEvent(2, tab.ownerGlobal)]);
   testTrackingPageUnblocked();
 
   privateWin.close();
 });
--- a/browser/base/content/test/trackingUI/browser_trackingUI_state.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_state.js
@@ -12,32 +12,34 @@
  *     2) A page with tracking elements is loaded.
  *
  * See also Bugs 1175327, 1043801, 1178985
  */
 
 const TP_PREF = "privacy.trackingprotection.enabled";
 const TP_PB_PREF = "privacy.trackingprotection.pbmode.enabled";
 const TPC_PREF = "network.cookie.cookieBehavior";
+const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
 const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
 const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
 const COOKIE_PAGE = "http://not-tracking.example.com/browser/browser/base/content/test/trackingUI/cookiePage.html";
 var ContentBlocking = null;
 var TrackingProtection = null;
 var ThirdPartyCookies = null;
 var tabbrowser = null;
 var gTrackingPageURL = TRACKING_PAGE;
 
 registerCleanupFunction(function() {
   TrackingProtection = ContentBlocking =
     ThirdPartyCookies = tabbrowser = null;
   UrlClassifierTestUtils.cleanupTestTrackers();
   Services.prefs.clearUserPref(TP_PREF);
   Services.prefs.clearUserPref(TP_PB_PREF);
   Services.prefs.clearUserPref(TPC_PREF);
+  Services.prefs.clearUserPref(DTSCBN_PREF);
 });
 
 // This is a special version of "hidden" that doesn't check for item
 // visibility and just asserts the display and opacity attributes.
 // That way we can test elements even when their panel is hidden...
 function hidden(sel) {
   let win = tabbrowser.ownerGlobal;
   let el = win.document.querySelector(sel);
@@ -101,30 +103,29 @@ function areTrackersBlocked(isPrivateBro
   return blockedByTP || blockedByTPC;
 }
 
 function testTrackingPage(window) {
   info("Tracking content must be blocked");
   ok(ContentBlocking.content.hasAttribute("detected"), "trackers are detected");
   ok(!ContentBlocking.content.hasAttribute("hasException"), "content shows no exception");
 
-  let isPrivateBrowsing = PrivateBrowsingUtils.isWindowPrivate(window);
-  let blockedByTP = areTrackersBlocked(isPrivateBrowsing);
+  let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
+  let blockedByTP = areTrackersBlocked(isWindowPrivate);
   is(BrowserTestUtils.is_visible(ContentBlocking.iconBox), blockedByTP,
      "icon box is" + (blockedByTP ? "" : " not") + " visible");
   is(ContentBlocking.iconBox.hasAttribute("active"), blockedByTP,
       "shield is" + (blockedByTP ? "" : " not") + " active");
   ok(!ContentBlocking.iconBox.hasAttribute("hasException"), "icon box shows no exception");
   is(ContentBlocking.iconBox.getAttribute("tooltiptext"),
      blockedByTP ? gNavigatorBundle.getString("trackingProtection.icon.activeTooltip") : "",
      "correct tooltip");
 
   ok(hidden("#tracking-action-block"), "blockButton is hidden");
 
-  let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
   if (isWindowPrivate) {
     ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
     is(!hidden("#tracking-action-unblock-private"), blockedByTP,
        "unblockButtonPrivate is" + (blockedByTP ? "" : " not") + " visible");
   } else {
     ok(hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is hidden");
     is(!hidden("#tracking-action-unblock"), blockedByTP,
        "unblockButton is" + (blockedByTP ? "" : " not") + " visible");
@@ -213,16 +214,18 @@ async function testContentBlocking(tab) 
   clickButton("#tracking-action-block");
   await tabReloadPromise;
   testTrackingPage(tab.ownerGlobal);
 }
 
 add_task(async function testNormalBrowsing() {
   await UrlClassifierTestUtils.addTestTrackers();
 
+  Services.prefs.setBoolPref(DTSCBN_PREF, true);
+
   tabbrowser = gBrowser;
   let tab = tabbrowser.selectedTab = BrowserTestUtils.addTab(tabbrowser);
 
   ContentBlocking = gBrowser.ownerGlobal.ContentBlocking;
   ok(ContentBlocking, "CB is attached to the browser window");
   TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the browser window");
   is(TrackingProtection.enabled, Services.prefs.getBoolPref(TP_PREF),
--- a/browser/base/content/test/trackingUI/browser_trackingUI_state_all_disabled.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_state_all_disabled.js
@@ -25,28 +25,30 @@ add_task(async function testContentBlock
     gBrowser.removeCurrentTab();
     Services.prefs.clearUserPref(ANIMATIONS_PREF);
     UrlClassifierTestUtils.cleanupTestTrackers();
   });
 
   let animationIcon = document.getElementById("tracking-protection-icon-animatable-image");
   let noAnimationIcon = document.getElementById("tracking-protection-icon");
 
-  await promiseTabLoadEvent(tab, TRACKING_PAGE);
+  await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
+                     waitForContentBlockingEvent(2, tab.ownerGlobal)]);
   ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden");
   ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden");
 
   await promiseTabLoadEvent(tab, BENIGN_PAGE);
   ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden");
   ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden");
 
   Services.prefs.setBoolPref(ANIMATIONS_PREF, false);
   await promiseTabLoadEvent(tab, TRACKING_PAGE);
   ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden");
   ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden");
 
   // Sanitity check that the shield is showing when at least one blocker is enabled.
   await SpecialPowers.pushPrefEnv({set: [
     [TP_PREF, true],
   ]});
-  await promiseTabLoadEvent(tab, TRACKING_PAGE);
+  await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
+                     waitForContentBlockingEvent(2, tab.ownerGlobal)]);
   ok(BrowserTestUtils.is_visible(noAnimationIcon), "the default icon is shown");
 });
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -314,16 +314,18 @@ using mozilla::TimeStamp;
   }                                                     \
   return GetCurrentInnerWindowInternal()->method args;  \
   PR_END_MACRO
 
 static LazyLogModule gDOMLeakPRLogOuter("DOMLeakOuter");
 
 static int32_t gOpenPopupSpamCount = 0;
 
+static bool gSyncContentBlockingNotifications = false;
+
 nsGlobalWindowOuter::OuterWindowByIdTable*
     nsGlobalWindowOuter::sOuterWindowsById = nullptr;
 
 /* static */
 nsPIDOMWindowOuter* nsPIDOMWindowOuter::GetFromCurrentInner(
     nsPIDOMWindowInner* aInner) {
   if (!aInner) {
     return nullptr;
@@ -5400,110 +5402,140 @@ void nsGlobalWindowOuter::NotifyContentB
 
   nsCOMPtr<nsIDocShell> docShell = GetDocShell();
   if (!docShell) {
     return;
   }
   nsCOMPtr<Document> doc = docShell->GetDocument();
   NS_ENSURE_TRUE_VOID(doc);
 
-  // This event might come after the user has navigated to another page.
-  // To prevent showing the TrackingProtection UI on the wrong page, we need to
-  // check that the loading URI for the channel is the same as the URI currently
-  // loaded in the document.
-  if (!SameLoadingURI(doc, aChannel) &&
-      aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
-    return;
-  }
-
-  // Notify nsIWebProgressListeners of this content blocking event.
-  // Can be used to change the UI state.
-  nsresult rv = NS_OK;
-  nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell, &rv);
-  NS_ENSURE_SUCCESS_VOID(rv);
-  uint32_t event = 0;
-  nsCOMPtr<nsISecureBrowserUI> securityUI;
-  docShell->GetSecurityUI(getter_AddRefs(securityUI));
-  if (!securityUI) {
+  nsCOMPtr<nsIURI> uri(aURIHint);
+  nsCOMPtr<nsIChannel> channel(aChannel);
+
+  static bool prefInitialized = false;
+  if (!prefInitialized) {
+    Preferences::AddBoolVarCache(
+        &gSyncContentBlockingNotifications,
+        "dom.testing.sync-content-blocking-notifications", false);
+    prefInitialized = true;
+  }
+
+  nsCOMPtr<nsIRunnable> func = NS_NewRunnableFunction(
+      "NotifyContentBlockingEventDelayed",
+      [doc, docShell, uri, channel, aEvent, aBlocked]() {
+        // This event might come after the user has navigated to another
+        // page. To prevent showing the TrackingProtection UI on the wrong
+        // page, we need to check that the loading URI for the channel is
+        // the same as the URI currently loaded in the document.
+        if (!SameLoadingURI(doc, channel) &&
+            aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
+          return;
+        }
+
+        // Notify nsIWebProgressListeners of this content blocking event.
+        // Can be used to change the UI state.
+        nsresult rv = NS_OK;
+        nsCOMPtr<nsISecurityEventSink> eventSink =
+            do_QueryInterface(docShell, &rv);
+        NS_ENSURE_SUCCESS_VOID(rv);
+        uint32_t event = 0;
+        nsCOMPtr<nsISecureBrowserUI> securityUI;
+        docShell->GetSecurityUI(getter_AddRefs(securityUI));
+        if (!securityUI) {
+          return;
+        }
+        securityUI->GetContentBlockingEvent(&event);
+        nsAutoCString origin;
+        nsContentUtils::GetASCIIOrigin(uri, origin);
+
+        bool blockedValue = aBlocked;
+        bool unblocked = false;
+        if (aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
+          doc->SetHasTrackingContentBlocked(aBlocked, origin);
+          if (!aBlocked) {
+            unblocked = !doc->GetHasTrackingContentBlocked();
+          }
+        } else if (aEvent ==
+                   nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
+          doc->SetHasTrackingContentLoaded(aBlocked, origin);
+          if (!aBlocked) {
+            unblocked = !doc->GetHasTrackingContentLoaded();
+          }
+        } else if (aEvent == nsIWebProgressListener::
+                                 STATE_COOKIES_BLOCKED_BY_PERMISSION) {
+          doc->SetHasCookiesBlockedByPermission(aBlocked, origin);
+          if (!aBlocked) {
+            unblocked = !doc->GetHasCookiesBlockedByPermission();
+          }
+        } else if (aEvent ==
+                   nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) {
+          doc->SetHasTrackingCookiesBlocked(aBlocked, origin);
+          if (!aBlocked) {
+            unblocked = !doc->GetHasTrackingCookiesBlocked();
+          }
+        } else if (aEvent ==
+                   nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) {
+          doc->SetHasAllCookiesBlocked(aBlocked, origin);
+          if (!aBlocked) {
+            unblocked = !doc->GetHasAllCookiesBlocked();
+          }
+        } else if (aEvent ==
+                   nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN) {
+          doc->SetHasForeignCookiesBlocked(aBlocked, origin);
+          if (!aBlocked) {
+            unblocked = !doc->GetHasForeignCookiesBlocked();
+          }
+        } else if (aEvent == nsIWebProgressListener::STATE_COOKIES_LOADED) {
+          MOZ_ASSERT(!aBlocked,
+                     "We don't expected to see blocked STATE_COOKIES_LOADED");
+          // Note that the logic in this branch is the logical negation of
+          // the logic in other branches, since the Document API we have is
+          // phrased in "loaded" terms as opposed to "blocked" terms.
+          blockedValue = !aBlocked;
+          doc->SetHasCookiesLoaded(blockedValue, origin);
+          if (!aBlocked) {
+            unblocked = !doc->GetHasCookiesLoaded();
+          }
+        } else {
+          // Ignore nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
+        }
+        const uint32_t oldEvent = event;
+        if (blockedValue) {
+          event |= aEvent;
+        } else if (unblocked) {
+          event &= ~aEvent;
+        }
+
+        if (event == oldEvent
+#ifdef ANDROID
+            // GeckoView always needs to notify about blocked trackers,
+            // since the GeckoView API always needs to report the URI and
+            // type of any blocked tracker. We use a platform-dependent code
+            // path here because reporting this notification on desktop
+            // platforms isn't necessary and doing so can have a big
+            // performance cost.
+            && aEvent != nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT
+#endif
+        ) {
+          // Avoid dispatching repeated notifications when nothing has
+          // changed
+          return;
+        }
+
+        eventSink->OnContentBlockingEvent(channel, event);
+      });
+  nsresult rv;
+  if (gSyncContentBlockingNotifications) {
+    rv = func->Run();
+  } else {
+    rv = NS_IdleDispatchToCurrentThread(func.forget(), 100);
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
-  securityUI->GetContentBlockingEvent(&event);
-  nsAutoCString origin;
-  nsContentUtils::GetASCIIOrigin(aURIHint, origin);
-
-  bool blockedValue = aBlocked;
-  bool unblocked = false;
-  if (aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
-    doc->SetHasTrackingContentBlocked(aBlocked, origin);
-    if (!aBlocked) {
-      unblocked = !doc->GetHasTrackingContentBlocked();
-    }
-  } else if (aEvent == nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
-    doc->SetHasTrackingContentLoaded(aBlocked, origin);
-    if (!aBlocked) {
-      unblocked = !doc->GetHasTrackingContentLoaded();
-    }
-  } else if (aEvent ==
-             nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION) {
-    doc->SetHasCookiesBlockedByPermission(aBlocked, origin);
-    if (!aBlocked) {
-      unblocked = !doc->GetHasCookiesBlockedByPermission();
-    }
-  } else if (aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) {
-    doc->SetHasTrackingCookiesBlocked(aBlocked, origin);
-    if (!aBlocked) {
-      unblocked = !doc->GetHasTrackingCookiesBlocked();
-    }
-  } else if (aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) {
-    doc->SetHasAllCookiesBlocked(aBlocked, origin);
-    if (!aBlocked) {
-      unblocked = !doc->GetHasAllCookiesBlocked();
-    }
-  } else if (aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN) {
-    doc->SetHasForeignCookiesBlocked(aBlocked, origin);
-    if (!aBlocked) {
-      unblocked = !doc->GetHasForeignCookiesBlocked();
-    }
-  } else if (aEvent == nsIWebProgressListener::STATE_COOKIES_LOADED) {
-    MOZ_ASSERT(!aBlocked,
-               "We don't expected to see blocked STATE_COOKIES_LOADED");
-    // Note that the logic in this branch is the logical negation of the logic
-    // in other branches, since the Document API we have is phrased in
-    // "loaded" terms as opposed to "blocked" terms.
-    blockedValue = !aBlocked;
-    doc->SetHasCookiesLoaded(blockedValue, origin);
-    if (!aBlocked) {
-      unblocked = !doc->GetHasCookiesLoaded();
-    }
-  } else {
-    // Ignore nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
-  }
-  const uint32_t oldEvent = event;
-  if (blockedValue) {
-    event |= aEvent;
-  } else if (unblocked) {
-    event &= ~aEvent;
-  }
-
-  if (event == oldEvent
-#ifdef ANDROID
-      // GeckoView always needs to notify about blocked trackers, since the
-      // GeckoView API always needs to report the URI and type of any blocked
-      // tracker.
-      // We use a platform-dependent code path here because reporting this
-      // notification on desktop platforms isn't necessary and doing so can have
-      // a big performance cost.
-      && aEvent != nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT
-#endif
-  ) {
-    // Avoid dispatching repeated notifications when nothing has changed
-    return;
-  }
-
-  eventSink->OnContentBlockingEvent(aChannel, event);
 }
 
 // static
 bool nsGlobalWindowOuter::SameLoadingURI(Document* aDoc, nsIChannel* aChannel) {
   nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
   nsCOMPtr<nsILoadInfo> channelLoadInfo = aChannel->GetLoadInfo();
   if (!channelLoadInfo || !docURI) {
     return false;