browser_devices_get_user_media.js (37828B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 requestLongerTimeout(2); 6 7 const permissionError = 8 "error: NotAllowedError: The request is not allowed " + 9 "by the user agent or the platform in the current context."; 10 11 const getPerm = name => 12 PermissionTestUtils.testExactPermission(gBrowser.contentPrincipal, name); 13 14 function clearPermissions() { 15 PermissionTestUtils.remove(gBrowser.contentPrincipal, "camera"); 16 PermissionTestUtils.remove(gBrowser.contentPrincipal, "microphone"); 17 } 18 19 async function addTabAndLoadBrowser() { 20 const tab = BrowserTestUtils.addTab(gBrowser, "https://example.com"); 21 await BrowserTestUtils.browserLoaded(tab.linkedBrowser); 22 return tab; 23 } 24 25 async function checkSplitViewPanelVisible(tab, isVisible) { 26 const panel = document.getElementById(tab.linkedPanel); 27 await BrowserTestUtils.waitForMutationCondition( 28 panel, 29 { attributes: true }, 30 () => panel.classList.contains("split-view-panel") == isVisible 31 ); 32 if (isVisible) { 33 Assert.ok( 34 gBrowser.splitViewBrowsers.includes(tab.linkedBrowser), 35 "Split view panel is active." 36 ); 37 } else { 38 Assert.ok( 39 !gBrowser.splitViewBrowsers.includes(tab.linkedBrowser), 40 "Split view panel is inactive." 41 ); 42 } 43 } 44 45 var gTests = [ 46 { 47 desc: "getUserMedia audio+video", 48 run: async function checkAudioVideo() { 49 let promise = promisePopupNotificationShown("webRTC-shareDevices"); 50 let observerPromise = expectObserverCalled("getUserMedia:request"); 51 await promiseRequestDevice(true, true); 52 await promise; 53 await observerPromise; 54 55 is( 56 PopupNotifications.getNotification("webRTC-shareDevices").anchorID, 57 "webRTC-shareDevices-notification-icon", 58 "anchored to device icon" 59 ); 60 checkDeviceSelectors(["microphone", "camera"]); 61 62 let indicator = promiseIndicatorWindow(); 63 let observerPromise1 = expectObserverCalled( 64 "getUserMedia:response:allow" 65 ); 66 let observerPromise2 = expectObserverCalled("recording-device-events"); 67 68 promise = promiseMessage("ok", () => { 69 PopupNotifications.panel.firstElementChild.button.click(); 70 }); 71 await observerPromise1; 72 await observerPromise2; 73 await promise; 74 75 Assert.deepEqual( 76 await getMediaCaptureState(), 77 { audio: true, video: true }, 78 "expected camera and microphone to be shared" 79 ); 80 81 await indicator; 82 await checkSharingUI({ audio: true, video: true }); 83 is(getPerm("microphone"), Services.perms.PROMPT_ACTION, "mic once"); 84 is(getPerm("camera"), Services.perms.PROMPT_ACTION, "cam once"); 85 clearPermissions(); 86 await closeStream(); 87 }, 88 }, 89 90 { 91 desc: "getUserMedia audio only", 92 run: async function checkAudioOnly() { 93 let observerPromise = expectObserverCalled("getUserMedia:request"); 94 let promise = promisePopupNotificationShown("webRTC-shareDevices"); 95 await promiseRequestDevice(true); 96 await promise; 97 await observerPromise; 98 99 is( 100 PopupNotifications.getNotification("webRTC-shareDevices").anchorID, 101 "webRTC-shareMicrophone-notification-icon", 102 "anchored to mic icon" 103 ); 104 checkDeviceSelectors(["microphone"]); 105 106 let indicator = promiseIndicatorWindow(); 107 let observerPromise1 = expectObserverCalled( 108 "getUserMedia:response:allow" 109 ); 110 let observerPromise2 = expectObserverCalled("recording-device-events"); 111 112 promise = promiseMessage("ok", () => { 113 PopupNotifications.panel.firstElementChild.button.click(); 114 }); 115 await observerPromise1; 116 await observerPromise2; 117 await promise; 118 Assert.deepEqual( 119 await getMediaCaptureState(), 120 { audio: true }, 121 "expected microphone to be shared" 122 ); 123 124 await indicator; 125 await checkSharingUI({ audio: true }); 126 is(getPerm("microphone"), Services.perms.PROMPT_ACTION, "mic once"); 127 is(getPerm("camera"), Services.perms.UNKNOWN_ACTION, "no cam once"); 128 clearPermissions(); 129 await closeStream(); 130 }, 131 }, 132 133 { 134 desc: "getUserMedia video only", 135 run: async function checkVideoOnly() { 136 let observerPromise = expectObserverCalled("getUserMedia:request"); 137 let promise = promisePopupNotificationShown("webRTC-shareDevices"); 138 await promiseRequestDevice(false, true); 139 await promise; 140 await observerPromise; 141 142 is( 143 PopupNotifications.getNotification("webRTC-shareDevices").anchorID, 144 "webRTC-shareDevices-notification-icon", 145 "anchored to device icon" 146 ); 147 checkDeviceSelectors(["camera"]); 148 149 let indicator = promiseIndicatorWindow(); 150 let observerPromise1 = expectObserverCalled( 151 "getUserMedia:response:allow" 152 ); 153 let observerPromise2 = expectObserverCalled("recording-device-events"); 154 await promiseMessage("ok", () => { 155 PopupNotifications.panel.firstElementChild.button.click(); 156 }); 157 await observerPromise1; 158 await observerPromise2; 159 Assert.deepEqual( 160 await getMediaCaptureState(), 161 { video: true }, 162 "expected camera to be shared" 163 ); 164 165 await indicator; 166 await checkSharingUI({ video: true }); 167 is(getPerm("microphone"), Services.perms.UNKNOWN_ACTION, "no mic once"); 168 is(getPerm("camera"), Services.perms.PROMPT_ACTION, "cam once"); 169 clearPermissions(); 170 await closeStream(); 171 }, 172 }, 173 174 { 175 desc: "getUserMedia video only popup notification with split view", 176 run: async function checkVideoOnlyWithSplitView() { 177 const tab1 = gBrowser.selectedTab; 178 const tab2 = await addTabAndLoadBrowser(); 179 const urlbarButton = document.getElementById("split-view-button"); 180 181 info("Activate split view."); 182 const splitView = gBrowser.addTabSplitView([tab1, tab2]); 183 for (const tab of splitView.tabs) { 184 await checkSplitViewPanelVisible(tab, true); 185 } 186 187 info("Select tabs using tab panels."); 188 await SimpleTest.promiseFocus(tab1.linkedBrowser); 189 let panel = document.getElementById(tab1.linkedPanel); 190 Assert.ok( 191 panel.classList.contains("deck-selected"), 192 "First panel is selected." 193 ); 194 195 let observerPromise = expectObserverCalled("getUserMedia:request"); 196 let promise = promisePopupNotificationShown("webRTC-shareDevices"); 197 await promiseRequestDevice(false, true); 198 await promise; 199 await observerPromise; 200 Assert.ok( 201 PopupNotifications.getNotification("webRTC-shareDevices"), 202 "webRTC-shareDevices popup notification is present" 203 ); 204 205 await SimpleTest.promiseFocus(tab2.linkedBrowser); 206 panel = document.getElementById(tab2.linkedPanel); 207 Assert.ok( 208 panel.classList.contains("deck-selected"), 209 "Second panel is selected." 210 ); 211 212 // Notification should only be present on the splitview panel it affects 213 Assert.ok( 214 !PopupNotifications.getNotification("webRTC-shareDevices"), 215 "webRTC-shareDevices popup notification is not present" 216 ); 217 218 info("Switch back to original split view tab."); 219 await BrowserTestUtils.switchTab(gBrowser, tab1); 220 for (const tab of splitView.tabs) { 221 await checkSplitViewPanelVisible(tab, true); 222 } 223 224 // Check we are able to go back to the original splitview panel and see notification 225 Assert.ok( 226 PopupNotifications.getNotification("webRTC-shareDevices"), 227 "webRTC-shareDevices popup notification is present" 228 ); 229 230 info("Select tabs using tabs"); 231 await BrowserTestUtils.switchTab(gBrowser, tab2); 232 panel = document.getElementById(tab2.linkedPanel); 233 Assert.ok( 234 panel.classList.contains("deck-selected"), 235 "Second panel is selected." 236 ); 237 238 Assert.ok( 239 !PopupNotifications.getNotification("webRTC-shareDevices"), 240 "webRTC-shareDevices popup notification is not present" 241 ); 242 243 info("Switch back to original split view tab."); 244 await BrowserTestUtils.switchTab(gBrowser, tab1); 245 for (const tab of splitView.tabs) { 246 await checkSplitViewPanelVisible(tab, true); 247 } 248 249 Assert.ok( 250 PopupNotifications.getNotification("webRTC-shareDevices"), 251 "webRTC-shareDevices popup notification is present" 252 ); 253 254 await BrowserTestUtils.waitForMutationCondition( 255 PopupNotifications.panel, 256 { childList: true }, 257 () => PopupNotifications.panel?.firstElementChild 258 ); 259 await BrowserTestUtils.waitForMutationCondition( 260 PopupNotifications.panel.firstElementChild, 261 { childList: true }, 262 () => PopupNotifications.panel.firstElementChild?.button 263 ); 264 let indicator = promiseIndicatorWindow(); 265 let observerPromise1 = expectObserverCalled( 266 "getUserMedia:response:allow" 267 ); 268 let observerPromise2 = expectObserverCalled("recording-device-events"); 269 await promiseMessage("ok", () => { 270 PopupNotifications.panel.firstElementChild.button.click(); 271 }); 272 await observerPromise1; 273 await observerPromise2; 274 Assert.deepEqual( 275 await getMediaCaptureState(), 276 { video: true }, 277 "expected camera to be shared" 278 ); 279 280 await indicator; 281 await checkSharingUI({ video: true }); 282 is(getPerm("microphone"), Services.perms.UNKNOWN_ACTION, "no mic once"); 283 is(getPerm("camera"), Services.perms.PROMPT_ACTION, "cam once"); 284 clearPermissions(); 285 await closeStream(); 286 287 info("Remove the split view, keeping tabs intact."); 288 splitView.unsplitTabs(); 289 await checkSplitViewPanelVisible(tab1, false); 290 await checkSplitViewPanelVisible(tab2, false); 291 await BrowserTestUtils.waitForMutationCondition( 292 urlbarButton, 293 { attributes: true }, 294 () => BrowserTestUtils.isHidden(urlbarButton) 295 ); 296 BrowserTestUtils.removeTab(tab2); 297 }, 298 }, 299 300 { 301 desc: 'getUserMedia audio+video, user clicks "Don\'t Share"', 302 run: async function checkDontShare() { 303 let observerPromise = expectObserverCalled("getUserMedia:request"); 304 let promise = promisePopupNotificationShown("webRTC-shareDevices"); 305 await promiseRequestDevice(true, true); 306 await promise; 307 await observerPromise; 308 checkDeviceSelectors(["microphone", "camera"]); 309 310 let observerPromise1 = expectObserverCalled("getUserMedia:response:deny"); 311 let observerPromise2 = expectObserverCalled("recording-window-ended"); 312 await promiseMessage(permissionError, () => { 313 activateSecondaryAction(kActionDeny); 314 }); 315 await observerPromise1; 316 await observerPromise2; 317 318 await checkNotSharing(); 319 320 // Verify that we set 'Temporarily blocked' permissions. 321 let browser = gBrowser.selectedBrowser; 322 let blockedPerms = document.getElementById( 323 "blocked-permissions-container" 324 ); 325 326 let { state, scope } = SitePermissions.getForPrincipal( 327 null, 328 "camera", 329 browser 330 ); 331 Assert.equal(state, SitePermissions.BLOCK); 332 Assert.equal(scope, SitePermissions.SCOPE_TEMPORARY); 333 ok( 334 blockedPerms.querySelector( 335 ".blocked-permission-icon.camera-icon[showing=true]" 336 ), 337 "the blocked camera icon is shown" 338 ); 339 340 ({ state, scope } = SitePermissions.getForPrincipal( 341 null, 342 "microphone", 343 browser 344 )); 345 Assert.equal(state, SitePermissions.BLOCK); 346 Assert.equal(scope, SitePermissions.SCOPE_TEMPORARY); 347 ok( 348 blockedPerms.querySelector( 349 ".blocked-permission-icon.microphone-icon[showing=true]" 350 ), 351 "the blocked microphone icon is shown" 352 ); 353 354 info("requesting devices again to check temporarily blocked permissions"); 355 promise = promiseMessage(permissionError); 356 observerPromise1 = expectObserverCalled("getUserMedia:request"); 357 observerPromise2 = expectObserverCalled("getUserMedia:response:deny"); 358 let observerPromise3 = expectObserverCalled("recording-window-ended"); 359 await promiseRequestDevice(true, true); 360 await promise; 361 await observerPromise1; 362 await observerPromise2; 363 await observerPromise3; 364 await checkNotSharing(); 365 366 SitePermissions.removeFromPrincipal( 367 browser.contentPrincipal, 368 "camera", 369 browser 370 ); 371 SitePermissions.removeFromPrincipal( 372 browser.contentPrincipal, 373 "microphone", 374 browser 375 ); 376 }, 377 }, 378 379 { 380 desc: "getUserMedia audio+video: stop sharing", 381 run: async function checkStopSharing() { 382 let observerPromise = expectObserverCalled("getUserMedia:request"); 383 let promise = promisePopupNotificationShown("webRTC-shareDevices"); 384 await promiseRequestDevice(true, true); 385 await promise; 386 await observerPromise; 387 checkDeviceSelectors(["microphone", "camera"]); 388 389 let indicator = promiseIndicatorWindow(); 390 let observerPromise1 = expectObserverCalled( 391 "getUserMedia:response:allow" 392 ); 393 let observerPromise2 = expectObserverCalled("recording-device-events"); 394 await promiseMessage("ok", () => { 395 PopupNotifications.panel.firstElementChild.button.click(); 396 }); 397 await observerPromise1; 398 await observerPromise2; 399 Assert.deepEqual( 400 await getMediaCaptureState(), 401 { audio: true, video: true }, 402 "expected camera and microphone to be shared" 403 ); 404 405 await indicator; 406 await checkSharingUI({ video: true, audio: true }); 407 408 is(getPerm("microphone"), Services.perms.PROMPT_ACTION, "mic once"); 409 is(getPerm("camera"), Services.perms.PROMPT_ACTION, "cam once"); 410 411 await stopSharing(); 412 413 is(getPerm("microphone"), Services.perms.UNKNOWN_ACTION, "mic revoked"); 414 is(getPerm("camera"), Services.perms.UNKNOWN_ACTION, "cam revoked"); 415 416 // the stream is already closed, but this will do some cleanup anyway 417 await closeStream(true); 418 419 // After stop sharing, gUM(audio+camera) causes a prompt. 420 observerPromise = expectObserverCalled("getUserMedia:request"); 421 promise = promisePopupNotificationShown("webRTC-shareDevices"); 422 await promiseRequestDevice(true, true); 423 await promise; 424 await observerPromise; 425 checkDeviceSelectors(["microphone", "camera"]); 426 427 observerPromise1 = expectObserverCalled("getUserMedia:response:deny"); 428 observerPromise2 = expectObserverCalled("recording-window-ended"); 429 await promiseMessage(permissionError, () => { 430 activateSecondaryAction(kActionDeny); 431 }); 432 433 await observerPromise1; 434 await observerPromise2; 435 await checkNotSharing(); 436 SitePermissions.removeFromPrincipal( 437 null, 438 "screen", 439 gBrowser.selectedBrowser 440 ); 441 SitePermissions.removeFromPrincipal( 442 null, 443 "camera", 444 gBrowser.selectedBrowser 445 ); 446 SitePermissions.removeFromPrincipal( 447 null, 448 "microphone", 449 gBrowser.selectedBrowser 450 ); 451 }, 452 }, 453 454 { 455 desc: "getUserMedia audio+video: reloading the page removes all gUM UI", 456 run: async function checkReloading() { 457 let observerPromise = expectObserverCalled("getUserMedia:request"); 458 let promise = promisePopupNotificationShown("webRTC-shareDevices"); 459 await promiseRequestDevice(true, true); 460 await promise; 461 await observerPromise; 462 checkDeviceSelectors(["microphone", "camera"]); 463 464 let indicator = promiseIndicatorWindow(); 465 let observerPromise1 = expectObserverCalled( 466 "getUserMedia:response:allow" 467 ); 468 let observerPromise2 = expectObserverCalled("recording-device-events"); 469 await promiseMessage("ok", () => { 470 PopupNotifications.panel.firstElementChild.button.click(); 471 }); 472 await observerPromise1; 473 await observerPromise2; 474 Assert.deepEqual( 475 await getMediaCaptureState(), 476 { audio: true, video: true }, 477 "expected camera and microphone to be shared" 478 ); 479 480 await indicator; 481 await checkSharingUI({ video: true, audio: true }); 482 483 await reloadAndAssertClosedStreams(); 484 485 await checkSharingUI( 486 { video: false, audio: false }, 487 undefined, 488 undefined, 489 { 490 audio: { 491 state: SitePermissions.PROMPT, 492 scope: SitePermissions.SCOPE_PERSISTENT, 493 }, 494 video: { 495 state: SitePermissions.PROMPT, 496 scope: SitePermissions.SCOPE_PERSISTENT, 497 }, 498 } 499 ); 500 501 is(getPerm("microphone"), Services.perms.PROMPT_ACTION, "mic once"); 502 is(getPerm("camera"), Services.perms.PROMPT_ACTION, "cam once"); 503 504 observerPromise = expectObserverCalled("getUserMedia:request"); 505 // After the reload, gUM(audio+camera) causes a prompt. 506 promise = promisePopupNotificationShown("webRTC-shareDevices"); 507 await promiseRequestDevice(true, true); 508 await promise; 509 await observerPromise; 510 checkDeviceSelectors(["microphone", "camera"]); 511 512 observerPromise1 = expectObserverCalled("getUserMedia:response:deny"); 513 observerPromise2 = expectObserverCalled("recording-window-ended"); 514 515 await promiseMessage(permissionError, () => { 516 activateSecondaryAction(kActionDeny); 517 }); 518 519 await observerPromise1; 520 await observerPromise2; 521 await checkNotSharing(); 522 SitePermissions.removeFromPrincipal( 523 null, 524 "screen", 525 gBrowser.selectedBrowser 526 ); 527 SitePermissions.removeFromPrincipal( 528 null, 529 "camera", 530 gBrowser.selectedBrowser 531 ); 532 SitePermissions.removeFromPrincipal( 533 null, 534 "microphone", 535 gBrowser.selectedBrowser 536 ); 537 }, 538 }, 539 540 { 541 desc: "getUserMedia prompt: Always/Never Share", 542 run: async function checkRememberCheckbox() { 543 let elt = id => document.getElementById(id); 544 545 async function checkPerm( 546 aRequestAudio, 547 aRequestVideo, 548 aExpectedAudioPerm, 549 aExpectedVideoPerm, 550 aNever 551 ) { 552 let observerPromise = expectObserverCalled("getUserMedia:request"); 553 let promise = promisePopupNotificationShown("webRTC-shareDevices"); 554 await promiseRequestDevice(aRequestAudio, aRequestVideo); 555 await promise; 556 await observerPromise; 557 558 let rememberCheckBoxLabel = "Remember this decision"; 559 if (aRequestVideo) { 560 rememberCheckBoxLabel = "Remember for all cameras"; 561 if (aRequestAudio) { 562 rememberCheckBoxLabel = "Remember for all cameras and microphones"; 563 } 564 } else if (aRequestAudio) { 565 rememberCheckBoxLabel = "Remember for all microphones"; 566 } 567 568 is( 569 PopupNotifications.getNotification("webRTC-shareDevices").options 570 .checkbox.label, 571 rememberCheckBoxLabel, 572 "Correct string used for decision checkbox" 573 ); 574 575 is( 576 elt("webRTC-selectMicrophone").hidden, 577 !aRequestAudio, 578 "microphone selector expected to be " + 579 (aRequestAudio ? "visible" : "hidden") 580 ); 581 582 is( 583 elt("webRTC-selectCamera").hidden, 584 !aRequestVideo, 585 "camera selector expected to be " + 586 (aRequestVideo ? "visible" : "hidden") 587 ); 588 589 let expected = {}; 590 let observerPromises = []; 591 let expectedMessage = aNever ? permissionError : "ok"; 592 if (expectedMessage == "ok") { 593 observerPromises.push( 594 expectObserverCalled("getUserMedia:response:allow") 595 ); 596 observerPromises.push( 597 expectObserverCalled("recording-device-events") 598 ); 599 if (aRequestVideo) { 600 expected.video = true; 601 } 602 if (aRequestAudio) { 603 expected.audio = true; 604 } 605 } else { 606 observerPromises.push( 607 expectObserverCalled("getUserMedia:response:deny") 608 ); 609 observerPromises.push(expectObserverCalled("recording-window-ended")); 610 } 611 await promiseMessage(expectedMessage, () => { 612 activateSecondaryAction(aNever ? kActionNever : kActionAlways); 613 }); 614 await Promise.all(observerPromises); 615 Assert.deepEqual( 616 await getMediaCaptureState(), 617 expected, 618 "expected " + Object.keys(expected).join(" and ") + " to be shared" 619 ); 620 621 function checkDevicePermissions(aDevice, aExpected) { 622 let uri = gBrowser.selectedBrowser.documentURI; 623 let devicePerms = PermissionTestUtils.testExactPermission( 624 uri, 625 aDevice 626 ); 627 if (aExpected === undefined) { 628 is( 629 devicePerms, 630 Services.perms.UNKNOWN_ACTION, 631 "no " + aDevice + " persistent permissions" 632 ); 633 } else { 634 is( 635 devicePerms, 636 aExpected 637 ? Services.perms.ALLOW_ACTION 638 : Services.perms.DENY_ACTION, 639 aDevice + " persistently " + (aExpected ? "allowed" : "denied") 640 ); 641 } 642 PermissionTestUtils.remove(uri, aDevice); 643 } 644 checkDevicePermissions("microphone", aExpectedAudioPerm); 645 checkDevicePermissions("camera", aExpectedVideoPerm); 646 647 if (expectedMessage == "ok") { 648 await closeStream(); 649 } 650 } 651 652 // 3 cases where the user accepts the device prompt. 653 info("audio+video, user grants, expect both Services.perms set to allow"); 654 await checkPerm(true, true, true, true); 655 info( 656 "audio only, user grants, check audio perm set to allow, video perm not set" 657 ); 658 await checkPerm(true, false, true, undefined); 659 info( 660 "video only, user grants, check video perm set to allow, audio perm not set" 661 ); 662 await checkPerm(false, true, undefined, true); 663 664 // 3 cases where the user rejects the device request by using 'Never Share'. 665 info( 666 "audio only, user denies, expect audio perm set to deny, video not set" 667 ); 668 await checkPerm(true, false, false, undefined, true); 669 info( 670 "video only, user denies, expect video perm set to deny, audio perm not set" 671 ); 672 await checkPerm(false, true, undefined, false, true); 673 info("audio+video, user denies, expect both Services.perms set to deny"); 674 await checkPerm(true, true, false, false, true); 675 }, 676 }, 677 678 { 679 desc: "getUserMedia without prompt: use persistent permissions", 680 run: async function checkUsePersistentPermissions() { 681 async function usePerm( 682 aAllowAudio, 683 aAllowVideo, 684 aRequestAudio, 685 aRequestVideo, 686 aExpectStream 687 ) { 688 let uri = gBrowser.selectedBrowser.documentURI; 689 690 if (aAllowAudio !== undefined) { 691 PermissionTestUtils.add( 692 uri, 693 "microphone", 694 aAllowAudio 695 ? Services.perms.ALLOW_ACTION 696 : Services.perms.DENY_ACTION 697 ); 698 } 699 if (aAllowVideo !== undefined) { 700 PermissionTestUtils.add( 701 uri, 702 "camera", 703 aAllowVideo 704 ? Services.perms.ALLOW_ACTION 705 : Services.perms.DENY_ACTION 706 ); 707 } 708 709 if (aExpectStream === undefined) { 710 // Check that we get a prompt. 711 let observerPromise = expectObserverCalled("getUserMedia:request"); 712 let promise = promisePopupNotificationShown("webRTC-shareDevices"); 713 await promiseRequestDevice(aRequestAudio, aRequestVideo); 714 await promise; 715 await observerPromise; 716 717 // Deny the request to cleanup... 718 let observerPromise1 = expectObserverCalled( 719 "getUserMedia:response:deny" 720 ); 721 let observerPromise2 = expectObserverCalled("recording-window-ended"); 722 await promiseMessage(permissionError, () => { 723 activateSecondaryAction(kActionDeny); 724 }); 725 await observerPromise1; 726 await observerPromise2; 727 728 let browser = gBrowser.selectedBrowser; 729 SitePermissions.removeFromPrincipal(null, "camera", browser); 730 SitePermissions.removeFromPrincipal(null, "microphone", browser); 731 } else { 732 let expectedMessage = aExpectStream ? "ok" : permissionError; 733 734 let observerPromises = [expectObserverCalled("getUserMedia:request")]; 735 if (expectedMessage == "ok") { 736 observerPromises.push( 737 expectObserverCalled("getUserMedia:response:allow"), 738 expectObserverCalled("recording-device-events") 739 ); 740 } else { 741 observerPromises.push( 742 expectObserverCalled("getUserMedia:response:deny"), 743 expectObserverCalled("recording-window-ended") 744 ); 745 } 746 747 let promise = promiseMessage(expectedMessage); 748 await promiseRequestDevice(aRequestAudio, aRequestVideo); 749 await promise; 750 await Promise.all(observerPromises); 751 752 if (expectedMessage == "ok") { 753 await promiseNoPopupNotification("webRTC-shareDevices"); 754 755 // Check what's actually shared. 756 let expected = {}; 757 if (aAllowVideo && aRequestVideo) { 758 expected.video = true; 759 } 760 if (aAllowAudio && aRequestAudio) { 761 expected.audio = true; 762 } 763 Assert.deepEqual( 764 await getMediaCaptureState(), 765 expected, 766 "expected " + 767 Object.keys(expected).join(" and ") + 768 " to be shared" 769 ); 770 771 await closeStream(); 772 } 773 } 774 775 PermissionTestUtils.remove(uri, "camera"); 776 PermissionTestUtils.remove(uri, "microphone"); 777 } 778 779 // Set both permissions identically 780 info("allow audio+video, request audio+video, expect ok (audio+video)"); 781 await usePerm(true, true, true, true, true); 782 info("deny audio+video, request audio+video, expect denied"); 783 await usePerm(false, false, true, true, false); 784 785 // Allow audio, deny video. 786 info("allow audio, deny video, request audio+video, expect denied"); 787 await usePerm(true, false, true, true, false); 788 info("allow audio, deny video, request audio, expect ok (audio)"); 789 await usePerm(true, false, true, false, true); 790 info("allow audio, deny video, request video, expect denied"); 791 await usePerm(true, false, false, true, false); 792 793 // Deny audio, allow video. 794 info("deny audio, allow video, request audio+video, expect denied"); 795 await usePerm(false, true, true, true, false); 796 info("deny audio, allow video, request audio, expect denied"); 797 await usePerm(false, true, true, false, false); 798 info("deny audio, allow video, request video, expect ok (video)"); 799 await usePerm(false, true, false, true, true); 800 801 // Allow audio, video not set. 802 info("allow audio, request audio+video, expect prompt"); 803 await usePerm(true, undefined, true, true, undefined); 804 info("allow audio, request audio, expect ok (audio)"); 805 await usePerm(true, undefined, true, false, true); 806 info("allow audio, request video, expect prompt"); 807 await usePerm(true, undefined, false, true, undefined); 808 809 // Deny audio, video not set. 810 info("deny audio, request audio+video, expect denied"); 811 await usePerm(false, undefined, true, true, false); 812 info("deny audio, request audio, expect denied"); 813 await usePerm(false, undefined, true, false, false); 814 info("deny audio, request video, expect prompt"); 815 await usePerm(false, undefined, false, true, undefined); 816 817 // Allow video, audio not set. 818 info("allow video, request audio+video, expect prompt"); 819 await usePerm(undefined, true, true, true, undefined); 820 info("allow video, request audio, expect prompt"); 821 await usePerm(undefined, true, true, false, undefined); 822 info("allow video, request video, expect ok (video)"); 823 await usePerm(undefined, true, false, true, true); 824 825 // Deny video, audio not set. 826 info("deny video, request audio+video, expect denied"); 827 await usePerm(undefined, false, true, true, false); 828 info("deny video, request audio, expect prompt"); 829 await usePerm(undefined, false, true, false, undefined); 830 info("deny video, request video, expect denied"); 831 await usePerm(undefined, false, false, true, false); 832 }, 833 }, 834 835 { 836 desc: "Stop Sharing removes permissions", 837 run: async function checkStopSharingRemovesPermissions() { 838 async function stopAndCheckPerm( 839 aRequestAudio, 840 aRequestVideo, 841 aStopAudio = aRequestAudio, 842 aStopVideo = aRequestVideo 843 ) { 844 let uri = gBrowser.selectedBrowser.documentURI; 845 846 // Initially set both permissions to 'allow'. 847 PermissionTestUtils.add(uri, "microphone", Services.perms.ALLOW_ACTION); 848 PermissionTestUtils.add(uri, "camera", Services.perms.ALLOW_ACTION); 849 // Also set device-specific temporary allows. 850 SitePermissions.setForPrincipal( 851 gBrowser.contentPrincipal, 852 "microphone^myDevice", 853 SitePermissions.ALLOW, 854 SitePermissions.SCOPE_TEMPORARY, 855 gBrowser.selectedBrowser, 856 10000000 857 ); 858 SitePermissions.setForPrincipal( 859 gBrowser.contentPrincipal, 860 "camera^myDevice2", 861 SitePermissions.ALLOW, 862 SitePermissions.SCOPE_TEMPORARY, 863 gBrowser.selectedBrowser, 864 10000000 865 ); 866 867 if (aRequestAudio || aRequestVideo) { 868 let indicator = promiseIndicatorWindow(); 869 let observerPromise1 = expectObserverCalled("getUserMedia:request"); 870 let observerPromise2 = expectObserverCalled( 871 "getUserMedia:response:allow" 872 ); 873 let observerPromise3 = expectObserverCalled( 874 "recording-device-events" 875 ); 876 // Start sharing what's been requested. 877 let promise = promiseMessage("ok"); 878 await promiseRequestDevice(aRequestAudio, aRequestVideo); 879 await promise; 880 await observerPromise1; 881 await observerPromise2; 882 await observerPromise3; 883 884 await indicator; 885 await checkSharingUI( 886 { video: aRequestVideo, audio: aRequestAudio }, 887 undefined, 888 undefined, 889 { 890 video: { scope: SitePermissions.SCOPE_PERSISTENT }, 891 audio: { scope: SitePermissions.SCOPE_PERSISTENT }, 892 } 893 ); 894 await stopSharing(aStopVideo ? "camera" : "microphone"); 895 } else { 896 await revokePermission(aStopVideo ? "camera" : "microphone"); 897 } 898 899 // Check that permissions have been removed as expected. 900 let audioPerm = SitePermissions.getForPrincipal( 901 gBrowser.contentPrincipal, 902 "microphone", 903 gBrowser.selectedBrowser 904 ); 905 let audioPermDevice = SitePermissions.getForPrincipal( 906 gBrowser.contentPrincipal, 907 "microphone^myDevice", 908 gBrowser.selectedBrowser 909 ); 910 911 if ( 912 aRequestAudio || 913 aRequestVideo || 914 aStopAudio || 915 (aStopVideo && aRequestAudio) 916 ) { 917 Assert.deepEqual( 918 audioPerm, 919 { 920 state: SitePermissions.UNKNOWN, 921 scope: SitePermissions.SCOPE_PERSISTENT, 922 }, 923 "microphone permissions removed" 924 ); 925 Assert.deepEqual( 926 audioPermDevice, 927 { 928 state: SitePermissions.UNKNOWN, 929 scope: SitePermissions.SCOPE_PERSISTENT, 930 }, 931 "microphone device-specific permissions removed" 932 ); 933 } else { 934 Assert.deepEqual( 935 audioPerm, 936 { 937 state: SitePermissions.ALLOW, 938 scope: SitePermissions.SCOPE_PERSISTENT, 939 }, 940 "microphone permissions untouched" 941 ); 942 Assert.deepEqual( 943 audioPermDevice, 944 { 945 state: SitePermissions.ALLOW, 946 scope: SitePermissions.SCOPE_TEMPORARY, 947 }, 948 "microphone device-specific permissions untouched" 949 ); 950 } 951 952 let videoPerm = SitePermissions.getForPrincipal( 953 gBrowser.contentPrincipal, 954 "camera", 955 gBrowser.selectedBrowser 956 ); 957 let videoPermDevice = SitePermissions.getForPrincipal( 958 gBrowser.contentPrincipal, 959 "camera^myDevice2", 960 gBrowser.selectedBrowser 961 ); 962 if ( 963 aRequestAudio || 964 aRequestVideo || 965 aStopVideo || 966 (aStopAudio && aRequestVideo) 967 ) { 968 Assert.deepEqual( 969 videoPerm, 970 { 971 state: SitePermissions.UNKNOWN, 972 scope: SitePermissions.SCOPE_PERSISTENT, 973 }, 974 "camera permissions removed" 975 ); 976 Assert.deepEqual( 977 videoPermDevice, 978 { 979 state: SitePermissions.UNKNOWN, 980 scope: SitePermissions.SCOPE_PERSISTENT, 981 }, 982 "camera device-specific permissions removed" 983 ); 984 } else { 985 Assert.deepEqual( 986 videoPerm, 987 { 988 state: SitePermissions.ALLOW, 989 scope: SitePermissions.SCOPE_PERSISTENT, 990 }, 991 "camera permissions untouched" 992 ); 993 Assert.deepEqual( 994 videoPermDevice, 995 { 996 state: SitePermissions.ALLOW, 997 scope: SitePermissions.SCOPE_TEMPORARY, 998 }, 999 "camera device-specific permissions untouched" 1000 ); 1001 } 1002 await checkNotSharing(); 1003 1004 // Cleanup. 1005 await closeStream(true); 1006 1007 SitePermissions.removeFromPrincipal( 1008 gBrowser.contentPrincipal, 1009 "camera", 1010 gBrowser.selectedBrowser 1011 ); 1012 SitePermissions.removeFromPrincipal( 1013 gBrowser.contentPrincipal, 1014 "microphone", 1015 gBrowser.selectedBrowser 1016 ); 1017 } 1018 1019 info("request audio+video, stop sharing video resets both"); 1020 await stopAndCheckPerm(true, true); 1021 info("request audio only, stop sharing audio resets both"); 1022 await stopAndCheckPerm(true, false); 1023 info("request video only, stop sharing video resets both"); 1024 await stopAndCheckPerm(false, true); 1025 info("request audio only, stop sharing video resets both"); 1026 await stopAndCheckPerm(true, false, false, true); 1027 info("request video only, stop sharing audio resets both"); 1028 await stopAndCheckPerm(false, true, true, false); 1029 info("request neither, stop audio affects audio only"); 1030 await stopAndCheckPerm(false, false, true, false); 1031 info("request neither, stop video affects video only"); 1032 await stopAndCheckPerm(false, false, false, true); 1033 }, 1034 }, 1035 1036 { 1037 desc: "'Always Allow' disabled on http pages", 1038 run: async function checkNoAlwaysOnHttp() { 1039 // Load an http page instead of the https version. 1040 await SpecialPowers.pushPrefEnv({ 1041 set: [ 1042 ["media.devices.insecure.enabled", true], 1043 ["media.getusermedia.insecure.enabled", true], 1044 // explicitly testing an http page, setting 1045 // https-first to false. 1046 ["dom.security.https_first", false], 1047 ], 1048 }); 1049 1050 // Disable while loading a new page 1051 await disableObserverVerification(); 1052 1053 let browser = gBrowser.selectedBrowser; 1054 BrowserTestUtils.startLoadingURIString( 1055 browser, 1056 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 1057 browser.documentURI.spec.replace("https://", "http://") 1058 ); 1059 await BrowserTestUtils.browserLoaded(browser); 1060 1061 await enableObserverVerification(); 1062 1063 // Initially set both permissions to 'allow'. 1064 let uri = browser.documentURI; 1065 PermissionTestUtils.add(uri, "microphone", Services.perms.ALLOW_ACTION); 1066 PermissionTestUtils.add(uri, "camera", Services.perms.ALLOW_ACTION); 1067 1068 // Request devices and expect a prompt despite the saved 'Allow' permission, 1069 // because the connection isn't secure. 1070 let observerPromise = expectObserverCalled("getUserMedia:request"); 1071 let promise = promisePopupNotificationShown("webRTC-shareDevices"); 1072 await promiseRequestDevice(true, true); 1073 await promise; 1074 await observerPromise; 1075 1076 // Ensure that checking the 'Remember this decision' checkbox disables 1077 // 'Allow'. 1078 let notification = PopupNotifications.panel.firstElementChild; 1079 let checkbox = notification.checkbox; 1080 ok(!!checkbox, "checkbox is present"); 1081 ok(!checkbox.checked, "checkbox is not checked"); 1082 checkbox.click(); 1083 ok(checkbox.checked, "checkbox now checked"); 1084 ok(notification.button.disabled, "Allow button is disabled"); 1085 ok( 1086 !notification.hasAttribute("warninghidden"), 1087 "warning message is shown" 1088 ); 1089 1090 // Cleanup. 1091 await closeStream(true); 1092 PermissionTestUtils.remove(uri, "camera"); 1093 PermissionTestUtils.remove(uri, "microphone"); 1094 }, 1095 }, 1096 ]; 1097 1098 add_task(async function test() { 1099 await runTests(gTests); 1100 });