Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/javascript/atoms/touchscreen.js
2884 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License. You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied. See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
/**
19
* @fileoverview The file contains an abstraction of a touch screen
20
* for simulating atomic touchscreen actions.
21
*/
22
23
goog.provide('bot.Touchscreen');
24
25
goog.require('bot');
26
goog.require('bot.Device');
27
goog.require('bot.Error');
28
goog.require('bot.ErrorCode');
29
goog.require('bot.dom');
30
goog.require('bot.events.EventType');
31
goog.require('goog.dom.TagName');
32
goog.require('goog.math.Coordinate');
33
goog.require('goog.userAgent.product');
34
35
36
37
/**
38
* A TouchScreen that provides atomic touch actions. The metaphor
39
* for this abstraction is a finger moving above the touchscreen that
40
* can press and then release the touchscreen when specified.
41
*
42
* The touchscreen supports three actions: press, release, and move.
43
*
44
* @constructor
45
* @extends {bot.Device}
46
*/
47
bot.Touchscreen = function () {
48
goog.base(this);
49
50
/** @private {!goog.math.Coordinate} */
51
this.clientXY_ = new goog.math.Coordinate(0, 0);
52
53
/** @private {!goog.math.Coordinate} */
54
this.clientXY2_ = new goog.math.Coordinate(0, 0);
55
};
56
goog.inherits(bot.Touchscreen, bot.Device);
57
58
59
/** @private {boolean} */
60
bot.Touchscreen.prototype.fireMouseEventsOnRelease_ = true;
61
62
63
/** @private {boolean} */
64
bot.Touchscreen.prototype.cancelled_ = false;
65
66
67
/** @private {number} */
68
bot.Touchscreen.prototype.touchIdentifier_ = 0;
69
70
71
/** @private {number} */
72
bot.Touchscreen.prototype.touchIdentifier2_ = 0;
73
74
75
/** @private {number} */
76
bot.Touchscreen.prototype.touchCounter_ = 2;
77
78
79
/**
80
* Press the touch screen. Pressing before moving results in an exception.
81
* Pressing while already pressed also results in an exception.
82
*
83
* @param {boolean=} opt_press2 Whether or not press the second finger during
84
* the press. If not defined or false, only the primary finger will be
85
* pressed.
86
*/
87
bot.Touchscreen.prototype.press = function (opt_press2) {
88
if (this.isPressed()) {
89
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
90
'Cannot press touchscreen when already pressed.');
91
}
92
93
this.touchIdentifier_ = this.touchCounter_++;
94
if (opt_press2) {
95
this.touchIdentifier2_ = this.touchCounter_++;
96
}
97
98
if (bot.userAgent.IE_DOC_10) {
99
this.fireMouseEventsOnRelease_ = true;
100
this.firePointerEvents_(bot.Touchscreen.fireSinglePressPointer_);
101
} else {
102
this.fireMouseEventsOnRelease_ = this.fireTouchEvent_(
103
bot.events.EventType.TOUCHSTART);
104
}
105
};
106
107
108
/**
109
* Releases an element on a touchscreen. Releasing an element that is not
110
* pressed results in an exception.
111
*/
112
bot.Touchscreen.prototype.release = function () {
113
if (!this.isPressed()) {
114
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
115
'Cannot release touchscreen when not already pressed.');
116
}
117
118
if (!bot.userAgent.IE_DOC_10) {
119
this.fireTouchReleaseEvents_();
120
} else if (!this.cancelled_) {
121
this.firePointerEvents_(bot.Touchscreen.fireSingleReleasePointer_);
122
}
123
bot.Device.clearPointerMap();
124
this.touchIdentifier_ = 0;
125
this.touchIdentifier2_ = 0;
126
this.cancelled_ = false;
127
};
128
129
130
/**
131
* Moves finger along the touchscreen.
132
*
133
* @param {!Element} element Element that is being pressed.
134
* @param {!goog.math.Coordinate} coords Coordinates relative to
135
* currentElement.
136
* @param {goog.math.Coordinate=} opt_coords2 Coordinates relative to
137
* currentElement.
138
*/
139
bot.Touchscreen.prototype.move = function (element, coords, opt_coords2) {
140
// The target element for touch actions is the original element. Hence, the
141
// element is set only when the touchscreen is not currently being pressed.
142
// The exception is IE10 which fire events on the moved to element.
143
var originalElement = this.getElement();
144
if (!this.isPressed() || bot.userAgent.IE_DOC_10) {
145
this.setElement(element);
146
}
147
148
var rect = bot.dom.getClientRect(element);
149
this.clientXY_.x = coords.x + rect.left;
150
this.clientXY_.y = coords.y + rect.top;
151
152
if (goog.isDef(opt_coords2)) {
153
this.clientXY2_.x = opt_coords2.x + rect.left;
154
this.clientXY2_.y = opt_coords2.y + rect.top;
155
}
156
157
if (this.isPressed()) {
158
if (!bot.userAgent.IE_DOC_10) {
159
this.fireMouseEventsOnRelease_ = false;
160
this.fireTouchEvent_(bot.events.EventType.TOUCHMOVE);
161
} else if (!this.cancelled_) {
162
if (element != originalElement) {
163
this.fireMouseEventsOnRelease_ = false;
164
}
165
if (bot.Touchscreen.hasMsTouchActionsEnabled_(element)) {
166
this.firePointerEvents_(bot.Touchscreen.fireSingleMovePointer_);
167
} else {
168
this.fireMSPointerEvent(bot.events.EventType.MSPOINTEROUT, coords, -1,
169
this.touchIdentifier_, MSPointerEvent.MSPOINTER_TYPE_TOUCH, true);
170
this.fireMouseEvent(bot.events.EventType.MOUSEOUT, coords, 0);
171
this.fireMSPointerEvent(bot.events.EventType.MSPOINTERCANCEL, coords, 0,
172
this.touchIdentifier_, MSPointerEvent.MSPOINTER_TYPE_TOUCH, true);
173
this.cancelled_ = true;
174
bot.Device.clearPointerMap();
175
}
176
}
177
}
178
};
179
180
181
/**
182
* Returns whether the touchscreen is currently pressed.
183
*
184
* @return {boolean} Whether the touchscreen is pressed.
185
*/
186
bot.Touchscreen.prototype.isPressed = function () {
187
return !!this.touchIdentifier_;
188
};
189
190
191
/**
192
* A helper function to fire touch events.
193
*
194
* @param {bot.events.EventType} type Event type.
195
* @return {boolean} Whether the event fired successfully or was cancelled.
196
* @private
197
*/
198
bot.Touchscreen.prototype.fireTouchEvent_ = function (type) {
199
if (!this.isPressed()) {
200
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
201
'Should never fire event when touchscreen is not pressed.');
202
}
203
var touchIdentifier2;
204
var coords2;
205
if (this.touchIdentifier2_) {
206
touchIdentifier2 = this.touchIdentifier2_;
207
coords2 = this.clientXY2_;
208
}
209
return this.fireTouchEvent(type, this.touchIdentifier_, this.clientXY_,
210
touchIdentifier2, coords2);
211
};
212
213
214
/**
215
* A helper function to fire touch events that occur on a release.
216
*
217
* @private
218
*/
219
bot.Touchscreen.prototype.fireTouchReleaseEvents_ = function () {
220
var touchendSuccess = this.fireTouchEvent_(bot.events.EventType.TOUCHEND);
221
222
// In general, TouchScreen.Release will fire the legacy mouse events:
223
// mousemove, mousedown, mouseup, and click after the touch events have been
224
// fired. The click button should be zero and only one mousemove should fire.
225
// Under the following cases, mouse events should not be fired:
226
// 1. Movement has occurred since press.
227
// 2. Any event handler for touchstart has called preventDefault().
228
// 3. Any event handler for touchend has called preventDefault(), and browser
229
// is Mobile Safari or Chrome.
230
var fireMouseEvents =
231
this.fireMouseEventsOnRelease_ &&
232
(touchendSuccess || !(bot.userAgent.IOS ||
233
goog.userAgent.product.CHROME));
234
235
if (fireMouseEvents) {
236
this.fireMouseEvent(bot.events.EventType.MOUSEMOVE, this.clientXY_, 0);
237
var performFocus = this.fireMouseEvent(bot.events.EventType.MOUSEDOWN,
238
this.clientXY_, 0);
239
// Element gets focus after the mousedown event only if the mousedown was
240
// not cancelled.
241
if (performFocus) {
242
this.focusOnElement();
243
}
244
this.maybeToggleOption();
245
246
// If a mouseup event is dispatched to an interactable event, and that
247
// mouseup would complete a click, then the click event must be dispatched
248
// even if the element becomes non-interactable after the mouseup.
249
var elementInteractableBeforeMouseup =
250
bot.dom.isInteractable(this.getElement());
251
this.fireMouseEvent(bot.events.EventType.MOUSEUP, this.clientXY_, 0);
252
253
// Special click logic to follow links and to perform form actions.
254
if (!(bot.userAgent.WINDOWS_PHONE &&
255
bot.dom.isElement(this.getElement(), goog.dom.TagName.OPTION))) {
256
this.clickElement(this.clientXY_,
257
/* button */ 0,
258
/* opt_force */ elementInteractableBeforeMouseup);
259
}
260
}
261
};
262
263
264
/**
265
* A helper function to fire a sequence of Pointer events.
266
* @param {function(!bot.Touchscreen, !Element, !goog.math.Coordinate, number,
267
* boolean)} fireSinglePointer A function that fires a set of events for one
268
* finger.
269
* @private
270
*/
271
bot.Touchscreen.prototype.firePointerEvents_ = function (fireSinglePointer) {
272
fireSinglePointer(this, this.getElement(), this.clientXY_,
273
this.touchIdentifier_, true);
274
if (this.touchIdentifier2_ &&
275
bot.Touchscreen.hasMsTouchActionsEnabled_(this.getElement())) {
276
fireSinglePointer(this, this.getElement(),
277
this.clientXY2_, this.touchIdentifier2_, false);
278
}
279
};
280
281
282
/**
283
* A helper function to fire Pointer events related to a press.
284
*
285
* @param {!bot.Touchscreen} ts A touchscreen object.
286
* @param {!Element} element Element that is being pressed.
287
* @param {!goog.math.Coordinate} coords Coordinates relative to
288
* currentElement.
289
* @param {number} id The touch identifier.
290
* @param {boolean} isPrimary Whether the pointer represents the primary point
291
* of contact.
292
* @private
293
*/
294
bot.Touchscreen.fireSinglePressPointer_ = function (ts, element, coords, id,
295
isPrimary) {
296
// Fire a mousemove event.
297
ts.fireMouseEvent(bot.events.EventType.MOUSEMOVE, coords, 0);
298
299
// Fire a MSPointerOver and mouseover events.
300
ts.fireMSPointerEvent(bot.events.EventType.MSPOINTEROVER, coords, 0, id,
301
MSPointerEvent.MSPOINTER_TYPE_TOUCH, isPrimary);
302
ts.fireMouseEvent(bot.events.EventType.MOUSEOVER, coords, 0);
303
304
// Fire a MSPointerDown and mousedown events.
305
ts.fireMSPointerEvent(bot.events.EventType.MSPOINTERDOWN, coords, 0, id,
306
MSPointerEvent.MSPOINTER_TYPE_TOUCH, isPrimary);
307
308
// Element gets focus after the mousedown event.
309
if (ts.fireMouseEvent(bot.events.EventType.MOUSEDOWN, coords, 0)) {
310
// For selectable elements, IE 10 fires a MSGotPointerCapture event.
311
if (bot.dom.isSelectable(element)) {
312
ts.fireMSPointerEvent(bot.events.EventType.MSGOTPOINTERCAPTURE, coords, 0,
313
id, MSPointerEvent.MSPOINTER_TYPE_TOUCH, isPrimary);
314
}
315
ts.focusOnElement();
316
}
317
};
318
319
320
/**
321
* A helper function to fire Pointer events related to a release.
322
*
323
* @param {!bot.Touchscreen} ts A touchscreen object.
324
* @param {!Element} element Element that is being released.
325
* @param {!goog.math.Coordinate} coords Coordinates relative to
326
* currentElement.
327
* @param {number} id The touch identifier.
328
* @param {boolean} isPrimary Whether the pointer represents the primary point
329
* of contact.
330
* @private
331
*/
332
bot.Touchscreen.fireSingleReleasePointer_ = function (ts, element, coords, id,
333
isPrimary) {
334
// Fire a MSPointerUp and mouseup events.
335
ts.fireMSPointerEvent(bot.events.EventType.MSPOINTERUP, coords, 0, id,
336
MSPointerEvent.MSPOINTER_TYPE_TOUCH, isPrimary);
337
338
// If a mouseup event is dispatched to an interactable event, and that mouseup
339
// would complete a click, then the click event must be dispatched even if the
340
// element becomes non-interactable after the mouseup.
341
var elementInteractableBeforeMouseup =
342
bot.dom.isInteractable(ts.getElement());
343
ts.fireMouseEvent(bot.events.EventType.MOUSEUP, coords, 0, null, 0, false,
344
id);
345
346
// Fire a click.
347
if (ts.fireMouseEventsOnRelease_) {
348
ts.maybeToggleOption();
349
if (!(bot.userAgent.WINDOWS_PHONE &&
350
bot.dom.isElement(element, goog.dom.TagName.OPTION))) {
351
ts.clickElement(ts.clientXY_,
352
/* button */ 0,
353
/* opt_force */ elementInteractableBeforeMouseup,
354
id);
355
}
356
}
357
358
if (bot.dom.isSelectable(element)) {
359
// For selectable elements, IE 10 fires a MSLostPointerCapture event.
360
ts.fireMSPointerEvent(bot.events.EventType.MSLOSTPOINTERCAPTURE,
361
new goog.math.Coordinate(0, 0), 0, id,
362
MSPointerEvent.MSPOINTER_TYPE_TOUCH, false);
363
}
364
365
// Fire a MSPointerOut and mouseout events.
366
ts.fireMSPointerEvent(bot.events.EventType.MSPOINTEROUT, coords, -1, id,
367
MSPointerEvent.MSPOINTER_TYPE_TOUCH, isPrimary);
368
ts.fireMouseEvent(bot.events.EventType.MOUSEOUT, coords, 0, null, 0, false,
369
id);
370
};
371
372
373
/**
374
* A helper function to fire Pointer events related to a move.
375
*
376
* @param {!bot.Touchscreen} ts A touchscreen object.
377
* @param {!Element} element Element that is being moved.
378
* @param {!goog.math.Coordinate} coords Coordinates relative to
379
* currentElement.
380
* @param {number} id The touch identifier.
381
* @param {boolean} isPrimary Whether the pointer represents the primary point
382
* of contact.
383
* @private
384
*/
385
bot.Touchscreen.fireSingleMovePointer_ = function (ts, element, coords, id,
386
isPrimary) {
387
// Fire a MSPointerMove and mousemove events.
388
ts.fireMSPointerEvent(bot.events.EventType.MSPOINTERMOVE, coords, -1, id,
389
MSPointerEvent.MSPOINTER_TYPE_TOUCH, isPrimary);
390
ts.fireMouseEvent(bot.events.EventType.MOUSEMOVE, coords, 0, null, 0, false,
391
id);
392
};
393
394
395
/**
396
* A function that determines whether an element can be manipulated by the user.
397
* The msTouchAction style is queried and an element can be manipulated if the
398
* style value is none. If an element cannot be manipulated, then move gestures
399
* will result in a cancellation and multi-touch events will be prevented. Tap
400
* gestures will still be allowed. If not on IE 10, the function returns true.
401
*
402
* @param {!Element} element The element being manipulated.
403
* @return {boolean} Whether the element can be manipulated.
404
* @private
405
*/
406
bot.Touchscreen.hasMsTouchActionsEnabled_ = function (element) {
407
if (!bot.userAgent.IE_DOC_10) {
408
throw new Error('hasMsTouchActionsEnable should only be called from IE 10');
409
}
410
411
// Although this particular element may have a style indicating that it cannot
412
// receive javascript events, its parent may indicate otherwise.
413
if (bot.dom.getEffectiveStyle(element, 'ms-touch-action') == 'none') {
414
return true;
415
} else {
416
var parent = bot.dom.getParentElement(element);
417
return !!parent && bot.Touchscreen.hasMsTouchActionsEnabled_(parent);
418
}
419
};
420
421