tor-browser

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

head.js (8872B)


      1 XPCOMUtils.defineLazyServiceGetter(
      2  this,
      3  "cps",
      4  "@mozilla.org/network/captive-portal-service;1",
      5  Ci.nsICaptivePortalService
      6 );
      7 
      8 const CANONICAL_CONTENT = "success";
      9 const CANONICAL_URL = "data:text/plain;charset=utf-8," + CANONICAL_CONTENT;
     10 const CANONICAL_URL_REDIRECTED = "data:text/plain;charset=utf-8,redirected";
     11 const PORTAL_NOTIFICATION_VALUE = "captive-portal-detected";
     12 const BAD_CERT_PAGE = "https://expired.example.com/";
     13 
     14 async function setupPrefsAndRecentWindowBehavior() {
     15  await SpecialPowers.pushPrefEnv({
     16    set: [
     17      ["captivedetect.canonicalURL", CANONICAL_URL],
     18      ["captivedetect.canonicalContent", CANONICAL_CONTENT],
     19    ],
     20  });
     21  // We need to test behavior when a portal is detected when there is no browser
     22  // window, but we can't close the default window opened by the test harness.
     23  // Instead, we deactivate CaptivePortalWatcher in the default window and
     24  // exclude it using an attribute to mask its presence.
     25  window.CaptivePortalWatcher.uninit();
     26  window.document.documentElement.setAttribute("ignorecaptiveportal", "true");
     27 
     28  registerCleanupFunction(function cleanUp() {
     29    window.CaptivePortalWatcher.init();
     30    window.document.documentElement.removeAttribute("ignorecaptiveportal");
     31  });
     32 }
     33 
     34 async function portalDetected() {
     35  Services.obs.notifyObservers(null, "captive-portal-login");
     36  await TestUtils.waitForCondition(() => {
     37    return cps.state == cps.LOCKED_PORTAL;
     38  }, "Waiting for Captive Portal Service to update state after portal detected.");
     39 }
     40 
     41 async function freePortal(aSuccess) {
     42  Services.obs.notifyObservers(
     43    null,
     44    "captive-portal-login-" + (aSuccess ? "success" : "abort")
     45  );
     46  await TestUtils.waitForCondition(() => {
     47    return cps.state != cps.LOCKED_PORTAL;
     48  }, "Waiting for Captive Portal Service to update state after portal freed.");
     49 }
     50 
     51 // If a window is provided, it will be focused. Otherwise, a new window
     52 // will be opened and focused.
     53 async function focusWindowAndWaitForPortalUI(aLongRecheck, win) {
     54  // CaptivePortalWatcher triggers a recheck when a window gains focus. If
     55  // the time taken for the check to complete is under PORTAL_RECHECK_DELAY_MS,
     56  // a tab with the login page is opened and selected. If it took longer,
     57  // no tab is opened. It's not reliable to time things in an async test,
     58  // so use a delay threshold of -1 to simulate a long recheck (so that any
     59  // amount of time is considered excessive), and a very large threshold to
     60  // simulate a short recheck.
     61  Services.prefs.setIntPref(
     62    "captivedetect.portalRecheckDelayMS",
     63    aLongRecheck ? -1 : 1000000
     64  );
     65 
     66  if (!win) {
     67    win = await BrowserTestUtils.openNewBrowserWindow();
     68  }
     69  let windowActivePromise = waitForBrowserWindowActive(win);
     70  win.focus();
     71  await windowActivePromise;
     72 
     73  // After a new window is opened, CaptivePortalWatcher asks for a recheck, and
     74  // waits for it to complete. We need to manually tell it a recheck completed.
     75  await TestUtils.waitForCondition(() => {
     76    return win.CaptivePortalWatcher._waitingForRecheck;
     77  }, "Waiting for CaptivePortalWatcher to trigger a recheck.");
     78  Services.obs.notifyObservers(null, "captive-portal-check-complete");
     79 
     80  let notification = await ensurePortalNotification(win);
     81 
     82  if (aLongRecheck) {
     83    ensureNoPortalTab(win);
     84    await testShowLoginPageButtonVisibility(notification, "visible");
     85    return win;
     86  }
     87 
     88  let tab = win.gBrowser.tabs[1];
     89  if (tab.linkedBrowser.currentURI.spec != CANONICAL_URL) {
     90    // The tab should load the canonical URL, wait for it.
     91    await BrowserTestUtils.waitForLocationChange(win.gBrowser, CANONICAL_URL);
     92  }
     93  is(
     94    win.gBrowser.selectedTab,
     95    tab,
     96    "The captive portal tab should be open and selected in the new window."
     97  );
     98  await testShowLoginPageButtonVisibility(notification, "hidden");
     99  return win;
    100 }
    101 
    102 function ensurePortalTab(win) {
    103  // For the tests that call this function, it's enough to ensure there
    104  // are two tabs in the window - the default tab and the portal tab.
    105  is(
    106    win.gBrowser.tabs.length,
    107    2,
    108    "There should be a captive portal tab in the window."
    109  );
    110 }
    111 
    112 async function ensurePortalNotification(win) {
    113  await BrowserTestUtils.waitForMutationCondition(
    114    win.gNavToolbox,
    115    { childList: true },
    116    () =>
    117      win.gNavToolbox
    118        .querySelector("notification-message")
    119        ?.getAttribute("value") == PORTAL_NOTIFICATION_VALUE
    120  );
    121 
    122  let notification = win.gNotificationBox.getNotificationWithValue(
    123    PORTAL_NOTIFICATION_VALUE
    124  );
    125  isnot(
    126    notification,
    127    null,
    128    "There should be a captive portal notification in the window."
    129  );
    130  return notification;
    131 }
    132 
    133 // Helper to test whether the "Show Login Page" is visible in the captive portal
    134 // notification (it should be hidden when the portal tab is selected).
    135 async function testShowLoginPageButtonVisibility(notification, visibility) {
    136  await notification.updateComplete;
    137  let showLoginPageButton = notification.buttonContainer.querySelector(
    138    "button.notification-button"
    139  );
    140  // If the visibility property was never changed from default, it will be
    141  // an empty string, so we pretend it's "visible" (effectively the same).
    142  is(
    143    showLoginPageButton.style.visibility || "visible",
    144    visibility,
    145    'The "Show Login Page" button should be ' + visibility + "."
    146  );
    147 }
    148 
    149 function ensureNoPortalTab(win) {
    150  is(
    151    win.gBrowser.tabs.length,
    152    1,
    153    "There should be no captive portal tab in the window."
    154  );
    155 }
    156 
    157 function ensureNoPortalNotification(win) {
    158  is(
    159    win.gNotificationBox.getNotificationWithValue(PORTAL_NOTIFICATION_VALUE),
    160    null,
    161    "There should be no captive portal notification in the window."
    162  );
    163 }
    164 
    165 /**
    166 * Some tests open a new window and close it later. When the window is closed,
    167 * the original window opened by mochitest gains focus, generating an
    168 * activate event. If the next test also opens a new window
    169 * before this event has a chance to fire, CaptivePortalWatcher picks
    170 * up the first one instead of the one from the new window. To avoid this
    171 * unfortunate intermittent timing issue, we wait for the event from
    172 * the original window every time we close a window that we opened.
    173 */
    174 function waitForBrowserWindowActive(win) {
    175  return new Promise(resolve => {
    176    if (Services.focus.activeWindow == win) {
    177      resolve();
    178    } else {
    179      win.addEventListener(
    180        "activate",
    181        () => {
    182          resolve();
    183        },
    184        { once: true }
    185      );
    186    }
    187  });
    188 }
    189 
    190 async function closeWindowAndWaitForWindowActivate(win) {
    191  let activationPromises = [];
    192  for (let w of BrowserWindowTracker.orderedWindows) {
    193    if (
    194      w != win &&
    195      !win.document.documentElement.getAttribute("ignorecaptiveportal")
    196    ) {
    197      activationPromises.push(waitForBrowserWindowActive(win));
    198    }
    199  }
    200  await BrowserTestUtils.closeWindow(win);
    201  await Promise.race(activationPromises);
    202 }
    203 
    204 /**
    205 * BrowserTestUtils.openNewBrowserWindow() does not guarantee the newly
    206 * opened window has received focus when the promise resolves, so we
    207 * have to manually wait every time.
    208 */
    209 async function openWindowAndWaitForFocus() {
    210  let win = await BrowserTestUtils.openNewBrowserWindow();
    211  await waitForBrowserWindowActive(win);
    212  return win;
    213 }
    214 
    215 async function openCaptivePortalErrorTab() {
    216  // Open a page with a cert error.
    217  let browser;
    218  let certErrorLoaded;
    219  let errorTab = await BrowserTestUtils.openNewForegroundTab(
    220    gBrowser,
    221    () => {
    222      let tab = BrowserTestUtils.addTab(gBrowser, BAD_CERT_PAGE);
    223      gBrowser.selectedTab = tab;
    224      browser = gBrowser.selectedBrowser;
    225      certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser);
    226      return tab;
    227    },
    228    false
    229  );
    230  await certErrorLoaded;
    231  info("A cert error page was opened");
    232  await SpecialPowers.spawn(errorTab.linkedBrowser, [], async () => {
    233    let doc = content.document;
    234    let loginButton = doc.getElementById("openPortalLoginPageButton");
    235    await ContentTaskUtils.waitForCondition(
    236      () => loginButton && doc.body.className == "captiveportal",
    237      "Captive portal error page UI is visible"
    238    );
    239  });
    240  info("Captive portal error page UI is visible");
    241 
    242  return errorTab;
    243 }
    244 
    245 async function openCaptivePortalLoginTab(
    246  errorTab,
    247  LOGIN_PAGE_URL = CANONICAL_URL
    248 ) {
    249  let portalTabPromise = BrowserTestUtils.waitForNewTab(
    250    gBrowser,
    251    LOGIN_PAGE_URL,
    252    true
    253  );
    254 
    255  await SpecialPowers.spawn(errorTab.linkedBrowser, [], async () => {
    256    let doc = content.document;
    257    let loginButton = doc.getElementById("openPortalLoginPageButton");
    258    info("Click on the login button on the captive portal error page");
    259    await EventUtils.synthesizeMouseAtCenter(loginButton, {}, content);
    260  });
    261 
    262  let portalTab = await portalTabPromise;
    263  is(
    264    gBrowser.selectedTab,
    265    portalTab,
    266    "Captive Portal login page is now open in a new foreground tab."
    267  );
    268 
    269  return portalTab;
    270 }