Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const permissionError =
"error: NotAllowedError: The request is not allowed " +
"by the user agent or the platform in the current context.";
const PromptResult = {
ALLOW: "allow",
DENY: "deny",
PROMPT: "prompt",
};
const Perms = Services.perms;
async function promptNoDelegate(aThirdPartyOrgin, audio = true, video = true) {
// Persistent allowed first party origin
const uri = gBrowser.selectedBrowser.documentURI;
if (audio) {
PermissionTestUtils.add(uri, "microphone", Services.perms.ALLOW_ACTION);
}
if (video) {
PermissionTestUtils.add(uri, "camera", Services.perms.ALLOW_ACTION);
}
// Check that we get a prompt.
const observerPromise = expectObserverCalled("getUserMedia:request");
const promise = promisePopupNotificationShown("webRTC-shareDevices");
await promiseRequestDevice(audio, video, "frame4");
await promise;
await observerPromise;
// The 'Remember this decision' checkbox is hidden.
const notification = PopupNotifications.panel.firstElementChild;
const checkbox = notification.checkbox;
ok(!!checkbox, "checkbox is present");
ok(checkbox.hidden, "checkbox is not visible");
ok(!checkbox.checked, "checkbox not checked");
// Check the label of the notification should be the first party
is(
PopupNotifications.getNotification("webRTC-shareDevices").options.name,
uri.host,
"Use first party's origin"
);
// Check the secondName of the notification should be the third party
is(
PopupNotifications.getNotification("webRTC-shareDevices").options
.secondName,
aThirdPartyOrgin,
"Use third party's origin as secondName"
);
let indicator = promiseIndicatorWindow();
let observerPromise1 = expectObserverCalled("getUserMedia:response:allow");
let observerPromise2 = expectObserverCalled("recording-device-events");
await promiseMessage("ok", () =>
EventUtils.synthesizeMouseAtCenter(notification.button, {})
);
await observerPromise1;
await observerPromise2;
let state = await getMediaCaptureState();
is(
!!state.audio,
audio,
`expected microphone to be ${audio ? "" : "not"} shared`
);
is(
!!state.video,
video,
`expected camera to be ${video ? "" : "not"} shared`
);
await indicator;
await checkSharingUI({ audio, video }, undefined, undefined, {
video: { scope: SitePermissions.SCOPE_PERSISTENT },
audio: { scope: SitePermissions.SCOPE_PERSISTENT },
});
// Cleanup.
await closeStream(false, "frame4");
PermissionTestUtils.remove(uri, "camera");
PermissionTestUtils.remove(uri, "microphone");
}
async function promptNoDelegateScreenSharing(aThirdPartyOrgin) {
// Persistent allow screen sharing
const uri = gBrowser.selectedBrowser.documentURI;
PermissionTestUtils.add(uri, "screen", Services.perms.ALLOW_ACTION);
const observerPromise = expectObserverCalled("getUserMedia:request");
const promise = promisePopupNotificationShown("webRTC-shareDevices");
await promiseRequestDevice(false, true, "frame4", "screen");
await promise;
await observerPromise;
checkDeviceSelectors(["screen"]);
const notification = PopupNotifications.panel.firstElementChild;
// The 'Remember this decision' checkbox is hidden.
const checkbox = notification.checkbox;
ok(!!checkbox, "checkbox is present");
if (ALLOW_SILENCING_NOTIFICATIONS) {
ok(!checkbox.hidden, "Notification silencing checkbox is visible");
} else {
ok(checkbox.hidden, "checkbox is not visible");
}
ok(!checkbox.checked, "checkbox not checked");
// Check the label of the notification should be the first party
is(
PopupNotifications.getNotification("webRTC-shareDevices").options.name,
uri.host,
"Use first party's origin"
);
// Check the secondName of the notification should be the third party
is(
PopupNotifications.getNotification("webRTC-shareDevices").options
.secondName,
aThirdPartyOrgin,
"Use third party's origin as secondName"
);
const menulist = document.getElementById("webRTC-selectWindow-menulist");
const count = menulist.itemCount;
menulist.getItemAtIndex(count - 1).doCommand();
ok(!notification.button.disabled, "Allow button is enabled");
const indicator = promiseIndicatorWindow();
const observerPromise1 = expectObserverCalled("getUserMedia:response:allow");
const observerPromise2 = expectObserverCalled("recording-device-events");
await promiseMessage("ok", () =>
EventUtils.synthesizeMouseAtCenter(notification.button, {})
);
await observerPromise1;
await observerPromise2;
Assert.deepEqual(
await getMediaCaptureState(),
{ screen: "Screen" },
"expected screen to be shared"
);
await indicator;
await checkSharingUI({ screen: "Screen" }, undefined, undefined, {
screen: { scope: SitePermissions.SCOPE_PERSISTENT },
});
await closeStream(false, "frame4");
PermissionTestUtils.remove(uri, "screen");
}
var gTests = [
{
desc: "'Always Allow' enabled on third party pages, when origin is explicitly allowed",
run: async function checkNoAlwaysOnThirdParty() {
// Initially set both permissions to 'prompt'.
const uri = gBrowser.selectedBrowser.documentURI;
PermissionTestUtils.add(uri, "microphone", Services.perms.PROMPT_ACTION);
PermissionTestUtils.add(uri, "camera", Services.perms.PROMPT_ACTION);
const observerPromise = expectObserverCalled("getUserMedia:request");
const promise = promisePopupNotificationShown("webRTC-shareDevices");
await promiseRequestDevice(true, true, "frame1");
await promise;
await observerPromise;
checkDeviceSelectors(["microphone", "camera"]);
// The 'Remember this decision' checkbox is visible.
const notification = PopupNotifications.panel.firstElementChild;
const checkbox = notification.checkbox;
ok(!!checkbox, "checkbox is present");
ok(!checkbox.hidden, "checkbox is visible");
ok(!checkbox.checked, "checkbox not checked");
// Check the label of the notification should be the first party
is(
PopupNotifications.getNotification("webRTC-shareDevices").options.name,
uri.host,
"Use first party's origin"
);
const indicator = promiseIndicatorWindow();
const observerPromise1 = expectObserverCalled(
"getUserMedia:response:allow"
);
const observerPromise2 = expectObserverCalled("recording-device-events");
await promiseMessage("ok", () =>
EventUtils.synthesizeMouseAtCenter(notification.button, {})
);
await observerPromise1;
await observerPromise2;
Assert.deepEqual(
await getMediaCaptureState(),
{ audio: true, video: true },
"expected camera and microphone to be shared"
);
await indicator;
await checkSharingUI({ audio: true, video: true });
// Cleanup.
await closeStream(false, "frame1");
PermissionTestUtils.remove(uri, "camera");
PermissionTestUtils.remove(uri, "microphone");
},
},
{
desc: "'Always Allow' disabled when sharing screen in third party iframes, when origin is explicitly allowed",
run: async function checkScreenSharing() {
const observerPromise = expectObserverCalled("getUserMedia:request");
const promise = promisePopupNotificationShown("webRTC-shareDevices");
await promiseRequestDevice(false, true, "frame1", "screen");
await promise;
await observerPromise;
checkDeviceSelectors(["screen"]);
const notification = PopupNotifications.panel.firstElementChild;
// The 'Remember this decision' checkbox is visible.
const checkbox = notification.checkbox;
ok(!!checkbox, "checkbox is present");
ok(!checkbox.hidden, "checkbox is visible");
ok(!checkbox.checked, "checkbox not checked");
const menulist = document.getElementById("webRTC-selectWindow-menulist");
const count = menulist.itemCount;
Assert.greaterOrEqual(
count,
4,
"There should be the 'Select Window or Screen' item, a separator and at least one window and one screen"
);
const noWindowOrScreenItem = menulist.getItemAtIndex(0);
ok(
noWindowOrScreenItem.hasAttribute("selected"),
"the 'Select Window or Screen' item is selected"
);
is(
menulist.selectedItem,
noWindowOrScreenItem,
"'Select Window or Screen' is the selected item"
);
is(menulist.value, "-1", "no window or screen is selected by default");
ok(
noWindowOrScreenItem.disabled,
"'Select Window or Screen' item is disabled"
);
ok(notification.button.disabled, "Allow button is disabled");
ok(
notification.hasAttribute("invalidselection"),
"Notification is marked as invalid"
);
menulist.getItemAtIndex(count - 1).doCommand();
ok(!notification.button.disabled, "Allow button is enabled");
const indicator = promiseIndicatorWindow();
const observerPromise1 = expectObserverCalled(
"getUserMedia:response:allow"
);
const observerPromise2 = expectObserverCalled("recording-device-events");
await promiseMessage("ok", () =>
EventUtils.synthesizeMouseAtCenter(notification.button, {})
);
await observerPromise1;
await observerPromise2;
Assert.deepEqual(
await getMediaCaptureState(),
{ screen: "Screen" },
"expected screen to be shared"
);
await indicator;
await checkSharingUI({ screen: "Screen" });
await closeStream(false, "frame1");
},
},
{
desc: "getUserMedia use persistent permissions from first party",
run: async function checkUsePersistentPermissionsFirstParty() {
async function checkPersistentPermission(
aPermission,
aRequestType,
aIframeId,
aExpect
) {
info(
`Test persistent permission ${aPermission} type ${aRequestType} expect ${aExpect}`
);
const uri = gBrowser.selectedBrowser.documentURI;
// Persistent allow/deny for first party uri
PermissionTestUtils.add(uri, aRequestType, aPermission);
let audio = aRequestType == "microphone";
let video = aRequestType == "camera";
const screen = aRequestType == "screen" ? "screen" : undefined;
if (screen) {
audio = false;
video = true;
}
if (aExpect == PromptResult.PROMPT) {
// Check that we get a prompt.
const observerPromise = expectObserverCalled("getUserMedia:request");
const observerPromise1 = expectObserverCalled(
"getUserMedia:response:deny"
);
const observerPromise2 = expectObserverCalled(
"recording-window-ended"
);
const promise = promisePopupNotificationShown("webRTC-shareDevices");
await promiseRequestDevice(audio, video, aIframeId, screen);
await promise;
await observerPromise;
// Check the label of the notification should be the first party
is(
PopupNotifications.getNotification("webRTC-shareDevices").options
.name,
uri.host,
"Use first party's origin"
);
// Deny the request to cleanup...
await promiseMessage(permissionError, () => {
activateSecondaryAction(kActionDeny);
});
await observerPromise1;
await observerPromise2;
let browser = gBrowser.selectedBrowser;
SitePermissions.removeFromPrincipal(null, aRequestType, browser);
} else if (aExpect == PromptResult.ALLOW) {
const observerPromise = expectObserverCalled("getUserMedia:request");
const observerPromise1 = expectObserverCalled(
"getUserMedia:response:allow"
);
const observerPromise2 = expectObserverCalled(
"recording-device-events"
);
const promise = promiseMessage("ok");
await promiseRequestDevice(audio, video, aIframeId, screen);
await promise;
await observerPromise;
await promiseNoPopupNotification("webRTC-shareDevices");
await observerPromise1;
await observerPromise2;
let expected = {};
if (audio) {
expected.audio = audio;
}
if (video) {
expected.video = video;
}
Assert.deepEqual(
await getMediaCaptureState(),
expected,
"expected " + Object.keys(expected).join(" and ") + " to be shared"
);
await closeStream(false, aIframeId);
} else if (aExpect == PromptResult.DENY) {
const promises = [];
// frame3 disallows by feature Permissions Policy before request.
if (aIframeId != "frame3") {
promises.push(
expectObserverCalled("getUserMedia:request"),
expectObserverCalled("getUserMedia:response:deny")
);
}
promises.push(
expectObserverCalled("recording-window-ended"),
promiseMessage(permissionError),
promiseRequestDevice(audio, video, aIframeId, screen)
);
await Promise.all(promises);
}
PermissionTestUtils.remove(uri, aRequestType);
}
await checkPersistentPermission(
Perms.PROMPT_ACTION,
"camera",
"frame1",
PromptResult.PROMPT
);
await checkPersistentPermission(
Perms.DENY_ACTION,
"camera",
"frame1",
PromptResult.DENY
);
await checkPersistentPermission(
Perms.ALLOW_ACTION,
"camera",
"frame1",
PromptResult.ALLOW
);
await checkPersistentPermission(
Perms.PROMPT_ACTION,
"microphone",
"frame1",
PromptResult.PROMPT
);
await checkPersistentPermission(
Perms.DENY_ACTION,
"microphone",
"frame1",
PromptResult.DENY
);
await checkPersistentPermission(
Perms.ALLOW_ACTION,
"microphone",
"frame1",
PromptResult.ALLOW
);
// Wildcard attributes still get delegation when their src is unchanged.
await checkPersistentPermission(
Perms.PROMPT_ACTION,
"camera",
"frame4",
PromptResult.PROMPT
);
await checkPersistentPermission(
Perms.DENY_ACTION,
"camera",
"frame4",
PromptResult.DENY
);
await checkPersistentPermission(
Perms.ALLOW_ACTION,
"camera",
"frame4",
PromptResult.ALLOW
);
// Wildcard attributes still get delegation when their src is unchanged.
await checkPersistentPermission(
Perms.PROMPT_ACTION,
"microphone",
"frame4",
PromptResult.PROMPT
);
await checkPersistentPermission(
Perms.DENY_ACTION,
"microphone",
"frame4",
PromptResult.DENY
);
await checkPersistentPermission(
Perms.ALLOW_ACTION,
"microphone",
"frame4",
PromptResult.ALLOW
);
await checkPersistentPermission(
Perms.PROMPT_ACTION,
"screen",
"frame1",
PromptResult.PROMPT
);
await checkPersistentPermission(
Perms.DENY_ACTION,
"screen",
"frame1",
PromptResult.DENY
);
// Always prompt screen sharing
await checkPersistentPermission(
Perms.ALLOW_ACTION,
"screen",
"frame1",
PromptResult.PROMPT
);
await checkPersistentPermission(
Perms.ALLOW_ACTION,
"screen",
"frame4",
PromptResult.PROMPT
);
// Denied by default if allow is not defined
await checkPersistentPermission(
Perms.PROMPT_ACTION,
"camera",
"frame3",
PromptResult.DENY
);
await checkPersistentPermission(
Perms.DENY_ACTION,
"camera",
"frame3",
PromptResult.DENY
);
await checkPersistentPermission(
Perms.ALLOW_ACTION,
"camera",
"frame3",
PromptResult.DENY
);
await checkPersistentPermission(
Perms.PROMPT_ACTION,
"microphone",
"frame3",
PromptResult.DENY
);
await checkPersistentPermission(
Perms.DENY_ACTION,
"microphone",
"frame3",
PromptResult.DENY
);
await checkPersistentPermission(
Perms.ALLOW_ACTION,
"microphone",
"frame3",
PromptResult.DENY
);
await checkPersistentPermission(
Perms.PROMPT_ACTION,
"screen",
"frame3",
PromptResult.DENY
);
await checkPersistentPermission(
Perms.DENY_ACTION,
"screen",
"frame3",
PromptResult.DENY
);
await checkPersistentPermission(
Perms.ALLOW_ACTION,
"screen",
"frame3",
PromptResult.DENY
);
},
},
{
desc: "getUserMedia use temporary blocked permissions from first party",
run: async function checkUseTempPermissionsBlockFirstParty() {
async function checkTempPermission(aRequestType) {
let browser = gBrowser.selectedBrowser;
let observerPromise = expectObserverCalled("getUserMedia:request");
let observerPromise1 = expectObserverCalled(
"getUserMedia:response:deny"
);
let observerPromise2 = expectObserverCalled("recording-window-ended");
let promise = promisePopupNotificationShown("webRTC-shareDevices");
let audio = aRequestType == "microphone";
let video = aRequestType == "camera";
const screen = aRequestType == "screen" ? "screen" : undefined;
if (screen) {
audio = false;
video = true;
}
await promiseRequestDevice(audio, video, null, screen);
await promise;
await observerPromise;
// Temporarily grant/deny from top level
// Only need to check allow and deny temporary permissions
await promiseMessage(permissionError, () => {
activateSecondaryAction(kActionDeny);
});
await observerPromise1;
await observerPromise2;
await checkNotSharing();
observerPromise = expectObserverCalled("getUserMedia:request");
observerPromise1 = expectObserverCalled("getUserMedia:response:deny");
observerPromise2 = expectObserverCalled("recording-window-ended");
promise = promiseMessage(permissionError);
await promiseRequestDevice(audio, video, "frame1", screen);
await promise;
await observerPromise;
await observerPromise1;
await observerPromise2;
SitePermissions.removeFromPrincipal(null, aRequestType, browser);
}
// At the moment we only save temporary deny
await checkTempPermission("camera");
await checkTempPermission("microphone");
await checkTempPermission("screen");
},
},
{
desc: "Don't reprompt while actively sharing in maybe unsafe permission delegation",
run: async function checkNoRepromptNoDelegate() {
// Change location to ensure that we're treated as potentially unsafe.
await promiseChangeLocationFrame(
"frame4",
);
// Check that we get a prompt.
let observerPromise = expectObserverCalled("getUserMedia:request");
let promise = promisePopupNotificationShown("webRTC-shareDevices");
await promiseRequestDevice(true, true, "frame4");
await promise;
await observerPromise;
// Check the secondName of the notification should be the third party
is(
PopupNotifications.getNotification("webRTC-shareDevices").options
.secondName,
"test2.example.com",
"Use third party's origin as secondName"
);
const notification = PopupNotifications.panel.firstElementChild;
let indicator = promiseIndicatorWindow();
let observerPromise1 = expectObserverCalled(
"getUserMedia:response:allow"
);
let observerPromise2 = expectObserverCalled("recording-device-events");
await promiseMessage("ok", () =>
EventUtils.synthesizeMouseAtCenter(notification.button, {})
);
await observerPromise1;
await observerPromise2;
let state = await getMediaCaptureState();
is(!!state.audio, true, "expected microphone to be shared");
is(!!state.video, true, "expected camera to be shared");
await indicator;
await checkSharingUI({ audio: true, video: true });
// Check that we now don't get a prompt.
observerPromise = expectObserverCalled("getUserMedia:request");
observerPromise1 = expectObserverCalled("getUserMedia:response:allow");
observerPromise2 = expectObserverCalled("recording-device-events");
promise = promiseMessage("ok");
await promiseRequestDevice(true, true, "frame4");
await promise;
await observerPromise;
await promiseNoPopupNotification("webRTC-shareDevices");
await observerPromise1;
await observerPromise2;
state = await getMediaCaptureState();
is(!!state.audio, true, "expected microphone to be shared");
is(!!state.video, true, "expected camera to be shared");
await checkSharingUI({ audio: true, video: true });
// Cleanup.
await closeStream(false, "frame4");
},
},
{
desc: "Change location, prompt and display both first party and third party origin in maybe unsafe permission delegation",
run: async function checkPromptNoDelegateChangeLoxation() {
await promiseChangeLocationFrame(
"frame4",
);
await promptNoDelegate("test2.example.com");
},
},
{
desc: "Change location, prompt and display both first party and third party origin when sharing screen in unsafe permission delegation",
run: async function checkPromptNoDelegateScreenSharingChangeLocation() {
await promiseChangeLocationFrame(
"frame4",
);
await promptNoDelegateScreenSharing("test2.example.com");
},
},
{
desc: "Prompt and display both first party and third party origin and temporary deny in frame does not change permission scope",
skipObserverVerification: true,
run: async function checkPromptBothOriginsTempDenyFrame() {
// Change location to ensure that we're treated as potentially unsafe.
await promiseChangeLocationFrame(
"frame4",
);
// Persistent allowed first party origin
let browser = gBrowser.selectedBrowser;
let uri = gBrowser.selectedBrowser.documentURI;
let principal = Services.scriptSecurityManager.createContentPrincipal(
uri,
{}
);
let observerPromise = expectObserverCalled("getUserMedia:request");
let promise = promisePopupNotificationShown("webRTC-shareDevices");
await promiseRequestDevice(true, true);
await promise;
await observerPromise;
// Ensure that checking the 'Remember this decision'
let notification = PopupNotifications.panel.firstElementChild;
let checkbox = notification.checkbox;
ok(!!checkbox, "checkbox is present");
ok(!checkbox.checked, "checkbox is not checked");
checkbox.click();
let indicator = promiseIndicatorWindow();
let observerPromise1 = expectObserverCalled(
"getUserMedia:response:allow"
);
let observerPromise2 = expectObserverCalled("recording-device-events");
await promiseMessage("ok", () =>
EventUtils.synthesizeMouseAtCenter(notification.button, {})
);
await observerPromise1;
await observerPromise2;
Assert.deepEqual(
await getMediaCaptureState(),
{ audio: true, video: true },
"expected camera and microphone to be shared"
);
await indicator;
await checkSharingUI({ audio: true, video: true }, undefined, undefined, {
audio: { scope: SitePermissions.SCOPE_PERSISTENT },
video: { scope: SitePermissions.SCOPE_PERSISTENT },
});
await closeStream(true);
// Check that we get a prompt.
observerPromise = expectObserverCalled("getUserMedia:request");
promise = promisePopupNotificationShown("webRTC-shareDevices");
await promiseRequestDevice(true, true, "frame4");
await promise;
await observerPromise;
// The 'Remember this decision' checkbox is hidden.
notification = PopupNotifications.panel.firstElementChild;
checkbox = notification.checkbox;
ok(!!checkbox, "checkbox is present");
ok(checkbox.hidden, "checkbox is not visible");
observerPromise1 = expectObserverCalled("getUserMedia:response:deny");
observerPromise2 = expectObserverCalled("recording-window-ended");
await promiseMessage(permissionError, () => {
activateSecondaryAction(kActionDeny);
});
await observerPromise1;
await observerPromise2;
await checkNotSharing();
// Make sure we are not changing the scope and state of persistent
// permission
let { state, scope } = SitePermissions.getForPrincipal(
principal,
"camera",
browser
);
Assert.equal(state, SitePermissions.ALLOW);
Assert.equal(scope, SitePermissions.SCOPE_PERSISTENT);
({ state, scope } = SitePermissions.getForPrincipal(
principal,
"microphone",
browser
));
Assert.equal(state, SitePermissions.ALLOW);
Assert.equal(scope, SitePermissions.SCOPE_PERSISTENT);
PermissionTestUtils.remove(uri, "camera");
PermissionTestUtils.remove(uri, "microphone");
},
},
];
add_task(async function test() {
await SpecialPowers.pushPrefEnv({
set: [
["dom.security.featurePolicy.header.enabled", true],
["dom.security.featurePolicy.webidl.enabled", true],
],
});
await runTests(gTests, {
relativeURI: "get_user_media_in_xorigin_frame.html",
});
});