Bug 1189060 - let webrtcUI.jsm etc. block initial Offer/Answer exchange through hook. r=florian,fabrice,mfinkle,mt

This commit is contained in:
Jan-Ivar Bruaroey 2015-08-07 15:22:30 -04:00
parent de5ade95f6
commit 2aa6eee49f
8 changed files with 239 additions and 37 deletions

View File

@ -525,6 +525,8 @@ addEventListener("DOMServiceWorkerFocusClient", function(event) {
}, false);
ContentWebRTC.init();
addMessageListener("rtcpeer:Allow", ContentWebRTC);
addMessageListener("rtcpeer:Deny", ContentWebRTC);
addMessageListener("webrtc:Allow", ContentWebRTC);
addMessageListener("webrtc:Deny", ContentWebRTC);
addMessageListener("webrtc:StopSharing", ContentWebRTC);

View File

@ -22,7 +22,8 @@ this.ContentWebRTC = {
return;
this._initialized = true;
Services.obs.addObserver(handleRequest, "getUserMedia:request", false);
Services.obs.addObserver(handleGUMRequest, "getUserMedia:request", false);
Services.obs.addObserver(handlePCRequest, "PeerConnection:request", false);
Services.obs.addObserver(updateIndicators, "recording-device-events", false);
Services.obs.addObserver(removeBrowserSpecificIndicator, "recording-window-ended", false);
},
@ -31,17 +32,31 @@ this.ContentWebRTC = {
handleEvent: function(aEvent) {
let contentWindow = aEvent.target.defaultView;
let mm = getMessageManagerForWindow(contentWindow);
for (let key of contentWindow.pendingGetUserMediaRequests.keys())
for (let key of contentWindow.pendingGetUserMediaRequests.keys()) {
mm.sendAsyncMessage("webrtc:CancelRequest", key);
}
for (let key of contentWindow.pendingPeerConnectionRequests.keys()) {
mm.sendAsyncMessage("rtcpeer:CancelRequest", key);
}
},
receiveMessage: function(aMessage) {
switch (aMessage.name) {
case "webrtc:Allow":
case "rtcpeer:Allow":
case "rtcpeer:Deny": {
let callID = aMessage.data.callID;
let contentWindow = Services.wm.getOuterWindowWithId(aMessage.data.windowID);
forgetPCRequest(contentWindow, callID);
let topic = (aMessage.name == "rtcpeer:Allow") ? "PeerConnection:response:allow" :
"PeerConnection:response:deny";
Services.obs.notifyObservers(null, topic, callID);
break;
}
case "webrtc:Allow": {
let callID = aMessage.data.callID;
let contentWindow = Services.wm.getOuterWindowWithId(aMessage.data.windowID);
let devices = contentWindow.pendingGetUserMediaRequests.get(callID);
forgetRequest(contentWindow, callID);
forgetGUMRequest(contentWindow, callID);
let allowedDevices = Cc["@mozilla.org/supports-array;1"]
.createInstance(Ci.nsISupportsArray);
@ -50,8 +65,9 @@ this.ContentWebRTC = {
Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", callID);
break;
}
case "webrtc:Deny":
denyRequest(aMessage.data);
denyGUMRequest(aMessage.data);
break;
case "webrtc:StopSharing":
Services.obs.notifyObservers(null, "getUserMedia:revoke", aMessage.data);
@ -60,7 +76,30 @@ this.ContentWebRTC = {
}
};
function handleRequest(aSubject, aTopic, aData) {
function handlePCRequest(aSubject, aTopic, aData) {
// Need to access JS object behind XPCOM wrapper added by observer API (using a
// WebIDL interface didn't work here as object comes from JSImplemented code).
aSubject = aSubject.wrappedJSObject;
let { windowID, callID, isSecure } = aSubject;
let contentWindow = Services.wm.getOuterWindowWithId(windowID);
if (!contentWindow.pendingPeerConnectionRequests) {
setupPendingListsInitially(contentWindow);
}
contentWindow.pendingPeerConnectionRequests.add(callID);
let request = {
callID: callID,
windowID: windowID,
documentURI: contentWindow.document.documentURI,
secure: isSecure,
};
let mm = getMessageManagerForWindow(contentWindow);
mm.sendAsyncMessage("rtcpeer:Request", request);
}
function handleGUMRequest(aSubject, aTopic, aData) {
let constraints = aSubject.getConstraints();
let secure = aSubject.isSecure;
let contentWindow = Services.wm.getOuterWindowWithId(aSubject.windowID);
@ -74,7 +113,7 @@ function handleRequest(aSubject, aTopic, aData) {
function (error) {
// bug 827146 -- In the future, the UI should catch NotFoundError
// and allow the user to plug in a device, instead of immediately failing.
denyRequest({callID: aSubject.callID}, error);
denyGUMRequest({callID: aSubject.callID}, error);
},
aSubject.innerWindowID);
}
@ -123,13 +162,12 @@ function prompt(aContentWindow, aWindowID, aCallID, aConstraints, aDevices, aSec
requestTypes.push(sharingAudio ? "AudioCapture" : "Microphone");
if (!requestTypes.length) {
denyRequest({callID: aCallID}, "NotFoundError");
denyGUMRequest({callID: aCallID}, "NotFoundError");
return;
}
if (!aContentWindow.pendingGetUserMediaRequests) {
aContentWindow.pendingGetUserMediaRequests = new Map();
aContentWindow.addEventListener("unload", ContentWebRTC);
setupPendingListsInitially(aContentWindow);
}
aContentWindow.pendingGetUserMediaRequests.set(aCallID, devices);
@ -149,7 +187,7 @@ function prompt(aContentWindow, aWindowID, aCallID, aConstraints, aDevices, aSec
mm.sendAsyncMessage("webrtc:Request", request);
}
function denyRequest(aData, aError) {
function denyGUMRequest(aData, aError) {
let msg = null;
if (aError) {
msg = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
@ -161,16 +199,36 @@ function denyRequest(aData, aError) {
return;
let contentWindow = Services.wm.getOuterWindowWithId(aData.windowID);
if (contentWindow.pendingGetUserMediaRequests)
forgetRequest(contentWindow, aData.callID);
forgetGUMRequest(contentWindow, aData.callID);
}
function forgetRequest(aContentWindow, aCallID) {
function forgetGUMRequest(aContentWindow, aCallID) {
aContentWindow.pendingGetUserMediaRequests.delete(aCallID);
if (aContentWindow.pendingGetUserMediaRequests.size)
return;
forgetPendingListsEventually(aContentWindow);
}
aContentWindow.removeEventListener("unload", ContentWebRTC);
function forgetPCRequest(aContentWindow, aCallID) {
aContentWindow.pendingPeerConnectionRequests.delete(aCallID);
forgetPendingListsEventually(aContentWindow);
}
function setupPendingListsInitially(aContentWindow) {
if (aContentWindow.pendingGetUserMediaRequests) {
return;
}
aContentWindow.pendingGetUserMediaRequests = new Map();
aContentWindow.pendingPeerConnectionRequests = new Set();
aContentWindow.addEventListener("unload", ContentWebRTC);
}
function forgetPendingListsEventually(aContentWindow) {
if (aContentWindow.pendingGetUserMediaRequests.size ||
aContentWindow.pendingPeerConnectionRequests.size) {
return;
}
aContentWindow.pendingGetUserMediaRequests = null;
aContentWindow.pendingPeerConnectionRequests = null;
aContentWindow.removeEventListener("unload", ContentWebRTC);
}
function updateIndicators() {

View File

@ -29,6 +29,8 @@ this.webrtcUI = {
let mm = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
mm.addMessageListener("rtcpeer:Request", this);
mm.addMessageListener("rtcpeer:CancelRequest", this);
mm.addMessageListener("webrtc:Request", this);
mm.addMessageListener("webrtc:CancelRequest", this);
mm.addMessageListener("webrtc:UpdateBrowserIndicators", this);
@ -44,6 +46,8 @@ this.webrtcUI = {
let mm = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
mm.removeMessageListener("rtcpeer:Request", this);
mm.removeMessageListener("rtcpeer:CancelRequest", this);
mm.removeMessageListener("webrtc:Request", this);
mm.removeMessageListener("webrtc:CancelRequest", this);
mm.removeMessageListener("webrtc:UpdateBrowserIndicators", this);
@ -124,6 +128,43 @@ this.webrtcUI = {
receiveMessage: function(aMessage) {
switch (aMessage.name) {
// Add-ons can override stock permission behavior by doing:
//
// var stockReceiveMessage = webrtcUI.receiveMessage;
//
// webrtcUI.receiveMessage = function(aMessage) {
// switch (aMessage.name) {
// case "rtcpeer:Request": {
// // new code.
// break;
// ...
// default:
// return stockReceiveMessage.call(this, aMessage);
//
// Intercepting gUM and peerConnection requests should let an add-on
// limit PeerConnection activity with automatic rules and/or prompts
// in a sensible manner that avoids double-prompting in typical
// gUM+PeerConnection scenarios. For example:
//
// State Sample Action
// --------------------------------------------------------------
// No IP leaked yet + No gUM granted Warn user
// No IP leaked yet + gUM granted Avoid extra dialog
// No IP leaked yet + gUM request pending. Delay until gUM grant
// IP already leaked Too late to warn
case "rtcpeer:Request": {
// Always allow. This code-point exists for add-ons to override.
let request = aMessage.data;
let mm = aMessage.target.messageManager;
mm.sendAsyncMessage("rtcpeer:Allow", { callID: request.callID,
windowID: request.windowID });
break;
}
case "rtcpeer:CancelRequest":
// No data to release. This code-point exists for add-ons to override.
break;
case "webrtc:Request":
prompt(aMessage.target, aMessage.data);
break;
@ -441,7 +482,7 @@ function prompt(aBrowser, aRequest) {
return;
}
let mm = notification.browser.messageManager
let mm = notification.browser.messageManager;
mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
windowID: aRequest.windowID,
devices: allowedDevices});

View File

@ -13,6 +13,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "PeerConnectionIdp",
"resource://gre/modules/media/PeerConnectionIdp.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "convertToRTCStatsReport",
"resource://gre/modules/media/RTCStatsReport.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
@ -40,11 +42,14 @@ function GlobalPCList() {
this._list = {};
this._networkdown = false; // XXX Need to query current state somehow
this._lifecycleobservers = {};
this._nextId = 1;
Services.obs.addObserver(this, "inner-window-destroyed", true);
Services.obs.addObserver(this, "profile-change-net-teardown", true);
Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
Services.obs.addObserver(this, "network:offline-status-changed", true);
Services.obs.addObserver(this, "gmp-plugin-crash", true);
Services.obs.addObserver(this, "PeerConnection:response:allow", true);
Services.obs.addObserver(this, "PeerConnection:response:deny", true);
if (Cc["@mozilla.org/childprocessmessagemanager;1"]) {
let mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
mm.addMessageListener("gmp-plugin-crash", this);
@ -78,9 +83,23 @@ GlobalPCList.prototype = {
} else {
this._list[winID] = [Cu.getWeakReference(pc)];
}
pc._globalPCListId = this._nextId++;
this.removeNullRefs(winID);
},
findPC: function(globalPCListId) {
for (let winId in this._list) {
if (this._list.hasOwnProperty(winId)) {
for (let pcref of this._list[winId]) {
let pc = pcref.get();
if (pc && pc._globalPCListId == globalPCListId) {
return pc;
}
}
}
}
},
removeNullRefs: function(winID) {
if (this._list[winID] === undefined) {
return;
@ -187,6 +206,18 @@ GlobalPCList.prototype = {
let data = { pluginID, pluginName };
this.handleGMPCrash(data);
}
} else if (topic == "PeerConnection:response:allow" ||
topic == "PeerConnection:response:deny") {
var pc = this.findPC(data);
if (pc) {
if (topic == "PeerConnection:response:allow") {
pc._settlePermission.allow();
} else {
let err = new pc._win.DOMException("The operation is insecure.",
"SecurityError");
pc._settlePermission.deny(err);
}
}
}
},
@ -355,6 +386,7 @@ RTCPeerConnection.prototype = {
}
// Save the appId
this._appId = Cu.getWebIDLCallerPrincipal().appId;
this._https = this._win.document.documentURIObject.schemeIs("https");
// Get the offline status for this appId
let appOffline = false;
@ -690,13 +722,12 @@ RTCPeerConnection.prototype = {
let origin = Cu.getWebIDLCallerPrincipal().origin;
return this._chain(() => {
let p = this._certificateReady.then(
() => new this._win.Promise((resolve, reject) => {
let p = Promise.all([this.getPermission(), this._certificateReady])
.then(() => new this._win.Promise((resolve, reject) => {
this._onCreateOfferSuccess = resolve;
this._onCreateOfferFailure = reject;
this._impl.createOffer(options);
})
);
}));
p = this._addIdentityAssertion(p, origin);
return p.then(
sdp => new this._win.mozRTCSessionDescription({ type: "offer", sdp: sdp }));
@ -715,8 +746,8 @@ RTCPeerConnection.prototype = {
return this._legacyCatch(onSuccess, onError, () => {
let origin = Cu.getWebIDLCallerPrincipal().origin;
return this._chain(() => {
let p = this._certificateReady.then(
() => new this._win.Promise((resolve, reject) => {
let p = Promise.all([this.getPermission(), this._certificateReady])
.then(() => new this._win.Promise((resolve, reject) => {
// We give up line-numbers in errors by doing this here, but do all
// state-checks inside the chain, to support the legacy feature that
// callers don't have to wait for setRemoteDescription to finish.
@ -731,8 +762,7 @@ RTCPeerConnection.prototype = {
this._onCreateAnswerSuccess = resolve;
this._onCreateAnswerFailure = reject;
this._impl.createAnswer();
})
);
}));
p = this._addIdentityAssertion(p, origin);
return p.then(sdp => {
return new this._win.mozRTCSessionDescription({ type: "answer", sdp: sdp });
@ -741,6 +771,27 @@ RTCPeerConnection.prototype = {
});
},
getPermission: function() {
if (this._havePermission) {
return this._havePermission;
}
if (AppConstants.MOZ_B2G ||
Services.prefs.getBoolPref("media.navigator.permission.disabled")) {
return this._havePermission = Promise.resolve();
}
return this._havePermission = new Promise((resolve, reject) => {
this._settlePermission = { allow: resolve, deny: reject };
let outerId = this._win.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
let request = { windowID: outerId,
innerWindowId: this._winID,
callID: this._globalPCListId,
isSecure: this._https };
request.wrappedJSObject = request;
Services.obs.notifyObservers(request, "PeerConnection:request", null);
});
},
setLocalDescription: function(desc, onSuccess, onError) {
return this._legacyCatch(onSuccess, onError, () => {
this._localType = desc.type;
@ -771,11 +822,12 @@ RTCPeerConnection.prototype = {
"InvalidParameterError");
}
return this._chain(() => new this._win.Promise((resolve, reject) => {
return this._chain(() => this.getPermission()
.then(() => new this._win.Promise((resolve, reject) => {
this._onSetLocalDescriptionSuccess = resolve;
this._onSetLocalDescriptionFailure = reject;
this._impl.setLocalDescription(type, desc.sdp);
}));
})));
});
},
@ -858,11 +910,12 @@ RTCPeerConnection.prototype = {
let origin = Cu.getWebIDLCallerPrincipal().origin;
return this._chain(() => {
let setRem = new this._win.Promise((resolve, reject) => {
this._onSetRemoteDescriptionSuccess = resolve;
this._onSetRemoteDescriptionFailure = reject;
this._impl.setRemoteDescription(type, desc.sdp);
});
let setRem = this.getPermission()
.then(() => new this._win.Promise((resolve, reject) => {
this._onSetRemoteDescriptionSuccess = resolve;
this._onSetRemoteDescriptionFailure = reject;
this._impl.setRemoteDescription(type, desc.sdp);
}));
if (desc.type === "rollback") {
return setRem;

View File

@ -3,14 +3,33 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["WebrtcUI"];
XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm");
var WebrtcUI = {
_notificationId: null,
// Add-ons can override stock permission behavior by doing:
//
// var stockObserve = WebrtcUI.observe;
//
// webrtcUI.observe = function(aSubject, aTopic, aData) {
// switch (aTopic) {
// case "PeerConnection:request": {
// // new code.
// break;
// ...
// default:
// return stockObserve.call(this, aSubject, aTopic, aData);
//
// See browser/modules/webrtcUI.jsm for details.
observe: function(aSubject, aTopic, aData) {
if (aTopic === "getUserMedia:request") {
this.handleRequest(aSubject, aTopic, aData);
this.handleGumRequest(aSubject, aTopic, aData);
} else if (aTopic === "PeerConnection:request") {
this.handlePCRequest(aSubject, aTopic, aData);
} else if (aTopic === "recording-device-events") {
switch (aData) {
case "shutdown":
@ -72,7 +91,17 @@ var WebrtcUI = {
}
},
handleRequest: function handleRequest(aSubject, aTopic, aData) {
handlePCRequest: function handlePCRequest(aSubject, aTopic, aData) {
aSubject = aSubject.wrappedJSObject;
let { callID } = aSubject;
// Also available: windowID, isSecure, innerWindowID. For contentWindow do:
//
// let contentWindow = Services.wm.getOuterWindowWithId(windowID);
Services.obs.notifyObservers(null, "PeerConnection:response:allow", callID);
},
handleGumRequest: function handleGumRequest(aSubject, aTopic, aData) {
let constraints = aSubject.getConstraints();
let contentWindow = Services.wm.getOuterWindowWithId(aSubject.windowID);

View File

@ -162,7 +162,9 @@ if (AppConstants.NIGHTLY_BUILD) {
}
if (AppConstants.MOZ_WEBRTC) {
lazilyLoadedObserverScripts.push(
["WebrtcUI", ["getUserMedia:request", "recording-device-events"], "chrome://browser/content/WebrtcUI.js"])
["WebrtcUI", ["getUserMedia:request",
"PeerConnection:request",
"recording-device-events"], "chrome://browser/content/WebrtcUI.js"])
}
lazilyLoadedObserverScripts.forEach(function (aScript) {

View File

@ -101,6 +101,14 @@ this.AppConstants = Object.freeze({
false,
#endif
# MOZ_B2G covers both device and desktop b2g
MOZ_B2G:
#ifdef MOZ_B2G
true,
#else
false,
#endif
# NOTE! XP_LINUX has to go after MOZ_WIDGET_ANDROID otherwise Android
# builds will be misidentified as linux.
platform:

View File

@ -13,7 +13,15 @@ let Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
function handleRequest(aSubject, aTopic, aData) {
function handlePCRequest(aSubject, aTopic, aData) {
aSubject = aSubject.wrappedJSObject;
let { windowID, innerWindowID, callID, isSecure } = aSubject;
let contentWindow = Services.wm.getOuterWindowWithId(windowID);
Services.obs.notifyObservers(null, "PeerConnection:response:allow", callID);
}
function handleGumRequest(aSubject, aTopic, aData) {
let { windowID, callID } = aSubject;
let constraints = aSubject.getConstraints();
let contentWindow = Services.wm.getOuterWindowWithId(windowID);
@ -100,4 +108,5 @@ function denyRequest(aCallID, aError) {
Services.obs.notifyObservers(msg, "getUserMedia:response:deny", aCallID);
}
Services.obs.addObserver(handleRequest, "getUserMedia:request", false);
Services.obs.addObserver(handleGumRequest, "getUserMedia:request", false);
Services.obs.addObserver(handlePCRequest, "PeerConnection:request", false);