Port 1570631 - Implement feature promotion doorhangers using CFR (#5308)

This commit is contained in:
Andrei Oprea 2019-09-13 12:47:40 +00:00 коммит произвёл GitHub
Родитель 7e4d3e082f
Коммит 806786f713
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 574 добавлений и 66 удалений

Просмотреть файл

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

Просмотреть файл

@ -15,6 +15,10 @@
}
},
"properties": {
"layout": {
"type": "string",
"description": "The layout style of the pop-over."
},
"category": {
"type": "string",
"description": "Attribute used for different groups of messages from the same provider"
@ -24,10 +28,18 @@
"description": "Attribute used for different groups of messages from the same provider",
"enum": ["message_and_animation", "icon_and_message", "addon_recommendation"]
},
"anchor_id": {
"type": "string",
"description": "A DOM element ID that the pop-over will be anchored."
},
"bucket_id": {
"type": "string",
"description": "A bucket identifier for the addon. This is used in order to anonymize telemetry for history-sensitive targeting."
},
"skip_address_bar_notifier": {
"type": "boolean",
"description": "Skip the 'Recommend' notifier and show directly."
},
"notification_text": {
"description": "The text in the small blue chicklet that appears in the URL bar. This can be a reference to a localized string in Firefox or just a plain string.",
"oneOf": [
@ -88,6 +100,11 @@
}
}
},
"learn_more": {
"type": "string",
"description": "Last part of the path in the SUMO URL to the support page with the information about the doorhanger.",
"examples": ["extensionpromotions", "extensionrecommendations"]
},
"heading_text": {
"description": "The larger heading text displayed in the pop-over. This can be a reference to a localized string in Firefox or just a plain string.",
"oneOf": [
@ -108,12 +125,20 @@
]
},
"icon": {
"description": "The icon displayed in the pop-over. Should be 64x64px and png/svg.",
"description": "The icon displayed in the pop-over. Should be 32x32px or 64x64px and png/svg.",
"allOf": [
{"$ref": "#/definitions/linkUrl"},
{"description": "Icon associated with the message"}
]
},
"icon_dark_theme": {
"type": "string",
"description": "Pop-over icon, dark theme variant. Should be 32x32px or 64x64px and png/svg."
},
"icon_class": {
"type": "string",
"description": "CSS class of the pop-over icon."
},
"addon": {
"description": "Addon information including AMO URL.",
"type": "object",
@ -336,5 +361,5 @@
}
},
"additionalProperties": false,
"required": ["category", "bucket_id", "notification_text", "heading_text", "text", "buttons"]
"required": ["layout", "category", "bucket_id", "notification_text", "heading_text", "text", "buttons"]
}

Просмотреть файл

@ -1844,6 +1844,10 @@ class _ASRouter {
csp: null,
});
break;
case ra.OPEN_PROTECTION_PANEL:
let { gProtectionsHandler } = target.browser.ownerGlobal;
gProtectionsHandler.showProtectionsPopup({});
break;
}
}

Просмотреть файл

