Bug 1570631 - Part 1.1: popup enhancement; r=k88hudson
authorLiang-Heng Chen <xeonchen@gmail.com>
Wed, 11 Sep 2019 09:16:49 +0000
changeset 492641 5e87be03a7a1b56e42b032b5e1e32f4e274c86f3
parent 492640 4ad27f27eb865d75bc5d50fb81cc647e092a0d7a
child 492642 6b93718550afe79a9114d5a1adc688024e572e8b
push id36563
push usercbrindusan@mozilla.com
push dateWed, 11 Sep 2019 21:53:06 +0000
treeherdermozilla-central@26711f10f5f4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersk88hudson
bugs1570631
milestone71.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1570631 - Part 1.1: popup enhancement; r=k88hudson # support `recommendation.content.anchor_id` # support `recommendation.content.skip_address_bar_notifier` # support `recommendation.content.learn_more` # support `recommendation.content.icon_dark_theme` # support `recommendation.content.icon_class` Differential Revision: https://phabricator.services.mozilla.com/D44177
browser/components/newtab/content-src/asrouter/templates/CFR/templates/ExtensionDoorhanger.schema.json
browser/components/newtab/lib/CFRMessageProvider.jsm
browser/components/newtab/lib/CFRPageActions.jsm
browser/components/newtab/test/unit/asrouter/templates/ExtensionDoorhanger.test.jsx
--- a/browser/components/newtab/content-src/asrouter/templates/CFR/templates/ExtensionDoorhanger.schema.json
+++ b/browser/components/newtab/content-src/asrouter/templates/CFR/templates/ExtensionDoorhanger.schema.json
@@ -10,29 +10,41 @@
     },
     "linkUrl": {
       "description": "Target for links or buttons",
       "type": "string",
       "format": "uri"
     }
   },
   "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"
     },
     "layout": {
       "type": "string",
       "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": [
         {
           "type": "string",
           "description": "Message shown in the location bar notification."
         },
         {
@@ -83,16 +95,21 @@
         },
         "sumo_path": {
           "type": "string",
           "description": "Last part of the path in the URL to the support page with the information about the doorhanger.",
           "examples": ["extensionpromotions", "extensionrecommendations"]
         }
       }
     },
+    "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": [
         {
           "type": "string",
           "description": "The message displayed in the title of the extension doorhanger"
         },
         {
@@ -103,22 +120,30 @@
             }
           },
           "required": ["string_id"],
           "description": "Id of localized string for extension doorhanger title"
         }
       ]
     },
     "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",
       "properties": {
         "id": {
           "allOf": [
             {"$ref": "#/definitions/plainText"},
             {"description": "Unique addon ID"}
@@ -331,10 +356,10 @@
               }
             }
           }
         }
       }
     }
   },
   "additionalProperties": false,
-  "required": ["category", "bucket_id", "notification_text", "heading_text", "text", "buttons"]
+  "required": ["layout", "category", "bucket_id", "notification_text", "heading_text", "text", "buttons"]
 }
--- a/browser/components/newtab/lib/CFRMessageProvider.jsm
+++ b/browser/components/newtab/lib/CFRMessageProvider.jsm
@@ -541,16 +541,17 @@ const CFR_MESSAGES = [
     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",
+      icon_class: "cfr-doorhanger-large-icon",
       buttons: {
         secondary: [
           {
             label: {
               string_id: "cfr-doorhanger-extension-cancel-button",
             },
             action: {
               type: "CANCEL",
--- a/browser/components/newtab/lib/CFRPageActions.jsm
+++ b/browser/components/newtab/lib/CFRPageActions.jsm
@@ -72,16 +72,26 @@ class PageAction {
       "browser/newtab/asrouter.ftl",
       "browser/branding/brandings.ftl",
       "browser/branding/sync-brand.ftl",
       "branding/brand.ftl",
     ]);
 
     // 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.
@@ -472,16 +482,19 @@ 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);
+    this.window.document
+      .getElementById("contextual-feature-recommendation-notification")
+      .setAttribute("data-notification-bucket", content.bucket_id);
 
     switch (content.layout) {
       case "icon_and_message":
         const author = this.window.document.getElementById(
           "cfr-notification-author"
         );
         author.textContent = await this.getStrings(content.text);
         primaryActionCallback = () => {
@@ -490,20 +503,33 @@ class PageAction {
           this.hideAddressBarNotifier();
           this._sendTelemetry({
             message_id: id,
             bucket_id: content.bucket_id,
             event: "ENABLE",
           });
           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":
         footerText.textContent = await this.getStrings(content.text);
         const stepsContainerId = "cfr-notification-feature-steps";
         let stepsContainer = this.window.document.getElementById(
           stepsContainerId
         );
@@ -658,17 +684,18 @@ class PageAction {
 
   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,
       bucket_id: content.bucket_id,
       event: "CLICK_DOORHANGER",
     });
     await this._renderPopup(message, browser);
   }
@@ -694,20 +721,21 @@ const CFRPageActions = {
     const win = browser.ownerGlobal;
     const pageAction = PageActionMap.get(win);
     if (!pageAction || browser !== win.gBrowser.selectedBrowser) {
       return;
     }
     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
         pageAction.showAddressBarNotifier(recommendation);
       } else if (recommendation.retain) {
         // Keep the recommendation first time the user navigates away just in
         // case they will go back to the previous page
         pageAction.hideAddressBarNotifier();
@@ -757,17 +785,23 @@ const CFRPageActions = {
   async forceRecommendation(browser, recommendation, dispatchToASRouter) {
     // If we are forcing via the Admin page, the browser comes in a different format
     const win = browser.browser.ownerGlobal;
     const { id, content } = recommendation;
     RecommendationMap.set(browser.browser, { id, retain: true, content });
     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;
   },
 
   /**
    * Add a recommendation specific to the given browser and host.
    * @param browser                 The browser for the recommendation
    * @param host                    The host for the recommendation
    * @param recommendation  The recommendation to show
@@ -790,17 +824,23 @@ const CFRPageActions = {
       // Don't replace an existing message
       return false;
     }
     const { id, content } = recommendation;
     RecommendationMap.set(browser, { id, host, retain: true, content });
     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;
   },
 
   /**
    * Clear all recommendations and hide all PageActions
    */
   clearRecommendations() {
     // WeakMaps aren't iterable so we have to test all existing windows
--- a/browser/components/newtab/test/unit/asrouter/templates/ExtensionDoorhanger.test.jsx
+++ b/browser/components/newtab/test/unit/asrouter/templates/ExtensionDoorhanger.test.jsx
@@ -1,12 +1,13 @@
 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",
   heading_text: "Recommended Extension",
   info_icon: {
     label: { attributes: { tooltiptext: "Why am I seeing this" } },
     sumo_path: "extensionrecommendations",
   },
@@ -35,16 +36,17 @@ const DEFAULT_CONTENT = {
         attributes: { accesskey: "N" },
       },
       action: { type: "CANCEL" },
     },
   },
 };
 
 const L10N_CONTENT = {
+  layout: "addon_recommendation",
   category: "dummyL10NCategory",
   bucket_id: "some_bucket_id",
   notification_text: { string_id: "notification_text_id" },
   heading_text: { string_id: "heading_text_id" },
   info_icon: {
     label: { string_id: "why_seeing_this" },
     sumo_path: "extensionrecommendations",
   },