REGRESSION (iOS 13): Tests that simulate multiple back-to-back single taps fail or...
[WebKit-https.git] / LayoutTests / resources / ui-helper.js
1
2 window.UIHelper = class UIHelper {
3     static isIOSFamily()
4     {
5         return testRunner.isIOSFamily;
6     }
7
8     static isWebKit2()
9     {
10         return testRunner.isWebKit2;
11     }
12
13     static doubleClickAt(x, y)
14     {
15         eventSender.mouseMoveTo(x, y);
16         eventSender.mouseDown();
17         eventSender.mouseUp();
18         eventSender.mouseDown();
19         eventSender.mouseUp();
20     }
21
22     static doubleClickAtThenDragTo(x1, y1, x2, y2)
23     {
24         eventSender.mouseMoveTo(x1, y1);
25         eventSender.mouseDown();
26         eventSender.mouseUp();
27         eventSender.mouseDown();
28         eventSender.mouseMoveTo(x2, y2);
29         eventSender.mouseUp();
30     }
31
32     static tapAt(x, y, modifiers=[])
33     {
34         console.assert(this.isIOSFamily());
35
36         if (!this.isWebKit2()) {
37             console.assert(!modifiers || !modifiers.length);
38             eventSender.addTouchPoint(x, y);
39             eventSender.touchStart();
40             eventSender.releaseTouchPoint(0);
41             eventSender.touchEnd();
42             return Promise.resolve();
43         }
44
45         return new Promise((resolve) => {
46             testRunner.runUIScript(`
47                 uiController.singleTapAtPointWithModifiers(${x}, ${y}, ${JSON.stringify(modifiers)}, function() {
48                     uiController.uiScriptComplete();
49                 });`, resolve);
50         });
51     }
52
53     static doubleTapAt(x, y, delay = 0)
54     {
55         console.assert(this.isIOSFamily());
56
57         if (!this.isWebKit2()) {
58             eventSender.addTouchPoint(x, y);
59             eventSender.touchStart();
60             eventSender.releaseTouchPoint(0);
61             eventSender.touchEnd();
62             eventSender.addTouchPoint(x, y);
63             eventSender.touchStart();
64             eventSender.releaseTouchPoint(0);
65             eventSender.touchEnd();
66             return Promise.resolve();
67         }
68
69         return new Promise((resolve) => {
70             testRunner.runUIScript(`
71                 uiController.doubleTapAtPoint(${x}, ${y}, ${delay}, function() {
72                     uiController.uiScriptComplete();
73                 });`, resolve);
74         });
75     }
76
77     static humanSpeedDoubleTapAt(x, y)
78     {
79         console.assert(this.isIOSFamily());
80
81         if (!this.isWebKit2()) {
82             // FIXME: Add a sleep in here.
83             eventSender.addTouchPoint(x, y);
84             eventSender.touchStart();
85             eventSender.releaseTouchPoint(0);
86             eventSender.touchEnd();
87             eventSender.addTouchPoint(x, y);
88             eventSender.touchStart();
89             eventSender.releaseTouchPoint(0);
90             eventSender.touchEnd();
91             return Promise.resolve();
92         }
93
94         return UIHelper.doubleTapAt(x, y, 0.12);
95     }
96
97     static humanSpeedZoomByDoubleTappingAt(x, y)
98     {
99         console.assert(this.isIOSFamily());
100
101         if (!this.isWebKit2()) {
102             // FIXME: Add a sleep in here.
103             eventSender.addTouchPoint(x, y);
104             eventSender.touchStart();
105             eventSender.releaseTouchPoint(0);
106             eventSender.touchEnd();
107             eventSender.addTouchPoint(x, y);
108             eventSender.touchStart();
109             eventSender.releaseTouchPoint(0);
110             eventSender.touchEnd();
111             return Promise.resolve();
112         }
113
114         return new Promise(async (resolve) => {
115             testRunner.runUIScript(`
116                 uiController.didEndZoomingCallback = () => {
117                     uiController.didEndZoomingCallback = null;
118                     uiController.uiScriptComplete(uiController.zoomScale);
119                 };
120                 uiController.doubleTapAtPoint(${x}, ${y}, 0.12, () => { });`, resolve);
121         });
122     }
123
124     static zoomByDoubleTappingAt(x, y)
125     {
126         console.assert(this.isIOSFamily());
127
128         if (!this.isWebKit2()) {
129             eventSender.addTouchPoint(x, y);
130             eventSender.touchStart();
131             eventSender.releaseTouchPoint(0);
132             eventSender.touchEnd();
133             eventSender.addTouchPoint(x, y);
134             eventSender.touchStart();
135             eventSender.releaseTouchPoint(0);
136             eventSender.touchEnd();
137             return Promise.resolve();
138         }
139
140         return new Promise((resolve) => {
141             testRunner.runUIScript(`
142                 uiController.didEndZoomingCallback = () => {
143                     uiController.didEndZoomingCallback = null;
144                     uiController.uiScriptComplete(uiController.zoomScale);
145                 };
146                 uiController.doubleTapAtPoint(${x}, ${y}, 0, () => { });`, resolve);
147         });
148     }
149
150     static activateAt(x, y)
151     {
152         if (!this.isWebKit2() || !this.isIOSFamily()) {
153             eventSender.mouseMoveTo(x, y);
154             eventSender.mouseDown();
155             eventSender.mouseUp();
156             return Promise.resolve();
157         }
158
159         return new Promise((resolve) => {
160             testRunner.runUIScript(`
161                 uiController.singleTapAtPoint(${x}, ${y}, function() {
162                     uiController.uiScriptComplete();
163                 });`, resolve);
164         });
165     }
166
167     static activateElement(element)
168     {
169         const x = element.offsetLeft + element.offsetWidth / 2;
170         const y = element.offsetTop + element.offsetHeight / 2;
171         return UIHelper.activateAt(x, y);
172     }
173
174     static activateElementAtHumanSpeed(element)
175     {
176         const x = element.offsetLeft + element.offsetWidth / 2;
177         const y = element.offsetTop + element.offsetHeight / 2;
178
179         if (!this.isWebKit2() || !this.isIOSFamily()) {
180             eventSender.mouseMoveTo(x, y);
181             eventSender.mouseDown();
182             eventSender.mouseUp();
183             return Promise.resolve();
184         }
185
186         return new Promise(async (resolve) => {
187             await new Promise(resolveAfterDelay => setTimeout(resolveAfterDelay, 350));
188             testRunner.runUIScript(`
189                 uiController.singleTapAtPoint(${x}, ${y}, function() {
190                     uiController.uiScriptComplete();
191                 });`, resolve);
192         });
193     }
194
195     static async doubleActivateAt(x, y)
196     {
197         if (this.isIOSFamily())
198             await UIHelper.doubleTapAt(x, y);
199         else
200             await UIHelper.doubleClickAt(x, y);
201     }
202
203     static async doubleActivateAtSelectionStart()
204     {
205         const rects = window.getSelection().getRangeAt(0).getClientRects();
206         const x = rects[0].left;
207         const y = rects[0].top;
208         if (this.isIOSFamily()) {
209             await UIHelper.activateAndWaitForInputSessionAt(x, y);
210             await UIHelper.doubleTapAt(x, y);
211             // This is only here to deal with async/sync copy/paste calls, so
212             // once <rdar://problem/16207002> is resolved, should be able to remove for faster tests.
213             await new Promise(resolve => testRunner.runUIScript("uiController.uiScriptComplete()", resolve));
214         } else
215             await UIHelper.doubleClickAt(x, y);
216     }
217
218     static async selectWordByDoubleTapOrClick(element, relativeX = 5, relativeY = 5)
219     {
220         const boundingRect = element.getBoundingClientRect();
221         const x = boundingRect.x + relativeX;
222         const y = boundingRect.y + relativeY;
223         if (this.isIOSFamily()) {
224             await UIHelper.activateAndWaitForInputSessionAt(x, y);
225             await UIHelper.doubleTapAt(x, y);
226             // This is only here to deal with async/sync copy/paste calls, so
227             // once <rdar://problem/16207002> is resolved, should be able to remove for faster tests.
228             await new Promise(resolve => testRunner.runUIScript("uiController.uiScriptComplete()", resolve));
229         } else {
230             await UIHelper.doubleClickAt(x, y);
231         }
232     }
233
234     static keyDown(key, modifiers=[])
235     {
236         if (!this.isWebKit2() || !this.isIOSFamily()) {
237             eventSender.keyDown(key, modifiers);
238             return Promise.resolve();
239         }
240
241         return new Promise((resolve) => {
242             testRunner.runUIScript(`uiController.keyDown("${key}", ${JSON.stringify(modifiers)});`, resolve);
243         });
244     }
245
246     static toggleCapsLock()
247     {
248         return new Promise((resolve) => {
249             testRunner.runUIScript(`uiController.toggleCapsLock(() => uiController.uiScriptComplete());`, resolve);
250         });
251     }
252
253     static ensurePresentationUpdate()
254     {
255         if (!this.isWebKit2()) {
256             testRunner.display();
257             return Promise.resolve();
258         }
259
260         return new Promise(resolve => {
261             testRunner.runUIScript(`
262                 uiController.doAfterPresentationUpdate(function() {
263                     uiController.uiScriptComplete();
264                 });`, resolve);
265         });
266     }
267
268     static ensureStablePresentationUpdate()
269     {
270         if (!this.isWebKit2()) {
271             testRunner.display();
272             return Promise.resolve();
273         }
274
275         return new Promise(resolve => {
276             testRunner.runUIScript(`
277                 uiController.doAfterNextStablePresentationUpdate(function() {
278                     uiController.uiScriptComplete();
279                 });`, resolve);
280         });
281     }
282
283     static ensurePositionInformationUpdateForElement(element)
284     {
285         const boundingRect = element.getBoundingClientRect();
286         const x = boundingRect.x + 5;
287         const y = boundingRect.y + 5;
288
289         if (!this.isWebKit2()) {
290             testRunner.display();
291             return Promise.resolve();
292         }
293
294         return new Promise(resolve => {
295             testRunner.runUIScript(`
296                 uiController.ensurePositionInformationIsUpToDateAt(${x}, ${y}, function () {
297                     uiController.uiScriptComplete();
298                 });`, resolve);
299         });
300     }
301
302     static delayFor(ms)
303     {
304         return new Promise(resolve => setTimeout(resolve, ms));
305     }
306     
307     static immediateScrollTo(x, y)
308     {
309         if (!this.isWebKit2()) {
310             window.scrollTo(x, y);
311             return Promise.resolve();
312         }
313
314         return new Promise(resolve => {
315             testRunner.runUIScript(`
316                 uiController.immediateScrollToOffset(${x}, ${y});`, resolve);
317         });
318     }
319
320     static immediateUnstableScrollTo(x, y)
321     {
322         if (!this.isWebKit2()) {
323             window.scrollTo(x, y);
324             return Promise.resolve();
325         }
326
327         return new Promise(resolve => {
328             testRunner.runUIScript(`
329                 uiController.stableStateOverride = false;
330                 uiController.immediateScrollToOffset(${x}, ${y});`, resolve);
331         });
332     }
333
334     static immediateScrollElementAtContentPointToOffset(x, y, scrollX, scrollY, scrollUpdatesDisabled = false)
335     {
336         if (!this.isWebKit2())
337             return Promise.resolve();
338
339         return new Promise(resolve => {
340             testRunner.runUIScript(`
341                 uiController.scrollUpdatesDisabled = ${scrollUpdatesDisabled};
342                 uiController.immediateScrollElementAtContentPointToOffset(${x}, ${y}, ${scrollX}, ${scrollY});`, resolve);
343         });
344     }
345
346     static ensureVisibleContentRectUpdate()
347     {
348         if (!this.isWebKit2())
349             return Promise.resolve();
350
351         return new Promise(resolve => {
352             const visibleContentRectUpdateScript = "uiController.doAfterVisibleContentRectUpdate(() => uiController.uiScriptComplete())";
353             testRunner.runUIScript(visibleContentRectUpdateScript, resolve);
354         });
355     }
356
357     static activateAndWaitForInputSessionAt(x, y)
358     {
359         if (!this.isWebKit2() || !this.isIOSFamily())
360             return this.activateAt(x, y);
361
362         return new Promise(resolve => {
363             testRunner.runUIScript(`
364                 (function() {
365                     uiController.didShowKeyboardCallback = function() {
366                         uiController.uiScriptComplete();
367                     };
368                     uiController.singleTapAtPoint(${x}, ${y}, function() { });
369                 })()`, resolve);
370         });
371     }
372
373     static activateElementAndWaitForInputSession(element)
374     {
375         const x = element.offsetLeft + element.offsetWidth / 2;
376         const y = element.offsetTop + element.offsetHeight / 2;
377         return this.activateAndWaitForInputSessionAt(x, y);
378     }
379
380     static activateFormControl(element)
381     {
382         if (!this.isWebKit2() || !this.isIOSFamily())
383             return this.activateElement(element);
384
385         const x = element.offsetLeft + element.offsetWidth / 2;
386         const y = element.offsetTop + element.offsetHeight / 2;
387
388         return new Promise(resolve => {
389             testRunner.runUIScript(`
390                 (function() {
391                     uiController.didStartFormControlInteractionCallback = function() {
392                         uiController.uiScriptComplete();
393                     };
394                     uiController.singleTapAtPoint(${x}, ${y}, function() { });
395                 })()`, resolve);
396         });
397     }
398
399     static dismissFormAccessoryView()
400     {
401         if (!this.isWebKit2() || !this.isIOSFamily())
402             return Promise.resolve();
403
404         return new Promise(resolve => {
405             testRunner.runUIScript(`
406                 (function() {
407                     uiController.dismissFormAccessoryView();
408                     uiController.uiScriptComplete();
409                 })()`, resolve);
410         });
411     }
412
413     static isShowingKeyboard()
414     {
415         return new Promise(resolve => {
416             testRunner.runUIScript("uiController.isShowingKeyboard", result => resolve(result === "true"));
417         });
418     }
419
420     static hasInputSession()
421     {
422         return new Promise(resolve => {
423             testRunner.runUIScript("uiController.hasInputSession", result => resolve(result === "true"));
424         });
425     }
426
427     static isPresentingModally()
428     {
429         return new Promise(resolve => {
430             testRunner.runUIScript("uiController.isPresentingModally", result => resolve(result === "true"));
431         });
432     }
433
434     static deactivateFormControl(element)
435     {
436         if (!this.isWebKit2() || !this.isIOSFamily()) {
437             element.blur();
438             return Promise.resolve();
439         }
440
441         return new Promise(async resolve => {
442             element.blur();
443             while (await this.isPresentingModally())
444                 continue;
445             while (await this.isShowingKeyboard())
446                 continue;
447             resolve();
448         });
449     }
450
451     static waitForPopoverToPresent()
452     {
453         if (!this.isWebKit2() || !this.isIOSFamily())
454             return Promise.resolve();
455
456         return new Promise(resolve => {
457             testRunner.runUIScript(`
458                 (function() {
459                     if (uiController.isShowingPopover)
460                         uiController.uiScriptComplete();
461                     else
462                         uiController.willPresentPopoverCallback = () => uiController.uiScriptComplete();
463                 })()`, resolve);
464         });
465     }
466
467     static waitForPopoverToDismiss()
468     {
469         if (!this.isWebKit2() || !this.isIOSFamily())
470             return Promise.resolve();
471
472         return new Promise(resolve => {
473             testRunner.runUIScript(`
474                 (function() {
475                     if (uiController.isShowingPopover)
476                         uiController.didDismissPopoverCallback = () => uiController.uiScriptComplete();
477                     else
478                         uiController.uiScriptComplete();
479                 })()`, resolve);
480         });
481     }
482
483     static waitForKeyboardToHide()
484     {
485         if (!this.isWebKit2() || !this.isIOSFamily())
486             return Promise.resolve();
487
488         return new Promise(resolve => {
489             testRunner.runUIScript(`
490                 (function() {
491                     if (uiController.isShowingKeyboard)
492                         uiController.didHideKeyboardCallback = () => uiController.uiScriptComplete();
493                     else
494                         uiController.uiScriptComplete();
495                 })()`, resolve);
496         });
497     }
498
499     static getUICaretRect()
500     {
501         if (!this.isWebKit2() || !this.isIOSFamily())
502             return Promise.resolve();
503
504         return new Promise(resolve => {
505             testRunner.runUIScript(`(function() {
506                 uiController.doAfterNextStablePresentationUpdate(function() {
507                     uiController.uiScriptComplete(JSON.stringify(uiController.textSelectionCaretRect));
508                 });
509             })()`, jsonString => {
510                 resolve(JSON.parse(jsonString));
511             });
512         });
513     }
514
515     static getUISelectionRects()
516     {
517         if (!this.isWebKit2() || !this.isIOSFamily())
518             return Promise.resolve();
519
520         return new Promise(resolve => {
521             testRunner.runUIScript(`(function() {
522                 uiController.doAfterNextStablePresentationUpdate(function() {
523                     uiController.uiScriptComplete(JSON.stringify(uiController.textSelectionRangeRects));
524                 });
525             })()`, jsonString => {
526                 resolve(JSON.parse(jsonString));
527             });
528         });
529     }
530
531     static getUICaretViewRect()
532     {
533         if (!this.isWebKit2() || !this.isIOSFamily())
534             return Promise.resolve();
535
536         return new Promise(resolve => {
537             testRunner.runUIScript(`(function() {
538                 uiController.doAfterNextStablePresentationUpdate(function() {
539                     uiController.uiScriptComplete(JSON.stringify(uiController.selectionCaretViewRect));
540                 });
541             })()`, jsonString => {
542                 resolve(JSON.parse(jsonString));
543             });
544         });
545     }
546
547     static getUISelectionViewRects()
548     {
549         if (!this.isWebKit2() || !this.isIOSFamily())
550             return Promise.resolve();
551
552         return new Promise(resolve => {
553             testRunner.runUIScript(`(function() {
554                 uiController.doAfterNextStablePresentationUpdate(function() {
555                     uiController.uiScriptComplete(JSON.stringify(uiController.selectionRangeViewRects));
556                 });
557             })()`, jsonString => {
558                 resolve(JSON.parse(jsonString));
559             });
560         });
561     }
562
563     static getSelectionStartGrabberViewRect()
564     {
565         if (!this.isWebKit2() || !this.isIOSFamily())
566             return Promise.resolve();
567
568         return new Promise(resolve => {
569             testRunner.runUIScript(`(function() {
570                 uiController.doAfterNextStablePresentationUpdate(function() {
571                     uiController.uiScriptComplete(JSON.stringify(uiController.selectionStartGrabberViewRect));
572                 });
573             })()`, jsonString => {
574                 resolve(JSON.parse(jsonString));
575             });
576         });
577     }
578
579     static getSelectionEndGrabberViewRect()
580     {
581         if (!this.isWebKit2() || !this.isIOSFamily())
582             return Promise.resolve();
583
584         return new Promise(resolve => {
585             testRunner.runUIScript(`(function() {
586                 uiController.doAfterNextStablePresentationUpdate(function() {
587                     uiController.uiScriptComplete(JSON.stringify(uiController.selectionEndGrabberViewRect));
588                 });
589             })()`, jsonString => {
590                 resolve(JSON.parse(jsonString));
591             });
592         });
593     }
594
595     static replaceTextAtRange(text, location, length) {
596         return new Promise(resolve => {
597             testRunner.runUIScript(`(() => {
598                 uiController.replaceTextAtRange("${text}", ${location}, ${length});
599                 uiController.uiScriptComplete();
600             })()`, resolve);
601         });
602     }
603
604     static wait(promise)
605     {
606         testRunner.waitUntilDone();
607         if (window.finishJSTest)
608             window.jsTestIsAsync = true;
609
610         let finish = () => {
611             if (window.finishJSTest)
612                 finishJSTest();
613             else
614                 testRunner.notifyDone();
615         }
616
617         return promise.then(finish, finish);
618     }
619
620     static withUserGesture(callback)
621     {
622         internals.withUserGesture(callback);
623     }
624
625     static selectFormAccessoryPickerRow(rowIndex)
626     {
627         const selectRowScript = `(() => uiController.selectFormAccessoryPickerRow(${rowIndex}))()`;
628         return new Promise(resolve => testRunner.runUIScript(selectRowScript, resolve));
629     }
630
631     static selectFormPopoverTitle()
632     {
633         return new Promise(resolve => {
634             testRunner.runUIScript(`(() => {
635                 uiController.uiScriptComplete(uiController.selectFormPopoverTitle);
636             })()`, resolve);
637         });
638     }
639
640     static enterText(text)
641     {
642         const escapedText = text.replace(/`/g, "\\`");
643         const enterTextScript = `(() => uiController.enterText(\`${escapedText}\`))()`;
644         return new Promise(resolve => testRunner.runUIScript(enterTextScript, resolve));
645     }
646
647     static setTimePickerValue(hours, minutes)
648     {
649         const setValueScript = `(() => uiController.setTimePickerValue(${hours}, ${minutes}))()`;
650         return new Promise(resolve => testRunner.runUIScript(setValueScript, resolve));
651     }
652
653     static setShareSheetCompletesImmediatelyWithResolution(resolved)
654     {
655         const resolveShareSheet = `(() => uiController.setShareSheetCompletesImmediatelyWithResolution(${resolved}))()`;
656         return new Promise(resolve => testRunner.runUIScript(resolveShareSheet, resolve));
657     }
658
659     static textContentType()
660     {
661         return new Promise(resolve => {
662             testRunner.runUIScript(`(() => {
663                 uiController.uiScriptComplete(uiController.textContentType);
664             })()`, resolve);
665         });
666     }
667
668     static formInputLabel()
669     {
670         return new Promise(resolve => {
671             testRunner.runUIScript(`(() => {
672                 uiController.uiScriptComplete(uiController.formInputLabel);
673             })()`, resolve);
674         });
675     }
676
677     static isShowingDataListSuggestions()
678     {
679         return new Promise(resolve => {
680             testRunner.runUIScript(`(() => {
681                 uiController.uiScriptComplete(uiController.isShowingDataListSuggestions);
682             })()`, result => resolve(result === "true"));
683         });
684     }
685
686     static zoomScale()
687     {
688         return new Promise(resolve => {
689             testRunner.runUIScript(`(() => {
690                 uiController.uiScriptComplete(uiController.zoomScale);
691             })()`, scaleAsString => resolve(parseFloat(scaleAsString)));
692         });
693     }
694
695     static zoomToScale(scale)
696     {
697         const uiScript = `uiController.zoomToScale(${scale}, () => uiController.uiScriptComplete(uiController.zoomScale))`;
698         return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
699     }
700
701     static immediateZoomToScale(scale)
702     {
703         const uiScript = `uiController.immediateZoomToScale(${scale})`;
704         return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
705     }
706
707     static typeCharacter(characterString)
708     {
709         if (!this.isWebKit2() || !this.isIOSFamily()) {
710             eventSender.keyDown(characterString);
711             return;
712         }
713
714         const escapedString = characterString.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
715         const uiScript = `uiController.typeCharacterUsingHardwareKeyboard(\`${escapedString}\`, () => uiController.uiScriptComplete())`;
716         return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
717     }
718
719     static applyAutocorrection(newText, oldText)
720     {
721         if (!this.isWebKit2())
722             return;
723
724         const [escapedNewText, escapedOldText] = [newText.replace(/`/g, "\\`"), oldText.replace(/`/g, "\\`")];
725         const uiScript = `uiController.applyAutocorrection(\`${escapedNewText}\`, \`${escapedOldText}\`, () => uiController.uiScriptComplete())`;
726         return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
727     }
728
729     static inputViewBounds()
730     {
731         if (!this.isWebKit2() || !this.isIOSFamily())
732             return Promise.resolve();
733
734         return new Promise(resolve => {
735             testRunner.runUIScript(`(() => {
736                 uiController.uiScriptComplete(JSON.stringify(uiController.inputViewBounds));
737             })()`, jsonString => {
738                 resolve(JSON.parse(jsonString));
739             });
740         });
741     }
742
743     static calendarType()
744     {
745         if (!this.isWebKit2())
746             return Promise.resolve();
747
748         return new Promise(resolve => {
749             testRunner.runUIScript(`(() => {
750                 uiController.doAfterNextStablePresentationUpdate(() => {
751                     uiController.uiScriptComplete(JSON.stringify(uiController.calendarType));
752                 })
753             })()`, jsonString => {
754                 resolve(JSON.parse(jsonString));
755             });
756         });
757     }
758
759     static setDefaultCalendarType(calendarIdentifier)
760     {
761         if (!this.isWebKit2())
762             return Promise.resolve();
763
764         return new Promise(resolve => testRunner.runUIScript(`uiController.setDefaultCalendarType('${calendarIdentifier}')`, resolve));
765
766     }
767
768     static setViewScale(scale)
769     {
770         if (!this.isWebKit2())
771             return Promise.resolve();
772
773         return new Promise(resolve => testRunner.runUIScript(`uiController.setViewScale(${scale})`, resolve));
774     }
775
776     static resignFirstResponder()
777     {
778         if (!this.isWebKit2())
779             return Promise.resolve();
780
781         return new Promise(resolve => testRunner.runUIScript(`uiController.resignFirstResponder()`, resolve));
782     }
783
784     static minimumZoomScale()
785     {
786         if (!this.isWebKit2())
787             return Promise.resolve();
788
789         return new Promise(resolve => {
790             testRunner.runUIScript(`(() => {
791                 uiController.uiScriptComplete(uiController.minimumZoomScale);
792             })()`, scaleAsString => resolve(parseFloat(scaleAsString)))
793         });
794     }
795
796     static drawSquareInEditableImage()
797     {
798         if (!this.isWebKit2())
799             return Promise.resolve();
800
801         return new Promise(resolve => testRunner.runUIScript(`uiController.drawSquareInEditableImage()`, resolve));
802     }
803
804     static stylusTapAt(x, y, modifiers=[])
805     {
806         if (!this.isWebKit2())
807             return Promise.resolve();
808
809         return new Promise((resolve) => {
810             testRunner.runUIScript(`
811                 uiController.stylusTapAtPointWithModifiers(${x}, ${y}, 2, 1, 0.5, ${JSON.stringify(modifiers)}, function() {
812                     uiController.uiScriptComplete();
813                 });`, resolve);
814         });
815     }
816
817     static numberOfStrokesInEditableImage()
818     {
819         if (!this.isWebKit2())
820             return Promise.resolve();
821
822         return new Promise(resolve => {
823             testRunner.runUIScript(`(() => {
824                 uiController.uiScriptComplete(uiController.numberOfStrokesInEditableImage);
825             })()`, numberAsString => resolve(parseInt(numberAsString, 10)))
826         });
827     }
828
829     static attachmentInfo(attachmentIdentifier)
830     {
831         if (!this.isWebKit2())
832             return Promise.resolve();
833
834         return new Promise(resolve => {
835             testRunner.runUIScript(`(() => {
836                 uiController.uiScriptComplete(JSON.stringify(uiController.attachmentInfo('${attachmentIdentifier}')));
837             })()`, jsonString => {
838                 resolve(JSON.parse(jsonString));
839             })
840         });
841     }
842
843     static setMinimumEffectiveWidth(effectiveWidth)
844     {
845         if (!this.isWebKit2())
846             return Promise.resolve();
847
848         return new Promise(resolve => testRunner.runUIScript(`uiController.setMinimumEffectiveWidth(${effectiveWidth})`, resolve));
849     }
850
851     static setAllowsViewportShrinkToFit(allows)
852     {
853         if (!this.isWebKit2())
854             return Promise.resolve();
855
856         return new Promise(resolve => testRunner.runUIScript(`uiController.setAllowsViewportShrinkToFit(${allows})`, resolve));
857     }
858
859     static setKeyboardInputModeIdentifier(identifier)
860     {
861         if (!this.isWebKit2())
862             return Promise.resolve();
863
864         const escapedIdentifier = identifier.replace(/`/g, "\\`");
865         return new Promise(resolve => testRunner.runUIScript(`uiController.setKeyboardInputModeIdentifier(\`${escapedIdentifier}\`)`, resolve));
866     }
867
868     static contentOffset()
869     {
870         if (!this.isIOSFamily())
871             return Promise.resolve();
872
873         const uiScript = "JSON.stringify([uiController.contentOffsetX, uiController.contentOffsetY])";
874         return new Promise(resolve => testRunner.runUIScript(uiScript, result => {
875             const [offsetX, offsetY] = JSON.parse(result)
876             resolve({ x: offsetX, y: offsetY });
877         }));
878     }
879
880     static undoAndRedoLabels()
881     {
882         if (!this.isWebKit2())
883             return Promise.resolve();
884
885         const script = "JSON.stringify([uiController.lastUndoLabel, uiController.firstRedoLabel])";
886         return new Promise(resolve => testRunner.runUIScript(script, result => resolve(JSON.parse(result))));
887     }
888
889     static waitForMenuToShow()
890     {
891         return new Promise(resolve => {
892             testRunner.runUIScript(`
893                 (function() {
894                     if (!uiController.isShowingMenu)
895                         uiController.didShowMenuCallback = () => uiController.uiScriptComplete();
896                     else
897                         uiController.uiScriptComplete();
898                 })()`, resolve);
899         });
900     }
901
902     static waitForMenuToHide()
903     {
904         return new Promise(resolve => {
905             testRunner.runUIScript(`
906                 (function() {
907                     if (uiController.isShowingMenu)
908                         uiController.didHideMenuCallback = () => uiController.uiScriptComplete();
909                     else
910                         uiController.uiScriptComplete();
911                 })()`, resolve);
912         });
913     }
914
915     static isShowingMenu()
916     {
917         return new Promise(resolve => {
918             testRunner.runUIScript(`uiController.isShowingMenu`, result => resolve(result === "true"));
919         });
920     }
921
922     static isDismissingMenu()
923     {
924         return new Promise(resolve => {
925             testRunner.runUIScript(`uiController.isDismissingMenu`, result => resolve(result === "true"));
926         });
927     }
928
929     static menuRect()
930     {
931         return new Promise(resolve => {
932             testRunner.runUIScript("JSON.stringify(uiController.menuRect)", result => resolve(JSON.parse(result)));
933         });
934     }
935
936     static setHardwareKeyboardAttached(attached)
937     {
938         return new Promise(resolve => testRunner.runUIScript(`uiController.setHardwareKeyboardAttached(${attached ? "true" : "false"})`, resolve));
939     }
940
941     static rectForMenuAction(action)
942     {
943         return new Promise(resolve => {
944             testRunner.runUIScript(`
945                 const rect = uiController.rectForMenuAction("${action}");
946                 uiController.uiScriptComplete(rect ? JSON.stringify(rect) : "");
947             `, stringResult => {
948                 resolve(stringResult.length ? JSON.parse(stringResult) : null);
949             });
950         });
951     }
952
953     static async chooseMenuAction(action)
954     {
955         const menuRect = await this.rectForMenuAction(action);
956         if (menuRect)
957             await this.activateAt(menuRect.left + menuRect.width / 2, menuRect.top + menuRect.height / 2);
958     }
959
960     static callFunctionAndWaitForEvent(functionToCall, target, eventName)
961     {
962         return new Promise((resolve) => {
963             target.addEventListener(eventName, resolve, { once: true });
964             functionToCall();
965         });
966
967     }
968
969     static callFunctionAndWaitForScrollToFinish(functionToCall, ...theArguments)
970     {
971         return new Promise((resolved) => {
972             function scrollDidFinish() {
973                 window.removeEventListener("scroll", handleScroll, true);
974                 resolved();
975             }
976
977             let lastScrollTimerId = 0; // When the timer with this id fires then the page has finished scrolling.
978             function handleScroll() {
979                 if (lastScrollTimerId) {
980                     window.clearTimeout(lastScrollTimerId);
981                     lastScrollTimerId = 0;
982                 }
983                 lastScrollTimerId = window.setTimeout(scrollDidFinish, 300); // Over 250ms to give some room for error.
984             }
985             window.addEventListener("scroll", handleScroll, true);
986
987             functionToCall.apply(this, theArguments);
988         });
989     }
990
991     static rotateDevice(orientationName, animatedResize = false)
992     {
993         if (!this.isWebKit2() || !this.isIOSFamily())
994             return Promise.resolve();
995
996         return new Promise(resolve => {
997             testRunner.runUIScript(`(() => {
998                 uiController.${animatedResize ? "simulateRotationLikeSafari" : "simulateRotation"}("${orientationName}", function() {
999                     uiController.doAfterVisibleContentRectUpdate(() => uiController.uiScriptComplete());
1000                 });
1001             })()`, resolve);
1002         });
1003     }
1004
1005     static getScrollingTree()
1006     {
1007         if (!this.isWebKit2() || !this.isIOSFamily())
1008             return Promise.resolve();
1009
1010         return new Promise(resolve => {
1011             testRunner.runUIScript(`(() => {
1012                 return uiController.scrollingTreeAsText;
1013             })()`, resolve);
1014         });
1015     }
1016
1017     static dragFromPointToPoint(fromX, fromY, toX, toY, duration)
1018     {
1019         if (!this.isWebKit2() || !this.isIOSFamily()) {
1020             eventSender.mouseMoveTo(fromX, fromY);
1021             eventSender.mouseDown();
1022             eventSender.leapForward(duration * 1000);
1023             eventSender.mouseMoveTo(toX, toY);
1024             eventSender.mouseUp();
1025             return Promise.resolve();
1026         }
1027
1028         return new Promise(resolve => {
1029             testRunner.runUIScript(`(() => {
1030                 uiController.dragFromPointToPoint(${fromX}, ${fromY}, ${toX}, ${toY}, ${duration}, () => {
1031                     uiController.uiScriptComplete("");
1032                 });
1033             })();`, resolve);
1034         });
1035     }
1036 }