mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
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:
parent
950b8090b3
commit
da9bdae004
@ -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);
|
||||
|
@ -166,6 +166,7 @@ for (const type of [
|
||||
"PIN_CURRENT_TAB",
|
||||
"ENABLE_FIREFOX_MONITOR",
|
||||
"OPEN_PROTECTION_PANEL",
|
||||
"OPEN_PROTECTION_REPORT",
|
||||
]) {
|
||||
ASRouterActions[type] = type;
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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 system’s 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
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -151,6 +151,7 @@ export class FakeRemotePageManager {
|
||||
},
|
||||
gProtectionsHandler: {
|
||||
showProtectionsPopup: sinon.stub(),
|
||||
openProtections: sinon.stub(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -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 system’s 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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -1110,6 +1110,7 @@ PopupNotifications.prototype = {
|
||||
telemetryStatId++;
|
||||
}
|
||||
}
|
||||
popupnotification.setAttribute("secondarybuttonhidden", "false");
|
||||
} else {
|
||||
popupnotification.setAttribute("secondarybuttonhidden", "true");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user