Bug 1584312 - Implement blocked tracker count milestones UI. r=johannh,flod
☠☠ backed out by 0be15ecc1848 ☠ ☠
authorNihanth Subramanya <nhnt11@gmail.com>
Tue, 15 Oct 2019 17:09:59 +0000
changeset 559127 1a28547631d7e62dc2ca218a9af527a4a60f075a
parent 559126 1f4132be47b0c11f3a1343d408df88ca3b1a3677
child 559128 0198b3ef7bf55329812bdc49dceda8c365d2a3fe
push id12175
push userccoroiu@mozilla.com
push dateThu, 17 Oct 2019 19:29:09 +0000
treeherdermozilla-beta@d333b6ef1fd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh, flod
bugs1584312
milestone71.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 1584312 - Implement blocked tracker count milestones UI. r=johannh,flod Differential Revision: https://phabricator.services.mozilla.com/D47799
browser/base/content/browser-siteProtections.js
browser/base/content/test/siteProtections/browser.ini
browser/base/content/test/siteProtections/browser_protections_UI.js
browser/base/content/test/siteProtections/browser_protections_UI_milestones.js
browser/base/content/test/siteProtections/head.js
browser/components/controlcenter/content/protectionsPanel.inc.xul
browser/locales/en-US/chrome/browser/browser.properties
browser/themes/shared/controlcenter/etp-milestone.svg
browser/themes/shared/controlcenter/panel.inc.css
browser/themes/shared/jar.inc.mn
--- a/browser/base/content/browser-siteProtections.js
+++ b/browser/base/content/browser-siteProtections.js
@@ -1259,16 +1259,23 @@ var gProtectionsHandler = {
 
   get noTrackersDetectedDescription() {
     delete this.noTrackersDetectedDescription;
     return (this.noTrackersDetectedDescription = document.getElementById(
       "protections-popup-no-trackers-found-description"
     ));
   },
 
+  get _protectionsPopupMilestonesText() {
+    delete this._protectionsPopupMilestonesText;
+    return (this._protectionsPopupMilestonesText = document.getElementById(
+      "protections-popup-milestones-text"
+    ));
+  },
+
   get _notBlockingWhyLink() {
     delete this._notBlockingWhyLink;
     return (this._notBlockingWhyLink = document.getElementById(
       "protections-popup-not-blocking-section-why"
     ));
   },
 
   get hasException() {
@@ -1332,16 +1339,52 @@ var gProtectionsHandler = {
 
     XPCOMUtils.defineLazyPreferenceGetter(
       this,
       "_protectionsPopupToastTimeout",
       "browser.protections_panel.toast.timeout",
       3000
     );
 
+    XPCOMUtils.defineLazyPreferenceGetter(
+      this,
+      "milestoneListPref",
+      "browser.contentblocking.cfr-milestone.milestones",
+      [],
+      () => this.maybeSetMilestoneCounterText(),
+      val => JSON.parse(val)
+    );
+
+    XPCOMUtils.defineLazyPreferenceGetter(
+      this,
+      "milestonePref",
+      "browser.contentblocking.cfr-milestone.milestone-achieved",
+      0,
+      () => this.maybeSetMilestoneCounterText()
+    );
+
+    XPCOMUtils.defineLazyPreferenceGetter(
+      this,
+      "milestoneTimestampPref",
+      "browser.contentblocking.cfr-milestone.milestone-shown-time",
+      0,
+      null,
+      val => parseInt(val)
+    );
+
+    XPCOMUtils.defineLazyPreferenceGetter(
+      this,
+      "milestonesEnabledPref",
+      "browser.contentblocking.cfr-milestone.enabled",
+      false,
+      () => this.maybeSetMilestoneCounterText()
+    );
+
+    this.maybeSetMilestoneCounterText();
+
     for (let blocker of this.blockers) {
       if (blocker.init) {
         blocker.init();
       }
     }
 
     this.updateAnimationsEnabled();
 
@@ -1378,16 +1421,21 @@ var gProtectionsHandler = {
   },
 
   openProtections(relatedToCurrent = false) {
     switchToTabHavingURI("about:protections", true, {
       replaceQueryString: true,
       relatedToCurrent,
       triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
     });
+
+    // Don't show the milestones section anymore.
+    Services.prefs.clearUserPref(
+      "browser.contentblocking.cfr-milestone.milestone-shown-time"
+    );
   },
 
   async showTrackersSubview(event) {
     await TrackingProtection.updateSubView();
     this._protectionsPopupMultiView.showSubView(
       "protections-popup-trackersView"
     );
   },
@@ -1783,16 +1831,26 @@ var gProtectionsHandler = {
         gNavigatorBundle.getFormattedString("protections.enableAriaLabel", [
           host,
         ])
       );
     }
 
     // Update the tooltip of the blocked tracker counter.
     this.maybeUpdateEarliestRecordedDateTooltip();
+
+    let today = Date.now();
+    let threeDaysMillis = 72 * 60 * 60 * 1000;
+    let expired = today - this.milestoneTimestampPref > threeDaysMillis;
+
+    if (this._milestoneTextSet && !expired) {
+      this._protectionsPopup.setAttribute("milestone", this.milestonePref);
+    } else {
+      this._protectionsPopup.removeAttribute("milestone");
+    }
   },
 
   /*
    * This function sorts the category items into the Blocked/Allowed/None Detected
    * sections. It's called immediately in onContentBlockingEvent if the popup
    * is presently open. Otherwise, the next time the popup is shown.
    */
   reorderCategoryItems() {
@@ -1947,16 +2005,56 @@ var gProtectionsHandler = {
 
     // Show the counter if the number of tracker is not zero.
     this._protectionsPopupTrackersCounterBox.toggleAttribute(
       "showing",
       trackerCount != 0
     );
   },
 
+  // Whenever one of the milestone prefs are changed, we attempt to update
+  // the milestone section string. This requires us to fetch the earliest
+  // recorded date from the Tracking DB, hence this process is async.
+  // When completed, we set _milestoneSetText to signal that the section
+  // is populated and ready to be shown - which happens next time we call
+  // refreshProtectionsPopup.
+  _milestoneTextSet: false,
+  async maybeSetMilestoneCounterText() {
+    let trackerCount = this.milestonePref;
+    if (
+      !this.milestonesEnabledPref ||
+      !trackerCount ||
+      !this.milestoneListPref.includes(trackerCount)
+    ) {
+      this._milestoneTextSet = false;
+      return;
+    }
+
+    let date = await TrackingDBService.getEarliestRecordedDate();
+    let dateLocaleStr = new Date(date).toLocaleDateString("default", {
+      month: "short",
+      year: "numeric",
+    });
+
+    let desc = PluralForm.get(
+      trackerCount,
+      gNavigatorBundle.getString("protections.milestone.description")
+    );
+
+    this._protectionsPopupMilestonesText.textContent = desc
+      .replace("#1", gBrandBundle.GetStringFromName("brandShortName"))
+      .replace(
+        "#2",
+        trackerCount.toLocaleString(Services.locale.appLocalesAsBCP47)
+      )
+      .replace("#3", dateLocaleStr);
+
+    this._milestoneTextSet = true;
+  },
+
   showDisabledTooltipForTPIcon() {
     this._trackingProtectionIconTooltipLabel.textContent = this.strings.disabledTooltipText;
     gIdentityHandler._trackingProtectionIconContainer.setAttribute(
       "aria-label",
       this.strings.disabledTooltipText
     );
   },
 
--- a/browser/base/content/test/siteProtections/browser.ini
+++ b/browser/base/content/test/siteProtections/browser.ini
@@ -1,5 +1,6 @@
 [DEFAULT]
 support-files =
   head.js
 
 [browser_protections_UI.js]
+[browser_protections_UI_milestones.js]
--- a/browser/base/content/test/siteProtections/browser_protections_UI.js
+++ b/browser/base/content/test/siteProtections/browser_protections_UI.js
@@ -4,60 +4,16 @@
 /* Basic UI tests for the protections panel */
 
 ChromeUtils.defineModuleGetter(
   this,
   "ContentBlockingAllowList",
   "resource://gre/modules/ContentBlockingAllowList.jsm"
 );
 
-function checkClickTelemetry(objectName, value) {
-  let events = Services.telemetry.snapshotEvents(
-    Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS
-  ).parent;
-  let buttonEvents = events.filter(
-    e =>
-      e[1] == "security.ui.protectionspopup" &&
-      e[2] == "click" &&
-      e[3] == objectName &&
-      (!value || e[4] == value)
-  );
-  is(buttonEvents.length, 1, `recorded ${objectName} telemetry event`);
-}
-
-XPCOMUtils.defineLazyServiceGetter(
-  this,
-  "TrackingDBService",
-  "@mozilla.org/tracking-db-service;1",
-  "nsITrackingDBService"
-);
-
-XPCOMUtils.defineLazyGetter(this, "TRACK_DB_PATH", function() {
-  return OS.Path.join(OS.Constants.Path.profileDir, "protections.sqlite");
-});
-
-const { Sqlite } = ChromeUtils.import("resource://gre/modules/Sqlite.jsm");
-
-async function addTrackerDataIntoDB(count) {
-  const insertSQL =
-    "INSERT INTO events (type, count, timestamp)" +
-    "VALUES (:type, :count, date(:timestamp));";
-
-  let db = await Sqlite.openConnection({ path: TRACK_DB_PATH });
-  let date = new Date().toISOString();
-
-  await db.execute(insertSQL, {
-    type: TrackingDBService.TRACKERS_ID,
-    count,
-    timestamp: date,
-  });
-
-  await db.close();
-}
-
 add_task(async function setup() {
   await SpecialPowers.pushPrefEnv({
     set: [
       // Set the auto hide timing to 100ms for blocking the test less.
       ["browser.protections_panel.toast.timeout", 100],
       // Hide protections cards so as not to trigger more async messaging
       // when landing on the page.
       ["browser.contentblocking.report.monitor.enabled", false],
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/siteProtections/browser_protections_UI_milestones.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function setup() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      // Hide protections cards so as not to trigger more async messaging
+      // when landing on the page.
+      ["browser.contentblocking.report.monitor.enabled", false],
+      ["browser.contentblocking.report.lockwise.enabled", false],
+      ["browser.contentblocking.report.proxy.enabled", false],
+    ],
+  });
+});
+
+add_task(async function doTest() {
+  // This also ensures that the DB tables have been initialized.
+  await TrackingDBService.clearAll();
+
+  let milestones = JSON.parse(
+    Services.prefs.getStringPref(
+      "browser.contentblocking.cfr-milestone.milestones"
+    )
+  );
+  let totalTrackerCount = 0;
+
+  let tab = await BrowserTestUtils.openNewForegroundTab(
+    gBrowser,
+    "https://example.com"
+  );
+
+  for (let milestone of milestones) {
+    let trackerCount = milestone - totalTrackerCount;
+    await addTrackerDataIntoDB(trackerCount);
+    totalTrackerCount += trackerCount;
+
+    // Trigger the milestone feature.
+    await TrackingDBService.saveEvents("{}");
+
+    await TestUtils.waitForCondition(
+      () => gProtectionsHandler._milestoneTextSet
+    );
+
+    // We set the shown-time pref to pretend that the CFR has been
+    // shown, so that we can test the panel.
+    // TODO: Full integration test for robustness.
+    Services.prefs.setStringPref(
+      "browser.contentblocking.cfr-milestone.milestone-shown-time",
+      Date.now().toString()
+    );
+
+    await openProtectionsPanel();
+
+    ok(
+      BrowserTestUtils.is_visible(
+        gProtectionsHandler._protectionsPopupMilestonesText
+      ),
+      "Milestones section should be visible in the panel."
+    );
+
+    await closeProtectionsPanel();
+    await openProtectionsPanel();
+
+    ok(
+      BrowserTestUtils.is_visible(
+        gProtectionsHandler._protectionsPopupMilestonesText
+      ),
+      "Milestones section should still be visible in the panel."
+    );
+
+    let newTabPromise = BrowserTestUtils.waitForNewTab(
+      gBrowser,
+      "about:protections"
+    );
+    await EventUtils.synthesizeMouseAtCenter(
+      document.getElementById("protections-popup-milestones-content"),
+      {}
+    );
+    let protectionsTab = await newTabPromise;
+
+    ok(true, "about:protections has been opened as expected.");
+
+    BrowserTestUtils.removeTab(protectionsTab);
+
+    await openProtectionsPanel();
+
+    ok(
+      !BrowserTestUtils.is_visible(
+        gProtectionsHandler._protectionsPopupMilestonesText
+      ),
+      "Milestones section should no longer be visible in the panel."
+    );
+
+    await closeProtectionsPanel();
+  }
+
+  BrowserTestUtils.removeTab(tab);
+  await TrackingDBService.clearAll();
+});
--- a/browser/base/content/test/siteProtections/head.js
+++ b/browser/base/content/test/siteProtections/head.js
@@ -1,11 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+const { Sqlite } = ChromeUtils.import("resource://gre/modules/Sqlite.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(
+  this,
+  "TrackingDBService",
+  "@mozilla.org/tracking-db-service;1",
+  "nsITrackingDBService"
+);
+
+XPCOMUtils.defineLazyGetter(this, "TRACK_DB_PATH", function() {
+  return OS.Path.join(OS.Constants.Path.profileDir, "protections.sqlite");
+});
+
 var protectionsPopup = document.getElementById("protections-popup");
 var protectionsPopupMainView = document.getElementById(
   "protections-popup-mainView"
 );
 var protectionsPopupHeader = document.getElementById(
   "protections-popup-mainView-panel-header"
 );
 
@@ -56,8 +69,39 @@ async function closeProtectionsPanel() {
   let popuphiddenPromise = BrowserTestUtils.waitForEvent(
     protectionsPopup,
     "popuphidden"
   );
 
   PanelMultiView.hidePopup(protectionsPopup);
   await popuphiddenPromise;
 }
+
+function checkClickTelemetry(objectName, value) {
+  let events = Services.telemetry.snapshotEvents(
+    Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS
+  ).parent;
+  let buttonEvents = events.filter(
+    e =>
+      e[1] == "security.ui.protectionspopup" &&
+      e[2] == "click" &&
+      e[3] == objectName &&
+      (!value || e[4] == value)
+  );
+  is(buttonEvents.length, 1, `recorded ${objectName} telemetry event`);
+}
+
+async function addTrackerDataIntoDB(count) {
+  const insertSQL =
+    "INSERT INTO events (type, count, timestamp)" +
+    "VALUES (:type, :count, date(:timestamp));";
+
+  let db = await Sqlite.openConnection({ path: TRACK_DB_PATH });
+  let date = new Date().toISOString();
+
+  await db.execute(insertSQL, {
+    type: TrackingDBService.TRACKERS_ID,
+    count,
+    timestamp: date,
+  });
+
+  await db.close();
+}
--- a/browser/components/controlcenter/content/protectionsPanel.inc.xul
+++ b/browser/components/controlcenter/content/protectionsPanel.inc.xul
@@ -150,16 +150,28 @@
           <hbox id="protections-popup-trackers-blocked-counter-box"
                 align="center"
                 end="0">
             <description id="protections-popup-trackers-blocked-counter-description"
                          onclick="gProtectionsHandler.openProtections(true);"/>
           </hbox>
         </stack>
       </vbox>
+
+      <hbox id="protections-popup-milestones" class="protections-popup-section">
+        <!-- wrap=true is needed for descriptionheightworkaround, see bug 1564077 -->
+        <toolbarbutton id="protections-popup-milestones-content"
+                       flex="1"
+                       wrap="true"
+                       oncommand="gProtectionsHandler.openProtections(true);">
+          <description id="protections-popup-milestones-text" flex="1"
+                       role="heading" aria-level="2"/>
+          <image id="protections-popup-milestones-illustration"/>
+        </toolbarbutton>
+      </hbox>
     </panelview>
 
     <!-- Site Not Working? SubView -->
     <panelview id="protections-popup-siteNotWorkingView"
                role="document"
                title="&protections.siteNotWorkingView.title;"
                descriptionheightworkaround="true"
                flex="1">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -570,16 +570,26 @@ protections.notBlocking.socialMediaTrack
 #   Semicolon-separated list of plural forms.
 #   See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 #   Replacement for #1 is a locale-string converted positive integer.
 protections.footer.blockedTrackerCounter.description=1 Blocked;#1 Blocked
 # LOCALIZATION NOTE (protections.footer.blockedTrackerCounter.tooltip):
 #   %S is the date on which we started counting (e.g., July 17, 2019).
 protections.footer.blockedTrackerCounter.tooltip=Since %S
 
+# Milestones section in the Protections Panel
+# LOCALIZATION NOTE (protections.milestone.description):
+#   Semicolon-separated list of plural forms.
+#   See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+#   #1 is replaced with brandShortName.
+#   #2 is replaced with the (locale-formatted) number of trackers blocked
+#   #3 is replaced by a locale-formatted date with short month and numeric year.
+#   In English this looks like "Firefox blocked over 10,000 trackers since Oct 2019"
+protections.milestone.description=#1 blocked #2 tracker since #3;#1 blocked over #2 trackers since #3
+
 # Edit Bookmark UI
 editBookmarkPanel.newBookmarkTitle=New Bookmark
 editBookmarkPanel.editBookmarkTitle=Edit This Bookmark
 editBookmarkPanel.cancel.label=Cancel
 editBookmarkPanel.cancel.accesskey=C
 
 # LOCALIZATION NOTE (editBookmark.removeBookmarks.label): Semicolon-separated list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/controlcenter/etp-milestone.svg
@@ -0,0 +1,4 @@
+<!-- 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/. -->
+<svg width="32" height="40" xmlns="http://www.w3.org/2000/svg"><path d="M31.617 10.435V5.009a1.74 1.74 0 00-1.443-1.74L16.296.87a1.687 1.687 0 00-.592 0L1.87 3.288a1.74 1.74 0 00-1.444 1.74v5.425c-.043 2.87-.07 4.948.052 6.244.748 8.295 2.496 12.078 5.47 16.147a16.165 16.165 0 009.704 6.287c.113.013.227.013.34 0 .112.014.226.014.338 0a16.409 16.409 0 009.757-6.313c2.974-4.07 4.722-7.826 5.47-16.156.13-1.287.104-3.365.06-6.226z" fill="context-fill"/><path d="M15.704.87L1.87 3.287a1.74 1.74 0 00-1.444 1.74v5.425c-.043 2.87-.07 4.948.052 6.244.748 8.295 2.496 12.078 5.47 16.147a16.165 16.165 0 009.704 6.287c.113.013.227.013.34 0V.87a1.74 1.74 0 00-.288 0z" opacity=".5" fill="context-stroke"/><path d="M12.878 11.635c-1.4.174-2.295 2.191-2.052 4.122.122.982.713 1.452.87 3.217 0 .174 0 .322.043.47a16.66 16.66 0 003.044-.166 5.009 5.009 0 01-.053-.87c.219-1.13.347-2.275.383-3.425-.226-1.757-.983-3.505-2.235-3.348zm2.052 8.487c-.84.135-1.688.202-2.539.2h-.548c.025.398.083.794.174 1.182.14.566.618 1.557 1.687 1.418a1.452 1.452 0 001.374-1.809c-.035-.348-.095-.67-.148-.991zm4.2-5.879c-1.252-.156-2.008 1.592-2.234 3.34.036 1.15.164 2.296.382 3.426.008.29-.01.581-.052.87 1.002.146 2.015.202 3.026.164v-.47c.174-1.738.765-2.234.87-3.216.295-1.922-.6-3.94-1.992-4.114zm-2.208 9.47a1.452 1.452 0 001.339 1.809c1.07.139 1.548-.87 1.687-1.418a7.07 7.07 0 00.174-1.174H19.6c-.845 0-1.688-.066-2.522-.2-.06.322-.121.644-.156.983z" fill="#FBFBFE"/></svg>
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -1102,8 +1102,79 @@ description#identity-popup-content-verif
 
 .protections-popup-description {
   border-bottom: 1px solid var(--panel-separator-color);
 }
 
 .protections-popup-description > description {
   margin: 10px 24px;
 }
+
+#protections-popup:not([milestone]) #protections-popup-milestones {
+  display: none;
+}
+
+#protections-popup-milestones {
+  margin: 0 calc(var(--horizontal-padding) * 0.5) var(--vertical-section-padding);
+  /* Override .protections-popup-section */
+  border-top: none;
+  background-color: var(--arrowpanel-dimmed);
+}
+
+#protections-popup-milestones-content {
+  padding: var(--vertical-section-padding) calc(var(--horizontal-padding) * 0.5);
+  margin: 0;
+}
+
+#protections-popup-milestones-content:hover {
+  background-color: var(--arrowpanel-dimmed);
+}
+
+#protections-popup-milestones-content:hover:active {
+  background-color: var(--arrowpanel-dimmed-further);
+}
+
+#protections-popup-milestones-text {
+  font-size: 1.23em;
+  margin: 0;
+}
+
+#protections-popup-milestones-illustration {
+  list-style-image: url(chrome://browser/skin/controlcenter/etp-milestone.svg);
+  -moz-context-properties: fill, stroke;
+  margin-inline-start: var(--horizontal-padding);
+  margin-inline-end: 4px;
+}
+
+#protections-popup[milestone] #protections-popup-milestones-illustration {
+  fill: #45278d;
+  stroke: #321c64;
+}
+
+#protections-popup[milestone="5000"] #protections-popup-milestones-illustration {
+  fill: #5a29cb;
+  stroke: #45278d;
+}
+
+#protections-popup[milestone="10000"] #protections-popup-milestones-illustration {
+  fill: #7641e5;
+  stroke: #5a29cb;
+}
+
+#protections-popup[milestone="25000"] #protections-popup-milestones-illustration {
+  fill: #e31587;
+  stroke: #c60084;
+}
+
+#protections-popup[milestone="50000"] #protections-popup-milestones-illustration {
+  fill: #ff298a;
+  stroke: #e31587;
+}
+
+#protections-popup[milestone="100000"] #protections-popup-milestones-illustration {
+  fill: #ffa436;
+  stroke: #e27f2e;
+}
+
+#protections-popup[milestone="500000"] #protections-popup-milestones-illustration {
+  fill: #ffd567;
+  stroke: #ffbd4f;
+}
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -26,16 +26,17 @@
   skin/classic/browser/addons/addon-private-browsing.svg       (../shared/addons/addon-private-browsing.svg)
   skin/classic/browser/controlcenter/3rdpartycookies.svg       (../shared/controlcenter/3rdpartycookies.svg)
   skin/classic/browser/controlcenter/3rdpartycookies-disabled.svg (../shared/controlcenter/3rdpartycookies-disabled.svg)
   skin/classic/browser/controlcenter/cryptominers.svg          (../shared/controlcenter/cryptominers.svg)
   skin/classic/browser/controlcenter/dashboard.svg             (../shared/controlcenter/dashboard.svg)
   skin/classic/browser/controlcenter/cryptominers-disabled.svg (../shared/controlcenter/cryptominers-disabled.svg)
   skin/classic/browser/controlcenter/mcb-disabled.svg          (../shared/controlcenter/mcb-disabled.svg)
   skin/classic/browser/controlcenter/extension.svg             (../shared/controlcenter/extension.svg)
+  skin/classic/browser/controlcenter/etp-milestone.svg         (../shared/controlcenter/etp-milestone.svg)
   skin/classic/browser/controlcenter/fingerprinters.svg        (../shared/controlcenter/fingerprinters.svg)
   skin/classic/browser/controlcenter/fingerprinters-disabled.svg (../shared/controlcenter/fingerprinters-disabled.svg)
   skin/classic/browser/controlcenter/info.svg                  (../shared/controlcenter/info.svg)
   skin/classic/browser/controlcenter/socialblock.svg           (../shared/controlcenter/socialblock.svg)
   skin/classic/browser/controlcenter/socialblock-disabled.svg  (../shared/controlcenter/socialblock-disabled.svg)
   skin/classic/browser/controlcenter/tracker-image.svg         (../shared/controlcenter/tracker-image.svg)
   skin/classic/browser/controlcenter/tracker-image-disabled.svg  (../shared/controlcenter/tracker-image-disabled.svg)
   skin/classic/browser/controlcenter/trackers.svg              (../shared/controlcenter/trackers.svg)