forked from mirrors/gecko-dev
		
	 656ef8b2ef
			
		
	
	
		656ef8b2ef
		
	
	
	
	
		
			
			The indicator never worked well on Wayland as the protocol gives us little control about window positioning, focus etc. by design, creating a quite bad user experience. At the same time Wayland puts constrains on recording the display, requiring apps to go through a "portal" where they usually have to actively select the screen or window to share. This, crucially, allows system compositors to show indicators itself - which all major DEs do or at least support. Unfortunately we can't disable the indicator at build-time as our builds support both Wayland and X11, thus make it runtime detectable. Differential Revision: https://phabricator.services.mozilla.com/D184193
		
			
				
	
	
		
			595 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			595 lines
		
	
	
	
		
			18 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/. */
 | |
| 
 | |
| const { XPCOMUtils } = ChromeUtils.importESModule(
 | |
|   "resource://gre/modules/XPCOMUtils.sys.mjs"
 | |
| );
 | |
| const { AppConstants } = ChromeUtils.importESModule(
 | |
|   "resource://gre/modules/AppConstants.sys.mjs"
 | |
| );
 | |
| const { showStreamSharingMenu, webrtcUI } = ChromeUtils.importESModule(
 | |
|   "resource:///modules/webrtcUI.sys.mjs"
 | |
| );
 | |
| 
 | |
| ChromeUtils.defineESModuleGetters(this, {
 | |
|   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
 | |
|   MacOSWebRTCStatusbarIndicator: "resource:///modules/webrtcUI.sys.mjs",
 | |
| });
 | |
| 
 | |
| XPCOMUtils.defineLazyServiceGetter(
 | |
|   this,
 | |
|   "gScreenManager",
 | |
|   "@mozilla.org/gfx/screenmanager;1",
 | |
|   "nsIScreenManager"
 | |
| );
 | |
| 
 | |
| /**
 | |
|  * Public function called by webrtcUI to update the indicator
 | |
|  * display when the active streams change.
 | |
|  */
 | |
