Bug 1649032 - If the user manages to close the WebRTC indicator, close all of the active streams. r=pbz

Differential Revision: https://phabricator.services.mozilla.com/D82989
This commit is contained in:
Mike Conley 2020-07-17 01:22:10 +00:00
Родитель d432995562
Коммит be5766d15a
2 изменённых файлов: 201 добавлений и 101 удалений

Просмотреть файл

@ -8,12 +8,6 @@ const { XPCOMUtils } = ChromeUtils.import(
);
const { webrtcUI } = ChromeUtils.import("resource:///modules/webrtcUI.jsm");
ChromeUtils.defineModuleGetter(
this,
"SitePermissions",
"resource:///modules/SitePermissions.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"AppConstants",
@ -47,6 +41,22 @@ function updateIndicatorState() {
WebRTCIndicator.updateIndicatorState();
}
/**
* Public function called by webrtcUI to indicate that webrtcUI
* is about to close the indicator. This is so that we can differentiate
* between closes that are caused by webrtcUI, and closes that are
* caused by other reasons (like the user closing the window via the
* OS window controls somehow).
*
* If the window is closed without having called this method first, the
* indicator will ask webrtcUI to shutdown any remaining streams and then
* select and focus the most recent browser tab that a stream was shared
* with.
*/
function closingInternally() {
WebRTCIndicator.closingInternally();
}
/**
* Main control object for the WebRTC global indicator
*/
@ -66,6 +76,7 @@ const WebRTCIndicator = {
this.updatingIndicatorState = false;
this.loaded = false;
this.isClosingInternally = false;
if (AppConstants.platform == "macosx") {
this.macOSIndicator = new MacOSWebRTCStatusbarIndicator();
@ -341,6 +352,21 @@ const WebRTCIndicator = {
onUnload() {
if (this.macOSIndicator) {
this.macOSIndicator.close();
this.macOSIndicator = null;
}
if (!this.isClosingInternally) {
// Something has closed the indicator, but it wasn't webrtcUI. This
// means we might still have some streams being shared. To protect
// the user from unknowingly sharing streams, we shut those streams
// down.
let activeStreams = webrtcUI.getActiveStreams(
true /* camera */,
true /* microphone */,
true /* screen */,
true /* window */
);
webrtcUI.stopSharingStreams(activeStreams);
}
},
@ -353,7 +379,7 @@ const WebRTCIndicator = {
true /* screen */,
false /* window */
);
this.stopSharingScreen(activeStreams);
webrtcUI.stopSharingStreams(activeStreams);
break;
}
case "stop-sharing-window": {
@ -363,14 +389,22 @@ const WebRTCIndicator = {
false /* screen */,
true /* window */
);
if (this.sharingBrowserWindow) {
let browserWindowStreams = activeStreams.filter(stream => {
return stream.devices.some(device => device.scary);
});
this.stopSharingScreen(browserWindowStreams);
} else {
this.stopSharingScreen(activeStreams);
webrtcUI.stopSharingStreams(
browserWindowStreams,
false /* camera */,
false /* microphone */,
false /* screen */,
true /* window */
);
break;
}
webrtcUI.stopSharingStreams(activeStreams);
break;
}
case "microphone-button":
@ -392,97 +426,6 @@ const WebRTCIndicator = {
}
},
/**
* Finds the most recent share in the set of active streams passed,
* and ends it.
*
* @param activeStreams (Array<Object>)
* An array of streams obtained via webrtcUI.getActiveStreams.
* It is presumed that one or more of those streams includes
* one that is sharing a screen or window.
*/
stopSharingScreen(activeStreams) {
if (!activeStreams.length) {
return;
}
// We'll default to choosing the most recent active stream to
// revoke the permissions from.
let chosenStream = activeStreams[activeStreams.length - 1];
let { browser } = chosenStream;
// This intentionally copies its approach from browser-siteIdentity.js,
// which powers the permission revocation from the Permissions Panel.
// Ideally, we would de-duplicate this with a shared revocation mechanism,
// but to lower the risk of uplifting this change, we keep it separate for
// now.
let gBrowser = browser.getTabBrowser();
if (!gBrowser) {
Cu.reportError("Can't stop sharing screen - cannot find gBrowser.");
return;
}
let tab = gBrowser.getTabForBrowser(browser);
if (!tab) {
Cu.reportError("Can't stop sharing screen - cannot find tab.");
return;
}
let permissions = SitePermissions.getAllPermissionDetailsForBrowser(
browser
);
let webrtcState = tab._sharingState.webRTC;
let windowId = `screen:${webrtcState.windowId}`;
// If WebRTC device or screen permissions are in use, we need to find
// the associated permission item to set the sharingState field.
if (webrtcState.screen) {
let found = false;
for (let permission of permissions) {
if (permission.id != "screen") {
continue;
}
found = true;
permission.sharingState = webrtcState.screen;
break;
}
if (!found) {
// If the permission item we were looking for doesn't exist,
// the user has temporarily allowed sharing and we need to add
// an item in the permissions array to reflect this.
permissions.push({
id: "screen",
state: SitePermissions.ALLOW,
scope: SitePermissions.SCOPE_REQUEST,
sharingState: webrtcState.screen,
});
}
}
let permission = permissions.find(perm => {
return perm.id == "screen";
});
if (!permission) {
Cu.reportError(
"Can't stop sharing screen - cannot find screen permission."
);
return;
}
let bc = webrtcState.browsingContext;
bc.currentWindowGlobal
.getActor("WebRTC")
.sendAsyncMessage("webrtc:StopSharing", windowId);
webrtcUI.forgetActivePermissionsFromBrowser(browser);
SitePermissions.removeFromPrincipal(
browser.contentPrincipal,
permission.id,
browser
);
},
/**
* Find the most recent share in the set of active streams passed,
* and opens up the Permissions Panel for the associated tab to
@ -517,6 +460,13 @@ const WebRTCIndicator = {
docEl.removeAttribute(attr);
}
},
/**
* See the documentation on the script global closingInternally() function.
*/
closingInternally() {
this.isClosingInternally = true;
},
};
WebRTCIndicator.init();

Просмотреть файл

@ -31,6 +31,11 @@ ChromeUtils.defineModuleGetter(
"XPCOMUtils",
"resource://gre/modules/XPCOMUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"SitePermissions",
"resource:///modules/SitePermissions.jsm"
);
var webrtcUI = {
initialized: false,
@ -482,6 +487,140 @@ var webrtcUI = {
this.updateGlobalIndicator();
},
/**
* Given some set of streams, stops device access for those streams.
* Optionally, it's possible to stop a subset of the devices on those
* streams by passing in optional arguments.
*
* Once the streams have been stopped, this method will also find the
* newest stream's <xul:browser> and window, focus the window, and
* select the browser.
*
* @param {Array<Object>} activeStreams - An array of streams obtained via webrtcUI.getActiveStreams.
* @param {boolean} stopCameras - True to stop the camera streams (defaults to true)
* @param {boolean} stopMics - True to stop the microphone streams (defaults to true)
* @param {boolean} stopScreens - True to stop the screen streams (defaults to true)
* @param {boolean} stopWindows - True to stop the window streams (defaults to true)
*/
stopSharingStreams(
activeStreams,
stopCameras = true,
stopMics = true,
stopScreens = true,
stopWindows = true
) {
if (!activeStreams.length) {
return;
}
let mostRecentStream = activeStreams[activeStreams.length - 1];
let { browser: browserToSelect } = mostRecentStream;
for (let stream of activeStreams) {
let { browser } = stream;
let gBrowser = browser.getTabBrowser();
if (!gBrowser) {
Cu.reportError("Can't stop sharing stream - cannot find gBrowser.");
continue;
}
let tab = gBrowser.getTabForBrowser(browser);
if (!tab) {
Cu.reportError("Can't stop sharing stream - cannot find tab.");
continue;
}
let permissions = SitePermissions.getAllPermissionDetailsForBrowser(
browser
);
let webrtcState = tab._sharingState.webRTC;
let clearRequested = {
camera: stopCameras,
microphone: stopMics,
screen: stopScreens || stopWindows,
};
for (let id of ["camera", "microphone", "screen"]) {
if (webrtcState[id] && clearRequested[id]) {
let found = false;
for (let permission of permissions) {
if (permission.id != id) {
continue;
}
found = true;
permission.sharingState = webrtcState[id];
break;
}
if (!found) {
// If the permission item we were looking for doesn't exist,
// the user has temporarily allowed sharing and we need to add
// an item in the permissions array to reflect this.
permissions.push({
id,
state: SitePermissions.ALLOW,
scope: SitePermissions.SCOPE_REQUEST,
sharingState: webrtcState[id],
});
}
}
}
for (let permission of permissions) {
let windowId = tab._sharingState.webRTC.windowId;
if (permission.id == "screen") {
windowId = `screen:${webrtcState.windowId}`;
} else if (permission.id == "camera" || permission.id == "microphone") {
// If we set persistent permissions or the sharing has
// started due to existing persistent permissions, we need
// to handle removing these even for frames with different hostnames.
let origins = browser.getDevicePermissionOrigins("webrtc");
for (let origin of origins) {
// It's not possible to stop sharing one of camera/microphone
// without the other.
let principal;
for (let id of ["camera", "microphone"]) {
if (webrtcState[id]) {
if (!principal) {
principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
origin
);
}
let perm = SitePermissions.getForPrincipal(principal, id);
if (
perm.state == SitePermissions.ALLOW &&
perm.scope == SitePermissions.SCOPE_PERSISTENT
) {
SitePermissions.removeFromPrincipal(principal, id);
}
}
}
}
}
let bc = webrtcState.browsingContext;
bc.currentWindowGlobal
.getActor("WebRTC")
.sendAsyncMessage("webrtc:StopSharing", windowId);
webrtcUI.forgetActivePermissionsFromBrowser(browser);
SitePermissions.removeFromPrincipal(
browser.contentPrincipal,
permission.id,
browser
);
}
}
let window = browserToSelect.ownerGlobal;
let gBrowser = browserToSelect.getTabBrowser();
let tab = gBrowser.getTabForBrowser(browserToSelect);
window.focus();
gBrowser.selectedTab = tab;
},
updateIndicators(aTopBrowsingContext) {
let tabState = this.getCombinedStateForBrowser(aTopBrowsingContext);
@ -644,6 +783,17 @@ var webrtcUI = {
}
}
} else if (gIndicatorWindow) {
if (
!webrtcUI.useLegacyGlobalIndicator &&
gIndicatorWindow.closingInternally
) {
// Before calling .close(), we call .closingInternally() to allow us to
// differentiate between situations where the indicator closes because
// we no longer want to show the indicator (this case), and cases where
// the user has found a way to close the indicator via OS window control
// mechanisms.
gIndicatorWindow.closingInternally();
}
gIndicatorWindow.close();
gIndicatorWindow = null;
}