From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../browser_devices_get_user_media_in_frame.js | 775 +++++++++++++++++++++ 1 file changed, 775 insertions(+) create mode 100644 browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js (limited to 'browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js') diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js new file mode 100644 index 0000000000..81e04cebce --- /dev/null +++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js @@ -0,0 +1,775 @@ +/* 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/. */ + +SpecialPowers.pushPrefEnv({ + set: [["permissions.delegation.enabled", true]], +}); + +// This test has been seen timing out locally in non-opt debug builds. +requestLongerTimeout(2); + +var gTests = [ + { + desc: "getUserMedia audio+video", + run: async function checkAudioVideo(aBrowser, aSubFrames) { + let { + bc: frame1BC, + id: frame1ID, + observeBC: frame1ObserveBC, + } = ( + await getBrowsingContextsAndFrameIdsForSubFrames( + aBrowser.browsingContext, + aSubFrames + ) + )[0]; + + let observerPromise = expectObserverCalled( + "getUserMedia:request", + 1, + frame1ObserveBC + ); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true, frame1ID, undefined, frame1BC); + await promise; + await observerPromise; + + is( + PopupNotifications.getNotification("webRTC-shareDevices").anchorID, + "webRTC-shareDevices-notification-icon", + "anchored to device icon" + ); + checkDeviceSelectors(["microphone", "camera"]); + is( + PopupNotifications.panel.firstElementChild.getAttribute("popupid"), + "webRTC-shareDevices", + "panel using devices icon" + ); + + let indicator = promiseIndicatorWindow(); + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow", + 1, + frame1ObserveBC + ); + let observerPromise2 = expectObserverCalled( + "recording-device-events", + 1, + frame1ObserveBC + ); + await promiseMessage("ok", () => { + PopupNotifications.panel.firstElementChild.button.click(); + }); + 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 }); + await closeStream(false, frame1ID, undefined, frame1BC, frame1ObserveBC); + }, + }, + + { + desc: "getUserMedia audio+video: stop sharing", + run: async function checkStopSharing(aBrowser, aSubFrames) { + let { + bc: frame1BC, + id: frame1ID, + observeBC: frame1ObserveBC, + } = ( + await getBrowsingContextsAndFrameIdsForSubFrames( + aBrowser.browsingContext, + aSubFrames + ) + )[0]; + + let observerPromise = expectObserverCalled( + "getUserMedia:request", + 1, + frame1ObserveBC + ); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true, frame1ID, undefined, frame1BC); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + let indicator = promiseIndicatorWindow(); + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow", + 1, + frame1ObserveBC + ); + let observerPromise2 = expectObserverCalled( + "recording-device-events", + 1, + frame1ObserveBC + ); + await promiseMessage("ok", () => { + activateSecondaryAction(kActionAlways); + }); + await observerPromise1; + await observerPromise2; + Assert.deepEqual( + await getMediaCaptureState(), + { audio: true, video: true }, + "expected camera and microphone to be shared" + ); + + await indicator; + await checkSharingUI({ video: true, audio: true }, undefined, undefined, { + video: { scope: SitePermissions.SCOPE_PERSISTENT }, + audio: { scope: SitePermissions.SCOPE_PERSISTENT }, + }); + + let uri = Services.io.newURI("https://example.com/"); + is( + PermissionTestUtils.testExactPermission(uri, "microphone"), + Services.perms.ALLOW_ACTION, + "microphone persistently allowed" + ); + is( + PermissionTestUtils.testExactPermission(uri, "camera"), + Services.perms.ALLOW_ACTION, + "camera persistently allowed" + ); + + await stopSharing("camera", false, frame1ObserveBC); + + // The persistent permissions for the frame should have been removed. + is( + PermissionTestUtils.testExactPermission(uri, "microphone"), + Services.perms.UNKNOWN_ACTION, + "microphone not persistently allowed" + ); + is( + PermissionTestUtils.testExactPermission(uri, "camera"), + Services.perms.UNKNOWN_ACTION, + "camera not persistently allowed" + ); + + // the stream is already closed, but this will do some cleanup anyway + await closeStream(true, frame1ID, undefined, frame1BC, frame1ObserveBC); + }, + }, + + { + desc: "getUserMedia audio+video: Revoking active devices in frame does not add grace period.", + run: async function checkStopSharingGracePeriod(aBrowser, aSubFrames) { + let { + bc: frame1BC, + id: frame1ID, + observeBC: frame1ObserveBC, + } = ( + await getBrowsingContextsAndFrameIdsForSubFrames( + aBrowser.browsingContext, + aSubFrames + ) + )[0]; + + let observerPromise = expectObserverCalled( + "getUserMedia:request", + 1, + frame1ObserveBC + ); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true, frame1ID, undefined, frame1BC); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + let indicator = promiseIndicatorWindow(); + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow", + 1, + frame1ObserveBC + ); + let observerPromise2 = expectObserverCalled( + "recording-device-events", + 1, + frame1ObserveBC + ); + await promiseMessage("ok", () => { + PopupNotifications.panel.firstElementChild.button.click(); + }); + await observerPromise1; + await observerPromise2; + Assert.deepEqual( + await getMediaCaptureState(), + { audio: true, video: true }, + "expected camera and microphone to be shared" + ); + + await indicator; + await checkSharingUI({ video: true, audio: true }); + + // Stop sharing for camera and test that we stopped sharing. + await stopSharing("camera", false, frame1ObserveBC); + + // There shouldn't be any grace period permissions at this point. + ok( + !SitePermissions.getAllForBrowser(aBrowser).length, + "Should not set any permissions." + ); + + // A new request should result in a prompt. + observerPromise = expectObserverCalled( + "getUserMedia:request", + 1, + frame1ObserveBC + ); + let notificationPromise = promisePopupNotificationShown( + "webRTC-shareDevices" + ); + await promiseRequestDevice(true, true, frame1ID, undefined, frame1BC); + await notificationPromise; + await observerPromise; + + let denyPromise = expectObserverCalled( + "getUserMedia:response:deny", + 1, + frame1ObserveBC + ); + let recordingEndedPromise = expectObserverCalled( + "recording-window-ended", + 1, + frame1ObserveBC + ); + const permissionError = + "error: NotAllowedError: The request is not allowed " + + "by the user agent or the platform in the current context."; + await promiseMessage(permissionError, () => { + activateSecondaryAction(kActionDeny); + }); + await denyPromise; + await recordingEndedPromise; + + // Clean up the temporary blocks from the prompt deny. + SitePermissions.clearTemporaryBlockPermissions(aBrowser); + + // the stream is already closed, but this will do some cleanup anyway + await closeStream(true, frame1ID, undefined, frame1BC, frame1ObserveBC); + }, + }, + + { + desc: "getUserMedia audio+video: reloading the frame removes all sharing UI", + run: async function checkReloading(aBrowser, aSubFrames) { + let { + bc: frame1BC, + id: frame1ID, + observeBC: frame1ObserveBC, + } = ( + await getBrowsingContextsAndFrameIdsForSubFrames( + aBrowser.browsingContext, + aSubFrames + ) + )[0]; + + let observerPromise = expectObserverCalled( + "getUserMedia:request", + 1, + frame1ObserveBC + ); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true, frame1ID, undefined, frame1BC); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow", + 1, + frame1ObserveBC + ); + let observerPromise2 = expectObserverCalled( + "recording-device-events", + 1, + frame1ObserveBC + ); + let indicator = promiseIndicatorWindow(); + await promiseMessage("ok", () => { + PopupNotifications.panel.firstElementChild.button.click(); + }); + await observerPromise1; + await observerPromise2; + Assert.deepEqual( + await getMediaCaptureState(), + { audio: true, video: true }, + "expected camera and microphone to be shared" + ); + + await indicator; + await checkSharingUI({ video: true, audio: true }); + + // Disable while loading a new page + await disableObserverVerification(); + + info("reloading the frame"); + let promises = [ + expectObserverCalledOnClose( + "recording-device-stopped", + 1, + frame1ObserveBC + ), + expectObserverCalledOnClose( + "recording-device-events", + 1, + frame1ObserveBC + ), + expectObserverCalledOnClose( + "recording-window-ended", + 1, + frame1ObserveBC + ), + ]; + await promiseReloadFrame(frame1ID, frame1BC); + await Promise.all(promises); + + await enableObserverVerification(); + + await checkNotSharing(); + }, + }, + + { + desc: "getUserMedia audio+video: reloading the frame removes prompts", + run: async function checkReloadingRemovesPrompts(aBrowser, aSubFrames) { + let { + bc: frame1BC, + id: frame1ID, + observeBC: frame1ObserveBC, + } = ( + await getBrowsingContextsAndFrameIdsForSubFrames( + aBrowser.browsingContext, + aSubFrames + ) + )[0]; + + let observerPromise = expectObserverCalled( + "getUserMedia:request", + 1, + frame1ObserveBC + ); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true, frame1ID, undefined, frame1BC); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + info("reloading the frame"); + promise = expectObserverCalledOnClose( + "recording-window-ended", + 1, + frame1ObserveBC + ); + await promiseReloadFrame(frame1ID, frame1BC); + await promise; + await promiseNoPopupNotification("webRTC-shareDevices"); + + await checkNotSharing(); + }, + }, + + { + desc: "getUserMedia audio+video: with two frames sharing at the same time, sharing UI shows all shared devices", + run: async function checkFrameOverridingSharingUI(aBrowser, aSubFrames) { + // This tests an edge case discovered in bug 1440356 that works like this + // - Share audio and video in iframe 1. + // - Share only video in iframe 2. + // The WebRTC UI should still show both video and audio indicators. + + let bcsAndFrameIds = await getBrowsingContextsAndFrameIdsForSubFrames( + aBrowser.browsingContext, + aSubFrames + ); + let { + bc: frame1BC, + id: frame1ID, + observeBC: frame1ObserveBC, + } = bcsAndFrameIds[0]; + + let observerPromise = expectObserverCalled( + "getUserMedia:request", + 1, + frame1ObserveBC + ); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true, frame1ID, undefined, frame1BC); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + let indicator = promiseIndicatorWindow(); + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow", + 1, + frame1ObserveBC + ); + let observerPromise2 = expectObserverCalled( + "recording-device-events", + 1, + frame1ObserveBC + ); + await promiseMessage("ok", () => { + PopupNotifications.panel.firstElementChild.button.click(); + }); + await observerPromise1; + await observerPromise2; + Assert.deepEqual( + await getMediaCaptureState(), + { audio: true, video: true }, + "expected camera and microphone to be shared" + ); + + await indicator; + await checkSharingUI({ video: true, audio: true }); + + // Check that requesting a new device from a different frame + // doesn't override sharing UI. + let { + bc: frame2BC, + id: frame2ID, + observeBC: frame2ObserveBC, + } = bcsAndFrameIds[1]; + + observerPromise = expectObserverCalled( + "getUserMedia:request", + 1, + frame2ObserveBC + ); + promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(false, true, frame2ID, undefined, frame2BC); + await promise; + await observerPromise; + checkDeviceSelectors(["camera"]); + + observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow", + 1, + frame2ObserveBC + ); + observerPromise2 = expectObserverCalled( + "recording-device-events", + 1, + frame2ObserveBC + ); + + await promiseMessage("ok", () => { + PopupNotifications.panel.firstElementChild.button.click(); + }); + await observerPromise1; + await observerPromise2; + Assert.deepEqual( + await getMediaCaptureState(), + { audio: true, video: true }, + "expected camera and microphone to be shared" + ); + + await checkSharingUI({ video: true, audio: true }); + + // Check that ending the stream with the other frame + // doesn't override sharing UI. + + observerPromise = expectObserverCalledOnClose( + "recording-window-ended", + 1, + frame2ObserveBC + ); + promise = expectObserverCalledOnClose( + "recording-device-events", + 1, + frame2ObserveBC + ); + await promiseReloadFrame(frame2ID, frame2BC); + await promise; + + await observerPromise; + await checkSharingUI({ video: true, audio: true }); + + await closeStream(false, frame1ID, undefined, frame1BC, frame1ObserveBC); + await checkNotSharing(); + }, + }, + + { + desc: "getUserMedia audio+video: reloading a frame updates the sharing UI", + run: async function checkUpdateWhenReloading(aBrowser, aSubFrames) { + // We'll share only the cam in the first frame, then share both in the + // second frame, then reload the second frame. After each step, we'll check + // the UI is in the correct state. + let bcsAndFrameIds = await getBrowsingContextsAndFrameIdsForSubFrames( + aBrowser.browsingContext, + aSubFrames + ); + let { + bc: frame1BC, + id: frame1ID, + observeBC: frame1ObserveBC, + } = bcsAndFrameIds[0]; + + let observerPromise = expectObserverCalled( + "getUserMedia:request", + 1, + frame1ObserveBC + ); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(false, true, frame1ID, undefined, frame1BC); + await promise; + await observerPromise; + checkDeviceSelectors(["camera"]); + + let indicator = promiseIndicatorWindow(); + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow", + 1, + frame1ObserveBC + ); + let observerPromise2 = expectObserverCalled( + "recording-device-events", + 1, + frame1ObserveBC + ); + await promiseMessage("ok", () => { + PopupNotifications.panel.firstElementChild.button.click(); + }); + await observerPromise1; + await observerPromise2; + Assert.deepEqual( + await getMediaCaptureState(), + { video: true }, + "expected camera to be shared" + ); + + await indicator; + await checkSharingUI({ video: true, audio: false }); + + let { + bc: frame2BC, + id: frame2ID, + observeBC: frame2ObserveBC, + } = bcsAndFrameIds[1]; + + observerPromise = expectObserverCalled( + "getUserMedia:request", + 1, + frame2ObserveBC + ); + promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true, frame2ID, undefined, frame2BC); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow", + 1, + frame2ObserveBC + ); + observerPromise2 = expectObserverCalled( + "recording-device-events", + 1, + frame2ObserveBC + ); + await promiseMessage("ok", () => { + PopupNotifications.panel.firstElementChild.button.click(); + }); + await observerPromise1; + await observerPromise2; + Assert.deepEqual( + await getMediaCaptureState(), + { audio: true, video: true }, + "expected camera and microphone to be shared" + ); + + await checkSharingUI({ video: true, audio: true }); + + info("reloading the second frame"); + + observerPromise1 = expectObserverCalledOnClose( + "recording-device-events", + 1, + frame2ObserveBC + ); + observerPromise2 = expectObserverCalledOnClose( + "recording-window-ended", + 1, + frame2ObserveBC + ); + await promiseReloadFrame(frame2ID, frame2BC); + await observerPromise1; + await observerPromise2; + + await checkSharingUI({ video: true, audio: false }); + + await closeStream(false, frame1ID, undefined, frame1BC, frame1ObserveBC); + await checkNotSharing(); + }, + }, + + { + desc: "getUserMedia audio+video: reloading the top level page removes all sharing UI", + run: async function checkReloading(aBrowser, aSubFrames) { + let { + bc: frame1BC, + id: frame1ID, + observeBC: frame1ObserveBC, + } = ( + await getBrowsingContextsAndFrameIdsForSubFrames( + aBrowser.browsingContext, + aSubFrames + ) + )[0]; + + let observerPromise = expectObserverCalled( + "getUserMedia:request", + 1, + frame1ObserveBC + ); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true, frame1ID, undefined, frame1BC); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + let indicator = promiseIndicatorWindow(); + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow", + 1, + frame1ObserveBC + ); + let observerPromise2 = expectObserverCalled( + "recording-device-events", + 1, + frame1ObserveBC + ); + await promiseMessage("ok", () => { + PopupNotifications.panel.firstElementChild.button.click(); + }); + await observerPromise1; + await observerPromise2; + Assert.deepEqual( + await getMediaCaptureState(), + { audio: true, video: true }, + "expected camera and microphone to be shared" + ); + + await indicator; + await checkSharingUI({ video: true, audio: true }); + + await reloadAndAssertClosedStreams(); + }, + }, + + { + desc: "getUserMedia audio+video: closing a window with two frames sharing at the same time, closes the indicator", + skipObserverVerification: true, + run: async function checkFrameIndicatorClosedUI(aBrowser, aSubFrames) { + // This tests a case where the indicator didn't close when audio/video is + // shared in two subframes and then the tabs are closed. + + let tabsToRemove = [gBrowser.selectedTab]; + + for (let t = 0; t < 2; t++) { + let { + bc: frame1BC, + id: frame1ID, + observeBC: frame1ObserveBC, + } = ( + await getBrowsingContextsAndFrameIdsForSubFrames( + gBrowser.selectedBrowser.browsingContext, + aSubFrames + ) + )[0]; + + let observerPromise = expectObserverCalled( + "getUserMedia:request", + 1, + frame1ObserveBC + ); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true, frame1ID, undefined, frame1BC); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + // During the second pass, the indicator is already open. + let indicator = t == 0 ? promiseIndicatorWindow() : Promise.resolve(); + + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow", + 1, + frame1ObserveBC + ); + let observerPromise2 = expectObserverCalled( + "recording-device-events", + 1, + frame1ObserveBC + ); + await promiseMessage("ok", () => { + PopupNotifications.panel.firstElementChild.button.click(); + }); + await observerPromise1; + await observerPromise2; + Assert.deepEqual( + await getMediaCaptureState(), + { audio: true, video: true }, + "expected camera and microphone to be shared" + ); + + await indicator; + await checkSharingUI({ video: true, audio: true }); + + // The first time around, open another tab with the same uri. + // The second time, just open a normal test tab. + let uri = t == 0 ? gBrowser.selectedBrowser.currentURI.spec : undefined; + tabsToRemove.push( + await BrowserTestUtils.openNewForegroundTab(gBrowser, uri) + ); + } + + BrowserTestUtils.removeTab(tabsToRemove[0]); + BrowserTestUtils.removeTab(tabsToRemove[1]); + + await checkNotSharing(); + }, + }, +]; + +add_task(async function test_inprocess() { + await runTests(gTests, { + relativeURI: "get_user_media_in_frame.html", + subFrames: { frame1: {}, frame2: {} }, + }); +}); + +add_task(async function test_outofprocess() { + const origin1 = encodeURI("https://test1.example.org"); + const origin2 = encodeURI("https://www.mozilla.org:443"); + const query = `origin=${origin1}&origin=${origin2}`; + const observe = SpecialPowers.useRemoteSubframes; + await runTests(gTests, { + relativeURI: `get_user_media_in_frame.html?${query}`, + subFrames: { frame1: { observe }, frame2: { observe } }, + }); +}); + +add_task(async function test_inprocess_in_outofprocess() { + const oopOrigin = encodeURI("https://www.mozilla.org"); + const sameOrigin = encodeURI("https://example.com"); + const query = `origin=${oopOrigin}&nested=${sameOrigin}&nested=${sameOrigin}`; + await runTests(gTests, { + relativeURI: `get_user_media_in_frame.html?${query}`, + subFrames: { + frame1: { + noTest: true, + children: { frame1: {}, frame2: {} }, + }, + }, + }); +}); -- cgit v1.2.3