Bug 1057006 - Implement UI for app sharing, r=Gijs.
authorFlorian Quèze <florian@queze.net>
Wed, 27 Aug 2014 16:52:22 +0200
changeset 223615 ffc10f34de13e81ad5ff359dc6297983ac81f929
parent 223614 af3510fa00fab623e39ef7e1d5d75aeebcdfcaba
child 223616 5229e47c07b24318b6b96f684a248c23b271b420
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs
bugs1057006
milestone34.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 1057006 - Implement UI for app sharing, r=Gijs.
browser/base/content/popup-notifications.inc
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/browser.properties
browser/locales/en-US/chrome/browser/webrtcIndicator.properties
browser/modules/webrtcUI.jsm
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -16,18 +16,17 @@
                control="webRTC-selectCamera-menulist"/>
         <menulist id="webRTC-selectCamera-menulist">
           <menupopup id="webRTC-selectCamera-menupopup"/>
         </menulist>
       </popupnotificationcontent>
 
       <popupnotificationcontent id="webRTC-selectWindowOrScreen" orient="vertical">
         <separator class="thin"/>
-        <label value="&getUserMedia.selectWindowOrScreen.label;"
-               accesskey="&getUserMedia.selectWindowOrScreen.accesskey;"
+        <label id="webRTC-selectWindow-label"
                control="webRTC-selectWindow-menulist"/>
         <menulist id="webRTC-selectWindow-menulist"
                   oncommand="gWebRTCUI.updateMainActionLabel(this);">
           <menupopup id="webRTC-selectWindow-menupopup"/>
         </menulist>
         <description id="webRTC-all-windows-shared" hidden="true">&getUserMedia.allWindowsShared.message;</description>
       </popupnotificationcontent>
 
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -713,18 +713,16 @@ just addresses the organization to follo
 
 <!ENTITY social.markpageMenu.accesskey "P">
 <!ENTITY social.markpageMenu.label "Save Page To…">
 <!ENTITY social.marklinkMenu.accesskey "L">
 <!ENTITY social.marklinkMenu.label "Save Link To…">
 
 <!ENTITY getUserMedia.selectCamera.label "Camera to share:">
 <!ENTITY getUserMedia.selectCamera.accesskey "C">
-<!ENTITY getUserMedia.selectWindowOrScreen.label "Window or screen to share:">
-<!ENTITY getUserMedia.selectWindowOrScreen.accesskey "W">
 <!ENTITY getUserMedia.selectMicrophone.label "Microphone to share:">
 <!ENTITY getUserMedia.selectMicrophone.accesskey "M">
 <!ENTITY getUserMedia.allWindowsShared.message "All visible windows on your screen will be shared.">
 
 <!-- Bad Content Blocker Doorhanger Notification -->
 <!ENTITY badContentBlocked.moreinfo "Most websites will work properly even if content is blocked.">
 
 <!ENTITY mixedContentBlocked2.message "Insecure content">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -510,62 +510,78 @@ identity.loggedIn.signOut.accessKey = O
 #                    getUserMedia.shareScreen.message, getUserMedia.shareCameraAndMicrophone.message,
 #                    getUserMedia.shareScreenAndMicrophone.message):
 #  %S is the website origin (e.g. www.mozilla.org)
 getUserMedia.shareCamera.message = Would you like to share your camera with %S?
 getUserMedia.shareMicrophone.message = Would you like to share your microphone with %S?
 getUserMedia.shareScreen.message = Would you like to share your screen with %S?
 getUserMedia.shareCameraAndMicrophone.message = Would you like to share your camera and microphone with %S?
 getUserMedia.shareScreenAndMicrophone.message = Would you like to share your microphone and screen with %S?
+getUserMedia.selectWindow.label=Window to share:
+getUserMedia.selectWindow.accesskey=W
+getUserMedia.selectScreen.label=Screen to share:
+getUserMedia.selectScreen.accesskey=S
+getUserMedia.selectApplication.label=Application to share:
+getUserMedia.selectApplication.accesskey=A
 getUserMedia.noVideo.label = No Video
