tor-browser

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

pointerevent_attributes.html (11674B)


      1 <!doctype html>
      2 <html>
      3 <head>
      4  <title>Pointer Events properties tests</title>
      5  <meta name="viewport" content="width=device-width">
      6  <meta name="variant" content="?mouse">
      7  <meta name="variant" content="?pen">
      8  <meta name="variant" content="?mouse-right">
      9  <meta name="variant" content="?pen-right">
     10  <meta name="variant" content="?touch">
     11  <meta name="variant" content="?mouse-nonstandard">
     12  <meta name="variant" content="?pen-nonstandard">
     13  <meta name="variant" content="?mouse-right-nonstandard">
     14  <meta name="variant" content="?pen-right-nonstandard">
     15  <meta name="variant" content="?touch-nonstandard">
     16  <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
     17  <style>
     18    html {
     19      touch-action: none;
     20    }
     21 
     22    div {
     23      padding: 0;
     24    }
     25 
     26    #square1 {
     27      background-color: green;
     28      border: 1px solid black;
     29      height: 50px;
     30      width: 50px;
     31      margin-bottom: 3px;
     32      display: inline-block;
     33    }
     34 
     35    #innerFrame {
     36      position: relative;
     37      margin-bottom: 3px;
     38      margin-left: 0;
     39      top: 0;
     40      left: 0;
     41    }
     42  </style>
     43 </head>
     44 <script src="/resources/testharness.js"></script>
     45 <script src="/resources/testharnessreport.js"></script>
     46 <script src="/resources/testdriver.js"></script>
     47 <script src="/resources/testdriver-actions.js"></script>
     48 <script src="/resources/testdriver-vendor.js"></script>
     49 <!-- Additional helper script for common checks across event types -->
     50 <script type="text/javascript" src="pointerevent_support.js"></script>
     51 <script>
     52 let frameLoaded = undefined;
     53 const frameLoadedPromise = new Promise(resolve => {
     54  frameLoaded = resolve;
     55 });
     56 </script>
     57 <body>
     58  <div id="square1"></div>
     59  <div>
     60    <iframe onLoad = "frameLoaded()" id="innerFrame" srcdoc='
     61      <style>
     62        html {
     63          touch-action: none;
     64        }
     65        #square2 {
     66          background-color: green;
     67          border: 1px solid black;
     68          height: 50px;
     69          width: 50px;
     70          display: inline-block;
     71        }
     72      </style>
     73      <script>
     74        // Prevent opening contextmenu.
     75        addEventListener("contextmenu", e => e.preventDefault());
     76      </script>
     77      <body>
     78        <div id="square2"></div>
     79      </body>
     80    '></iframe>
     81  </div>
     82  <!-- Used to detect a sentinel event. Once triggered, all other events must
     83       have been processed. -->
     84  <div>
     85    <button id="done">done</button>
     86  </div>
     87 </body>
     88 <script>
     89  window.onload = runTests();
     90 
     91  async function runTests() {
     92    // Prevent opening contextmenu.
     93    addEventListener("contextmenu", e => e.preventDefault());
     94 
     95    const queryStringFragments = location.search.substring(1).split('-');
     96    const pointerType = queryStringFragments[0];
     97    const button = queryStringFragments[1] === "right" ? "right" : undefined;
     98    const standard = !(queryStringFragments[queryStringFragments.length - 1] === "nonstandard");
     99 
    100    const eventList = [
    101      'pointerover',
    102      'pointerenter',
    103      'pointerdown',
    104      'pointerup',
    105      'pointerout',
    106      'pointerleave',
    107      'pointermove'
    108    ];
    109 
    110    function injectScrubGesture(element) {
    111      const actions = new test_driver.Actions();
    112 
    113      let buttonArguments =
    114        (button == 'right') ? { button: actions.ButtonType.RIGHT }
    115                            : undefined;
    116 
    117      // The following comments refer to the first event of each type since
    118      // that is what is being validated in the test.
    119      return actions
    120        .addPointer('pointer1', pointerType)
    121        // The pointermove, pointerover and pointerenter events will be
    122        // triggered here with a hover pointer.
    123        .pointerMove(0, -20, { origin: element })
    124        // Pointerdown triggers pointerover, pointerenter with a non-hover
    125        // pointer type.
    126        .pointerDown(buttonArguments)
    127        // This move triggers pointermove with a non-hover pointer-type.
    128        .pointerMove(0, 20, { origin: element })
    129        // The pointerout and pointerleave events are triggered here with a
    130        // touch pointer.
    131        .pointerUp(buttonArguments)
    132        // An addition move outside of the target bounds is required to trigger
    133        // pointerout & pointerleave events with a hover pointer.
    134        .pointerMove(0, 0)
    135        .send();
    136    }
    137 
    138    // Processing a click or tap on the done button is used to signal that all
    139    // other events should have been handled. This is used to catch unhandled
    140    // events that would otherwise result in a timeout.
    141    function clickOrTapDone() {
    142      const doneButton = document.getElementById('done');
    143      const pointerupPromise = getEvent('pointerup', doneButton);
    144      const actionPromise = new test_driver.Actions()
    145        .addPointer('pointer1', 'touch')
    146        .pointerMove(0, 0, {origin: doneButton})
    147        .pointerDown()
    148        .pointerUp()
    149        .send();
    150      return actionPromise.then(pointerupPromise);
    151    }
    152 
    153    function verifyButtonAttributes(event) {
    154      let downButton, upButton, downButtons, upButtons;
    155      if (button == 'right') {
    156         downButton = 2;
    157         downButtons = 2;
    158         upButton = 2;
    159         upButtons = 0;
    160      } else {
    161        // defaults to left button click
    162        downButton = 0;
    163        downButtons = 1;
    164        upButton = 0;
    165        upButtons = 0;
    166      }
    167      const expectationsHover = {
    168        // Pointer over, enter, and move are processed before the button press.
    169        pointerover:  { button: -1, buttons: 0 },
    170        pointerenter: { button:  -1, buttons: 0 },
    171        pointermove:  { button:  -1, buttons: 0 },
    172        // Button status changes on pointer down and up.
    173        pointerdown:  { button:  downButton, buttons: downButtons },
    174        pointerup:    { button:  upButton, buttons: upButtons },
    175        // Pointer out and leave are processed after the button release.
    176        pointerout:   { button:  -1, buttons: 0 },
    177        pointerleave: { button:  -1, buttons: 0 }
    178      };
    179      const expectationsNoHover = {
    180        // We don't see pointer events except during a touch gesture.
    181        // Move is the only pointer event where the "button" click state is not
    182        // changing. All other pointer events are associated with the start or
    183        // end of a touch gesture.
    184        pointerover:  { button:  0, buttons: 1 },
    185        pointerenter: { button:  0, buttons: 1 },
    186        pointerdown:  { button:  0, buttons: 1 },
    187        pointermove:  { button: -1, buttons: 1 },
    188        pointerup:    { button:  0, buttons: 0 },
    189        pointerout:   { button:  0, buttons: 0 },
    190        pointerleave: { button:  0, buttons: 0 }
    191      };
    192      const expectations =
    193          (pointerType == 'touch') ? expectationsNoHover : expectationsHover;
    194 
    195      assert_equals(event.button, expectations[event.type].button,
    196                    `Button attribute on ${event.type}`);
    197      assert_equals(event.buttons, expectations[event.type].buttons,
    198                    `Buttons attribute on ${event.type}`);
    199    }
    200 
    201    function verifyPosition(event) {
    202      const boundingRect = event.target.getBoundingClientRect();
    203 
    204      // With a touch pointer type, the pointerout and pointerleave will trigger
    205      // on pointerup while clientX and clientY are still within the target's
    206      // bounds. With a hover pointer, these events will be triggered only after
    207      // clientX or clientY are out of the target's bounds.
    208      if (pointerType != 'touch' &&
    209          (event.type == 'pointerout' || event.type == 'pointerleave')) {
    210        assert_true(
    211            boundingRect.left > event.clientX ||
    212            boundingRect.right < event.clientX ||
    213            boundingRect.top > event.clientY ||
    214            boundingRect.bottom < event.clientY,
    215            `clientX/clientY is outside the element bounds for ${event.type} event`);
    216      } else {
    217        assert_true(
    218            boundingRect.left <= event.clientX &&
    219            boundingRect.right >= event.clientX,
    220            `clientX is within the expected range for ${event.type} event`);
    221        assert_true(
    222            boundingRect.top <= event.clientY &&
    223            boundingRect.bottom >= event.clientY,
    224            `clientY is within the expected range for ${event.type} event`);
    225      }
    226    }
    227 
    228    function verifyEventAttributes(event, testNamePrefix) {
    229       verifyButtonAttributes(event);
    230       verifyPosition(event);
    231       assert_true(event.isPrimary, 'isPrimary attribute is true');
    232       check_PointerEvent(event, testNamePrefix, standard);
    233    }
    234 
    235    function pointerPromise(test, testNamePrefix, type, target) {
    236      let rejectCallback = undefined;
    237      promise = new Promise((resolve, reject) => {
    238        // Store a reference to the promise rejection functions, which would
    239        // otherwise not be visible outside the promise object. If the callback
    240        // remains set when the deadline is reached, it means that the promise
    241        // will not get resolved and should be rejected.
    242        rejectCallback = reject;
    243        const pointerEventListener = event => {
    244          rejectCallback = undefined;
    245          assert_equals(event.type, type, `type attribute for ${type} event`);
    246          event.preventDefault();
    247          resolve(event);
    248        };
    249        target.addEventListener(type, pointerEventListener, { once: true });
    250        test.add_cleanup(() => {
    251          // Just in case of an assert prior to the events being triggered.
    252          document.removeEventListener(type, pointerEventListener,
    253                                       { once: true });
    254        });
    255      }).then(result => { verifyEventAttributes(result, testNamePrefix); },
    256              error => { assert_unreached(error); });
    257      promise.deadlineReached = () => {
    258        // If the event has not been received, the promise will not be
    259        // fulfilled, leading to a timeout. Reject the promise if still pending.
    260        if (rejectCallback) {
    261          rejectCallback(`missing ${type} event`);
    262        }
    263      }
    264      return promise;
    265    }
    266 
    267    async function runPointerEventsTest(test, testNamePrefix, target) {
    268      assert_true(['mouse', 'pen', 'touch'].indexOf(pointerType) >= 0,
    269                  `Unexpected pointer type (${pointerType})`);
    270 
    271      const promises = [];
    272      eventList.forEach(type => {
    273        // Create a promise for each event type. If clicking on the done button
    274        // is detected before an event's promise is resolved, then the promise
    275        // will be rejected. Otherwise, the attributes for the event are
    276        // verified.
    277        promises.push(pointerPromise(test, testNamePrefix, type, target));
    278      });
    279 
    280      await injectScrubGesture(target);
    281 
    282      // The injected gestures consist of a shrub on a button followed by a
    283      // click on the done button. The promise is only resolved after the
    284      // done click is detected. At this stage all other events must have been
    285      // processed. Any unresolved promises in the list will be rejected to
    286      //  avoid a test timeout. The rejection will trigger a test failure.
    287      await clickOrTapDone().then(promises.map(p => p.deadlineReached()));
    288 
    289      // Once all promises are resolved, all event attributes have been
    290      // successfully verified.
    291      return Promise.all(promises);
    292    }
    293 
    294    promise_test(t => {
    295      const square1 = document.getElementById('square1');
    296      return runPointerEventsTest(t, '', square1);
    297    }, 'Test pointer events in the main document');
    298 
    299    promise_test(async t => {
    300      const innerFrame = document.getElementById('innerFrame');
    301      await frameLoadedPromise;
    302      const square2 = innerFrame.contentDocument.getElementById('square2');
    303      return runPointerEventsTest(t, 'Inner Frame', square2);
    304    }, 'Test pointer events in an iframe');
    305  }
    306 </script>