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