зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1712892 skip selectAudioOutput() prompt if the requested device has been previously allowed r=jib
Permission grants are persistent, supporting content use of selectAudioOutput(previousDeviceId) to maintain the same output(s) on subsequent same-origin page loads. Permissions can be revoked from the site permissions panel. Differential Revision: https://phabricator.services.mozilla.com/D162234
This commit is contained in:
Родитель
281ca8cf56
Коммит
7d1c6fc239
|
@ -380,8 +380,36 @@ class WebRTCParent extends JSWindowActorParent {
|
|||
if (aRequest.sharingScreen) {
|
||||
return false;
|
||||
}
|
||||
if (aRequest.audioOutputDevices?.length) {
|
||||
return false;
|
||||
let {
|
||||
callID,
|
||||
windowID,
|
||||
audioInputDevices,
|
||||
videoInputDevices,
|
||||
audioOutputDevices,
|
||||
hasInherentAudioConstraints,
|
||||
hasInherentVideoConstraints,
|
||||
audioOutputId,
|
||||
} = aRequest;
|
||||
|
||||
if (audioOutputDevices?.length) {
|
||||
// Prompt if a specific device is not requested, available and allowed.
|
||||
let device = audioOutputDevices.find(({ id }) => id == audioOutputId);
|
||||
if (
|
||||
!device ||
|
||||
!lazy.SitePermissions.getForPrincipal(
|
||||
aPrincipal,
|
||||
["speaker", device.id].join("^"),
|
||||
this.getBrowser()
|
||||
).state == lazy.SitePermissions.ALLOW
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
this.sendAsyncMessage("webrtc:Allow", {
|
||||
callID,
|
||||
windowID,
|
||||
devices: [device.deviceIndex],
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
let { perms } = Services;
|
||||
|
@ -400,15 +428,6 @@ class WebRTCParent extends JSWindowActorParent {
|
|||
aRequest.secondOrigin;
|
||||
|
||||
let map = lazy.webrtcUI.activePerms.get(this.manager.outerWindowId);
|
||||
let {
|
||||
callID,
|
||||
windowID,
|
||||
audioInputDevices,
|
||||
videoInputDevices,
|
||||
hasInherentAudioConstraints,
|
||||
hasInherentVideoConstraints,
|
||||
} = aRequest;
|
||||
|
||||
// We consider a camera or mic active if it is active or was active within a
|
||||
// grace period of milliseconds ago.
|
||||
const isAllowed = ({ mediaSource, rawId }, permissionID) =>
|
||||
|
@ -1149,6 +1168,14 @@ function prompt(aActor, aBrowser, aRequest) {
|
|||
let allowSpeaker = audioDeviceIndex != "-1";
|
||||
if (allowSpeaker) {
|
||||
allowedDevices.push(audioDeviceIndex);
|
||||
let { id } = audioOutputDevices.find(
|
||||
({ deviceIndex }) => deviceIndex == audioDeviceIndex
|
||||
);
|
||||
lazy.SitePermissions.setForPrincipal(
|
||||
principal,
|
||||
["speaker", id].join("^"),
|
||||
lazy.SitePermissions.ALLOW
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,12 +8,18 @@ const permissionError =
|
|||
"error: NotAllowedError: The request is not allowed " +
|
||||
"by the user agent or the platform in the current context.";
|
||||
|
||||
async function requestAudioOutputExpectingPrompt() {
|
||||
async function requestAudioOutput(options) {
|
||||
await Promise.all([
|
||||
promisePopupNotificationShown("webRTC-shareDevices"),
|
||||
expectObserverCalled("getUserMedia:request"),
|
||||
expectObserverCalled("recording-window-ended"),
|
||||
promiseRequestAudioOutput(),
|
||||
promiseRequestAudioOutput(options),
|
||||
]);
|
||||
}
|
||||
|
||||
async function requestAudioOutputExpectingPrompt(options) {
|
||||
await Promise.all([
|
||||
promisePopupNotificationShown("webRTC-shareDevices"),
|
||||
requestAudioOutput(options),
|
||||
]);
|
||||
|
||||
is(
|
||||
|
@ -54,20 +60,24 @@ async function simulateAudioOutputRequest(options) {
|
|||
);
|
||||
}
|
||||
|
||||
async function allow() {
|
||||
async function allowPrompt() {
|
||||
const observerPromise = expectObserverCalled("getUserMedia:response:allow");
|
||||
await promiseMessage("ok", () => {
|
||||
PopupNotifications.panel.firstElementChild.button.click();
|
||||
});
|
||||
PopupNotifications.panel.firstElementChild.button.click();
|
||||
await observerPromise;
|
||||
}
|
||||
|
||||
async function allow() {
|
||||
await Promise.all([promiseMessage("ok"), allowPrompt()]);
|
||||
}
|
||||
|
||||
async function denyPrompt() {
|
||||
const observerPromise = expectObserverCalled("getUserMedia:response:deny");
|
||||
activateSecondaryAction(kActionDeny);
|
||||
await observerPromise;
|
||||
}
|
||||
|
||||
async function deny() {
|
||||
const observerPromise = expectObserverCalled("getUserMedia:response:deny");
|
||||
await promiseMessage(permissionError, () => {
|
||||
activateSecondaryAction(kActionDeny);
|
||||
});
|
||||
await observerPromise;
|
||||
await Promise.all([promiseMessage(permissionError), denyPrompt()]);
|
||||
}
|
||||
|
||||
async function escapePrompt() {
|
||||
|
@ -82,13 +92,27 @@ async function escape() {
|
|||
|
||||
var gTests = [
|
||||
{
|
||||
desc: 'User clicks "Allow"',
|
||||
desc: 'User clicks "Allow" and revokes',
|
||||
run: async function checkAllow() {
|
||||
await requestAudioOutputExpectingPrompt();
|
||||
await allow();
|
||||
|
||||
info("selectAudioOutput() with no deviceId again should prompt again.");
|
||||
await requestAudioOutputExpectingPrompt();
|
||||
await allow();
|
||||
|
||||
info("selectAudioOutput() with same deviceId should not prompt again.");
|
||||
await Promise.all([
|
||||
expectObserverCalled("getUserMedia:response:allow"),
|
||||
promiseMessage("ok"),
|
||||
requestAudioOutput({ requestSameDevice: true }),
|
||||
]);
|
||||
|
||||
await revokePermission("speaker", true);
|
||||
info("Same deviceId should prompt again after revoked permission.");
|
||||
await requestAudioOutputExpectingPrompt({ requestSameDevice: true });
|
||||
await allow();
|
||||
await revokePermission("speaker", true);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -106,6 +130,7 @@ var gTests = [
|
|||
info("selectAudioOutput() after Esc should prompt again.");
|
||||
await requestAudioOutputExpectingPrompt();
|
||||
await allow();
|
||||
await revokePermission("speaker", true);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -122,16 +147,39 @@ var gTests = [
|
|||
{
|
||||
desc: "Multi Device with deviceId",
|
||||
run: async function checkMulti() {
|
||||
const deviceCount = 4;
|
||||
await Promise.all([
|
||||
promisePopupNotificationShown("webRTC-shareDevices"),
|
||||
simulateAudioOutputRequest({ deviceCount: 4, deviceId: "id 2" }),
|
||||
simulateAudioOutputRequest({ deviceCount, deviceId: "id 2" }),
|
||||
]);
|
||||
const selectorList = document.getElementById(
|
||||
`webRTC-selectSpeaker-menulist`
|
||||
);
|
||||
is(selectorList.selectedIndex, 2, "pre-selected index");
|
||||
checkDeviceSelectors(["speaker"]);
|
||||
await allowPrompt();
|
||||
|
||||
info("Expect same-device request allowed without prompt");
|
||||
await Promise.all([
|
||||
expectObserverCalled("getUserMedia:response:allow"),
|
||||
simulateAudioOutputRequest({ deviceCount, deviceId: "id 2" }),
|
||||
]);
|
||||
|
||||
info("Expect prompt for different-device request");
|
||||
await Promise.all([
|
||||
promisePopupNotificationShown("webRTC-shareDevices"),
|
||||
simulateAudioOutputRequest({ deviceCount, deviceId: "id 1" }),
|
||||
]);
|
||||
await denyPrompt();
|
||||
|
||||
info("Expect prompt again for denied-device request");
|
||||
await Promise.all([
|
||||
promisePopupNotificationShown("webRTC-shareDevices"),
|
||||
simulateAudioOutputRequest({ deviceCount, deviceId: "id 1" }),
|
||||
]);
|
||||
await escapePrompt();
|
||||
|
||||
await revokePermission("speaker", true);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -74,10 +74,15 @@ async function requestDevice(aAudio, aVideo, aShare, aBadDevice = false) {
|
|||
}
|
||||
}
|
||||
|
||||
async function requestAudioOutput() {
|
||||
let selectedAudioOutputId;
|
||||
async function requestAudioOutput(options = {}) {
|
||||
const audioOutputOptions = options.requestSameDevice && {
|
||||
deviceId: selectedAudioOutputId,
|
||||
};
|
||||
SpecialPowers.wrap(document).notifyUserGestureActivation();
|
||||
try {
|
||||
await navigator.mediaDevices.selectAudioOutput();
|
||||
({ deviceId: selectedAudioOutputId } =
|
||||
await navigator.mediaDevices.selectAudioOutput(audioOutputOptions));
|
||||
message("ok");
|
||||
} catch (err) {
|
||||
message("error: " + err);
|
||||
|
|
|
@ -705,12 +705,12 @@ async function promiseRequestDevice(
|
|||
);
|
||||
}
|
||||
|
||||
async function promiseRequestAudioOutput() {
|
||||
async function promiseRequestAudioOutput(options) {
|
||||
info("requesting audio output");
|
||||
const bc = gBrowser.selectedBrowser;
|
||||
return SpecialPowers.spawn(bc, [], async function() {
|
||||
return SpecialPowers.spawn(bc, [options], async function(opts) {
|
||||
const global = content.wrappedJSObject;
|
||||
global.requestAudioOutput();
|
||||
global.requestAudioOutput(Cu.cloneInto(opts, content));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче