summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/pointerevents/pointerevent_attributes.html
blob: 13fb30f61e969b95c4c7193e5c8050c68ea0c040 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
<!doctype html>
<html>
<head>
  <title>Pointer Events properties tests</title>
  <meta name="viewport" content="width=device-width">
  <meta name="variant" content="?mouse">
  <meta name="variant" content="?pen">
  <meta name="variant" content="?mouse-right">
  <meta name="variant" content="?pen-right">
  <meta name="variant" content="?touch">
  <meta name="variant" content="?mouse-nonstandard">
  <meta name="variant" content="?pen-nonstandard">
  <meta name="variant" content="?mouse-right-nonstandard">
  <meta name="variant" content="?pen-right-nonstandard">
  <meta name="variant" content="?touch-nonstandard">
  <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
  <style>
    html {
      touch-action: none;
    }

    div {
      padding: 0;
    }

    #square1 {
      background-color: green;
      border: 1px solid black;
      height: 50px;
      width: 50px;
      margin-bottom: 3px;
      display: inline-block;
    }

    #innerFrame {
      position: relative;
      margin-bottom: 3px;
      margin-left: 0;
      top: 0;
      left: 0;
    }
  </style>
</head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<!-- Additional helper script for common checks across event types -->
<script type="text/javascript" src="pointerevent_support.js"></script>
<script>
let frameLoaded = undefined;
const frameLoadedPromise = new Promise(resolve => {
  frameLoaded = resolve;
});
</script>
<body>
  <div id="square1"></div>
  <div>
    <iframe onLoad = "frameLoaded()" id="innerFrame" srcdoc='
      <style>
        html {
          touch-action: none;
        }
        #square2 {
          background-color: green;
          border: 1px solid black;
          height: 50px;
          width: 50px;
          display: inline-block;
        }
      </style>
      <body>
        <div id="square2"></div>
      </body>
    '></iframe>
  </div>
  <!-- Used to detect a sentinel event. Once triggered, all other events must
       have been processed. -->
  <div>
    <button id="done">done</button>
  </div>
</body>
<script>
  window.onload = runTests();

  async function runTests() {

    const queryStringFragments = location.search.substring(1).split('-');
    const pointerType = queryStringFragments[0];
    const button = queryStringFragments[1] === "right" ? "right" : undefined;
    const standard = !(queryStringFragments[queryStringFragments.length - 1] === "nonstandard");

    const eventList = [
      'pointerover',
      'pointerenter',
      'pointerdown',
      'pointerup',
      'pointerout',
      'pointerleave',
      'pointermove'
    ];

    function injectScrubGesture(element) {
      const doneButton = document.getElementById('done');
      const actions = new test_driver.Actions();

      let buttonArguments =
        (button == 'right') ? { button: actions.ButtonType.RIGHT }
                            : undefined;

      // The following comments refer to the first event of each type since
      // that is what is being validated in the test.
      return actions
        .addPointer('pointer1', pointerType)
        // The pointermove, pointerover and pointerenter events will be
        // triggered here with a hover pointer.
        .pointerMove(0, -20, { origin: element })
        // Pointerdown triggers pointerover, pointerenter with a non-hover
        // pointer type.
        .pointerDown(buttonArguments)
        // This move triggers pointermove with a non-hover pointer-type.
        .pointerMove(0, 20, { origin: element })
        // The pointerout and pointerleave events are triggered here with a
        // touch pointer.
        .pointerUp(buttonArguments)
        // An addition move outside of the target bounds is required to trigger
        // pointerout & pointerleave events with a hover pointer.
        .pointerMove(0, 0)
        .send();
    }

    // Processing a click or tap on the done button is used to signal that all
    // other events should have beem handled. This is used to catch unhandled
    // events that would otherwise result in a timeout.
    function clickOrTapDone() {
      const doneButton = document.getElementById('done');
      const pointerupPromise = getEvent('pointerup', doneButton);
      const actionPromise = new test_driver.Actions()
        .addPointer('pointer1', 'touch')
        .pointerMove(0, 0, {origin: doneButton})
        .pointerDown()
        .pointerUp()
        .send();
      return actionPromise.then(pointerupPromise);
    }

    function verifyButtonAttributes(event) {
      let downButton, upButton, downButtons, upButtons;
      if (button == 'right') {
         downButton = 2;
         downButtons = 2;
         upButton = 2;
         upButtons = 0;
      } else {
        // defaults to left button click
        downButton = 0;
        downButtons = 1;
        upButton = 0;
        upButtons = 0;
      }
      const expectationsHover = {
        // Pointer over, enter, and move are processed before the button press.
        pointerover:  { button: -1, buttons: 0 },
        pointerenter: { button:  -1, buttons: 0 },
        pointermove:  { button:  -1, buttons: 0 },
        // Button status changes on pointer down and up.
        pointerdown:  { button:  downButton, buttons: downButtons },
        pointerup:    { button:  upButton, buttons: upButtons },
        // Pointer out and leave are processed after the button release.
        pointerout:   { button:  -1, buttons: 0 },
        pointerleave: { button:  -1, buttons: 0 }
      };
      const expectationsNoHover = {
        // We don't see pointer events except during a touch gesture.
        // Move is the only pointer event where the "button" click state is not
        // changing. All other pointer events are associated with the start or
        // end of a touch gesture.
        pointerover:  { button:  0, buttons: 1 },
        pointerenter: { button:  0, buttons: 1 },
        pointerdown:  { button:  0, buttons: 1 },
        pointermove:  { button: -1, buttons: 1 },
        pointerup:    { button:  0, buttons: 0 },
        pointerout:   { button:  0, buttons: 0 },
        pointerleave: { button:  0, buttons: 0 }
      };
      const expectations =
          (pointerType == 'touch') ? expectationsNoHover : expectationsHover;

      assert_equals(event.button, expectations[event.type].button,
                    `Button attribute on ${event.type}`);
      assert_equals(event.buttons, expectations[event.type].buttons,
                    `Buttons attribute on ${event.type}`);
    }

    function verifyPosition(event) {
      const boundingRect = event.target.getBoundingClientRect();

      // With a touch pointer type, the pointerout and pointerleave will trigger
      // on pointerup while clientX and clientY are still within the target's
      // bounds. With a hover pointer, these events will be triggered only after
      // clientX or clientY are out of the target's bounds.
      if (pointerType != 'touch' &&
          (event.type == 'pointerout' || event.type == 'pointerleave')) {
        assert_true(
            boundingRect.left > event.clientX ||
            boundingRect.right < event.clientX ||
            boundingRect.top > event.clientY ||
            boundingRect.bottom < event.clientY,
            `clientX/clientY is outside the element bounds for ${event.type} event`);
      } else {
        assert_true(
            boundingRect.left <= event.clientX &&
            boundingRect.right >= event.clientX,
            `clientX is within the expected range for ${event.type} event`);
        assert_true(
            boundingRect.top <= event.clientY &&
            boundingRect.bottom >= event.clientY,
            `clientY is within the expected range for ${event.type} event`);
      }
    }

    function verifyEventAttributes(event, testNamePrefix) {
       verifyButtonAttributes(event);
       verifyPosition(event);
       assert_true(event.isPrimary, 'isPrimary attribute is true');
       check_PointerEvent(event, testNamePrefix, standard);
    }

    function pointerPromise(test, testNamePrefix, type, target) {
      let rejectCallback = undefined;
      promise = new Promise((resolve, reject) => {
        // Store a reference to the promise rejection functions, which would
        // otherwise not be visible outside the promise object. If the callback
        // remains set when the deadline is reached, it means that the promise
        // will not get resolved and should be rejected.
        rejectCallback = reject;
        const pointerEventListener = event => {
          rejectCallback = undefined;
          assert_equals(event.type, type, `type attribute for ${type} event`);
          event.preventDefault();
          resolve(event);
        };
        target.addEventListener(type, pointerEventListener, { once: true });
        test.add_cleanup(() => {
          // Just in case of an assert prior to the events being triggered.
          document.removeEventListener(type, pointerEventListener,
                                       { once: true });
        });
      }).then(result => { verifyEventAttributes(result, testNamePrefix); },
              error => { assert_unreached(error); });
      promise.deadlineReached = () => {
        // If the event has not been received, the promise will not be
        // fulfilled, leading to a timeout. Reject the promise if still pending.
        if (rejectCallback) {
          rejectCallback(`missing ${type} event`);
        }
      }
      return promise;
    }

    async function runPointerEventsTest(test, testNamePrefix, target) {
      assert_true(['mouse', 'pen', 'touch'].indexOf(pointerType) >= 0,
                  `Unexpected pointer type (${pointerType})`);

      const promises = [];
      eventList.forEach(type => {
        // Create a promise for each event type. If clicking on the done button
        // is detected before an event's promise is resolved, then the promise
        // will be rejected. Otherwise, the attributes for the event are
        // verified.
        promises.push(pointerPromise(test, testNamePrefix, type, target));
      });

      await injectScrubGesture(target);

      // The injected gestures consist of a shrub on a button followed by a
      // click on the done button. The promise is only resolved after the
      // done click is detected. At this stage all other events must have been
      // processed. Any unresolved promises in the list will be rejected to
      //  avoid a test timeout. The rejection will trigger a test failure.
      await clickOrTapDone().then(promises.map(p => p.deadlineReached()));

      // Once all promises are resolved, all event attributes have been
      // successfully verified.
      return Promise.all(promises);
    }

    promise_test(t => {
      const square1 = document.getElementById('square1');
      return runPointerEventsTest(t, '', square1);
    }, 'Test pointer events in the main document');

    promise_test(async t => {
      const innerFrame = document.getElementById('innerFrame');
      await frameLoadedPromise;
      const square2 = innerFrame.contentDocument.getElementById('square2');
      return runPointerEventsTest(t, 'Inner Frame', square2);
    }, 'Test pointer events in an iframe');
  }
</script>