Bug 1589476 - Emit a separate notification when a tracker from the Level 2 Disconnect blocklist is observed on a page and use this code to avoid using the URL classifer service in the front-end; r=nhnt11,droeh
authorEhsan Akhgari <ehsan@mozilla.com>
Mon, 18 Nov 2019 20:56:36 +0000
changeset 502477 91697065e99f5b88ceaaf92af99ebdbbfc1dda88
parent 502476 ba115d212e18bad5168af1ae0541c44de1a20089
child 502478 21f755c04005255c5305a13ad9087420e4489b7b
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnhnt11, droeh
bugs1589476
milestone72.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 1589476 - Emit a separate notification when a tracker from the Level 2 Disconnect blocklist is observed on a page and use this code to avoid using the URL classifer service in the front-end; r=nhnt11,droeh Differential Revision: https://phabricator.services.mozilla.com/D49660
browser/base/content/browser-siteProtections.js
browser/components/protections/test/browser/browser_protections_telemetry.js
dom/base/ContentBlockingLog.h
dom/base/Document.h
dom/base/nsGlobalWindowOuter.cpp
mobile/android/geckoview/api.txt
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentBlockingController.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp
security/manager/ssl/nsSecureBrowserUIImpl.cpp
toolkit/components/antitracking/TrackingDBService.jsm
toolkit/components/antitracking/test/browser/browser_allowListNotifications.js
toolkit/components/antitracking/test/browser/browser_subResources.js
toolkit/components/antitracking/test/xpcshell/test_tracking_db_service.js
uriloader/base/nsIWebProgressListener.idl
--- a/browser/base/content/browser-siteProtections.js
+++ b/browser/base/content/browser-siteProtections.js
@@ -280,16 +280,18 @@ var Cryptomining = {
 };
 
 var TrackingProtection = {
   reportBreakageLabel: "trackingprotection",
   PREF_ENABLED_GLOBALLY: "privacy.trackingprotection.enabled",
   PREF_ENABLED_IN_PRIVATE_WINDOWS: "privacy.trackingprotection.pbmode.enabled",
   PREF_TRACKING_TABLE: "urlclassifier.trackingTable",
   PREF_TRACKING_ANNOTATION_TABLE: "urlclassifier.trackingAnnotationTable",
+  PREF_ANNOTATIONS_LEVEL_2_ENABLED:
+    "privacy.annotate_channels.strict_list.enabled",
   enabledGlobally: false,
   enabledInPrivateWindows: false,
 
   get categoryItem() {
     delete this.categoryItem;
     return (this.categoryItem = document.getElementById(
       "protections-popup-category-tracking-protection"
     ));
@@ -345,27 +347,38 @@ var TrackingProtection = {
       false
     );
     XPCOMUtils.defineLazyPreferenceGetter(
       this,
       "trackingAnnotationTable",
       this.PREF_TRACKING_ANNOTATION_TABLE,
       false
     );
+    XPCOMUtils.defineLazyPreferenceGetter(
+      this,
+      "annotationsLevel2Enabled",
+      this.PREF_ANNOTATIONS_LEVEL_2_ENABLED,
+      false
+    );
   },
 
   uninit() {
     Services.prefs.removeObserver(this.PREF_ENABLED_GLOBALLY, this);
     Services.prefs.removeObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this);
   },
 
   observe() {
     this.updateEnabled();
   },
 
+  get trackingProtectionLevel2Enabled() {
+    const CONTENT_TABLE = "content-track-digest256";
+    return this.trackingTable.includes(CONTENT_TABLE);
+  },
+
   get enabled() {
     return (
       this.enabledGlobally ||
       (this.enabledInPrivateWindows &&
         PrivateBrowsingUtils.isWindowPrivate(window))
     );
   },
 
@@ -380,22 +393,36 @@ var TrackingProtection = {
   },
 
   isBlocking(state) {
     return (
       (state & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT) != 0
     );
   },
 
-  isAllowing(state) {
+  isAllowingLevel1(state) {
     return (
-      (state & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT) != 0
+      (state &
+        Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_1_TRACKING_CONTENT) !=
+      0
     );
   },
 
+  isAllowingLevel2(state) {
+    return (
+      (state &
+        Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_2_TRACKING_CONTENT) !=
+      0
+    );
+  },
+
+  isAllowing(state) {
+    return this.isAllowingLevel1(state) || this.isAllowingLevel2(state);
+  },
+
   isDetected(state) {
     return this.isBlocking(state) || this.isAllowing(state);
   },
 
   async updateSubView() {
     let previousURI = gBrowser.currentURI.spec;
     let previousWindow = gBrowser.selectedBrowser.innerWindowID;
 
@@ -448,55 +475,41 @@ var TrackingProtection = {
         "title",
         this.enabled && !gProtectionsHandler.hasException
           ? this.strings.subViewTitleBlocking
           : this.strings.subViewTitleNotBlocking
       );
     }
   },
 
