From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../browser_devices_get_user_media_paused.js | 999 +++++++++++++++++++++ 1 file changed, 999 insertions(+) create mode 100644 browser/base/content/test/webrtc/browser_devices_get_user_media_paused.js (limited to 'browser/base/content/test/webrtc/browser_devices_get_user_media_paused.js') diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_paused.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_paused.js new file mode 100644 index 0000000000..27271c2a45 --- /dev/null +++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_paused.js @@ -0,0 +1,999 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function setCameraMuted(mute) { + return sendObserverNotification( + mute ? "getUserMedia:muteVideo" : "getUserMedia:unmuteVideo" + ); +} + +function setMicrophoneMuted(mute) { + return sendObserverNotification( + mute ? "getUserMedia:muteAudio" : "getUserMedia:unmuteAudio" + ); +} + +function sendObserverNotification(topic) { + const windowId = gBrowser.selectedBrowser.innerWindowID; + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [{ topic, windowId }], + function (args) { + Services.obs.notifyObservers( + content.window, + args.topic, + JSON.stringify(args.windowId) + ); + } + ); +} + +function setTrackEnabled(audio, video) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [{ audio, video }], + function (args) { + let stream = content.wrappedJSObject.gStreams[0]; + if (args.audio != null) { + stream.getAudioTracks()[0].enabled = args.audio; + } + if (args.video != null) { + stream.getVideoTracks()[0].enabled = args.video; + } + } + ); +} + +async function getVideoTrackMuted() { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => content.wrappedJSObject.gStreams[0].getVideoTracks()[0].muted + ); +} + +async function getVideoTrackEvents() { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => content.wrappedJSObject.gVideoEvents + ); +} + +async function getAudioTrackMuted() { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => content.wrappedJSObject.gStreams[0].getAudioTracks()[0].muted + ); +} + +async function getAudioTrackEvents() { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => content.wrappedJSObject.gAudioEvents + ); +} + +function cloneTracks(audio, video) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [{ audio, video }], + function (args) { + if (!content.wrappedJSObject.gClones) { + content.wrappedJSObject.gClones = []; + } + let clones = content.wrappedJSObject.gClones; + let stream = content.wrappedJSObject.gStreams[0]; + if (args.audio != null) { + clones.push(stream.getAudioTracks()[0].clone()); + } + if (args.video != null) { + clones.push(stream.getVideoTracks()[0].clone()); + } + } + ); +} + +function stopClonedTracks(audio, video) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [{ audio, video }], + function (args) { + let clones = content.wrappedJSObject.gClones || []; + if (args.audio != null) { + clones.filter(t => t.kind == "audio").forEach(t => t.stop()); + } + if (args.video != null) { + clones.filter(t => t.kind == "video").forEach(t => t.stop()); + } + let liveClones = clones.filter(t => t.readyState == "live"); + if (!liveClones.length) { + delete content.wrappedJSObject.gClones; + } else { + content.wrappedJSObject.gClones = liveClones; + } + } + ); +} + +var gTests = [ + { + desc: "getUserMedia audio+video: disabling the stream shows the paused indicator", + run: async function checkDisabled() { + let observerPromise = expectObserverCalled("getUserMedia:request"); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + let indicator = promiseIndicatorWindow(); + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow" + ); + let observerPromise2 = expectObserverCalled("recording-device-events"); + 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: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + + // Disable both audio and video. + observerPromise = expectObserverCalled("recording-device-events", 2); + await setTrackEnabled(false, false); + + // Wait for capture state to propagate to the UI asynchronously. + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.camera == + STATE_CAPTURE_DISABLED, + "video should be disabled" + ); + + await observerPromise; + + // The identity UI should show both as disabled. + await checkSharingUI({ + video: STATE_CAPTURE_DISABLED, + audio: STATE_CAPTURE_DISABLED, + }); + + // Enable only audio again. + observerPromise = expectObserverCalled("recording-device-events"); + await setTrackEnabled(true); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.microphone == + STATE_CAPTURE_ENABLED, + "audio should be enabled" + ); + + await observerPromise; + + // The identity UI should show only video as disabled. + await checkSharingUI({ + video: STATE_CAPTURE_DISABLED, + audio: STATE_CAPTURE_ENABLED, + }); + + // Enable video again. + observerPromise = expectObserverCalled("recording-device-events"); + await setTrackEnabled(null, true); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.camera == + STATE_CAPTURE_ENABLED, + "video should be enabled" + ); + + await observerPromise; + + // Both streams should show as running. + await checkSharingUI({ + video: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + await closeStream(); + }, + }, + + { + desc: "getUserMedia audio+video: disabling the original tracks and stopping enabled clones shows the paused indicator", + run: async function checkDisabledAfterCloneStop() { + let observerPromise = expectObserverCalled("getUserMedia:request"); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + let indicator = promiseIndicatorWindow(); + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow" + ); + let observerPromise2 = expectObserverCalled("recording-device-events"); + 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: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + + // Clone audio and video, their state will be enabled + await cloneTracks(true, true); + + // Disable both audio and video. + await setTrackEnabled(false, false); + + observerPromise = expectObserverCalled("recording-device-events", 2); + + // Stop the clones. This should disable the sharing indicators. + await stopClonedTracks(true, true); + + // Wait for capture state to propagate to the UI asynchronously. + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.camera == + STATE_CAPTURE_DISABLED && + window.gPermissionPanel._sharingState.webRTC.microphone == + STATE_CAPTURE_DISABLED, + "video and audio should be disabled" + ); + + await observerPromise; + + // The identity UI should show both as disabled. + await checkSharingUI({ + video: STATE_CAPTURE_DISABLED, + audio: STATE_CAPTURE_DISABLED, + }); + + // Enable only audio again. + observerPromise = expectObserverCalled("recording-device-events"); + await setTrackEnabled(true); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.microphone == + STATE_CAPTURE_ENABLED, + "audio should be enabled" + ); + + await observerPromise; + + // The identity UI should show only video as disabled. + await checkSharingUI({ + video: STATE_CAPTURE_DISABLED, + audio: STATE_CAPTURE_ENABLED, + }); + + // Enable video again. + observerPromise = expectObserverCalled("recording-device-events"); + await setTrackEnabled(null, true); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.camera == + STATE_CAPTURE_ENABLED, + "video should be enabled" + ); + + await observerPromise; + + // Both streams should show as running. + await checkSharingUI({ + video: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + await closeStream(); + }, + }, + + { + desc: "getUserMedia screen: disabling the stream shows the paused indicator", + run: async function checkScreenDisabled() { + let observerPromise = expectObserverCalled("getUserMedia:request"); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(false, true, null, "screen"); + await promise; + await observerPromise; + + is( + PopupNotifications.getNotification("webRTC-shareDevices").anchorID, + "webRTC-shareScreen-notification-icon", + "anchored to device icon" + ); + checkDeviceSelectors(["screen"]); + + let menulist = document.getElementById("webRTC-selectWindow-menulist"); + menulist.getItemAtIndex(menulist.itemCount - 1).doCommand(); + + let indicator = promiseIndicatorWindow(); + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow" + ); + let observerPromise2 = expectObserverCalled("recording-device-events"); + await promiseMessage("ok", () => { + PopupNotifications.panel.firstElementChild.button.click(); + }); + await observerPromise1; + await observerPromise2; + Assert.deepEqual( + await getMediaCaptureState(), + { screen: "Screen" }, + "expected screen to be shared" + ); + + await indicator; + await checkSharingUI({ screen: "Screen" }); + + observerPromise = expectObserverCalled("recording-device-events"); + await setTrackEnabled(null, false); + + // Wait for capture state to propagate to the UI asynchronously. + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.screen == "ScreenPaused", + "screen should be disabled" + ); + await observerPromise; + await checkSharingUI({ screen: "ScreenPaused" }, window, { + screen: "Screen", + }); + + observerPromise = expectObserverCalled("recording-device-events"); + await setTrackEnabled(null, true); + + await BrowserTestUtils.waitForCondition( + () => window.gPermissionPanel._sharingState.webRTC.screen == "Screen", + "screen should be enabled" + ); + await observerPromise; + await checkSharingUI({ screen: "Screen" }); + await closeStream(); + }, + }, + + { + desc: "getUserMedia audio+video: muting the camera shows the muted indicator", + run: async function checkCameraMuted() { + let observerPromise = expectObserverCalled("getUserMedia:request"); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + let indicator = promiseIndicatorWindow(); + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow" + ); + let observerPromise2 = expectObserverCalled("recording-device-events"); + 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: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + is(await getVideoTrackMuted(), false, "video track starts unmuted"); + Assert.deepEqual( + await getVideoTrackEvents(), + [], + "no video track events fired yet" + ); + + // Mute camera. + observerPromise = expectObserverCalled("recording-device-events"); + await setCameraMuted(true); + + // Wait for capture state to propagate to the UI asynchronously. + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.camera == + STATE_CAPTURE_DISABLED, + "video should be muted" + ); + + await observerPromise; + + // The identity UI should show only camera as disabled. + await checkSharingUI({ + video: STATE_CAPTURE_DISABLED, + audio: STATE_CAPTURE_ENABLED, + }); + is(await getVideoTrackMuted(), true, "video track is muted"); + Assert.deepEqual(await getVideoTrackEvents(), ["mute"], "mute fired"); + + // Unmute video again. + observerPromise = expectObserverCalled("recording-device-events"); + await setCameraMuted(false); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.camera == + STATE_CAPTURE_ENABLED, + "video should be enabled" + ); + + await observerPromise; + + // Both streams should show as running. + await checkSharingUI({ + video: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + is(await getVideoTrackMuted(), false, "video track is unmuted"); + Assert.deepEqual( + await getVideoTrackEvents(), + ["mute", "unmute"], + "unmute fired" + ); + await closeStream(); + }, + }, + + { + desc: "getUserMedia audio+video: muting the microphone shows the muted indicator", + run: async function checkMicrophoneMuted() { + let observerPromise = expectObserverCalled("getUserMedia:request"); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + let indicator = promiseIndicatorWindow(); + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow" + ); + let observerPromise2 = expectObserverCalled("recording-device-events"); + 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: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + is(await getAudioTrackMuted(), false, "audio track starts unmuted"); + Assert.deepEqual( + await getAudioTrackEvents(), + [], + "no audio track events fired yet" + ); + + // Mute microphone. + observerPromise = expectObserverCalled("recording-device-events"); + await setMicrophoneMuted(true); + + // Wait for capture state to propagate to the UI asynchronously. + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.microphone == + STATE_CAPTURE_DISABLED, + "audio should be muted" + ); + + await observerPromise; + + // The identity UI should show only microphone as disabled. + await checkSharingUI({ + video: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_DISABLED, + }); + is(await getAudioTrackMuted(), true, "audio track is muted"); + Assert.deepEqual(await getAudioTrackEvents(), ["mute"], "mute fired"); + + // Unmute audio again. + observerPromise = expectObserverCalled("recording-device-events"); + await setMicrophoneMuted(false); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.microphone == + STATE_CAPTURE_ENABLED, + "audio should be enabled" + ); + + await observerPromise; + + // Both streams should show as running. + await checkSharingUI({ + video: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + is(await getAudioTrackMuted(), false, "audio track is unmuted"); + Assert.deepEqual( + await getAudioTrackEvents(), + ["mute", "unmute"], + "unmute fired" + ); + await closeStream(); + }, + }, + + { + desc: "getUserMedia audio+video: disabling & muting camera in combination", + // Test the following combinations of disabling and muting camera: + // 1. Disable video track only. + // 2. Mute camera & disable audio (to have a condition to wait for) + // 3. Enable both audio and video tracks (only audio should flow). + // 4. Unmute camera again (video should flow). + // 5. Mute camera & disable both tracks. + // 6. Unmute camera & enable audio (only audio should flow) + // 7. Enable video track again (video should flow). + run: async function checkDisabledMutedCombination() { + let observerPromise = expectObserverCalled("getUserMedia:request"); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + let indicator = promiseIndicatorWindow(); + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow" + ); + let observerPromise2 = expectObserverCalled("recording-device-events"); + 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: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + + // 1. Disable video track only. + observerPromise = expectObserverCalled("recording-device-events"); + await setTrackEnabled(null, false); + + // Wait for capture state to propagate to the UI asynchronously. + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.camera == + STATE_CAPTURE_DISABLED, + "video should be disabled" + ); + + await observerPromise; + + // The identity UI should show only video as disabled. + await checkSharingUI({ + video: STATE_CAPTURE_DISABLED, + audio: STATE_CAPTURE_ENABLED, + }); + is(await getVideoTrackMuted(), false, "video track still unmuted"); + Assert.deepEqual( + await getVideoTrackEvents(), + [], + "no video track events fired yet" + ); + + // 2. Mute camera & disable audio (to have a condition to wait for) + observerPromise = expectObserverCalled("recording-device-events", 2); + await setCameraMuted(true); + await setTrackEnabled(false, null); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.microphone == + STATE_CAPTURE_DISABLED, + "audio should be disabled" + ); + + await observerPromise; + + // The identity UI should show both as disabled. + await checkSharingUI({ + video: STATE_CAPTURE_DISABLED, + audio: STATE_CAPTURE_DISABLED, + }); + is(await getVideoTrackMuted(), true, "video track is muted"); + Assert.deepEqual( + await getVideoTrackEvents(), + ["mute"], + "mute is still fired even though track was disabled" + ); + + // 3. Enable both audio and video tracks (only audio should flow). + observerPromise = expectObserverCalled("recording-device-events", 2); + await setTrackEnabled(true, true); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.microphone == + STATE_CAPTURE_ENABLED, + "audio should be enabled" + ); + + await observerPromise; + + // The identity UI should show only audio as enabled, as video is muted. + await checkSharingUI({ + video: STATE_CAPTURE_DISABLED, + audio: STATE_CAPTURE_ENABLED, + }); + is(await getVideoTrackMuted(), true, "video track is still muted"); + Assert.deepEqual(await getVideoTrackEvents(), ["mute"], "no new events"); + + // 4. Unmute camera again (video should flow). + observerPromise = expectObserverCalled("recording-device-events"); + await setCameraMuted(false); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.camera == + STATE_CAPTURE_ENABLED, + "video should be enabled" + ); + + await observerPromise; + + // Both streams should show as running. + await checkSharingUI({ + video: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + is(await getVideoTrackMuted(), false, "video track is unmuted"); + Assert.deepEqual( + await getVideoTrackEvents(), + ["mute", "unmute"], + "unmute fired" + ); + + // 5. Mute camera & disable both tracks. + observerPromise = expectObserverCalled("recording-device-events", 3); + await setCameraMuted(true); + await setTrackEnabled(false, false); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.camera == + STATE_CAPTURE_DISABLED, + "video should be disabled" + ); + + await observerPromise; + + // The identity UI should show both as disabled. + await checkSharingUI({ + video: STATE_CAPTURE_DISABLED, + audio: STATE_CAPTURE_DISABLED, + }); + is(await getVideoTrackMuted(), true, "video track is muted"); + Assert.deepEqual( + await getVideoTrackEvents(), + ["mute", "unmute", "mute"], + "mute fired afain" + ); + + // 6. Unmute camera & enable audio (only audio should flow) + observerPromise = expectObserverCalled("recording-device-events", 2); + await setCameraMuted(false); + await setTrackEnabled(true, null); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.microphone == + STATE_CAPTURE_ENABLED, + "audio should be enabled" + ); + + await observerPromise; + + // Only audio should show as running, as video track is still disabled. + await checkSharingUI({ + video: STATE_CAPTURE_DISABLED, + audio: STATE_CAPTURE_ENABLED, + }); + is(await getVideoTrackMuted(), false, "video track is unmuted"); + Assert.deepEqual( + await getVideoTrackEvents(), + ["mute", "unmute", "mute", "unmute"], + "unmute fired even though track is disabled" + ); + + // 7. Enable video track again (video should flow). + observerPromise = expectObserverCalled("recording-device-events"); + await setTrackEnabled(null, true); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.camera == + STATE_CAPTURE_ENABLED, + "video should be enabled" + ); + + await observerPromise; + + // The identity UI should show both as running again. + await checkSharingUI({ + video: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + is(await getVideoTrackMuted(), false, "video track remains unmuted"); + Assert.deepEqual( + await getVideoTrackEvents(), + ["mute", "unmute", "mute", "unmute"], + "no new events fired" + ); + await closeStream(); + }, + }, + + { + desc: "getUserMedia audio+video: disabling & muting microphone in combination", + // Test the following combinations of disabling and muting microphone: + // 1. Disable audio track only. + // 2. Mute microphone & disable video (to have a condition to wait for) + // 3. Enable both audio and video tracks (only video should flow). + // 4. Unmute microphone again (audio should flow). + // 5. Mute microphone & disable both tracks. + // 6. Unmute microphone & enable video (only video should flow) + // 7. Enable audio track again (audio should flow). + run: async function checkDisabledMutedCombination() { + let observerPromise = expectObserverCalled("getUserMedia:request"); + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + await promiseRequestDevice(true, true); + await promise; + await observerPromise; + checkDeviceSelectors(["microphone", "camera"]); + + let indicator = promiseIndicatorWindow(); + let observerPromise1 = expectObserverCalled( + "getUserMedia:response:allow" + ); + let observerPromise2 = expectObserverCalled("recording-device-events"); + 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: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + + // 1. Disable audio track only. + observerPromise = expectObserverCalled("recording-device-events"); + await setTrackEnabled(false, null); + + // Wait for capture state to propagate to the UI asynchronously. + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.microphone == + STATE_CAPTURE_DISABLED, + "audio should be disabled" + ); + + await observerPromise; + + // The identity UI should show only audio as disabled. + await checkSharingUI({ + video: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_DISABLED, + }); + is(await getAudioTrackMuted(), false, "audio track still unmuted"); + Assert.deepEqual( + await getAudioTrackEvents(), + [], + "no audio track events fired yet" + ); + + // 2. Mute microphone & disable video (to have a condition to wait for) + observerPromise = expectObserverCalled("recording-device-events", 2); + await setMicrophoneMuted(true); + await setTrackEnabled(null, false); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.camera == + STATE_CAPTURE_DISABLED, + "camera should be disabled" + ); + + await observerPromise; + + // The identity UI should show both as disabled. + await checkSharingUI({ + video: STATE_CAPTURE_DISABLED, + audio: STATE_CAPTURE_DISABLED, + }); + is(await getAudioTrackMuted(), true, "audio track is muted"); + Assert.deepEqual( + await getAudioTrackEvents(), + ["mute"], + "mute is still fired even though track was disabled" + ); + + // 3. Enable both audio and video tracks (only video should flow). + observerPromise = expectObserverCalled("recording-device-events", 2); + await setTrackEnabled(true, true); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.camera == + STATE_CAPTURE_ENABLED, + "video should be enabled" + ); + + await observerPromise; + + // The identity UI should show only video as enabled, as audio is muted. + await checkSharingUI({ + video: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_DISABLED, + }); + is(await getAudioTrackMuted(), true, "audio track is still muted"); + Assert.deepEqual(await getAudioTrackEvents(), ["mute"], "no new events"); + + // 4. Unmute microphone again (audio should flow). + observerPromise = expectObserverCalled("recording-device-events"); + await setMicrophoneMuted(false); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.microphone == + STATE_CAPTURE_ENABLED, + "audio should be enabled" + ); + + await observerPromise; + + // Both streams should show as running. + await checkSharingUI({ + video: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + is(await getAudioTrackMuted(), false, "audio track is unmuted"); + Assert.deepEqual( + await getAudioTrackEvents(), + ["mute", "unmute"], + "unmute fired" + ); + + // 5. Mute microphone & disable both tracks. + observerPromise = expectObserverCalled("recording-device-events", 3); + await setMicrophoneMuted(true); + await setTrackEnabled(false, false); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.microphone == + STATE_CAPTURE_DISABLED, + "audio should be disabled" + ); + + await observerPromise; + + // The identity UI should show both as disabled. + await checkSharingUI({ + video: STATE_CAPTURE_DISABLED, + audio: STATE_CAPTURE_DISABLED, + }); + is(await getAudioTrackMuted(), true, "audio track is muted"); + Assert.deepEqual( + await getAudioTrackEvents(), + ["mute", "unmute", "mute"], + "mute fired again" + ); + + // 6. Unmute microphone & enable video (only video should flow) + observerPromise = expectObserverCalled("recording-device-events", 2); + await setMicrophoneMuted(false); + await setTrackEnabled(null, true); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.camera == + STATE_CAPTURE_ENABLED, + "video should be enabled" + ); + + await observerPromise; + + // Only video should show as running, as audio track is still disabled. + await checkSharingUI({ + video: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_DISABLED, + }); + is(await getAudioTrackMuted(), false, "audio track is unmuted"); + Assert.deepEqual( + await getAudioTrackEvents(), + ["mute", "unmute", "mute", "unmute"], + "unmute fired even though track is disabled" + ); + + // 7. Enable audio track again (audio should flow). + observerPromise = expectObserverCalled("recording-device-events"); + await setTrackEnabled(true, null); + + await BrowserTestUtils.waitForCondition( + () => + window.gPermissionPanel._sharingState.webRTC.microphone == + STATE_CAPTURE_ENABLED, + "audio should be enabled" + ); + + await observerPromise; + + // The identity UI should show both as running again. + await checkSharingUI({ + video: STATE_CAPTURE_ENABLED, + audio: STATE_CAPTURE_ENABLED, + }); + is(await getAudioTrackMuted(), false, "audio track remains unmuted"); + Assert.deepEqual( + await getAudioTrackEvents(), + ["mute", "unmute", "mute", "unmute"], + "no new events fired" + ); + await closeStream(); + }, + }, +]; + +add_task(async function test() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.getusermedia.camera.off_while_disabled.delay_ms", 0], + ["media.getusermedia.microphone.off_while_disabled.delay_ms", 0], + ], + }); + + SimpleTest.requestCompleteLog(); + await runTests(gTests); +}); -- cgit v1.2.3