Bug 1270572 - allow un-prompted gUM access if the page has a live track connected to the same device; r=florian,gcp,smaug

MozReview-Commit-ID: EvATqR4NNTH

--HG--
extra : rebase_source : cf3b08d7f47e8239bdb6ed27a2eaacb491200d48
This commit is contained in:
Munro Mengjue Chiang 2017-01-19 12:24:59 +08:00
parent 7e5fc282b8
commit f0be461749
7 changed files with 213 additions and 9 deletions

View File

@ -4943,8 +4943,8 @@ var TabsProgressListener = {
let tab = gBrowser.getTabForBrowser(aBrowser);
if (tab && tab._sharingState) {
gBrowser.setBrowserSharing(aBrowser, {});
webrtcUI.forgetStreamsFromBrowser(aBrowser);
}
webrtcUI.forgetStreamsFromBrowser(aBrowser);
gBrowser.getNotificationBox(aBrowser).removeTransientNotifications();
@ -7512,6 +7512,7 @@ var gIdentityHandler = {
}
}
browser.messageManager.sendAsyncMessage("webrtc:StopSharing", windowId);
webrtcUI.forgetActivePermissionsFromBrowser(gBrowser.selectedBrowser);
}
SitePermissions.remove(gBrowser.currentURI, aPermission.id, browser);

View File

