tor-browser

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

browser_PanelMultiView.js (15352B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 /**
      7 * Unit tests for the PanelMultiView module.
      8 */
      9 
     10 const PANELS_COUNT = 2;
     11 let gPanelAnchors = [];
     12 let gPanels = [];
     13 let gPanelMultiViews = [];
     14 
     15 const PANELVIEWS_COUNT = 4;
     16 let gPanelViews = [];
     17 let gPanelViewLabels = [];
     18 
     19 const EVENT_TYPES = [
     20  "popupshown",
     21  "popuphidden",
     22  "PanelMultiViewHidden",
     23  "ViewShowing",
     24  "ViewShown",
     25  "ViewHiding",
     26 ];
     27 
     28 /**
     29 * Checks that the element is displayed, including the state of the popup where
     30 * the element is located. This can trigger a synchronous reflow if necessary,
     31 * because even though the code under test is designed to avoid synchronous
     32 * reflows, it can raise completion events while a layout flush is still needed.
     33 *
     34 * In production code, event handlers for ViewShown have to wait for a flush if
     35 * they need to read style or layout information, like other code normally does.
     36 */
     37 function is_visible(element) {
     38  let win = element.ownerGlobal;
     39  let style = win.getComputedStyle(element);
     40  if (style.display == "none") {
     41    return false;
     42  }
     43  if (style.visibility != "visible") {
     44    return false;
     45  }
     46  if (win.XULPopupElement.isInstance(element) && element.state != "open") {
     47    return false;
     48  }
     49 
     50  // Hiding a parent element will hide all its children
     51  if (element.parentNode != element.ownerDocument) {
     52    return is_visible(element.parentNode);
     53  }
     54 
     55  return true;
     56 }
     57 
     58 /**
     59 * Checks whether the label in the specified view is visible.
     60 */
     61 function assertLabelVisible(viewIndex, expectedVisible) {
     62  Assert.equal(
     63    is_visible(gPanelViewLabels[viewIndex]),
     64    expectedVisible,
     65    `Visibility of label in view ${viewIndex}`
     66  );
     67 }
     68 
     69 /**
     70 * Opens the specified view as the main view in the specified panel.
     71 */
     72 async function openPopup(panelIndex, viewIndex) {
     73  gPanelMultiViews[panelIndex].setAttribute(
     74    "mainViewId",
     75    gPanelViews[viewIndex].id
     76  );
     77 
     78  let promiseShown = BrowserTestUtils.waitForEvent(
     79    gPanelViews[viewIndex],
     80    "ViewShown"
     81  );
     82  PanelMultiView.openPopup(
     83    gPanels[panelIndex],
     84    gPanelAnchors[panelIndex],
     85    "bottomright topright"
     86  );
     87  await promiseShown;
     88 
     89  Assert.ok(PanelView.forNode(gPanelViews[viewIndex]).active);
     90  assertLabelVisible(viewIndex, true);
     91 }
     92 
     93 /**
     94 * Closes the specified panel.
     95 */
     96 async function hidePopup(panelIndex) {
     97  gPanelMultiViews[panelIndex].setAttribute(
     98    "mainViewId",
     99    gPanelViews[panelIndex].id
    100  );
    101 
    102  let promiseHidden = BrowserTestUtils.waitForEvent(
    103    gPanels[panelIndex],
    104    "popuphidden"
    105  );
    106  PanelMultiView.hidePopup(gPanels[panelIndex]);
    107  await promiseHidden;
    108 }
    109 
    110 /**
    111 * Opens the specified subview in the specified panel.
    112 */
    113 async function showSubView(panelIndex, viewIndex) {
    114  let promiseShown = BrowserTestUtils.waitForEvent(
    115    gPanelViews[viewIndex],
    116    "ViewShown"
    117  );
    118  gPanelMultiViews[panelIndex].showSubView(gPanelViews[viewIndex]);
    119  await promiseShown;
    120 
    121  Assert.ok(PanelView.forNode(gPanelViews[viewIndex]).active);
    122  assertLabelVisible(viewIndex, true);
    123 }
    124 
    125 /**
    126 * Navigates backwards to the specified view, which is displayed as a result.
    127 */
    128 async function goBack(panelIndex, viewIndex) {
    129  let promiseShown = BrowserTestUtils.waitForEvent(
    130    gPanelViews[viewIndex],
    131    "ViewShown"
    132  );
    133  gPanelMultiViews[panelIndex].goBack();
    134  await promiseShown;
    135 
    136  Assert.ok(PanelView.forNode(gPanelViews[viewIndex]).active);
    137  assertLabelVisible(viewIndex, true);
    138 }
    139 
    140 /**
    141 * Records the specified events on an element into the specified array. An
    142 * optional callback can be used to respond to events and trigger nested events.
    143 */
    144 function recordEvents(
    145  element,
    146  eventTypes,
    147  recordArray,
    148  eventCallback = () => {}
    149 ) {
    150  let nestedEvents = [];
    151  element.recorders = eventTypes.map(eventType => {
    152    let recorder = {
    153      eventType,
    154      listener(event) {
    155        let eventString =
    156          nestedEvents.join("") + `${event.originalTarget.id}: ${event.type}`;
    157        info(`Event on ${eventString}`);
    158        recordArray.push(eventString);
    159        // Any synchronous event triggered from within the given callback will
    160        // include information about the current event.
    161        nestedEvents.unshift(`${eventString} > `);
    162        eventCallback(event);
    163        nestedEvents.shift();
    164      },
    165    };
    166    element.addEventListener(recorder.eventType, recorder.listener);
    167    return recorder;
    168  });
    169 }
    170 
    171 /**
    172 * Stops recording events on an element.
    173 */
    174 function stopRecordingEvents(element) {
    175  for (let recorder of element.recorders) {
    176    element.removeEventListener(recorder.eventType, recorder.listener);
    177  }
    178  delete element.recorders;
    179 }
    180 
    181 /**
    182 * Sets up the elements in the browser window that will be used by all the other
    183 * regression tests. Since the panel and view elements can live anywhere in the
    184 * document, they are simply added to the same toolbar as the panel anchors.
    185 *
    186 * <toolbar id="nav-bar">
    187 *     <toolbarbutton/>      ->  gPanelAnchors[panelIndex]
    188 *     <panel>               ->  gPanels[panelIndex]
    189 *       <panelmultiview/>   ->  gPanelMultiViews[panelIndex]
    190 *     </panel>
    191 *     <panelview>           ->  gPanelViews[viewIndex]
    192 *       <label/>            ->  gPanelViewLabels[viewIndex]
    193 *     </panelview>
    194 * </toolbar>
    195 */
    196 add_task(async function test_setup() {
    197  let navBar = document.getElementById("nav-bar");
    198 
    199  for (let i = 0; i < PANELS_COUNT; i++) {
    200    gPanelAnchors[i] = document.createXULElement("toolbarbutton");
    201    gPanelAnchors[i].classList.add(
    202      "toolbarbutton-1",
    203      "chromeclass-toolbar-additional"
    204    );
    205    navBar.appendChild(gPanelAnchors[i]);
    206 
    207    gPanels[i] = document.createXULElement("panel");
    208    gPanels[i].id = "panel-" + i;
    209    gPanels[i].setAttribute("type", "arrow");
    210    gPanels[i].setAttribute("photon", true);
    211    navBar.appendChild(gPanels[i]);
    212 
    213    gPanelMultiViews[i] = document.createXULElement("panelmultiview");
    214    gPanelMultiViews[i].id = "panelmultiview-" + i;
    215    gPanels[i].appendChild(gPanelMultiViews[i]);
    216  }
    217 
    218  for (let i = 0; i < PANELVIEWS_COUNT; i++) {
    219    gPanelViews[i] = document.createXULElement("panelview");
    220    gPanelViews[i].id = "panelview-" + i;
    221    navBar.appendChild(gPanelViews[i]);
    222 
    223    gPanelViewLabels[i] = document.createXULElement("label");
    224    gPanelViewLabels[i].setAttribute("value", "PanelView " + i);
    225    gPanelViews[i].appendChild(gPanelViewLabels[i]);
    226  }
    227 
    228  registerCleanupFunction(() => {
    229    [...gPanelAnchors, ...gPanels, ...gPanelViews].forEach(e => e.remove());
    230  });
    231 });
    232 
    233 /**
    234 * Shows and hides all views in a panel with this static structure:
    235 *
    236 * - Panel 0
    237 *   - View 0
    238 *     - View 1
    239 *     - View 3
    240 *       - View 2
    241 */
    242 add_task(async function test_simple() {
    243  // Show main view 0.
    244  await openPopup(0, 0);
    245 
    246  // Show and hide subview 1.
    247  await showSubView(0, 1);
    248  assertLabelVisible(0, false);
    249  await goBack(0, 0);
    250  assertLabelVisible(1, false);
    251 
    252  // Show subview 3.
    253  await showSubView(0, 3);
    254  assertLabelVisible(0, false);
    255 
    256  // Show and hide subview 2.
    257  await showSubView(0, 2);
    258  assertLabelVisible(3, false);
    259  await goBack(0, 3);
    260  assertLabelVisible(2, false);
    261 
    262  // Hide subview 3.
    263  await goBack(0, 0);
    264  assertLabelVisible(3, false);
    265 
    266  // Hide main view 0.
    267  await hidePopup(0);
    268  assertLabelVisible(0, false);
    269 });
    270 
    271 /**
    272 * Tests the event sequence in a panel with this static structure:
    273 *
    274 * - Panel 0
    275 *   - View 0
    276 *     - View 1
    277 *     - View 3
    278 *       - View 2
    279 */
    280 add_task(async function test_simple_event_sequence() {
    281  let recordArray = [];
    282  recordEvents(gPanels[0], EVENT_TYPES, recordArray);
    283 
    284  await openPopup(0, 0);
    285  await showSubView(0, 1);
    286  await goBack(0, 0);
    287  await showSubView(0, 3);
    288  await showSubView(0, 2);
    289  await goBack(0, 3);
    290  await goBack(0, 0);
    291  await hidePopup(0);
    292 
    293  stopRecordingEvents(gPanels[0]);
    294 
    295  Assert.deepEqual(recordArray, [
    296    "panelview-0: ViewShowing",
    297    "panelview-0: ViewShown",
    298    "panel-0: popupshown",
    299    "panelview-1: ViewShowing",
    300    "panelview-1: ViewShown",
    301    "panelview-1: ViewHiding",
    302    "panelview-0: ViewShown",
    303    "panelview-3: ViewShowing",
    304    "panelview-3: ViewShown",
    305    "panelview-2: ViewShowing",
    306    "panelview-2: ViewShown",
    307    "panelview-2: ViewHiding",
    308    "panelview-3: ViewShown",
    309    "panelview-3: ViewHiding",
    310    "panelview-0: ViewShown",
    311    "panelview-0: ViewHiding",
    312    "panelmultiview-0: PanelMultiViewHidden",
    313    "panel-0: popuphidden",
    314  ]);
    315 });
    316 
    317 /**
    318 * Tests that further navigation is suppressed until the new view is shown.
    319 */
    320 add_task(async function test_navigation_suppression() {
    321  await openPopup(0, 0);
    322 
    323  // Test re-entering the "showSubView" method.
    324  let promiseShown = BrowserTestUtils.waitForEvent(gPanelViews[1], "ViewShown");
    325  gPanelMultiViews[0].showSubView(gPanelViews[1]);
    326  Assert.ok(
    327    !PanelView.forNode(gPanelViews[0]).active,
    328    "The previous view should become inactive synchronously."
    329  );
    330 
    331  // The following call will have no effect.
    332  gPanelMultiViews[0].showSubView(gPanelViews[2]);
    333  await promiseShown;
    334 
    335  // Test re-entering the "goBack" method.
    336  promiseShown = BrowserTestUtils.waitForEvent(gPanelViews[0], "ViewShown");
    337  gPanelMultiViews[0].goBack();
    338  Assert.ok(
    339    !PanelView.forNode(gPanelViews[1]).active,
    340    "The previous view should become inactive synchronously."
    341  );
    342 
    343  // The following call will have no effect.
    344  gPanelMultiViews[0].goBack();
    345  await promiseShown;
    346 
    347  // Main view 0 should be displayed.
    348  assertLabelVisible(0, true);
    349 
    350  await hidePopup(0);
    351 });
    352 
    353 /**
    354 * Tests reusing views that are already open in another panel. In this test, the
    355 * structure of the first panel will change dynamically:
    356 *
    357 * - Panel 0
    358 *   - View 0
    359 *     - View 1
    360 * - Panel 1
    361 *   - View 1
    362 *     - View 2
    363 * - Panel 0
    364 *   - View 1
    365 *     - View 0
    366 */
    367 add_task(async function test_switch_event_sequence() {
    368  let recordArray = [];
    369  recordEvents(gPanels[0], EVENT_TYPES, recordArray);
    370  recordEvents(gPanels[1], EVENT_TYPES, recordArray);
    371 
    372  // Show panel 0.
    373  await openPopup(0, 0);
    374  await showSubView(0, 1);
    375 
    376  // Show panel 1 with the view that is already open and visible in panel 0.
    377  // This will close panel 0 automatically.
    378  await openPopup(1, 1);
    379  await showSubView(1, 2);
    380 
    381  // Show panel 0 with a view that is already open but invisible in panel 1.
    382  // This will close panel 1 automatically.
    383  await openPopup(0, 1);
    384  await showSubView(0, 0);
    385 
    386  // Hide panel 0.
    387  await hidePopup(0);
    388 
    389  stopRecordingEvents(gPanels[0]);
    390  stopRecordingEvents(gPanels[1]);
    391 
    392  Assert.deepEqual(recordArray, [
    393    "panelview-0: ViewShowing",
    394    "panelview-0: ViewShown",
    395    "panel-0: popupshown",
    396    "panelview-1: ViewShowing",
    397    "panelview-1: ViewShown",
    398    "panelview-1: ViewHiding",
    399    "panelview-0: ViewHiding",
    400    "panelmultiview-0: PanelMultiViewHidden",
    401    "panel-0: popuphidden",
    402    "panelview-1: ViewShowing",
    403    "panel-1: popupshown",
    404    "panelview-1: ViewShown",
    405    "panelview-2: ViewShowing",
    406    "panelview-2: ViewShown",
    407    "panel-1: popuphidden",
    408    "panelview-2: ViewHiding",
    409    "panelview-1: ViewHiding",
    410    "panelmultiview-1: PanelMultiViewHidden",
    411    "panelview-1: ViewShowing",
    412    "panelview-1: ViewShown",
    413    "panel-0: popupshown",
    414    "panelview-0: ViewShowing",
    415    "panelview-0: ViewShown",
    416    "panelview-0: ViewHiding",
    417    "panelview-1: ViewHiding",
    418    "panelmultiview-0: PanelMultiViewHidden",
    419    "panel-0: popuphidden",
    420  ]);
    421 });
    422 
    423 /**
    424 * Tests the event sequence when opening the main view is canceled.
    425 */
    426 add_task(async function test_cancel_mainview_event_sequence() {
    427  let recordArray = [];
    428  recordEvents(gPanels[0], EVENT_TYPES, recordArray, event => {
    429    if (event.type == "ViewShowing") {
    430      event.preventDefault();
    431    }
    432  });
    433 
    434  gPanelMultiViews[0].setAttribute("mainViewId", gPanelViews[0].id);
    435 
    436  let promiseHidden = BrowserTestUtils.waitForEvent(gPanels[0], "popuphidden");
    437  PanelMultiView.openPopup(
    438    gPanels[0],
    439    gPanelAnchors[0],
    440    "bottomright topright"
    441  );
    442  await promiseHidden;
    443 
    444  stopRecordingEvents(gPanels[0]);
    445 
    446  Assert.deepEqual(recordArray, [
    447    "panelview-0: ViewShowing",
    448    "panelview-0: ViewHiding",
    449    "panelmultiview-0: PanelMultiViewHidden",
    450    "panelmultiview-0: popuphidden",
    451  ]);
    452 });
    453 
    454 /**
    455 * Tests the event sequence when opening a subview is canceled.
    456 */
    457 add_task(async function test_cancel_subview_event_sequence() {
    458  let recordArray = [];
    459  recordEvents(gPanels[0], EVENT_TYPES, recordArray, event => {
    460    if (
    461      event.type == "ViewShowing" &&
    462      event.originalTarget.id == gPanelViews[1].id
    463    ) {
    464      event.preventDefault();
    465    }
    466  });
    467 
    468  await openPopup(0, 0);
    469 
    470  let promiseHiding = BrowserTestUtils.waitForEvent(
    471    gPanelViews[1],
    472    "ViewHiding"
    473  );
    474  gPanelMultiViews[0].showSubView(gPanelViews[1]);
    475  await promiseHiding;
    476 
    477  // Only the subview should have received the hidden event at this point.
    478  Assert.deepEqual(recordArray, [
    479    "panelview-0: ViewShowing",
    480    "panelview-0: ViewShown",
    481    "panel-0: popupshown",
    482    "panelview-1: ViewShowing",
    483    "panelview-1: ViewHiding",
    484  ]);
    485  recordArray.length = 0;
    486 
    487  await hidePopup(0);
    488 
    489  stopRecordingEvents(gPanels[0]);
    490 
    491  Assert.deepEqual(recordArray, [
    492    "panelview-0: ViewHiding",
    493    "panelmultiview-0: PanelMultiViewHidden",
    494    "panel-0: popuphidden",
    495  ]);
    496 });
    497 
    498 /**
    499 * Tests the event sequence when closing the panel while opening the main view.
    500 */
    501 add_task(async function test_close_while_showing_mainview_event_sequence() {
    502  let recordArray = [];
    503  recordEvents(gPanels[0], EVENT_TYPES, recordArray, event => {
    504    if (event.type == "ViewShowing") {
    505      PanelMultiView.hidePopup(gPanels[0]);
    506    }
    507  });
    508 
    509  gPanelMultiViews[0].setAttribute("mainViewId", gPanelViews[0].id);
    510 
    511  let promiseHidden = BrowserTestUtils.waitForEvent(gPanels[0], "popuphidden");
    512  let promiseHiding = BrowserTestUtils.waitForEvent(
    513    gPanelViews[0],
    514    "ViewHiding"
    515  );
    516  PanelMultiView.openPopup(
    517    gPanels[0],
    518    gPanelAnchors[0],
    519    "bottomright topright"
    520  );
    521  await promiseHiding;
    522  await promiseHidden;
    523 
    524  stopRecordingEvents(gPanels[0]);
    525 
    526  Assert.deepEqual(recordArray, [
    527    "panelview-0: ViewShowing",
    528    "panelview-0: ViewShowing > panelview-0: ViewHiding",
    529    "panelview-0: ViewShowing > panelmultiview-0: PanelMultiViewHidden",
    530    "panelview-0: ViewShowing > panelmultiview-0: popuphidden",
    531  ]);
    532 });
    533 
    534 /**
    535 * Tests the event sequence when closing the panel while opening a subview.
    536 */
    537 add_task(async function test_close_while_showing_subview_event_sequence() {
    538  let recordArray = [];
    539  recordEvents(gPanels[0], EVENT_TYPES, recordArray, event => {
    540    if (
    541      event.type == "ViewShowing" &&
    542      event.originalTarget.id == gPanelViews[1].id
    543    ) {
    544      PanelMultiView.hidePopup(gPanels[0]);
    545    }
    546  });
    547 
    548  await openPopup(0, 0);
    549 
    550  let promiseHidden = BrowserTestUtils.waitForEvent(gPanels[0], "popuphidden");
    551  gPanelMultiViews[0].showSubView(gPanelViews[1]);
    552  await promiseHidden;
    553 
    554  stopRecordingEvents(gPanels[0]);
    555 
    556  Assert.deepEqual(recordArray, [
    557    "panelview-0: ViewShowing",
    558    "panelview-0: ViewShown",
    559    "panel-0: popupshown",
    560    "panelview-1: ViewShowing",
    561    "panelview-1: ViewShowing > panelview-1: ViewHiding",
    562    "panelview-1: ViewShowing > panelview-0: ViewHiding",
    563    "panelview-1: ViewShowing > panelmultiview-0: PanelMultiViewHidden",
    564    "panelview-1: ViewShowing > panel-0: popuphidden",
    565  ]);
    566 });