Bug 1392472 - Implement the blue dot on the overlay icon button, r?gasolin draft
authorFischer.json <fischer.json@gmail.com>
Mon, 04 Sep 2017 12:14:33 +0800
changeset 658414 9be040f975dbba6215cfb5b0cdefd6820297a39a
parent 656346 04b6be50a2526c7a26a63715f441c47e1aa1f9be
child 729646 bbdde1c811dedeacfbe882c3e3415650c3bd256c
push id77761
push userbmo:fliu@mozilla.com
push dateMon, 04 Sep 2017 04:15:20 +0000
reviewersgasolin
bugs1392472, 1392471
milestone57.0a1
Bug 1392472 - Implement the blue dot on the overlay icon button, r?gasolin The patch - fixes Bug 1392471 together: before the 1st notification mute session show the speech bubble by default; after the 1st notification mute session show the blue dot by default. - shows the blue dot by default if the window width is smaller then 1150px disregarding the 1st notification mute session - shows the speech bubble when hovering on the blue dot MozReview-Commit-ID: 6TXZrwDwfV3
browser/extensions/onboarding/content/onboarding.css
browser/extensions/onboarding/content/onboarding.js
--- a/browser/extensions/onboarding/content/onboarding.css
+++ b/browser/extensions/onboarding/content/onboarding.css
@@ -45,27 +45,47 @@
 }
 
 #onboarding-overlay-button-icon {
   width: 32px;
   vertical-align: top;
 }
 
 #onboarding-overlay-button::after {
+  content: " ";
+  border-radius: 50%;
+  margin-top: -1px;
+  margin-inline-start: -13px;
+  border: 2px solid #f2f2f2;
+  background: #0A84FF;
+  padding: 0;
+  width: 10px;
+  height: 10px;
+  min-width: unset;
+  max-width: unset;
+  display: block;
+  box-sizing: content-box;
+  float: inline-end;
+  position: relative;
+}
+
+#onboarding-overlay-button:hover::after,
+#onboarding-overlay-button.onboarding-speech-bubble::after {
   background: #0060df;
   font-size: 13px;
   text-align: center;
   color: #fff;
   box-sizing: content-box;
   font-weight: 400;
   content: attr(aria-label);
-  display: inline-block;
   border: 1px solid transparent;
   border-radius: 2px;
   padding: 10px 16px;
+  width: auto;
+  height: auto;
   min-width: 100px;
   max-width: 140px;
   white-space: pre-line;
   margin-inline-start: 4px;
   margin-top: -10px;
   box-shadow: -2px 0 5px 0 rgba(74, 74, 79, 0.25);
 }
 
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -16,16 +16,18 @@ const ABOUT_NEWTAB_URL = "about:newtab";
 const BUNDLE_URI = "chrome://onboarding/locale/onboarding.properties";
 const UITOUR_JS_URI = "resource://onboarding/lib/UITour-lib.js";
 const TOUR_AGENT_JS_URI = "resource://onboarding/onboarding-tour-agent.js";
 const BRAND_SHORT_NAME = Services.strings
                      .createBundle("chrome://branding/locale/brand.properties")
                      .GetStringFromName("brandShortName");
 const PROMPT_COUNT_PREF = "browser.onboarding.notification.prompt-count";
 const ONBOARDING_DIALOG_ID = "onboarding-overlay-dialog";
