Bumping manifests a=b2g-bump
[gecko.git] / browser / modules / ContentWebRTC.jsm
blob96d29612afd938697a1f9d5c77508fb47405e0eb
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/. */
5 "use strict";
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 = {
18   _initialized: false,
20   init: function() {
21     if (this._initialized)
22       return;
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);
28   },
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);
36   },
38   receiveMessage: function(aMessage) {
39     switch (aMessage.name) {
40       case "webrtc:Allow":
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);
52         break;
53       case "webrtc:Deny":
54         denyRequest(aMessage.data);
55         break;
56       case "webrtc:StopSharing":
57         Services.obs.notifyObservers(null, "getUserMedia:revoke", aMessage.data);
58         break;
59     }
60   }
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(
69     constraints,
70     function (devices) {
71       prompt(contentWindow, aSubject.windowID, aSubject.callID,
72              constraints, devices, secure);
73     },
74     function (error) {
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);
78     },
79     aSubject.innerWindowID);
82 function prompt(aContentWindow, aWindowID, aCallID, aConstraints, aDevices, aSecure) {
83   let audioDevices = [];
84   let videoDevices = [];
85   let devices = [];
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) {
94       case "audio":
95         if (aConstraints.audio) {
96           audioDevices.push({name: device.name, deviceIndex: devices.length});
97           devices.push(device);
98         }
99         break;
100       case "video":
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);
107         }
108         break;
109     }
110   }
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");
120     return;
121   }
123   if (!aContentWindow.pendingGetUserMediaRequests) {
124     aContentWindow.pendingGetUserMediaRequests = new Map();
125     aContentWindow.addEventListener("unload", ContentWebRTC);
126   }
127   aContentWindow.pendingGetUserMediaRequests.set(aCallID, devices);
129   let request = {
130     callID: aCallID,
131     windowID: aWindowID,
132     documentURI: aContentWindow.document.documentURI,
133     secure: aSecure,
134     requestTypes: requestTypes,
135     sharingScreen: sharingScreen,
136     audioDevices: audioDevices,
137     videoDevices: videoDevices
138   };
140   let mm = getMessageManagerForWindow(aContentWindow);
141   mm.sendAsyncMessage("webrtc:Request", request);
144 function denyRequest(aData, aError) {
145   let msg = null;
146   if (aError) {
147     msg = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
148     msg.data = aError;
149   }
150   Services.obs.notifyObservers(msg, "getUserMedia:response:deny", aData.callID);
152   if (!aData.windowID)
153     return;
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)
162     return;
164   aContentWindow.removeEventListener("unload", ContentWebRTC);
165   aContentWindow.pendingGetUserMediaRequests = null;
168 function updateIndicators() {
169   let contentWindowSupportsArray = MediaManagerService.activeMediaCaptureWindows;
170   let count = contentWindowSupportsArray.Count();
172   let state = {
173     showGlobalIndicator: count > 0,
174     showCameraIndicator: false,
175     showMicrophoneIndicator: false,
176     showScreenSharingIndicator: ""
177   };
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);
189   }
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};
196     if (camera.value)
197       state.showCameraIndicator = true;
198     if (microphone.value)
199       state.showMicrophoneIndicator = true;
200     if (screen.value) {
201       state.showScreenSharingIndicator = "Screen";
202       tabState.screen = "Screen";
203     }
204     else if (window.value) {
205       if (state.showScreenSharingIndicator != "Screen")
206         state.showScreenSharingIndicator = "Window";
207       tabState.screen = "Window";
208     }
209     else if (app.value) {
210       if (!state.showScreenSharingIndicator)
211         state.showScreenSharingIndicator = "Application";
212       tabState.screen = "Application";
213     }
215     tabState.windowId = getInnerWindowIDForWindow(contentWindow);
216     tabState.documentURI = contentWindow.document.documentURI;
217     let mm = getMessageManagerForWindow(contentWindow);
218     mm.sendAsyncMessage("webrtc:UpdateBrowserIndicators", tabState);
219   }
221   cpmm.sendAsyncMessage("webrtc:UpdateGlobalIndicators", state);
224 function removeBrowserSpecificIndicator(aSubject, aTopic, aData) {
225   let contentWindow = Services.wm.getOuterWindowWithId(aData);
226   let mm = getMessageManagerForWindow(contentWindow);
227   if (mm)
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);
243   try {
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) {
247     return null;
248   }