-  // Given a URI from a source that was tracking-annotated, figure out
-  // if it's really on the tracking table or just on the annotation table.
-  _isOnTrackingTable(uri) {
-    if (this.trackingTable == this.trackingAnnotationTable) {
-      return true;
-    }
-
-    let feature = classifierService.getFeatureByName("tracking-protection");
-    if (!feature) {
-      return false;
-    }
-
-    return new Promise(resolve => {
-      classifierService.asyncClassifyLocalWithFeatures(
-        uri,
-        [feature],
-        Ci.nsIUrlClassifierFeature.blacklist,
-        list => resolve(!!list.length)
-      );
-    });
-  },
-
   async _createListItem(origin, actions) {
     // Figure out if this list entry was actually detected by TP or something else.
     let isAllowed = actions.some(([state]) => this.isAllowing(state));
     let isDetected =
       isAllowed || actions.some(([state]) => this.isBlocking(state));
 
     if (!isDetected) {
       return null;
     }
 
     let uri = Services.io.newURI(origin);
 
     // Because we might use different lists for annotation vs. blocking, we
     // need to make sure that this is a tracker that we would actually have blocked
     // before showing it to the user.
-    let isTracker = await this._isOnTrackingTable(uri);
-    if (!isTracker) {
+    if (
+      this.annotationsLevel2Enabled &&
+      !this.trackingProtectionLevel2Enabled &&
+      actions.some(
+        ([state]) =>
+          (state &
+            Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_2_TRACKING_CONTENT) !=
+          0
+      )
+    ) {
       return null;
     }
 
     let listItem = document.createXULElement("hbox");
     listItem.className = "protections-popup-list-item";
     listItem.classList.toggle("allowed", isAllowed);
     // Repeat the host in the tooltip in case it's too long
     // and overflows in our panel.
--- a/browser/components/protections/test/browser/browser_protections_telemetry.js
+++ b/browser/components/protections/test/browser/browser_protections_telemetry.js
@@ -25,17 +25,17 @@ const LOG = {
   ],
   "https://5.example.com": [
     [Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, true, 1],
   ],
   // Cookie blocked for other reason, then identified as a tracker
   "https://6.example.com": [
     [
       Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_ALL |
-        Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT,
+        Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_1_TRACKING_CONTENT,
       true,
       4,
     ],
   ],
 };
 
 requestLongerTimeout(2);
 
--- a/dom/base/ContentBlockingLog.h
+++ b/dom/base/ContentBlockingLog.h
@@ -29,19 +29,22 @@ class ContentBlockingLog final {
     uint32_t mType;
     uint32_t mRepeatCount;
     bool mBlocked;
     Maybe<AntiTrackingCommon::StorageAccessGrantedReason> mReason;
     nsTArray<nsCString> mTrackingFullHashes;
   };
 
   struct OriginDataEntry {
-    OriginDataEntry() : mHasTrackingContentLoaded(false) {}
+    OriginDataEntry()
+        : mHasLevel1TrackingContentLoaded(false),
+          mHasLevel2TrackingContentLoaded(false) {}
 
-    bool mHasTrackingContentLoaded;
+    bool mHasLevel1TrackingContentLoaded;
+    bool mHasLevel2TrackingContentLoaded;
     Maybe<bool> mHasCookiesLoaded;
     Maybe<bool> mHasTrackerCookiesLoaded;
     Maybe<bool> mHasSocialTrackerCookiesLoaded;
     nsTArray<LogEntry> mLogs;
   };
 
   struct OriginEntry {
     OriginEntry() { mData = MakeUnique<OriginDataEntry>(); }
@@ -137,18 +140,22 @@ class ContentBlockingLog final {
 
     OriginEntry* entry = mLog.AppendElement();
     if (NS_WARN_IF(!entry || !entry->mData)) {
       return;
     }
 
     entry->mOrigin = aOrigin;
 
-    if (aType == nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
-      entry->mData->mHasTrackingContentLoaded = aBlocked;
+    if (aType ==
+        nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT) {
+      entry->mData->mHasLevel1TrackingContentLoaded = aBlocked;
+    } else if (aType ==
+               nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT) {
+      entry->mData->mHasLevel2TrackingContentLoaded = aBlocked;
     } else if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED) {
       MOZ_ASSERT(entry->mData->mHasCookiesLoaded.isNothing());
       entry->mData->mHasCookiesLoaded.emplace(aBlocked);
     } else if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER) {
       MOZ_ASSERT(entry->mData->mHasTrackerCookiesLoaded.isNothing());
       entry->mData->mHasTrackerCookiesLoaded.emplace(aBlocked);
     } else if (aType ==
                nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER) {
@@ -203,18 +210,24 @@ class ContentBlockingLog final {
     // loop is to scan the log to see if we find a matching entry, and if so
     // we would return true, otherwise in the end of the function outside of
     // the loop we take the common `return false;` statement.
     for (const OriginEntry& entry : mLog) {
       if (!entry.mData) {
         continue;
       }
 
-      if (aType == nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
-        if (entry.mData->mHasTrackingContentLoaded) {
+      if (aType ==
+          nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT) {
+        if (entry.mData->mHasLevel1TrackingContentLoaded) {
+          return true;
+        }
+      } else if (aType == nsIWebProgressListener::
+                              STATE_LOADED_LEVEL_2_TRACKING_CONTENT) {
+        if (entry.mData->mHasLevel2TrackingContentLoaded) {
           return true;
         }
       } else if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED) {
         if (entry.mData->mHasCookiesLoaded.isSome() &&
             entry.mData->mHasCookiesLoaded.value()) {
           return true;
         }
       } else if (aType ==
@@ -252,18 +265,24 @@ class ContentBlockingLog final {
                                     aSizes.mState.mMallocSizeOf);
       }
     }
   }
 
  private:
   bool RecordLogEntryInCustomField(uint32_t aType, OriginEntry& aEntry,
                                    bool aBlocked) {
-    if (aType == nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
-      aEntry.mData->mHasTrackingContentLoaded = aBlocked;
+    if (aType ==
+        nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT) {
+      aEntry.mData->mHasLevel1TrackingContentLoaded = aBlocked;
+      return true;
+    }
+    if (aType ==
+        nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT) {
+      aEntry.mData->mHasLevel2TrackingContentLoaded = aBlocked;
       return true;
     }
     if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED) {
       if (aEntry.mData->mHasCookiesLoaded.isSome()) {
         aEntry.mData->mHasCookiesLoaded.ref() = aBlocked;
       } else {
         aEntry.mData->mHasCookiesLoaded.emplace(aBlocked);
       }
@@ -284,21 +303,31 @@ class ContentBlockingLog final {
         aEntry.mData->mHasSocialTrackerCookiesLoaded.emplace(aBlocked);
       }
       return true;
     }
     return false;
   }
 
   void StringifyCustomFields(const OriginEntry& aEntry, JSONWriter& aWriter) {
-    if (aEntry.mData->mHasTrackingContentLoaded) {
+    if (aEntry.mData->mHasLevel1TrackingContentLoaded) {
       aWriter.StartArrayElement(aWriter.SingleLineStyle);
       {
         aWriter.IntElement(
-            nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT);
+            nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT);
+        aWriter.BoolElement(true);  // blocked
+        aWriter.IntElement(1);      // repeat count
+      }
+      aWriter.EndArray();
+    }
+    if (aEntry.mData->mHasLevel2TrackingContentLoaded) {
+      aWriter.StartArrayElement(aWriter.SingleLineStyle);
+      {
+        aWriter.IntElement(
+            nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT);
         aWriter.BoolElement(true);  // blocked
         aWriter.IntElement(1);      // repeat count
       }
       aWriter.EndArray();
     }
     if (aEntry.mData->mHasCookiesLoaded.isSome()) {
       aWriter.StartArrayElement(aWriter.SingleLineStyle);
       {
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -1336,30 +1336,50 @@ class Document : public nsINode,
    * Get the social tracker cookies loaded flag for this document.
    */
   bool GetHasSocialTrackerCookiesLoaded() {
     return mContentBlockingLog.HasBlockedAnyOfType(
         nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER);
   }
 
   /**
-   * Get tracking content loaded flag for this document.
-   */
-  bool GetHasTrackingContentLoaded() {
+   * Get level 1 tracking content loaded flag for this document.
+   */
+  bool GetHasLevel1TrackingContentLoaded() {
     return mContentBlockingLog.HasBlockedAnyOfType(
-        nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT);
-  }
-
-  /**
-   * Set the tracking content loaded flag for this document.
-   */
-  void SetHasTrackingContentLoaded(bool aHasTrackingContentLoaded,
-                                   const nsACString& aOriginBlocked) {
+        nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT);
+  }
+
+  /**
+   * Set the level 1 tracking content loaded flag for this document.
+   */
+  void SetHasLevel1TrackingContentLoaded(bool aHasTrackingContentLoaded,
+                                         const nsACString& aOriginBlocked) {
     RecordContentBlockingLog(
-        aOriginBlocked, nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT,
+        aOriginBlocked,
+        nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT,
+        aHasTrackingContentLoaded);
+  }
+
+  /**
+   * Get level 2 tracking content loaded flag for this document.
+   */
+  bool GetHasLevel2TrackingContentLoaded() {
+    return mContentBlockingLog.HasBlockedAnyOfType(
+        nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT);
+  }
+
+  /**
+   * Set the level 2 tracking content loaded flag for this document.
+   */
+  void SetHasLevel2TrackingContentLoaded(bool aHasTrackingContentLoaded,
+                                         const nsACString& aOriginBlocked) {
+    RecordContentBlockingLog(
+        aOriginBlocked,
+        nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT,
         aHasTrackingContentLoaded);
   }
 
   /**
    * Get fingerprinting content loaded flag for this document.
    */
   bool GetHasFingerprintingContentLoaded() {
     return mContentBlockingLog.HasBlockedAnyOfType(
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -5405,21 +5405,27 @@ void nsGlobalWindowOuter::NotifyContentB
 
         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);
+        } else if (aEvent == nsIWebProgressListener::
+                                 STATE_LOADED_LEVEL_1_TRACKING_CONTENT) {
+          doc->SetHasLevel1TrackingContentLoaded(aBlocked, origin);
           if (!aBlocked) {
-            unblocked = !doc->GetHasTrackingContentLoaded();
+            unblocked = !doc->GetHasLevel1TrackingContentLoaded();
+          }
+        } else if (aEvent == nsIWebProgressListener::
+                                 STATE_LOADED_LEVEL_2_TRACKING_CONTENT) {
+          doc->SetHasLevel2TrackingContentLoaded(aBlocked, origin);
+          if (!aBlocked) {
+            unblocked = !doc->GetHasLevel2TrackingContentLoaded();
           }
         } else if (aEvent == nsIWebProgressListener::
                                  STATE_BLOCKED_FINGERPRINTING_CONTENT) {
           doc->SetHasFingerprintingContentBlocked(aBlocked, origin);
           if (!aBlocked) {
             unblocked = !doc->GetHasFingerprintingContentBlocked();
           }
         } else if (aEvent == nsIWebProgressListener::
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -312,18 +312,19 @@ package org.mozilla.geckoview {
     field public static final int COOKIES_BLOCKED_SOCIALTRACKER = 16777216;
     field public static final int COOKIES_BLOCKED_TRACKER = 536870912;
     field public static final int COOKIES_LOADED = 32768;
     field public static final int COOKIES_LOADED_SOCIALTRACKER = 524288;
     field public static final int COOKIES_LOADED_TRACKER = 262144;
     field public static final int COOKIES_PARTITIONED_FOREIGN = -2147483648;
     field public static final int LOADED_CRYPTOMINING_CONTENT = 2097152;
     field public static final int LOADED_FINGERPRINTING_CONTENT = 1024;
+    field public static final int LOADED_LEVEL_1_TRACKING_CONTENT = 8192;
+    field public static final int LOADED_LEVEL_2_TRACKING_CONTENT = 1048576;
     field public static final int LOADED_SOCIALTRACKING_CONTENT = 131072;
-    field public static final int LOADED_TRACKING_CONTENT = 8192;
   }
 
   @AnyThread public class ContentBlockingController.ExceptionList {
     ctor public ExceptionList(@NonNull String);
     ctor public ExceptionList(@NonNull JSONObject);
     method @NonNull public String[] getUris();
     method @NonNull public JSONObject toJson();
   }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentBlockingController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentBlockingController.java
@@ -207,19 +207,24 @@ public class ContentBlockingController {
         // These values must be kept in sync with the corresponding values in
         // nsIWebProgressListener.idl.
         /**
          * Tracking content has been blocked from loading.
          */
         public static final int BLOCKED_TRACKING_CONTENT        = 0x00001000;
 
         /**
-         * Tracking content has been loaded.
+         * Level 1 tracking content has been loaded.
          */
-        public static final int LOADED_TRACKING_CONTENT         = 0x00002000;
+        public static final int LOADED_LEVEL_1_TRACKING_CONTENT = 0x00002000;
+
+        /**
+         * Level 2 tracking content has been loaded.
+         */
+        public static final int LOADED_LEVEL_2_TRACKING_CONTENT = 0x00100000;
 
         /**
          * Fingerprinting content has been blocked from loading.
          */
         public static final int BLOCKED_FINGERPRINTING_CONTENT  = 0x00000040;
 
         /**
          * Fingerprinting content has been loaded.
@@ -315,25 +320,26 @@ public class ContentBlockingController {
      */
     @AnyThread
     public static class LogEntry {
         /**
          * Data about why a given entry was blocked.
          */
         public static class BlockingData {
             @Retention(RetentionPolicy.SOURCE)
-            @IntDef({ Event.BLOCKED_TRACKING_CONTENT, Event.LOADED_TRACKING_CONTENT,
-                      Event.BLOCKED_FINGERPRINTING_CONTENT, Event.LOADED_FINGERPRINTING_CONTENT,
-                      Event.BLOCKED_CRYPTOMINING_CONTENT, Event.LOADED_CRYPTOMINING_CONTENT,
-                      Event.BLOCKED_UNSAFE_CONTENT, Event.COOKIES_LOADED,
-                      Event.COOKIES_LOADED_TRACKER, Event.COOKIES_LOADED_SOCIALTRACKER,
-                      Event.COOKIES_BLOCKED_BY_PERMISSION, Event.COOKIES_BLOCKED_TRACKER,
-                      Event.COOKIES_BLOCKED_SOCIALTRACKER, Event.COOKIES_BLOCKED_ALL,
-                      Event.COOKIES_PARTITIONED_FOREIGN, Event.COOKIES_BLOCKED_FOREIGN,
-                      Event.BLOCKED_SOCIALTRACKING_CONTENT, Event.LOADED_SOCIALTRACKING_CONTENT })
+            @IntDef({ Event.BLOCKED_TRACKING_CONTENT, Event.LOADED_LEVEL_1_TRACKING_CONTENT,
+                      Event.LOADED_LEVEL_2_TRACKING_CONTENT, Event.BLOCKED_FINGERPRINTING_CONTENT,
+                      Event.LOADED_FINGERPRINTING_CONTENT, Event.BLOCKED_CRYPTOMINING_CONTENT,
+                      Event.LOADED_CRYPTOMINING_CONTENT, Event.BLOCKED_UNSAFE_CONTENT,
+                      Event.COOKIES_LOADED, Event.COOKIES_LOADED_TRACKER,
+                      Event.COOKIES_LOADED_SOCIALTRACKER, Event.COOKIES_BLOCKED_BY_PERMISSION,
+                      Event.COOKIES_BLOCKED_TRACKER, Event.COOKIES_BLOCKED_SOCIALTRACKER,
+                      Event.COOKIES_BLOCKED_ALL, Event.COOKIES_PARTITIONED_FOREIGN,
+                      Event.COOKIES_BLOCKED_FOREIGN, Event.BLOCKED_SOCIALTRACKING_CONTENT,
+                      Event.LOADED_SOCIALTRACKING_CONTENT })
             /* package */ @interface LogEvent {}
 
             /**
              * A category the entry falls under.
              */
             public final @LogEvent int category;
 
             /**
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
@@ -38,31 +38,37 @@ exclude: true
 - Added `Autofill` commit support.
   ([bug 1577005]({{bugzilla}}1577005))
 - Added [`GeckoView.setViewBackend`][72.11] to set whether GeckoView should be
   backed by a [`TextureView`][72.12] or a [`SurfaceView`][72.13].
   ([bug 1530402]({{bugzilla}}1530402))
 - Added support for Browser and Page Action from the WebExtension API.
   See [`WebExtension.Action`][72.14].
   ([bug 1530402]({{bugzilla}}1530402))
+- ⚠️ Split [`ContentBlockingController.Event.LOADED_TRACKING_CONTENT`][72.15] into
+  [`ContentBlockingController.Event.LOADED_LEVEL_1_TRACKING_CONTENT`][72.16] and
+  [`ContentBlockingController.Event.LOADED_LEVEL_2_TRACKING_CONTENT`][72.17].
 
 [72.1]: {{javadoc_uri}}/GeckoSession.NavigationDelegate.LoadRequest#hasUserGesture-
 [72.2]: {{javadoc_uri}}/Autofill.html
 [72.3]: {{javadoc_uri}}/WebResponse.html#body
 [72.4]: {{javadoc_uri}}/WebResponse.html#setReadTimeoutMillis-long-
 [72.5]: {{javadoc_uri}}/WebResponse.html#DEFAULT_READ_TIMEOUT_MS
 [72.6]: {{javadoc_uri}}/GeckoSession.SelectionActionDelegate.html#onShowActionRequest-org.mozilla.geckoview.GeckoSession-org.mozilla.geckoview.GeckoSession.SelectionActionDelegate.Selection-
 [72.7]: {{javadoc_uri}}/BasicSelectionActionDelegate.html#onShowActionRequest-org.mozilla.geckoview.GeckoSession-org.mozilla.geckoview.GeckoSession.SelectionActionDelegate.Selection-
 [72.8]: {{javadoc_uri}}/GeckoSession.SelectionActionDelegate.Selection.html
 [72.9]: {{javadoc_uri}}/BasicSelectionActionDelegate.html#getSelection-
 [72.10]: {{javadoc_uri}}/BasicSelectionActionDelegate.html#clearSelection-
 [72.11]: {{javadoc_uri}}/GeckoView.html#setViewBackend-int-
 [72.12]: https://developer.android.com/reference/android/view/TextureView
 [72.13]: https://developer.android.com/reference/android/view/SurfaceView
 [72.14]: {{javadoc_uri}}/WebExtension.Action.html
+[72.15]: {{javadoc_uri}}/ContentBlockingController.Event.html#LOADED_TRACKING_CONTENT
+[72.16]: {{javadoc_uri}}/ContentBlockingController.Event.html#LOADED_LEVEL_1_TRACKING_CONTENT
+[72.17]: {{javadoc_uri}}/ContentBlockingController.Event.html#LOADED_LEVEL_2_TRACKING_CONTENT
 
 ## v71
 - Added a content blocking flag for blocked social cookies to [`ContentBlocking`][70.17].
   ([bug 1584479]({{bugzilla}}1584479))
 - Added [`onBooleanScalar`][71.1], [`onLongScalar`][71.2],
   [`onStringScalar`][71.3] to [`RuntimeTelemetry.Delegate`][70.12] to support
   scalars in streaming telemetry. ⚠️  As part of this change,
   `onTelemetryReceived` has been renamed to [`onHistogram`][71.4], and
--- a/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp
@@ -141,18 +141,23 @@ UrlClassifierFeatureTrackingAnnotation::
       };
 
   uint32_t flags = UrlClassifierCommon::TablesToClassificationFlags(
       aList, sClassificationData,
       nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_TRACKING);
 
   UrlClassifierCommon::SetTrackingInfo(aChannel, aList, aHashes);
 
-  UrlClassifierCommon::AnnotateChannel(
-      aChannel, flags, nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT);
+  uint32_t notification =
+      ((flags & nsIClassifiedChannel::ClassificationFlags::
+                    CLASSIFIED_TRACKING_CONTENT) != 0)
+          ? nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT
+          : nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT;
+
+  UrlClassifierCommon::AnnotateChannel(aChannel, flags, notification);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 UrlClassifierFeatureTrackingAnnotation::GetURIByListType(
     nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
     nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) {
--- a/security/manager/ssl/nsSecureBrowserUIImpl.cpp
+++ b/security/manager/ssl/nsSecureBrowserUIImpl.cpp
@@ -180,18 +180,22 @@ void nsSecureBrowserUIImpl::CheckForCont
     return;
   }
 
   // Has tracking content been blocked or loaded?
   if (doc->GetHasTrackingContentBlocked()) {
     mEvent |= STATE_BLOCKED_TRACKING_CONTENT;
   }
 
-  if (doc->GetHasTrackingContentLoaded()) {
-    mEvent |= STATE_LOADED_TRACKING_CONTENT;
+  if (doc->GetHasLevel1TrackingContentLoaded()) {
+    mEvent |= STATE_LOADED_LEVEL_1_TRACKING_CONTENT;
+  }
+
+  if (doc->GetHasLevel2TrackingContentLoaded()) {
+    mEvent |= STATE_LOADED_LEVEL_2_TRACKING_CONTENT;
   }
 
   // Has fingerprinting content been blocked or loaded?
   if (doc->GetHasFingerprintingContentBlocked()) {
     mEvent |= STATE_BLOCKED_FINGERPRINTING_CONTENT;
   }
 
   if (doc->GetHasFingerprintingContentLoaded()) {
--- a/toolkit/components/antitracking/TrackingDBService.jsm
+++ b/toolkit/components/antitracking/TrackingDBService.jsm
@@ -188,17 +188,21 @@ TrackingDBService.prototype = {
     task.arm();
     this.waitingTasks.add(task);
   },
 
   identifyType(events) {
     let result = null;
     let isTracker = false;
     for (let [state, blocked] of events) {
-      if (state & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT) {
+      if (
+        state &
+          Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_1_TRACKING_CONTENT ||
+        state & Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_2_TRACKING_CONTENT
+      ) {
         isTracker = true;
       }
       if (blocked) {
         if (
           state & Ci.nsIWebProgressListener.STATE_BLOCKED_FINGERPRINTING_CONTENT
         ) {
           result = Ci.nsITrackingDBService.FINGERPRINTERS_ID;
         } else if (
--- a/toolkit/components/antitracking/test/browser/browser_allowListNotifications.js
+++ b/toolkit/components/antitracking/test/browser/browser_allowListNotifications.js
@@ -86,17 +86,17 @@ add_task(async function() {
         ifr.src = obj.page;
       });
     }
   );
 
   let expectTrackerFound = item => {
     is(
       item[0],
-      Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT,
+      Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_1_TRACKING_CONTENT,
       "Correct blocking type reported"
     );
     is(item[1], true, "Correct blocking status reported");
     ok(item[2] >= 1, "Correct repeat count reported");
   };
 
   let log = JSON.parse(await browser.getContentBlockingLog());
   for (let trackerOrigin in log) {
--- a/toolkit/components/antitracking/test/browser/browser_subResources.js
+++ b/toolkit/components/antitracking/test/browser/browser_subResources.js
@@ -203,17 +203,17 @@ add_task(async function() {
     );
     is(item[1], blocked, "Correct blocking status reported");
     ok(item[2] >= 1, "Correct repeat count reported");
   };
 
   let expectTrackerFound = item => {
     is(
       item[0],
-      Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT,
+      Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_1_TRACKING_CONTENT,
       "Correct blocking type reported"
     );
     is(item[1], true, "Correct blocking status reported");
     ok(item[2] >= 1, "Correct repeat count reported");
   };
 
   let expectCookiesLoaded = item => {
     is(
--- a/toolkit/components/antitracking/test/xpcshell/test_tracking_db_service.js
+++ b/toolkit/components/antitracking/test/xpcshell/test_tracking_db_service.js
@@ -53,17 +53,17 @@ const LOG = {
   ],
   "https://5.example.com": [
     [Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, true, 1],
   ],
   // Cookie blocked for other reason, then identified as a tracker
   "https://6.example.com": [
     [
       Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_ALL |
-        Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT,
+        Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_1_TRACKING_CONTENT,
       true,
       4,
     ],
   ],
   "https://7.example.com": [
     [Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_SOCIALTRACKER, true, 1],
   ],
   "https://8.example.com": [
--- a/uriloader/base/nsIWebProgressListener.idl
+++ b/uriloader/base/nsIWebProgressListener.idl
@@ -263,18 +263,21 @@ interface nsIWebProgressListener : nsISu
    * nsSecureBrowserUIImpl::CheckForBlockedContent() AND UPDATE THE
    * CORRESPONDING LIST IN ContentBlockingController.java
    *
    * These flags describe the reason of cookie jar rejection.
    *
    * STATE_BLOCKED_TRACKING_CONTENT
    *   Tracking content has been blocked from loading.
    *
-   * STATE_LOADED_TRACKING_CONTENT
-   *   Tracking content has been loaded.
+   * STATE_LOADED_LEVEL_1_TRACKING_CONTENT
+   *   Tracking content from the Disconnect Level 1 list has been loaded.
+   *
+   * STATE_LOADED_LEVEL_2_TRACKING_CONTENT
+   *   Tracking content from the Disconnect Level 2 list has been loaded.
    *
    * STATE_BLOCKED_FINGERPRINTING_CONTENT
    *   Fingerprinting content has been blocked from loading.
    *
    * STATE_LOADED_FINGERPRINTING_CONTENT
    *   Fingerprinting content has been loaded.
    *
    * STATE_BLOCKED_CRYPTOMINING_CONTENT
@@ -325,34 +328,35 @@ interface nsIWebProgressListener : nsISu
    *   Rejected because cookie policy blocks 3rd party cookies.
    *
    * STATE_BLOCKED_SOCIALTRACKING_CONTENT
    *   SocialTracking content has been blocked from loading.
    *
    * STATE_LOADED_SOCIALTRACKING_CONTENT
    *   SocialTracking content has been loaded.
    */
-  const unsigned long STATE_BLOCKED_TRACKING_CONTENT       = 0x00001000;
-  const unsigned long STATE_LOADED_TRACKING_CONTENT        = 0x00002000;
-  const unsigned long STATE_BLOCKED_FINGERPRINTING_CONTENT = 0x00000040;
-  const unsigned long STATE_LOADED_FINGERPRINTING_CONTENT  = 0x00000400;
-  const unsigned long STATE_BLOCKED_CRYPTOMINING_CONTENT   = 0x00000800;
-  const unsigned long STATE_LOADED_CRYPTOMINING_CONTENT    = 0x00200000;
-  const unsigned long STATE_BLOCKED_UNSAFE_CONTENT         = 0x00004000;
-  const unsigned long STATE_COOKIES_LOADED                 = 0x00008000;
-  const unsigned long STATE_COOKIES_LOADED_TRACKER         = 0x00040000;
-  const unsigned long STATE_COOKIES_LOADED_SOCIALTRACKER   = 0x00080000;
-  const unsigned long STATE_COOKIES_BLOCKED_BY_PERMISSION  = 0x10000000;
-  const unsigned long STATE_COOKIES_BLOCKED_TRACKER        = 0x20000000;
-  const unsigned long STATE_COOKIES_BLOCKED_SOCIALTRACKER  = 0x01000000;
-  const unsigned long STATE_COOKIES_BLOCKED_ALL            = 0x40000000;
-  const unsigned long STATE_COOKIES_PARTITIONED_FOREIGN    = 0x80000000;
-  const unsigned long STATE_COOKIES_BLOCKED_FOREIGN        = 0x00000080;
-  const unsigned long STATE_BLOCKED_SOCIALTRACKING_CONTENT = 0x00010000;
-  const unsigned long STATE_LOADED_SOCIALTRACKING_CONTENT  = 0x00020000;
+  const unsigned long STATE_BLOCKED_TRACKING_CONTENT        = 0x00001000;
+  const unsigned long STATE_LOADED_LEVEL_1_TRACKING_CONTENT = 0x00002000;
+  const unsigned long STATE_LOADED_LEVEL_2_TRACKING_CONTENT = 0x00100000;
+  const unsigned long STATE_BLOCKED_FINGERPRINTING_CONTENT  = 0x00000040;
+  const unsigned long STATE_LOADED_FINGERPRINTING_CONTENT   = 0x00000400;
+  const unsigned long STATE_BLOCKED_CRYPTOMINING_CONTENT    = 0x00000800;
+  const unsigned long STATE_LOADED_CRYPTOMINING_CONTENT     = 0x00200000;
+  const unsigned long STATE_BLOCKED_UNSAFE_CONTENT          = 0x00004000;
+  const unsigned long STATE_COOKIES_LOADED                  = 0x00008000;
+  const unsigned long STATE_COOKIES_LOADED_TRACKER          = 0x00040000;
+  const unsigned long STATE_COOKIES_LOADED_SOCIALTRACKER    = 0x00080000;
+  const unsigned long STATE_COOKIES_BLOCKED_BY_PERMISSION   = 0x10000000;
+  const unsigned long STATE_COOKIES_BLOCKED_TRACKER         = 0x20000000;
+  const unsigned long STATE_COOKIES_BLOCKED_SOCIALTRACKER   = 0x01000000;
+  const unsigned long STATE_COOKIES_BLOCKED_ALL             = 0x40000000;
+  const unsigned long STATE_COOKIES_PARTITIONED_FOREIGN     = 0x80000000;
+  const unsigned long STATE_COOKIES_BLOCKED_FOREIGN         = 0x00000080;
+  const unsigned long STATE_BLOCKED_SOCIALTRACKING_CONTENT  = 0x00010000;
+  const unsigned long STATE_LOADED_SOCIALTRACKING_CONTENT   = 0x00020000;
 
   /**
    * Notification indicating the state has changed for one of the requests
    * associated with aWebProgress.
    *
    * @param aWebProgress
    *        The nsIWebProgress instance that fired the notification
    * @param aRequest