diff options
Diffstat (limited to 'mobile/android/chrome/content/WebrtcUI.js')
-rw-r--r-- | mobile/android/chrome/content/WebrtcUI.js | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/mobile/android/chrome/content/WebrtcUI.js b/mobile/android/chrome/content/WebrtcUI.js new file mode 100644 index 000000000..475d05bd2 --- /dev/null +++ b/mobile/android/chrome/content/WebrtcUI.js @@ -0,0 +1,302 @@ +/* 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"]; + +XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm"); +XPCOMUtils.defineLazyServiceGetter(this, "ParentalControls", "@mozilla.org/parental-controls-service;1", "nsIParentalControlsService"); +XPCOMUtils.defineLazyModuleGetter(this, "RuntimePermissions", "resource://gre/modules/RuntimePermissions.jsm"); + +var WebrtcUI = { + _notificationId: null, + + // Add-ons can override stock permission behavior by doing: + // + // var stockObserve = WebrtcUI.observe; + // + // webrtcUI.observe = function(aSubject, aTopic, aData) { + // switch (aTopic) { + // case "PeerConnection:request": { + // // new code. + // break; + // ... + // default: + // return stockObserve.call(this, aSubject, aTopic, aData); + // + // See browser/modules/webrtcUI.jsm for details. + + observe: function(aSubject, aTopic, aData) { + if (aTopic === "getUserMedia:request") { + RuntimePermissions + .waitForPermissions(this._determineNeededRuntimePermissions(aSubject)) + .then((permissionGranted) => { + if (permissionGranted) { + WebrtcUI.handleGumRequest(aSubject, aTopic, aData); + } else { + Services.obs.notifyObservers(null, "getUserMedia:response:deny", aSubject.callID); + }}); + } else if (aTopic === "PeerConnection:request") { + this.handlePCRequest(aSubject, aTopic, aData); + } else if (aTopic === "recording-device-events") { + switch (aData) { + case "shutdown": + case "starting": + this.notify(); + break; + } + } else if (aTopic === "VideoCapture:Paused") { + if (this._notificationId) { + Notifications.cancel(this._notificationId); + this._notificationId = null; + } + } else if (aTopic === "VideoCapture:Resumed") { + this.notify(); + } + }, + + notify: function() { + let windows = MediaManagerService.activeMediaCaptureWindows; + let count = windows.length; + let msg = {}; + if (count == 0) { + if (this._notificationId) { + Notifications.cancel(this._notificationId); + this._notificationId = null; + } + } else { + let notificationOptions = { + title: Strings.brand.GetStringFromName("brandShortName"), + when: null, // hide the date row + light: [0xFF9500FF, 1000, 1000], + ongoing: true + }; + + let cameraActive = false; + let audioActive = false; + for (let i = 0; i < count; i++) { + let win = windows.queryElementAt(i, Ci.nsIDOMWindow); + let hasAudio = {}; + let hasVideo = {}; + MediaManagerService.mediaCaptureWindowState(win, hasVideo, hasAudio); + if (hasVideo.value) cameraActive = true; + if (hasAudio.value) audioActive = true; + } + + if (cameraActive && audioActive) { + notificationOptions.message = Strings.browser.GetStringFromName("getUserMedia.sharingCameraAndMicrophone.message2"); + notificationOptions.icon = "drawable:alert_mic_camera"; + } else if (cameraActive) { + notificationOptions.message = Strings.browser.GetStringFromName("getUserMedia.sharingCamera.message2"); + notificationOptions.icon = "drawable:alert_camera"; + } else if (audioActive) { + notificationOptions.message = Strings.browser.GetStringFromName("getUserMedia.sharingMicrophone.message2"); + notificationOptions.icon = "drawable:alert_mic"; + } else { + // somethings wrong. lets throw + throw "Couldn't find any cameras or microphones being used" + } + + if (this._notificationId) + Notifications.update(this._notificationId, notificationOptions); + else + this._notificationId = Notifications.create(notificationOptions); + if (count > 1) + msg.count = count; + } + }, + + handlePCRequest: function handlePCRequest(aSubject, aTopic, aData) { + aSubject = aSubject.wrappedJSObject; + let { callID } = aSubject; + // Also available: windowID, isSecure, innerWindowID. For contentWindow do: + // + // let contentWindow = Services.wm.getOuterWindowWithId(windowID); + + Services.obs.notifyObservers(null, "PeerConnection:response:allow", callID); + }, + + handleGumRequest: function handleGumRequest(aSubject, aTopic, aData) { + let constraints = aSubject.getConstraints(); + let contentWindow = Services.wm.getOuterWindowWithId(aSubject.windowID); + + contentWindow.navigator.mozGetUserMediaDevices( + constraints, + function (devices) { + if (!ParentalControls.isAllowed(ParentalControls.CAMERA_MICROPHONE)) { + Services.obs.notifyObservers(null, "getUserMedia:response:deny", aSubject.callID); + WebrtcUI.showBlockMessage(devices); + return; + } + + WebrtcUI.prompt(contentWindow, aSubject.callID, constraints.audio, + constraints.video, devices); + }, + function (error) { + Cu.reportError(error); + }, + aSubject.innerWindowID, + aSubject.callID); + }, + + getDeviceButtons: function(audioDevices, videoDevices, aCallID, aUri) { + return [{ + label: Strings.browser.GetStringFromName("getUserMedia.denyRequest.label"), + callback: function() { + Services.obs.notifyObservers(null, "getUserMedia:response:deny", aCallID); + } + }, + { + label: Strings.browser.GetStringFromName("getUserMedia.shareRequest.label"), + callback: function(checked /* ignored */, inputs) { + let allowedDevices = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); + + let audioId = 0; + if (inputs && inputs.audioDevice != undefined) + audioId = inputs.audioDevice; + if (audioDevices[audioId]) + allowedDevices.appendElement(audioDevices[audioId], /*weak =*/ false); + + let videoId = 0; + if (inputs && inputs.videoSource != undefined) + videoId = inputs.videoSource; + if (videoDevices[videoId]) { + allowedDevices.appendElement(videoDevices[videoId], /*weak =*/ false); + let perms = Services.perms; + // Although the lifetime is "session" it will be removed upon + // use so it's more of a one-shot. + perms.add(aUri, "MediaManagerVideo", perms.ALLOW_ACTION, perms.EXPIRE_SESSION); + } + + Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID); + }, + positive: true + }]; + }, + + _determineNeededRuntimePermissions: function(aSubject) { + let permissions = []; + + let constraints = aSubject.getConstraints(); + if (constraints.video) { + permissions.push(RuntimePermissions.CAMERA); + } + if (constraints.audio) { + permissions.push(RuntimePermissions.RECORD_AUDIO); + } + + return permissions; + }, + + // Get a list of string names for devices. Ensures that none of the strings are blank + _getList: function(aDevices, aType) { + let defaultCount = 0; + return aDevices.map(function(device) { + // if this is a Camera input, convert the name to something readable + let res = /Camera\ \d+,\ Facing (front|back)/.exec(device.name); + if (res) + return Strings.browser.GetStringFromName("getUserMedia." + aType + "." + res[1] + "Camera"); + + if (device.name.startsWith("&") && device.name.endsWith(";")) + return Strings.browser.GetStringFromName(device.name.substring(1, device.name.length -1)); + + if (device.name.trim() == "") { + defaultCount++; + return Strings.browser.formatStringFromName("getUserMedia." + aType + ".default", [defaultCount], 1); + } + return device.name + }, this); + }, + + _addDevicesToOptions: function(aDevices, aType, aOptions) { + if (aDevices.length) { + + // Filter out empty items from the list + let list = this._getList(aDevices, aType); + + if (list.length > 0) { + aOptions.inputs.push({ + id: aType, + type: "menulist", + label: Strings.browser.GetStringFromName("getUserMedia." + aType + ".prompt"), + values: list + }); + + } + } + }, + + showBlockMessage: function(aDevices) { + let microphone = false; + let camera = false; + + for (let device of aDevices) { + device = device.QueryInterface(Ci.nsIMediaDevice); + if (device.type == "audio") { + microphone = true; + } else if (device.type == "video") { + camera = true; + } + } + + let message; + if (microphone && !camera) { + message = Strings.browser.GetStringFromName("getUserMedia.blockedMicrophoneAccess"); + } else if (camera && !microphone) { + message = Strings.browser.GetStringFromName("getUserMedia.blockedCameraAccess"); + } else { + message = Strings.browser.GetStringFromName("getUserMedia.blockedCameraAndMicrophoneAccess"); + } + + NativeWindow.doorhanger.show(message, "webrtc-blocked", [], BrowserApp.selectedTab.id, {}); + }, + + prompt: function prompt(aContentWindow, aCallID, aAudioRequested, + aVideoRequested, aDevices) { + let audioDevices = []; + let videoDevices = []; + for (let device of aDevices) { + device = device.QueryInterface(Ci.nsIMediaDevice); + switch (device.type) { + case "audio": + if (aAudioRequested) + audioDevices.push(device); + break; + case "video": + if (aVideoRequested) + videoDevices.push(device); + break; + } + } + + let requestType; + if (audioDevices.length && videoDevices.length) + requestType = "CameraAndMicrophone"; + else if (audioDevices.length) + requestType = "Microphone"; + else if (videoDevices.length) + requestType = "Camera"; + else + return; + + let uri = aContentWindow.document.documentURIObject; + let host = uri.host; + let requestor = BrowserApp.manifest ? "'" + BrowserApp.manifest.name + "'" : host; + let message = Strings.browser.formatStringFromName("getUserMedia.share" + requestType + ".message", [ requestor ], 1); + + let options = { inputs: [] }; + if (videoDevices.length > 1 || audioDevices.length > 0) { + // videoSource is both the string used for l10n lookup and the object that will be returned + this._addDevicesToOptions(videoDevices, "videoSource", options); + } + + if (audioDevices.length > 1 || videoDevices.length > 0) { + this._addDevicesToOptions(audioDevices, "audioDevice", options); + } + + let buttons = this.getDeviceButtons(audioDevices, videoDevices, aCallID, uri); + + NativeWindow.doorhanger.show(message, "webrtc-request", buttons, BrowserApp.selectedTab.id, options, "WEBRTC"); + } +} |