GeckoViewPermissionProcessChild.sys.mjs (5615B)
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 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs"; 6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; 7 8 const lazy = {}; 9 10 XPCOMUtils.defineLazyServiceGetter( 11 lazy, 12 "MediaManagerService", 13 "@mozilla.org/mediaManagerService;1", 14 Ci.nsIMediaManagerService 15 ); 16 17 const STATUS_RECORDING = "recording"; 18 const STATUS_INACTIVE = "inactive"; 19 const TYPE_CAMERA = "camera"; 20 const TYPE_MICROPHONE = "microphone"; 21 22 export class GeckoViewPermissionProcessChild extends JSProcessActorChild { 23 getActor(window) { 24 return window.windowGlobalChild.getActor("GeckoViewPermission"); 25 } 26 27 /* ---------- nsIObserver ---------- */ 28 async observe(aSubject, aTopic, aData) { 29 switch (aTopic) { 30 case "getUserMedia:ask-device-permission": { 31 await this.sendQuery("AskDevicePermission", aData); 32 Services.obs.notifyObservers( 33 aSubject, 34 "getUserMedia:got-device-permission" 35 ); 36 break; 37 } 38 case "getUserMedia:request": { 39 const { callID } = aSubject; 40 const allowedDevices = await this.handleMediaRequest(aSubject); 41 Services.obs.notifyObservers( 42 allowedDevices, 43 allowedDevices 44 ? "getUserMedia:response:allow" 45 : "getUserMedia:response:deny", 46 callID 47 ); 48 break; 49 } 50 case "PeerConnection:request": { 51 Services.obs.notifyObservers( 52 null, 53 "PeerConnection:response:allow", 54 aSubject.callID 55 ); 56 break; 57 } 58 case "recording-device-events": { 59 this.handleRecordingDeviceEvents(aSubject); 60 break; 61 } 62 } 63 } 64 65 handleRecordingDeviceEvents(aRequest) { 66 aRequest.QueryInterface(Ci.nsIPropertyBag2); 67 const contentWindow = aRequest.getProperty("window"); 68 const devices = []; 69 70 const getStatusString = function (activityStatus) { 71 switch (activityStatus) { 72 case lazy.MediaManagerService.STATE_CAPTURE_ENABLED: 73 case lazy.MediaManagerService.STATE_CAPTURE_DISABLED: 74 return STATUS_RECORDING; 75 case lazy.MediaManagerService.STATE_NOCAPTURE: 76 return STATUS_INACTIVE; 77 default: 78 throw new Error("Unexpected activityStatus value"); 79 } 80 }; 81 82 const hasCamera = {}; 83 const hasMicrophone = {}; 84 const screen = {}; 85 const window = {}; 86 const browser = {}; 87 const mediaDevices = {}; 88 lazy.MediaManagerService.mediaCaptureWindowState( 89 contentWindow, 90 hasCamera, 91 hasMicrophone, 92 screen, 93 window, 94 browser, 95 mediaDevices 96 ); 97 var cameraStatus = getStatusString(hasCamera.value); 98 var microphoneStatus = getStatusString(hasMicrophone.value); 99 if (hasCamera.value != lazy.MediaManagerService.STATE_NOCAPTURE) { 100 devices.push({ 101 type: TYPE_CAMERA, 102 status: cameraStatus, 103 }); 104 } 105 if (hasMicrophone.value != lazy.MediaManagerService.STATE_NOCAPTURE) { 106 devices.push({ 107 type: TYPE_MICROPHONE, 108 status: microphoneStatus, 109 }); 110 } 111 this.getActor(contentWindow).mediaRecordingStatusChanged(devices); 112 } 113 114 async handleMediaRequest(aRequest) { 115 const constraints = aRequest.getConstraints(); 116 const { devices, windowID } = aRequest; 117 const window = Services.wm.getOuterWindowWithId(windowID); 118 if (window.closed) { 119 return null; 120 } 121 122 // Release the request first 123 aRequest = undefined; 124 125 const sources = devices.map(device => { 126 device = device.QueryInterface(Ci.nsIMediaDevice); 127 return { 128 type: device.type, 129 id: device.rawId, 130 rawId: device.rawId, 131 name: device.rawName, // unfiltered device name to show to the user 132 mediaSource: device.mediaSource, 133 }; 134 }); 135 136 if ( 137 constraints.video && 138 !sources.some(source => source.type === "videoinput") 139 ) { 140 console.error("Media device error: no video source"); 141 return null; 142 } else if ( 143 constraints.audio && 144 !sources.some(source => source.type === "audioinput") 145 ) { 146 console.error("Media device error: no audio source"); 147 return null; 148 } 149 150 const response = await this.getActor(window).getMediaPermission({ 151 uri: window.document.documentURI, 152 video: constraints.video 153 ? sources.filter(source => source.type === "videoinput") 154 : null, 155 audio: constraints.audio 156 ? sources.filter(source => source.type === "audioinput") 157 : null, 158 }); 159 160 if (!response) { 161 // Rejected. 162 return null; 163 } 164 165 const allowedDevices = Cc["@mozilla.org/array;1"].createInstance( 166 Ci.nsIMutableArray 167 ); 168 if (constraints.video) { 169 const video = devices.find(device => response.video === device.rawId); 170 if (!video) { 171 console.error("Media device error: invalid video id"); 172 return null; 173 } 174 await this.getActor(window).addCameraPermission(); 175 allowedDevices.appendElement(video); 176 } 177 if (constraints.audio) { 178 const audio = devices.find(device => response.audio === device.rawId); 179 if (!audio) { 180 console.error("Media device error: invalid audio id"); 181 return null; 182 } 183 allowedDevices.appendElement(audio); 184 } 185 return allowedDevices; 186 } 187 } 188 189 const { debug, warn } = GeckoViewUtils.initLogging( 190 "GeckoViewPermissionProcessChild" 191 );