tor-browser

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

browser_bug1058164.js (7534B)


      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 SpecialPowers.pushPrefEnv({
      8  set: [["security.allow_eval_with_system_principal", true]],
      9 });
     10 
     11 const PAGE =
     12  "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
     13 
     14 let gListenerId = 0;
     15 
     16 /**
     17 * Adds a content event listener on the given browser element. NOTE: this test
     18 * is checking the behavior of pageshow and pagehide as seen by frame scripts,
     19 * so it is specifically implemented using the message message manager.
     20 * Similar to BrowserTestUtils.waitForContentEvent, but the listener will fire
     21 * until it is removed. A callable object is returned that, when called, removes
     22 * the event listener. Note that this function works even if the browser's
     23 * frameloader is swapped.
     24 *
     25 * @param {xul:browser} browser
     26 *        The browser element to listen for events in.
     27 * @param {string} eventName
     28 *        Name of the event to listen to.
     29 * @param {function} listener
     30 *        Function to call in parent process when event fires.
     31 *        Not passed any arguments.
     32 * @param {function} checkFn [optional]
     33 *        Called with the Event object as argument, should return true if the
     34 *        event is the expected one, or false if it should be ignored and
     35 *        listening should continue. If not specified, the first event with
     36 *        the specified name resolves the returned promise. This is called
     37 *        within the content process and can have no closure environment.
     38 *
     39 * @returns function
     40 *        If called, the return value will remove the event listener.
     41 */
     42 function addContentEventListenerWithMessageManager(
     43  browser,
     44  eventName,
     45  listener,
     46  checkFn
     47 ) {
     48  let id = gListenerId++;
     49  let checkFnSource = checkFn
     50    ? encodeURIComponent(escape(checkFn.toSource()))
     51    : "";
     52 
     53  // To correctly handle frameloader swaps, we load a frame script
     54  // into all tabs but ignore messages from the ones not related to
     55  // |browser|.
     56 
     57  /* eslint-disable no-eval */
     58  function frameScript(innerId, innerEventName, innerCheckFnSource) {
     59    let innerCheckFn;
     60    if (innerCheckFnSource) {
     61      innerCheckFn = eval(`(() => (${unescape(innerCheckFnSource)}))()`);
     62    }
     63 
     64    function innerListener(event) {
     65      if (innerCheckFn && !innerCheckFn(event)) {
     66        return;
     67      }
     68      sendAsyncMessage("ContentEventListener:Run", innerId);
     69    }
     70    function removeListener(msg) {
     71      if (msg.data == innerId) {
     72        removeMessageListener("ContentEventListener:Remove", removeListener);
     73        removeEventListener(innerEventName, innerListener);
     74      }
     75    }
     76    addMessageListener("ContentEventListener:Remove", removeListener);
     77    addEventListener(innerEventName, innerListener);
     78  }
     79  /* eslint-enable no-eval */
     80 
     81  let frameScriptSource = `data:,(${frameScript.toString()})(${id}, "${eventName}",
     82     "${checkFnSource}")`;
     83 
     84  let mm = Services.mm;
     85 
     86  function runListener(msg) {
     87    if (msg.data == id && msg.target == browser) {
     88      listener();
     89    }
     90  }
     91  mm.addMessageListener("ContentEventListener:Run", runListener);
     92 
     93  let needCleanup = true;
     94 
     95  let unregisterFunction = function () {
     96    if (!needCleanup) {
     97      return;
     98    }
     99    needCleanup = false;
    100    mm.removeMessageListener("ContentEventListener:Run", runListener);
    101    mm.broadcastAsyncMessage("ContentEventListener:Remove", id);
    102    mm.removeDelayedFrameScript(frameScriptSource);
    103  };
    104 
    105  function cleanupObserver(subject, topic, data) {
    106    if (subject == browser.messageManager) {
    107      unregisterFunction();
    108    }
    109  }
    110 
    111  mm.loadFrameScript(frameScriptSource, true);
    112 
    113  return unregisterFunction;
    114 }
    115 
    116 /**
    117 * Returns a Promise that resolves when it sees a pageshow and
    118 * pagehide events in a particular order, where each event must
    119 * have the persisted property set to true. Will cause a test
    120 * failure to be logged if it sees an event out of order.
    121 *
    122 * @param browser (<xul:browser>)
    123 *        The browser to expect the events from.
    124 * @param expectedOrder (array)
    125 *        An array of strings describing what order the pageshow
    126 *        and pagehide events should be in.
    127 *        Example:
    128 *        ["pageshow", "pagehide", "pagehide", "pageshow"]
    129 * @returns Promise
    130 */
    131 function prepareForVisibilityEvents(browser, expectedOrder) {
    132  return new Promise(resolve => {
    133    let order = [];
    134 
    135    let rmvHide, rmvShow;
    136 
    137    let checkSatisfied = () => {
    138      if (order.length < expectedOrder.length) {
    139        // We're still waiting...
    140        return;
    141      }
    142      rmvHide();
    143      rmvShow();
    144 
    145      for (let i = 0; i < expectedOrder.length; ++i) {
    146        is(order[i], expectedOrder[i], "Got expected event");
    147      }
    148      resolve();
    149    };
    150 
    151    let eventListener = type => {
    152      order.push(type);
    153      checkSatisfied();
    154    };
    155 
    156    let checkFn = e => e.persisted;
    157 
    158    rmvHide = addContentEventListenerWithMessageManager(
    159      browser,
    160      "pagehide",
    161      () => eventListener("pagehide"),
    162      checkFn
    163    );
    164    rmvShow = addContentEventListenerWithMessageManager(
    165      browser,
    166      "pageshow",
    167      () => eventListener("pageshow"),
    168      checkFn
    169    );
    170  });
    171 }
    172 
    173 /**
    174 * Tests that frame scripts get pageshow / pagehide events when
    175 * swapping browser frameloaders (which occurs when moving a tab
    176 * into a different window).
    177 */
    178 add_task(async function test_swap_frameloader_pagevisibility_events() {
    179  // Disable window occlusion. Bug 1733955
    180  if (navigator.platform.indexOf("Win") == 0) {
    181    await SpecialPowers.pushPrefEnv({
    182      set: [["widget.windows.window_occlusion_tracking.enabled", false]],
    183    });
    184  }
    185 
    186  // Load a new tab that we'll tear out...
    187  let tab = BrowserTestUtils.addTab(gBrowser, PAGE);
    188  gBrowser.selectedTab = tab;
    189  let firstBrowser = tab.linkedBrowser;
    190  await BrowserTestUtils.browserLoaded(firstBrowser);
    191 
    192  // Swap the browser out to a new window
    193  let newWindow = gBrowser.replaceTabWithWindow(tab);
    194 
    195  // We have to wait for the window to load so we can get the selected browser
    196  // to listen to.
    197  await BrowserTestUtils.waitForEvent(newWindow, "DOMContentLoaded");
    198  let newWindowBrowser = newWindow.gBrowser.selectedBrowser;
    199 
    200  // Wait for the expected pagehide and pageshow events on the initial browser
    201  await prepareForVisibilityEvents(newWindowBrowser, ["pagehide", "pageshow"]);
    202 
    203  // Now let's send the browser back to the original window
    204 
    205  // First, create a new, empty browser tab to replace the window with
    206  let newTab = BrowserTestUtils.addTab(gBrowser);
    207  gBrowser.selectedTab = newTab;
    208  let emptyBrowser = newTab.linkedBrowser;
    209 
    210  // Wait for that initial doc to be visible because if its pageshow hasn't
    211  // happened we don't confuse it with the other expected events.
    212  await ContentTask.spawn(emptyBrowser, null, async () => {
    213    if (content.document.visibilityState === "hidden") {
    214      info("waiting for hidden emptyBrowser to be visible");
    215      await ContentTaskUtils.waitForEvent(
    216        content.document,
    217        "visibilitychange",
    218        {}
    219      );
    220    }
    221  });
    222 
    223  info("emptyBrowser is shown now.");
    224 
    225  // The empty tab we just added show now fire a pagehide as its replaced,
    226  // and a pageshow once the swap is finished.
    227  let emptyBrowserPromise = prepareForVisibilityEvents(emptyBrowser, [
    228    "pagehide",
    229    "pageshow",
    230  ]);
    231 
    232  gBrowser.swapBrowsersAndCloseOther(newTab, newWindow.gBrowser.selectedTab);
    233 
    234  await emptyBrowserPromise;
    235 
    236  gBrowser.removeTab(gBrowser.selectedTab);
    237 });