-getUserMedia.noWindowOrScreen.label = No Window or Screen
+getUserMedia.noApplication.label = No Application
+getUserMedia.noScreen.label = No Screen
+getUserMedia.noWindow.label = No Window
 getUserMedia.noAudio.label = No Audio
 getUserMedia.shareEntireScreen.label = Entire screen
 # LOCALIZATION NOTE (getUserMedia.shareSelectedDevices.label):
 # Semicolon-separated list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # The number of devices can be either one or two.
 getUserMedia.shareSelectedDevices.label = Share Selected Device;Share Selected Devices
 getUserMedia.shareSelectedDevices.accesskey = S
 getUserMedia.shareScreen.label = Share Screen
+getUserMedia.shareApplication.label = Share Selected Application
 getUserMedia.shareWindow.label = Share Selected Window
 getUserMedia.shareSelectedItems.label = Share Selected Items
 getUserMedia.always.label = Always Share
 getUserMedia.always.accesskey = A
 getUserMedia.denyRequest.label = Don't Share
 getUserMedia.denyRequest.accesskey = D
 getUserMedia.never.label = Never Share
 getUserMedia.never.accesskey = N
 getUserMedia.sharingCamera.message2 = You are currently sharing your camera with this page.
 getUserMedia.sharingMicrophone.message2 = You are currently sharing your microphone with this page.
 getUserMedia.sharingCameraAndMicrophone.message2 = You are currently sharing your camera and microphone with this page.
+getUserMedia.sharingApplication.message = You are currently sharing an application with this page.
 getUserMedia.sharingScreen.message = You are currently sharing your screen with this page.
 getUserMedia.sharingWindow.message = You are currently sharing a window with this page.
 getUserMedia.continueSharing.label = Continue Sharing
 getUserMedia.continueSharing.accesskey = C
 getUserMedia.stopSharing.label = Stop Sharing
 getUserMedia.stopSharing.accesskey = S
 
 getUserMedia.sharingMenu.label = Tabs sharing devices
 getUserMedia.sharingMenu.accesskey = d
 # LOCALIZATION NOTE (getUserMedia.sharingMenuCamera, getUserMedia.sharingMenuCamera,
-#                    getUserMedia.sharingMenuMicrophone, getUserMedia.sharingMenuScreen,
-#                    getUserMedia.sharingMenuWindow, getUserMedia.sharingMenuCameraMicrophone,
+#                    getUserMedia.sharingMenuMicrophone, getUserMedia.sharingMenuApplication,
+#                    getUserMedia.sharingMenuScreen, getUserMedia.sharingMenuWindow,
+#                    getUserMedia.sharingMenuCameraMicrophone,
+#                    getUserMedia.sharingMenuCameraMicrophoneApplication,
 #                    getUserMedia.sharingMenuCameraMicrophoneScreen,
 #                    getUserMedia.sharingMenuCameraMicrophoneWindow,
+#                    getUserMedia.sharingMenuMicrophoneApplication,
 #                    getUserMedia.sharingMenuMicrophoneScreen,
 #                    getUserMedia.sharingMenuMicrophoneWindow):
 # %S is the website origin (e.g. www.mozilla.org)
 getUserMedia.sharingMenuCamera = %S (camera)
 getUserMedia.sharingMenuMicrophone = %S (microphone)
+getUserMedia.sharingMenuApplication = %S (application)
 getUserMedia.sharingMenuScreen = %S (screen)
 getUserMedia.sharingMenuWindow = %S (window)
 getUserMedia.sharingMenuCameraMicrophone = %S (camera and microphone)
+getUserMedia.sharingMenuCameraMicrophoneApplication = %S (camera, microphone and application)
 getUserMedia.sharingMenuCameraMicrophoneScreen = %S (camera, microphone and screen)
 getUserMedia.sharingMenuCameraMicrophoneWindow = %S (camera, microphone and window)