@ -488,8 +488,13 @@ this.ASRouterTargeting = {
return (
(candidateMessageTrigger.params &&
trigger.param.host &&
candidateMessageTrigger.params.includes(trigger.param.host)) ||
(candidateMessageTrigger.params &&
trigger.param.type &&
candidateMessageTrigger.params.includes(trigger.param.type)) ||
(candidateMessageTrigger.patterns &&
trigger.param.url &&
new MatchPatternSet(candidateMessageTrigger.patterns).matches(
trigger.param.url
))

Просмотреть файл

@ -429,6 +429,107 @@ this.ASRouterTriggerListeners = new Map([
},
},
],
/**
* Attach listener to count location changes and notify the trigger handler
* on content blocked event
*/
[
"trackingProtection",
{
_initialized: false,
_triggerHandler: null,
_events: [],
_sessionPageLoad: 0,
onLocationChange: null,
async init(triggerHandler, params, patterns) {
params.forEach(p => this._events.push(p));
if (!this._initialized) {
Services.obs.addObserver(this, "SiteProtection:ContentBlockingEvent");
this.onLocationChange = this._onLocationChange.bind(this);
// Add listeners to all existing browser windows
for (let win of Services.wm.getEnumerator("navigator:browser")) {
if (isPrivateWindow(win)) {
continue;
}
await checkStartupFinished(win);
win.gBrowser.addTabsProgressListener(this);
}
this._initialized = true;
}
this._triggerHandler = triggerHandler;
},
uninit() {
if (this._initialized) {
Services.obs.removeObserver(
this,
"SiteProtection:ContentBlockingEvent"
);
for (let win of Services.wm.getEnumerator("navigator:browser")) {
if (isPrivateWindow(win)) {
continue;
}
win.gBrowser.removeTabsProgressListener(this);
}
this.onLocationChange = null;
this._initialized = false;
}
this._triggerHandler = null;
this._events = [];
this._sessionPageLoad = 0;
},
observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "SiteProtection:ContentBlockingEvent":
const { browser, host, event } = aSubject.wrappedJSObject;
if (this._events.includes(event)) {
this._triggerHandler(browser, {
id: "trackingProtection",
param: {
host,
type: event,
},
context: {
pageLoad: this._sessionPageLoad,
},
});
}
break;
}
},
_onLocationChange(
aBrowser,
aWebProgress,
aRequest,
aLocationURI,
aFlags
) {
// Some websites trigger redirect events after they finish loading even
// though the location remains the same. This results in onLocationChange
// events to be fired twice.
const isSameDocument = !!(
aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT
);
if (
["http", "https"].includes(aLocationURI.scheme) &&
aWebProgress.isTopLevel &&
!isSameDocument
) {
this._sessionPageLoad += 1;
}
},
},
],
]);
const EXPORTED_SYMBOLS = ["ASRouterTriggerListeners"];

Просмотреть файл

