diff --git a/browser/components/newtab/lib/ASRouter.jsm b/browser/components/newtab/lib/ASRouter.jsm index 74aea29903cb..3b4e57364902 100644 --- a/browser/components/newtab/lib/ASRouter.jsm +++ b/browser/components/newtab/lib/ASRouter.jsm @@ -1346,7 +1346,7 @@ class _ASRouter { } else { CFRPageActions.addRecommendation( target, - trigger.param.host, + trigger.param && trigger.param.host, message, this.dispatch ); diff --git a/browser/components/newtab/lib/CFRMessageProvider.jsm b/browser/components/newtab/lib/CFRMessageProvider.jsm index ddee21a2f60a..450ac34fa99f 100644 --- a/browser/components/newtab/lib/CFRMessageProvider.jsm +++ b/browser/components/newtab/lib/CFRMessageProvider.jsm @@ -126,6 +126,7 @@ const CFR_MESSAGES = [ id: "FACEBOOK_CONTAINER_3", template: "cfr_doorhanger", content: { + layout: "addon_recommendation", category: "cfrAddons", bucket_id: "CFR_M1", notification_text: { string_id: "cfr-doorhanger-extension-notification" }, @@ -194,6 +195,7 @@ const CFR_MESSAGES = [ id: "GOOGLE_TRANSLATE_3", template: "cfr_doorhanger", content: { + layout: "addon_recommendation", category: "cfrAddons", bucket_id: "CFR_M1", notification_text: { string_id: "cfr-doorhanger-extension-notification" }, @@ -263,6 +265,7 @@ const CFR_MESSAGES = [ id: "YOUTUBE_ENHANCE_3", template: "cfr_doorhanger", content: { + layout: "addon_recommendation", category: "cfrAddons", bucket_id: "CFR_M1", notification_text: { string_id: "cfr-doorhanger-extension-notification" }, @@ -333,6 +336,7 @@ const CFR_MESSAGES = [ template: "cfr_doorhanger", exclude: true, content: { + layout: "addon_recommendation", category: "cfrAddons", bucket_id: "CFR_M1", notification_text: { string_id: "cfr-doorhanger-extension-notification" }, @@ -406,6 +410,7 @@ const CFR_MESSAGES = [ template: "cfr_doorhanger", exclude: true, content: { + layout: "addon_recommendation", category: "cfrAddons", bucket_id: "CFR_M1", notification_text: { string_id: "cfr-doorhanger-extension-notification" }, @@ -475,6 +480,7 @@ const CFR_MESSAGES = [ id: "PIN_TAB", template: "cfr_doorhanger", content: { + layout: "message_and_animation", category: "cfrFeatures", bucket_id: "CFR_PIN_TAB", notification_text: { string_id: "cfr-doorhanger-extension-notification" }, @@ -526,6 +532,78 @@ const CFR_MESSAGES = [ frequency: { lifetime: 3 }, trigger: { id: "frequentVisits", params: PINNED_TABS_TARGET_SITES }, }, + { + id: "SAVE_LOGIN", + frequency: { + lifetime: 3, + }, + targeting: "usesFirefoxSync == false", + template: "cfr_doorhanger", + last_modified: 1565907636313, + content: { + layout: "icon_and_message", + text: { + string_id: "cfr-doorhanger-sync-logins-body", + }, + icon: "chrome://browser/content/aboutlogins/icons/intro-illustration.svg", + buttons: { + secondary: [ + { + label: { + string_id: "cfr-doorhanger-extension-cancel-button", + }, + action: { + type: "CANCEL", + }, + }, + { + label: { + string_id: "cfr-doorhanger-extension-never-show-recommendation", + }, + }, + { + label: { + string_id: "cfr-doorhanger-extension-manage-settings-button", + }, + action: { + type: "OPEN_PREFERENCES_PAGE", + data: { + category: "general-cfrfeatures", + }, + }, + }, + ], + primary: { + label: { + string_id: "cfr-doorhanger-sync-logins-ok-button", + }, + action: { + type: "OPEN_PREFERENCES_PAGE", + data: { + category: "sync", + }, + }, + }, + }, + bucket_id: "CFR_SAVE_LOGIN", + heading_text: { + string_id: "cfr-doorhanger-sync-logins-header", + }, + info_icon: { + label: { + string_id: "cfr-doorhanger-extension-sumo-link", + }, + sumo_path: "extensionrecommendations", + }, + notification_text: { + string_id: "cfr-doorhanger-extension-notification", + }, + category: "cfrFeatures", + }, + trigger: { + id: "newSavedLogin", + }, + }, ]; const CFRMessageProvider = { diff --git a/browser/components/newtab/lib/CFRPageActions.jsm b/browser/components/newtab/lib/CFRPageActions.jsm index 4c5e6d1f5158..56e180d1ebf1 100644 --- a/browser/components/newtab/lib/CFRPageActions.jsm +++ b/browser/components/newtab/lib/CFRPageActions.jsm @@ -428,6 +428,7 @@ class PageAction { } } + // eslint-disable-next-line max-statements async _renderPopup(message, browser) { const { id, content } = message; @@ -451,12 +452,6 @@ class PageAction { let options = {}; let panelTitle; - // Use the message category 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", message.content.category); - headerLabel.value = await this.getStrings(content.heading_text); headerLink.setAttribute( "href", @@ -473,76 +468,108 @@ class PageAction { bucket_id: content.bucket_id, event: "RATIONALE", }); + // 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); - footerText.textContent = await this.getStrings(content.text); - - if (content.addon) { - await this._setAddonAuthorAndRating(this.window.document, content); - panelTitle = await this.getStrings(content.addon.title); - options = { popupIconURL: content.addon.icon }; - - footerLink.value = await this.getStrings({ - string_id: "cfr-doorhanger-extension-learn-more-link", - }); - footerLink.setAttribute("href", content.addon.amo_url); - footerLink.onclick = () => - this._sendTelemetry({ - message_id: id, - bucket_id: content.bucket_id, - event: "LEARN_MORE", - }); - - primaryActionCallback = async () => { - // eslint-disable-next-line no-use-before-define - primary.action.data.url = await CFRPageActions._fetchLatestAddonVersion( - content.addon.id + switch (content.layout) { + case "icon_and_message": + const author = this.window.document.getElementById( + "cfr-notification-author" ); - this._blockMessage(id); - this.dispatchUserAction(primary.action); - this.hideAddressBarNotifier(); - this._sendTelemetry({ - message_id: id, - bucket_id: content.bucket_id, - event: "INSTALL", - }); - RecommendationMap.delete(browser); - }; - } else { - const stepsContainerId = "cfr-notification-feature-steps"; - let stepsContainer = this.window.document.getElementById( - stepsContainerId - ); - primaryActionCallback = () => { - this._blockMessage(id); - this.dispatchUserAction(primary.action); - this.hideAddressBarNotifier(); - this._sendTelemetry({ - message_id: id, - bucket_id: content.bucket_id, - event: "PIN", - }); - RecommendationMap.delete(browser); - }; - panelTitle = await this.getStrings(content.heading_text); + author.textContent = await this.getStrings(content.text); + primaryActionCallback = () => { + this._blockMessage(id); + this.dispatchUserAction(primary.action); + this.hideAddressBarNotifier(); + this._sendTelemetry({ + message_id: id, + bucket_id: content.bucket_id, + event: "ENABLE", + }); + RecommendationMap.delete(browser); + }; + panelTitle = await this.getStrings(content.heading_text); + options = { + popupIconURL: content.icon, + popupIconClass: "cfr-doorhanger-large-icon", + }; + break; + case "message_and_animation": + footerText.textContent = await this.getStrings(content.text); + const stepsContainerId = "cfr-notification-feature-steps"; + let stepsContainer = this.window.document.getElementById( + stepsContainerId + ); + primaryActionCallback = () => { + this._blockMessage(id); + this.dispatchUserAction(primary.action); + this.hideAddressBarNotifier(); + this._sendTelemetry({ + message_id: id, + bucket_id: content.bucket_id, + event: "PIN", + }); + RecommendationMap.delete(browser); + }; + panelTitle = await this.getStrings(content.heading_text); - if (stepsContainer) { - // If it exists we need to empty it - stepsContainer.remove(); - stepsContainer = stepsContainer.cloneNode(false); - } else { - stepsContainer = this.window.document.createXULElement("vbox"); - stepsContainer.setAttribute("id", stepsContainerId); - } - footerText.parentNode.appendChild(stepsContainer); - for (let step of content.descriptionDetails.steps) { - // This li is a generic xul element with custom styling - const li = this.window.document.createXULElement("li"); - this._l10n.setAttributes(li, step.string_id); - stepsContainer.appendChild(li); - } - await this._l10n.translateElements([...stepsContainer.children]); + if (content.descriptionDetails) { + if (stepsContainer) { + // If it exists we need to empty it + stepsContainer.remove(); + stepsContainer = stepsContainer.cloneNode(false); + } else { + stepsContainer = this.window.document.createXULElement("vbox"); + stepsContainer.setAttribute("id", stepsContainerId); + } + footerText.parentNode.appendChild(stepsContainer); + for (let step of content.descriptionDetails.steps) { + // This li is a generic xul element with custom styling + const li = this.window.document.createXULElement("li"); + this._l10n.setAttributes(li, step.string_id); + stepsContainer.appendChild(li); + } + await this._l10n.translateElements([...stepsContainer.children]); + } - await this._renderPinTabAnimation(); + await this._renderPinTabAnimation(); + break; + default: + panelTitle = await this.getStrings(content.addon.title); + await this._setAddonAuthorAndRating(this.window.document, content); + // Main body content of the dropdown + footerText.textContent = await this.getStrings(content.text); + options = { popupIconURL: content.addon.icon }; + + footerLink.value = await this.getStrings({ + string_id: "cfr-doorhanger-extension-learn-more-link", + }); + footerLink.setAttribute("href", content.addon.amo_url); + footerLink.onclick = () => + this._sendTelemetry({ + message_id: id, + bucket_id: content.bucket_id, + event: "LEARN_MORE", + }); + + primaryActionCallback = async () => { + // eslint-disable-next-line no-use-before-define + primary.action.data.url = await CFRPageActions._fetchLatestAddonVersion( + content.addon.id + ); + this._blockMessage(id); + this.dispatchUserAction(primary.action); + this.hideAddressBarNotifier(); + this._sendTelemetry({ + message_id: id, + bucket_id: content.bucket_id, + event: "INSTALL", + }); + RecommendationMap.delete(browser); + }; } const primaryBtnStrings = await this.getStrings(primary.label); @@ -749,7 +776,8 @@ const CFRPageActions = { } if ( browser !== win.gBrowser.selectedBrowser || - !isHostMatch(browser, host) + // We can have recommendations without URL restrictions + (host && !isHostMatch(browser, host)) ) { return false; } diff --git a/browser/components/newtab/test/browser/browser_asrouter_cfr.js b/browser/components/newtab/test/browser/browser_asrouter_cfr.js index 840916a7f0f7..76d4dca593d9 100644 --- a/browser/components/newtab/test/browser/browser_asrouter_cfr.js +++ b/browser/components/newtab/test/browser/browser_asrouter_cfr.js @@ -8,8 +8,14 @@ const { ASRouter } = ChromeUtils.import( "resource://activity-stream/lib/ASRouter.jsm" ); -const createDummyRecommendation = ({ action, category, heading_text }) => ({ +const createDummyRecommendation = ({ + action, + category, + heading_text, + layout, +}) => ({ content: { + layout: layout || "addon_recommendation", category, notification_text: "Mochitest", heading_text: heading_text || "Mochitest", @@ -63,9 +69,10 @@ const createDummyRecommendation = ({ action, category, heading_text }) => ({ function checkCFRFeaturesElements(notification) { Assert.ok(notification.hidden === false, "Panel should be visible"); - Assert.ok( - notification.getAttribute("data-notification-category") === "cfrFeatures", - "Panel have corret data attribute" + Assert.equal( + notification.getAttribute("data-notification-category"), + "message_and_animation", + "Panel have correct data attribute" ); Assert.ok( notification.querySelector( @@ -81,9 +88,10 @@ function checkCFRFeaturesElements(notification) { function checkCFRAddonsElements(notification) { Assert.ok(notification.hidden === false, "Panel should be visible"); - Assert.ok( - notification.getAttribute("data-notification-category") === "cfrAddons", - "Panel have corret data attribute" + Assert.equal( + notification.getAttribute("data-notification-category"), + "addon_recommendation", + "Panel have correct data attribute" ); Assert.ok( notification.querySelector("#cfr-notification-footer-text-and-addon-info"), @@ -115,13 +123,19 @@ function clearNotifications() { function trigger_cfr_panel( browser, trigger, - { action = { type: "FOO" }, heading_text, category = "cfrAddons" } = {} + { + action = { type: "FOO" }, + heading_text, + category = "cfrAddons", + layout, + } = {} ) { // a fake action type will result in the action being ignored const recommendation = createDummyRecommendation({ action, category, heading_text, + layout, }); if (category !== "cfrAddons") { delete recommendation.content.addon; @@ -340,6 +354,7 @@ add_task(async function test_cfr_pin_tab_notification_show() { const response = await trigger_cfr_panel(browser, "example.com", { action: { type: "PIN_CURRENT_TAB" }, category: "cfrFeatures", + layout: "message_and_animation", }); Assert.ok( response, @@ -395,6 +410,7 @@ add_task(async function test_cfr_features_and_addon_show() { let response = await trigger_cfr_panel(browser, "example.com", { action: { type: "PIN_CURRENT_TAB" }, category: "cfrFeatures", + layout: "message_and_animation", }); Assert.ok( response, @@ -522,7 +538,7 @@ add_task(async function test_cfr_addon_and_features_show() { // Trigger Addon CFR response = await trigger_cfr_panel(browser, "example.com", { action: { type: "PIN_CURRENT_TAB" }, - category: "cfrFeatures", + category: "cfrAddons", }); Assert.ok( response, @@ -542,7 +558,7 @@ add_task(async function test_cfr_addon_and_features_show() { .hidden === false, "Panel should be visible" ); - checkCFRFeaturesElements( + checkCFRAddonsElements( document.getElementById("contextual-feature-recommendation-notification") ); diff --git a/browser/themes/shared/browser.inc.css b/browser/themes/shared/browser.inc.css index a4a88815f7e8..0a04b2a5f776 100644 --- a/browser/themes/shared/browser.inc.css +++ b/browser/themes/shared/browser.inc.css @@ -238,6 +238,11 @@ margin-inline-end: 4px; } +#contextual-feature-recommendation-notification .cfr-doorhanger-large-icon { + width: 64px; + height: 64px; +} + #contextual-feature-recommendation-notification .popup-notification-body-container { padding-bottom: 0; } @@ -257,12 +262,35 @@ font-size: 13px; } -#contextual-feature-recommendation-notification[data-notification-category="cfrFeatures"] .popup-notification-body-container, -#contextual-feature-recommendation-notification[data-notification-category="cfrFeatures"] #cfr-notification-footer-addon-info, -#contextual-feature-recommendation-notification[data-notification-category="cfrAddons"] #cfr-notification-feature-steps { +#contextual-feature-recommendation-notification[data-notification-category="message_and_animation"] .popup-notification-body-container, +#contextual-feature-recommendation-notification[data-notification-category="message_and_animation"] #cfr-notification-footer-addon-info, +#contextual-feature-recommendation-notification[data-notification-category="addon_recommendation"] #cfr-notification-feature-steps, +#contextual-feature-recommendation-notification[data-notification-category="icon_and_message"] .popup-notification-footer-container { display: none; } +/* + * `icon_and_message` CFR doorhanger with: icon, title and subtitle. + * No panel header is shown + */ +#contextual-feature-recommendation-notification[data-notification-category="icon_and_message"] #cfr-notification-header { + display: none; +} + +#contextual-feature-recommendation-notification[data-notification-category="icon_and_message"] .popup-notification-description { + font-size: 16px; + font-weight: 600; + margin-bottom: 4px; +} + +#contextual-feature-recommendation-notification[data-notification-category="icon_and_message"] popupnotificationcontent { + display: block; /* This forces the subtitle content to wrap */ +} + +#contextual-feature-recommendation-notification[data-notification-category="icon_and_message"] .popup-notification-body-container { + padding-bottom: 20px; +} + #cfr-notification-feature-steps { display: flex; flex-direction: column; @@ -284,7 +312,7 @@ inset-inline-start: 0; } -#contextual-feature-recommendation-notification[data-notification-category="cfrFeatures"] #cfr-notification-footer-text { +#contextual-feature-recommendation-notification[data-notification-category="message_and_animation"] #cfr-notification-footer-text { font-size: 14px; font-weight: 600; } @@ -370,7 +398,7 @@ } } -#contextual-feature-recommendation-notification[data-notification-category="cfrAddons"] #cfr-notification-footer-pintab-animation-container { +#contextual-feature-recommendation-notification[data-notification-category="addon_recommendation"] #cfr-notification-footer-pintab-animation-container { display: none; }