1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 this.EXPORTED_SYMBOLS = ["webrtcUI"];
9 const Cu = Components.utils;
10 const Cc = Components.classes;
11 const Ci = Components.interfaces;
13 Cu.import("resource://gre/modules/Services.jsm");
14 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
16 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
17 "resource://gre/modules/PluralForm.jsm");
21 Services.obs.addObserver(maybeAddMenuIndicator, "browser-delayed-startup-finished", false);
23 let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
24 .getService(Ci.nsIMessageBroadcaster);
25 ppmm.addMessageListener("webrtc:UpdatingIndicators", this);
26 ppmm.addMessageListener("webrtc:UpdateGlobalIndicators", this);
28 let mm = Cc["@mozilla.org/globalmessagemanager;1"]
29 .getService(Ci.nsIMessageListenerManager);
30 mm.addMessageListener("webrtc:Request", this);
31 mm.addMessageListener("webrtc:CancelRequest", this);
32 mm.addMessageListener("webrtc:UpdateBrowserIndicators", this);
36 Services.obs.removeObserver(maybeAddMenuIndicator, "browser-delayed-startup-finished");
38 let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
39 .getService(Ci.nsIMessageBroadcaster);
40 ppmm.removeMessageListener("webrtc:UpdatingIndicators", this);
41 ppmm.removeMessageListener("webrtc:UpdateGlobalIndicators", this);
43 let mm = Cc["@mozilla.org/globalmessagemanager;1"]
44 .getService(Ci.nsIMessageListenerManager);
45 mm.removeMessageListener("webrtc:Request", this);
46 mm.removeMessageListener("webrtc:CancelRequest", this);
47 mm.removeMessageListener("webrtc:UpdateBrowserIndicators", this);
50 showGlobalIndicator: false,
51 showCameraIndicator: false,
52 showMicrophoneIndicator: false,
53 showScreenSharingIndicator: "", // either "Application", "Screen" or "Window"
56 // The boolean parameters indicate which streams should be included in the result.
57 getActiveStreams: function(aCamera, aMicrophone, aScreen) {
58 return webrtcUI._streams.filter(aStream => {
59 let state = aStream.state;
60 return aCamera && state.camera ||
61 aMicrophone && state.microphone ||
62 aScreen && state.screen;
64 let state = aStream.state;
65 let types = {camera: state.camera, microphone: state.microphone,
66 screen: state.screen};
67 let browser = aStream.browser;
68 let browserWindow = browser.ownerDocument.defaultView;
69 let tab = browserWindow.gBrowser &&
70 browserWindow.gBrowser.getTabForBrowser(browser);
71 return {uri: state.documentURI, tab: tab, browser: browser, types: types};
75 swapBrowserForNotification: function(aOldBrowser, aNewBrowser) {
76 for (let stream of this._streams) {
77 if (stream.browser == aOldBrowser)
78 stream.browser = aNewBrowser;
82 showSharingDoorhanger: function(aActiveStream, aType) {
83 let browserWindow = aActiveStream.browser.ownerDocument.defaultView;
84 if (aActiveStream.tab) {
85 browserWindow.gBrowser.selectedTab = aActiveStream.tab;
87 aActiveStream.browser.focus();
89 browserWindow.focus();
90 let PopupNotifications = browserWindow.PopupNotifications;
91 let notif = PopupNotifications.getNotification("webRTC-sharing" + aType,
92 aActiveStream.browser);
94 if (!Services.focus.activeWindow) {
95 browserWindow.addEventListener("activate", function onActivate() {
96 browserWindow.removeEventListener("activate", onActivate);
97 Services.tm.mainThread.dispatch(function() {
99 }, Ci.nsIThread.DISPATCH_NORMAL);
101 Cc["@mozilla.org/widget/macdocksupport;1"].getService(Ci.nsIMacDockSupport)
102 .activateApplication(true);
109 updateMainActionLabel: function(aMenuList) {
110 let type = aMenuList.selectedItem.getAttribute("devicetype");
111 let document = aMenuList.ownerDocument;
112 document.getElementById("webRTC-all-windows-shared").hidden = type != "Screen";
114 // If we are also requesting audio in addition to screen sharing,
115 // always use a generic label.
116 if (!document.getElementById("webRTC-selectMicrophone").hidden)
119 let bundle = document.defaultView.gNavigatorBundle;
120 let stringId = "getUserMedia.share" + (type || "SelectedItems") + ".label";
121 let popupnotification = aMenuList.parentNode.parentNode;
122 popupnotification.setAttribute("buttonlabel", bundle.getString(stringId));
125 receiveMessage: function(aMessage) {
126 switch (aMessage.name) {
127 case "webrtc:Request":
128 prompt(aMessage.target, aMessage.data);
130 case "webrtc:CancelRequest":
131 removePrompt(aMessage.target, aMessage.data);
133 case "webrtc:UpdatingIndicators":
134 webrtcUI._streams = [];
136 case "webrtc:UpdateGlobalIndicators":
137 updateIndicators(aMessage.data)
139 case "webrtc:UpdateBrowserIndicators":
140 webrtcUI._streams.push({browser: aMessage.target, state: aMessage.data});
141 updateBrowserSpecificIndicator(aMessage.target, aMessage.data);
147 function getBrowserForWindow(aContentWindow) {
148 return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
149 .getInterface(Ci.nsIWebNavigation)
150 .QueryInterface(Ci.nsIDocShell)
154 function denyRequest(aBrowser, aRequest) {
155 aBrowser.messageManager.sendAsyncMessage("webrtc:Deny",
156 {callID: aRequest.callID,
157 windowID: aRequest.windowID});
160 function getHost(uri, href) {
164 uri = Services.io.newURI(href, null, null);
169 if (uri && uri.scheme.toLowerCase() == "about") {
170 // For about URIs, just use the full spec, without any #hash parts
171 host = uri.specIgnoringRef;
173 // This is unfortunate, but we should display *something*...
174 const kBundleURI = "chrome://browser/locale/browser.properties";
175 let bundle = Services.strings.createBundle(kBundleURI);
176 host = bundle.GetStringFromName("getUserMedia.sharingMenuUnknownHost");
182 function prompt(aBrowser, aRequest) {
183 let {audioDevices: audioDevices, videoDevices: videoDevices,
184 sharingScreen: sharingScreen, requestTypes: requestTypes} = aRequest;
185 let uri = Services.io.newURI(aRequest.documentURI, null, null);
186 let host = getHost(uri);
187 let chromeDoc = aBrowser.ownerDocument;
188 let chromeWin = chromeDoc.defaultView;
189 let stringBundle = chromeWin.gNavigatorBundle;
190 let stringId = "getUserMedia.share" + requestTypes.join("And") + ".message";
191 let message = stringBundle.getFormattedString(stringId, [host]);
195 mainLabel = stringBundle.getString("getUserMedia.shareSelectedItems.label");
198 let string = stringBundle.getString("getUserMedia.shareSelectedDevices.label");
199 mainLabel = PluralForm.get(requestTypes.length, string);
202 let notification; // Used by action callbacks.
205 accessKey: stringBundle.getString("getUserMedia.shareSelectedDevices.accesskey"),
206 // The real callback will be set during the "showing" event. The
207 // empty function here is so that PopupNotifications.show doesn't
208 // reject the action.
209 callback: function() {}
212 let secondaryActions = [
214 label: stringBundle.getString("getUserMedia.denyRequest.label"),
215 accessKey: stringBundle.getString("getUserMedia.denyRequest.accesskey"),
216 callback: function () {
217 denyRequest(notification.browser, aRequest);
222 if (!sharingScreen) { // Bug 1037438: implement 'never' for screen sharing.
223 secondaryActions.push({
224 label: stringBundle.getString("getUserMedia.never.label"),
225 accessKey: stringBundle.getString("getUserMedia.never.accesskey"),
226 callback: function () {
227 denyRequest(notification.browser, aRequest);
228 // Let someone save "Never" for http sites so that they can be stopped from
229 // bothering you with doorhangers.
230 let perms = Services.perms;
231 if (audioDevices.length)
232 perms.add(uri, "microphone", perms.DENY_ACTION);
233 if (videoDevices.length)
234 perms.add(uri, "camera", perms.DENY_ACTION);
239 if (aRequest.secure && !sharingScreen) {
240 // Don't show the 'Always' action if the connection isn't secure, or for
241 // screen sharing (because we can't guess which window the user wants to
242 // share without prompting).
243 secondaryActions.unshift({
244 label: stringBundle.getString("getUserMedia.always.label"),
245 accessKey: stringBundle.getString("getUserMedia.always.accesskey"),
246 callback: function () {
247 mainAction.callback(true);
253 eventCallback: function(aTopic, aNewBrowser) {
254 if (aTopic == "swapping")
257 let chromeDoc = this.browser.ownerDocument;
259 if (aTopic == "shown") {
260 let PopupNotifications = chromeDoc.defaultView.PopupNotifications;
261 let popupId = "Devices";
262 if (requestTypes.length == 1 && requestTypes[0] == "Microphone")
263 popupId = "Microphone";
264 if (requestTypes.indexOf("Screen") != -1)
266 PopupNotifications.panel.firstChild.setAttribute("popupid", "webRTC-share" + popupId);
269 if (aTopic != "showing")
272 // DENY_ACTION is handled immediately by MediaManager, but handling
273 // of ALLOW_ACTION is delayed until the popupshowing event
274 // to avoid granting permissions automatically to background tabs.
275 if (aRequest.secure) {
276 let perms = Services.perms;
278 let micPerm = perms.testExactPermission(uri, "microphone");
279 if (micPerm == perms.PROMPT_ACTION)
280 micPerm = perms.UNKNOWN_ACTION;
282 let camPerm = perms.testExactPermission(uri, "camera");
283 if (camPerm == perms.PROMPT_ACTION)
284 camPerm = perms.UNKNOWN_ACTION;
286 // Screen sharing shouldn't follow the camera permissions.
287 if (videoDevices.length && sharingScreen)
288 camPerm = perms.UNKNOWN_ACTION;
290 // We don't check that permissions are set to ALLOW_ACTION in this
291 // test; only that they are set. This is because if audio is allowed
292 // and video is denied persistently, we don't want to show the prompt,
293 // and will grant audio access immediately.
294 if ((!audioDevices.length || micPerm) && (!videoDevices.length || camPerm)) {
295 // All permissions we were about to request are already persistently set.
296 let allowedDevices = [];
297 if (videoDevices.length && camPerm == perms.ALLOW_ACTION)
298 allowedDevices.push(videoDevices[0].deviceIndex);
299 if (audioDevices.length && micPerm == perms.ALLOW_ACTION)
300 allowedDevices.push(audioDevices[0].deviceIndex);
301 let mm = this.browser.messageManager;
302 mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
303 windowID: aRequest.windowID,
304 devices: allowedDevices});
310 function listDevices(menupopup, devices) {
311 while (menupopup.lastChild)
312 menupopup.removeChild(menupopup.lastChild);
314 for (let device of devices)
315 addDeviceToList(menupopup, device.name, device.deviceIndex);
318 function listScreenShareDevices(menupopup, devices) {
319 while (menupopup.lastChild)
320 menupopup.removeChild(menupopup.lastChild);
322 let type = devices[0].mediaSource;
323 let typeName = type.charAt(0).toUpperCase() + type.substr(1);
325 let label = chromeDoc.getElementById("webRTC-selectWindow-label");
326 let stringId = "getUserMedia.select" + typeName;
327 label.setAttribute("value",
328 stringBundle.getString(stringId + ".label"));
329 label.setAttribute("accesskey",
330 stringBundle.getString(stringId + ".accesskey"));
332 // "No <type>" is the default because we can't pick a
333 // 'default' window to share.
334 addDeviceToList(menupopup,
335 stringBundle.getString("getUserMedia.no" + typeName + ".label"),
337 menupopup.appendChild(chromeDoc.createElement("menuseparator"));
339 // Build the list of 'devices'.
340 for (let i = 0; i < devices.length; ++i) {
342 // Screen has a special treatment because we currently only support
343 // sharing the primary screen and want to display a localized string.
344 if (type == "screen") {
345 name = stringBundle.getString("getUserMedia.shareEntireScreen.label");
348 name = devices[i].name;
349 if (type == "application") {
350 // The application names returned by the platform are of the form:
351 // <window count>\x1e<application name>
352 let sepIndex = name.indexOf("\x1e");
353 let count = name.slice(0, sepIndex);
354 let stringId = "getUserMedia.shareApplicationWindowCount.label";
355 name = PluralForm.get(parseInt(count), stringBundle.getString(stringId))
356 .replace("#1", name.slice(sepIndex + 1))
357 .replace("#2", count);
360 addDeviceToList(menupopup, name, i, typeName);
363 // Always re-select the "No <type>" item.
364 chromeDoc.getElementById("webRTC-selectWindow-menulist").removeAttribute("value");
365 chromeDoc.getElementById("webRTC-all-windows-shared").hidden = true;
368 function addDeviceToList(menupopup, deviceName, deviceIndex, type) {
369 let menuitem = chromeDoc.createElement("menuitem");
370 menuitem.setAttribute("value", deviceIndex);
371 menuitem.setAttribute("label", deviceName);
372 menuitem.setAttribute("tooltiptext", deviceName);
374 menuitem.setAttribute("devicetype", type);
375 menupopup.appendChild(menuitem);
378 chromeDoc.getElementById("webRTC-selectCamera").hidden = !videoDevices.length || sharingScreen;
379 chromeDoc.getElementById("webRTC-selectWindowOrScreen").hidden = !sharingScreen || !videoDevices.length;
380 chromeDoc.getElementById("webRTC-selectMicrophone").hidden = !audioDevices.length;
382 let camMenupopup = chromeDoc.getElementById("webRTC-selectCamera-menupopup");
383 let windowMenupopup = chromeDoc.getElementById("webRTC-selectWindow-menupopup");
384 let micMenupopup = chromeDoc.getElementById("webRTC-selectMicrophone-menupopup");
386 listScreenShareDevices(windowMenupopup, videoDevices);
388 listDevices(camMenupopup, videoDevices);
389 listDevices(micMenupopup, audioDevices);
390 if (requestTypes.length == 2) {
391 let stringBundle = chromeDoc.defaultView.gNavigatorBundle;
393 addDeviceToList(camMenupopup, stringBundle.getString("getUserMedia.noVideo.label"), "-1");
394 addDeviceToList(micMenupopup, stringBundle.getString("getUserMedia.noAudio.label"), "-1");
397 this.mainAction.callback = function(aRemember) {
398 let allowedDevices = [];
399 let perms = Services.perms;
400 if (videoDevices.length) {
401 let listId = "webRTC-select" + (sharingScreen ? "Window" : "Camera") + "-menulist";
402 let videoDeviceIndex = chromeDoc.getElementById(listId).value;
403 let allowCamera = videoDeviceIndex != "-1";
405 allowedDevices.push(videoDeviceIndex);
407 perms.add(uri, "camera",
408 allowCamera ? perms.ALLOW_ACTION : perms.DENY_ACTION);
411 if (audioDevices.length) {
412 let audioDeviceIndex = chromeDoc.getElementById("webRTC-selectMicrophone-menulist").value;
413 let allowMic = audioDeviceIndex != "-1";
415 allowedDevices.push(audioDeviceIndex);
417 perms.add(uri, "microphone",
418 allowMic ? perms.ALLOW_ACTION : perms.DENY_ACTION);
422 if (!allowedDevices.length) {
423 denyRequest(notification.browser, aRequest);
427 let mm = notification.browser.messageManager
428 mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
429 windowID: aRequest.windowID,
430 devices: allowedDevices});
436 let anchorId = "webRTC-shareDevices-notification-icon";
437 if (requestTypes.length == 1 && requestTypes[0] == "Microphone")
438 anchorId = "webRTC-shareMicrophone-notification-icon";
439 if (requestTypes.indexOf("Screen") != -1)
440 anchorId = "webRTC-shareScreen-notification-icon";
442 chromeWin.PopupNotifications.show(aBrowser, "webRTC-shareDevices", message,
443 anchorId, mainAction, secondaryActions,
445 notification.callID = aRequest.callID;
448 function removePrompt(aBrowser, aCallId) {
449 let chromeWin = aBrowser.ownerDocument.defaultView;
451 chromeWin.PopupNotifications.getNotification("webRTC-shareDevices", aBrowser);
452 if (notification && notification.callID == aCallId)
453 notification.remove();
456 function getGlobalIndicator() {
458 const INDICATOR_CHROME_URI = "chrome://browser/content/webrtcIndicator.xul";
459 const features = "chrome,dialog=yes,titlebar=no,popup=yes";
461 return Services.ww.openWindow(null, INDICATOR_CHROME_URI, "_blank", features, []);
468 _hiddenDoc: Cc["@mozilla.org/appshell/appShellService;1"]
469 .getService(Ci.nsIAppShellService)
470 .hiddenDOMWindow.document,
471 _statusBar: Cc["@mozilla.org/widget/macsystemstatusbar;1"]
472 .getService(Ci.nsISystemStatusBar),
474 _command: function(aEvent) {
475 let type = this.getAttribute("type");
476 if (type == "Camera" || type == "Microphone")
478 else if (type == "Window" || type == "Application")
480 webrtcUI.showSharingDoorhanger(aEvent.target.stream, type);
483 _popupShowing: function(aEvent) {
484 let type = this.getAttribute("type");
486 if (type == "Camera") {
487 activeStreams = webrtcUI.getActiveStreams(true, false, false);
489 else if (type == "Microphone") {
490 activeStreams = webrtcUI.getActiveStreams(false, true, false);
492 else if (type == "Screen") {
493 activeStreams = webrtcUI.getActiveStreams(false, false, true);
494 type = webrtcUI.showScreenSharingIndicator;
498 Services.strings.createBundle("chrome://browser/locale/webrtcIndicator.properties");
500 if (activeStreams.length == 1) {
501 let stream = activeStreams[0];
503 let menuitem = this.ownerDocument.createElement("menuitem");
504 let labelId = "webrtcIndicator.sharing" + type + "With.menuitem";
505 let label = stream.browser.contentTitle || stream.uri;
506 menuitem.setAttribute("label", bundle.formatStringFromName(labelId, [label], 1));
507 menuitem.setAttribute("disabled", "true");
508 this.appendChild(menuitem);
510 menuitem = this.ownerDocument.createElement("menuitem");
511 menuitem.setAttribute("label",
512 bundle.GetStringFromName("webrtcIndicator.controlSharing.menuitem"));
513 menuitem.setAttribute("type", type);
514 menuitem.stream = stream;
515 menuitem.addEventListener("command", indicator._command);
517 this.appendChild(menuitem);
521 // We show a different menu when there are several active streams.
522 let menuitem = this.ownerDocument.createElement("menuitem");
523 let labelId = "webrtcIndicator.sharing" + type + "WithNTabs.menuitem";
524 let count = activeStreams.length;
525 let label = PluralForm.get(count, bundle.GetStringFromName(labelId)).replace("#1", count);
526 menuitem.setAttribute("label", label);
527 menuitem.setAttribute("disabled", "true");
528 this.appendChild(menuitem);
530 for (let stream of activeStreams) {
531 let item = this.ownerDocument.createElement("menuitem");
532 let labelId = "webrtcIndicator.controlSharingOn.menuitem";
533 let label = stream.browser.contentTitle || stream.uri;
534 item.setAttribute("label", bundle.formatStringFromName(labelId, [label], 1));
535 item.setAttribute("type", type);
536 item.stream = stream;
537 item.addEventListener("command", indicator._command);
538 this.appendChild(item);
544 _popupHiding: function(aEvent) {
545 while (this.firstChild)
546 this.firstChild.remove();
549 _setIndicatorState: function(aName, aState) {
550 let field = "_" + aName.toLowerCase();
551 if (aState && !this[field]) {
552 let menu = this._hiddenDoc.createElement("menu");
553 menu.setAttribute("id", "webRTC-sharing" + aName + "-menu");
555 // The CSS will only be applied if the menu is actually inserted in the DOM.
556 this._hiddenDoc.documentElement.appendChild(menu);
558 this._statusBar.addItem(menu);
560 let menupopup = this._hiddenDoc.createElement("menupopup");
561 menupopup.setAttribute("type", aName);
562 menupopup.addEventListener("popupshowing", this._popupShowing);
563 menupopup.addEventListener("popuphiding", this._popupHiding);
564 menupopup.addEventListener("command", this._command);
565 menu.appendChild(menupopup);
569 else if (this[field] && !aState) {
570 this._statusBar.removeItem(this[field]);
571 this[field].remove();
575 updateIndicatorState: function() {
576 this._setIndicatorState("Camera", webrtcUI.showCameraIndicator);
577 this._setIndicatorState("Microphone", webrtcUI.showMicrophoneIndicator);
578 this._setIndicatorState("Screen", webrtcUI.showScreenSharingIndicator);
581 this._setIndicatorState("Camera", false);
582 this._setIndicatorState("Microphone", false);
583 this._setIndicatorState("Screen", false);
587 indicator.updateIndicatorState();
592 function onTabSharingMenuPopupShowing(e) {
593 let streams = webrtcUI.getActiveStreams(true, true, true);
594 for (let streamInfo of streams) {
595 let stringName = "getUserMedia.sharingMenu";
596 let types = streamInfo.types;
598 stringName += "Camera";
599 if (types.microphone)
600 stringName += "Microphone";
602 stringName += types.screen;
604 let doc = e.target.ownerDocument;
605 let bundle = doc.defaultView.gNavigatorBundle;
607 let origin = getHost(null, streamInfo.uri);
608 let menuitem = doc.createElement("menuitem");
609 menuitem.setAttribute("label", bundle.getFormattedString(stringName, [origin]));
610 menuitem.stream = streamInfo;
612 // We can only open 1 doorhanger at a time. Guessing that users would be
613 // most eager to control screen/window/app sharing, and only then
614 // camera/microphone sharing, in that (decreasing) order of priority.
616 if ((/Screen|Window|Application/).test(stringName)) {
617 doorhangerType = "Screen";
619 doorhangerType = "Devices";
621 menuitem.setAttribute("doorhangertype", doorhangerType);
622 menuitem.addEventListener("command", onTabSharingMenuPopupCommand);
623 e.target.appendChild(menuitem);
627 function onTabSharingMenuPopupHiding(e) {
628 while (this.lastChild)
629 this.lastChild.remove();
632 function onTabSharingMenuPopupCommand(e) {
633 let type = e.target.getAttribute("doorhangertype");
634 webrtcUI.showSharingDoorhanger(e.target.stream, type);
637 function showOrCreateMenuForWindow(aWindow) {
638 let document = aWindow.document;
639 let menu = document.getElementById("tabSharingMenu");
641 let stringBundle = aWindow.gNavigatorBundle;
642 menu = document.createElement("menu");
643 menu.id = "tabSharingMenu";
644 let labelStringId = "getUserMedia.sharingMenu.label";
645 menu.setAttribute("label", stringBundle.getString(labelStringId));
647 let container = document.getElementById("windowPopup");
648 let insertionPoint = document.getElementById("sep-window-list");
649 let separator = document.createElement("menuseparator");
650 separator.id = "tabSharingSeparator";
651 container.insertBefore(separator, insertionPoint);
653 let accesskeyStringId = "getUserMedia.sharingMenu.accesskey";
654 menu.setAttribute("accesskey", stringBundle.getString(accesskeyStringId));
655 let container = document.getElementById("main-menubar");
656 let insertionPoint = document.getElementById("helpMenu");
658 let popup = document.createElement("menupopup");
659 popup.id = "tabSharingMenuPopup";
660 popup.addEventListener("popupshowing", onTabSharingMenuPopupShowing);
661 popup.addEventListener("popuphiding", onTabSharingMenuPopupHiding);
662 menu.appendChild(popup);
663 container.insertBefore(menu, insertionPoint);
667 document.getElementById("tabSharingSeparator").hidden = false;
672 function maybeAddMenuIndicator(window) {
673 if (webrtcUI.showGlobalIndicator) {
674 showOrCreateMenuForWindow(window);
678 var gIndicatorWindow = null;
680 function updateIndicators(data) {
681 webrtcUI.showGlobalIndicator = data.showGlobalIndicator;
682 webrtcUI.showCameraIndicator = data.showCameraIndicator;
683 webrtcUI.showMicrophoneIndicator = data.showMicrophoneIndicator;
684 webrtcUI.showScreenSharingIndicator = data.showScreenSharingIndicator;
686 let browserWindowEnum = Services.wm.getEnumerator("navigator:browser");
687 while (browserWindowEnum.hasMoreElements()) {
688 let chromeWin = browserWindowEnum.getNext();
689 if (webrtcUI.showGlobalIndicator) {
690 showOrCreateMenuForWindow(chromeWin);
692 let doc = chromeWin.document;
693 let existingMenu = doc.getElementById("tabSharingMenu");
695 existingMenu.hidden = true;
698 let separator = doc.getElementById("tabSharingSeparator");
700 separator.hidden = true;
706 if (webrtcUI.showGlobalIndicator) {
707 if (!gIndicatorWindow)
708 gIndicatorWindow = getGlobalIndicator();
710 gIndicatorWindow.updateIndicatorState();
711 } else if (gIndicatorWindow) {
712 gIndicatorWindow.close();
713 gIndicatorWindow = null;
717 function updateBrowserSpecificIndicator(aBrowser, aState) {
719 if (aState.camera && aState.microphone) {
720 captureState = "CameraAndMicrophone";
721 } else if (aState.camera) {
722 captureState = "Camera";
723 } else if (aState.microphone) {
724 captureState = "Microphone";
727 let chromeWin = aBrowser.ownerDocument.defaultView;
728 let stringBundle = chromeWin.gNavigatorBundle;
730 let windowId = aState.windowId;
731 let notification; // Used by action callbacks.
733 label: stringBundle.getString("getUserMedia.continueSharing.label"),
734 accessKey: stringBundle.getString("getUserMedia.continueSharing.accesskey"),
735 callback: function () {},
738 let secondaryActions = [{
739 label: stringBundle.getString("getUserMedia.stopSharing.label"),
740 accessKey: stringBundle.getString("getUserMedia.stopSharing.accesskey"),
741 callback: function () {
742 let uri = Services.io.newURI(aState.documentURI, null, null);
743 let host = getHost(uri);
744 let perms = Services.perms;
746 perms.testExactPermission(uri, "camera") == perms.ALLOW_ACTION)
747 perms.remove(host, "camera");
748 if (aState.microphone &&
749 perms.testExactPermission(uri, "microphone") == perms.ALLOW_ACTION)
750 perms.remove(host, "microphone");
752 let mm = notification.browser.messageManager;
753 mm.sendAsyncMessage("webrtc:StopSharing", windowId);
759 eventCallback: function(aTopic, aNewBrowser) {
760 if (aTopic == "shown") {
761 let PopupNotifications = this.browser.ownerDocument.defaultView.PopupNotifications;
762 let popupId = captureState == "Microphone" ? "Microphone" : "Devices";
763 PopupNotifications.panel.firstChild.setAttribute("popupid", "webRTC-sharing" + popupId);
766 if (aTopic == "swapping") {
767 webrtcUI.swapBrowserForNotification(this.browser, aNewBrowser);
775 let anchorId = captureState == "Microphone" ? "webRTC-sharingMicrophone-notification-icon"
776 : "webRTC-sharingDevices-notification-icon";
777 let message = stringBundle.getString("getUserMedia.sharing" + captureState + ".message2");
779 chromeWin.PopupNotifications.show(aBrowser, "webRTC-sharingDevices", message,
780 anchorId, mainAction, secondaryActions, options);
783 removeBrowserNotification(aBrowser,"webRTC-sharingDevices");
786 // Now handle the screen sharing indicator.
787 if (!aState.screen) {
788 removeBrowserNotification(aBrowser,"webRTC-sharingScreen");
792 let screenSharingNotif; // Used by action callbacks.
796 eventCallback: function(aTopic, aNewBrowser) {
797 if (aTopic == "shown") {
798 let PopupNotifications = this.browser.ownerDocument.defaultView.PopupNotifications;
799 PopupNotifications.panel.firstChild.setAttribute("popupid", "webRTC-sharingScreen");
802 if (aTopic == "swapping") {
803 webrtcUI.swapBrowserForNotification(this.browser, aNewBrowser);
810 secondaryActions = [{
811 label: stringBundle.getString("getUserMedia.stopSharing.label"),
812 accessKey: stringBundle.getString("getUserMedia.stopSharing.accesskey"),
813 callback: function () {
814 let mm = screenSharingNotif.browser.messageManager;
815 mm.sendAsyncMessage("webrtc:StopSharing", "screen:" + windowId);
818 // If we are sharing both a window and the screen, we show 'Screen'.
819 let stringId = "getUserMedia.sharing" + aState.screen;
821 chromeWin.PopupNotifications.show(aBrowser, "webRTC-sharingScreen",
822 stringBundle.getString(stringId + ".message"),
823 "webRTC-sharingScreen-notification-icon",
824 mainAction, secondaryActions, options);
827 function removeBrowserNotification(aBrowser, aNotificationId) {
828 let win = aBrowser.ownerDocument.defaultView;
830 win.PopupNotifications.getNotification(aNotificationId, aBrowser);
832 win.PopupNotifications.remove(notification);