Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = {};
XPCOMUtils.defineLazyServiceGetter(
lazy,
"MediaManagerService",
"@mozilla.org/mediaManagerService;1",
"nsIMediaManagerService"
);
const STATUS_RECORDING = "recording";
const STATUS_INACTIVE = "inactive";
const TYPE_CAMERA = "camera";
const TYPE_MICROPHONE = "microphone";
export class GeckoViewPermissionProcessChild extends JSProcessActorChild {
getActor(window) {
return window.windowGlobalChild.getActor("GeckoViewPermission");
}
/* ---------- nsIObserver ---------- */
async observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "getUserMedia:ask-device-permission": {
await this.sendQuery("AskDevicePermission", aData);
Services.obs.notifyObservers(
aSubject,
"getUserMedia:got-device-permission"
);
break;
}
case "getUserMedia:request": {
const { callID } = aSubject;
const allowedDevices = await this.handleMediaRequest(aSubject);
Services.obs.notifyObservers(
allowedDevices,
allowedDevices
? "getUserMedia:response:allow"
: "getUserMedia:response:deny",
callID
);
break;
}
case "PeerConnection:request": {
Services.obs.notifyObservers(
null,
"PeerConnection:response:allow",
aSubject.callID
);
break;
}
case "recording-device-events": {
this.handleRecordingDeviceEvents(aSubject);
break;
}
}
}
handleRecordingDeviceEvents(aRequest) {
aRequest.QueryInterface(Ci.nsIPropertyBag2);
const contentWindow = aRequest.getProperty("window");
const devices = [];
const getStatusString = function (activityStatus) {
switch (activityStatus) {
case lazy.MediaManagerService.STATE_CAPTURE_ENABLED:
case lazy.MediaManagerService.STATE_CAPTURE_DISABLED:
return STATUS_RECORDING;
case lazy.MediaManagerService.STATE_NOCAPTURE:
return STATUS_INACTIVE;
default:
throw new Error("Unexpected activityStatus value");
}
};
const hasCamera = {};
const hasMicrophone = {};
const screen = {};
const window = {};
const browser = {};
const mediaDevices = {};
lazy.MediaManagerService.mediaCaptureWindowState(
contentWindow,
hasCamera,
hasMicrophone,
screen,
window,
browser,
mediaDevices
);
var cameraStatus = getStatusString(hasCamera.value);
var microphoneStatus = getStatusString(hasMicrophone.value);
if (hasCamera.value != lazy.MediaManagerService.STATE_NOCAPTURE) {
devices.push({
type: TYPE_CAMERA,
status: cameraStatus,
});
}
if (hasMicrophone.value != lazy.MediaManagerService.STATE_NOCAPTURE) {
devices.push({
type: TYPE_MICROPHONE,
status: microphoneStatus,
});
}
this.getActor(contentWindow).mediaRecordingStatusChanged(devices);
}
async handleMediaRequest(aRequest) {
const constraints = aRequest.getConstraints();
const { devices, windowID } = aRequest;
const window = Services.wm.getOuterWindowWithId(windowID);
if (window.closed) {
return null;
}
// Release the request first
aRequest = undefined;
const sources = devices.map(device => {
device = device.QueryInterface(Ci.nsIMediaDevice);
return {
type: device.type,
id: device.rawId,
rawId: device.rawId,
name: device.rawName, // unfiltered device name to show to the user
mediaSource: device.mediaSource,
};
});
if (
constraints.video &&
!sources.some(source => source.type === "videoinput")
) {
console.error("Media device error: no video source");
return null;
} else if (
constraints.audio &&
!sources.some(source => source.type === "audioinput")
) {
console.error("Media device error: no audio source");
return null;
}
const response = await this.getActor(window).getMediaPermission({
uri: window.document.documentURI,
video: constraints.video
? sources.filter(source => source.type === "videoinput")
: null,
audio: constraints.audio
? sources.filter(source => source.type === "audioinput")
: null,
});
if (!response) {
// Rejected.
return null;
}
const allowedDevices = Cc["@mozilla.org/array;1"].createInstance(
Ci.nsIMutableArray
);
if (constraints.video) {
const video = devices.find(device => response.video === device.rawId);
if (!video) {
console.error("Media device error: invalid video id");
return null;
}
await this.getActor(window).addCameraPermission();
allowedDevices.appendElement(video);
}
if (constraints.audio) {
const audio = devices.find(device => response.audio === device.rawId);
if (!audio) {
console.error("Media device error: invalid audio id");
return null;
}
allowedDevices.appendElement(audio);
}
return allowedDevices;
}
}
const { debug, warn } = GeckoViewUtils.initLogging(
"GeckoViewPermissionProcessChild"
);