/* 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: {} }, }, }, }); });