@ -25,6 +25,7 @@ this.ContentWebRTC = {
this._initialized = true;
Services.obs.addObserver(handleGUMRequest, "getUserMedia:request", false);
Services.obs.addObserver(handleGUMStop, "recording-device-stopped", false);
Services.obs.addObserver(handlePCRequest, "PeerConnection:request", false);
Services.obs.addObserver(updateIndicators, "recording-device-events", false);
Services.obs.addObserver(removeBrowserSpecificIndicator, "recording-window-ended", false);
@ -35,6 +36,7 @@ this.ContentWebRTC = {
uninit() {
Services.obs.removeObserver(handleGUMRequest, "getUserMedia:request");
Services.obs.removeObserver(handleGUMStop, "recording-device-stopped");
Services.obs.removeObserver(handlePCRequest, "PeerConnection:request");
Services.obs.removeObserver(updateIndicators, "recording-device-events");
Services.obs.removeObserver(removeBrowserSpecificIndicator, "recording-window-ended");
@ -124,6 +126,19 @@ function handlePCRequest(aSubject, aTopic, aData) {
mm.sendAsyncMessage("rtcpeer:Request", request);
}
function handleGUMStop(aSubject, aTopic, aData) {
let contentWindow = Services.wm.getOuterWindowWithId(aSubject.windowID);
let request = {
windowID: aSubject.windowID,
rawID: aSubject.rawID,
mediaSource: aSubject.mediaSource,
};
let mm = getMessageManagerForWindow(contentWindow);
mm.sendAsyncMessage("webrtc:StopRecording", request);
}
function handleGUMRequest(aSubject, aTopic, aData) {
let constraints = aSubject.getConstraints();
let secure = aSubject.isSecure;

View File

@ -45,6 +45,7 @@ this.webrtcUI = {
mm.addMessageListener("rtcpeer:Request", this);
mm.addMessageListener("rtcpeer:CancelRequest", this);
mm.addMessageListener("webrtc:Request", this);
mm.addMessageListener("webrtc:StopRecording", this);
mm.addMessageListener("webrtc:CancelRequest", this);
mm.addMessageListener("webrtc:UpdateBrowserIndicators", this);
},
@ -62,6 +63,7 @@ this.webrtcUI = {
mm.removeMessageListener("rtcpeer:Request", this);
mm.removeMessageListener("rtcpeer:CancelRequest", this);
mm.removeMessageListener("webrtc:Request", this);
mm.removeMessageListener("webrtc:StopRecording");
mm.removeMessageListener("webrtc:CancelRequest", this);
mm.removeMessageListener("webrtc:UpdateBrowserIndicators", this);
@ -72,6 +74,7 @@ this.webrtcUI = {
},
processIndicators: new Map(),
activePerms: new Map(),
get showGlobalIndicator() {
for (let [, indicators] of this.processIndicators) {
@ -142,8 +145,13 @@ this.webrtcUI = {
}
},
forgetActivePermissionsFromBrowser(aBrowser) {
webrtcUI.activePerms.delete(aBrowser.outerWindowID);
},
forgetStreamsFromBrowser(aBrowser) {
this._streams = this._streams.filter(stream => stream.browser != aBrowser);
webrtcUI.forgetActivePermissionsFromBrowser(aBrowser);
},
showSharingDoorhanger(aActiveStream) {
@ -265,6 +273,9 @@ this.webrtcUI = {
case "webrtc:Request":
prompt(aMessage.target, aMessage.data);
break;
case "webrtc:StopRecording":
stopRecording(aMessage.target, aMessage.data);
break;
case "webrtc:CancelRequest":
removePrompt(aMessage.target, aMessage.data);
break;
@ -334,6 +345,21 @@ function getHost(uri, href) {
return host;
}
function stopRecording(aBrowser, aRequest) {
let outerWindowID = aBrowser.outerWindowID;
if (!webrtcUI.activePerms.has(outerWindowID)) {
return;
}
if (!aRequest.rawID) {
webrtcUI.activePerms.delete(outerWindowID);
} else {
let set = webrtcUI.activePerms.get(outerWindowID);
set.delete(aRequest.windowID + aRequest.mediaSource + aRequest.rawID);
}
}
function prompt(aBrowser, aRequest) {
let { audioDevices, videoDevices, sharingScreen, sharingAudio,
requestTypes } = aRequest;
@ -476,18 +502,37 @@ function prompt(aBrowser, aRequest) {
if (videoDevices.length && sharingScreen)
camAllowed = false;
if ((!audioDevices.length || micAllowed) &&
(!videoDevices.length || camAllowed)) {
// All permissions we were about to request are already persistently set.
let activeCamera;
let activeMic;
for (let device of videoDevices) {
let set = webrtcUI.activePerms.get(aBrowser.outerWindowID);
if (set && set.has(aRequest.windowID + device.mediaSource + device.id)) {
activeCamera = device;
break;
}
}
for (let device of audioDevices) {
let set = webrtcUI.activePerms.get(aBrowser.outerWindowID);
if (set && set.has(aRequest.windowID + device.mediaSource + device.id)) {
activeMic = device;
break;
}
}
if ((!audioDevices.length || micAllowed || activeMic) &&
(!videoDevices.length || camAllowed || activeCamera)) {
let allowedDevices = [];
if (videoDevices.length && camAllowed) {
allowedDevices.push(videoDevices[0].deviceIndex);
if (videoDevices.length) {
allowedDevices.push((activeCamera || videoDevices[0]).deviceIndex);
Services.perms.add(uri, "MediaManagerVideo",
Services.perms.ALLOW_ACTION,
Services.perms.EXPIRE_SESSION);
}
if (audioDevices.length && micAllowed)
allowedDevices.push(audioDevices[0].deviceIndex);
if (audioDevices.length) {
allowedDevices.push((activeMic || audioDevices[0]).deviceIndex);
}
// Remember on which URIs we found persistent permissions so that we
// can remove them if the user clicks 'Stop Sharing'. There's no
@ -680,6 +725,17 @@ function prompt(aBrowser, aRequest) {
// (it's really one-shot, not for the entire session)
perms.add(uri, "MediaManagerVideo", perms.ALLOW_ACTION,
perms.EXPIRE_SESSION);
if (!webrtcUI.activePerms.has(aBrowser.outerWindowID)) {
webrtcUI.activePerms.set(aBrowser.outerWindowID, new Set());
}
for (let device of videoDevices) {
if (device.deviceIndex == videoDeviceIndex) {
webrtcUI.activePerms.get(aBrowser.outerWindowID)
.add(aRequest.windowID + device.mediaSource + device.id);
break;
}
}
if (remember)
SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
} else {
@ -693,6 +749,17 @@ function prompt(aBrowser, aRequest) {
let allowMic = audioDeviceIndex != "-1";
if (allowMic) {
allowedDevices.push(audioDeviceIndex);
if (!webrtcUI.activePerms.has(aBrowser.outerWindowID)) {
webrtcUI.activePerms.set(aBrowser.outerWindowID, new Set());
}
for (let device of audioDevices) {
if (device.deviceIndex == audioDeviceIndex) {
webrtcUI.activePerms.get(aBrowser.outerWindowID)
.add(aRequest.windowID + device.mediaSource + device.id);
break;
}
}
if (remember)
SitePermissions.set(uri, "microphone", SitePermissions.ALLOW);
} else {

View File

@ -25,6 +25,18 @@ GetUserMediaRequest::GetUserMediaRequest(
{
}
GetUserMediaRequest::GetUserMediaRequest(
nsPIDOMWindowInner* aInnerWindow,
const nsAString& aRawId,
const nsAString& aMediaSource)
: mRawID(aRawId)
, mMediaSource(aMediaSource)
{
if (aInnerWindow && aInnerWindow->GetOuterWindow()) {
mOuterWindowID = aInnerWindow->GetOuterWindow()->WindowID();
}
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(GetUserMediaRequest)
NS_IMPL_CYCLE_COLLECTING_ADDREF(GetUserMediaRequest)
NS_IMPL_CYCLE_COLLECTING_RELEASE(GetUserMediaRequest)
@ -49,6 +61,16 @@ void GetUserMediaRequest::GetCallID(nsString& retval)
retval = mCallID;
}
void GetUserMediaRequest::GetRawID(nsString& retval)
{
retval = mRawID;
}
void GetUserMediaRequest::GetMediaSource(nsString& retval)
{
retval = mMediaSource;
}
uint64_t GetUserMediaRequest::WindowID()
{
return mOuterWindowID;

View File

@ -24,6 +24,9 @@ public:
const nsAString& aCallID,
const MediaStreamConstraints& aConstraints,
bool aIsSecure);
GetUserMediaRequest(nsPIDOMWindowInner* aInnerWindow,
const nsAString& aRawId,
const nsAString& aMediaSource);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(GetUserMediaRequest)
@ -35,6 +38,8 @@ public:
uint64_t InnerWindowID();
bool IsSecure();
void GetCallID(nsString& retval);
void GetRawID(nsString& retval);
void GetMediaSource(nsString& retval);
void GetConstraints(MediaStreamConstraints &result);
private:
@ -42,6 +47,8 @@ private:
uint64_t mInnerWindowID, mOuterWindowID;
const nsString mCallID;
const nsString mRawID;
const nsString mMediaSource;
nsAutoPtr<MediaStreamConstraints> mConstraints;
bool mIsSecure;
};

View File

@ -175,6 +175,7 @@ HostIsHttps(nsIURI &docURI)
*/
class GetUserMediaCallbackMediaStreamListener : public MediaStreamListener
{
friend MediaManager;
public:
// Create in an inactive state
GetUserMediaCallbackMediaStreamListener(base::Thread *aThread,
@ -2749,15 +2750,97 @@ MediaManager::RemoveFromWindowList(uint64_t aWindowID,
{
MOZ_ASSERT(NS_IsMainThread());
nsString videoRawId;
nsString audioRawId;
nsString videoSourceType;
nsString audioSourceType;
bool hasVideoDevice = aListener->mVideoDevice;
bool hasAudioDevice = aListener->mAudioDevice;
if (hasVideoDevice) {
aListener->mVideoDevice->GetRawId(videoRawId);
aListener->mVideoDevice->GetMediaSource(videoSourceType);
}
if (hasAudioDevice) {
aListener->mAudioDevice->GetRawId(audioRawId);
aListener->mAudioDevice->GetMediaSource(audioSourceType);
}
// This is defined as safe on an inactive GUMCMSListener
aListener->Remove(); // really queues the remove
StreamListeners* listeners = GetWindowListeners(aWindowID);
if (!listeners) {
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(aWindowID);
RefPtr<nsPIDOMWindowInner> window = globalWindow ? globalWindow->AsInner()
: nullptr;
if (window != nullptr) {
RefPtr<GetUserMediaRequest> req =
new GetUserMediaRequest(window, NullString(), NullString());
obs->NotifyObservers(req, "recording-device-stopped", nullptr);
}
return;
}
listeners->RemoveElement(aListener);
if (listeners->Length() == 0) {
uint32_t length = listeners->Length();
if (hasVideoDevice) {
bool revokeVideoPermission = true;
for (uint32_t i = 0; i < length; ++i) {
RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
listeners->ElementAt(i);
if (hasVideoDevice && listener->mVideoDevice) {
nsString rawId;
listener->mVideoDevice->GetRawId(rawId);
if (videoRawId.Equals(rawId)) {
revokeVideoPermission = false;
break;
}
}
}
if (revokeVideoPermission) {
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(aWindowID);
RefPtr<nsPIDOMWindowInner> window = globalWindow ? globalWindow->AsInner()
: nullptr;
RefPtr<GetUserMediaRequest> req =
new GetUserMediaRequest(window, videoRawId, videoSourceType);
obs->NotifyObservers(req, "recording-device-stopped", nullptr);
}
}
if (hasAudioDevice) {
bool revokeAudioPermission = true;
for (uint32_t i = 0; i < length; ++i) {
RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
listeners->ElementAt(i);
if (hasAudioDevice && listener->mAudioDevice) {
nsString rawId;
listener->mAudioDevice->GetRawId(rawId);
if (audioRawId.Equals(rawId)) {
revokeAudioPermission = false;
break;
}
}
}
if (revokeAudioPermission) {
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(aWindowID);
RefPtr<nsPIDOMWindowInner> window = globalWindow ? globalWindow->AsInner()
: nullptr;
RefPtr<GetUserMediaRequest> req =
new GetUserMediaRequest(window, audioRawId, audioSourceType);
obs->NotifyObservers(req, "recording-device-stopped", nullptr);
}
}
if (length == 0) {
RemoveWindowID(aWindowID);
// listeners has been deleted here
}

View File

@ -6,11 +6,20 @@
* This is an internal IDL file
*/
// for gUM request start (getUserMedia:request) notification,
// rawID and mediaSource won't be set.
// for gUM request stop (recording-device-stopped) notification due to page reload,
// only windowID will be set.
// for gUM request stop (recording-device-stopped) notification due to track stop,
// only windowID, rawID and mediaSource will be set
[NoInterfaceObject]
interface GetUserMediaRequest {
readonly attribute unsigned long long windowID;
readonly attribute unsigned long long innerWindowID;
readonly attribute DOMString callID;
readonly attribute DOMString rawID;
readonly attribute DOMString mediaSource;
MediaStreamConstraints getConstraints();
readonly attribute boolean isSecure;
};