+const ONBOARDING_MIN_WIDTH_PX = 960;
+const SPEECH_BUBBLE_MIN_WIDTH_PX = 1150;
 
 /**
  * Add any number of tours, key is the tourId, value should follow the format below
  * "tourId": { // The short tour id which could be saved in pref
  *   // The unique tour id
  *   id: "onboarding-tour-addons",
  *   // The string id of tour name which would be displayed on the navigation bar
  *   tourNameId: "onboarding.tour-addon",
@@ -345,21 +347,28 @@ class Onboarding {
     this._window.addEventListener("unload", () => this.destroy());
 
     this.uiInitialized = false;
     this._resizeTimerId =
       this._window.requestIdleCallback(() => this._resizeUI());
   }
 
   _resizeUI() {
-    // Don't show the overlay UI before we get to a better, responsive design.
-    if (this._window.document.body.getBoundingClientRect().width < 960) {
+    let width = this._window.document.body.getBoundingClientRect().width;
+    if (width < ONBOARDING_MIN_WIDTH_PX) {
+      // Don't show the overlay UI before we get to a better, responsive design.
       this.destroy();
+      return;
+    }
+
+    this._initUI();
+    if (this._isFirstSession && width >= SPEECH_BUBBLE_MIN_WIDTH_PX) {
+      this._overlayIcon.classList.add("onboarding-speech-bubble");
     } else {
-      this._initUI();
+      this._overlayIcon.classList.remove("onboarding-speech-bubble");
     }
   }
 
   _initUI() {
     if (this.uiInitialized) {
       return;
     }
     this.uiInitialized = true;
@@ -746,38 +755,55 @@ class Onboarding {
       targetItem.classList.remove("onboarding-complete");
       targetItem.removeAttribute("aria-describedby");
       if (completedText) {
         completedText.remove();
       }
     }
   }
 
-  _muteNotificationOnFirstSession() {
-    if (Services.prefs.prefHasUserValue("browser.onboarding.notification.tour-ids-queue")) {
-      // There is a queue. We had prompted before, this must not be the 1st session.
+  get _isFirstSession() {
+    // Should only directly return on the "false" case. Consider:
+    //   1. On the 1st session, `_firstSession` is true
+    //   2. During the 1st session, user resizes window so that the UI is destroyed
+    //   3. After the 1st mute session, user resizes window so that the UI is re-init
+    if (this._firstSession === false) {
       return false;
     }
+    this._firstSession = true;
 
-    let muteDuration = Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms");
-    if (muteDuration == 0) {
-      // Don't mute when this is set to 0 on purpose.
+    // There is a queue, which means we had prompted tour notifications before. Therefore this is not the 1st session.
+    if (Services.prefs.prefHasUserValue("browser.onboarding.notification.tour-ids-queue")) {
+      this._firstSession = false;
+    }
+
+    // When this is set to 0 on purpose, always judge as not the 1st session
+    if (Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms") === 0) {
+      this._firstSession = false;
+    }
+
+    return this._firstSession;
+  }
+
+  _muteNotificationOnFirstSession() {
+    if (!this._isFirstSession) {
       return false;
     }
 
     // Reuse the `last-time-of-changing-tour-sec` to save the time that
     // we try to prompt on the 1st session.
     let lastTime = 1000 * Services.prefs.getIntPref("browser.onboarding.notification.last-time-of-changing-tour-sec", 0);
     if (lastTime <= 0) {
       sendMessageToChrome("set-prefs", [{
         name: "browser.onboarding.notification.last-time-of-changing-tour-sec",
         value: Math.floor(Date.now() / 1000)
       }]);
       return true;
     }
+    let muteDuration = Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms");
     return Date.now() - lastTime <= muteDuration;
   }
 
   _isTimeForNextTourNotification() {
     let promptCount = Services.prefs.getIntPref("browser.onboarding.notification.prompt-count", 0);
     let maxCount = Services.prefs.getIntPref("browser.onboarding.notification.max-prompt-count-per-tour");
     if (promptCount >= maxCount) {
       return true;
@@ -835,16 +861,19 @@ class Onboarding {
   showNotification() {
     if (Services.prefs.getBoolPref("browser.onboarding.notification.finished", false)) {
       return;
     }
 
     if (this._muteNotificationOnFirstSession()) {
       return;
     }
+    // After the notification mute on the 1st session,
+    // we don't want to show the speech bubble by default
+    this._overlayIcon.classList.remove("onboarding-speech-bubble");
 
     let queue = this._getNotificationQueue();
     let startQueueLength = queue.length;
     // See if need to move on to the next tour
     if (queue.length > 0 && this._isTimeForNextTourNotification()) {
       queue.shift();
     }
     // We don't want to prompt completed tour.