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/. */
7 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
9 this.EXPORTED_SYMBOLS = [ "ContentWebRTC" ];
11 Cu.import("resource://gre/modules/Services.jsm");
12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
13 XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
14 "@mozilla.org/mediaManagerService;1",
15 "nsIMediaManagerService");
17 this.ContentWebRTC = {
21 if (this._initialized)
24 this._initialized = true;
25 Services.obs.addObserver(handleRequest, "getUserMedia:request", false);
26 Services.obs.addObserver(updateIndicators, "recording-device-events", false);
27 Services.obs.addObserver(removeBrowserSpecificIndicator, "recording-window-ended", false);
30 // Called only for 'unload' to remove pending gUM prompts in reloaded frames.
31 handleEvent: function(aEvent) {
32 let contentWindow = aEvent.target.defaultView;
33 let mm = getMessageManagerForWindow(contentWindow);
34 for (let key of contentWindow.pendingGetUserMediaRequests.keys())
35 mm.sendAsyncMessage("webrtc:CancelRequest", key);
38 receiveMessage: function(aMessage) {
39 switch (aMessage.name) {
41 let callID = aMessage.data.callID;
42 let contentWindow = Services.wm.getOuterWindowWithId(aMessage.data.windowID);
43 let devices = contentWindow.pendingGetUserMediaRequests.get(callID);
44 forgetRequest(contentWindow, callID);
46 let allowedDevices = Cc["@mozilla.org/supports-array;1"]
47 .createInstance(Ci.nsISupportsArray);
48 for (let deviceIndex of aMessage.data.devices)
49 allowedDevices.AppendElement(devices[deviceIndex]);
51 Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", callID);
54 denyRequest(aMessage.data);
56 case "webrtc:StopSharing":
57 Services.obs.notifyObservers(null, "getUserMedia:revoke", aMessage.data);
63 function handleRequest(aSubject, aTopic, aData) {
64 let constraints = aSubject.getConstraints();
65 let secure = aSubject.isSecure;
66 let contentWindow = Services.wm.getOuterWindowWithId(aSubject.windowID);
68 contentWindow.navigator.mozGetUserMediaDevices(
71 prompt(contentWindow, aSubject.windowID, aSubject.callID,
72 constraints, devices, secure);
75 // bug 827146 -- In the future, the UI should catch NotFoundError
76 // and allow the user to plug in a device, instead of immediately failing.
77 denyRequest({callID: aSubject.callID}, error);
79 aSubject.innerWindowID);
82 function prompt(aContentWindow, aWindowID, aCallID, aConstraints, aDevices, aSecure) {
83 let audioDevices = [];
84 let videoDevices = [];
87 // MediaStreamConstraints defines video as 'boolean or MediaTrackConstraints'.
88 let video = aConstraints.video || aConstraints.picture;
89 let sharingScreen = video && typeof(video) != "boolean" &&
90 video.mediaSource != "camera";
91 for (let device of aDevices) {
92 device = device.QueryInterface(Ci.nsIMediaDevice);
93 switch (device.type) {
95 if (aConstraints.audio) {
96 audioDevices.push({name: device.name, deviceIndex: devices.length});
101 // Verify that if we got a camera, we haven't requested a screen share,
102 // or that if we requested a screen share we aren't getting a camera.
103 if (video && (device.mediaSource == "camera") != sharingScreen) {
104 videoDevices.push({name: device.name, deviceIndex: devices.length,
105 mediaSource: device.mediaSource});
106 devices.push(device);
112 let requestTypes = [];
113 if (videoDevices.length)
114 requestTypes.push(sharingScreen ? "Screen" : "Camera");
115 if (audioDevices.length)
116 requestTypes.push("Microphone");
118 if (!requestTypes.length) {
119 denyRequest({callID: aCallID}, "NotFoundError");
123 if (!aContentWindow.pendingGetUserMediaRequests) {
124 aContentWindow.pendingGetUserMediaRequests = new Map();
125 aContentWindow.addEventListener("unload", ContentWebRTC);
127 aContentWindow.pendingGetUserMediaRequests.set(aCallID, devices);
132 documentURI: aContentWindow.document.documentURI,
134 requestTypes: requestTypes,
135 sharingScreen: sharingScreen,
136 audioDevices: audioDevices,
137 videoDevices: videoDevices
140 let mm = getMessageManagerForWindow(aContentWindow);
141 mm.sendAsyncMessage("webrtc:Request", request);
144 function denyRequest(aData, aError) {
147 msg = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
150 Services.obs.notifyObservers(msg, "getUserMedia:response:deny", aData.callID);
154 let contentWindow = Services.wm.getOuterWindowWithId(aData.windowID);
155 if (contentWindow.pendingGetUserMediaRequests)
156 forgetRequest(contentWindow, aData.callID);
159 function forgetRequest(aContentWindow, aCallID) {
160 aContentWindow.pendingGetUserMediaRequests.delete(aCallID);
161 if (aContentWindow.pendingGetUserMediaRequests.size)
164 aContentWindow.removeEventListener("unload", ContentWebRTC);
165 aContentWindow.pendingGetUserMediaRequests = null;
168 function updateIndicators() {
169 let contentWindowSupportsArray = MediaManagerService.activeMediaCaptureWindows;
170 let count = contentWindowSupportsArray.Count();
173 showGlobalIndicator: count > 0,
174 showCameraIndicator: false,
175 showMicrophoneIndicator: false,
176 showScreenSharingIndicator: ""
179 let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
180 .getService(Ci.nsIMessageSender);
181 cpmm.sendAsyncMessage("webrtc:UpdatingIndicators");
183 // If several iframes in the same page use media streams, it's possible to
184 // have the same top level window several times. We use a Set to avoid
185 // sending duplicate notifications.
186 let contentWindows = new Set();
187 for (let i = 0; i < count; ++i) {
188 contentWindows.add(contentWindowSupportsArray.GetElementAt(i).top);
191 for (let contentWindow of contentWindows) {
192 let camera = {}, microphone = {}, screen = {}, window = {}, app = {};
193 MediaManagerService.mediaCaptureWindowState(contentWindow, camera,
194 microphone, screen, window, app);
195 let tabState = {camera: camera.value, microphone: microphone.value};
197 state.showCameraIndicator = true;
198 if (microphone.value)
199 state.showMicrophoneIndicator = true;
201 state.showScreenSharingIndicator = "Screen";
202 tabState.screen = "Screen";
204 else if (window.value) {
205 if (state.showScreenSharingIndicator != "Screen")
206 state.showScreenSharingIndicator = "Window";
207 tabState.screen = "Window";
209 else if (app.value) {
210 if (!state.showScreenSharingIndicator)
211 state.showScreenSharingIndicator = "Application";
212 tabState.screen = "Application";
215 tabState.windowId = getInnerWindowIDForWindow(contentWindow);
216 tabState.documentURI = contentWindow.document.documentURI;
217 let mm = getMessageManagerForWindow(contentWindow);
218 mm.sendAsyncMessage("webrtc:UpdateBrowserIndicators", tabState);
221 cpmm.sendAsyncMessage("webrtc:UpdateGlobalIndicators", state);
224 function removeBrowserSpecificIndicator(aSubject, aTopic, aData) {
225 let contentWindow = Services.wm.getOuterWindowWithId(aData);
226 let mm = getMessageManagerForWindow(contentWindow);
228 mm.sendAsyncMessage("webrtc:UpdateBrowserIndicators",
229 {windowId: getInnerWindowIDForWindow(contentWindow)});
232 function getInnerWindowIDForWindow(aContentWindow) {
233 return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
234 .getInterface(Ci.nsIDOMWindowUtils)
235 .currentInnerWindowID;
238 function getMessageManagerForWindow(aContentWindow) {
239 let ir = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
240 .getInterface(Ci.nsIDocShell)
241 .sameTypeRootTreeItem
242 .QueryInterface(Ci.nsIInterfaceRequestor);
244 // If e10s is disabled, this throws NS_NOINTERFACE for closed tabs.
245 return ir.getInterface(Ci.nsIContentFrameMessageManager);
246 } catch(e if e.result == Cr.NS_NOINTERFACE) {