Bug 1579974 - Add celebrating milestone toast when certain numbers of trackers are blocked. r=k88hudson,johannh,fluent-reviewers,flod

Differential Revision: https://phabricator.services.mozilla.com/D47512

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Erica Wright 2019-10-15 19:30:11 +00:00
parent 950b8090b3
commit da9bdae004
17 changed files with 579 additions and 5 deletions

View File

@ -1653,6 +1653,10 @@ pref("browser.contentblocking.report.tracker.url", "https://support.mozilla.org/
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);
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);

View File

@ -166,6 +166,7 @@ for (const type of [
"PIN_CURRENT_TAB",
"ENABLE_FIREFOX_MONITOR",
"OPEN_PROTECTION_PANEL",
"OPEN_PROTECTION_REPORT",
]) {
ASRouterActions[type] = type;
}

View File

@ -26,7 +26,7 @@
"layout": {
"type": "string",
"description": "Attribute used for different groups of messages from the same provider",
"enum": ["message_and_animation", "icon_and_message", "addon_recommendation"]
"enum": ["short_message", "message_and_animation", "icon_and_message", "addon_recommendation"]
},
"anchor_id": {
"type": "string",

View File

@ -1438,6 +1438,9 @@ class _ASRouter {
case "update_action":
ToolbarBadgeHub.registerBadgeNotificationListener(message, { force });
break;
case "milestone_message":
CFRPageActions.showMilestone(target, message, this.dispatch, { force });
break;
default:
try {
target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
@ -1937,6 +1940,9 @@ class _ASRouter {
let { gProtectionsHandler } = target.browser.ownerGlobal;
gProtectionsHandler.showProtectionsPopup({});
break;
case ra.OPEN_PROTECTION_REPORT:
target.browser.ownerGlobal.gProtectionsHandler.openProtections();
break;
}
}

View File

@ -467,6 +467,10 @@ this.ASRouterTriggerListeners = new Map([
if (!this._initialized) {
Services.obs.addObserver(this, "SiteProtection:ContentBlockingEvent");
Services.obs.addObserver(
this,
"SiteProtection:ContentBlockingMilestone"
);
this.onLocationChange = this._onLocationChange.bind(this);
EveryWindow.registerCallback(
this.id,
@ -493,6 +497,10 @@ this.ASRouterTriggerListeners = new Map([
this,
"SiteProtection:ContentBlockingEvent"
);
Services.obs.removeObserver(
this,
"SiteProtection:ContentBlockingMilestone"
);
EveryWindow.unregisterCallback(this.id);
this.onLocationChange = null;
this._initialized = false;
@ -519,6 +527,23 @@ this.ASRouterTriggerListeners = new Map([
});
}
break;
case "SiteProtection:ContentBlockingMilestone":
if (this._events.includes(aSubject.wrappedJSObject.event)) {
this._triggerHandler(
Services.wm.getMostRecentBrowserWindow().gBrowser
.selectedBrowser,
{
id: "trackingProtection",
context: {
pageLoad: this._sessionPageLoad,
},
param: {
type: aSubject.wrappedJSObject.event,
},
}
);
}
break;
}
},

View File

@ -743,6 +743,35 @@ const CFR_MESSAGES = [
params: [Ci.nsIWebProgressListener.STATE_BLOCKED_CRYPTOMINING_CONTENT],
},
},
{
id: "MILESTONE_MESSAGE",
template: "milestone_message",
content: {
layout: "short_message",
category: "cfrFeatures",
anchor_id: "tracking-protection-icon-box",
skip_address_bar_notifier: true,
bucket_id: "CFR_MILESTONE_MESSAGE",
heading_text: { string_id: "cfr-doorhanger-milestone-heading" },
notification_text: "",
text: "",
buttons: {
primary: {
label: { string_id: "cfr-doorhanger-milestone-ok-button" },
action: { type: "OPEN_PROTECTION_REPORT" },
event: "PROTECTION",
},
},
},
targeting: "pageLoad >= 4",
frequency: {
lifetime: 7, // Length of privacy.trackingprotection.cfr-milestone.milestones pref
},
trigger: {
id: "trackingProtection",
params: ["ContentBlockingMilestone"],
},
},
];
const CFRMessageProvider = {

View File

@ -25,6 +25,12 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/L10nRegistry.jsm"
);
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyServiceGetter(
this,
"TrackingDBService",
"@mozilla.org/tracking-db-service;1",
"nsITrackingDBService"
);
const POPUP_NOTIFICATION_ID = "contextual-feature-recommendation";
const ANIMATION_BUTTON_ID = "cfr-notification-footer-animation-button";
@ -521,6 +527,104 @@ class PageAction {
}
}
async _renderMilestonePopup(message, browser, cfrMilestonePref) {
let { content } = message;
let { primary } = content.buttons;
let dateFormat = new Services.intl.DateTimeFormat(
this.window.gBrowser.ownerGlobal.navigator.language,
{
month: "long",
year: "numeric",
}
).format;
let earliestDate = await TrackingDBService.getEarliestRecordedDate();
let monthName = dateFormat(new Date(earliestDate));
let panelTitle = "";
let headerLabel = this.window.document.getElementById(
"cfr-notification-header-label"
);
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,
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
.getElementById("contextual-feature-recommendation-notification")
.setAttribute("data-notification-category", content.layout);
this.window.document
.getElementById("contextual-feature-recommendation-notification")
.setAttribute("data-notification-bucket", content.bucket_id);
let notification = this.window.document.getElementById(
"notification-popup"
);
let primaryBtnString = await this.getStrings(primary.label);
let primaryActionCallback = () => {
this.dispatchUserAction(primary.action);
RecommendationMap.delete(browser);
// Invalidate the pref after the user interacts with the button.
// We don't need to show the illustration in the privacy panel.
Services.prefs.clearUserPref(
"browser.contentblocking.cfr-milestone.milestone-shown-time"
);
};
let mainAction = {
label: primaryBtnString,
accessKey: primaryBtnString.attributes.accesskey,
callback: primaryActionCallback,
};
let style = this.window.document.createElement("style");
style.textContent = `
.cfr-notification-milestone .panel-arrow {
fill: #0250BB !important;
}
`;
let arrow;
let manageClass = event => {
if (event === "dismissed" || event === "removed") {
notification.shadowRoot.removeChild(style);
arrow.classList.remove("cfr-notification-milestone");
} else if (event === "showing") {
notification.shadowRoot.appendChild(style);
arrow = notification.shadowRoot.querySelector(".panel-arrowcontainer");
arrow.classList.add("cfr-notification-milestone");
}
};
// Actually show the notification
this.currentNotification = this.window.PopupNotifications.show(
browser,
POPUP_NOTIFICATION_ID,
panelTitle,
"cfr",
mainAction,
null,
{
hideClose: true,
eventCallback: manageClass,
}
);
Services.prefs.setStringPref(
"browser.contentblocking.cfr-milestone.milestone-shown-time",
Date.now().toString()
);
}
// eslint-disable-next-line max-statements
async _renderPopup(message, browser) {
const { id, content } = message;
@ -787,6 +891,20 @@ class PageAction {
});
await this._renderPopup(message, browser);
}
async showMilestonePopup(cfrMilestonePref) {
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);
return true;
}
}
function isHostMatch(browser, host) {
@ -863,6 +981,47 @@ const CFRPageActions = {
return url;
},
/**
* Show Milestone notification.
* @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);
}
return successfullyShown;
},
/**
* Force a recommendation to be shown. Should only happen via the Admin page.
* @param browser The browser for the recommendation

View File

@ -173,3 +173,15 @@ cfr-doorhanger-fingerprinters-heading = { -brand-short-name } blocked a fingerpr
cfr-doorhanger-fingerprinters-description = Your privacy matters. { -brand-short-name } now blocks fingerprinters, which collect pieces of uniquely identifiable information about your device to track you.
cfr-doorhanger-cryptominers-heading = { -brand-short-name } blocked a cryptominer on this page
cfr-doorhanger-cryptominers-description = Your privacy matters. { -brand-short-name } now blocks cryptominers, which use your systems computing power to mine digital money.
## Enhanced Tracking Protection Milestones
# Variables:
# $blockedCount (Number) - The total count of blocked trackers. This number will always be greater than 1.
# $date (String) - The date we began recording the count of blocked trackers
cfr-doorhanger-milestone-heading =
{ $blockedCount ->
*[other] { -brand-short-name } blocked over <b>{ $blockedCount }</b> trackers since { $date }!
}
cfr-doorhanger-milestone-ok-button = See All
.accesskey = S

View File

@ -14,7 +14,9 @@ const createDummyRecommendation = ({
heading_text,
layout,
skip_address_bar_notifier,
template,
}) => ({
template,
content: {
layout: layout || "addon_recommendation",
category,
@ -126,6 +128,14 @@ function checkCFRSocialTrackingProtection(notification) {
);
}
function checkCFRTrackingProtectionMilestone(notification) {
Assert.ok(notification.hidden === false, "Panel should be visible");
Assert.ok(
notification.getAttribute("data-notification-category") === "short_message",
"Panel have correct data attribute"
);
}
function clearNotifications() {
for (let notification of PopupNotifications._currentNotifications) {
notification.remove();
@ -149,6 +159,7 @@ function trigger_cfr_panel(
layout,
skip_address_bar_notifier = false,
use_single_secondary_button = false,
template = "cfr_doorhanger",
} = {}
) {
// a fake action type will result in the action being ignored
@ -158,6 +169,7 @@ function trigger_cfr_panel(
heading_text,
layout,
skip_address_bar_notifier,
template,
});
if (category !== "cfrAddons") {
delete recommendation.content.addon;
@ -169,7 +181,14 @@ function trigger_cfr_panel(
}
clearNotifications();
if (recommendation.template === "milestone_message") {
return CFRPageActions.showMilestone(
browser,
recommendation,
// Use the real AS dispatch method to trigger real notifications
ASRouter.dispatch
);
}
return CFRPageActions.addRecommendation(
browser,
trigger,
@ -476,6 +495,62 @@ add_task(
}
);
add_task(
async function test_cfr_tracking_protection_milestone_notification_show() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.contentblocking.cfr-milestone.milestone-achieved", 1000],
[
"browser.newtabpage.activity-stream.asrouter.providers.cfr",
`{"id":"cfr","enabled":true,"type":"local","localProvider":"CFRMessageProvider","frequency":{"custom":[{"period":"daily","cap":10}]},"categories":["cfrAddons","cfrFeatures"],"updateCycleInMs":3600000}`,
],
],
});
// addRecommendation checks that scheme starts with http and host matches
let browser = gBrowser.selectedBrowser;
await BrowserTestUtils.loadURI(browser, "http://example.com/");
await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
const showPanel = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown"
);
const response = await trigger_cfr_panel(browser, "example.com", {
action: { type: "OPEN_PROTECTION_REPORT" },
category: "cfrFeatures",
layout: "short_message",
skip_address_bar_notifier: true,
heading_text: "Test Milestone Message",
template: "milestone_message",
});
Assert.ok(
response,
"Should return true if addRecommendation checks were successful"
);
await showPanel;
const notification = document.getElementById(
"contextual-feature-recommendation-notification"
);
// checkCFRSocialTrackingProtection(notification);
checkCFRTrackingProtectionMilestone(notification);
// Check there is a primary button and click it. It will trigger the callback.
Assert.ok(notification.button);
let hidePanel = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popuphidden"
);
document
.getElementById("contextual-feature-recommendation-notification")
.button.click();
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
await hidePanel;
}
);
add_task(async function test_cfr_features_and_addon_show() {
// addRecommendation checks that scheme starts with http and host matches
let browser = gBrowser.selectedBrowser;

View File

@ -266,7 +266,89 @@ add_task(async function check_trackingProtection_listener() {
);
await new Promise(resolve => executeSoon(resolve));
is(observerEvent, 1, "shouldn't receive obs. notification after uninit");
is(pageLoadSum, 2, "shouldn't receive obs. notification after uninit");
}
);
});
add_task(async function check_trackingProtectionMilestone_listener() {
const TEST_URL =
"https://example.com/browser/browser/components/newtab/test/browser/red_page.html";
let observerEvent = 0;
const triggerHandler = (target, trigger) => {
const {
id,
param: { type },
} = trigger;
is(id, "trackingProtection", "should match event name");
is(type, "ContentBlockingMilestone", "Should be the correct event type");
observerEvent += 1;
};
const trackingProtectionListener = ASRouterTriggerListeners.get(
"trackingProtection"
);
// Previously initialized by the Router
trackingProtectionListener.uninit();
// Initialise listener
trackingProtectionListener.init(triggerHandler, ["ContentBlockingMilestone"]);
await BrowserTestUtils.withNewTab(
TEST_URL,
async function triggerTrackingProtection(browser) {
Services.obs.notifyObservers(
{
wrappedJSObject: {
browser,
event: "Other Event",
},
},
"SiteProtection:ContentBlockingMilestone"
);
}
);
is(observerEvent, 0, "shouldn't receive unrelated observer notification");
await BrowserTestUtils.withNewTab(
TEST_URL,
async function triggerTrackingProtection(browser) {
Services.obs.notifyObservers(
{
wrappedJSObject: {
browser,
event: "ContentBlockingMilestone",
},
},
"SiteProtection:ContentBlockingMilestone"
);
await BrowserTestUtils.waitForCondition(
() => observerEvent !== 0,
"Wait for the observer notification to run"
);
is(observerEvent, 1, "should receive observer notification");
}
);
// Uninitialise listener
trackingProtectionListener.uninit();
await BrowserTestUtils.withNewTab(
TEST_URL,
async function triggerTrackingProtectionAfterUninit(browser) {
Services.obs.notifyObservers(
{
wrappedJSObject: {
browser,
event: "ContentBlockingMilestone",
},
},
"SiteProtection:ContentBlockingMilestone"
);
await new Promise(resolve => executeSoon(resolve));
is(observerEvent, 1, "shouldn't receive obs. notification after uninit");
}
);
});

View File

@ -2118,6 +2118,17 @@ describe("ASRouter", () => {
});
});
describe("#onMessage: OPEN_PROTECTION_REPORT", () => {
it("should open protection report", async () => {
const msg = fakeExecuteUserAction({ type: "OPEN_PROTECTION_REPORT" });
let { gProtectionsHandler } = msg.target.browser.ownerGlobal;
await Router.onMessage(msg);
assert.calledOnce(gProtectionsHandler.openProtections);
});
});
describe("#dispatch(action, target)", () => {
it("should an action and target to onMessage", async () => {
// use the IMPRESSION action to make sure actions are actually getting processed

View File

@ -11,8 +11,8 @@ const REGULAR_IDS = [
];
describe("CFRMessageProvider", () => {
it("should have a total of 8 messages", () => {
assert.lengthOf(messages, 8);
it("should have a total of 9 messages", () => {
assert.lengthOf(messages, 9);
});
it("should have one message each for the three regular addons", () => {
for (const id of REGULAR_IDS) {

View File

@ -151,6 +151,7 @@ export class FakeRemotePageManager {
},
gProtectionsHandler: {
showProtectionsPopup: sinon.stub(),
openProtections: sinon.stub(),
},
},
};

View File

@ -173,3 +173,15 @@ cfr-doorhanger-fingerprinters-heading = { -brand-short-name } blocked a fingerpr
cfr-doorhanger-fingerprinters-description = Your privacy matters. { -brand-short-name } now blocks fingerprinters, which collect pieces of uniquely identifiable information about your device to track you.
cfr-doorhanger-cryptominers-heading = { -brand-short-name } blocked a cryptominer on this page
cfr-doorhanger-cryptominers-description = Your privacy matters. { -brand-short-name } now blocks cryptominers, which use your systems computing power to mine digital money.
## Enhanced Tracking Protection Milestones
# Variables:
# $blockedCount (Number) - The total count of blocked trackers. This number will always be greater than 1.
# $date (String) - The date we began recording the count of blocked trackers
cfr-doorhanger-milestone-heading =
{ $blockedCount ->
*[other] { -brand-short-name } blocked over <b>{ $blockedCount }</b> trackers since { $date }!
}
cfr-doorhanger-milestone-ok-button = See All
.accesskey = S

View File

@ -13,6 +13,8 @@
/* Note: Setting this to 0 (without px) breaks CSS calculations for OSX. */
--space-above-tabbar: 0px;
--tabs-navbar-shadow-size: 1px;
--short-notification-background: #0250BB;
--short-notification-gradient: #9059FF;
}
:root[extradragspace][tabsintitlebar]:not([inFullscreen]) {
@ -254,6 +256,94 @@ menupopup::part(drop-indicator) {
display: block;
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] {
background: radial-gradient(circle farthest-side at top right, var(--short-notification-gradient), var(--short-notification-background));
width: unset;
max-width: 700px;
overflow-wrap: break-word;
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"]:-moz-locale-dir(rtl) {
background: radial-gradient(circle farthest-side at top left, var(--short-notification-gradient), var(--short-notification-background));
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-body-container,
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-footer-container,
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] #cfr-notification-header-link {
display: none;
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] #cfr-notification-header {
box-shadow: none;
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] description {
font-weight: 400;
margin: unset;
margin-inline-end: 12px;
margin-inline-start: 12px;
transform: translateY(50%);
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-button-container,
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-header-container {
display: inline-flex;
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-button-container {
float: right;
background-color: transparent;
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-button-container:-moz-locale-dir(rtl) {
float: left;
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-button.popup-notification-primary-button {
background-color: rgba(216, 216, 216, 0.2);
border-radius: 2px;
margin: 4px;
padding: 3px 12px;
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-button.popup-notification-primary-button:after {
content: url(chrome://browser/skin/back-12.svg);
-moz-context-properties: fill;
fill: currentColor;
transform: scaleX(-1) translateY(2px);
float: right;
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"]:-moz-locale-dir(rtl) .popup-notification-button.popup-notification-primary-button:after {
float: left;
transform: scaleX(1) translateY(2px);
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-button.popup-notification-primary-button:hover {
background-color: rgba(216, 216, 216, 0.4);
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-button.popup-notification-primary-button:active {
background-color: rgba(216, 216, 216, 0.5);
}
:root[lwt-popup-brighttext] #contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-button.popup-notification-primary-button {
background-color: rgba(216, 216, 216, 0.5);
}
:root[lwt-popup-brighttext] #contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-button.popup-notification-primary-button:hover {
background-color: rgba(216, 216, 216, 0.4);
}
:root[lwt-popup-brighttext] #contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-button.popup-notification-primary-button:active {
background-color: rgba(216, 216, 216, 0.2);
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_MILESTONE_MESSAGE"] .popup-notification-header-container {
color: white;
max-width: unset;
}
#contextual-feature-recommendation-notification[data-notification-bucket="CFR_SOCIAL_TRACKING_PROTECTION"] {
width: 386px;
}

View File

@ -28,6 +28,29 @@ XPCOMUtils.defineLazyPreferenceGetter(
false
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"milestoneMessagingEnabled",
"browser.contentblocking.cfr-milestone.enabled",
false
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"milestones",
"browser.contentblocking.cfr-milestone.milestones",
"[]",
null,
JSON.parse
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"oldMilestone",
"browser.contentblocking.cfr-milestone.milestone-achieved",
0
);
ChromeUtils.defineModuleGetter(
this,
"AsyncShutdown",
@ -255,6 +278,49 @@ TrackingDBService.prototype = {
} catch (e) {
Cu.reportError(e);
}
// If milestone CFR messaging is not enabled we don't need to update the milestone pref or send the event.
// If we have checked in the last 24 hours, don't bother checking again. Exit early.
if (
!milestoneMessagingEnabled ||
(this.lastChecked && Date.now() - this.lastChecked < 24 * 60 * 60 * 1000)
) {
return;
}
this.lastChecked = Date.now();
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"
);
}
},
async clearAll() {

View File

@ -1110,6 +1110,7 @@ PopupNotifications.prototype = {
telemetryStatId++;
}
}
popupnotification.setAttribute("secondarybuttonhidden", "false");
} else {
popupnotification.setAttribute("secondarybuttonhidden", "true");
}