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