tor-browser

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

browser_geolocation_indicator.js (11043B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 requestLongerTimeout(2);
      7 
      8 const { PermissionUI } = ChromeUtils.importESModule(
      9  "resource:///modules/PermissionUI.sys.mjs"
     10 );
     11 
     12 const { PermissionTestUtils } = ChromeUtils.importESModule(
     13  "resource://testing-common/PermissionTestUtils.sys.mjs"
     14 );
     15 
     16 const CP = Cc["@mozilla.org/content-pref/service;1"].getService(
     17  Ci.nsIContentPrefService2
     18 );
     19 
     20 const EXAMPLE_PAGE_URL = "https://example.com";
     21 const EXAMPLE_PAGE_URI = Services.io.newURI(EXAMPLE_PAGE_URL);
     22 const EXAMPLE_PAGE_PRINCIPAL =
     23  Services.scriptSecurityManager.createContentPrincipal(EXAMPLE_PAGE_URI, {});
     24 const GEO_CONTENT_PREF_KEY = "permissions.geoLocation.lastAccess";
     25 const POLL_INTERVAL_FALSE_STATE = 50;
     26 
     27 async function testGeoSharingIconVisible(state = true) {
     28  let sharingIcon = document.getElementById("geo-sharing-icon");
     29  ok(sharingIcon, "Geo sharing icon exists");
     30 
     31  try {
     32    await TestUtils.waitForCondition(
     33      () => sharingIcon.hasAttribute("sharing") === true,
     34      "Waiting for geo sharing icon visibility state",
     35      // If we wait for sharing icon to *not* show, waitForCondition will always timeout on correct state.
     36      // In these cases we want to reduce the wait time from 5 seconds to 2.5 seconds to prevent test duration timeouts
     37      !state ? POLL_INTERVAL_FALSE_STATE : undefined
     38    );
     39  } catch (e) {
     40    ok(!state, "Geo sharing icon not showing");
     41    return;
     42  }
     43  ok(state, "Geo sharing icon showing");
     44 }
     45 
     46 async function checkForDOMElement(state, id) {
     47  info(`Testing state ${state} of element  ${id}`);
     48  let el;
     49  try {
     50    await TestUtils.waitForCondition(
     51      () => {
     52        el = document.getElementById(id);
     53        return el != null;
     54      },
     55      `Waiting for ${id}`,
     56      !state ? POLL_INTERVAL_FALSE_STATE : undefined
     57    );
     58  } catch (e) {
     59    ok(!state, `${id} has correct state`);
     60    return el;
     61  }
     62  ok(state, `${id} has correct state`);
     63 
     64  return el;
     65 }
     66 
     67 async function testPermissionPopupGeoContainer(
     68  containerVisible,
     69  timestampVisible
     70 ) {
     71  // The container holds the timestamp element, therefore we can't have a
     72  // visible timestamp without the container.
     73  if (timestampVisible && !containerVisible) {
     74    ok(false, "Can't have timestamp without container");
     75  }
     76 
     77  // Only call openPermissionPopup if popup is closed, otherwise it does not resolve
     78  if (!gPermissionPanel._identityPermissionBox.hasAttribute("open")) {
     79    await openPermissionPopup();
     80  }
     81 
     82  let checkContainer = checkForDOMElement(
     83    containerVisible,
     84    "permission-popup-geo-container"
     85  );
     86 
     87  if (containerVisible && timestampVisible) {
     88    // Wait for the geo container to be fully populated.
     89    // The time label is computed async.
     90    let container = await checkContainer;
     91    await TestUtils.waitForCondition(
     92      () => container.childElementCount == 2,
     93      "permission-popup-geo-container should have two elements."
     94    );
     95    is(
     96      container.childNodes[0].classList[0],
     97      "permission-popup-permission-item",
     98      "Geo container should have permission item."
     99    );
    100    is(
    101      container.childNodes[1].id,
    102      "geo-access-indicator-item",
    103      "Geo container should have indicator item."
    104    );
    105  }
    106  let checkAccessIndicator = checkForDOMElement(
    107    timestampVisible,
    108    "geo-access-indicator-item"
    109  );
    110 
    111  return Promise.all([checkContainer, checkAccessIndicator]);
    112 }
    113 
    114 function openExamplePage(tabbrowser = gBrowser) {
    115  return BrowserTestUtils.openNewForegroundTab(tabbrowser, EXAMPLE_PAGE_URL);
    116 }
    117 
    118 function requestGeoLocation(browser) {
    119  return SpecialPowers.spawn(browser, [], () => {
    120    return new Promise(resolve => {
    121      content.navigator.geolocation.getCurrentPosition(
    122        () => resolve(true),
    123        error => resolve(error.code !== 1) // PERMISSION_DENIED = 1
    124      );
    125    });
    126  });
    127 }
    128 
    129 function answerGeoLocationPopup(allow, remember = false) {
    130  let notification = PopupNotifications.getNotification("geolocation");
    131  ok(
    132    PopupNotifications.isPanelOpen && notification,
    133    "Geolocation notification is open"
    134  );
    135 
    136  let rememberCheck = PopupNotifications.panel.querySelector(
    137    ".popup-notification-checkbox"
    138  );
    139  rememberCheck.checked = remember;
    140 
    141  let popupHidden = BrowserTestUtils.waitForEvent(
    142    PopupNotifications.panel,
    143    "popuphidden"
    144  );
    145  if (allow) {
    146    let allowBtn = PopupNotifications.panel.querySelector(
    147      ".popup-notification-primary-button"
    148    );
    149    allowBtn.click();
    150  } else {
    151    let denyBtn = PopupNotifications.panel.querySelector(
    152      ".popup-notification-secondary-button"
    153    );
    154    denyBtn.click();
    155  }
    156  return popupHidden;
    157 }
    158 
    159 function setGeoLastAccess(browser, state) {
    160  return new Promise(resolve => {
    161    let host = browser.currentURI.host;
    162    let handler = {
    163      handleCompletion: () => resolve(),
    164    };
    165 
    166    if (!state) {
    167      CP.removeByDomainAndName(
    168        host,
    169        GEO_CONTENT_PREF_KEY,
    170        browser.loadContext,
    171        handler
    172      );
    173      return;
    174    }
    175    CP.set(
    176      host,
    177      GEO_CONTENT_PREF_KEY,
    178      new Date().toString(),
    179      browser.loadContext,
    180      handler
    181    );
    182  });
    183 }
    184 
    185 async function testGeoLocationLastAccessSet(browser) {
    186  let timestamp = await new Promise(resolve => {
    187    let lastAccess = null;
    188    CP.getByDomainAndName(
    189      gBrowser.currentURI.spec,
    190      GEO_CONTENT_PREF_KEY,
    191      browser.loadContext,
    192      {
    193        handleResult(pref) {
    194          lastAccess = pref.value;
    195        },
    196        handleCompletion() {
    197          resolve(lastAccess);
    198        },
    199      }
    200    );
    201  });
    202 
    203  Assert.notEqual(timestamp, null, "Geo last access timestamp set");
    204 
    205  let parseSuccess = true;
    206  try {
    207    timestamp = new Date(timestamp);
    208  } catch (e) {
    209    parseSuccess = false;
    210  }
    211  ok(
    212    parseSuccess && !isNaN(timestamp),
    213    "Geo last access timestamp is valid Date"
    214  );
    215 }
    216 
    217 async function cleanup(tab) {
    218  await setGeoLastAccess(tab.linkedBrowser, false);
    219  SitePermissions.removeFromPrincipal(
    220    tab.linkedBrowser.contentPrincipal,
    221    "geo",
    222    tab.linkedBrowser
    223  );
    224  gBrowser.resetBrowserSharing(tab.linkedBrowser);
    225  BrowserTestUtils.removeTab(tab);
    226 }
    227 
    228 async function testIndicatorGeoSharingState(active) {
    229  let tab = await openExamplePage();
    230  gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: active });
    231  await testGeoSharingIconVisible(active);
    232 
    233  await cleanup(tab);
    234 }
    235 
    236 async function testIndicatorExplicitAllow(persistent) {
    237  let tab = await openExamplePage();
    238 
    239  let popupShown = BrowserTestUtils.waitForEvent(
    240    PopupNotifications.panel,
    241    "popupshown"
    242  );
    243  info("Requesting geolocation");
    244  let request = requestGeoLocation(tab.linkedBrowser);
    245  await popupShown;
    246  info("Allowing geolocation via popup");
    247  answerGeoLocationPopup(true, persistent);
    248  await request;
    249 
    250  await Promise.all([
    251    testGeoSharingIconVisible(true),
    252    testPermissionPopupGeoContainer(true, true),
    253    testGeoLocationLastAccessSet(tab.linkedBrowser),
    254  ]);
    255 
    256  await cleanup(tab);
    257 }
    258 
    259 // Indicator and permission popup entry shown after explicit PermissionUI geolocation allow
    260 add_task(function test_indicator_and_timestamp_after_explicit_allow() {
    261  return testIndicatorExplicitAllow(false);
    262 });
    263 add_task(function test_indicator_and_timestamp_after_explicit_allow_remember() {
    264  return testIndicatorExplicitAllow(true);
    265 });
    266 
    267 // Indicator and permission popup entry shown after auto PermissionUI geolocation allow
    268 add_task(async function test_indicator_and_timestamp_after_implicit_allow() {
    269  PermissionTestUtils.add(
    270    EXAMPLE_PAGE_URI,
    271    "geo",
    272    Services.perms.ALLOW_ACTION,
    273    Services.perms.EXPIRE_NEVER
    274  );
    275  let tab = await openExamplePage();
    276  let result = await requestGeoLocation(tab.linkedBrowser);
    277  ok(result, "Request should be allowed");
    278 
    279  await Promise.all([
    280    testGeoSharingIconVisible(true),
    281    testPermissionPopupGeoContainer(true, true),
    282    testGeoLocationLastAccessSet(tab.linkedBrowser),
    283  ]);
    284 
    285  await cleanup(tab);
    286 });
    287 
    288 // Indicator shown when manually setting sharing state to true
    289 add_task(function test_indicator_sharing_state_active() {
    290  return testIndicatorGeoSharingState(true);
    291 });
    292 
    293 // Indicator not shown when manually setting sharing state to false
    294 add_task(function test_indicator_sharing_state_inactive() {
    295  return testIndicatorGeoSharingState(false);
    296 });
    297 
    298 // Permission popup shows permission if geo permission is set to persistent allow
    299 add_task(async function test_permission_popup_permission_scope_permanent() {
    300  PermissionTestUtils.add(
    301    EXAMPLE_PAGE_URI,
    302    "geo",
    303    Services.perms.ALLOW_ACTION,
    304    Services.perms.EXPIRE_NEVER
    305  );
    306  let tab = await openExamplePage();
    307 
    308  await testPermissionPopupGeoContainer(true, false); // Expect permission to be visible, but not lastAccess indicator
    309 
    310  await cleanup(tab);
    311 });
    312 
    313 // Sharing state set, but no permission
    314 add_task(async function test_permission_popup_permission_sharing_state() {
    315  let tab = await openExamplePage();
    316  gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true });
    317  await testPermissionPopupGeoContainer(true, false);
    318 
    319  await cleanup(tab);
    320 });
    321 
    322 // Permission popup has correct state if sharing state and last geo access timestamp are set
    323 add_task(
    324  async function test_permission_popup_permission_sharing_state_timestamp() {
    325    let tab = await openExamplePage();
    326    gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true });
    327    await setGeoLastAccess(tab.linkedBrowser, true);
    328 
    329    await testPermissionPopupGeoContainer(true, true);
    330 
    331    await cleanup(tab);
    332  }
    333 );
    334 
    335 // Clicking permission clear button clears permission and resets geo sharing state
    336 add_task(async function test_permission_popup_permission_clear() {
    337  PermissionTestUtils.add(
    338    EXAMPLE_PAGE_URI,
    339    "geo",
    340    Services.perms.ALLOW_ACTION,
    341    Services.perms.EXPIRE_NEVER
    342  );
    343  let tab = await openExamplePage();
    344  gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true });
    345 
    346  await openPermissionPopup();
    347 
    348  let clearButton = document.querySelector(
    349    "#permission-popup-geo-container button"
    350  );
    351  ok(clearButton, "Clear button is visible");
    352  clearButton.click();
    353 
    354  await Promise.all([
    355    testGeoSharingIconVisible(false),
    356    testPermissionPopupGeoContainer(false, false),
    357    TestUtils.waitForCondition(() => {
    358      let sharingState = tab._sharingState;
    359      return (
    360        sharingState == null ||
    361        sharingState.geo == null ||
    362        sharingState.geo === false
    363      );
    364    }, "Waiting for geo sharing state to reset"),
    365  ]);
    366  await cleanup(tab);
    367 });
    368 
    369 /**
    370 * Tests that we only show the last access label once when the sharing
    371 * state is updated multiple times while the popup is open.
    372 */
    373 add_task(async function test_permission_no_duplicate_last_access_label() {
    374  let tab = await openExamplePage();
    375  await setGeoLastAccess(tab.linkedBrowser, true);
    376  await openPermissionPopup();
    377  gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true });
    378  gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true });
    379  await testPermissionPopupGeoContainer(true, true);
    380  await cleanup(tab);
    381 });