(iPad) Link tapping is sluggish on many sites
[WebKit-https.git] / LayoutTests / resources / ui-helper.js
1
2 window.UIHelper = class UIHelper {
3     static isIOS()
4     {
5         return navigator.userAgent.includes('iPhone') || navigator.userAgent.includes('iPad');
6     }
7
8     static isWebKit2()
9     {
10         return window.testRunner.isWebKit2;
11     }
12
13     static tapAt(x, y)
14     {
15         console.assert(this.isIOS());
16
17         if (!this.isWebKit2()) {
18             eventSender.addTouchPoint(x, y);
19             eventSender.touchStart();
20             eventSender.releaseTouchPoint(0);
21             eventSender.touchEnd();
22             return Promise.resolve();
23         }
24
25         return new Promise((resolve) => {
26             testRunner.runUIScript(`
27                 uiController.singleTapAtPoint(${x}, ${y}, function() {
28                     uiController.uiScriptComplete();
29                 });`, resolve);
30         });
31     }
32
33     static doubleTapAt(x, y)
34     {
35         console.assert(this.isIOS());
36
37         if (!this.isWebKit2()) {
38             eventSender.addTouchPoint(x, y);
39             eventSender.touchStart();
40             eventSender.releaseTouchPoint(0);
41             eventSender.touchEnd();
42             eventSender.addTouchPoint(x, y);
43             eventSender.touchStart();
44             eventSender.releaseTouchPoint(0);
45             eventSender.touchEnd();
46             return Promise.resolve();
47         }
48
49         return new Promise((resolve) => {
50             testRunner.runUIScript(`
51                 uiController.doubleTapAtPoint(${x}, ${y}, function() {
52                     uiController.uiScriptComplete();
53                 });`, resolve);
54         });
55     }
56
57     static zoomByDoubleTappingAt(x, y)
58     {
59         console.assert(this.isIOS());
60
61         if (!this.isWebKit2()) {
62             eventSender.addTouchPoint(x, y);
63             eventSender.touchStart();
64             eventSender.releaseTouchPoint(0);
65             eventSender.touchEnd();
66             eventSender.addTouchPoint(x, y);
67             eventSender.touchStart();
68             eventSender.releaseTouchPoint(0);
69             eventSender.touchEnd();
70             return Promise.resolve();
71         }
72
73         return new Promise((resolve) => {
74             testRunner.runUIScript(`
75                 uiController.didEndZoomingCallback = () => {
76                     uiController.didEndZoomingCallback = null;
77                     uiController.uiScriptComplete(uiController.zoomScale);
78                 };
79                 uiController.doubleTapAtPoint(${x}, ${y}, () => {});`, resolve);
80         });
81     }
82
83     static activateAt(x, y)
84     {
85         if (!this.isWebKit2() || !this.isIOS()) {
86             eventSender.mouseMoveTo(x, y);
87             eventSender.mouseDown();
88             eventSender.mouseUp();
89             return Promise.resolve();
90         }
91
92         return new Promise((resolve) => {
93             testRunner.runUIScript(`
94                 uiController.singleTapAtPoint(${x}, ${y}, function() {
95                     uiController.uiScriptComplete();
96                 });`, resolve);
97         });
98     }
99
100     static activateElement(element)
101     {
102         const x = element.offsetLeft + element.offsetWidth / 2;
103         const y = element.offsetTop + element.offsetHeight / 2;
104         return UIHelper.activateAt(x, y);
105     }
106
107     static keyDown(key, modifiers=[])
108     {
109         if (!this.isWebKit2() || !this.isIOS()) {
110             eventSender.keyDown(key, modifiers);
111             return Promise.resolve();
112         }
113
114         return new Promise((resolve) => {
115             testRunner.runUIScript(`uiController.keyDown("${key}", ${JSON.stringify(modifiers)});`, resolve);
116         });
117     }
118
119     static toggleCapsLock()
120     {
121         return new Promise((resolve) => {
122             testRunner.runUIScript(`uiController.toggleCapsLock(() => uiController.uiScriptComplete());`, resolve);
123         });
124     }
125
126     static ensurePresentationUpdate()
127     {
128         if (!this.isWebKit2()) {
129             testRunner.display();
130             return Promise.resolve();
131         }
132
133         return new Promise(resolve => {
134             testRunner.runUIScript(`
135                 uiController.doAfterPresentationUpdate(function() {
136                     uiController.uiScriptComplete();
137                 });`, resolve);
138         });
139     }
140
141     static ensureVisibleContentRectUpdate()
142     {
143         if (!this.isWebKit2())
144             return Promise.resolve();
145
146         return new Promise(resolve => {
147             const visibleContentRectUpdateScript = "uiController.doAfterVisibleContentRectUpdate(() => uiController.uiScriptComplete())";
148             testRunner.runUIScript(visibleContentRectUpdateScript, resolve);
149         });
150     }
151
152     static activateAndWaitForInputSessionAt(x, y)
153     {
154         if (!this.isWebKit2() || !this.isIOS())
155             return this.activateAt(x, y);
156
157         return new Promise(resolve => {
158             testRunner.runUIScript(`
159                 (function() {
160                     uiController.didShowKeyboardCallback = function() {
161                         uiController.uiScriptComplete();
162                     };
163                     uiController.singleTapAtPoint(${x}, ${y}, function() { });
164                 })()`, resolve);
165         });
166     }
167
168     static activateFormControl(element)
169     {
170         if (!this.isWebKit2() || !this.isIOS())
171             return this.activateElement(element);
172
173         const x = element.offsetLeft + element.offsetWidth / 2;
174         const y = element.offsetTop + element.offsetHeight / 2;
175
176         return new Promise(resolve => {
177             testRunner.runUIScript(`
178                 (function() {
179                     uiController.didStartFormControlInteractionCallback = function() {
180                         uiController.uiScriptComplete();
181                     };
182                     uiController.singleTapAtPoint(${x}, ${y}, function() { });
183                 })()`, resolve);
184         });
185     }
186
187     static waitForKeyboardToHide()
188     {
189         if (!this.isWebKit2() || !this.isIOS())
190             return Promise.resolve();
191
192         return new Promise(resolve => {
193             testRunner.runUIScript(`
194                 (function() {
195                     if (uiController.isShowingKeyboard)
196                         uiController.didHideKeyboardCallback = () => uiController.uiScriptComplete();
197                     else
198                         uiController.uiScriptComplete();
199                 })()`, resolve);
200         });
201     }
202
203     static getUICaretRect()
204     {
205         if (!this.isWebKit2() || !this.isIOS())
206             return Promise.resolve();
207
208         return new Promise(resolve => {
209             testRunner.runUIScript(`(function() {
210                 uiController.doAfterNextStablePresentationUpdate(function() {
211                     uiController.uiScriptComplete(JSON.stringify(uiController.textSelectionCaretRect));
212                 });
213             })()`, jsonString => {
214                 resolve(JSON.parse(jsonString));
215             });
216         });
217     }
218
219     static getUISelectionRects()
220     {
221         if (!this.isWebKit2() || !this.isIOS())
222             return Promise.resolve();
223
224         return new Promise(resolve => {
225             testRunner.runUIScript(`(function() {
226                 uiController.doAfterNextStablePresentationUpdate(function() {
227                     uiController.uiScriptComplete(JSON.stringify(uiController.textSelectionRangeRects));
228                 });
229             })()`, jsonString => {
230                 resolve(JSON.parse(jsonString));
231             });
232         });
233     }
234
235     static getUICaretViewRect()
236     {
237         if (!this.isWebKit2() || !this.isIOS())
238             return Promise.resolve();
239
240         return new Promise(resolve => {
241             testRunner.runUIScript(`(function() {
242                 uiController.doAfterNextStablePresentationUpdate(function() {
243                     uiController.uiScriptComplete(JSON.stringify(uiController.selectionCaretViewRect));
244                 });
245             })()`, jsonString => {
246                 resolve(JSON.parse(jsonString));
247             });
248         });
249     }
250
251     static getUISelectionViewRects()
252     {
253         if (!this.isWebKit2() || !this.isIOS())
254             return Promise.resolve();
255
256         return new Promise(resolve => {
257             testRunner.runUIScript(`(function() {
258                 uiController.doAfterNextStablePresentationUpdate(function() {
259                     uiController.uiScriptComplete(JSON.stringify(uiController.selectionRangeViewRects));
260                 });
261             })()`, jsonString => {
262                 resolve(JSON.parse(jsonString));
263             });
264         });
265     }
266
267     static getSelectionStartGrabberViewRect()
268     {
269         if (!this.isWebKit2() || !this.isIOS())
270             return Promise.resolve();
271
272         return new Promise(resolve => {
273             testRunner.runUIScript(`(function() {
274                 uiController.doAfterNextStablePresentationUpdate(function() {
275                     uiController.uiScriptComplete(JSON.stringify(uiController.selectionStartGrabberViewRect));
276                 });
277             })()`, jsonString => {
278                 resolve(JSON.parse(jsonString));
279             });
280         });
281     }
282
283     static getSelectionEndGrabberViewRect()
284     {
285         if (!this.isWebKit2() || !this.isIOS())
286             return Promise.resolve();
287
288         return new Promise(resolve => {
289             testRunner.runUIScript(`(function() {
290                 uiController.doAfterNextStablePresentationUpdate(function() {
291                     uiController.uiScriptComplete(JSON.stringify(uiController.selectionEndGrabberViewRect));
292                 });
293             })()`, jsonString => {
294                 resolve(JSON.parse(jsonString));
295             });
296         });
297     }
298
299     static replaceTextAtRange(text, location, length) {
300         return new Promise(resolve => {
301             testRunner.runUIScript(`(() => {
302                 uiController.replaceTextAtRange("${text}", ${location}, ${length});
303                 uiController.uiScriptComplete();
304             })()`, resolve);
305         });
306     }
307
308     static wait(promise)
309     {
310         testRunner.waitUntilDone();
311         if (window.finishJSTest)
312             window.jsTestIsAsync = true;
313
314         let finish = () => {
315             if (window.finishJSTest)
316                 finishJSTest();
317             else
318                 testRunner.notifyDone();
319         }
320
321         return promise.then(finish, finish);
322     }
323
324     static withUserGesture(callback)
325     {
326         internals.withUserGesture(callback);
327     }
328
329     static selectFormAccessoryPickerRow(rowIndex)
330     {
331         const selectRowScript = `(() => uiController.selectFormAccessoryPickerRow(${rowIndex}))()`;
332         return new Promise(resolve => testRunner.runUIScript(selectRowScript, resolve));
333     }
334
335     static selectFormPopoverTitle()
336     {
337         return new Promise(resolve => {
338             testRunner.runUIScript(`(() => {
339                 uiController.uiScriptComplete(uiController.selectFormPopoverTitle);
340             })()`, resolve);
341         });
342     }
343
344     static enterText(text)
345     {
346         const escapedText = text.replace(/`/g, "\\`");
347         const enterTextScript = `(() => uiController.enterText(\`${escapedText}\`))()`;
348         return new Promise(resolve => testRunner.runUIScript(enterTextScript, resolve));
349     }
350
351     static setTimePickerValue(hours, minutes)
352     {
353         const setValueScript = `(() => uiController.setTimePickerValue(${hours}, ${minutes}))()`;
354         return new Promise(resolve => testRunner.runUIScript(setValueScript, resolve));
355     }
356
357     static setShareSheetCompletesImmediatelyWithResolution(resolved)
358     {
359         const resolveShareSheet = `(() => uiController.setShareSheetCompletesImmediatelyWithResolution(${resolved}))()`;
360         return new Promise(resolve => testRunner.runUIScript(resolveShareSheet, resolve));
361     }
362
363     static textContentType()
364     {
365         return new Promise(resolve => {
366             testRunner.runUIScript(`(() => {
367                 uiController.uiScriptComplete(uiController.textContentType);
368             })()`, resolve);
369         });
370     }
371
372     static formInputLabel()
373     {
374         return new Promise(resolve => {
375             testRunner.runUIScript(`(() => {
376                 uiController.uiScriptComplete(uiController.formInputLabel);
377             })()`, resolve);
378         });
379     }
380
381     static isShowingDataListSuggestions()
382     {
383         return new Promise(resolve => {
384             testRunner.runUIScript(`(() => {
385                 uiController.uiScriptComplete(uiController.isShowingDataListSuggestions);
386             })()`, result => resolve(result === "true" ? true : false));
387         });
388     }
389
390     static zoomScale()
391     {
392         return new Promise(resolve => {
393             testRunner.runUIScript(`(() => {
394                 uiController.uiScriptComplete(uiController.zoomScale);
395             })()`, scaleAsString => resolve(parseFloat(scaleAsString)));
396         });
397     }
398
399     static zoomToScale(scale)
400     {
401         const uiScript = `uiController.zoomToScale(${scale}, () => uiController.uiScriptComplete(uiController.zoomScale))`;
402         return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
403     }
404
405     static typeCharacter(characterString)
406     {
407         if (!this.isWebKit2() || !this.isIOS()) {
408             eventSender.keyDown(characterString);
409             return;
410         }
411
412         const escapedString = characterString.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
413         const uiScript = `uiController.typeCharacterUsingHardwareKeyboard(\`${escapedString}\`, () => uiController.uiScriptComplete())`;
414         return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
415     }
416
417     static applyAutocorrection(newText, oldText)
418     {
419         if (!this.isWebKit2())
420             return;
421
422         const [escapedNewText, escapedOldText] = [newText.replace(/`/g, "\\`"), oldText.replace(/`/g, "\\`")];
423         const uiScript = `uiController.applyAutocorrection(\`${escapedNewText}\`, \`${escapedOldText}\`, () => uiController.uiScriptComplete())`;
424         return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
425     }
426
427     static inputViewBounds()
428     {
429         if (!this.isWebKit2() || !this.isIOS())
430             return Promise.resolve();
431
432         return new Promise(resolve => {
433             testRunner.runUIScript(`(() => {
434                 uiController.uiScriptComplete(JSON.stringify(uiController.inputViewBounds));
435             })()`, jsonString => {
436                 resolve(JSON.parse(jsonString));
437             });
438         });
439     }
440
441     static calendarType()
442     {
443         if (!this.isWebKit2())
444             return Promise.resolve();
445
446         return new Promise(resolve => {
447             testRunner.runUIScript(`(() => {
448                 uiController.doAfterNextStablePresentationUpdate(() => {
449                     uiController.uiScriptComplete(JSON.stringify(uiController.calendarType));
450                 })
451             })()`, jsonString => {
452                 resolve(JSON.parse(jsonString));
453             });
454         });
455     }
456
457     static setDefaultCalendarType(calendarIdentifier)
458     {
459         if (!this.isWebKit2())
460             return Promise.resolve();
461
462         return new Promise(resolve => testRunner.runUIScript(`uiController.setDefaultCalendarType('${calendarIdentifier}')`, resolve));
463
464     }
465
466     static setViewScale(scale)
467     {
468         if (!this.isWebKit2())
469             return Promise.resolve();
470
471         return new Promise(resolve => testRunner.runUIScript(`uiController.setViewScale(${scale})`, resolve));
472     }
473
474     static resignFirstResponder()
475     {
476         if (!this.isWebKit2())
477             return Promise.resolve();
478
479         return new Promise(resolve => testRunner.runUIScript(`uiController.resignFirstResponder()`, resolve));
480     }
481
482     static minimumZoomScale()
483     {
484         if (!this.isWebKit2())
485             return Promise.resolve();
486
487         return new Promise(resolve => {
488             testRunner.runUIScript(`(() => {
489                 uiController.uiScriptComplete(uiController.minimumZoomScale);
490             })()`, scaleAsString => resolve(parseFloat(scaleAsString)))
491         });
492     }
493
494     static drawSquareInEditableImage()
495     {
496         if (!this.isWebKit2())
497             return Promise.resolve();
498
499         return new Promise(resolve => testRunner.runUIScript(`uiController.drawSquareInEditableImage()`, resolve));
500     }
501
502     static stylusTapAt(x, y)
503     {
504         if (!this.isWebKit2())
505             return Promise.resolve();
506
507         return new Promise((resolve) => {
508             testRunner.runUIScript(`
509                 uiController.stylusTapAtPoint(${x}, ${y}, 2, 1, 0.5, function() {
510                     uiController.uiScriptComplete();
511                 });`, resolve);
512         });
513     }
514
515     static numberOfStrokesInEditableImage()
516     {
517         if (!this.isWebKit2())
518             return Promise.resolve();
519
520         return new Promise(resolve => {
521             testRunner.runUIScript(`(() => {
522                 uiController.uiScriptComplete(uiController.numberOfStrokesInEditableImage);
523             })()`, numberAsString => resolve(parseInt(numberAsString, 10)))
524         });
525     }
526
527     static attachmentInfo(attachmentIdentifier)
528     {
529         if (!this.isWebKit2())
530             return Promise.resolve();
531
532         return new Promise(resolve => {
533             testRunner.runUIScript(`(() => {
534                 uiController.uiScriptComplete(JSON.stringify(uiController.attachmentInfo('${attachmentIdentifier}')));
535             })()`, jsonString => {
536                 resolve(JSON.parse(jsonString));
537             })
538         });
539     }
540
541     static setMinimumEffectiveWidth(effectiveWidth)
542     {
543         if (!this.isWebKit2())
544             return Promise.resolve();
545
546         return new Promise(resolve => testRunner.runUIScript(`uiController.setMinimumEffectiveWidth(${effectiveWidth})`, resolve));
547     }
548
549     static setKeyboardInputModeIdentifier(identifier)
550     {
551         if (!this.isWebKit2())
552             return Promise.resolve();
553
554         const escapedIdentifier = identifier.replace(/`/g, "\\`");
555         return new Promise(resolve => testRunner.runUIScript(`uiController.setKeyboardInputModeIdentifier(\`${escapedIdentifier}\`)`, resolve));
556     }
557 }