tor-browser

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

synthetic-mouse-enter-leave-over-out-button-state-after-target-removed.tentative.html (8615B)


      1 <!doctype html>
      2 <html>
      3 <head>
      4 <meta charset="utf-8">
      5 <meta name="variant" content="?buttonType=LEFT&button=0&buttons=1">
      6 <meta name="variant" content="?buttonType=MIDDLE&button=1&buttons=4">
      7 <title>Testing button state of synthesized mouse(out|over|leave|enter) events</title>
      8 <script src="/resources/testharness.js"></script>
      9 <script src="/resources/testharnessreport.js"></script>
     10 <script src="/resources/testdriver.js"></script>
     11 <script src="/resources/testdriver-vendor.js"></script>
     12 <script src="/resources/testdriver-actions.js"></script>
     13 <style>
     14 #parent {
     15  background-color: lightseagreen;
     16  padding: 0;
     17  height: 40px;
     18  width: 40px;
     19 }
     20 #child {
     21  background-color: red;
     22  margin: 0;
     23  height: 30px;
     24  width: 30px;
     25 }
     26 </style>
     27 </head>
     28 <body>
     29 <div id="parent"><div id="child">abc</div></div>
     30 <script>
     31 const searchParams = new URLSearchParams(document.location.search);
     32 const buttonType = searchParams.get("buttonType");
     33 const button = parseInt(searchParams.get("button"));
     34 const buttons = parseInt(searchParams.get("buttons"));
     35 
     36 let events = [];
     37 function eventToString(data) {
     38  if (!data) {
     39    return "{}";
     40  }
     41  return `{ '${data.type}' on '${data.target}': button=${data.button}, buttons=${data.buttons} }`;
     42 }
     43 
     44 function eventsToString(events) {
     45  if (!events.length) {
     46    return "[]";
     47  }
     48  let ret = "[";
     49  for (const data of events) {
     50    if (ret != "[") {
     51      ret += ", ";
     52    }
     53    ret += eventToString(data);
     54  }
     55  return ret + "]";
     56 }
     57 
     58 function removeEventsBefore(eventType) {
     59  while (events[0]?.type != eventType) {
     60    events.shift();
     61  }
     62 }
     63 
     64 const parentElement = document.getElementById("parent");
     65 const childElement = document.getElementById("child");
     66 
     67 function promiseLayout() {
     68  return new Promise(resolve => {
     69    (childElement.isConnected ? childElement : parentElement).getBoundingClientRect();
     70    requestAnimationFrame(() => requestAnimationFrame(resolve));
     71  });
     72 }
     73 
     74 promise_test(async () => {
     75  await new Promise(resolve => {
     76    addEventListener("load", resolve, { once: true });
     77  });
     78 
     79  ["mouseout", "mouseover", "mouseleave", "mouseenter", "mousemove", "mousedown"].forEach(eventType => {
     80    parentElement.addEventListener(eventType, event => {
     81      if (event.target != parentElement) {
     82        return;
     83      }
     84      events.push({
     85        type: event.type,
     86        target: "parent",
     87        button: event.button,
     88        buttons: event.buttons,
     89      });
     90    });
     91    childElement.addEventListener(eventType, event => {
     92      if (event.target != childElement) {
     93        return;
     94      }
     95      events.push({
     96        type: event.type,
     97        target: "child",
     98        button: event.button,
     99        buttons: event.buttons,
    100      });
    101    });
    102  });
    103 }, "Setup event listeners and wait for load");
    104 
    105 promise_test(async t => {
    106  events = [];
    107  await promiseLayout();
    108  childElement.addEventListener("mousedown", () => childElement.remove(), {once: true});
    109  const {x, y} = (function () {
    110    const rect = childElement.getBoundingClientRect();
    111    return { x: rect.left, y: rect.top };
    112  })();
    113  const actions = new test_driver.Actions();
    114  await actions.pointerMove(10, 10, {origin: childElement})
    115               .pointerDown({button: actions.ButtonType[buttonType]})
    116               .pause(100) // Allow browsers to synthesize mouseout, etc
    117               .pointerUp({button: actions.ButtonType[buttonType]})
    118               .send();
    119  await promiseLayout();
    120  removeEventsBefore("mousedown");
    121  test(() => {
    122    const maybeMouseDownEvent =
    123      events.length && events[0].type == "mousedown" ? events.shift() : undefined;
    124    assert_equals(
    125      eventToString(maybeMouseDownEvent),
    126      eventToString({ type: "mousedown",  target: "child", button, buttons })
    127    );
    128  }, `${t.name}: mousedown should've been fired`);
    129  assert_true(events.length > 0, `${t.name}: Some events should've been fired after mousedown`);
    130  test(() => {
    131    // Before `mousedown` is fired, both parent and child must have received
    132    // `mouseenter`, only the child must have received `mouseover`.  Then, the
    133    // child is now moved away by the `mousedown` listener.  Therefore,
    134    // `mouseout` and `mouseleave` should be fired on the child as the spec of
    135    // UI Events defines. Then, they are not a button press events.  Therefore,
    136    // the `button` should be 0, but buttons should be set to 4 because of
    137    // pressing the middle button.
    138    let mouseOutOrLeave = [];
    139    while (events[0]?.type == "mouseout" || events[0]?.type == "mouseleave") {
    140      mouseOutOrLeave.push(events.shift());
    141    }
    142    assert_equals(
    143      eventsToString(mouseOutOrLeave),
    144      eventsToString([
    145        { type: "mouseout",   target: "child", button: 0, buttons },
    146        { type: "mouseleave", target: "child", button: 0, buttons },
    147      ])
    148    );
    149  }, `${t.name}: mouseout and mouseleave should've been fired on the removed child`);
    150  test(() => {
    151    // And `mouseover` should be fired on the parent as the spec of UI Events
    152    // defines.
    153    let mouseOver = [];
    154    while (events[0]?.type == "mouseover") {
    155      mouseOver.push(events.shift());
    156    }
    157    assert_equals(
    158      eventsToString(mouseOver),
    159      eventsToString([{ type: "mouseover",  target: "parent", button: 0, buttons }])
    160    );
    161  }, `${t.name}: mouseover should've been fired on the parent`);
    162  test(() => {
    163    // On the other hand, it's unclear about `mouseenter`.  The mouse cursor has
    164    // never been moved out from the parent.  Therefore, it shouldn't be fired
    165    // on the parent ideally, but all browsers do not pass this test and there
    166    // is no clear definition about this case.
    167    let mouseEnter = [];
    168    while (events.length && events[0].type == "mouseenter") {
    169      mouseEnter.push(events.shift());
    170    }
    171    assert_equals(eventsToString(mouseEnter), eventsToString([]));
    172  }, `${t.name}: mouseenter should not have been fired on the parent`);
    173  assert_equals(eventsToString(events), eventsToString([]), "All events should've been checked");
    174  parentElement.appendChild(childElement);
    175 }, "Removing an element at mousedown");
    176 
    177 promise_test(async t => {
    178  events = [];
    179  await promiseLayout();
    180  childElement.addEventListener("mouseup", () => childElement.remove(), {once: true});
    181  const {x, y} = (function () {
    182    const rect = childElement.getBoundingClientRect();
    183    return { x: rect.left, y: rect.top };
    184  })();
    185  const actions = new test_driver.Actions();
    186  await actions.pointerMove(10, 10, {origin: childElement})
    187               .pointerDown({button: actions.ButtonType[buttonType]})
    188               .pointerUp({button: actions.ButtonType[buttonType]})
    189               .send();
    190  await promiseLayout();
    191  removeEventsBefore("mousedown");
    192  test(() => {
    193    const maybeMouseDownEvent =
    194      events.length && events[0].type == "mousedown" ? events.shift() : undefined;
    195    assert_equals(
    196      eventToString(maybeMouseDownEvent),
    197      eventToString({ type: "mousedown",  target: "child", button, buttons })
    198    );
    199  }, `${t.name}: mousedown should've been fired`);
    200  assert_true(events.length > 0, `${t.name}: Some events should've been fired after mousedown`);
    201  // Same as the `mousedown` case except `buttons` value because `mouseout`,
    202  // `mouseleave`, `mouseover` and `mouseenter` should (or may) be fired
    203  // after the `mouseup`.  Therefore, `.buttons` should not have the button
    204  // flag.
    205  test(() => {
    206    let mouseOutOrLeave = [];
    207    while (events[0]?.type == "mouseout" || events[0]?.type == "mouseleave") {
    208      mouseOutOrLeave.push(events.shift());
    209    }
    210    assert_equals(
    211      eventsToString(mouseOutOrLeave),
    212      eventsToString([
    213        { type: "mouseout",   target: "child", button: 0, buttons: 0 },
    214        { type: "mouseleave", target: "child", button: 0, buttons: 0 },
    215      ])
    216    );
    217  }, `${t.name}: mouseout and mouseleave should've been fired on the removed child`);
    218  test(() => {
    219    let mouseOver = [];
    220    while (events[0]?.type == "mouseover") {
    221      mouseOver.push(events.shift());
    222    }
    223    assert_equals(
    224      eventsToString(mouseOver),
    225      eventsToString([{ type: "mouseover",  target: "parent", button: 0, buttons: 0 }])
    226    );
    227  }, `${t.name}: mouseover should've been fired on the parent`);
    228  test(() => {
    229    let mouseEnter = [];
    230    while (events[0]?.type == "mouseenter") {
    231      mouseEnter.push(events.shift());
    232    }
    233    assert_equals(eventsToString(mouseEnter), eventsToString([]));
    234  }, `${t.name}: mouseenter should not have been fired on the parent`);
    235  assert_equals(eventsToString(events), eventsToString([]), "All events should've been checked");
    236  parentElement.appendChild(childElement);
    237 }, "Removing an element at mouseup");
    238 </script>
    239 </body>
    240 </html>