tor-browser

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

pointerevent_click_during_parent_capture.html (9939B)


      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4 <meta charset="utf-8">
      5 <meta name="variant" content="?pointerType=mouse&preventDefault=">
      6 <meta name="variant" content="?pointerType=mouse&preventDefault=pointerdown">
      7 <meta name="variant" content="?pointerType=touch&preventDefault=">
      8 <meta name="variant" content="?pointerType=touch&preventDefault=pointerdown">
      9 <meta name="variant" content="?pointerType=touch&preventDefault=touchstart">
     10 <title>Test `click` event target when a parent element captures the pointer</title>
     11 <style>
     12  #parent {
     13    background: green;
     14    border: 1px solid black;
     15    width: 40px;
     16    height: 40px;
     17  }
     18 
     19  #target {
     20    background: blue;
     21    border: 1px solid black;
     22    width: 20px;
     23    height: 20px;
     24    margin: 10px;
     25  }
     26 </style>
     27 <script src="/resources/testharness.js"></script>
     28 <script src="/resources/testharnessreport.js"></script>
     29 <script src="/resources/testdriver.js"></script>
     30 <script src="/resources/testdriver-vendor.js"></script>
     31 <script src="/resources/testdriver-actions.js"></script>
     32 <script>
     33 "use strict";
     34 
     35 const searchParams = new URLSearchParams(document.location.search);
     36 const pointerType = searchParams.get("pointerType");
     37 const preventDefaultType = searchParams.get("preventDefault");
     38 
     39 addEventListener(
     40  "load",
     41  () => {
     42    const iframe = document.querySelector("iframe");
     43    iframe.contentDocument.head.innerHTML = `<style>${
     44      document.querySelector("style").textContent
     45    }</style>`;
     46 
     47    async function runTest(t, win, doc) {
     48      let pointerId;
     49      const parent = doc.getElementById("parent");
     50      const target = doc.getElementById("target");
     51      const body = doc.body;
     52      const html = doc.documentElement;
     53      let eventTypes = [];
     54      let composedPaths = [];
     55      function stringifyIfElement(eventTarget) {
     56        if (!(eventTarget instanceof win.Node)) {
     57          return eventTarget;
     58        }
     59        switch (eventTarget.nodeType) {
     60          case win.Node.ELEMENT_NODE:
     61            return `<${eventTarget.localName}${
     62              eventTarget.id ? ` id="${eventTarget.id}"` : ""
     63            }>`;
     64          default:
     65            return eventTarget;
     66        }
     67      }
     68      function stringifyElements(eventTargets) {
     69        return eventTargets.map(stringifyIfElement);
     70      }
     71      function captureEvent(e) {
     72        eventTypes.push(e.type);
     73        composedPaths.push(e.composedPath());
     74      }
     75      const expectedEvents = (() => {
     76        const pathToTarget = [target, parent, body, html, doc, win];
     77        const pathToParent = [parent, body, html, doc, win];
     78        function getExpectedEventsForMouse() {
     79          switch (preventDefaultType) {
     80            case "pointerdown":
     81              return {
     82                types: ["pointerdown", "pointerup", "click"],
     83                composedPaths: [
     84                  pathToTarget, // pointerdown
     85                  pathToParent, // pointerup
     86                  // Captured by the parent element, `click` should be fired on
     87                  // it.
     88                  pathToParent, // click
     89                ],
     90              };
     91            default:
     92              return {
     93                types: [
     94                  "pointerdown",
     95                  "mousedown",
     96                  "pointerup",
     97                  "mouseup",
     98                  "click",
     99                ],
    100                composedPaths: [
    101                  pathToTarget, // pointerdown
    102                  // `mousedown` target should be considered without the
    103                  // capturing element.
    104                  pathToTarget, // mousedown
    105                  pathToParent, // pointerup
    106                  // However, `mouseup` target should be considered with the
    107                  // capturing element.
    108                  pathToParent, // mouseup
    109                  // Captured by the parent element, `click` should be fired on
    110                  // it.
    111                  pathToParent, // click
    112                ],
    113              };
    114          }
    115        }
    116        function getExpectedEventsForTouch() {
    117          switch (preventDefaultType) {
    118            case "pointerdown":
    119              return {
    120                types: [
    121                  "pointerdown",
    122                  "touchstart",
    123                  "pointerup",
    124                  "touchend",
    125                  "click",
    126                ],
    127                composedPaths: [
    128                  pathToTarget, // pointerdown
    129                  // `touchstart` target should be considered without the
    130                  // capturing element.
    131                  pathToTarget, // touchstart
    132                  pathToParent, // pointerup
    133                  // Different from `mouseup`, `touchend` should always be fired
    134                  // on same target as `touchstart`.
    135                  pathToTarget, // touchend
    136                  // `click` event is NOT a compatibility mouse event of Touch
    137                  // Events because canceling `pointerdown` should cancel them.
    138                  // So, the event target should be considered with `userEvent`
    139                  // which caused this `click` event.  In this case, it's the
    140                  // preceding `pointerup`.  Therefore, this should be
    141                  // considered with the capturing element.
    142                  pathToParent, // click
    143                ],
    144              };
    145            case "touchstart":
    146              return {
    147                types: ["pointerdown", "touchstart", "pointerup", "touchend"],
    148                composedPaths: [
    149                  pathToTarget, // pointerdown
    150                  // `touchstart` target should be considered without the
    151                  // capturing element.
    152                  pathToTarget, // touchstart
    153                  pathToParent, // pointerup
    154                  // Different from `mouseup`, `touchend` should always be fired
    155                  // on same target as `touchstart`.
    156                  pathToTarget, // touchend
    157                  // `click` shouldn't be fired if `touchstart` is canceled
    158                  // especially for the backward compatibility.
    159                ],
    160              };
    161            default:
    162              return {
    163                types: [
    164                  "pointerdown",
    165                  "touchstart",
    166                  "pointerup",
    167                  "touchend",
    168                  "mousedown",
    169                  "mouseup",
    170                  "click",
    171                ],
    172                composedPaths: [
    173                  pathToTarget, // pointerdown
    174                  // `touchstart` target should be considered without the
    175                  // capturing element.
    176                  pathToTarget, // touchstart
    177                  pathToParent, // touchup
    178                  // Different from `mouseup`, `touchend` should always be fired
    179                  // on same target as `touchstart`.
    180                  pathToTarget, // touchend
    181                  // Compatibility mouse events should be fired on the element
    182                  // at the touch point.
    183                  pathToTarget, // mousedown
    184                  pathToTarget, // mouseup
    185                  // `click` should NOT be a compatibility mouse event of the
    186                  // Touch Events since touchstart was not consumed.  So,
    187                  // captured by the parent element, `click` should be fired on
    188                  // it.
    189                  pathToParent, //click
    190                ],
    191              };
    192          }
    193        }
    194        return pointerType == "mouse"
    195          ? getExpectedEventsForMouse()
    196          : getExpectedEventsForTouch();
    197      })();
    198 
    199      win.addEventListener(
    200        "pointerdown",
    201        e => {
    202          captureEvent(e);
    203          pointerId = e.pointerId;
    204          parent.setPointerCapture(pointerId);
    205          if (preventDefaultType == e.type) {
    206            e.preventDefault();
    207          }
    208        },
    209        { once: true, passive: false }
    210      );
    211      win.addEventListener(
    212        "pointerup",
    213        e => {
    214          captureEvent(e);
    215          parent.releasePointerCapture(pointerId);
    216        },
    217        { once: true }
    218      );
    219      win.addEventListener(
    220        "touchstart",
    221        e => {
    222          captureEvent(e);
    223          if (preventDefaultType == e.type) {
    224            e.preventDefault();
    225          }
    226        },
    227        { once: true, passive: false }
    228      );
    229      win.addEventListener(
    230        "touchend",
    231        captureEvent,
    232        { once: true }
    233      );
    234      win.addEventListener("mousedown", captureEvent, { once: true });
    235      win.addEventListener("mouseup", captureEvent, { once: true });
    236      win.addEventListener("click", captureEvent, { once: true });
    237 
    238      await new test_driver.Actions()
    239        .addPointer("TestPointer", pointerType)
    240        .pointerMove(0, 0, { origin: target })
    241        .pointerDown()
    242        .pointerUp()
    243        .pause(100) // XXX Required for preventing intermittent failure of Firefox
    244        .send();
    245 
    246      test(() => {
    247        assert_array_equals(eventTypes, expectedEvents.types);
    248      }, `${t.name}: all expected events should be fired`);
    249      for (let i = 0; i < eventTypes.length; i++) {
    250        const eventType = eventTypes[i];
    251        const expectedEventIndex = expectedEvents.types.indexOf(eventType);
    252        if (expectedEventIndex < 0) {
    253          continue;
    254        }
    255        test(() => {
    256          assert_array_equals(
    257            stringifyElements(composedPaths[i]),
    258            stringifyElements(expectedEvents.composedPaths[expectedEventIndex])
    259          );
    260        }, `${t.name}: "${eventType}" event should be fired on expected target`);
    261      }
    262    }
    263 
    264    promise_test(async t => {
    265      await runTest(t, window, document);
    266    }, "Test in the topmost document");
    267    promise_test(async t => {
    268      await runTest(t, iframe.contentWindow, iframe.contentDocument);
    269    }, "Test in the iframe");
    270  },
    271  { once: true }
    272 );
    273 </script>
    274 </head>
    275 <body>
    276  <div id="parent">
    277    <div id="target"></div>
    278  </div>
    279  <iframe srcdoc="<div id='parent'><div id='target'></div></div>"></iframe>
    280 </body>
    281 </html>