blob: ec8157588a9d9fc0b7f58c02be538d837b828ae1 [file] [log] [blame] [edit]
window.UIHelper = class UIHelper {
static isIOSFamily()
{
return testRunner.isIOSFamily;
}
static isMac()
{
return testRunner.isMac;
}
static isWebKit2()
{
return testRunner.isWebKit2;
}
static doubleClickAt(x, y)
{
eventSender.mouseMoveTo(x, y);
eventSender.mouseDown();
eventSender.mouseUp();
eventSender.mouseDown();
eventSender.mouseUp();
}
static doubleClickAtMouseDown(x1, y1)
{
eventSender.mouseMoveTo(x1, y1);
eventSender.mouseDown();
eventSender.mouseUp();
eventSender.mouseDown();
}
static mouseUp()
{
eventSender.mouseUp();
}
static doubleClickAtThenDragTo(x1, y1, x2, y2)
{
eventSender.mouseMoveTo(x1, y1);
eventSender.mouseDown();
eventSender.mouseUp();
eventSender.mouseDown();
eventSender.mouseMoveTo(x2, y2);
eventSender.mouseUp();
}
static dragMouseAcrossElement(element)
{
const x1 = element.offsetLeft + element.offsetWidth;
const x2 = element.offsetLeft + element.offsetWidth * .75;
const x3 = element.offsetLeft + element.offsetWidth / 2;
const x4 = element.offsetLeft + element.offsetWidth / 4;
const x5 = element.offsetLeft;
const y = element.offsetTop + element.offsetHeight / 2;
eventSender.mouseMoveTo(x1, y);
eventSender.mouseMoveTo(x2, y);
eventSender.mouseMoveTo(x3, y);
eventSender.mouseMoveTo(x4, y);
eventSender.mouseMoveTo(x5, y);
}
static doubleClickElementMouseDown(element1)
{
const x1 = element1.offsetLeft + element1.offsetWidth / 2;
const y1 = element1.offsetTop + element1.offsetHeight / 2;
return UIHelper.doubleClickAtMouseDown(x1, y1);
}
static async moveMouseAndWaitForFrame(x, y)
{
eventSender.mouseMoveTo(x, y);
await UIHelper.animationFrame();
}
static async startMonitoringWheelEvents(...args)
{
eventSender.monitorWheelEvents(args);
await UIHelper.ensurePresentationUpdate();
}
static async mouseWheelScrollAt(x, y, beginX, beginY, deltaX, deltaY)
{
if (beginX === undefined)
beginX = 0;
if (beginY === undefined)
beginY = -1;
if (deltaX === undefined)
deltaX = 0;
if (deltaY === undefined)
deltaY = -10;
await UIHelper.startMonitoringWheelEvents();
eventSender.mouseMoveTo(x, y);
eventSender.mouseScrollByWithWheelAndMomentumPhases(beginX, beginY, "began", "none");
eventSender.mouseScrollByWithWheelAndMomentumPhases(deltaX, deltaY, "changed", "none");
eventSender.mouseScrollByWithWheelAndMomentumPhases(0, 0, "ended", "none");
await UIHelper.waitForScrollCompletion();
}
static async statelessMouseWheelScrollAt(x, y, deltaX, deltaY)
{
await UIHelper.startMonitoringWheelEvents();
eventSender.mouseMoveTo(x, y);
eventSender.mouseScrollBy(deltaX, deltaY);
return UIHelper.waitForScrollCompletion();
}
static async mouseWheelMayBeginAt(x, y)
{
eventSender.mouseMoveTo(x, y);
eventSender.mouseScrollByWithWheelAndMomentumPhases(x, y, "maybegin", "none");
await UIHelper.animationFrame();
}
static async mouseWheelCancelAt(x, y)
{
eventSender.mouseMoveTo(x, y);
eventSender.mouseScrollByWithWheelAndMomentumPhases(x, y, "cancelled", "none");
await UIHelper.animationFrame();
}
static async mouseWheelSequence(eventStream, { waitForCompletion = true } = {})
{
if (waitForCompletion)
await UIHelper.startMonitoringWheelEvents();
const eventStreamAsString = JSON.stringify(eventStream);
await new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.sendEventStream(\`${eventStreamAsString}\`, () => {
uiController.uiScriptComplete();
});
})();
`, resolve);
});
if (waitForCompletion)
await UIHelper.waitForScrollCompletion();
}
static async waitForScrollCompletion()
{
if (this.isIOSFamily()) {
await new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.didEndScrollingCallback = function() {
uiController.uiScriptComplete();
}
})()`, resolve);
});
// Wait for the new scroll position to get back to the web process.
return UIHelper.renderingUpdate();
}
return new Promise(resolve => {
eventSender.callAfterScrollingCompletes(() => {
requestAnimationFrame(resolve);
});
});
}
static async scrollDown()
{
const midX = innerWidth / 2;
const midY = innerHeight / 2;
if (!this.isIOSFamily())
return await this.mouseWheelScrollAt(midX, midY, 0, -1, 0, -100);
await this.sendEventStream(new this.EventStreamBuilder()
.begin(midX, midY + 180)
.move(midX, midY - 180, 0.3)
.end()
.takeResult());
}
static async animationFrame()
{
return new Promise(requestAnimationFrame);
}
static async renderingUpdate()
{
await UIHelper.animationFrame();
await UIHelper.delayFor(0);
}
static async waitForCondition(conditionFunc)
{
while (!conditionFunc()) {
await UIHelper.animationFrame();
}
}
static async waitForConditionAsync(conditionFunc)
{
var condition = await conditionFunc();
while (!condition) {
await UIHelper.animationFrame();
condition = await conditionFunc();
}
}
static sendEventStream(eventStream)
{
const eventStreamAsString = JSON.stringify(eventStream);
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.sendEventStream(\`${eventStreamAsString}\`, () => {
uiController.uiScriptComplete();
});
})();
`, resolve);
});
}
static roundToDevicePixel(value)
{
return Math.round(value * devicePixelRatio) / devicePixelRatio;
}
static roundRectToDevicePixel(rect)
{
return Object.fromEntries(Object.keys(rect).map(key => {
return [key, this.roundToDevicePixel(rect[key])];
}));
}
static tapAt(x, y, modifiers=[])
{
console.assert(this.isIOSFamily());
if (!this.isWebKit2()) {
console.assert(!modifiers || !modifiers.length);
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
return Promise.resolve();
}
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.singleTapAtPointWithModifiers(${x}, ${y}, ${JSON.stringify(modifiers)}, function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static tapElement(element, delay = 0)
{
const x = element.offsetLeft + (element.offsetWidth / 2);
const y = element.offsetTop + (element.offsetHeight / 2);
this.tapAt(x, y);
}
static doubleTapElement(element, delay = 0)
{
const x = element.offsetLeft + (element.offsetWidth / 2);
const y = element.offsetTop + (element.offsetHeight / 2);
this.doubleTapAt(x, y, delay);
}
static doubleTapAt(x, y, delay = 0)
{
console.assert(this.isIOSFamily());
if (!this.isWebKit2()) {
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
return Promise.resolve();
}
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.doubleTapAtPoint(${x}, ${y}, ${delay}, function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static humanSpeedDoubleTapAt(x, y)
{
console.assert(this.isIOSFamily());
if (!this.isWebKit2()) {
// FIXME: Add a sleep in here.
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
return Promise.resolve();
}
return UIHelper.doubleTapAt(x, y, 0.12);
}
static humanSpeedZoomByDoubleTappingAt(x, y)
{
console.assert(this.isIOSFamily());
if (!this.isWebKit2()) {
// FIXME: Add a sleep in here.
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
return Promise.resolve();
}
return new Promise(async (resolve) => {
testRunner.runUIScript(`
uiController.didEndZoomingCallback = () => {
uiController.didEndZoomingCallback = null;
uiController.uiScriptComplete(uiController.zoomScale);
};
uiController.doubleTapAtPoint(${x}, ${y}, 0.12, () => { });`, resolve);
});
}
static selectionHitTestOffset()
{
return this.isIOSFamily() ? 40 : 0;
}
static zoomByDoubleTappingAt(x, y)
{
console.assert(this.isIOSFamily());
if (!this.isWebKit2()) {
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
eventSender.addTouchPoint(x, y);
eventSender.touchStart();
eventSender.releaseTouchPoint(0);
eventSender.touchEnd();
return Promise.resolve();
}
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.didEndZoomingCallback = () => {
uiController.didEndZoomingCallback = null;
uiController.uiScriptComplete(uiController.zoomScale);
};
uiController.doubleTapAtPoint(${x}, ${y}, 0, () => { });`, resolve);
});
}
static async activateAt(x, y, modifiers=[])
{
if (!this.isWebKit2() || !this.isIOSFamily()) {
await eventSender.asyncMouseMoveTo(x, y);
await eventSender.asyncMouseDown(0, modifiers);
await eventSender.asyncMouseUp(0, modifiers);
return;
}
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.singleTapAtPointWithModifiers(${x}, ${y}, ${JSON.stringify(modifiers)}, function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static activateElement(element, modifiers=[])
{
const x = element.offsetLeft + element.offsetWidth / 2;
const y = element.offsetTop + element.offsetHeight / 2;
return UIHelper.activateAt(x, y, modifiers);
}
static async doubleActivateAt(x, y)
{
if (this.isIOSFamily())
await UIHelper.doubleTapAt(x, y);
else
await UIHelper.doubleClickAt(x, y);
}
static async doubleActivateAtSelectionStart()
{
const rects = window.getSelection().getRangeAt(0).getClientRects();
const x = rects[0].left;
const y = rects[0].top;
if (this.isIOSFamily()) {
await UIHelper.activateAndWaitForInputSessionAt(x, y);
await UIHelper.doubleTapAt(x, y);
// This is only here to deal with async/sync copy/paste calls, so
// once <rdar://problem/16207002> is resolved, should be able to remove for faster tests.
await new Promise(resolve => testRunner.runUIScript("uiController.uiScriptComplete()", resolve));
} else
await UIHelper.doubleClickAt(x, y);
}
static async selectWordByDoubleTapOrClick(element, relativeX = 5, relativeY = 5)
{
const boundingRect = element.getBoundingClientRect();
const x = boundingRect.x + relativeX;
const y = boundingRect.y + relativeY;
if (this.isIOSFamily()) {
await UIHelper.activateAndWaitForInputSessionAt(x, y);
await UIHelper.doubleTapAt(x, y);
// This is only here to deal with async/sync copy/paste calls, so
// once <rdar://problem/16207002> is resolved, should be able to remove for faster tests.
await new Promise(resolve => testRunner.runUIScript("uiController.uiScriptComplete()", resolve));
} else {
await UIHelper.doubleClickAt(x, y);
}
}
static rawKeyDown(key, modifiers=[])
{
if (!this.isWebKit2() || !this.isIOSFamily()) {
eventSender.rawKeyDown(key, modifiers);
return Promise.resolve();
}
return new Promise((resolve) => {
testRunner.runUIScript(`uiController.rawKeyDown("${key}", ${JSON.stringify(modifiers)});`, resolve);
});
}
static rawKeyUp(key, modifiers=[])
{
if (!this.isWebKit2() || !this.isIOSFamily()) {
eventSender.rawKeyUp(key, modifiers);
return Promise.resolve();
}
return new Promise((resolve) => {
testRunner.runUIScript(`uiController.rawKeyUp("${key}", ${JSON.stringify(modifiers)});`, resolve);
});
}
static keyDown(key, modifiers=[])
{
if (!this.isWebKit2() || !this.isIOSFamily()) {
eventSender.keyDown(key, modifiers);
return Promise.resolve();
}
return new Promise((resolve) => {
testRunner.runUIScript(`uiController.keyDown("${key}", ${JSON.stringify(modifiers)});`, resolve);
});
}
static async keyboardScroll(key)
{
eventSender.keyDown(key);
await UIHelper.ensurePresentationUpdate();
}
static toggleCapsLock()
{
return new Promise((resolve) => {
testRunner.runUIScript(`uiController.toggleCapsLock(() => uiController.uiScriptComplete());`, resolve);
});
}
static keyboardWillHideCount()
{
return new Promise(resolve => {
testRunner.runUIScript(`uiController.keyboardWillHideCount`, result => resolve(parseInt(result)));
});
}
static keyboardIsAutomaticallyShifted()
{
return new Promise(resolve => {
testRunner.runUIScript(`uiController.keyboardIsAutomaticallyShifted`, result => resolve(result === "true"));
});
}
static isAnimatingDragCancel()
{
return new Promise(resolve => {
testRunner.runUIScript(`uiController.isAnimatingDragCancel`, result => resolve(result === "true"));
});
}
static ensurePresentationUpdate()
{
if (!this.isWebKit2())
return UIHelper.renderingUpdate();
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.doAfterPresentationUpdate(function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static ensureStablePresentationUpdate()
{
if (!this.isWebKit2())
return UIHelper.renderingUpdate();
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static ensurePositionInformationUpdateForElement(element)
{
const boundingRect = element.getBoundingClientRect();
const x = boundingRect.x + 5;
const y = boundingRect.y + 5;
if (!this.isWebKit2()) {
testRunner.display();
return Promise.resolve();
}
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.ensurePositionInformationIsUpToDateAt(${x}, ${y}, function () {
uiController.uiScriptComplete();
});`, resolve);
});
}
static delayFor(ms)
{
return new Promise(resolve => setTimeout(resolve, ms));
}
static async initiateUserScroll()
{
if (this.isIOSFamily()) {
await UIHelper.scrollTo(0, 10);
}
else {
await UIHelper.statelessMouseWheelScrollAt(10, 10, 0, 10);
}
}
static scrollTo(x, y, unconstrained)
{
if (!this.isWebKit2()) {
window.scrollTo(x, y);
return Promise.resolve();
}
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.didEndScrollingCallback = function() {
uiController.uiScriptComplete();
}
uiController.scrollToOffset(${x}, ${y}, { unconstrained: ${unconstrained} });
})()`, resolve);
});
}
static immediateScrollTo(x, y, unconstrained)
{
if (!this.isWebKit2()) {
window.scrollTo(x, y);
return Promise.resolve();
}
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.immediateScrollToOffset(${x}, ${y}, { unconstrained: ${unconstrained} });`, resolve);
});
}
static immediateUnstableScrollTo(x, y, unconstrained)
{
if (!this.isWebKit2()) {
window.scrollTo(x, y);
return Promise.resolve();
}
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.stableStateOverride = false;
uiController.immediateScrollToOffset(${x}, ${y}, { unconstrained: ${unconstrained} });`, resolve);
});
}
static immediateScrollElementAtContentPointToOffset(x, y, scrollX, scrollY, scrollUpdatesDisabled = false)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.scrollUpdatesDisabled = ${scrollUpdatesDisabled};
uiController.immediateScrollElementAtContentPointToOffset(${x}, ${y}, ${scrollX}, ${scrollY});`, resolve);
});
}
static ensureVisibleContentRectUpdate()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
const visibleContentRectUpdateScript = "uiController.doAfterVisibleContentRectUpdate(() => uiController.uiScriptComplete())";
testRunner.runUIScript(visibleContentRectUpdateScript, resolve);
});
}
static longPressAndGetContextMenuContentAt(x, y)
{
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.didShowContextMenuCallback = function() {
uiController.uiScriptComplete(JSON.stringify(uiController.contentsOfUserInterfaceItem('contextMenu')));
};
uiController.longPressAtPoint(${x}, ${y}, function() { });
})();`, result => resolve(JSON.parse(result)));
});
}
static async setSafeAreaInsets(top, right, bottom, left)
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.setSafeAreaInsets(${top}, ${right}, ${bottom}, ${left});
uiController.doAfterNextVisibleContentRectAndStablePresentationUpdate(function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static async setInlinePrediction(text, startIndex = 0)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.setInlinePrediction(\`${text}\`, ${startIndex})`, resolve));
}
static async acceptInlinePrediction()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.acceptInlinePrediction()`, resolve));
}
static async activateAndWaitForInputSessionAt(x, y)
{
if (!this.isWebKit2() || !this.isIOSFamily())
return this.activateAt(x, y);
if (testRunner.isKeyboardImmediatelyAvailable) {
await new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.singleTapAtPoint(${x}, ${y}, function() { });
uiController.uiScriptComplete();
})()`, resolve);
});
await this.ensureStablePresentationUpdate();
return;
}
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
function clearCallbacksAndScriptComplete() {
uiController.didShowContextMenuCallback = null;
uiController.didShowKeyboardCallback = null;
uiController.willPresentPopoverCallback = null;
uiController.uiScriptComplete();
}
uiController.didShowContextMenuCallback = clearCallbacksAndScriptComplete;
uiController.didShowKeyboardCallback = clearCallbacksAndScriptComplete;
uiController.willPresentPopoverCallback = clearCallbacksAndScriptComplete;
uiController.singleTapAtPoint(${x}, ${y}, function() { });
})()`, resolve);
});
}
static waitForInputSessionToDismiss()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (!uiController.isShowingKeyboard && !uiController.isShowingContextMenu && !uiController.isShowingPopover) {
uiController.uiScriptComplete();
return;
}
function clearCallbacksAndScriptComplete() {
uiController.didHideKeyboardCallback = null;
uiController.didDismissPopoverCallback = null;
uiController.didDismissContextMenuCallback = null;
uiController.uiScriptComplete();
}
uiController.didHideKeyboardCallback = clearCallbacksAndScriptComplete;
uiController.didDismissPopoverCallback = clearCallbacksAndScriptComplete;
uiController.didDismissContextMenuCallback = clearCallbacksAndScriptComplete;
})()`, resolve);
});
}
static resizeWindowTo(width, height)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.resizeWindowTo(${width}, ${height})`, resolve));
}
static activateElementAndWaitForInputSession(element)
{
const x = element.offsetLeft + element.offsetWidth / 2;
const y = element.offsetTop + element.offsetHeight / 2;
return this.activateAndWaitForInputSessionAt(x, y);
}
static activateFormControl(element)
{
if (!this.isWebKit2() || !this.isIOSFamily())
return this.activateElement(element);
const x = element.offsetLeft + element.offsetWidth / 2;
const y = element.offsetTop + element.offsetHeight / 2;
return this.activateFormControlAtPoint(x, y);
}
static activateFormControlAtPoint(x, y)
{
if (!this.isWebKit2() || !this.isIOSFamily())
return this.activateAt(x, y);
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.didStartFormControlInteractionCallback = function() {
uiController.uiScriptComplete();
};
uiController.singleTapAtPoint(${x}, ${y}, function() { });
})()`, resolve);
});
}
static dismissFormAccessoryView()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.dismissFormAccessoryView();
uiController.uiScriptComplete();
})()`, resolve);
});
}
static isShowingKeyboard()
{
return new Promise(resolve => {
testRunner.runUIScript("uiController.isShowingKeyboard", result => resolve(result === "true"));
});
}
static isShowingFormValidationBubble()
{
return new Promise(resolve => {
testRunner.runUIScript("uiController.isShowingFormValidationBubble", result => resolve(result === "true"));
});
}
static hasInputSession()
{
return new Promise(resolve => {
testRunner.runUIScript("uiController.hasInputSession", result => resolve(result === "true"));
});
}
static isPresentingModally()
{
return new Promise(resolve => {
testRunner.runUIScript("uiController.isPresentingModally", result => resolve(result === "true"));
});
}
static isZoomingOrScrolling()
{
return new Promise(resolve => {
testRunner.runUIScript("uiController.isZoomingOrScrolling", result => resolve(result === "true"));
});
}
static async waitForZoomingOrScrollingToEnd()
{
do {
await this.ensureStablePresentationUpdate();
} while (this.isIOSFamily() && await this.isZoomingOrScrolling());
}
static deactivateFormControl(element)
{
if (!this.isWebKit2() || !this.isIOSFamily()) {
element.blur();
return Promise.resolve();
}
return new Promise(async resolve => {
element.blur();
while (await this.isPresentingModally())
continue;
while (await this.isShowingKeyboard())
continue;
resolve();
});
}
static waitForPopoverToPresent()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingPopover)
uiController.uiScriptComplete();
else
uiController.willPresentPopoverCallback = () => uiController.uiScriptComplete();
})()`, resolve);
});
}
static waitForPopoverToDismiss()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingPopover)
uiController.didDismissPopoverCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static waitForViewControllerToShow()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.didPresentViewControllerCallback = () => uiController.uiScriptComplete();
})()`, resolve);
});
}
static waitForContextMenuToShow()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (!uiController.isShowingContextMenu)
uiController.didShowContextMenuCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static waitForContextMenuToHide()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingContextMenu)
uiController.didDismissContextMenuCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static isShowingContextMenu()
{
return new Promise(resolve => {
testRunner.runUIScript(`uiController.isShowingContextMenu`, result => resolve(result === "true"));
});
}
static dismissMenu()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript("uiController.dismissMenu()", resolve);
});
}
static waitForKeyboardToShow()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingKeyboard)
uiController.uiScriptComplete();
else
uiController.didShowKeyboardCallback = () => uiController.uiScriptComplete();
})()`, resolve);
});
}
static waitForKeyboardToHide()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingKeyboard)
uiController.didHideKeyboardCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static scrollbarState(scroller, isVertical)
{
var internalFunctions = scroller ? scroller.ownerDocument.defaultView.internals : internals;
if (!this.isWebKit2())
return Promise.resolve();
if (internals.isUsingUISideCompositing() && (!scroller || scroller.nodeName != "SELECT")) {
var scrollingNodeID = internalFunctions.scrollingNodeIDForNode(scroller);
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete(uiController.scrollbarStateForScrollingNodeID(${scrollingNodeID.nodeIdentifier}, ${scrollingNodeID.processIdentifier}, ${isVertical}));
});
})()`, state => {
resolve(state);
});
});
} else {
return isVertical ? Promise.resolve(internalFunctions.verticalScrollbarState(scroller)) : Promise.resolve(internalFunctions.horizontalScrollbarState(scroller));
}
}
static verticalScrollbarState(scroller)
{
return UIHelper.scrollbarState(scroller, true);
}
static horizontalScrollbarState(scroller)
{
return UIHelper.scrollbarState(scroller, false);
}
static didCallEnsurePositionInformationIsUpToDateSinceLastCheck()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve(false);
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.uiScriptComplete(uiController.didCallEnsurePositionInformationIsUpToDateSinceLastCheck);
})()`, result => {
resolve(result === "true");
});
});
}
static clearEnsurePositionInformationIsUpToDateTracking()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.clearEnsurePositionInformationIsUpToDateTracking();
uiController.uiScriptComplete();
})()`, resolve);
});
}
static getUICaretViewRect()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete(JSON.stringify(uiController.selectionCaretViewRect));
});
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static getUICaretViewRectInGlobalCoordinates()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete(JSON.stringify(uiController.selectionCaretViewRectInGlobalCoordinates));
});
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static getUISelectionViewRects()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete(JSON.stringify(uiController.selectionRangeViewRects));
});
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static async isSelectionVisuallyContiguous()
{
const rects = await this.getUISelectionViewRects();
if (!rects?.length)
return false;
rects.sort((a, b) => {
if (a.top < b.top)
return -1;
if (a.top > b.top)
return 1;
return a.left < b.left ? -1 : (a.left > b.left ? 1 : 0);
});
for (let i = 1; i < rects.length; ++i) {
const previousRect = rects[i - 1];
const rect = rects[i];
if (previousRect.top !== rect.top)
continue;
if (previousRect.left + previousRect.width < rect.left)
return false;
}
return true;
}
static async selectionBounds()
{
const rects = await this.getUISelectionViewRects();
if (!rects?.length)
return null;
let minTop = Infinity;
let minLeft = Infinity;
let maxTop = -Infinity;
let maxLeft = -Infinity;
for (const rect of rects) {
minTop = Math.min(minTop, rect.top);
minLeft = Math.min(minLeft, rect.left);
maxTop = Math.max(maxTop, rect.top + rect.height);
maxLeft = Math.max(maxLeft, rect.left + rect.width);
}
return { left: minLeft, top: minTop, width: maxLeft - minLeft, height: maxTop - minTop };
}
static getSelectionStartGrabberViewRect()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete(JSON.stringify(uiController.selectionStartGrabberViewRect));
});
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static getSelectionEndGrabberViewRect()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete(JSON.stringify(uiController.selectionEndGrabberViewRect));
});
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static getSelectionEndGrabberViewShapePathDescription()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(function() {
uiController.doAfterNextStablePresentationUpdate(function() {
uiController.uiScriptComplete(JSON.stringify(uiController.selectionEndGrabberViewShapePathDescription));
});
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static midPointOfRect(rect) {
return { x: rect.left + (rect.width / 2), y: rect.top + (rect.height / 2) };
}
static rectContainsOtherRect(rect, otherRect) {
return rect.left <= otherRect.left
&& rect.top <= otherRect.top
&& (rect.left + rect.width) >= (otherRect.left + otherRect.width)
&& (rect.top + rect.height) >= (otherRect.top + otherRect.height);
}
static computeLineBounds(element, firstRunIndex, lastRunIndex = undefined) {
const range = document.createRange();
range.selectNodeContents(element);
const clientRects = range.getClientRects();
const firstRect = clientRects[firstRunIndex];
const secondRect = clientRects[lastRunIndex || firstRunIndex];
const x = Math.min(firstRect.left, secondRect.left);
const y = Math.min(firstRect.top, secondRect.top);
const maxX = Math.max(firstRect.left + firstRect.width, secondRect.left + secondRect.width);
const maxY = Math.max(firstRect.top + firstRect.height, secondRect.top + secondRect.height);
return {
top: Math.round(y),
left: Math.round(x),
width: Math.round(maxX - x),
height: Math.round(maxY - y)
};
}
static selectionCaretBackgroundColor()
{
return new Promise(resolve => {
testRunner.runUIScript("uiController.uiScriptComplete(uiController.selectionCaretBackgroundColor)", resolve);
});
}
static tapHighlightViewRect()
{
return new Promise(resolve => {
testRunner.runUIScript("JSON.stringify(uiController.tapHighlightViewRect)", jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static replaceTextAtRange(text, location, length) {
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.replaceTextAtRange("${text}", ${location}, ${length});
uiController.uiScriptComplete();
})()`, resolve);
});
}
static wait(promise)
{
testRunner.waitUntilDone();
if (window.finishJSTest)
window.jsTestIsAsync = true;
let finish = () => {
if (window.finishJSTest)
finishJSTest();
else
testRunner.notifyDone();
}
return promise.then(finish, finish);
}
static withUserGesture(callback)
{
internals.withUserGesture(callback);
}
static selectFormAccessoryPickerRow(rowIndex)
{
const selectRowScript = `uiController.selectFormAccessoryPickerRow(${rowIndex})`;
return new Promise(resolve => testRunner.runUIScript(selectRowScript, resolve));
}
static selectFormAccessoryHasCheckedItemAtRow(rowIndex)
{
return new Promise(resolve => testRunner.runUIScript(`uiController.selectFormAccessoryHasCheckedItemAtRow(${rowIndex})`, result => {
resolve(result === "true");
}));
}
static selectFormPopoverTitle()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.selectFormPopoverTitle);
})()`, resolve);
});
}
static selectMenuItems()
{
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (!uiController.isShowingContextMenu) {
uiController.didShowContextMenuCallback = function() {
uiController.uiScriptComplete(JSON.stringify(uiController.contentsOfUserInterfaceItem('selectMenu')));
};
} else {
uiController.uiScriptComplete(JSON.stringify(uiController.contentsOfUserInterfaceItem('selectMenu')));
}
})();`, result => resolve(JSON.parse(result).selectMenu));
});
}
static contextMenuItems()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(JSON.stringify(uiController.contentsOfUserInterfaceItem('contextMenu')))
})()`, result => resolve(result ? JSON.parse(result).contextMenu : null));
});
}
static setSelectedColorForColorPicker(red, green, blue)
{
const selectColorScript = `uiController.setSelectedColorForColorPicker(${red}, ${green}, ${blue})`;
return new Promise(resolve => testRunner.runUIScript(selectColorScript, resolve));
}
static isShowingColorPicker()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.isShowingColorPicker);
})()`, result => resolve(result === "true"));
});
}
static waitForColorPickerToPresent()
{
if (!this.isWebKit2())
return Promise.resolve();
if (this.isIOSFamily()) {
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingColorPicker)
uiController.uiScriptComplete();
else
uiController.willPresentPopoverCallback = () => uiController.uiScriptComplete();
})()`, resolve);
});
}
// macOS: poll for color picker in subview hierarchy.
return new Promise(async resolve => {
while (!await this.isShowingColorPicker())
continue;
resolve();
});
}
static enterText(text)
{
const escapedText = text.replace(/`/g, "\\`");
const enterTextScript = `(() => uiController.enterText(\`${escapedText}\`))()`;
return new Promise(resolve => testRunner.runUIScript(enterTextScript, resolve));
}
static setTimePickerValue(hours, minutes)
{
const setValueScript = `(() => uiController.setTimePickerValue(${hours}, ${minutes}))()`;
return new Promise(resolve => testRunner.runUIScript(setValueScript, resolve));
}
static timerPickerValues()
{
if (!this.isIOSFamily())
return Promise.resolve();
const uiScript = "JSON.stringify([uiController.timePickerValueHour, uiController.timePickerValueMinute])";
return new Promise(resolve => testRunner.runUIScript(uiScript, result => {
const [hour, minute] = JSON.parse(result)
resolve({ hour: hour, minute: minute });
}));
}
static textContentType()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.textContentType);
})()`, resolve);
});
}
static formInputLabel()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.formInputLabel);
})()`, resolve);
});
}
static dismissFilePicker()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
const script = `uiController.dismissFilePicker(() => {
uiController.uiScriptComplete();
})`;
return new Promise(resolve => testRunner.runUIScript(script, resolve));
}
static filePickerAcceptedTypeIdentifiers()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(JSON.stringify(uiController.filePickerAcceptedTypeIdentifiers));
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static activateDataListSuggestion(index) {
const script = `uiController.activateDataListSuggestion(${index}, () => {
uiController.uiScriptComplete("");
});`;
return new Promise(resolve => testRunner.runUIScript(script, resolve));
}
static isShowingDataListSuggestions()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.isShowingDataListSuggestions);
})()`, result => resolve(result === "true"));
});
}
static waitForDataListSuggestionsToChangeVisibility(visible)
{
return new Promise(async resolve => {
while (visible != await this.isShowingDataListSuggestions())
continue;
resolve();
});
}
static isShowingDateTimePicker()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.isShowingDateTimePicker);
})()`, result => resolve(result === "true"));
});
}
static dateTimePickerValue()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.dateTimePickerValue);
})()`, valueAsString => resolve(parseFloat(valueAsString)));
});
}
static chooseDateTimePickerValue()
{
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.chooseDateTimePickerValue();
uiController.uiScriptComplete();
`, resolve);
});
}
static zoomScale()
{
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.zoomScale);
})()`, scaleAsString => resolve(parseFloat(scaleAsString)));
});
}
static zoomToScale(scale)
{
const uiScript = `uiController.zoomToScale(${scale}, () => uiController.uiScriptComplete(uiController.zoomScale))`;
return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
}
static immediateZoomToScale(scale)
{
const uiScript = `uiController.immediateZoomToScale(${scale})`;
return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
}
static async smartMagnifyAt(x, y)
{
if (!this.isWebKit2() || !this.isMac()) {
console.log('Smart magnify testing is currently only supported on macOS');
return Promise.resolve();
}
await UIHelper.startMonitoringWheelEvents();
// If smartMagnify is not working, ensure you've called setWebViewAllowsMagnification(true).
eventSender.mouseMoveTo(x, y);
eventSender.smartMagnify();
await UIHelper.waitForScrollCompletion();
}
static typeCharacter(characterString)
{
if (!this.isWebKit2() || !this.isIOSFamily()) {
eventSender.keyDown(characterString);
return;
}
const escapedString = characterString.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
const uiScript = `uiController.typeCharacterUsingHardwareKeyboard(\`${escapedString}\`, () => uiController.uiScriptComplete())`;
return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
}
static selectWordForReplacement()
{
if (!this.isWebKit2())
return;
return new Promise(resolve => testRunner.runUIScript("uiController.selectWordForReplacement()", resolve));
}
static applyAutocorrection(newText, oldText, underline)
{
if (!this.isWebKit2())
return;
const [escapedNewText, escapedOldText] = [newText.replace(/`/g, "\\`"), oldText.replace(/`/g, "\\`")];
const uiScript = `uiController.applyAutocorrection(\`${escapedNewText}\`, \`${escapedOldText}\`, () => uiController.uiScriptComplete(), ${underline})`;
return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
}
static inputViewBounds()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(JSON.stringify(uiController.inputViewBounds));
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static calendarType()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.doAfterNextStablePresentationUpdate(() => {
uiController.uiScriptComplete(JSON.stringify(uiController.calendarType));
})
})()`, jsonString => {
resolve(JSON.parse(jsonString));
});
});
}
static setDefaultCalendarType(calendarIdentifier, localeIdentifier)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.setDefaultCalendarType('${calendarIdentifier}', '${localeIdentifier}')`, resolve));
}
static setViewScale(scale)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.setViewScale(${scale})`, resolve));
}
static setScrollViewKeyboardAvoidanceEnabled(enabled)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.setScrollViewKeyboardAvoidanceEnabled(${enabled})`, resolve));
}
static async setAlwaysBounceVertical(enabled)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.setAlwaysBounceVertical(${enabled})`, resolve));
}
static async setAlwaysBounceHorizontal(enabled)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.setAlwaysBounceHorizontal(${enabled})`, resolve));
}
static presentFindNavigator() {
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.presentFindNavigator()`, resolve));
}
static dismissFindNavigator() {
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.dismissFindNavigator()`, resolve));
}
static resignFirstResponder()
{
if (!this.isWebKit2()) {
testRunner.setMainFrameIsFirstResponder(false);
return Promise.resolve();
}
return new Promise(resolve => testRunner.runUIScript(`uiController.resignFirstResponder()`, resolve));
}
static becomeFirstResponder()
{
if (!this.isWebKit2()) {
testRunner.setMainFrameIsFirstResponder(true);
return Promise.resolve();
}
return new Promise(resolve => testRunner.runUIScript(`uiController.becomeFirstResponder()`, resolve));
}
static removeViewFromWindow()
{
if (!this.isWebKit2())
return Promise.resolve();
const scriptToRun = `(function() {
uiController.removeViewFromWindow();
uiController.uiScriptComplete();
})()`;
return new Promise(resolve => testRunner.runUIScript(scriptToRun, resolve));
}
static addViewToWindow()
{
if (!this.isWebKit2())
return Promise.resolve();
const scriptToRun = `(function() {
uiController.addViewToWindow();
uiController.uiScriptComplete();
})()`;
return new Promise(resolve => testRunner.runUIScript(scriptToRun, resolve));
}
static minimumZoomScale()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(uiController.minimumZoomScale);
})()`, scaleAsString => resolve(parseFloat(scaleAsString)))
});
}
static stylusTapAt(x, y, modifiers=[])
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.stylusTapAtPointWithModifiers(${x}, ${y}, 2, 1, 0.5, ${JSON.stringify(modifiers)}, function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static attachmentInfo(attachmentIdentifier)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.uiScriptComplete(JSON.stringify(uiController.attachmentInfo('${attachmentIdentifier}')));
})()`, jsonString => {
resolve(JSON.parse(jsonString));
})
});
}
static insertAttachmentForFilePath(path, contentType)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
uiController.insertAttachmentForFilePath('${path}', '${contentType}', function() {
uiController.uiScriptComplete();
});`, resolve);
});
}
static setMinimumEffectiveWidth(effectiveWidth)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.setMinimumEffectiveWidth(${effectiveWidth})`, resolve));
}
static setAllowsViewportShrinkToFit(allows)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.setAllowsViewportShrinkToFit(${allows})`, resolve));
}
static setKeyboardInputModeIdentifier(identifier)
{
if (!this.isWebKit2())
return Promise.resolve();
const escapedIdentifier = identifier.replace(/`/g, "\\`");
return new Promise(resolve => testRunner.runUIScript(`uiController.setKeyboardInputModeIdentifier(\`${escapedIdentifier}\`)`, resolve));
}
static setFocusStartsInputSessionPolicy(policy)
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => testRunner.runUIScript(`uiController.setFocusStartsInputSessionPolicy("${policy}")`, resolve));
}
static contentOffset()
{
if (!this.isIOSFamily())
return Promise.resolve();
const uiScript = "JSON.stringify([uiController.contentOffsetX, uiController.contentOffsetY])";
return new Promise(resolve => testRunner.runUIScript(uiScript, result => {
const [offsetX, offsetY] = JSON.parse(result)
resolve({ x: offsetX, y: offsetY });
}));
}
static adjustedContentInset()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript("JSON.stringify(uiController.adjustedContentInset)", result => resolve(JSON.parse(result)));
});
}
static undoAndRedoLabels()
{
if (!this.isWebKit2())
return Promise.resolve();
const script = "JSON.stringify([uiController.lastUndoLabel, uiController.firstRedoLabel])";
return new Promise(resolve => testRunner.runUIScript(script, result => resolve(JSON.parse(result))));
}
static waitForMenuToShow()
{
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (!uiController.isShowingMenu)
uiController.didShowMenuCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static waitForMenuToHide()
{
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingMenu)
uiController.didHideMenuCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static isShowingMenu()
{
return new Promise(resolve => {
testRunner.runUIScript(`uiController.isShowingMenu`, result => resolve(result === "true"));
});
}
static isDismissingMenu()
{
return new Promise(resolve => {
testRunner.runUIScript(`uiController.isDismissingMenu`, result => resolve(result === "true"));
});
}
static menuRect()
{
return new Promise(resolve => {
testRunner.runUIScript("JSON.stringify(uiController.menuRect)", result => resolve(JSON.parse(result)));
});
}
static contextMenuRect()
{
return new Promise(resolve => {
testRunner.runUIScript("JSON.stringify(uiController.contextMenuRect)", result => resolve(JSON.parse(result)));
});
}
static contextMenuPreviewRect()
{
return new Promise(resolve => {
testRunner.runUIScript("JSON.stringify(uiController.contextMenuPreviewRect)", result => resolve(JSON.parse(result)));
});
}
static setHardwareKeyboardAttached(attached)
{
return new Promise(resolve => testRunner.runUIScript(`uiController.setHardwareKeyboardAttached(${attached ? "true" : "false"})`, resolve));
}
static setShowKeyboardAfterElementFocusDelay(delay)
{
return new Promise(resolve => testRunner.runUIScript(`uiController.setShowKeyboardAfterElementFocusDelay(${delay})`, resolve));
}
static setWebViewEditable(editable)
{
return new Promise(resolve => testRunner.runUIScript(`uiController.setWebViewEditable(${editable ? "true" : "false"})`, resolve));
}
static setWebViewAllowsMagnification(allowsMagnification)
{
return new Promise(resolve => testRunner.runUIScript(`uiController.setWebViewAllowsMagnification(${allowsMagnification ? "true" : "false"})`, resolve));
}
static rectForMenuAction(action)
{
return new Promise(resolve => {
testRunner.runUIScript(`
(() => {
const rect = uiController.rectForMenuAction("${action}");
uiController.uiScriptComplete(rect ? JSON.stringify(rect) : "");
})();
`, stringResult => {
resolve(stringResult.length ? JSON.parse(stringResult) : null);
});
});
}
static chooseMenuAction(action)
{
return new Promise(resolve => {
testRunner.runUIScript(`
(() => {
uiController.chooseMenuAction("${action}", () => {
uiController.uiScriptComplete();
});
})();
`, resolve);
});
}
static waitForEvent(target, eventName)
{
return new Promise(resolve => target.addEventListener(eventName, resolve, { once: true }));
}
static waitForEventHandler(target, eventName, handler)
{
return new Promise((resolve) => {
target.addEventListener(eventName, (e) => {
handler(e)
resolve();
}, { once: true });
});
}
static callFunctionAndWaitForEvent(functionToCall, target, eventName)
{
return new Promise(async resolve => {
let event;
await Promise.all([
new Promise((eventListenerResolve) => {
target.addEventListener(eventName, (e) => {
event = e;
eventListenerResolve();
}, { once: true });
}),
new Promise(async functionResolve => {
await functionToCall();
functionResolve();
})
]);
resolve(event);
});
}
static callFunctionAndWaitForScrollToFinish(functionToCall, ...theArguments)
{
return UIHelper.callFunctionAndWaitForTargetScrollToFinish(window, functionToCall, theArguments)
}
static callFunctionAndWaitForTargetScrollToFinish(scrollTarget, functionToCall, ...theArguments)
{
return new Promise((resolved) => {
function scrollDidFinish() {
scrollTarget.removeEventListener("scroll", handleScroll, true);
resolved();
}
let lastScrollTimerId = 0; // When the timer with this id fires then the page has finished scrolling.
function handleScroll() {
if (lastScrollTimerId) {
window.clearTimeout(lastScrollTimerId);
lastScrollTimerId = 0;
}
lastScrollTimerId = window.setTimeout(scrollDidFinish, 300); // Over 250ms to give some room for error.
}
scrollTarget.addEventListener("scroll", handleScroll, true);
functionToCall.apply(this, theArguments);
});
}
static waitForTargetScrollAnimationToSettle(scrollTarget)
{
return new Promise((resolved) => {
let lastObservedScrollPosition = [scrollTarget.scrollLeft, scrollTarget.scrollTop];
let frameOfLastChange = 0;
let totalFrames = 0;
function animationFrame() {
if (lastObservedScrollPosition[0] != scrollTarget.scrollLeft ||
lastObservedScrollPosition[1] != scrollTarget.scrollTop) {
lastObservedScrollPosition = [scrollTarget.scrollLeft, scrollTarget.scrollTop];
frameOfLastChange = totalFrames;
}
// If we have gone 20 frames without changing, resolve. If we have gone 500, then time out.
// This matches the amount of frames used in the WPT scroll animation helper.
if (totalFrames - frameOfLastChange >= 20 || totalFrames > 500)
resolved();
totalFrames++;
requestAnimationFrame(animationFrame);
}
requestAnimationFrame(animationFrame);
});
}
static beginInteractiveObscuredInsetsChange()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript("uiController.beginInteractiveObscuredInsetsChange()", resolve);
});
}
static endInteractiveObscuredInsetsChange()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript("uiController.endInteractiveObscuredInsetsChange()", resolve);
});
}
static async setObscuredInsets(top, right, bottom, left)
{
if (!window.testRunner)
return Promise.resolve();
if (this.isWebKit2() && this.isIOSFamily()) {
const scriptToRun = `uiController.setObscuredInsets(${top}, ${right}, ${bottom}, ${left})`;
await new Promise(resolve => testRunner.runUIScript(scriptToRun, resolve));
await this.ensureVisibleContentRectUpdate();
} else
testRunner.setObscuredContentInsets(top, right, bottom, left);
await this.ensurePresentationUpdate();
}
static rotateDevice(orientationName, animatedResize = false)
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.${animatedResize ? "simulateRotationLikeSafari" : "simulateRotation"}("${orientationName}", function() {
uiController.doAfterVisibleContentRectUpdate(() => uiController.uiScriptComplete());
});
})()`, resolve);
});
}
static getScrollingTree()
{
if (!this.isWebKit2())
return Promise.resolve('');
if (window.internals.haveScrollingTree())
return Promise.resolve(window.internals.scrollingTreeAsText());
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
return uiController.scrollingTreeAsText;
})()`, resolve);
});
}
static getUIViewTree()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
return uiController.uiViewTreeAsText;
})()`, resolve);
});
}
static getUIViewTreeForCompositedElement(element)
{
if (!this.isWebKit2())
return Promise.resolve();
const layerID = window.internals?.layerIDForElement(element);
const script = `uiController.uiScriptComplete(uiController.uiViewTreeAsTextForViewWithLayerID(${layerID}))`;
return new Promise(resolve => {
testRunner.runUIScript(script, resolve);
});
}
static propertiesOfLayerWithID(layerID)
{
if (!this.isWebKit2())
return Promise.resolve();
const script = `uiController.uiScriptComplete(JSON.stringify(uiController.propertiesOfLayerWithID(${layerID})))`;
return new Promise(resolve => {
testRunner.runUIScript(script, properties => resolve(JSON.parse(properties)));
});
}
static getCALayerTree()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
return uiController.caLayerTreeAsText;
})()`, resolve);
});
}
static getCALayerTreeForCompositedElement(element)
{
if (!this.isWebKit2())
return Promise.resolve();
const layerID = window.internals?.layerIDForElement(element);
const script = `uiController.uiScriptComplete(uiController.caLayerTreeAsTextForLayerWithID(${layerID}))`;
return new Promise(resolve => {
testRunner.runUIScript(script, resolve);
});
}
static remoteAnimationStackForElement(element)
{
if (!this.isWebKit2() || !element)
return Promise.resolve();
const layerID = window.internals?.layerIDForElement(element);
const script = `uiController.uiScriptComplete(uiController.animationStackForLayerWithID(${layerID}))`;
return new Promise(resolve => {
testRunner.runUIScript(script, result => resolve(JSON.parse(result)));
});
}
static async remoteTimeline(timeline)
{
if (!this.isWebKit2() || !(timeline instanceof ScrollTimeline))
return;
const scrollingNodeID = window.internals?.scrollingNodeIDForTimeline(timeline);
if (!scrollingNodeID.nodeIdentifier || !scrollingNodeID.processIdentifier)
return;
const identifier = window.internals?.identifierForTimeline(timeline);
const script = `uiController.uiScriptComplete(uiController.progressBasedTimelinesForScrollingNodeID(${scrollingNodeID.nodeIdentifier}, ${scrollingNodeID.processIdentifier}))`;
const result = await new Promise(resolve => {
testRunner.runUIScript(script, result => resolve(JSON.parse(result)));
});
const timelines = result.timelines;
for (const timeline of timelines) {
if (timeline.identifier == identifier)
return timeline;
}
}
static async displayLinkWantsHighFrameRate()
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve(false);
const script = `uiController.displayLinkWantsHighFrameRate()`;
return new Promise(resolve => testRunner.runUIScript(script, result => resolve(result === "true")));
}
static dragFromPointToPoint(fromX, fromY, toX, toY, duration)
{
if (!this.isWebKit2() || !this.isIOSFamily()) {
eventSender.mouseMoveTo(fromX, fromY);
eventSender.mouseDown();
eventSender.leapForward(duration * 1000);
eventSender.mouseMoveTo(toX, toY);
eventSender.mouseUp();
return Promise.resolve();
}
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.dragFromPointToPoint(${fromX}, ${fromY}, ${toX}, ${toY}, ${duration}, () => {
uiController.uiScriptComplete();
});
})();`, resolve);
});
}
static async pinch(firstStartX, firstStartY, secondStartX, secondStartY, firstEndX, firstEndY, secondEndX, secondEndY)
{
await UIHelper.sendEventStream({
events: [
{
interpolate : "linear",
timestep : 0.01,
coordinateSpace : "content",
startEvent : {
inputType : "hand",
timeOffset : 0,
touches : [
{ inputType : "finger", phase : "began", id : 1, x : firstStartX, y : firstStartY, pressure : 0 },
{ inputType : "finger", phase : "began", id : 2, x : secondStartX, y : secondStartY, pressure : 0 }
]
},
endEvent : {
inputType : "hand",
timeOffset : 0.01,
touches : [
{ inputType : "finger", phase : "began", id : 1, x : firstStartX, y : firstStartY, pressure : 0 },
{ inputType : "finger", phase : "began", id : 2, x : secondStartX, y : secondStartY, pressure : 0 }
]
}
},
{
interpolate : "linear",
timestep : 0.01,
coordinateSpace : "content",
startEvent : {
inputType : "hand",
timeOffset : 0.01,
touches : [
{ inputType : "finger", phase : "moved", id : 1, x : firstStartX, y : firstStartY, pressure : 0 },
{ inputType : "finger", phase : "moved", id : 2, x : secondStartX, y : secondStartY, pressure : 0 }
]
},
endEvent : {
inputType : "hand",
timeOffset : 0.9,
touches : [
{ inputType : "finger", phase : "moved", id : 1, x : firstEndX, y : firstEndY, pressure : 0 },
{ inputType : "finger", phase : "moved", id : 2, x : secondEndX, y : secondEndY, pressure : 0 }
]
}
},
{
interpolate : "linear",
timestep : 0.01,
coordinateSpace : "content",
startEvent : {
inputType : "hand",
timeOffset : 0.9,
touches : [
{ inputType : "finger", phase : "stationary", id : 1, x : firstEndX, y : firstEndY, pressure : 0 },
{ inputType : "finger", phase : "stationary", id : 2, x : secondEndX, y : secondEndY, pressure : 0 }
]
},
endEvent : {
inputType : "hand",
timeOffset : 0.99,
touches : [
{ inputType : "finger", phase : "stationary", id : 1, x : firstEndX, y : firstEndY, pressure : 0 },
{ inputType : "finger", phase : "stationary", id : 2, x : secondEndX, y : secondEndY, pressure : 0 }
]
}
},
{
interpolate : "linear",
timestep : 0.01,
coordinateSpace : "content",
startEvent : {
inputType : "hand",
timeOffset : 0.99,
touches : [
{ inputType : "finger", phase : "ended", id : 1, x : firstEndX, y : firstEndY, pressure : 0 },
{ inputType : "finger", phase : "ended", id : 2, x : secondEndX, y : secondEndY, pressure : 0 }
]
},
endEvent : {
inputType : "hand",
timeOffset : 1,
touches : [
{ inputType : "finger", phase : "ended", id : 1, x : firstEndX, y : firstEndY, pressure : 0 },
{ inputType : "finger", phase : "ended", id : 2, x : secondEndX, y : secondEndY, pressure : 0 }
]
}
}
]
});
}
static setWindowIsKey(isKey)
{
if (!this.isWebKit2()) {
testRunner.setWindowIsKey(isKey);
return Promise.resolve();
}
const script = `uiController.windowIsKey = ${isKey}`;
return new Promise(resolve => testRunner.runUIScript(script, resolve));
}
static windowIsKey()
{
const script = "uiController.uiScriptComplete(uiController.windowIsKey)";
return new Promise(resolve => testRunner.runUIScript(script, (result) => {
resolve(result === "true");
}));
}
static waitForDoubleTapDelay()
{
const uiScript = `uiController.doAfterDoubleTapDelay(() => uiController.uiScriptComplete(""))`;
return new Promise(resolve => testRunner.runUIScript(uiScript, resolve));
}
static async waitForSelectionToAppear() {
while (true) {
let selectionRects = await this.getUISelectionViewRects();
if (selectionRects.length > 0)
return selectionRects;
}
}
static async waitForSelectionToDisappear() {
while (true) {
if (!(await this.getUISelectionViewRects()).length)
break;
}
}
static async copyText(text) {
const copyTextScript = `uiController.copyText(\`${text.replace(/`/g, "\\`")}\`)`;
return new Promise(resolve => testRunner.runUIScript(copyTextScript, resolve));
}
static async paste() {
return new Promise(resolve => testRunner.runUIScript(`uiController.paste()`, resolve));
}
static async pasteboardChangeCount() {
return new Promise(resolve => testRunner.runUIScript(`uiController.pasteboardChangeCount`, (result) => {
resolve(parseInt(result))
}));
}
static async setContinuousSpellCheckingEnabled(enabled) {
return new Promise(resolve => {
testRunner.runUIScript(`uiController.setContinuousSpellCheckingEnabled(${enabled})`, resolve);
});
}
static async longPressElement(element)
{
const { x, y } = this.midPointOfRect(element.getBoundingClientRect());
return this.longPressAtPoint(x, y);
}
static async longPressAtPoint(x, y)
{
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.longPressAtPoint(${x}, ${y}, function() {
uiController.uiScriptComplete();
});
})();`, resolve);
});
}
// When setting expected results, if your test includes
// a return after the string as a part of the test,
// make sure to include two entries for the string,
// one with the '\n' and one without.
// Mac and iOS have different tokenizing behavior
// which can lead to different results when looking
// for the key string in this dictionary.
static async setSpellCheckerResults(results)
{
if (!this.isMac() && !this.isIOSFamily())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.setSpellCheckerResults(${JSON.stringify(results)});
uiController.uiScriptComplete();
})()`, resolve);
});
}
static async activateElementAfterInstallingTapGestureOnWindow(element)
{
if (!this.isWebKit2() || !this.isIOSFamily())
return activateElement(element);
const x = element.offsetLeft + element.offsetWidth / 2;
const y = element.offsetTop + element.offsetHeight / 2;
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
let progress = 0;
function incrementProgress() {
if (++progress == 2)
uiController.uiScriptComplete();
}
uiController.installTapGestureOnWindow(incrementProgress);
uiController.singleTapAtPoint(${x}, ${y}, incrementProgress);
})();`, resolve);
});
}
static mayContainEditableElementsInRect(x, y, width, height)
{
if (!this.isWebKit2() || !this.isIOSFamily())
return Promise.resolve(false);
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
uiController.doAfterPresentationUpdate(function() {
uiController.uiScriptComplete(uiController.mayContainEditableElementsInRect(${x}, ${y}, ${width}, ${height}));
})
})();`, result => resolve(result === "true"));
});
}
static currentImageAnalysisRequestID()
{
if (!this.isWebKit2())
return Promise.resolve(0);
return new Promise(resolve => {
testRunner.runUIScript("uiController.uiScriptComplete(uiController.currentImageAnalysisRequestID)", result => resolve(result));
});
}
static installFakeMachineReadableCodeResultsForImageAnalysis()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript("uiController.installFakeMachineReadableCodeResultsForImageAnalysis()", resolve);
});
}
static moveToNextByKeyboardAccessoryBar()
{
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.keyboardAccessoryBarNext();
uiController.uiScriptComplete();
`, resolve);
});
}
static moveToPreviousByKeyboardAccessoryBar()
{
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.keyboardAccessoryBarPrevious();
uiController.uiScriptComplete();
`, resolve);
});
}
static waitForContactPickerToShow()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (!uiController.isShowingContactPicker)
uiController.didShowContactPickerCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static waitForContactPickerToHide()
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`
(function() {
if (uiController.isShowingContactPicker)
uiController.didHideContactPickerCallback = () => uiController.uiScriptComplete();
else
uiController.uiScriptComplete();
})()`, resolve);
});
}
static dismissContactPickerWithContacts(contacts)
{
const script = `(() => uiController.dismissContactPickerWithContacts(${JSON.stringify(contacts)}))()`;
return new Promise(resolve => testRunner.runUIScript(script, resolve));
}
static setAppAccentColor(red, green, blue)
{
if (!this.isWebKit2() || !this.isMac())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.setAppAccentColor(${red}, ${green}, ${blue});
})()`, resolve);
});
}
static async cookiesForDomain(domain)
{
if (!this.isWebKit2())
return Promise.resolve([ ]);
const script = `uiController.cookiesForDomain("${domain}", cookieData => {
uiController.uiScriptComplete(JSON.stringify(cookieData));
});`;
return new Promise(resolve => {
testRunner.runUIScript(script, cookieData => {
resolve(JSON.parse(cookieData));
});
});
}
static cancelFixedColorExtensionFadeAnimations()
{
if (!this.isWebKit2())
return Promise.resolve();
const script = "uiController.cancelFixedColorExtensionFadeAnimations()";
return new Promise(resolve => testRunner.runUIScript(script, resolve));
}
static fixedContainerEdgeColors()
{
if (!this.isWebKit2())
return { };
const script = "JSON.stringify(uiController.fixedContainerEdgeColors)";
return new Promise(resolve => testRunner.runUIScript(script, colors => {
resolve(JSON.parse(colors));
}));
}
static async waitForFixedContainerEdgeColors(expectedColors)
{
while (true) {
await this.renderingUpdate();
const colors = await this.fixedContainerEdgeColors();
for (const edge of ["top", "left", "right", "bottom"]) {
if (colors[edge] !== expectedColors[edge])
continue;
}
break;
}
}
static addChromeInputField()
{
return new Promise(resolve => testRunner.addChromeInputField(resolve));
}
static removeChromeInputField()
{
return new Promise(resolve => testRunner.removeChromeInputField(resolve));
}
static setTextInChromeInputField(text)
{
return new Promise(resolve => testRunner.setTextInChromeInputField(text, resolve));
}
static selectChromeInputField()
{
return new Promise(resolve => testRunner.selectChromeInputField(resolve));
}
static getSelectedTextInChromeInputField()
{
return new Promise(resolve => testRunner.getSelectedTextInChromeInputField(resolve));
}
static requestTextExtraction(options)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.requestTextExtraction(
result => uiController.uiScriptComplete(result),
${JSON.stringify(options)}
);
})()`, resolve);
});
}
static requestRenderedTextForFrontmostTarget(x, y)
{
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.requestRenderedTextForFrontmostTarget(${x}, ${y}, result => uiController.uiScriptComplete(result));
})()`, resolve);
});
}
static adjustVisibilityForFrontmostTarget(x, y) {
if (!this.isWebKit2())
return Promise.resolve();
if (x instanceof HTMLElement) {
const point = this.midPointOfRect(x.getBoundingClientRect());
x = point.x;
y = point.y;
}
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.adjustVisibilityForFrontmostTarget(${x}, ${y}, result => uiController.uiScriptComplete(result || ""));
})()`, resolve);
});
}
static resetVisibilityAdjustments() {
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript("uiController.resetVisibilityAdjustments(result => uiController.uiScriptComplete(result));", resolve);
});
}
static async waitForPDFFadeIn()
{
const pdfFadeInDelay = 250;
await new Promise(resolve => setTimeout(resolve, pdfFadeInDelay));
await new Promise(requestAnimationFrame);
}
static async frontmostViewAtPoint(x, y) {
if (!this.isWebKit2())
return Promise.resolve();
return new Promise(resolve => {
testRunner.runUIScript(`uiController.frontmostViewAtPoint(${x}, ${y})`, resolve);
});
}
static async keyboardUpdateForChangedSelectionCount() {
if (!this.isWebKit2())
return 0;
return new Promise(resolve => {
testRunner.runUIScript("uiController.keyboardUpdateForChangedSelectionCount", resolve);
});
}
static async typeCharacters(stringToType, waitForEvent = "input", eventTarget = null) {
for (let character of [...stringToType]) {
await UIHelper.callFunctionAndWaitForEvent(async () => {
if (window.testRunner)
await UIHelper.typeCharacter(character);
await UIHelper.ensurePresentationUpdate();
}, eventTarget || document.activeElement, waitForEvent);
}
}
static async requestDebugText(options = { })
{
if (!this.isWebKit2())
return Promise.resolve("");
return new Promise(resolve => {
testRunner.runUIScript(`(() => {
uiController.requestDebugText(result => {
uiController.uiScriptComplete(result);
}, ${JSON.stringify(options)});
})()`, debugText => {
if (options.normalize) {
debugText = debugText
.replace(/uid=((\d+_)+)?(\d+)/g, "uid=…")
.replace(/"uid":\"((\d+_)+)?(\d+)\"/g, "\"uid\":\"…\"")
.replace(/contentSize=\[\d+×\d+\]/g, "contentSize=[…]")
.replace(/\[\d+,\d+;\d+×\d+\]/g, "[…]")
.replace(/\t/g, " ");
}
resolve(debugText);
});
});
}
static nodeIdentifierFromDebugText(text, searchTerm)
{
for (let line of text.split("\n")) {
if (!line.includes(searchTerm))
continue;
const match = line.match(/uid=(\d+)/);
if (match)
return match[1];
}
return null;
}
static async performTextExtractionInteraction(action, options)
{
if (!this.isWebKit2())
return Promise.resolve(false);
return new Promise(resolve => {
const scriptToRun = `uiController.performTextExtractionInteraction("${action}", ${JSON.stringify(options)}, result => {
uiController.uiScriptComplete(result)
})`;
testRunner.runUIScript(scriptToRun, resolve);
});
}
}
UIHelper.EventStreamBuilder = class {
constructor()
{
// FIXME: This could support additional customization options, such as interpolation, timestep, and different
// digitizer indices in the future. For now, just make it simpler to string together sequences of pan gestures.
this._reset();
}
_reset() {
this.events = [];
this.currentTimeOffset = 0;
this.currentX = 0;
this.currentY = 0;
}
begin(x, y) {
console.assert(this.currentTimeOffset === 0);
this.events.push({
interpolate : "linear",
timestep : 0.016,
coordinateSpace : "content",
startEvent : {
inputType : "hand",
timeOffset : this.currentTimeOffset,
touches : [{ inputType : "finger", phase : "began", id : 1, x : x, y : y, pressure : 0 }]
},
endEvent : {
inputType : "hand",
timeOffset : this.currentTimeOffset,
touches : [{ inputType : "finger", phase : "began", id : 1, x : x, y : y, pressure : 0 }]
}
});
this.currentX = x;
this.currentY = y;
return this;
}
wait(duration) {
this.currentTimeOffset += duration;
return this;
}
move(x, y, duration = 0) {
const previousTimeOffset = this.currentTimeOffset;
this.currentTimeOffset += duration;
this.events.push({
interpolate : "linear",
timestep : 0.016,
coordinateSpace : "content",
startEvent : {
inputType : "hand",
timeOffset : previousTimeOffset,
touches : [{ inputType : "finger", phase : "moved", id : 1, x : this.currentX, y : this.currentY, pressure : 0 }]
},
endEvent : {
inputType : "hand",
timeOffset : this.currentTimeOffset,
touches : [{ inputType : "finger", phase : "moved", id : 1, x : x, y : y, pressure : 0 }]
}
});
this.currentX = x;
this.currentY = y;
return this;
}
end() {
this.events.push({
interpolate : "linear",
timestep : 0.016,
coordinateSpace : "content",
startEvent : {
inputType : "hand",
timeOffset : this.currentTimeOffset,
touches : [{ inputType : "finger", phase : "ended", id : 1, x : this.currentX, y : this.currentY, pressure : 0 }]
},
endEvent : {
inputType : "hand",
timeOffset : this.currentTimeOffset,
touches : [{ inputType : "finger", phase : "ended", id : 1, x : this.currentX, y : this.currentY, pressure : 0 }]
}
});
return this;
}
takeResult() {
const events = this.events;
this._reset();
return { "events": events };
}
}