@ -546,6 +546,7 @@ const CFR_MESSAGES = [
string_id: "cfr-doorhanger-sync-logins-body",
},
icon: "chrome://browser/content/aboutlogins/icons/intro-illustration.svg",
icon_class: "cfr-doorhanger-large-icon",
buttons: {
secondary: [
{
@ -604,6 +605,144 @@ const CFR_MESSAGES = [
id: "newSavedLogin",
},
},
{
id: "SOCIAL_TRACKING_PROTECTION",
template: "cfr_doorhanger",
content: {
layout: "icon_and_message",
category: "cfrFeatures",
anchor_id: "tracking-protection-icon-box",
skip_address_bar_notifier: true,
bucket_id: "CFR_SOCIAL_TRACKING_PROTECTION",
heading_text: { string_id: "cfr-doorhanger-socialtracking-heading" },
notification_text: "",
info_icon: {
label: {
string_id: "cfr-doorhanger-extension-sumo-link",
},
sumo_path: "extensionrecommendations",
},
learn_more: "social-media-tracking-report",
text: { string_id: "cfr-doorhanger-socialtracking-description" },
icon: "chrome://browser/skin/notification-icons/block-social.svg",
icon_dark_theme:
"chrome://browser/skin/notification-icons/block-social-dark.svg",
buttons: {
primary: {
label: { string_id: "cfr-doorhanger-socialtracking-ok-button" },
action: { type: "OPEN_PROTECTION_PANEL" },
event: "PROTECTION",
},
secondary: [
{
label: { string_id: "cfr-doorhanger-socialtracking-close-button" },
event: "BLOCK",
},
],
},
},
targeting: "pageLoad >= 4",
frequency: {
lifetime: 2,
custom: [{ period: 2 * 86400 * 1000, cap: 1 }],
},
trigger: {
id: "trackingProtection",
params: [Ci.nsIWebProgressListener.STATE_BLOCKED_SOCIALTRACKING_CONTENT],
},
},
{
id: "FINGERPRINTERS_PROTECTION",
template: "cfr_doorhanger",
content: {
layout: "icon_and_message",
category: "cfrFeatures",
anchor_id: "tracking-protection-icon-box",
skip_address_bar_notifier: true,
bucket_id: "CFR_SOCIAL_TRACKING_PROTECTION",
heading_text: { string_id: "cfr-doorhanger-fingerprinters-heading" },
notification_text: "",
info_icon: {
label: {
string_id: "cfr-doorhanger-extension-sumo-link",
},
sumo_path: "extensionrecommendations",
},
learn_more: "fingerprinters-report",
text: { string_id: "cfr-doorhanger-fingerprinters-description" },
icon: "chrome://browser/skin/notification-icons/block-fingerprinter.svg",
icon_dark_theme:
"chrome://browser/skin/notification-icons/block-fingerprinter-dark.svg",
buttons: {
primary: {
label: { string_id: "cfr-doorhanger-socialtracking-ok-button" },
action: { type: "OPEN_PROTECTION_PANEL" },
event: "PROTECTION",
},
secondary: [
{
label: { string_id: "cfr-doorhanger-socialtracking-close-button" },
event: "BLOCK",
},
],
},
},
targeting: "pageLoad >= 4",
frequency: {
lifetime: 2,
custom: [{ period: 2 * 86400 * 1000, cap: 1 }],
},
trigger: {
id: "trackingProtection",
params: [Ci.nsIWebProgressListener.STATE_BLOCKED_FINGERPRINTING_CONTENT],
},
},
{
id: "CRYPTOMINERS_PROTECTION",
template: "cfr_doorhanger",
content: {
layout: "icon_and_message",
category: "cfrFeatures",
anchor_id: "tracking-protection-icon-box",
skip_address_bar_notifier: true,
bucket_id: "CFR_SOCIAL_TRACKING_PROTECTION",
heading_text: { string_id: "cfr-doorhanger-cryptominers-heading" },
notification_text: "",
info_icon: {
label: {
string_id: "cfr-doorhanger-extension-sumo-link",
},
sumo_path: "extensionrecommendations",
},
learn_more: "cryptominers-report",
text: { string_id: "cfr-doorhanger-cryptominers-description" },
icon: "chrome://browser/skin/notification-icons/block-cryptominer.svg",
icon_dark_theme:
"chrome://browser/skin/notification-icons/block-cryptominer-dark.svg",
buttons: {
primary: {
label: { string_id: "cfr-doorhanger-socialtracking-ok-button" },
action: { type: "OPEN_PROTECTION_PANEL" },
event: "PROTECTION",
},
secondary: [
{
label: { string_id: "cfr-doorhanger-socialtracking-close-button" },
event: "BLOCK",
},
],
},
},
targeting: "pageLoad >= 4",
frequency: {
lifetime: 2,
custom: [{ period: 2 * 86400 * 1000, cap: 1 }],
},
trigger: {
id: "trackingProtection",
params: [Ci.nsIWebProgressListener.STATE_BLOCKED_CRYPTOMINING_CONTENT],
},
},
];
const CFRMessageProvider = {

Просмотреть файл

@ -77,6 +77,31 @@ class PageAction {
// Saved timeout IDs for scheduled state changes, so they can be cancelled
this.stateTransitionTimeoutIDs = [];
XPCOMUtils.defineLazyGetter(this, "isDarkTheme", () => {
try {
return this.window.document.documentElement.hasAttribute(
"lwt-toolbar-field-brighttext"
);
} catch (e) {
return false;
}
});
}
addImpression(recommendation) {
this._dispatchImpression(recommendation);
// Only send an impression ping upon the first expansion.
// Note that when the user clicks on the "show" button on the asrouter admin
// page (both `bucket_id` and `id` will be set as null), we don't want to send
// the impression ping in that case.
if (!!recommendation.id && !!recommendation.content.bucket_id) {
this._sendTelemetry({
message_id: recommendation.id,
bucket_id: recommendation.content.bucket_id,
event: "IMPRESSION",
});
}
}
async showAddressBarNotifier(recommendation, shouldExpand = false) {
@ -114,18 +139,7 @@ class PageAction {
// After one second, expand
this._expand(DELAY_BEFORE_EXPAND_MS);
this._dispatchImpression(recommendation);
// Only send an impression ping upon the first expansion.
// Note that when the user clicks on the "show" button on the asrouter admin
// page (both `bucket_id` and `id` will be set as null), we don't want to send
// the impression ping in that case.
if (!!recommendation.id && !!recommendation.content.bucket_id) {
this._sendTelemetry({
message_id: recommendation.id,
bucket_id: recommendation.content.bucket_id,
event: "IMPRESSION",
});
}
this.addImpression(recommendation);
}
}
@ -473,6 +487,9 @@ class PageAction {
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);
switch (content.layout) {
case "icon_and_message":
@ -491,10 +508,23 @@ class PageAction {
});
RecommendationMap.delete(browser);
};
let getIcon = () => {
if (content.icon_dark_theme && this.isDarkTheme) {
return content.icon_dark_theme;
}
return content.icon;
};
let learnMoreURL = content.learn_more
? SUMO_BASE_URL + content.learn_more
: null;
panelTitle = await this.getStrings(content.heading_text);
options = {
popupIconURL: content.icon,
popupIconClass: "cfr-doorhanger-large-icon",
popupIconURL: getIcon(),
popupIconClass: content.icon_class,
learnMoreURL,
};
break;
case "message_and_animation":
@ -579,52 +609,41 @@ class PageAction {
callback: primaryActionCallback,
};
// For each secondary action, get the strings and attributes
const secondaryBtnStrings = [];
for (let button of secondary) {
let _renderSecondaryButtonAction = async (event, button) => {
let label = await this.getStrings(button.label);
secondaryBtnStrings.push({ label, attributes: label.attributes });
}
const secondaryActions = [
{
label: secondaryBtnStrings[0].label,
accessKey: secondaryBtnStrings[0].attributes.accesskey,
let { attributes } = label;
return {
label,
accessKey: attributes.accesskey,
callback: () => {
this.dispatchUserAction(secondary[0].action);
if (button.action) {
this.dispatchUserAction(button.action);
} else {
this._blockMessage(id);
this.hideAddressBarNotifier();
RecommendationMap.delete(browser);
}
this._sendTelemetry({
message_id: id,
bucket_id: content.bucket_id,
event: "DISMISS",
event,
});
},
},
{
label: secondaryBtnStrings[1].label,
accessKey: secondaryBtnStrings[1].attributes.accesskey,
callback: () => {
this._blockMessage(id);
this.hideAddressBarNotifier();
this._sendTelemetry({
message_id: id,
bucket_id: content.bucket_id,
event: "BLOCK",
});
RecommendationMap.delete(browser);
},
},
{
label: secondaryBtnStrings[2].label,
accessKey: secondaryBtnStrings[2].attributes.accesskey,
callback: () => {
this.dispatchUserAction(secondary[2].action);
this._sendTelemetry({
message_id: id,
bucket_id: content.bucket_id,
event: "MANAGE",
});
},
},
];
};
};
// For each secondary action, define default telemetry event
const defaultSecondaryEvent = ["DISMISS", "BLOCK", "MANAGE"];
const secondaryActions = await Promise.all(
secondary.map((button, i) => {
return _renderSecondaryButtonAction(
button.event || defaultSecondaryEvent[i],
button
);
})
);
// Actually show the notification
this.currentNotification = this.window.PopupNotifications.show(
@ -655,15 +674,23 @@ class PageAction {
return;
}
const message = RecommendationMap.get(browser);
const { id, content } = message;
// The recommendation should remain either collapsed or expanded while the
// doorhanger is showing
this._clearScheduledStateChanges(browser, message);
await this.showPopup();
}
async showPopup() {
const browser = this.window.gBrowser.selectedBrowser;
const message = RecommendationMap.get(browser);
const { id, 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.container;
browser.cfrpopupnotificationanchor =
this.window.document.getElementById(content.anchor_id) || this.container;
this._sendTelemetry({
message_id: id,
@ -699,10 +726,11 @@ const CFRPageActions = {
if (RecommendationMap.has(browser)) {
const recommendation = RecommendationMap.get(browser);
if (
isHostMatch(browser, recommendation.host) ||
// If there is no host associated we assume we're back on a tab
// that had a CFR message so we should show it again
!recommendation.host
!recommendation.content.skip_address_bar_notifier &&
(isHostMatch(browser, recommendation.host) ||
// If there is no host associated we assume we're back on a tab
// that had a CFR message so we should show it again
!recommendation.host)
) {
// The browser has a recommendation specified with this host, so show
// the page action
@ -762,7 +790,13 @@ const CFRPageActions = {
if (!PageActionMap.has(win)) {
PageActionMap.set(win, new PageAction(win, dispatchToASRouter));
}
await PageActionMap.get(win).showAddressBarNotifier(recommendation, true);
if (content.skip_address_bar_notifier) {
await PageActionMap.get(win).showPopup();
PageActionMap.get(win).addImpression(recommendation);
} else {
await PageActionMap.get(win).showAddressBarNotifier(recommendation, true);
}
return true;
},
@ -795,7 +829,13 @@ const CFRPageActions = {
if (!PageActionMap.has(win)) {
PageActionMap.set(win, new PageAction(win, dispatchToASRouter));
}
await PageActionMap.get(win).showAddressBarNotifier(recommendation, true);
if (content.skip_address_bar_notifier) {
await PageActionMap.get(win).showPopup();
PageActionMap.get(win).addImpression(recommendation);
} else {
await PageActionMap.get(win).showAddressBarNotifier(recommendation, true);
}
return true;
},

Просмотреть файл

@ -156,3 +156,16 @@ cfr-doorhanger-firefox-send-header = Share this PDF securely
cfr-doorhanger-firefox-send-body = Keep your sensitive documents safe from prying eyes with end-to-end encryption and a link that disappears when youre done.
cfr-doorhanger-firefox-send-ok-button = Try { -send-brand-name }
.accesskey = T
## Social Tracking Protection
cfr-doorhanger-socialtracking-ok-button = See Protections
.accesskey = P
cfr-doorhanger-socialtracking-close-button = Close
.accesskey = C
cfr-doorhanger-socialtracking-heading = { -brand-short-name } stopped a social network from tracking you here
cfr-doorhanger-socialtracking-description = Your privacy matters. { -brand-short-name } now blocks common social media trackers, limiting how much data they can collect about what you do online.
cfr-doorhanger-fingerprinters-heading = { -brand-short-name } blocked a fingerprinter on this page
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.

Просмотреть файл

@ -13,16 +13,22 @@ const createDummyRecommendation = ({
category,
heading_text,
layout,
skip_address_bar_notifier,
}) => ({
content: {
layout: layout || "addon_recommendation",
category,
anchor_id: "page-action-buttons",
skip_address_bar_notifier,
notification_text: "Mochitest",
heading_text: heading_text || "Mochitest",
info_icon: {
label: { attributes: { tooltiptext: "Why am I seeing this" } },
sumo_path: "extensionrecommendations",
},
icon: "foo",
icon_dark_theme: "bar",
learn_more: "extensionrecommendations",
addon: {
id: "addon-id",
title: "Addon name",
@ -107,6 +113,19 @@ function checkCFRAddonsElements(notification) {
);
}
function checkCFRSocialTrackingProtection(notification) {
Assert.ok(notification.hidden === false, "Panel should be visible");
Assert.ok(
notification.getAttribute("data-notification-category") ===
"icon_and_message",
"Panel have corret data attribute"
);
Assert.ok(
notification.querySelector("#cfr-notification-footer-learn-more-link"),
"Panel should have learn more link"
);
}
function clearNotifications() {
for (let notification of PopupNotifications._currentNotifications) {
notification.remove();
@ -128,6 +147,8 @@ function trigger_cfr_panel(
heading_text,
category = "cfrAddons",
layout,
skip_address_bar_notifier = false,
use_single_secondary_button = false,
} = {}
) {
// a fake action type will result in the action being ignored
@ -136,10 +157,16 @@ function trigger_cfr_panel(
category,
heading_text,
layout,
skip_address_bar_notifier,
});
if (category !== "cfrAddons") {
delete recommendation.content.addon;
}
if (use_single_secondary_button) {
recommendation.content.buttons.secondary = [
recommendation.content.buttons.secondary[0],
];
}
clearNotifications();
@ -400,6 +427,49 @@ add_task(async function test_cfr_pin_tab_notification_show() {
);
});
add_task(
async function test_cfr_social_tracking_protection_notification_show() {
// 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_PANEL" },
category: "cfrFeatures",
layout: "icon_and_message",
skip_address_bar_notifier: true,
use_single_secondary_button: true,
});
Assert.ok(
response,
"Should return true if addRecommendation checks were successful"
);
await showPanel;
const notification = document.getElementById(
"contextual-feature-recommendation-notification"
);
checkCFRSocialTrackingProtection(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 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;

Просмотреть файл

@ -141,3 +141,97 @@ add_task(async function check_newSavedLogin_listener() {
}
);
});
add_task(async function check_trackingProtection_listener() {
const TEST_URL =
"https://example.com/browser/browser/components/newtab/test/browser/red_page.html";
const contentBlockingEvent = 1234;
let observerEvent = 0;
let pageLoadSum = 0;
const triggerHandler = (target, trigger) => {
const {
id,
param: { host },
context: { pageLoad },
} = trigger;
is(id, "trackingProtection", "should match event name");
is(host, TEST_URL, "should match test URL");
observerEvent += 1;
pageLoadSum += pageLoad;
};
const trackingProtectionListener = ASRouterTriggerListeners.get(
"trackingProtection"
);
// Previously initialized by the Router
trackingProtectionListener.uninit();
// Initialise listener
await trackingProtectionListener.init(triggerHandler, [contentBlockingEvent]);
await BrowserTestUtils.withNewTab(
TEST_URL,
async function triggerTrackingProtection(browser) {
Services.obs.notifyObservers(
{
wrappedJSObject: {
browser,
host: TEST_URL,
event: contentBlockingEvent + 1,
},
},
"SiteProtection:ContentBlockingEvent"
);
}
);
is(observerEvent, 0, "shouldn't receive unrelated observer notification");
is(pageLoadSum, 0, "shouldn't receive unrelated observer notification");
await BrowserTestUtils.withNewTab(
TEST_URL,
async function triggerTrackingProtection(browser) {
Services.obs.notifyObservers(
{
wrappedJSObject: {
browser,
host: TEST_URL,
event: contentBlockingEvent,
},
},
"SiteProtection:ContentBlockingEvent"
);
await BrowserTestUtils.waitForCondition(
() => observerEvent !== 0,
"Wait for the observer notification to run"
);
is(observerEvent, 1, "should receive observer notification");
is(pageLoadSum, 2, "should receive observer notification");
}
);
// Uninitialise listener
trackingProtectionListener.uninit();
await BrowserTestUtils.withNewTab(
TEST_URL,
async function triggerTrackingProtectionAfterUninit(browser) {
Services.obs.notifyObservers(
{
wrappedJSObject: {
browser,
host: TEST_URL,
event: contentBlockingEvent,
},
},
"SiteProtection:ContentBlockingEvent"
);
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");
}
);
});

Просмотреть файл

@ -1971,6 +1971,18 @@ describe("ASRouter", () => {
});
});
describe("#onMessage: OPEN_PROTECTION_PANEL", () => {
it("should open protection panel", async () => {
const msg = fakeExecuteUserAction({ type: "OPEN_PROTECTION_PANEL" });
let { gProtectionsHandler } = msg.target.browser.ownerGlobal;
await Router.onMessage(msg);
assert.calledOnce(gProtectionsHandler.showProtectionsPopup);
assert.calledWithExactly(gProtectionsHandler.showProtectionsPopup, {});
});
});
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 5 messages", () => {
assert.lengthOf(messages, 5);
it("should have a total of 8 messages", () => {
assert.lengthOf(messages, 8);
});
it("should have one message each for the three regular addons", () => {
for (const id of REGULAR_IDS) {

Просмотреть файл

@ -120,7 +120,6 @@ export const FAKE_RECOMMENDATION = {
},
{
label: { string_id: "secondary_button_id_2" },
action: { id: "secondary_action" },
},
{
label: { string_id: "secondary_button_id_3" },
@ -150,6 +149,9 @@ export class FakeRemotePageManager {
ConfirmationHint: {
show: sinon.stub(),
},
gProtectionsHandler: {
showProtectionsPopup: sinon.stub(),
},
},
};
this.portID = "6000:2";

Просмотреть файл

@ -2,6 +2,7 @@ import { CFRMessageProvider } from "lib/CFRMessageProvider.jsm";
import schema from "content-src/asrouter/templates/CFR/templates/ExtensionDoorhanger.schema.json";
const DEFAULT_CONTENT = {
layout: "addon_recommendation",
category: "dummyCategory",
bucket_id: "some_bucket_id",
notification_text: "Recommendation",
@ -40,6 +41,7 @@ const DEFAULT_CONTENT = {
};
const L10N_CONTENT = {
layout: "addon_recommendation",
category: "dummyL10NCategory",
bucket_id: "some_bucket_id",
notification_text: { string_id: "notification_text_id" },