Bug 1135045: Show an infobar when tab sharing is activated in a Loop conversation. r=Standard8, a=lsblakk
authorMike de Boer <mdeboer@mozilla.com>
Thu, 12 Mar 2015 15:13:38 +0100
changeset 248123 f4c0e3fd326a519f08cd6ccf21c662e32f8cd065
parent 248122 249da78cf780e9ca20aafea915f526d8f0d4f4b0
child 248124 b3d399af0d4c6137d8dd3b12a233ef1db6180e55
push id7762
push usermdeboer@mozilla.com
push dateMon, 16 Mar 2015 15:07:09 +0000
treeherdermozilla-aurora@9b59f3a2743d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8, lsblakk
bugs1135045
milestone38.0a2
Bug 1135045: Show an infobar when tab sharing is activated in a Loop conversation. r=Standard8, a=lsblakk
browser/app/profile/firefox.js
browser/base/content/browser-loop.js
browser/base/content/browser.css
browser/base/content/tabbrowser.xml
browser/themes/linux/browser.css
browser/themes/osx/browser.css
browser/themes/windows/browser.css
toolkit/content/widgets/tabbox.xml
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1716,16 +1716,17 @@ pref("loop.CSP", "default-src 'self' abo
 #endif
 pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto");
 pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");
 pref("loop.fxa_oauth.tokendata", "");
 pref("loop.fxa_oauth.profile", "");
 pref("loop.support_url", "https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc");
 pref("loop.contacts.gravatars.show", false);
 pref("loop.contacts.gravatars.promo", true);
+pref("loop.browserSharing.showInfoBar", true);
 
 // serverURL to be assigned by services team
 pref("services.push.serverURL", "wss://push.services.mozilla.com/");
 
 pref("social.sidebar.unload_timeout_ms", 10000);
 
 // activation from inside of share panel is possible if activationPanelEnabled
 // is true. Pref'd off for release while usage testing is done through beta.
--- a/browser/base/content/browser-loop.js
+++ b/browser/base/content/browser-loop.js
@@ -7,16 +7,20 @@ let LoopUI;
 
 XPCOMUtils.defineLazyModuleGetter(this, "injectLoopAPI", "resource:///modules/loop/MozLoopAPI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoopRooms", "resource:///modules/loop/LoopRooms.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MozLoopService", "resource:///modules/loop/MozLoopService.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame", "resource:///modules/PanelFrame.jsm");
 
 
 (function() {
+  const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+  const kBrowserSharingNotificationId = "loop-sharing-notification";
+  const kPrefBrowserSharingInfoBar = "browserSharing.showInfoBar";
+
   LoopUI = {
     /**
      * @var {XULWidgetSingleWrapper} toolbarButton Getter for the Loop toolbarbutton
      *                                             instance for this window.
      */
     get toolbarButton() {
       delete this.toolbarButton;
       return this.toolbarButton = CustomizableUI.getWidget("loop-button").forWindow(window);
@@ -357,16 +361,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
      */
     addBrowserSharingListener: function(listener) {
       if (!this._tabChangeListeners) {
         this._tabChangeListeners = new Set();
         gBrowser.addEventListener("select", this);
       }
 
       this._tabChangeListeners.add(listener);
+      this._maybeShowBrowserSharingInfoBar();
 
       // Get the first window Id for the listener.
       listener(null, gBrowser.selectedTab.linkedBrowser.outerWindowID);
     },
 
     /**
      * Removes a listener from browser sharing.
      *
@@ -377,29 +382,132 @@ XPCOMUtils.defineLazyModuleGetter(this, 
         return;
       }
 
       if (this._tabChangeListeners.has(listener)) {
         this._tabChangeListeners.delete(listener);
       }
 
       if (!this._tabChangeListeners.size) {
+        this._hideBrowserSharingInfoBar();
         gBrowser.removeEventListener("select", this);
         delete this._tabChangeListeners;
       }
     },
 
     /**
+     * Helper function to fetch a localized string via the MozLoopService API.
+     * It's currently inconveniently wrapped inside a string of stringified JSON.
+     *
+     * @param  {String} key The element id to get strings for.
+     * @return {String}
+     */
+    _getString: function(key) {
+      let str = MozLoopService.getStrings(key);
+      if (str) {
+        str = JSON.parse(str).textContent;
+      }
+      return str;
+    },
+
+    /**
+     * Shows an infobar notification at the top of the browser window that warns
+     * the user that their browser tabs are being broadcasted through the current
+     * conversation.
+     */
+    _maybeShowBrowserSharingInfoBar: function() {
+      this._hideBrowserSharingInfoBar();
+
+      // Don't show the infobar if it's been permanently disabled from the menu.
+      if (!MozLoopService.getLoopPref(kPrefBrowserSharingInfoBar)) {
+        return;
+      }
+
+      // Create the menu that is shown when the menu-button' dropmarker is clicked
+      // inside the notification bar.
+      let menuPopup = document.createElementNS(kNSXUL, "menupopup");
+      let menuItem = menuPopup.appendChild(document.createElementNS(kNSXUL, "menuitem"));
+      menuItem.setAttribute("label", this._getString("infobar_menuitem_dontshowagain_label"));
+      menuItem.setAttribute("accesskey", this._getString("infobar_menuitem_dontshowagain_accesskey"));
+      menuItem.addEventListener("command", () => {
+        // We're being told to hide the bar permanently.
+        this._hideBrowserSharingInfoBar(true);
+      });
+
+      let box = gBrowser.getNotificationBox();
+      let bar = box.appendNotification(
+        this._getString("infobar_screenshare_browser_message"),
+        kBrowserSharingNotificationId,
+        // Icon is defined in browser theme CSS.
+        null,
+        box.PRIORITY_WARNING_LOW,
+        [{
+          label: this._getString("infobar_button_gotit_label"),
+          accessKey: this._getString("infobar_button_gotit_accesskey"),
+          type: "menu-button",
+          popup: menuPopup,
+          anchor: "dropmarker",
+          callback: () => {
+            this._hideBrowserSharingInfoBar();
+          }
+        }]
+      );
+
+      // Keep showing the notification bar until the user explicitly closes it.
+      bar.persistence = -1;
+    },
+
+    /**
+     * Hides the infobar, permanantly if requested.
+     *
+     * @param {Boolean} permanently Flag that determines if the infobar will never
+     *                              been shown again. Defaults to `false`.
+     * @return {Boolean} |true| if the infobar was hidden here.
+     */
+    _hideBrowserSharingInfoBar: function(permanently = false, browser) {
+      browser = browser || gBrowser.selectedTab.linkedBrowser;
+      let box = gBrowser.getNotificationBox(browser);
+      let notification = box.getNotificationWithValue(kBrowserSharingNotificationId);
+      let removed = false;
+      if (notification) {
+        box.removeNotification(notification);
+        removed = true;
+      }
+
+      if (permanently) {
+        MozLoopService.setLoopPref(kPrefBrowserSharingInfoBar, false);
+      }
+
+      return removed;
+    },
+
+    /**
      * Handles events from gBrowser.
      */
     handleEvent: function(event) {
       // We only should get "select" events.
       if (event.type != "select") {
         return;
       }
 
+      let wasVisible = false;
+      // Hide the infobar from the previous tab.
+      if (event.fromTab) {
+        wasVisible = this._hideBrowserSharingInfoBar(false, event.fromTab.linkedBrowser);
+      }
+
       // We've changed the tab, so get the new window id.
       for (let listener of this._tabChangeListeners) {
-        listener(null, gBrowser.selectedTab.linkedBrowser.outerWindowID);
+        try {
+          listener(null, gBrowser.selectedTab.linkedBrowser.outerWindowID);
+        } catch (ex) {
+          Cu.reportError("Tab switch caused an error: " + ex.message);
+        }
       };
+
+      if (wasVisible) {
+        // If the infobar was visible before, we should show it again after the
+        // switch.
+        this._maybeShowBrowserSharingInfoBar();
+      }
     },
   };
 })();
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -894,16 +894,21 @@ panelview > .social-panel-frame {
   height: auto;
 }
 
 /* Translation */
 notification[value="translation"] {
   -moz-binding: url("chrome://browser/content/translation-infobar.xml#translationbar");
 }
 
+/* Loop/ Hello */
+notification[value="loop-sharing-notification"] .close-icon {
+  display: none;
+}
+
 /* Social */
 /* Note the chatbox 'width' values are duplicated in socialchat.xml */
 chatbox {
   -moz-binding: url("chrome://browser/content/socialchat.xml#chatbox");
   transition: height 150ms ease-out, width 150ms ease-out;
   height: 285px;
   width: 260px; /* CHAT_WIDTH_OPEN in socialchat.xml */
 }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -5474,16 +5474,18 @@
           let switchPromise = gBrowser._prepareForTabSwitch(toTab, fromTab);
 
           var panel = this._selectedPanel;
           var newPanel = this.childNodes[val];
           this._selectedPanel = newPanel;
           if (this._selectedPanel != panel) {
             var event = document.createEvent("Events");
             event.initEvent("select", true, true);
+            event.fromTab = fromTab;
+            event.toTab = toTab;
             this.dispatchEvent(event);
 
             this._selectedIndex = val;
 
             switchPromise.then(() => {
               // If we cannot find the tabpanel that we were trying to switch to, then
               // it must have been removed before our Promise could be resolved. In
               // that case, we just cancel the tab switch.
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1479,16 +1479,26 @@ notification[value="translation"] menuli
 }
 
 .translated-notification-icon,
 #translated-notification-icon {
   list-style-image: url(chrome://browser/skin/translation-16.png);
   -moz-image-region: rect(0px, 32px, 16px, 16px);
 }
 
+/* Loop/ Hello browser styles */
+
+notification[value="loop-sharing-notification"] .button-menubutton-button {
+  min-width: 0;
+}
+
+notification[value="loop-sharing-notification"] .messageImage {
+  list-style-image: url(chrome://browser/skin/webRTC-shareScreen-16.png);
+}
+
 #treecolAutoCompleteImage {
   max-width : 36px;
 }
 
 .ac-result-type-bookmark,
 .autocomplete-treebody::-moz-tree-image(bookmark, treecolAutoCompleteImage) {
   list-style-image: url("chrome://browser/skin/places/star-icons.png");
   -moz-image-region: rect(0px 32px 16px 16px);
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -4111,16 +4111,37 @@ menulist.translate-infobar-element > .me
     list-style-image: url("chrome://global/skin/icons/glyph-dropdown@2x.png");
   }
 
   menulist.translate-infobar-element > .menulist-dropmarker > .dropmarker-icon {
     width: 8px;
   }
 }
 
+/* Loop/ Hello browser styles */
+
+notification[value="loop-sharing-notification"] .notification-button {
+  padding: 1px 5px;
+}
+
+notification[value="loop-sharing-notification"] .button-menubutton-button {
+  -moz-appearance: none;
+  min-width: 0;
+  margin: 0;
+}
+
+notification[value="loop-sharing-notification"] .messageImage {
+  list-style-image: url(chrome://browser/skin/webRTC-sharingScreen-menubar.png);
+}
+@media (min-resolution: 2dppx) {
+  notification[value="loop-sharing-notification"] .messageImage {
+    list-style-image: url(chrome://browser/skin/webRTC-sharingScreen-menubar@2x.png);
+  }
+}
+
 
 .popup-notification-icon {
   width: 64px;
   height: 64px;
   -moz-margin-end: 10px;
 }
 
 .popup-notification-icon[popupid="geolocation"] {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2500,16 +2500,29 @@ notification[value="translation"] {
   list-style-image: url(chrome://browser/skin/translation-16.png);
   -moz-image-region: rect(0px, 32px, 16px, 16px);
 }
 
 .translation-menupopup {
   -moz-appearance: none;
 }
 
+/* Loop/ Hello browser styles */
+
+notification[value="loop-sharing-notification"] .button-menubutton-button {
+  -moz-appearance: none;
+  min-width: 0;
+  border: 0;
+  margin: 0;
+}
+
+notification[value="loop-sharing-notification"] .messageImage {
+  list-style-image: url(chrome://browser/skin/webRTC-shareScreen-16.png);
+}
+
 /* Bookmarks roots menu-items */
 #subscribeToPageMenuitem:not([disabled]),
 #subscribeToPageMenupopup,
 #BMB_subscribeToPageMenuitem:not([disabled]),
 #BMB_subscribeToPageMenupopup {
   list-style-image: url("chrome://browser/skin/feeds/feedIcon16.png");
 }
 
--- a/toolkit/content/widgets/tabbox.xml
+++ b/toolkit/content/widgets/tabbox.xml
@@ -662,16 +662,18 @@
           if (val < 0 || val >= this.childNodes.length)
             return val;
           var panel = this._selectedPanel;
           this._selectedPanel = this.childNodes[val];
           this.setAttribute("selectedIndex", val);
           if (this._selectedPanel != panel) {
             var event = document.createEvent("Events");
             event.initEvent("select", true, true);
+            event.fromTab = this.getRelatedElement(panel);
+            event.toTab = this.getRelatedElement(this._selectedPanel);
             this.dispatchEvent(event);
           }
           return val;
         ]]>
         </setter>
       </property>
 
       <property name="selectedPanel">