forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1085 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1085 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | |
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| this.EXPORTED_SYMBOLS = ["webrtcUI"];
 | |
| 
 | |
| const Cu = Components.utils;
 | |
| const Cc = Components.classes;
 | |
| const Ci = Components.interfaces;
 | |
| 
 | |
| Cu.import("resource:///modules/syncedtabs/EventEmitter.jsm");
 | |
| Cu.import("resource://gre/modules/Services.jsm");
 | |
| Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 | |
| 
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
 | |
|                                   "resource://gre/modules/AppConstants.jsm");
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
 | |
|                                   "resource://gre/modules/PluralForm.jsm");
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "Task",
 | |
|                                   "resource://gre/modules/Task.jsm");
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
 | |
|                                   "resource:///modules/SitePermissions.jsm");
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
 | |
|   return Services.strings.createBundle("chrome://branding/locale/brand.properties");
 | |
| });
 | |
| 
 | |
| this.webrtcUI = {
 | |
|   peerConnectionBlockers: new Set(),
 | |
|   emitter: new EventEmitter(),
 | |
| 
 | |
|   init() {
 | |
|     Services.obs.addObserver(maybeAddMenuIndicator, "browser-delayed-startup-finished", false);
 | |
| 
 | |
|     let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
 | |
|                  .getService(Ci.nsIMessageBroadcaster);
 | |
|     ppmm.addMessageListener("webrtc:UpdatingIndicators", this);
 | |
|     ppmm.addMessageListener("webrtc:UpdateGlobalIndicators", this);
 | |
|     ppmm.addMessageListener("child-process-shutdown", this);
 | |
| 
 | |
|     let mm = Cc["@mozilla.org/globalmessagemanager;1"]
 | |
|                .getService(Ci.nsIMessageListenerManager);
 | |
|     mm.addMessageListener("rtcpeer:Request", this);
 | |
|     mm.addMessageListener("rtcpeer:CancelRequest", this);
 | |
|     mm.addMessageListener("webrtc:Request", this);
 | |
|     mm.addMessageListener("webrtc:StopRecording", this);
 | |
|     mm.addMessageListener("webrtc:CancelRequest", this);
 | |
|     mm.addMessageListener("webrtc:UpdateBrowserIndicators", this);
 | |
|   },
 | |
| 
 | |
|   uninit() {
 | |
|     Services.obs.removeObserver(maybeAddMenuIndicator, "browser-delayed-startup-finished");
 | |
| 
 | |
|     let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
 | |
|                  .getService(Ci.nsIMessageBroadcaster);
 | |
|     ppmm.removeMessageListener("webrtc:UpdatingIndicators", this);
 | |
|     ppmm.removeMessageListener("webrtc:UpdateGlobalIndicators", this);
 | |
| 
 | |
|     let mm = Cc["@mozilla.org/globalmessagemanager;1"]
 | |
|                .getService(Ci.nsIMessageListenerManager);
 | |
|     mm.removeMessageListener("rtcpeer:Request", this);
 | |
|     mm.removeMessageListener("rtcpeer:CancelRequest", this);
 | |
|     mm.removeMessageListener("webrtc:Request", this);
 | |
|     mm.removeMessageListener("webrtc:StopRecording", this);
 | |
|     mm.removeMessageListener("webrtc:CancelRequest", this);
 | |
|     mm.removeMessageListener("webrtc:UpdateBrowserIndicators", this);
 | |
| 
 | |
|     if (gIndicatorWindow) {
 | |
|       gIndicatorWindow.close();
 | |
|       gIndicatorWindow = null;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   processIndicators: new Map(),
 | |
|   activePerms: new Map(),
 | |
| 
 | |
|   get showGlobalIndicator() {
 | |
|     for (let [, indicators] of this.processIndicators) {
 | |
|       if (indicators.showGlobalIndicator)
 | |
|         return true;
 | |
|     }
 | |
|     return false;
 | |
|   },
 | |
| 
 | |
|   get showCameraIndicator() {
 | |
|     for (let [, indicators] of this.processIndicators) {
 | |
|       if (indicators.showCameraIndicator)
 | |
|         return true;
 | |
|     }
 | |
|     return false;
 | |
|   },
 | |
| 
 | |
|   get showMicrophoneIndicator() {
 | |
|     for (let [, indicators] of this.processIndicators) {
 | |
|       if (indicators.showMicrophoneIndicator)
 | |
|         return true;
 | |
|     }
 | |
|     return false;
 | |
|   },
 | |
| 
 | |
|   get showScreenSharingIndicator() {
 | |
|     let list = [""];
 | |
|     for (let [, indicators] of this.processIndicators) {
 | |
|       if (indicators.showScreenSharingIndicator)
 | |
|         list.push(indicators.showScreenSharingIndicator);
 | |
|     }
 | |
| 
 | |
|     let precedence =
 | |
|       ["Screen", "Window", "Application", "Browser", ""];
 | |
| 
 | |
|     list.sort((a, b) => {
 | |
|  return precedence.indexOf(a) -
 | |
|                                  precedence.indexOf(b);
 | |
| });
 | |
| 
 | |
|     return list[0];
 | |
|   },
 | |
| 
 | |
|   _streams: [],
 | |
|   // The boolean parameters indicate which streams should be included in the result.
 | |
|   getActiveStreams(aCamera, aMicrophone, aScreen) {
 | |
|     return webrtcUI._streams.filter(aStream => {
 | |
|       let state = aStream.state;
 | |
|       return aCamera && state.camera ||
 | |
|              aMicrophone && state.microphone ||
 | |
|              aScreen && state.screen;
 | |
|     }).map(aStream => {
 | |
|       let state = aStream.state;
 | |
|       let types = {camera: state.camera, microphone: state.microphone,
 | |
|                    screen: state.screen};
 | |
|       let browser = aStream.browser;
 | |
|       let browserWindow = browser.ownerGlobal;
 | |
|       let tab = browserWindow.gBrowser &&
 | |
|                 browserWindow.gBrowser.getTabForBrowser(browser);
 | |
|       return {uri: state.documentURI, tab, browser, types};
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   swapBrowserForNotification(aOldBrowser, aNewBrowser) {
 | |
|     for (let stream of this._streams) {
 | |
|       if (stream.browser == aOldBrowser)
 | |
|         stream.browser = aNewBrowser;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   forgetActivePermissionsFromBrowser(aBrowser) {
 | |
|     webrtcUI.activePerms.delete(aBrowser.outerWindowID);
 | |
|   },
 | |
| 
 | |
|   forgetStreamsFromBrowser(aBrowser) {
 | |
|     this._streams = this._streams.filter(stream => stream.browser != aBrowser);
 | |
|     webrtcUI.forgetActivePermissionsFromBrowser(aBrowser);
 | |
|   },
 | |
| 
 | |
|   showSharingDoorhanger(aActiveStream) {
 | |
|     let browserWindow = aActiveStream.browser.ownerGlobal;
 | |
|     if (aActiveStream.tab) {
 | |
|       browserWindow.gBrowser.selectedTab = aActiveStream.tab;
 | |
|     } else {
 | |
|       aActiveStream.browser.focus();
 | |
|     }
 | |
|     browserWindow.focus();
 | |
|     let identityBox = browserWindow.document.getElementById("identity-box");
 | |
|     if (AppConstants.platform == "macosx" && !Services.focus.activeWindow) {
 | |
|       browserWindow.addEventListener("activate", function() {
 | |
|         Services.tm.mainThread.dispatch(function() {
 | |
|           identityBox.click();
 | |
|         }, Ci.nsIThread.DISPATCH_NORMAL);
 | |
|       }, {once: true});
 | |
|       Cc["@mozilla.org/widget/macdocksupport;1"].getService(Ci.nsIMacDockSupport)
 | |
|         .activateApplication(true);
 | |
|       return;
 | |
|     }
 | |
|     identityBox.click();
 | |
|   },
 | |
| 
 | |
|   updateWarningLabel(aMenuList) {
 | |
|     let type = aMenuList.selectedItem.getAttribute("devicetype");
 | |
|     let document = aMenuList.ownerDocument;
 | |
|     document.getElementById("webRTC-all-windows-shared").hidden = type != "Screen";
 | |
|   },
 | |
| 
 | |
|   // Add-ons can override stock permission behavior by doing:
 | |
|   //
 | |
|   //   webrtcUI.addPeerConnectionBlocker(function(aParams) {
 | |
|   //     // new permission checking logic
 | |
|   //   }));
 | |
|   //
 | |
|   // The blocking function receives an object with origin, callID, and windowID
 | |
|   // parameters.  If it returns the string "deny" or a Promise that resolves
 | |
|   // to "deny", the connection is immediately blocked.  With any other return
 | |
|   // value (though the string "allow" is suggested for consistency), control
 | |
|   // is passed to other registered blockers.  If no registered blockers block
 | |
|   // the connection (or of course if there are no registered blockers), then
 | |
|   // the connection is allowed.
 | |
|   //
 | |
|   // Add-ons may also use webrtcUI.on/off to listen to events without
 | |
|   // blocking anything:
 | |
|   //   peer-request-allowed is emitted when a new peer connection is
 | |
|   //                        established (and not blocked).
 | |
|   //   peer-request-blocked is emitted when a peer connection request is
 | |
|   //                        blocked by some blocking connection handler.
 | |
|   //   peer-request-cancel is emitted when a peer-request connection request
 | |
|   //                       is canceled.  (This would typically be used in
 | |
|   //                       conjunction with a blocking handler to cancel
 | |
|   //                       a user prompt or other work done by the handler)
 | |
|   addPeerConnectionBlocker(aCallback) {
 | |
|     this.peerConnectionBlockers.add(aCallback);
 | |
|   },
 | |
| 
 | |
|   removePeerConnectionBlocker(aCallback) {
 | |
|     this.peerConnectionBlockers.delete(aCallback);
 | |
|   },
 | |
| 
 | |
|   on(...args) {
 | |
|     return this.emitter.on(...args);
 | |
|   },
 | |
| 
 | |
|   off(...args) {
 | |
|     return this.emitter.off(...args);
 | |
|   },
 | |
| 
 | |
|   receiveMessage(aMessage) {
 | |
|     switch (aMessage.name) {
 | |
| 
 | |
|       case "rtcpeer:Request": {
 | |
|         let params = Object.freeze(Object.assign({
 | |
|           origin: aMessage.target.contentPrincipal.origin
 | |
|         }, aMessage.data));
 | |
| 
 | |
|         let blockers = Array.from(this.peerConnectionBlockers);
 | |
| 
 | |
|         Task.spawn(function*() {
 | |
|           for (let blocker of blockers) {
 | |
|             try {
 | |
|               let result = yield blocker(params);
 | |
|               if (result == "deny") {
 | |
|                 return false;
 | |
|               }
 | |
|             } catch (err) {
 | |
|               Cu.reportError(`error in PeerConnection blocker: ${err.message}`);
 | |
|             }
 | |
|           }
 | |
|           return true;
 | |
|         }).then(decision => {
 | |
|           let message;
 | |
|           if (decision) {
 | |
|             this.emitter.emit("peer-request-allowed", params);
 | |
|             message = "rtcpeer:Allow";
 | |
|           } else {
 | |
|             this.emitter.emit("peer-request-blocked", params);
 | |
|             message = "rtcpeer:Deny";
 | |
|           }
 | |
| 
 | |
|           aMessage.target.messageManager.sendAsyncMessage(message, {
 | |
|             callID: params.callID,
 | |
|             windowID: params.windowID,
 | |
|           });
 | |
|         });
 | |
|         break;
 | |
|       }
 | |
|       case "rtcpeer:CancelRequest": {
 | |
|         let params = Object.freeze({
 | |
|           origin: aMessage.target.contentPrincipal.origin,
 | |
|           callID: aMessage.data
 | |
|         });
 | |
|         this.emitter.emit("peer-request-cancel", params);
 | |
|         break;
 | |
|       }
 | |
|       case "webrtc:Request":
 | |
|         prompt(aMessage.target, aMessage.data);
 | |
|         break;
 | |
|       case "webrtc:StopRecording":
 | |
|         stopRecording(aMessage.target, aMessage.data);
 | |
|         break;
 | |
|       case "webrtc:CancelRequest":
 | |
|         removePrompt(aMessage.target, aMessage.data);
 | |
|         break;
 | |
|       case "webrtc:UpdatingIndicators":
 | |
|         webrtcUI._streams = [];
 | |
|         break;
 | |
|       case "webrtc:UpdateGlobalIndicators":
 | |
|         updateIndicators(aMessage.data, aMessage.target);
 | |
|         break;
 | |
|       case "webrtc:UpdateBrowserIndicators":
 | |
|         let id = aMessage.data.windowId;
 | |
|         let index;
 | |
|         for (index = 0; index < webrtcUI._streams.length; ++index) {
 | |
|           if (webrtcUI._streams[index].state.windowId == id)
 | |
|             break;
 | |
|         }
 | |
|         // If there's no documentURI, the update is actually a removal of the
 | |
|         // stream, triggered by the recording-window-ended notification.
 | |
|         if (!aMessage.data.documentURI && index < webrtcUI._streams.length)
 | |
|           webrtcUI._streams.splice(index, 1);
 | |
|         else
 | |
|           webrtcUI._streams[index] = {browser: aMessage.target, state: aMessage.data};
 | |
|         let tabbrowser = aMessage.target.ownerGlobal.gBrowser;
 | |
|         if (tabbrowser)
 | |
|           tabbrowser.setBrowserSharing(aMessage.target, aMessage.data);
 | |
|         break;
 | |
|       case "child-process-shutdown":
 | |
|         webrtcUI.processIndicators.delete(aMessage.target);
 | |
|         updateIndicators(null, null);
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| function getBrowserForWindow(aContentWindow) {
 | |
|   return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
 | |
|                        .getInterface(Ci.nsIWebNavigation)
 | |
|                        .QueryInterface(Ci.nsIDocShell)
 | |
|                        .chromeEventHandler;
 | |
| }
 | |
| 
 | |
| function denyRequest(aBrowser, aRequest) {
 | |
|   aBrowser.messageManager.sendAsyncMessage("webrtc:Deny",
 | |
|                                            {callID: aRequest.callID,
 | |
|                                             windowID: aRequest.windowID});
 | |
| }
 | |
| 
 | |
| function getHost(uri, href) {
 | |
|   let host;
 | |
|   try {
 | |
|     if (!uri) {
 | |
|       uri = Services.io.newURI(href);
 | |
|     }
 | |
|     host = uri.host;
 | |
|   } catch (ex) {}
 | |
|   if (!host) {
 | |
|     if (uri && uri.scheme.toLowerCase() == "about") {
 | |
|       // For about URIs, just use the full spec, without any #hash parts.
 | |
|       host = uri.specIgnoringRef;
 | |
|     } else {
 | |
|       // This is unfortunate, but we should display *something*...
 | |
|       const kBundleURI = "chrome://browser/locale/browser.properties";
 | |
|       let bundle = Services.strings.createBundle(kBundleURI);
 | |
|       host = bundle.GetStringFromName("getUserMedia.sharingMenuUnknownHost");
 | |
|     }
 | |
|   }
 | |
|   return host;
 | |
| }
 | |
| 
 | |
| function stopRecording(aBrowser, aRequest) {
 | |
|   let outerWindowID = aBrowser.outerWindowID;
 | |
| 
 | |
|   if (!webrtcUI.activePerms.has(outerWindowID)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!aRequest.rawID) {
 | |
|     webrtcUI.activePerms.delete(outerWindowID);
 | |
|   } else {
 | |
|     let set = webrtcUI.activePerms.get(outerWindowID);
 | |
|     set.delete(aRequest.windowID + aRequest.mediaSource + aRequest.rawID);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function prompt(aBrowser, aRequest) {
 | |
|   let { audioDevices, videoDevices, sharingScreen, sharingAudio,
 | |
|         requestTypes } = aRequest;
 | |
| 
 | |
|   // If the user has already denied access once in this tab,
 | |
|   // deny again without even showing the notification icon.
 | |
|   if ((audioDevices.length && SitePermissions
 | |
|         .get(null, "microphone", aBrowser).state == SitePermissions.BLOCK) ||
 | |
|       (videoDevices.length && SitePermissions
 | |
|         .get(null, sharingScreen ? "screen" : "camera", aBrowser).state == SitePermissions.BLOCK)) {
 | |
|     denyRequest(aBrowser, aRequest);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Tell the browser to refresh the identity block display in case there
 | |
|   // are expired permission states.
 | |
|   aBrowser.dispatchEvent(new aBrowser.ownerGlobal
 | |
|                                      .CustomEvent("PermissionStateChange"));
 | |
| 
 | |
|   let uri = Services.io.newURI(aRequest.documentURI);
 | |
|   let host = getHost(uri);
 | |
|   let chromeDoc = aBrowser.ownerDocument;
 | |
|   let stringBundle = chromeDoc.defaultView.gNavigatorBundle;
 | |
| 
 | |
|   // Mind the order, because for simplicity we're iterating over the list using
 | |
|   // "includes()". This allows the rotation of string identifiers. We list the
 | |
|   // full identifiers here so they can be cross-referenced more easily.
 | |
|   let joinedRequestTypes = requestTypes.join("And");
 | |
|   let stringId = [
 | |
|     // Individual request types first.
 | |
|     "getUserMedia.shareCamera2.message",
 | |
|     "getUserMedia.shareMicrophone2.message",
 | |
|     "getUserMedia.shareScreen3.message",
 | |
|     "getUserMedia.shareAudioCapture2.message",
 | |
|     // Combinations of the above request types last.
 | |
|     "getUserMedia.shareCameraAndMicrophone2.message",
 | |
|     "getUserMedia.shareCameraAndAudioCapture2.message",
 | |
|     "getUserMedia.shareScreenAndMicrophone3.message",
 | |
|     "getUserMedia.shareScreenAndAudioCapture3.message",
 | |
|   ].find(id => id.includes(joinedRequestTypes));
 | |
| 
 | |
|   let message = stringBundle.getFormattedString(stringId, [host]);
 | |
| 
 | |
|   let notification; // Used by action callbacks.
 | |
|   let mainAction = {
 | |
|     label: stringBundle.getString("getUserMedia.allow.label"),
 | |
|     accessKey: stringBundle.getString("getUserMedia.allow.accesskey"),
 | |
|     // The real callback will be set during the "showing" event. The
 | |
|     // empty function here is so that PopupNotifications.show doesn't
 | |
|     // reject the action.
 | |
|     callback() {}
 | |
|   };
 | |
| 
 | |
|   let secondaryActions = [
 | |
|     {
 | |
|       label: stringBundle.getString("getUserMedia.dontAllow.label"),
 | |
|       accessKey: stringBundle.getString("getUserMedia.dontAllow.accesskey"),
 | |
|       callback(aState) {
 | |
|         denyRequest(notification.browser, aRequest);
 | |
|         let scope = SitePermissions.SCOPE_TEMPORARY;
 | |
|         if (aState && aState.checkboxChecked) {
 | |
|           scope = SitePermissions.SCOPE_PERSISTENT;
 | |
|         }
 | |
|         if (audioDevices.length)
 | |
|           SitePermissions.set(uri, "microphone",
 | |
|                               SitePermissions.BLOCK, scope, notification.browser);
 | |
|         if (videoDevices.length)
 | |
|           SitePermissions.set(uri, sharingScreen ? "screen" : "camera",
 | |
|                               SitePermissions.BLOCK, scope, notification.browser);
 | |
|       }
 | |
|     }
 | |
|   ];
 | |
| 
 | |
|   let productName = gBrandBundle.GetStringFromName("brandShortName");
 | |
| 
 | |
|   // Disable the permanent 'Allow' action if the connection isn't secure, or for
 | |
|   // screen/audio sharing (because we can't guess which window the user wants to
 | |
|   // share without prompting).
 | |
|   let reasonForNoPermanentAllow = "";
 | |
|   if (sharingScreen) {
 | |
|     reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.screen2";
 | |
|   } else if (sharingAudio) {
 | |
|     reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.audio";
 | |
|   } else if (!aRequest.secure) {
 | |
|     reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.insecure";
 | |
|   }
 | |
| 
 | |
|   let options = {
 | |
|     persistent: true,
 | |
|     hideClose: !Services.prefs.getBoolPref("privacy.permissionPrompts.showCloseButton"),
 | |
|     checkbox: {
 | |
|       label: stringBundle.getString("getUserMedia.remember"),
 | |
|       checkedState: reasonForNoPermanentAllow ? {
 | |
|         disableMainAction: true,
 | |
|         warningLabel: stringBundle.getFormattedString(reasonForNoPermanentAllow,
 | |
|                                                       [productName])
 | |
|       } : undefined,
 | |
|     },
 | |
|     eventCallback(aTopic, aNewBrowser) {
 | |
|       if (aTopic == "swapping")
 | |
|         return true;
 | |
| 
 | |
|       let doc = this.browser.ownerDocument;
 | |
| 
 | |
|       // Clean-up video streams of screensharing previews.
 | |
|       if ((aTopic == "dismissed" || aTopic == "removed") &&
 | |
|           requestTypes.includes("Screen")) {
 | |
|         let video = doc.getElementById("webRTC-previewVideo");
 | |
|         video.deviceId = undefined;
 | |
|         if (video.stream) {
 | |
|           video.stream.getTracks().forEach(t => t.stop());
 | |
|           video.stream = null;
 | |
|           video.src = null;
 | |
|           doc.getElementById("webRTC-preview").hidden = true;
 | |
|         }
 | |
|         let menupopup = doc.getElementById("webRTC-selectWindow-menupopup");
 | |
|         if (menupopup._commandEventListener) {
 | |
|           menupopup.removeEventListener("command", menupopup._commandEventListener);
 | |
|           menupopup._commandEventListener = null;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (aTopic != "showing")
 | |
|         return false;
 | |
| 
 | |
|       // BLOCK is handled immediately by MediaManager if it has been set
 | |
|       // persistently in the permission manager. If it has been set on the tab,
 | |
|       // it is handled synchronously before we add the notification.
 | |
|       // Handling of ALLOW is delayed until the popupshowing event,
 | |
|       // to avoid granting permissions automatically to background tabs.
 | |
|       if (aRequest.secure) {
 | |
|         let micAllowed =
 | |
|           SitePermissions.get(uri, "microphone").state == SitePermissions.ALLOW;
 | |
|         let camAllowed =
 | |
|           SitePermissions.get(uri, "camera").state == SitePermissions.ALLOW;
 | |
| 
 | |
|         let perms = Services.perms;
 | |
|         let mediaManagerPerm =
 | |
|           perms.testExactPermission(uri, "MediaManagerVideo");
 | |
|         if (mediaManagerPerm) {
 | |
|           perms.remove(uri, "MediaManagerVideo");
 | |
|         }
 | |
| 
 | |
|         // Screen sharing shouldn't follow the camera permissions.
 | |
|         if (videoDevices.length && sharingScreen)
 | |
|           camAllowed = false;
 | |
| 
 | |
|         let activeCamera;
 | |
|         let activeMic;
 | |
| 
 | |
|         for (let device of videoDevices) {
 | |
|           let set = webrtcUI.activePerms.get(aBrowser.outerWindowID);
 | |
|           if (set && set.has(aRequest.windowID + device.mediaSource + device.id)) {
 | |
|             activeCamera = device;
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         for (let device of audioDevices) {
 | |
|           let set = webrtcUI.activePerms.get(aBrowser.outerWindowID);
 | |
|           if (set && set.has(aRequest.windowID + device.mediaSource + device.id)) {
 | |
|             activeMic = device;
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         if ((!audioDevices.length || micAllowed || activeMic) &&
 | |
|             (!videoDevices.length || camAllowed || activeCamera)) {
 | |
|           let allowedDevices = [];
 | |
|           if (videoDevices.length) {
 | |
|             allowedDevices.push((activeCamera || videoDevices[0]).deviceIndex);
 | |
|             Services.perms.add(uri, "MediaManagerVideo",
 | |
|                                Services.perms.ALLOW_ACTION,
 | |
|                                Services.perms.EXPIRE_SESSION);
 | |
|           }
 | |
|           if (audioDevices.length) {
 | |
|             allowedDevices.push((activeMic || audioDevices[0]).deviceIndex);
 | |
|           }
 | |
| 
 | |
|           // Remember on which URIs we found persistent permissions so that we
 | |
|           // can remove them if the user clicks 'Stop Sharing'. There's no
 | |
|           // other way for the stop sharing code to know the hostnames of frames
 | |
|           // using devices until bug 1066082 is fixed.
 | |
|           let browser = this.browser;
 | |
|           browser._devicePermissionURIs = browser._devicePermissionURIs || [];
 | |
|           browser._devicePermissionURIs.push(uri);
 | |
| 
 | |
|           let mm = browser.messageManager;
 | |
|           mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
 | |
|                                                windowID: aRequest.windowID,
 | |
|                                                devices: allowedDevices});
 | |
|           this.remove();
 | |
|           return true;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       function listDevices(menupopup, devices) {
 | |
|         while (menupopup.lastChild)
 | |
|           menupopup.removeChild(menupopup.lastChild);
 | |
|         // Removing the child nodes of the menupopup doesn't clear the value
 | |
|         // attribute of the menulist. This can have unfortunate side effects
 | |
|         // when the list is rebuilt with a different content, so we remove
 | |
|         // the value attribute explicitly.
 | |
|         menupopup.parentNode.removeAttribute("value");
 | |
| 
 | |
|         for (let device of devices)
 | |
|           addDeviceToList(menupopup, device.name, device.deviceIndex);
 | |
|       }
 | |
| 
 | |
|       function listScreenShareDevices(menupopup, devices) {
 | |
|         while (menupopup.lastChild)
 | |
|           menupopup.removeChild(menupopup.lastChild);
 | |
| 
 | |
|         let type = devices[0].mediaSource;
 | |
|         let typeName = type.charAt(0).toUpperCase() + type.substr(1);
 | |
| 
 | |
|         let label = doc.getElementById("webRTC-selectWindow-label");
 | |
|         let gumStringId = "getUserMedia.select" + typeName;
 | |
|         label.setAttribute("value",
 | |
|                            stringBundle.getString(gumStringId + ".label"));
 | |
|         label.setAttribute("accesskey",
 | |
|                            stringBundle.getString(gumStringId + ".accesskey"));
 | |
| 
 | |
|         // "No <type>" is the default because we can't pick a
 | |
|         // 'default' window to share.
 | |
|         addDeviceToList(menupopup,
 | |
|                         stringBundle.getString("getUserMedia.no" + typeName + ".label"),
 | |
|                         "-1");
 | |
|         menupopup.appendChild(doc.createElement("menuseparator"));
 | |
| 
 | |
|         // Build the list of 'devices'.
 | |
|         let monitorIndex = 1;
 | |
|         for (let i = 0; i < devices.length; ++i) {
 | |
|           let device = devices[i];
 | |
| 
 | |
|           let name;
 | |
|           // Building screen list from available screens.
 | |
|           if (type == "screen") {
 | |
|             if (device.name == "Primary Monitor") {
 | |
|               name = stringBundle.getString("getUserMedia.shareEntireScreen.label");
 | |
|             } else {
 | |
|               name = stringBundle.getFormattedString("getUserMedia.shareMonitor.label",
 | |
|                                                      [monitorIndex]);
 | |
|               ++monitorIndex;
 | |
|             }
 | |
|           } else {
 | |
|             name = device.name;
 | |
|             if (type == "application") {
 | |
|               // The application names returned by the platform are of the form:
 | |
|               // <window count>\x1e<application name>
 | |
|               let sepIndex = name.indexOf("\x1e");
 | |
|               let count = name.slice(0, sepIndex);
 | |
|               let sawcStringId = "getUserMedia.shareApplicationWindowCount.label";
 | |
|               name = PluralForm.get(parseInt(count), stringBundle.getString(sawcStringId))
 | |
|                                .replace("#1", name.slice(sepIndex + 1))
 | |
|                                .replace("#2", count);
 | |
|             }
 | |
|           }
 | |
|           let item = addDeviceToList(menupopup, name, i, typeName);
 | |
|           item.deviceId = device.id;
 | |
|           if (device.scary)
 | |
|             item.scary = true;
 | |
|         }
 | |
| 
 | |
|         // Always re-select the "No <type>" item.
 | |
|         doc.getElementById("webRTC-selectWindow-menulist").removeAttribute("value");
 | |
|         doc.getElementById("webRTC-all-windows-shared").hidden = true;
 | |
|         menupopup._commandEventListener = event => {
 | |
|           let video = doc.getElementById("webRTC-previewVideo");
 | |
|           if (video.stream) {
 | |
|             video.stream.getTracks().forEach(t => t.stop());
 | |
|             video.stream = null;
 | |
|           }
 | |
| 
 | |
|           let deviceId = event.target.deviceId;
 | |
|           if (deviceId == undefined) {
 | |
|             doc.getElementById("webRTC-preview").hidden = true;
 | |
|             video.src = null;
 | |
|             return;
 | |
|           }
 | |
| 
 | |
|           let scary = event.target.scary;
 | |
|           let warning = doc.getElementById("webRTC-previewWarning");
 | |
|           warning.hidden = !scary;
 | |
|           let chromeWin = doc.defaultView;
 | |
|           if (scary) {
 | |
|             warning.hidden = false;
 | |
|             let string;
 | |
|             let bundle = chromeWin.gNavigatorBundle;
 | |
| 
 | |
|             let learnMoreText =
 | |
|               bundle.getString("getUserMedia.shareScreen.learnMoreLabel");
 | |
|             let baseURL =
 | |
|               Services.urlFormatter.formatURLPref("app.support.baseURL");
 | |
|             let learnMore =
 | |
|               "<label class='text-link' href='" + baseURL + "screenshare-safety'>" +
 | |
|               learnMoreText + "</label>";
 | |
| 
 | |
|             if (type == "screen") {
 | |
|               string = bundle.getFormattedString("getUserMedia.shareScreenWarning.message",
 | |
|                                                  [learnMore]);
 | |
|             } else {
 | |
|               let brand =
 | |
|                 doc.getElementById("bundle_brand").getString("brandShortName");
 | |
|               string = bundle.getFormattedString("getUserMedia.shareFirefoxWarning.message",
 | |
|                                                  [brand, learnMore]);
 | |
|             }
 | |
|             warning.innerHTML = string;
 | |
|           }
 | |
| 
 | |
|           let perms = Services.perms;
 | |
|           let chromeUri = Services.io.newURI(doc.documentURI);
 | |
|           perms.add(chromeUri, "MediaManagerVideo", perms.ALLOW_ACTION,
 | |
|                     perms.EXPIRE_SESSION);
 | |
| 
 | |
|           video.deviceId = deviceId;
 | |
|           let constraints = { video: { mediaSource: type, deviceId: {exact: deviceId } } };
 | |
|           chromeWin.navigator.mediaDevices.getUserMedia(constraints).then(stream => {
 | |
|             if (video.deviceId != deviceId) {
 | |
|               // The user has selected a different device or closed the panel
 | |
|               // before getUserMedia finished.
 | |
|               stream.getTracks().forEach(t => t.stop());
 | |
|               return;
 | |
|             }
 | |
|             video.src = chromeWin.URL.createObjectURL(stream);
 | |
|             video.stream = stream;
 | |
|             doc.getElementById("webRTC-preview").hidden = false;
 | |
|             video.onloadedmetadata = function(e) {
 | |
|               video.play();
 | |
|             };
 | |
|           });
 | |
|         };
 | |
|         menupopup.addEventListener("command", menupopup._commandEventListener);
 | |
|       }
 | |
| 
 | |
|       function addDeviceToList(menupopup, deviceName, deviceIndex, type) {
 | |
|         let menuitem = doc.createElement("menuitem");
 | |
|         menuitem.setAttribute("value", deviceIndex);
 | |
|         menuitem.setAttribute("label", deviceName);
 | |
|         menuitem.setAttribute("tooltiptext", deviceName);
 | |
|         if (type)
 | |
|           menuitem.setAttribute("devicetype", type);
 | |
|         menupopup.appendChild(menuitem);
 | |
|         return menuitem;
 | |
|       }
 | |
| 
 | |
|       doc.getElementById("webRTC-selectCamera").hidden = !videoDevices.length || sharingScreen;
 | |
|       doc.getElementById("webRTC-selectWindowOrScreen").hidden = !sharingScreen || !videoDevices.length;
 | |
|       doc.getElementById("webRTC-selectMicrophone").hidden = !audioDevices.length || sharingAudio;
 | |
| 
 | |
|       let camMenupopup = doc.getElementById("webRTC-selectCamera-menupopup");
 | |
|       let windowMenupopup = doc.getElementById("webRTC-selectWindow-menupopup");
 | |
|       let micMenupopup = doc.getElementById("webRTC-selectMicrophone-menupopup");
 | |
|       if (sharingScreen)
 | |
|         listScreenShareDevices(windowMenupopup, videoDevices);
 | |
|       else
 | |
|         listDevices(camMenupopup, videoDevices);
 | |
| 
 | |
|       if (!sharingAudio)
 | |
|         listDevices(micMenupopup, audioDevices);
 | |
| 
 | |
|       this.mainAction.callback = function(aState) {
 | |
|         let remember = aState && aState.checkboxChecked;
 | |
|         let allowedDevices = [];
 | |
|         let perms = Services.perms;
 | |
|         if (videoDevices.length) {
 | |
|           let listId = "webRTC-select" + (sharingScreen ? "Window" : "Camera") + "-menulist";
 | |
|           let videoDeviceIndex = doc.getElementById(listId).value;
 | |
|           let allowCamera = videoDeviceIndex != "-1";
 | |
|           if (allowCamera) {
 | |
|             allowedDevices.push(videoDeviceIndex);
 | |
|             // Session permission will be removed after use
 | |
|             // (it's really one-shot, not for the entire session)
 | |
|             perms.add(uri, "MediaManagerVideo", perms.ALLOW_ACTION,
 | |
|                       perms.EXPIRE_SESSION);
 | |
|             if (!webrtcUI.activePerms.has(aBrowser.outerWindowID)) {
 | |
|               webrtcUI.activePerms.set(aBrowser.outerWindowID, new Set());
 | |
|             }
 | |
| 
 | |
|             for (let device of videoDevices) {
 | |
|               if (device.deviceIndex == videoDeviceIndex) {
 | |
|                 webrtcUI.activePerms.get(aBrowser.outerWindowID)
 | |
|                         .add(aRequest.windowID + device.mediaSource + device.id);
 | |
|                 break;
 | |
|               }
 | |
|             }
 | |
|             if (remember)
 | |
|               SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
 | |
|           } else {
 | |
|             let scope = remember ? SitePermissions.SCOPE_PERSISTENT : SitePermissions.SCOPE_TEMPORARY;
 | |
|             SitePermissions.set(uri, "camera", SitePermissions.BLOCK, scope, aBrowser);
 | |
|           }
 | |
|         }
 | |
|         if (audioDevices.length) {
 | |
|           if (!sharingAudio) {
 | |
|             let audioDeviceIndex = doc.getElementById("webRTC-selectMicrophone-menulist").value;
 | |
|             let allowMic = audioDeviceIndex != "-1";
 | |
|             if (allowMic) {
 | |
|               allowedDevices.push(audioDeviceIndex);
 | |
|               if (!webrtcUI.activePerms.has(aBrowser.outerWindowID)) {
 | |
|                 webrtcUI.activePerms.set(aBrowser.outerWindowID, new Set());
 | |
|               }
 | |
| 
 | |
|               for (let device of audioDevices) {
 | |
|                 if (device.deviceIndex == audioDeviceIndex) {
 | |
|                   webrtcUI.activePerms.get(aBrowser.outerWindowID)
 | |
|                           .add(aRequest.windowID + device.mediaSource + device.id);
 | |
|                   break;
 | |
|                 }
 | |
|               }
 | |
|               if (remember)
 | |
|                 SitePermissions.set(uri, "microphone", SitePermissions.ALLOW);
 | |
|             } else {
 | |
|                 let scope = remember ? SitePermissions.SCOPE_PERSISTENT : SitePermissions.SCOPE_TEMPORARY;
 | |
|                 SitePermissions.set(uri, "microphone", SitePermissions.BLOCK, scope, aBrowser);
 | |
|             }
 | |
|           } else {
 | |
|             // Only one device possible for audio capture.
 | |
|             allowedDevices.push(0);
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         if (!allowedDevices.length) {
 | |
|           denyRequest(notification.browser, aRequest);
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         if (remember) {
 | |
|           // Remember on which URIs we set persistent permissions so that we
 | |
|           // can remove them if the user clicks 'Stop Sharing'.
 | |
|           aBrowser._devicePermissionURIs = aBrowser._devicePermissionURIs || [];
 | |
|           aBrowser._devicePermissionURIs.push(uri);
 | |
|         }
 | |
| 
 | |
|         let mm = notification.browser.messageManager;
 | |
|         mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
 | |
|                                              windowID: aRequest.windowID,
 | |
|                                              devices: allowedDevices});
 | |
|       };
 | |
|       return false;
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   let iconType = "Devices";
 | |
|   if (requestTypes.length == 1 && (requestTypes[0] == "Microphone" ||
 | |
|                                    requestTypes[0] == "AudioCapture"))
 | |
|     iconType = "Microphone";
 | |
|   if (requestTypes.includes("Screen"))
 | |
|     iconType = "Screen";
 | |
|   let anchorId = "webRTC-share" + iconType + "-notification-icon";
 | |
| 
 | |
|   let iconClass = iconType.toLowerCase();
 | |
|   if (iconClass == "devices")
 | |
|     iconClass = "camera";
 | |
|   options.popupIconClass = iconClass + "-icon";
 | |
| 
 | |
|   notification =
 | |
|     chromeDoc.defaultView
 | |
|              .PopupNotifications
 | |
|              .show(aBrowser, "webRTC-shareDevices", message,
 | |
|                    anchorId, mainAction, secondaryActions,
 | |
|                    options);
 | |
|   notification.callID = aRequest.callID;
 | |
| }
 | |
| 
 | |
| function removePrompt(aBrowser, aCallId) {
 | |
|   let chromeWin = aBrowser.ownerGlobal;
 | |
|   let notification =
 | |
|     chromeWin.PopupNotifications.getNotification("webRTC-shareDevices", aBrowser);
 | |
|   if (notification && notification.callID == aCallId)
 | |
|     notification.remove();
 | |
| }
 | |
| 
 | |
| function getGlobalIndicator() {
 | |
|   if (AppConstants.platform != "macosx") {
 | |
|     const INDICATOR_CHROME_URI = "chrome://browser/content/webrtcIndicator.xul";
 | |
|     const features = "chrome,dialog=yes,titlebar=no,popup=yes";
 | |
| 
 | |
|     return Services.ww.openWindow(null, INDICATOR_CHROME_URI, "_blank", features, []);
 | |
|   }
 | |
| 
 | |
|   let indicator = {
 | |
|     _camera: null,
 | |
|     _microphone: null,
 | |
|     _screen: null,
 | |
| 
 | |
|     _hiddenDoc: Cc["@mozilla.org/appshell/appShellService;1"]
 | |
|                   .getService(Ci.nsIAppShellService)
 | |
|                   .hiddenDOMWindow.document,
 | |
|     _statusBar: Cc["@mozilla.org/widget/macsystemstatusbar;1"]
 | |
|                   .getService(Ci.nsISystemStatusBar),
 | |
| 
 | |
|     _command(aEvent) {
 | |
|       webrtcUI.showSharingDoorhanger(aEvent.target.stream);
 | |
|     },
 | |
| 
 | |
|     _popupShowing(aEvent) {
 | |
|       let type = this.getAttribute("type");
 | |
|       let activeStreams;
 | |
|       if (type == "Camera") {
 | |
|         activeStreams = webrtcUI.getActiveStreams(true, false, false);
 | |
|       } else if (type == "Microphone") {
 | |
|         activeStreams = webrtcUI.getActiveStreams(false, true, false);
 | |
|       } else if (type == "Screen") {
 | |
|         activeStreams = webrtcUI.getActiveStreams(false, false, true);
 | |
|         type = webrtcUI.showScreenSharingIndicator;
 | |
|       }
 | |
| 
 | |
|       let bundle =
 | |
|         Services.strings.createBundle("chrome://browser/locale/webrtcIndicator.properties");
 | |
| 
 | |
|       if (activeStreams.length == 1) {
 | |
|         let stream = activeStreams[0];
 | |
| 
 | |
|         let menuitem = this.ownerDocument.createElement("menuitem");
 | |
|         let labelId = "webrtcIndicator.sharing" + type + "With.menuitem";
 | |
|         let label = stream.browser.contentTitle || stream.uri;
 | |
|         menuitem.setAttribute("label", bundle.formatStringFromName(labelId, [label], 1));
 | |
|         menuitem.setAttribute("disabled", "true");
 | |
|         this.appendChild(menuitem);
 | |
| 
 | |
|         menuitem = this.ownerDocument.createElement("menuitem");
 | |
|         menuitem.setAttribute("label",
 | |
|                               bundle.GetStringFromName("webrtcIndicator.controlSharing.menuitem"));
 | |
|         menuitem.stream = stream;
 | |
|         menuitem.addEventListener("command", indicator._command);
 | |
| 
 | |
|         this.appendChild(menuitem);
 | |
|         return true;
 | |
|       }
 | |
| 
 | |
|       // We show a different menu when there are several active streams.
 | |
|       let menuitem = this.ownerDocument.createElement("menuitem");
 | |
|       let labelId = "webrtcIndicator.sharing" + type + "WithNTabs.menuitem";
 | |
|       let count = activeStreams.length;
 | |
|       let label = PluralForm.get(count, bundle.GetStringFromName(labelId)).replace("#1", count);
 | |
|       menuitem.setAttribute("label", label);
 | |
|       menuitem.setAttribute("disabled", "true");
 | |
|       this.appendChild(menuitem);
 | |
| 
 | |
|       for (let stream of activeStreams) {
 | |
|         let item = this.ownerDocument.createElement("menuitem");
 | |
|         labelId = "webrtcIndicator.controlSharingOn.menuitem";
 | |
|         label = stream.browser.contentTitle || stream.uri;
 | |
|         item.setAttribute("label", bundle.formatStringFromName(labelId, [label], 1));
 | |
|         item.stream = stream;
 | |
|         item.addEventListener("command", indicator._command);
 | |
|         this.appendChild(item);
 | |
|       }
 | |
| 
 | |
|       return true;
 | |
|     },
 | |
| 
 | |
|     _popupHiding(aEvent) {
 | |
|       while (this.firstChild)
 | |
|         this.firstChild.remove();
 | |
|     },
 | |
| 
 | |
|     _setIndicatorState(aName, aState) {
 | |
|       let field = "_" + aName.toLowerCase();
 | |
|       if (aState && !this[field]) {
 | |
|         let menu = this._hiddenDoc.createElement("menu");
 | |
|         menu.setAttribute("id", "webRTC-sharing" + aName + "-menu");
 | |
| 
 | |
|         // The CSS will only be applied if the menu is actually inserted in the DOM.
 | |
|         this._hiddenDoc.documentElement.appendChild(menu);
 | |
| 
 | |
|         this._statusBar.addItem(menu);
 | |
| 
 | |
|         let menupopup = this._hiddenDoc.createElement("menupopup");
 | |
|         menupopup.setAttribute("type", aName);
 | |
|         menupopup.addEventListener("popupshowing", this._popupShowing);
 | |
|         menupopup.addEventListener("popuphiding", this._popupHiding);
 | |
|         menupopup.addEventListener("command", this._command);
 | |
|         menu.appendChild(menupopup);
 | |
| 
 | |
|         this[field] = menu;
 | |
|       } else if (this[field] && !aState) {
 | |
|         this._statusBar.removeItem(this[field]);
 | |
|         this[field].remove();
 | |
|         this[field] = null
 | |
|       }
 | |
|     },
 | |
|     updateIndicatorState() {
 | |
|       this._setIndicatorState("Camera", webrtcUI.showCameraIndicator);
 | |
|       this._setIndicatorState("Microphone", webrtcUI.showMicrophoneIndicator);
 | |
|       this._setIndicatorState("Screen", webrtcUI.showScreenSharingIndicator);
 | |
|     },
 | |
|     close() {
 | |
|       this._setIndicatorState("Camera", false);
 | |
|       this._setIndicatorState("Microphone", false);
 | |
|       this._setIndicatorState("Screen", false);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   indicator.updateIndicatorState();
 | |
|   return indicator;
 | |
| }
 | |
| 
 | |
| function onTabSharingMenuPopupShowing(e) {
 | |
|   let streams = webrtcUI.getActiveStreams(true, true, true);
 | |
|   for (let streamInfo of streams) {
 | |
|     let stringName = "getUserMedia.sharingMenu";
 | |
|     let types = streamInfo.types;
 | |
|     if (types.camera)
 | |
|       stringName += "Camera";
 | |
|     if (types.microphone)
 | |
|       stringName += "Microphone";
 | |
|     if (types.screen)
 | |
|       stringName += types.screen;
 | |
| 
 | |
|     let doc = e.target.ownerDocument;
 | |
|     let bundle = doc.defaultView.gNavigatorBundle;
 | |
| 
 | |
|     let origin = getHost(null, streamInfo.uri);
 | |
|     let menuitem = doc.createElement("menuitem");
 | |
|     menuitem.setAttribute("label", bundle.getFormattedString(stringName, [origin]));
 | |
|     menuitem.stream = streamInfo;
 | |
|     menuitem.addEventListener("command", onTabSharingMenuPopupCommand);
 | |
|     e.target.appendChild(menuitem);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function onTabSharingMenuPopupHiding(e) {
 | |
|   while (this.lastChild)
 | |
|     this.lastChild.remove();
 | |
| }
 | |
| 
 | |
| function onTabSharingMenuPopupCommand(e) {
 | |
|   webrtcUI.showSharingDoorhanger(e.target.stream);
 | |
| }
 | |
| 
 | |
| function showOrCreateMenuForWindow(aWindow) {
 | |
|   let document = aWindow.document;
 | |
|   let menu = document.getElementById("tabSharingMenu");
 | |
|   if (!menu) {
 | |
|     let stringBundle = aWindow.gNavigatorBundle;
 | |
|     menu = document.createElement("menu");
 | |
|     menu.id = "tabSharingMenu";
 | |
|     let labelStringId = "getUserMedia.sharingMenu.label";
 | |
|     menu.setAttribute("label", stringBundle.getString(labelStringId));
 | |
| 
 | |
|     let container, insertionPoint;
 | |
|     if (AppConstants.platform == "macosx") {
 | |
|       container = document.getElementById("windowPopup");
 | |
|       insertionPoint = document.getElementById("sep-window-list");
 | |
|       let separator = document.createElement("menuseparator");
 | |
|       separator.id = "tabSharingSeparator";
 | |
|       container.insertBefore(separator, insertionPoint);
 | |
|     } else {
 | |
|       let accesskeyStringId = "getUserMedia.sharingMenu.accesskey";
 | |
|       menu.setAttribute("accesskey", stringBundle.getString(accesskeyStringId));
 | |
|       container = document.getElementById("main-menubar");
 | |
|       insertionPoint = document.getElementById("helpMenu");
 | |
|     }
 | |
|     let popup = document.createElement("menupopup");
 | |
|     popup.id = "tabSharingMenuPopup";
 | |
|     popup.addEventListener("popupshowing", onTabSharingMenuPopupShowing);
 | |
|     popup.addEventListener("popuphiding", onTabSharingMenuPopupHiding);
 | |
|     menu.appendChild(popup);
 | |
|     container.insertBefore(menu, insertionPoint);
 | |
|   } else {
 | |
|     menu.hidden = false;
 | |
|     if (AppConstants.platform == "macosx") {
 | |
|       document.getElementById("tabSharingSeparator").hidden = false;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| function maybeAddMenuIndicator(window) {
 | |
|   if (webrtcUI.showGlobalIndicator) {
 | |
|     showOrCreateMenuForWindow(window);
 | |
|   }
 | |
| }
 | |
| 
 | |
| var gIndicatorWindow = null;
 | |
| 
 | |
| function updateIndicators(data, target) {
 | |
|   if (data) {
 | |
|     // the global indicators specific to this process
 | |
|     let indicators;
 | |
|     if (webrtcUI.processIndicators.has(target)) {
 | |
|       indicators = webrtcUI.processIndicators.get(target);
 | |
|     } else {
 | |
|       indicators = {};
 | |
|       webrtcUI.processIndicators.set(target, indicators);
 | |
|     }
 | |
| 
 | |
|     indicators.showGlobalIndicator = data.showGlobalIndicator;
 | |
|     indicators.showCameraIndicator = data.showCameraIndicator;
 | |
|     indicators.showMicrophoneIndicator = data.showMicrophoneIndicator;
 | |
|     indicators.showScreenSharingIndicator = data.showScreenSharingIndicator;
 | |
|   }
 | |
| 
 | |
|   let browserWindowEnum = Services.wm.getEnumerator("navigator:browser");
 | |
|   while (browserWindowEnum.hasMoreElements()) {
 | |
|     let chromeWin = browserWindowEnum.getNext();
 | |
|     if (webrtcUI.showGlobalIndicator) {
 | |
|       showOrCreateMenuForWindow(chromeWin);
 | |
|     } else {
 | |
|       let doc = chromeWin.document;
 | |
|       let existingMenu = doc.getElementById("tabSharingMenu");
 | |
|       if (existingMenu) {
 | |
|         existingMenu.hidden = true;
 | |
|       }
 | |
|       if (AppConstants.platform == "macosx") {
 | |
|         let separator = doc.getElementById("tabSharingSeparator");
 | |
|         if (separator) {
 | |
|           separator.hidden = true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (webrtcUI.showGlobalIndicator) {
 | |
|     if (!gIndicatorWindow)
 | |
|       gIndicatorWindow = getGlobalIndicator();
 | |
|     else
 | |
|       gIndicatorWindow.updateIndicatorState();
 | |
|   } else if (gIndicatorWindow) {
 | |
|     gIndicatorWindow.close();
 | |
|     gIndicatorWindow = null;
 | |
|   }
 | |
| }
 | 
