tor-browser

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

browser_devices_get_user_media_grace.js (13895B)


      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 "use strict";
      6 
      7 requestLongerTimeout(2);
      8 
      9 const permissionError =
     10  "error: NotAllowedError: The request is not allowed " +
     11  "by the user agent or the platform in the current context.";
     12 
     13 const SAME_ORIGIN = "https://example.com";
     14 const CROSS_ORIGIN = "https://example.org";
     15 
     16 const PATH = "/browser/browser/base/content/test/webrtc/get_user_media.html";
     17 const PATH2 = "/browser/browser/base/content/test/webrtc/get_user_media2.html";
     18 
     19 const GRACE_PERIOD_MS = 3000;
     20 const WAIT_PERIOD_MS = GRACE_PERIOD_MS + 500;
     21 
     22 // We're inherently testing timeouts (grace periods)
     23 /* eslint-disable mozilla/no-arbitrary-setTimeout */
     24 const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
     25 const perms = SitePermissions;
     26 
     27 // These tests focus on camera and microphone, so we define some helpers.
     28 
     29 async function prompt(audio, video) {
     30  let observerPromise = expectObserverCalled("getUserMedia:request");
     31  let promise = promisePopupNotificationShown("webRTC-shareDevices");
     32  await promiseRequestDevice(audio, video);
     33  await promise;
     34  await observerPromise;
     35  const expectedDeviceSelectorTypes = [
     36    audio && "microphone",
     37    video && "camera",
     38  ].filter(x => x);
     39  checkDeviceSelectors(expectedDeviceSelectorTypes);
     40 }
     41 
     42 async function allow(audio, video) {
     43  let indicator = promiseIndicatorWindow();
     44  let observerPromise1 = expectObserverCalled("getUserMedia:response:allow");
     45  let observerPromise2 = expectObserverCalled("recording-device-events");
     46  await promiseMessage("ok", () => {
     47    PopupNotifications.panel.firstElementChild.button.click();
     48  });
     49  await observerPromise1;
     50  await observerPromise2;
     51  Assert.deepEqual(
     52    Object.assign({ audio: false, video: false }, await getMediaCaptureState()),
     53    { audio, video },
     54    `expected ${video ? "camera " : ""} ${audio ? "microphone " : ""}shared`
     55  );
     56  await indicator;
     57  await checkSharingUI({ audio, video });
     58 }
     59 
     60 async function deny(action) {
     61  let observerPromise1 = expectObserverCalled("getUserMedia:response:deny");
     62  let observerPromise2 = expectObserverCalled("recording-window-ended");
     63  await promiseMessage(permissionError, () => {
     64    activateSecondaryAction(action);
     65  });
     66  await observerPromise1;
     67  await observerPromise2;
     68  await checkNotSharing();
     69 }
     70 
     71 async function noPrompt(audio, video) {
     72  let observerPromises = [
     73    expectObserverCalled("getUserMedia:request"),
     74    expectObserverCalled("getUserMedia:response:allow"),
     75    expectObserverCalled("recording-device-events"),
     76  ];
     77  let promise = promiseMessage("ok");
     78  await promiseRequestDevice(audio, video);
     79  await promise;
     80  await Promise.all(observerPromises);
     81  await promiseNoPopupNotification("webRTC-shareDevices");
     82  Assert.deepEqual(
     83    Object.assign({ audio: false, video: false }, await getMediaCaptureState()),
     84    { audio, video },
     85    `expected ${video ? "camera " : ""} ${audio ? "microphone " : ""}shared`
     86  );
     87  await checkSharingUI({ audio, video });
     88 }
     89 
     90 async function navigate(browser, url) {
     91  await disableObserverVerification();
     92  let loaded = BrowserTestUtils.browserLoaded(browser, false, url);
     93  await SpecialPowers.spawn(
     94    browser,
     95    [url],
     96    u => (content.document.location = u)
     97  );
     98  await loaded;
     99  await enableObserverVerification();
    100 }
    101 
    102 var gTests = [
    103  {
    104    desc: "getUserMedia camera+mic survives track.stop but not past grace",
    105    run: async function checkAudioVideoGracePastStop() {
    106      await prompt(true, true);
    107      await allow(true, true);
    108 
    109      info(
    110        "After closing all streams, gUM(camera+mic) returns a stream " +
    111          "without prompting within grace period."
    112      );
    113      await closeStream();
    114      await checkNotSharingWithinGracePeriod();
    115      await noPrompt(true, true);
    116 
    117      info(
    118        "After closing all streams, gUM(mic) returns a stream " +
    119          "without prompting within grace period."
    120      );
    121      await closeStream();
    122      await checkNotSharingWithinGracePeriod();
    123      await noPrompt(true, false);
    124 
    125      info(
    126        "After closing all streams, gUM(camera) returns a stream " +
    127          "without prompting within grace period."
    128      );
    129      await closeStream();
    130      await checkNotSharingWithinGracePeriod();
    131      await noPrompt(false, true);
    132 
    133      info("gUM(screen) still causes a prompt.");
    134      let observerPromise = expectObserverCalled("getUserMedia:request");
    135      let promise = promisePopupNotificationShown("webRTC-shareDevices");
    136      await promiseRequestDevice(false, true, null, "screen");
    137      await promise;
    138      await observerPromise;
    139 
    140      is(
    141        PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
    142        "webRTC-shareScreen-notification-icon",
    143        "anchored to device icon"
    144      );
    145      checkDeviceSelectors(["screen"]);
    146 
    147      observerPromise = expectObserverCalled("getUserMedia:response:deny");
    148      await promiseMessage(permissionError, () => {
    149        activateSecondaryAction(kActionDeny);
    150      });
    151      await observerPromise;
    152      perms.removeFromPrincipal(null, "screen", gBrowser.selectedBrowser);
    153 
    154      await closeStream();
    155      info("Closed stream. Waiting past grace period.");
    156      await checkNotSharingWithinGracePeriod();
    157      await wait(WAIT_PERIOD_MS);
    158      await checkNotSharing();
    159 
    160      info("After grace period expires, gUM(camera) causes a prompt.");
    161      await prompt(false, true);
    162      await deny(kActionDeny);
    163      perms.removeFromPrincipal(null, "camera", gBrowser.selectedBrowser);
    164 
    165      info("After grace period expires, gUM(mic) causes a prompt.");
    166      await prompt(true, false);
    167      await deny(kActionDeny);
    168      perms.removeFromPrincipal(null, "microphone", gBrowser.selectedBrowser);
    169    },
    170  },
    171 
    172  {
    173    desc: "getUserMedia camera+mic survives page reload but not past grace",
    174    run: async function checkAudioVideoGracePastReload() {
    175      await prompt(true, true);
    176      await allow(true, true);
    177      await closeStream();
    178 
    179      await reloadFromContent();
    180      info(
    181        "After page reload, gUM(camera+mic) returns a stream " +
    182          "without prompting within grace period."
    183      );
    184      await checkNotSharingWithinGracePeriod();
    185      await noPrompt(true, true);
    186      await closeStream();
    187 
    188      await reloadAsUser();
    189      info(
    190        "After user page reload, gUM(camera+mic) returns a stream " +
    191          "without prompting within grace period."
    192      );
    193      await checkNotSharingWithinGracePeriod();
    194      await noPrompt(true, true);
    195 
    196      info("gUM(screen) still causes a prompt.");
    197      let observerPromise = expectObserverCalled("getUserMedia:request");
    198      let promise = promisePopupNotificationShown("webRTC-shareDevices");
    199      await promiseRequestDevice(false, true, null, "screen");
    200      await promise;
    201      await observerPromise;
    202 
    203      is(
    204        PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
    205        "webRTC-shareScreen-notification-icon",
    206        "anchored to device icon"
    207      );
    208      checkDeviceSelectors(["screen"]);
    209 
    210      observerPromise = expectObserverCalled("getUserMedia:response:deny");
    211      await promiseMessage(permissionError, () => {
    212        activateSecondaryAction(kActionDeny);
    213      });
    214      await observerPromise;
    215      perms.removeFromPrincipal(null, "screen", gBrowser.selectedBrowser);
    216 
    217      await closeStream();
    218      info("Closed stream. Waiting past grace period.");
    219      await checkNotSharingWithinGracePeriod();
    220      await wait(WAIT_PERIOD_MS);
    221      await checkNotSharing();
    222 
    223      info("After grace period expires, gUM(camera) causes a prompt.");
    224      await prompt(false, true);
    225      await deny(kActionDeny);
    226      perms.removeFromPrincipal(null, "camera", gBrowser.selectedBrowser);
    227 
    228      info("After grace period expires, gUM(mic) causes a prompt.");
    229      await prompt(true, false);
    230      await deny(kActionDeny);
    231      perms.removeFromPrincipal(null, "microphone", gBrowser.selectedBrowser);
    232    },
    233  },
    234 
    235  {
    236    desc: "getUserMedia camera+mic grace period does not carry over to new tab",
    237    run: async function checkAudioVideoGraceEndsNewTab() {
    238      await prompt(true, true);
    239      await allow(true, true);
    240 
    241      info("Open same page in a new tab");
    242      await disableObserverVerification();
    243      await BrowserTestUtils.withNewTab(SAME_ORIGIN + PATH, async () => {
    244        info("In new tab, gUM(camera+mic) causes a prompt.");
    245        await prompt(true, true);
    246      });
    247      info("Closed tab");
    248      await enableObserverVerification();
    249      await closeStream();
    250      info("Closed stream. Waiting past grace period.");
    251      await checkNotSharingWithinGracePeriod();
    252      await wait(WAIT_PERIOD_MS);
    253      await checkNotSharing();
    254 
    255      info("After grace period expires, gUM(camera+mic) causes a prompt.");
    256      await prompt(true, true);
    257      await deny(kActionDeny);
    258      perms.removeFromPrincipal(null, "camera", gBrowser.selectedBrowser);
    259      perms.removeFromPrincipal(null, "microphone", gBrowser.selectedBrowser);
    260    },
    261  },
    262 
    263  {
    264    desc: "getUserMedia camera+mic survives navigation but not past grace",
    265    run: async function checkAudioVideoGracePastNavigation(browser) {
    266      // Use longer grace period in this test to accommodate navigation
    267      const LONG_GRACE_PERIOD_MS = 9000;
    268      const LONG_WAIT_PERIOD_MS = LONG_GRACE_PERIOD_MS + 500;
    269      await SpecialPowers.pushPrefEnv({
    270        set: [
    271          ["privacy.webrtc.deviceGracePeriodTimeoutMs", LONG_GRACE_PERIOD_MS],
    272        ],
    273      });
    274      await prompt(true, true);
    275      await allow(true, true);
    276      await closeStream();
    277 
    278      info("Navigate to a second same-origin page");
    279      await navigate(browser, SAME_ORIGIN + PATH2);
    280      info(
    281        "After navigating to second same-origin page, gUM(camera+mic) " +
    282          "returns a stream without prompting within grace period."
    283      );
    284      await checkNotSharingWithinGracePeriod();
    285      await noPrompt(true, true);
    286      await closeStream();
    287 
    288      info("Closed stream. Waiting past grace period.");
    289      await checkNotSharingWithinGracePeriod();
    290      await wait(LONG_WAIT_PERIOD_MS);
    291      await checkNotSharing();
    292 
    293      info("After grace period expires, gUM(camera+mic) causes a prompt.");
    294      await prompt(true, true);
    295      await allow(true, true);
    296 
    297      info("Navigate to a different-origin page");
    298      await navigate(browser, CROSS_ORIGIN + PATH2);
    299      info(
    300        "After navigating to a different-origin page, gUM(camera+mic) " +
    301          "causes a prompt."
    302      );
    303      await prompt(true, true);
    304      await deny(kActionDeny);
    305      perms.removeFromPrincipal(null, "camera", gBrowser.selectedBrowser);
    306      perms.removeFromPrincipal(null, "microphone", gBrowser.selectedBrowser);
    307 
    308      info("Navigate back to the first page");
    309      await navigate(browser, SAME_ORIGIN + PATH);
    310      info(
    311        "After navigating back to the first page, gUM(camera+mic) " +
    312          "returns a stream without prompting within grace period."
    313      );
    314      await checkNotSharingWithinGracePeriod();
    315      await noPrompt(true, true);
    316      await closeStream();
    317      info("Closed stream. Waiting past grace period.");
    318      await checkNotSharingWithinGracePeriod();
    319      await wait(LONG_WAIT_PERIOD_MS);
    320      await checkNotSharing();
    321 
    322      info("After grace period expires, gUM(camera+mic) causes a prompt.");
    323      await prompt(true, true);
    324      await deny(kActionDeny);
    325      perms.removeFromPrincipal(null, "camera", gBrowser.selectedBrowser);
    326      perms.removeFromPrincipal(null, "microphone", gBrowser.selectedBrowser);
    327    },
    328  },
    329 
    330  {
    331    desc: "getUserMedia camera+mic grace period cleared on permission block",
    332    run: async function checkAudioVideoGraceEndsNewTab() {
    333      await SpecialPowers.pushPrefEnv({
    334        set: [["privacy.webrtc.deviceGracePeriodTimeoutMs", 10000]],
    335      });
    336      info("Set up longer camera grace period.");
    337      await prompt(false, true);
    338      await allow(false, true);
    339      await closeStream();
    340      let principal = gBrowser.selectedBrowser.contentPrincipal;
    341      info("Request both to get prompted so we can block both.");
    342      await prompt(true, true);
    343      // We need to remember this decision to set a block permission here and not just 'Not now' the request, see Bug:1609578
    344      await deny(kActionNever);
    345      // Clear the block so we can prompt again.
    346      perms.removeFromPrincipal(principal, "camera", gBrowser.selectedBrowser);
    347      perms.removeFromPrincipal(
    348        principal,
    349        "microphone",
    350        gBrowser.selectedBrowser
    351      );
    352 
    353      info("Revoking permission clears camera grace period.");
    354      await prompt(false, true);
    355      await deny(kActionDeny);
    356      perms.removeFromPrincipal(null, "camera", gBrowser.selectedBrowser);
    357 
    358      info("Set up longer microphone grace period.");
    359      await prompt(true, false);
    360      await allow(true, false);
    361      await closeStream();
    362 
    363      info("Request both to get prompted so we can block both.");
    364      await prompt(true, true);
    365      // We need to remember this decision to be able to set a block permission here
    366      await deny(kActionNever);
    367      perms.removeFromPrincipal(principal, "camera", gBrowser.selectedBrowser);
    368      perms.removeFromPrincipal(
    369        principal,
    370        "microphone",
    371        gBrowser.selectedBrowser
    372      );
    373 
    374      info("Revoking permission clears microphone grace period.");
    375      await prompt(true, false);
    376      // We need to remember this decision to be able to set a block permission here
    377      await deny(kActionNever);
    378      perms.removeFromPrincipal(null, "microphone", gBrowser.selectedBrowser);
    379    },
    380  },
    381 ];
    382 
    383 add_task(async function test() {
    384  await SpecialPowers.pushPrefEnv({
    385    set: [["privacy.webrtc.deviceGracePeriodTimeoutMs", GRACE_PERIOD_MS]],
    386  });
    387  await runTests(gTests);
    388 });