| function updateIndicatorState() {
 | |
|   WebRTCIndicator.updateIndicatorState();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Public function called by webrtcUI to indicate that webrtcUI
 | |
|  * is about to close the indicator. This is so that we can differentiate
 | |
|  * between closes that are caused by webrtcUI, and closes that are
 | |
|  * caused by other reasons (like the user closing the window via the
 | |
|  * OS window controls somehow).
 | |
|  *
 | |
|  * If the window is closed without having called this method first, the
 | |
|  * indicator will ask webrtcUI to shutdown any remaining streams and then
 | |
|  * select and focus the most recent browser tab that a stream was shared
 | |
|  * with.
 | |
|  */
 | |
| function closingInternally() {
 | |
|   WebRTCIndicator.closingInternally();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Main control object for the WebRTC global indicator
 | |
|  */
 | |
| const WebRTCIndicator = {
 | |
|   init(event) {
 | |
|     addEventListener("load", this);
 | |
|     addEventListener("unload", this);
 | |
| 
 | |
|     // If the user customizes the position of the indicator, we will
 | |
|     // not try to re-center it on the primary display after indicator
 | |
|     // state updates.
 | |
|     this.positionCustomized = false;
 | |
| 
 | |
|     this.updatingIndicatorState = false;
 | |
|     this.loaded = false;
 | |
|     this.isClosingInternally = false;
 | |
| 
 | |
|     this.statusBar = null;
 | |
|     this.statusBarMenus = new Set();
 | |
| 
 | |
|     this.showGlobalMuteToggles = Services.prefs.getBoolPref(
 | |
|       "privacy.webrtc.globalMuteToggles",
 | |
|       false
 | |
|     );
 | |
| 
 | |
|     this.hideGlobalIndicator =
 | |
|       Services.prefs.getBoolPref("privacy.webrtc.hideGlobalIndicator", false) ||
 | |
|       Services.appinfo.isWayland;
 | |
| 
 | |
|     if (this.hideGlobalIndicator) {
 | |
|       this.setVisibility(false);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Controls the visibility of the global indicator. Also sets the value of
 | |
|    * a "visible" attribute on the document element to "true" or "false".
 | |
|    *
 | |
|    * @param isVisible (boolean)
 | |
|    *   Whether or not the global indicator should be visible.
 | |
|    */
 | |
|   setVisibility(isVisible) {
 | |
|     let baseWin = window.docShell.treeOwner.QueryInterface(Ci.nsIBaseWindow);
 | |
|     baseWin.visibility = isVisible;
 | |
|     // AppWindow::GetVisibility _always_ returns true (see
 | |
|     // https://bugzilla.mozilla.org/show_bug.cgi?id=306245), so we'll set an
 | |
|     // attribute on the document to make it easier for tests to know that the
 | |
|     // indicator is not visible.
 | |
|     document.documentElement.setAttribute("visible", isVisible);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Exposed externally so that webrtcUI can alert the indicator to
 | |
|    * update itself when sharing states have changed.
 | |
|    */
 | |
|   updateIndicatorState() {
 | |
|     // It's possible that we were called externally before the indicator
 | |
|     // finished loading. If so, then bail out - we're going to call
 | |
|     // updateIndicatorState ourselves automatically once the load
 | |
|     // event fires.
 | |
|     if (!this.loaded) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // We've started to update the indicator state. We set this flag so
 | |
|     // that the MozUpdateWindowPos event handler doesn't interpret indicator
 | |
|     // state updates as window movement caused by the user.
 | |
|     this.updatingIndicatorState = true;
 | |
| 
 | |
|     let showCameraIndicator = webrtcUI.showCameraIndicator;
 | |
|     let showMicrophoneIndicator = webrtcUI.showMicrophoneIndicator;
 | |
|     let showScreenSharingIndicator = webrtcUI.showScreenSharingIndicator;
 | |
|     if (this.statusBar) {
 | |
|       let statusMenus = new Map([
 | |
|         ["Camera", showCameraIndicator],
 | |
|         ["Microphone", showMicrophoneIndicator],
 | |
|         ["Screen", showScreenSharingIndicator],
 | |
|       ]);
 | |
| 
 | |
|       for (let [name, shouldShow] of statusMenus) {
 | |
|         let menu = document.getElementById(`webRTC-sharing${name}-menu`);
 | |
|         if (shouldShow && !this.statusBarMenus.has(menu)) {
 | |
|           this.statusBar.addItem(menu);
 | |
|           this.statusBarMenus.add(menu);
 | |
|         } else if (!shouldShow && this.statusBarMenus.has(menu)) {
 | |
|           this.statusBar.removeItem(menu);
 | |
|           this.statusBarMenus.delete(menu);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (!this.showGlobalMuteToggles && !webrtcUI.showScreenSharingIndicator) {
 | |
|       this.setVisibility(false);
 | |
|     } else if (!this.hideGlobalIndicator) {
 | |
|       this.setVisibility(true);
 | |
|     }
 | |
| 
 | |
|     if (this.showGlobalMuteToggles) {
 | |
|       this.updateWindowAttr("sharingvideo", showCameraIndicator);
 | |
|       this.updateWindowAttr("sharingaudio", showMicrophoneIndicator);
 | |
|     }
 | |
| 
 | |
|     let sharingScreen = showScreenSharingIndicator.startsWith("Screen");
 | |
|     this.updateWindowAttr("sharingscreen", sharingScreen);
 | |
| 
 | |
|     // We don't currently support the browser-tab sharing case, so we don't
 | |
|     // check if the screen sharing indicator starts with "Browser".
 | |
| 
 | |
|     // We special-case sharing a window, because we want to have a slightly
 | |
|     // different UI if we're sharing a browser window.
 | |
|     let sharingWindow = showScreenSharingIndicator.startsWith("Window");
 | |
|     this.updateWindowAttr("sharingwindow", sharingWindow);
 | |
| 
 | |
|     if (sharingWindow) {
 | |
|       // Get the active window streams and see if any of them are "scary".
 | |
|       // If so, then we're sharing a browser window.
 | |
|       let activeStreams = webrtcUI.getActiveStreams(
 | |
|         false /* camera */,
 | |
|         false /* microphone */,
 | |
|         false /* screen */,
 | |
|         true /* window */
 | |
|       );
 | |
|       let hasBrowserWindow = activeStreams.some(stream => {
 | |
|         return stream.devices.some(device => device.scary);
 | |
|       });
 | |
| 
 | |
|       this.updateWindowAttr("sharingbrowserwindow", hasBrowserWindow);
 | |
|       this.sharingBrowserWindow = hasBrowserWindow;
 | |
|     } else {
 | |
|       this.updateWindowAttr("sharingbrowserwindow");
 | |
|       this.sharingBrowserWindow = false;
 | |
|     }
 | |
| 
 | |
|     // The label that's displayed when sharing a display followed a priority.
 | |
|     // The more "risky" we deem the display is for sharing, the higher priority.
 | |
|     // This gives us the following priorities, from highest to lowest.
 | |
|     //
 | |
|     // 1. Screen
 | |
|     // 2. Browser window
 | |
|     // 3. Other application window
 | |
|     // 4. Browser tab (unimplemented)
 | |
|     //
 | |
|     // The CSS for the indicator does the work of showing or hiding these labels
 | |
|     // for us, but we need to update the aria-labelledby attribute on the container
 | |
|     // of those labels to make it clearer for screenreaders which one the user cares
 | |
|     // about.
 | |
|     let displayShare = document.getElementById("display-share");
 | |
|     let labelledBy;
 | |
|     if (sharingScreen) {
 | |
|       labelledBy = "screen-share-info";
 | |
|     } else if (this.sharingBrowserWindow) {
 | |
|       labelledBy = "browser-window-share-info";
 | |
|     } else if (sharingWindow) {
 | |
|       labelledBy = "window-share-info";
 | |
|     }
 | |
|     displayShare.setAttribute("aria-labelledby", labelledBy);
 | |
| 
 | |
|     if (window.windowState != window.STATE_MINIMIZED) {
 | |
|       // Resize and ensure the window position is correct
 | |
|       // (sizeToContent messes with our position).
 | |
|       let docElStyle = document.documentElement.style;
 | |
|       docElStyle.minWidth = docElStyle.maxWidth = "unset";
 | |
|       docElStyle.minHeight = docElStyle.maxHeight = "unset";
 | |
|       window.sizeToContent();
 | |
| 
 | |
|       // On Linux GTK, the style of window we're using by default is resizable. We
 | |
|       // workaround this by setting explicit limits on the height and width of the
 | |
|       // window.
 | |
|       if (AppConstants.platform == "linux") {
 | |
|         let { width, height } = window.windowUtils.getBoundsWithoutFlushing(
 | |
|           document.documentElement
 | |
|         );
 | |
| 
 | |
|         docElStyle.minWidth = docElStyle.maxWidth = `${width}px`;
 | |
|         docElStyle.minHeight = docElStyle.maxHeight = `${height}px`;
 | |
|       }
 | |
| 
 | |
|       this.ensureOnScreen();
 | |
| 
 | |
|       if (!this.positionCustomized) {
 | |
|         this.centerOnLatestBrowser();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     this.updatingIndicatorState = false;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * After the indicator has been updated, checks to see if it has expanded
 | |
|    * such that part of the indicator is now outside of the screen. If so,
 | |
|    * it then adjusts the position to put the entire indicator on screen.
 | |
|    */
 | |
|   ensureOnScreen() {
 | |
|     let desiredX = Math.max(window.screenX, screen.availLeft);
 | |
|     let maxX =
 | |
|       screen.availLeft +
 | |
|       screen.availWidth -
 | |
|       document.documentElement.clientWidth;
 | |
|     window.moveTo(Math.min(desiredX, maxX), window.screenY);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * If the indicator is first being opened, we'll find the browser window
 | |
|    * associated with the most recent share, and pin the indicator to the
 | |
|    * very top of the content area.
 | |
|    */
 | |
|   centerOnLatestBrowser() {
 | |
|     let activeStreams = webrtcUI.getActiveStreams(
 | |
|       true /* camera */,
 | |
|       true /* microphone */,
 | |
|       true /* screen */,
 | |
|       true /* window */
 | |
|     );
 | |
| 
 | |
|     if (!activeStreams.length) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let browser = activeStreams[activeStreams.length - 1].browser;
 | |
|     let browserWindow = browser.ownerGlobal;
 | |
|     let browserRect =
 | |
|       browserWindow.windowUtils.getBoundsWithoutFlushing(browser);
 | |
| 
 | |
|     // This should be called in initialize right after we've just called
 | |
|     // updateIndicatorState. Since updateIndicatorState uses
 | |
|     // window.sizeToContent, the layout information should be up to date,
 | |
|     // and so the numbers that we get without flushing should be sufficient.
 | |
|     let { width: windowWidth } = window.windowUtils.getBoundsWithoutFlushing(
 | |
|       document.documentElement
 | |
|     );
 | |
| 
 | |
|     window.moveTo(
 | |
|       browserWindow.mozInnerScreenX +
 | |
|         browserRect.left +
 | |
|         (browserRect.width - windowWidth) / 2,
 | |
|       browserWindow.mozInnerScreenY + browserRect.top
 | |
|     );
 | |
|   },
 | |
| 
 | |
|   handleEvent(event) {
 | |
|     switch (event.type) {
 | |
|       case "load": {
 | |
|         this.onLoad();
 | |
|         break;
 | |
|       }
 | |
|       case "unload": {
 | |
|         this.onUnload();
 | |
|         break;
 | |
|       }
 | |
|       case "click": {
 | |
|         this.onClick(event);
 | |
|         break;
 | |
|       }
 | |
|       case "change": {
 | |
|         this.onChange(event);
 | |
|         break;
 | |
|       }
 | |
|       case "MozUpdateWindowPos": {
 | |
|         if (!this.updatingIndicatorState) {
 | |
|           // The window moved while not updating the indicator state,
 | |
|           // so the user probably moved it.
 | |
|           this.positionCustomized = true;
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       case "sizemodechange": {
 | |
|         if (window.windowState != window.STATE_MINIMIZED) {
 | |
|           this.updateIndicatorState();
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       case "popupshowing": {
 | |
|         this.onPopupShowing(event);
 | |
|         break;
 | |
|       }
 | |
|       case "popuphiding": {
 | |
|         this.onPopupHiding(event);
 | |
|         break;
 | |
|       }
 | |
|       case "command": {
 | |
|         this.onCommand(event);
 | |
|         break;
 | |
|       }
 | |
|       case "DOMWindowClose":
 | |
|       case "close": {
 | |
|         this.onClose(event);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onLoad() {
 | |
|     this.loaded = true;
 | |
| 
 | |
|     if (AppConstants.platform == "macosx" || AppConstants.platform == "win") {
 | |
|       this.statusBar = Cc["@mozilla.org/widget/systemstatusbar;1"].getService(
 | |
|         Ci.nsISystemStatusBar
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     this.updateIndicatorState();
 | |
| 
 | |
|     window.addEventListener("click", this);
 | |
|     window.addEventListener("change", this);
 | |
|     window.addEventListener("sizemodechange", this);
 | |
| 
 | |
|     // There are two ways that the dialog can close - either via the
 | |
|     // .close() window method, or via the OS. We handle both of those
 | |
|     // cases here.
 | |
|     window.addEventListener("DOMWindowClose", this);
 | |
|     window.addEventListener("close", this);
 | |
| 
 | |
|     if (this.statusBar) {
 | |
|       // We only want these events for the system status bar menus.
 | |
|       window.addEventListener("popupshowing", this);
 | |
|       window.addEventListener("popuphiding", this);
 | |
|       window.addEventListener("command", this);
 | |
|     }
 | |
| 
 | |
|     window.windowRoot.addEventListener("MozUpdateWindowPos", this);
 | |
| 
 | |
|     // Alert accessibility implementations stuff just changed. We only need to do
 | |
|     // this initially, because changes after this will automatically fire alert
 | |
|     // events if things change materially.
 | |
|     let ev = new CustomEvent("AlertActive", {
 | |
|       bubbles: true,
 | |
|       cancelable: true,
 | |
|     });
 | |
|     document.documentElement.dispatchEvent(ev);
 | |
| 
 | |
|     this.loaded = true;
 | |
|   },
 | |
| 
 | |
|   onClose(event) {
 | |
|     // This event is fired from when the indicator window tries to be closed.
 | |
|     // If we preventDefault() the event, we are able to cancel that close
 | |
|     // attempt.
 | |
|     //
 | |
|     // We want to do that if we're not showing the global mute toggles
 | |
|     // and we're still sharing a camera or a microphone so that we can
 | |
|     // keep the status bar indicators present (since those status bar
 | |
|     // indicators are bound to this window).
 | |
|     if (
 | |
|       !this.showGlobalMuteToggles &&
 | |
|       (webrtcUI.showCameraIndicator || webrtcUI.showMicrophoneIndicator)
 | |
|     ) {
 | |
|       event.preventDefault();
 | |
|       this.setVisibility(false);
 | |
|     }
 | |
| 
 | |
|     if (!this.isClosingInternally) {
 | |
|       // Something has tried to close the indicator, but it wasn't webrtcUI.
 | |
|       // This means we might still have some streams being shared. To protect
 | |
|       // the user from unknowingly sharing streams, we shut those streams
 | |
|       // down.
 | |
|       //
 | |
|       // This only includes the camera and microphone streams if the user
 | |
|       // has the global mute toggles enabled, since these toggles visually
 | |
|       // associate the indicator with those streams.
 | |
|       let activeStreams = webrtcUI.getActiveStreams(
 | |
|         this.showGlobalMuteToggles /* camera */,
 | |
|         this.showGlobalMuteToggles /* microphone */,
 | |
|         true /* screen */,
 | |
|         true /* window */
 | |
|       );
 | |
|       webrtcUI.stopSharingStreams(
 | |
|         activeStreams,
 | |
|         this.showGlobalMuteToggles /* camera */,
 | |
|         this.showGlobalMuteToggles /* microphone */,
 | |
|         true /* screen */,
 | |
|         true /* window */
 | |
|       );
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onUnload() {
 | |
|     Services.ppmm.sharedData.set("WebRTC:GlobalCameraMute", false);
 | |
|     Services.ppmm.sharedData.set("WebRTC:GlobalMicrophoneMute", false);
 | |
|     Services.ppmm.sharedData.flush();
 | |
| 
 | |
|     if (this.statusBar) {
 | |
|       for (let menu of this.statusBarMenus) {
 | |
|         this.statusBar.removeItem(menu);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onClick(event) {
 | |
|     switch (event.target.id) {
 | |
|       case "stop-sharing": {
 | |
|         let activeStreams = webrtcUI.getActiveStreams(
 | |
|           false /* camera */,
 | |
|           false /* microphone */,
 | |
|           true /* screen */,
 | |
|           true /* window */
 | |
|         );
 | |
| 
 | |
|         if (!activeStreams.length) {
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         // getActiveStreams is filtering for streams that have screen
 | |
|         // sharing, but those streams might _also_ be sharing other
 | |
|         // devices like camera or microphone. This is why we need to
 | |
|         // tell stopSharingStreams explicitly which device type we want
 | |
|         // to stop.
 | |
|         webrtcUI.stopSharingStreams(
 | |
|           activeStreams,
 | |
|           false /* camera */,
 | |
|           false /* microphone */,
 | |
|           true /* screen */,
 | |
|           true /* window */
 | |
|         );
 | |
|         break;
 | |
|       }
 | |
|       case "minimize": {
 | |
|         window.minimize();
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onChange(event) {
 | |
|     switch (event.target.id) {
 | |
|       case "microphone-mute-toggle": {
 | |
|         this.toggleMicrophoneMute(event.target);
 | |
|         break;
 | |
|       }
 | |
|       case "camera-mute-toggle": {
 | |
|         this.toggleCameraMute(event.target);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onPopupShowing(event) {
 | |
|     if (this.eventIsForDeviceMenuPopup(event)) {
 | |
|       // When the indicator is hidden by default, opening the menu from the
 | |
|       // system tray _might_ cause the indicator to try to become visible again.
 | |
|       // We work around this by re-hiding it if it wasn't already visible.
 | |
|       if (document.documentElement.getAttribute("visible") != "true") {
 | |
|         let baseWin = window.docShell.treeOwner.QueryInterface(
 | |
|           Ci.nsIBaseWindow
 | |
|         );
 | |
|         baseWin.visibility = false;
 | |
|       }
 | |
| 
 | |
|       showStreamSharingMenu(window, event, true);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onPopupHiding(event) {
 | |
|     if (!this.eventIsForDeviceMenuPopup(event)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let menu = event.target;
 | |
|     while (menu.firstChild) {
 | |
|       menu.firstChild.remove();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onCommand(event) {
 | |
|     webrtcUI.showSharingDoorhanger(event.target.stream, event);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns true if an event was fired for one of the shared device
 | |
|    * menupopups.
 | |
|    *
 | |
|    * @param event (Event)
 | |
|    *   The event to check.
 | |
|    * @returns True if the event was for one of the shared device
 | |
|    *   menupopups.
 | |
|    */
 | |
|   eventIsForDeviceMenuPopup(event) {
 | |
|     let menupopup = event.target;
 | |
|     let type = menupopup.getAttribute("type");
 | |
| 
 | |
|     return ["Camera", "Microphone", "Screen"].includes(type);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Mutes or unmutes the microphone globally based on the checked
 | |
|    * state of toggleEl. Also updates the tooltip of toggleEl once
 | |
|    * the state change is done.
 | |
|    *
 | |
|    * @param toggleEl (Element)
 | |
|    *   The input[type="checkbox"] for toggling the microphone mute
 | |
|    *   state.
 | |
|    */
 | |
|   toggleMicrophoneMute(toggleEl) {
 | |
|     Services.ppmm.sharedData.set(
 | |
|       "WebRTC:GlobalMicrophoneMute",
 | |
|       toggleEl.checked
 | |
|     );
 | |
|     Services.ppmm.sharedData.flush();
 | |
|     let l10nId =
 | |
|       "webrtc-microphone-" + (toggleEl.checked ? "muted" : "unmuted");
 | |
|     document.l10n.setAttributes(toggleEl, l10nId);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Mutes or unmutes the camera globally based on the checked
 | |
|    * state of toggleEl. Also updates the tooltip of toggleEl once
 | |
|    * the state change is done.
 | |
|    *
 | |
|    * @param toggleEl (Element)
 | |
|    *   The input[type="checkbox"] for toggling the camera mute
 | |
|    *   state.
 | |
|    */
 | |
|   toggleCameraMute(toggleEl) {
 | |
|     Services.ppmm.sharedData.set("WebRTC:GlobalCameraMute", toggleEl.checked);
 | |
|     Services.ppmm.sharedData.flush();
 | |
|     let l10nId = "webrtc-camera-" + (toggleEl.checked ? "muted" : "unmuted");
 | |
|     document.l10n.setAttributes(toggleEl, l10nId);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Updates an attribute on the <window> element.
 | |
|    *
 | |
|    * @param attr (String)
 | |
|    *   The name of the attribute to update.
 | |
|    * @param value (String?)
 | |
|    *   A string to set the attribute to. If the value is false-y,
 | |
|    *   the attribute is removed.
 | |
|    */
 | |
|   updateWindowAttr(attr, value) {
 | |
|     let docEl = document.documentElement;
 | |
|     if (value) {
 | |
|       docEl.setAttribute(attr, "true");
 | |
|     } else {
 | |
|       docEl.removeAttribute(attr);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * See the documentation on the script global closingInternally() function.
 | |
|    */
 | |
|   closingInternally() {
 | |
|     this.isClosingInternally = true;
 | |
|   },
 | |
| };
 | |
| 
 | |
| WebRTCIndicator.init();
 |