+getUserMedia.sharingMenuMicrophoneApplication = %S (microphone and application)
 getUserMedia.sharingMenuMicrophoneScreen = %S (microphone and screen)
 getUserMedia.sharingMenuMicrophoneWindow = %S (microphone and window)
 # LOCALIZATION NOTE(getUserMedia.sharingMenuUnknownHost): this is used for the website
 # origin for the sharing menu if no readable origin could be deduced from the URL.
 getUserMedia.sharingMenuUnknownHost = Unknown origin
 
 # LOCALIZATION NOTE - %S is brandShortName
 slowStartup.message = %S seems slow… to… start.
--- a/browser/locales/en-US/chrome/browser/webrtcIndicator.properties
+++ b/browser/locales/en-US/chrome/browser/webrtcIndicator.properties
@@ -7,31 +7,34 @@
 # LOCALIZATION NOTE (webrtcIndicator.windowtitle): %S is the brand name (e.g. Firefox).
 # This string is used so that the window has a title in tools that enumerate/look for window
 # titles. It is not normally visible anywhere.
 webrtcIndicator.windowtitle = %S - Sharing Indicator
 
 webrtcIndicator.sharingCameraAndMicrophone.tooltip = Your camera and microphone are being shared. Click to control sharing.
 webrtcIndicator.sharingCamera.tooltip              = Your camera is being shared. Click to control sharing.
 webrtcIndicator.sharingMicrophone.tooltip          = Your microphone is being shared. Click to control sharing.
+webrtcIndicator.sharingApplication.tooltip = An application is being shared. Click to control sharing.
 webrtcIndicator.sharingScreen.tooltip = Your screen is being shared. Click to control sharing.
 webrtcIndicator.sharingWindow.tooltip = A window is being shared. Click to control sharing.
 
 
 # LOCALIZATION NOTE : The following strings are only used on Mac for
 # menus attached to icons near the clock on the mac menubar.
 
 # LOCALIZATION NOTE (webrtcIndicator.sharing*With.menuitem):
 # %S is the title of the tab using the share.
 webrtcIndicator.sharingCameraWith.menuitem = Sharing Camera with "%S"
 webrtcIndicator.sharingMicrophoneWith.menuitem = Sharing Microphone with "%S"
+webrtcIndicator.sharingApplicationWith.menuitem = Sharing an Application with "%S"
 webrtcIndicator.sharingScreenWith.menuitem = Sharing Screen with "%S"
 webrtcIndicator.sharingWindowWith.menuitem = Sharing a Window with "%S"
 webrtcIndicator.controlSharing.menuitem = Control Sharing
 # LOCALIZATION NOTE (webrtcIndicator.sharing*WithNTabs.menuitem):
 # Semicolon-separated list of plural forms.
 webrtcIndicator.sharingCameraWithNTabs.menuitem = Sharing Camera with #1 tab;Sharing Camera with #1 tabs
 webrtcIndicator.sharingMicrophoneWithNTabs.menuitem = Sharing Microphone with #1 tab;Sharing Microphone with #1 tabs
+webrtcIndicator.sharingApplicationWithNTabs.menuitem = Sharing an Application with #1 tab;Sharing Applications with #1 tabs
 webrtcIndicator.sharingScreenWithNTabs.menuitem = Sharing Screen with #1 tab;Sharing Screen with #1 tabs
 webrtcIndicator.sharingWindowWithNTabs.menuitem = Sharing a Window with #1 tab;Sharing Windows with #1 tabs
 # LOCALIZATION NOTE (webrtcIndicator.controlSharingOn.menuitem):
 # %S is the title of the tab using the share.
 webrtcIndicator.controlSharingOn.menuitem = Control Sharing on "%S"
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -33,38 +33,40 @@ this.webrtcUI = {
     Services.obs.removeObserver(updateIndicators, "recording-device-events");
     Services.obs.removeObserver(removeBrowserSpecificIndicator, "recording-window-ended");
     Services.obs.removeObserver(maybeAddMenuIndicator, "browser-delayed-startup-finished");
   },
 
   showGlobalIndicator: false,
   showCameraIndicator: false,
   showMicrophoneIndicator: false,
