tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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