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