-  showScreenSharingIndicator: "", // either "Screen" or "Window"
+  showScreenSharingIndicator: "", // either "Application", "Screen" or "Window"
 
   // The boolean parameters indicate which streams should be included in the result.
   getActiveStreams: function(aCamera, aMicrophone, aScreen) {
     let contentWindowSupportsArray = MediaManagerService.activeMediaCaptureWindows;
     let count = contentWindowSupportsArray.Count();
     let activeStreams = [];
     for (let i = 0; i < count; i++) {
       let contentWindow = contentWindowSupportsArray.GetElementAt(i);
 
       let info = {
         Camera: {},
         Microphone: {},
         Window: {},
         Screen: {},
+        Application: {}
       };
       MediaManagerService.mediaCaptureWindowState(contentWindow, info.Camera,
                                                   info.Microphone, info.Screen,
-                                                  info.Window);
+                                                  info.Window, info.Application);
       if (!(aCamera && info.Camera.value ||
             aMicrophone && info.Microphone.value ||
-            aScreen && (info.Screen.value || info.Window.value)))
+            aScreen && (info.Screen.value || info.Window.value ||
+                        info.Application.value)))
         continue;
 
       let browser = getBrowserForWindow(contentWindow);
       let browserWindow = browser.ownerDocument.defaultView;
       let tab = browserWindow.gBrowser &&
                 browserWindow.gBrowser._getTabForContentWindow(contentWindow.top);
       activeStreams.push({
         uri: contentWindow.location.href,
@@ -324,48 +326,46 @@ function prompt(aContentWindow, aCallID,
           deviceIndex++;
         }
       }
 
       function listScreenShareDevices(menupopup, devices) {
         while (menupopup.lastChild)
           menupopup.removeChild(menupopup.lastChild);
 
-        // "No Window or Screen" is the default because we can't pick a
+        let type = devices[0].mediaSource;
+        let typeName = type.charAt(0).toUpperCase() + type.substr(1);
+
+        let label = chromeDoc.getElementById("webRTC-selectWindow-label");
+        let stringId = "getUserMedia.select" + typeName;
+        label.setAttribute("value",
+                           stringBundle.getString(stringId + ".label"));
+        label.setAttribute("accesskey",
+                           stringBundle.getString(stringId + ".accesskey"));
+
+        // "No <type>" is the default because we can't pick a
         // 'default' window to share.
         addDeviceToList(menupopup,
-                        stringBundle.getString("getUserMedia.noWindowOrScreen.label"),
+                        stringBundle.getString("getUserMedia.no" + typeName + ".label"),
                         "-1");
+        menupopup.appendChild(chromeDoc.createElement("menuseparator"));
 
-        // Then add the 'Entire screen' item if mozGetUserMediaDevices returned it.
+        // Build the list of 'devices'.
         for (let i = 0; i < devices.length; ++i) {
-          if (devices[i].mediaSource == "screen") {
-            menupopup.appendChild(chromeDoc.createElement("menuseparator"));
-            addDeviceToList(menupopup,
-                            stringBundle.getString("getUserMedia.shareEntireScreen.label"),
-                            i, "Screen");
-            break;
-          }
+          let name;
+          // Screen has a special treatment because we currently only support
+          // sharing the primary screen and want to display a localized string.
+          if (type == "screen")
+            name = stringBundle.getString("getUserMedia.shareEntireScreen.label");
+          else
+            name = devices[i].name;
+          addDeviceToList(menupopup, name, i, typeName);
         }
 
-        // Finally add all the window names.
-        let separatorNeeded = true;
-        for (let i = 0; i < devices.length; ++i) {
-          // treat window and application as the same for UI purposes
-          let deviceMediaSource = devices[i].mediaSource;
-          if (deviceMediaSource == "window" || deviceMediaSource == "application") {
-            if (separatorNeeded) {
-              menupopup.appendChild(chromeDoc.createElement("menuseparator"));
-              separatorNeeded = false;
-            }
-            addDeviceToList(menupopup, devices[i].name, i, "Window");
-          }
-        }
-
-        // Always re-select the "No Window or Screen" item.
+        // Always re-select the "No <type>" item.
         chromeDoc.getElementById("webRTC-selectWindow-menulist").removeAttribute("value");
         chromeDoc.getElementById("webRTC-all-windows-shared").hidden = true;
       }
 
       function addDeviceToList(menupopup, deviceName, deviceIndex, type) {
         let menuitem = chromeDoc.createElement("menuitem");
         menuitem.setAttribute("value", deviceIndex);
         menuitem.setAttribute("label", deviceName);
@@ -457,17 +457,17 @@ function getGlobalIndicator() {
                   .hiddenDOMWindow.document,
     _statusBar: Cc["@mozilla.org/widget/macsystemstatusbar;1"]
                   .getService(Ci.nsISystemStatusBar),
 
     _command: function(aEvent) {
       let type = this.getAttribute("type");
       if (type == "Camera" || type == "Microphone")
         type = "Devices";
-      else if (type == "Window")
+      else if (type == "Window" || type == "Application")
         type = "Screen";
       webrtcUI.showSharingDoorhanger(aEvent.target.stream, type);
     },
 
     _popupShowing: function(aEvent) {
       let type = this.getAttribute("type");
       let activeStreams;
       if (type == "Camera") {
@@ -575,25 +575,27 @@ function getGlobalIndicator() {
   return indicator;
 #endif
 }
 
 function onTabSharingMenuPopupShowing(e) {
   let streams = webrtcUI.getActiveStreams(true, true, true);
   for (let streamInfo of streams) {
     let stringName = "getUserMedia.sharingMenu";
-    // Guarantee sorting order here or bad things will happen if
-    // the not-specced-but-implemented-everywhere object key sorting changes:
-    let types = Object.keys(streamInfo.types).sort();
-    // Then construct a string ID out of these types:
-    for (let type of types) {
-      if (streamInfo.types[type].value) {
-        stringName += type;
-      }
-    }
+    let types = streamInfo.types;
+    if (types.Camera.value)
+      stringName += "Camera";
+    if (types.Microphone.value)
+      stringName += "Microphone";
+    if (types.Screen.value)
+      stringName += "Screen";
+    else if (types.Application.value)
+      stringName += "Application";
+    else if (types.Window.value)
+      stringName += "Window";
 
     let doc = e.target.ownerDocument;
     let bundle = doc.defaultView.gNavigatorBundle;
 
     let origin;
     let uri;
     let href = streamInfo.uri;
     try {
@@ -610,20 +612,20 @@ function onTabSharingMenuPopupShowing(e)
       }
     }
 
     let menuitem = doc.createElement("menuitem");
     menuitem.setAttribute("label", bundle.getFormattedString(stringName, [origin]));
     menuitem.stream = streamInfo;
 
     // We can only open 1 doorhanger at a time. Guessing that users would be
-    // most eager to control screen/window sharing, and only then
+    // most eager to control screen/window/app sharing, and only then
     // camera/microphone sharing, in that (decreasing) order of priority.
     let doorhangerType;
-    if ((/Screen|Window/).test(stringName)) {
+    if ((/Screen|Window|Application/).test(stringName)) {
       doorhangerType = "Screen";
     } else {
       doorhangerType = "Devices";
     }
     menuitem.setAttribute("doorhangertype", doorhangerType);
     menuitem.addEventListener("command", onTabSharingMenuPopupCommand);
     e.target.appendChild(menuitem);
   }
@@ -688,27 +690,29 @@ function updateIndicators() {
 
   webrtcUI.showGlobalIndicator = count > 0;
   webrtcUI.showCameraIndicator = false;
   webrtcUI.showMicrophoneIndicator = false;
   webrtcUI.showScreenSharingIndicator = "";
 
   for (let i = 0; i < count; ++i) {
     let contentWindow = contentWindowSupportsArray.GetElementAt(i);
-    let camera = {}, microphone = {}, screen = {}, window = {};
+    let camera = {}, microphone = {}, screen = {}, window = {}, app = {};
     MediaManagerService.mediaCaptureWindowState(contentWindow, camera,
-                                                microphone, screen, window);
+                                                microphone, screen, window, app);
     if (camera.value)
       webrtcUI.showCameraIndicator = true;
     if (microphone.value)
       webrtcUI.showMicrophoneIndicator = true;
     if (screen.value)
       webrtcUI.showScreenSharingIndicator = "Screen";
-    else if (window.value && !webrtcUI.showScreenSharingIndicator)
+    else if (window.value && webrtcUI.showScreenSharingIndicator != "Screen")
       webrtcUI.showScreenSharingIndicator = "Window";
+    else if (app.value && !webrtcUI.showScreenSharingIndicator)
+      webrtcUI.showScreenSharingIndicator = "Application";
 
     updateBrowserSpecificIndicator(getBrowserForWindow(contentWindow));
   }
 
   let browserWindowEnum = Services.wm.getEnumerator("navigator:browser");
   while (browserWindowEnum.hasMoreElements()) {
     let chromeWin = browserWindowEnum.getNext();
     if (webrtcUI.showGlobalIndicator) {
@@ -735,19 +739,20 @@ function updateIndicators() {
       gIndicatorWindow.updateIndicatorState();
   } else if (gIndicatorWindow) {
     gIndicatorWindow.close();
     gIndicatorWindow = null;
   }
 }
 
 function updateBrowserSpecificIndicator(aBrowser) {
-  let camera = {}, microphone = {}, screen = {}, window = {};
+  let camera = {}, microphone = {}, screen = {}, window = {}, app = {};
   MediaManagerService.mediaCaptureWindowState(aBrowser.contentWindow,
-                                              camera, microphone, screen, window);
+                                              camera, microphone, screen,
+                                              window, app);
   let captureState;
   if (camera.value && microphone.value) {
     captureState = "CameraAndMicrophone";
   } else if (camera.value) {
     captureState = "Camera";
   } else if (microphone.value) {
     captureState = "Microphone";
   }
@@ -800,17 +805,17 @@ function updateBrowserSpecificIndicator(
     chromeWin.PopupNotifications.show(aBrowser, "webRTC-sharingDevices", message,
                                       anchorId, mainAction, secondaryActions, options);
   }
   else {
     removeBrowserNotification(aBrowser,"webRTC-sharingDevices");
   }
 
   // Now handle the screen sharing indicator.
-  if (!screen.value && !window.value) {
+  if (!screen.value && !window.value && !app.value) {
     removeBrowserNotification(aBrowser,"webRTC-sharingScreen");
     return;
   }
 
   options = {
     hideNotNow: true,
     dismissed: true,
     eventCallback: function(aTopic) {
@@ -824,19 +829,25 @@ function updateBrowserSpecificIndicator(
   secondaryActions = [{
     label: stringBundle.getString("getUserMedia.stopSharing.label"),
     accessKey: stringBundle.getString("getUserMedia.stopSharing.accesskey"),
     callback: function () {
       Services.obs.notifyObservers(null, "getUserMedia:revoke", "screen:" + windowId);
     }
   }];
   // If we are sharing both a window and the screen, show 'Screen'.
-  let stringId = "getUserMedia.sharing" + (screen.value ? "Screen" : "Window") + ".message";
+  let stringId = "getUserMedia.sharing";
+  if (screen.value)
+    stringId += "Screen";
+  else if (app.value)
+    stringId += "Application";
+  else
+    stringId += "Window";
   chromeWin.PopupNotifications.show(aBrowser, "webRTC-sharingScreen",
-                                    stringBundle.getString(stringId),
+                                    stringBundle.getString(stringId + ".message"),
                                     "webRTC-sharingScreen-notification-icon",
                                     mainAction, secondaryActions, options);
 }
 
 function removeBrowserNotification(aBrowser, aNotificationId) {
   let win = aBrowser.ownerDocument.defaultView;
   let notification =
     win.PopupNotifications.getNotification(aNotificationId, aBrowser);