Bug 1593258 - set milestone achieved flag only when milestone shown, to allow it to trigger the show message again. r=k88hudson,nhnt11
authorErica Wright <ewright@mozilla.com>
Fri, 15 Nov 2019 21:40:26 +0000
changeset 502285 2cadb540632e5ff822f31b444a3870869676d772
parent 502284 284910a66370a61a378e10f667256b5c5b59607f
child 502286 4921e64c42017a8fc5d935eb12e3334bb23a7b60
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)
reviewersk88hudson, nhnt11
bugs1593258
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 1593258 - set milestone achieved flag only when milestone shown, to allow it to trigger the show message again. r=k88hudson,nhnt11 Differential Revision: https://phabricator.services.mozilla.com/D52771
browser/app/profile/firefox.js
browser/base/content/test/siteProtections/browser_protections_UI_milestones.js
browser/components/newtab/lib/CFRPageActions.jsm
toolkit/components/antitracking/TrackingDBService.jsm
toolkit/components/antitracking/test/xpcshell/test_tracking_db_service.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1645,16 +1645,17 @@ pref("browser.contentblocking.report.loc
 pref("browser.contentblocking.report.social.url", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/social-media-tracking-report");
 pref("browser.contentblocking.report.cookie.url", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/cross-site-tracking-report");
 pref("browser.contentblocking.report.tracker.url", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/tracking-content-report");
 pref("browser.contentblocking.report.fingerprinter.url", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/fingerprinters-report");
 pref("browser.contentblocking.report.cryptominer.url", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/cryptominers-report");
 
 pref("browser.contentblocking.cfr-milestone.enabled", true);
 pref("browser.contentblocking.cfr-milestone.milestone-achieved", 0);
+// Milestones should always be in increasing order
 pref("browser.contentblocking.cfr-milestone.milestones", "[1000, 5000, 10000, 25000, 50000, 100000, 500000]");
 
 // Enables the new Protections Panel.
 #ifdef NIGHTLY_BUILD
   pref("browser.protections_panel.enabled", true);
   pref("browser.protections_panel.infoMessage.seen", false);
 #endif
 
--- a/browser/base/content/test/siteProtections/browser_protections_UI_milestones.js
+++ b/browser/base/content/test/siteProtections/browser_protections_UI_milestones.js
@@ -18,30 +18,28 @@ 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("{}");
+    Services.prefs.setIntPref(
+      "browser.contentblocking.cfr-milestone.milestone-achieved",
+      milestone
+    );
 
     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.
--- a/browser/components/newtab/lib/CFRPageActions.jsm
+++ b/browser/components/newtab/lib/CFRPageActions.jsm
@@ -26,16 +26,24 @@ ChromeUtils.defineModuleGetter(
 );
 ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyServiceGetter(
   this,
   "TrackingDBService",
   "@mozilla.org/tracking-db-service;1",
   "nsITrackingDBService"
 );
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
+  "milestones",
+  "browser.contentblocking.cfr-milestone.milestones",
+  "[]",
+  null,
+  JSON.parse
+);
 
 const POPUP_NOTIFICATION_ID = "contextual-feature-recommendation";
 const ANIMATION_BUTTON_ID = "cfr-notification-footer-animation-button";
 const ANIMATION_LABEL_ID = "cfr-notification-footer-animation-label";
 const SUMO_BASE_URL = Services.urlFormatter.formatURLPref(
   "app.support.baseURL"
 );
 const ADDONS_API_URL =
@@ -533,17 +541,17 @@ class PageAction {
             string_id: "cfr-doorhanger-pintab-animation-pause",
           });
         }
       };
       animationButton.addEventListener("click", this.onAnimationButtonClick);
     }
   }
 
