f18e4c951c99eed939de99d2bb4efb03367a664e
[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 typeCharacter(characterString)
712     {
713         if (!this.isWebKit2() || !this.isIOSFamily()) {
714             eventSender.keyDown(characterString);
715             return;
716         }
717
718         const escapedString = characterString.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
719         const uiScript = `uiController.typeCharacterUsingHardwareKeyboard(\`${escapedString}\`, () => uiController.uiScriptComplete())`;
720         return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
721     }
722
723     static applyAutocorrection(newText, oldText)
724     {
725         if (!this.isWebKit2())
726             return;
727
728         const [escapedNewText, escapedOldText] = [newText.replace(/`/g, "\\`"), oldText.replace(/`/g, "\\`")];
729         const uiScript = `uiController.applyAutocorrection(\`${escapedNewText}\`, \`${escapedOldText}\`, () => uiController.uiScriptComplete())`;
730         return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
731     }
732
733     static inputViewBounds()
734     {
735         if (!this.isWebKit2() || !this.isIOSFamily())
736             return Promise.resolve();
737
738         return new Promise(resolve => {
739             testRunner.runUIScript(`(() => {
740                 uiController.uiScriptComplete(JSON.stringify(uiController.inputViewBounds));
741             })()`, jsonString => {
742                 resolve(JSON.parse(jsonString));
743             });
744         });
745     }
746
747     static calendarType()
748     {
749         if (!this.isWebKit2())
750             return Promise.resolve();
751
752         return new Promise(resolve => {
753             testRunner.runUIScript(`(() => {
754                 uiController.doAfterNextStablePresentationUpdate(() => {
755                     uiController.uiScriptComplete(JSON.stringify(uiController.calendarType));
756                 })
757             })()`, jsonString => {
758                 resolve(JSON.parse(jsonString));
759             });
760         });
761     }
762
763     static setDefaultCalendarType(calendarIdentifier)
764     {
765         if (!this.isWebKit2())
766             return Promise.resolve();
767
768         return new Promise(resolve => testRunner.runUIScript(`uiController.setDefaultCalendarType('${calendarIdentifier}')`, resolve));
769
770     }
771
772     static setViewScale(scale)
773     {
774         if (!this.isWebKit2())
775             return Promise.resolve();
776
777         return new Promise(resolve => testRunner.runUIScript(`uiController.setViewScale(${scale})`, resolve));
778     }
779
780     static resignFirstResponder()
781     {
782         if (!this.isWebKit2())
783             return Promise.resolve();
784
785         return new Promise(resolve => testRunner.runUIScript(`uiController.resignFirstResponder()`, resolve));
786     }
787
788     static minimumZoomScale()
789     {
790         if (!this.isWebKit2())
791             return Promise.resolve();
792
793         return new Promise(resolve => {
794             testRunner.runUIScript(`(() => {
795                 uiController.uiScriptComplete(uiController.minimumZoomScale);
796             })()`, scaleAsString => resolve(parseFloat(scaleAsString)))
797         });
798     }
799
800     static drawSquareInEditableImage()
801     {
802         if (!this.isWebKit2())
803             return Promise.resolve();
804
805         return new Promise(resolve => testRunner.runUIScript(`uiController.drawSquareInEditableImage()`, resolve));
806     }
807
808     static stylusTapAt(x, y, modifiers=[])
809     {
810         if (!this.isWebKit2())
811             return Promise.resolve();
812
813         return new Promise((resolve) => {
814             testRunner.runUIScript(`
815                 uiController.stylusTapAtPointWithModifiers(${x}, ${y}, 2, 1, 0.5, ${JSON.stringify(modifiers)}, function() {
816                     uiController.uiScriptComplete();
817                 });`, resolve);
818         });
819     }
820
821     static numberOfStrokesInEditableImage()
822     {
823         if (!this.isWebKit2())
824             return Promise.resolve();
825
826         return new Promise(resolve => {
827             testRunner.runUIScript(`(() => {
828                 uiController.uiScriptComplete(uiController.numberOfStrokesInEditableImage);
829             })()`, numberAsString => resolve(parseInt(numberAsString, 10)))
830         });
831     }
832
833     static attachmentInfo(attachmentIdentifier)
834     {
835         if (!this.isWebKit2())
836             return Promise.resolve();
837
838         return new Promise(resolve => {
839             testRunner.runUIScript(`(() => {
840                 uiController.uiScriptComplete(JSON.stringify(uiController.attachmentInfo('${attachmentIdentifier}')));
841             })()`, jsonString => {
842                 resolve(JSON.parse(jsonString));
843             })
844         });
845     }
846
847     static setMinimumEffectiveWidth(effectiveWidth)
848     {
849         if (!this.isWebKit2())
850             return Promise.resolve();
851
852         return new Promise(resolve => testRunner.runUIScript(`uiController.setMinimumEffectiveWidth(${effectiveWidth})`, resolve));
853     }
854
855     static setAllowsViewportShrinkToFit(allows)
856     {
857         if (!this.isWebKit2())
858             return Promise.resolve();
859
860         return new Promise(resolve => testRunner.runUIScript(`uiController.setAllowsViewportShrinkToFit(${allows})`, resolve));
861     }
862
863     static setKeyboardInputModeIdentifier(identifier)
864     {
865         if (!this.isWebKit2())
866             return Promise.resolve();
867
868         const escapedIdentifier = identifier.replace(/`/g, "\\`");
869         return new Promise(resolve => testRunner.runUIScript(`uiController.setKeyboardInputModeIdentifier(\`${escapedIdentifier}\`)`, resolve));
870     }
871
872     static contentOffset()
873     {
874         if (!this.isIOSFamily())
875             return Promise.resolve();
876
877         const uiScript = "JSON.stringify([uiController.contentOffsetX, uiController.contentOffsetY])";
878         return new Promise(resolve => testRunner.runUIScript(uiScript, result => {
879             const [offsetX, offsetY] = JSON.parse(result)
880             resolve({ x: offsetX, y: offsetY });
881         }));
882     }
883
884     static undoAndRedoLabels()
885     {
886         if (!this.isWebKit2())
887             return Promise.resolve();
888
889         const script = "JSON.stringify([uiController.lastUndoLabel, uiController.firstRedoLabel])";
890         return new Promise(resolve => testRunner.runUIScript(script, result => resolve(JSON.parse(result))));
891     }
892
893     static waitForMenuToShow()
894     {
895         return new Promise(resolve => {
896             testRunner.runUIScript(`
897                 (function() {
898                     if (!uiController.isShowingMenu)
899                         uiController.didShowMenuCallback = () => uiController.uiScriptComplete();
900                     else
901                         uiController.uiScriptComplete();
902                 })()`, resolve);
903         });
904     }
905
906     static waitForMenuToHide()
907     {
908         return new Promise(resolve => {
909             testRunner.runUIScript(`
910                 (function() {
911                     if (uiController.isShowingMenu)
912                         uiController.didHideMenuCallback = () => uiController.uiScriptComplete();
913                     else
914                         uiController.uiScriptComplete();
915                 })()`, resolve);
916         });
917     }
918
919     static isShowingMenu()
920     {
921         return new Promise(resolve => {
922             testRunner.runUIScript(`uiController.isShowingMenu`, result => resolve(result === "true"));
923         });
924     }
925
926     static isDismissingMenu()
927     {
928         return new Promise(resolve => {
929             testRunner.runUIScript(`uiController.isDismissingMenu`, result => resolve(result === "true"));
930         });
931     }
932
933     static menuRect()
934     {
935         return new Promise(resolve => {
936             testRunner.runUIScript("JSON.stringify(uiController.menuRect)", result => resolve(JSON.parse(result)));
937         });
938     }
939
940     static setHardwareKeyboardAttached(attached)
941     {
942         return new Promise(resolve => testRunner.runUIScript(`uiController.setHardwareKeyboardAttached(${attached ? "true" : "false"})`, resolve));
943     }
944
945     static rectForMenuAction(action)
946     {
947         return new Promise(resolve => {
948             testRunner.runUIScript(`
949                 const rect = uiController.rectForMenuAction("${action}");
950                 uiController.uiScriptComplete(rect ? JSON.stringify(rect) : "");
951             `, stringResult => {
952                 resolve(stringResult.length ? JSON.parse(stringResult) : null);
953             });
954         });
955     }
956
957     static async chooseMenuAction(action)
958     {
959         const menuRect = await this.rectForMenuAction(action);
960         if (menuRect)
961             await this.activateAt(menuRect.left + menuRect.width / 2, menuRect.top + menuRect.height / 2);
962     }
963
964     static callFunctionAndWaitForEvent(functionToCall, target, eventName)
965     {
966         return new Promise((resolve) => {
967             target.addEventListener(eventName, resolve, { once: true });
968             functionToCall();
969         });
970
971     }
972
973     static callFunctionAndWaitForScrollToFinish(functionToCall, ...theArguments)
974     {
975         return new Promise((resolved) => {
976             function scrollDidFinish() {
977                 window.removeEventListener("scroll", handleScroll, true);
978                 resolved();
979             }
980
981             let lastScrollTimerId = 0; // When the timer with this id fires then the page has finished scrolling.
982             function handleScroll() {
983                 if (lastScrollTimerId) {
984                     window.clearTimeout(lastScrollTimerId);
985                     lastScrollTimerId = 0;
986                 }
987                 lastScrollTimerId = window.setTimeout(scrollDidFinish, 300); // Over 250ms to give some room for error.
988             }
989             window.addEventListener("scroll", handleScroll, true);
990
991             functionToCall.apply(this, theArguments);
992         });
993     }
994
995     static rotateDevice(orientationName, animatedResize = false)
996     {
997         if (!this.isWebKit2() || !this.isIOSFamily())
998             return Promise.resolve();
999
1000         return new Promise(resolve => {
1001             testRunner.runUIScript(`(() => {
1002                 uiController.${animatedResize ? "simulateRotationLikeSafari" : "simulateRotation"}("${orientationName}", function() {
1003                     uiController.doAfterVisibleContentRectUpdate(() => uiController.uiScriptComplete());
1004                 });
1005             })()`, resolve);
1006         });
1007     }
1008
1009     static getScrollingTree()
1010     {
1011         if (!this.isWebKit2() || !this.isIOSFamily())
1012             return Promise.resolve();
1013
1014         return new Promise(resolve => {
1015             testRunner.runUIScript(`(() => {
1016                 return uiController.scrollingTreeAsText;
1017             })()`, resolve);
1018         });
1019     }
1020 }