Bug 1392472 - Implement the blue dot on the overlay icon button, r=gasolin
authorFischer.json <fischer.json@gmail.com>
Mon, 04 Sep 2017 12:14:33 +0800
changeset 428230 8b94452304c31f785aa1ca7500faf7f5897ddaf7
parent 428229 0a674f7cbf8c7f7bc3322e29980e500803868472
child 428231 1518f8954ba2c76f68518c53674399145c8f375b
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgasolin
bugs1392472, 1392471
milestone57.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 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
@@ -46,27 +46,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",
@@ -375,21 +377,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;
@@ -775,38 +784,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;
@@ -864,16 +890,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.