-  async _renderMilestonePopup(message, browser, cfrMilestonePref) {
+  async _renderMilestonePopup(message, browser) {
     let { content } = message;
     let { primary } = content.buttons;
 
     let dateFormat = new Services.intl.DateTimeFormat(
       this.window.gBrowser.ownerGlobal.navigator.language,
       {
         month: "long",
         year: "numeric",
@@ -551,23 +559,30 @@ class PageAction {
     ).format;
 
     let earliestDate = await TrackingDBService.getEarliestRecordedDate();
     let monthName = dateFormat(new Date(earliestDate));
     let panelTitle = "";
     let headerLabel = this.window.document.getElementById(
       "cfr-notification-header-label"
     );
+    let reachedMilestone = null;
+    let totalSaved = await TrackingDBService.sumAllEvents();
+    for (let milestone of milestones) {
+      if (totalSaved >= milestone) {
+        reachedMilestone = milestone;
+      }
+    }
     if (typeof message.content.heading_text === "string") {
       // This is a test environment.
       panelTitle = message.content.heading_text;
       headerLabel.value = panelTitle;
     } else {
       this._l10n.setAttributes(headerLabel, content.heading_text.string_id, {
-        blockedCount: cfrMilestonePref,
+        blockedCount: reachedMilestone,
         date: monthName,
       });
       await this._l10n.translateElements([headerLabel]);
     }
 
     // Use the message layout as a CSS selector to hide different parts of the
     // notification template markup
     this.window.document
@@ -624,17 +639,20 @@ class PageAction {
       "cfr",
       mainAction,
       null,
       {
         hideClose: true,
         eventCallback: manageClass,
       }
     );
-
+    Services.prefs.setIntPref(
+      "browser.contentblocking.cfr-milestone.milestone-achieved",
+      reachedMilestone
+    );
     Services.prefs.setStringPref(
       "browser.contentblocking.cfr-milestone.milestone-shown-time",
       Date.now().toString()
     );
   }
 
   // eslint-disable-next-line max-statements
   async _renderPopup(message, browser) {
@@ -898,27 +916,27 @@ class PageAction {
     this._sendTelemetry({
       message_id: id,
       bucket_id: content.bucket_id,
       event: "CLICK_DOORHANGER",
     });
     await this._renderPopup(message, browser);
   }
 
-  async showMilestonePopup(cfrMilestonePref) {
+  async showMilestonePopup() {
     const browser = this.window.gBrowser.selectedBrowser;
     const message = RecommendationMap.get(browser);
     const { content } = message;
 
     // A hacky way of setting the popup anchor outside the usual url bar icon box
     // See https://searchfox.org/mozilla-central/rev/847b64cc28b74b44c379f9bff4f415b97da1c6d7/toolkit/modules/PopupNotifications.jsm#42
     browser.cfrpopupnotificationanchor =
       this.window.document.getElementById(content.anchor_id) || this.container;
 
-    await this._renderMilestonePopup(message, browser, cfrMilestonePref);
+    await this._renderMilestonePopup(message, browser);
     return true;
   }
 }
 
 function isHostMatch(browser, host) {
   return (
     browser.documentURI.scheme.startsWith("http") &&
     browser.documentURI.host === host
@@ -997,45 +1015,34 @@ const CFRPageActions = {
    * @param browser                 The browser for the recommendation
    * @param recommendation          The recommendation to show
    * @param dispatchToASRouter      A function to dispatch resulting actions to
    * @return                        Did adding the recommendation succeed?
    */
   async showMilestone(browser, message, dispatchToASRouter, options = {}) {
     let win = null;
     const { id, content } = message;
-    let cfrMilestonePref = Services.prefs.getIntPref(
-      "browser.contentblocking.cfr-milestone.milestone-achieved",
-      0
-    );
 
     // If we are forcing via the Admin page, the browser comes in a different format
     if (options.force) {
       win = browser.browser.ownerGlobal;
       RecommendationMap.set(browser.browser, { id, retain: true, content });
     } else {
       win = browser.ownerGlobal;
       RecommendationMap.set(browser, { id, retain: true, content });
-      if (!cfrMilestonePref) {
-        return false;
-      }
     }
 
     if (!PageActionMap.has(win)) {
       PageActionMap.set(win, new PageAction(win, dispatchToASRouter));
     }
 
-    let successfullyShown = await PageActionMap.get(win).showMilestonePopup(
-      cfrMilestonePref
-    );
-    if (successfullyShown) {
-      PageActionMap.get(win).addImpression(message);
-    }
+    await PageActionMap.get(win).showMilestonePopup();
+    PageActionMap.get(win).addImpression(message);
 
-    return successfullyShown;
+    return true;
   },
 
   /**
    * Force a recommendation to be shown. Should only happen via the Admin page.
    * @param browser                 The browser for the recommendation
    * @param recommendation  The recommendation to show
    * @param dispatchToASRouter      A function to dispatch resulting actions to
    * @return                        Did adding the recommendation succeed?
--- a/toolkit/components/antitracking/TrackingDBService.jsm
+++ b/toolkit/components/antitracking/TrackingDBService.jsm
@@ -304,32 +304,26 @@ TrackingDBService.prototype = {
     let totalSaved = await this.sumAllEvents();
 
     let reachedMilestone = null;
     let nextMilestone = null;
     for (let [index, milestone] of milestones.entries()) {
       if (totalSaved >= milestone) {
         reachedMilestone = milestone;
         nextMilestone = milestones[index + 1];
-      } else {
-        break;
       }
     }
 
     // Show the milestone message if the user is not too close to the next milestone.
     // Or if there is no next milestone.
     if (
       reachedMilestone &&
       (!nextMilestone || nextMilestone - totalSaved > 3000) &&
       (!oldMilestone || oldMilestone < reachedMilestone)
     ) {
-      Services.prefs.setIntPref(
-        "browser.contentblocking.cfr-milestone.milestone-achieved",
-        reachedMilestone
-      );
       Services.obs.notifyObservers(
         {
           wrappedJSObject: {
             event: "ContentBlockingMilestone",
           },
         },
         "SiteProtection:ContentBlockingMilestone"
       );
--- a/toolkit/components/antitracking/test/xpcshell/test_tracking_db_service.js
+++ b/toolkit/components/antitracking/test/xpcshell/test_tracking_db_service.js
@@ -4,16 +4,19 @@
 
 // Note: This test may cause intermittents if run at exactly midnight.
 
 "use strict";
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
+const { TestUtils } = ChromeUtils.import(
+  "resource://testing-common/TestUtils.jsm"
+);
 const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 const { Sqlite } = ChromeUtils.import("resource://gre/modules/Sqlite.jsm");
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyServiceGetter(
   this,
   "TrackingDBService",
   "@mozilla.org/tracking-db-service;1",
   "nsITrackingDBService"
@@ -92,19 +95,39 @@ const LOG = {
 
 do_get_profile();
 
 Services.prefs.setBoolPref("browser.contentblocking.database.enabled", true);
 Services.prefs.setBoolPref(
   "privacy.socialtracking.block_cookies.enabled",
   true
 );
+Services.prefs.setBoolPref(
+  "browser.contentblocking.cfr-milestone.enabled",
+  true
+);
+Services.prefs.setIntPref(
+  "browser.contentblocking.cfr-milestone.update-interval",
+  0
+);
+Services.prefs.setStringPref(
+  "browser.contentblocking.cfr-milestone.milestones",
+  "[1000, 5000, 10000, 25000, 100000, 500000]"
+);
+
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("browser.contentblocking.database.enabled");
   Services.prefs.clearUserPref("privacy.socialtracking.block_cookies.enabled");
+  Services.prefs.clearUserPref("browser.contentblocking.cfr-milestone.enabled");
+  Services.prefs.clearUserPref(
+    "browser.contentblocking.cfr-milestone.update-interval"
+  );
+  Services.prefs.clearUserPref(
+    "browser.contentblocking.cfr-milestone.milestones"
+  );
 });
 
 // This tests that data is added successfully, different types of events should get
 // their own entries, when the type is the same they should be aggregated. Events
 // that are not blocking events should not be recorded. Cookie blocking events
 // should only be recorded if we can identify the cookie as a tracking cookie.
 add_task(async function test_save_and_delete() {
   await TrackingDBService.saveEvents(JSON.stringify(LOG));
@@ -418,8 +441,43 @@ add_task(async function test_getEarliest
 
   timestamp = await TrackingDBService.getEarliestRecordedDate();
   let date = new Date(timestamp).toISOString().split("T")[0];
   equal(date, daysBefore9, "The earliest recorded event is nine days before.");
 
   await TrackingDBService.clearAll();
   await db.close();
 });
+
+// This tests that a message to CFR is sent when the amount of saved trackers meets a milestone
+add_task(async function test_sendMilestoneNotification() {
+  let milestones = JSON.parse(
+    Services.prefs.getStringPref(
+      "browser.contentblocking.cfr-milestone.milestones"
+    )
+  );
+  // This creates the schema.
+  await TrackingDBService.saveEvents(JSON.stringify({}));
+  let db = await Sqlite.openConnection({ path: DB_PATH });
+  // save number of trackers equal to the first milestone
+  await db.execute(SQL.insertCustomTimeEvent, {
+    type: TrackingDBService.CRYPTOMINERS_ID,
+    count: milestones[0],
+    timestamp: new Date().toISOString(),
+  });
+
+  let awaitNotification = TestUtils.topicObserved(
+    "SiteProtection:ContentBlockingMilestone"
+  );
+
+  // trigger a "save" event to compare the trackers with the milestone.
+  await TrackingDBService.saveEvents(
+    JSON.stringify({
+      "https://1.example.com": [
+        [Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT, true, 1],
+      ],
+    })
+  );
+  await awaitNotification;
+
+  await TrackingDBService.clearAll();
+  await db.close();
+});