Skip to content

Commit 683f9e2

Browse files
committed
[iOS] WKWebView touch event gesture recognition should not block the application process main thread when possible
https://bugs.webkit.org/show_bug.cgi?id=204664 <rdar://problem/38670692> Reviewed by Tim Horton. Source/WebKit: Adds a mechanism that allows some sync touch events on iOS to be sent asynchronously. To do this, we use the deferring gesture mechanism introduced in trac.webkit.org/r253005 to defer all gestures under WKContentView (and WebKit-owned scroll views) when a touch starts, such that they will not recognize until we know that the page has either prevented default or not (assuming that the touch was over an active listener). See below for more details. Tests: fast/events/touch/ios/prevent-default-on-touch-start-with-slow-event-listener.html fast/events/touch/ios/scroll-on-touch-start-with-slow-event-listener.html * UIProcess/GenericCallback.h: * UIProcess/PageClient.h: * UIProcess/RemoteLayerTree/ios/RemoteLayerTreeViews.mm: (-[WKChildScrollView gestureRecognizer:shouldRequireFailureOfGestureRecognizer:]): (-[WKChildScrollView gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:]): Implement gesture recognizer delegate hooks to add dynamic failure requirements between a child scroll view's gestures and the new deferring gesture recognizers on WKContentView. This allows pan gestures over a scrollable container to hold off on recognizing while the deferring gesture recognizer has not failed yet. * UIProcess/WebPageProxy.cpp: (WebKit::WebPageProxy::handlePreventableTouchEvent): (WebKit::WebPageProxy::handleUnpreventableTouchEvent): Rename handleTouchEventSynchronously and handleTouchEventAsynchronously to handlePreventableTouchEvent and handleUnpreventableTouchEvent, respectively. Instead of always sending touchstart events that may prevent native gestures synchronously, we may now go through the same `EventDispatcher::TouchEvent` codepath used when dispatching touch events in passive tracking regions. However, in the case of preventable touchstarts, we additionally store a completion callback that is invoked after the touch event has been handled by the page; we then either un-defer or prevent native gestures here (depending on whether the page prevented default) by calling PageClient::doneDeferringNativeGestures. Non-touchstart events are still dispatched synchronously, to ensure that calling preventDefault() on touchmove and touchend continue to prevent default gestures from recognizing. (WebKit::WebPageProxy::boolCallback): (WebKit::WebPageProxy::handleTouchEventSynchronously): Deleted. (WebKit::WebPageProxy::handleTouchEventAsynchronously): Deleted. See above. * UIProcess/WebPageProxy.h: (WebKit::WebPageProxy::isHandlingPreventableTouchStart const): This is used in WKContentView to determine whether deferring gestures need to remain active after the touch ends. See below for more detail. * UIProcess/WebPageProxy.messages.in: * UIProcess/ios/PageClientImplIOS.h: * UIProcess/ios/PageClientImplIOS.mm: (WebKit::PageClientImpl::doneDeferringNativeGestures): * UIProcess/ios/WKContentViewInteraction.h: * UIProcess/ios/WKContentViewInteraction.mm: (-[UIGestureRecognizer _wk_cancel]): (-[WKContentView setupInteraction]): (-[WKContentView cleanupInteraction]): (-[WKContentView _removeDefaultGestureRecognizers]): (-[WKContentView _addDefaultGestureRecognizers]): Add and remove the new deferring gesture recognizers here. (-[WKContentView _webTouchEventsRecognized:]): (-[WKContentView _webTouchEvent:preventsNativeGestures:]): (-[WKContentView _doneDeferringNativeGestures:]): (-[WKContentView gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:]): (-[WKContentView ensurePositionInformationIsUpToDate:]): Drive-by fix: add a missing hasRunningProcess check that causes a flaky assertion under `AuxiliaryProcessProxy::connection()` in layout tests. (-[WKContentView gestureRecognizer:shouldRequireFailureOfGestureRecognizer:]): (-[WKContentView gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:]): Add dynamic failure requirements between WKContentView's gestures (including all text interaction, context menu, and drag and drop gestures) and the new deferring gesture recognizers. (-[WKContentView _didStartProvisionalLoadForMainFrame]): Force the two-finger double tap gesture recognizer to reset when loading a new page. Without this, the layout test fast/events/ios/click-event-while-editing-node.html will rarely fail when run after a test that dispatches a two-finger tap, such as fast/events/ios/click-event-two-finger-single-tap-meta-key.html. This is because the new deferring gestures will temporarily unite multi-finger tap gestures with one-finger double tap gestures in the same subgraph when performing a tap gesture with more than one finger. This means that there's a 300 ms delay before a normal single tap can be recognized again, which (without forcing the two-finger double tap to reset) would cause a subsequent test that loads in under 300 ms and attempts to send a tap to fail. (-[WKContentView deferringGestureRecognizer:shouldDeferGesturesAfterBeginningTouchesWithEvent:]): Avoid deferring native gestures if the scroll view is decelerating; this matches behavior of the web touch event gesture recognizer. (-[WKContentView deferringGestureRecognizer:shouldDeferGesturesAfterEndingTouchesWithEvent:]): Normally, after -touchesEnded:withEvent:, we stop deferring native gesture recognizers by failing the deferring gestures. However, if we're still waiting for a response from the web process, then let -_doneDeferringNativeGestures: handle this instead. (-[WKContentView deferringGestureRecognizer:shouldDeferOtherGestureRecognizer:]): (-[WKContentView deferringGestureRecognizer:shouldDeferGesturesWithEvent:]): Deleted. Renamed to -shouldDeferGesturesAfterBeginningTouchesWithEvent:. * UIProcess/ios/WKDeferringGestureRecognizer.h: * UIProcess/ios/WKDeferringGestureRecognizer.mm: (-[WKDeferringGestureRecognizer touchesBegan:withEvent:]): (-[WKDeferringGestureRecognizer touchesEnded:withEvent:]): Override this and add a new delegate hook to determine whether we want the deferring gesture recognizer to immediately fail when touches end. It's important to override this and transition to failure state in this case, since not doing so could mean that the deferring gestures stay in Possible state forever; this may lead to the gesture subgraph containing these deferring gestures being unable to reset, since it's waiting for the deferring gesture to either fail or end. * UIProcess/ios/WKScrollView.mm: (-[WKScrollView gestureRecognizer:shouldRequireFailureOfGestureRecognizer:]): (-[WKScrollView gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:]): Defer more scroll view gestures. * WebProcess/WebPage/EventDispatcher.cpp: (WebKit::EventDispatcher::touchEvent): Add an optional CallbackID parameter to this IPC message. If a callback ID is given, then we avoid coalescing the touch event. To implement this, we additionally refactor the queued touch events map to contain lists of <WebTouchEvent, Optional<CallbackID>> pairs; if a queued touch event has a corresponding CallbackID, then we fire the callback corresponding to the ID, indicating whether the touch event was handled by the page. * WebProcess/WebPage/EventDispatcher.h: * WebProcess/WebPage/EventDispatcher.messages.in: * WebProcess/WebPage/WebPage.h: * WebProcess/WebPage/ios/WebPageIOS.mm: (WebKit::WebPage::dispatchAsynchronousTouchEvents): LayoutTests: * fast/events/touch/ios/prevent-default-on-touch-start-with-slow-event-listener-expected.txt: Added. * fast/events/touch/ios/prevent-default-on-touch-start-with-slow-event-listener.html: Added. * fast/events/touch/ios/scroll-on-touch-start-with-slow-event-listener-expected.txt: Added. * fast/events/touch/ios/scroll-on-touch-start-with-slow-event-listener.html: Added. Add new layout tests to cover behaviors when panning over active touchstart handlers that spin for an extended length of time (in this case, 400 milliseconds) in overflow scrolling containers. A touchstart handler that prevents default should still block scrolling, and a touchstart handler that does not should still allow the user to scroll. * fast/events/touch/ios/show-modal-alert-during-touch-start.html: * http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt: * http/tests/security/anchor-download-block-crossorigin-expected.txt: Rebaseline these tests by changing some line numbers. * resources/ui-helper.js: (window.UIHelper.sendEventStream.return.new.Promise): (window.UIHelper.sendEventStream): Add a new UIHelper method to send a JSON object as an event stream. (UIHelper.EventStreamBuilder.prototype._reset): (UIHelper.EventStreamBuilder.prototype.begin): (UIHelper.EventStreamBuilder.prototype.move): (UIHelper.EventStreamBuilder.prototype.end): (UIHelper.EventStreamBuilder.prototype.takeResult): Add a new helper class to make it easier to construct event streams, for the purposes of sending to UIScriptController::sendEventStream. Canonical link: https://commits.webkit.org/218214@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@253267 268f45cc-cd09-0410-ab3c-d52691b4dbfc
1 parent 588bfb0 commit 683f9e2

28 files changed

Lines changed: 729 additions & 64 deletions

LayoutTests/ChangeLog

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,42 @@
1+
2019-12-07 Wenson Hsieh <[email protected]>
2+
3+
[iOS] WKWebView touch event gesture recognition should not block the application process main thread when possible
4+
https://bugs.webkit.org/show_bug.cgi?id=204664
5+
<rdar://problem/38670692>
6+
7+
Reviewed by Tim Horton.
8+
9+
* fast/events/touch/ios/prevent-default-on-touch-start-with-slow-event-listener-expected.txt: Added.
10+
* fast/events/touch/ios/prevent-default-on-touch-start-with-slow-event-listener.html: Added.
11+
* fast/events/touch/ios/scroll-on-touch-start-with-slow-event-listener-expected.txt: Added.
12+
* fast/events/touch/ios/scroll-on-touch-start-with-slow-event-listener.html: Added.
13+
14+
Add new layout tests to cover behaviors when panning over active touchstart handlers that spin for an extended
15+
length of time (in this case, 400 milliseconds) in overflow scrolling containers. A touchstart handler that
16+
prevents default should still block scrolling, and a touchstart handler that does not should still allow the
17+
user to scroll.
18+
19+
* fast/events/touch/ios/show-modal-alert-during-touch-start.html:
20+
* http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt:
21+
* http/tests/security/anchor-download-block-crossorigin-expected.txt:
22+
23+
Rebaseline these tests by changing some line numbers.
24+
25+
* resources/ui-helper.js:
26+
(window.UIHelper.sendEventStream.return.new.Promise):
27+
(window.UIHelper.sendEventStream):
28+
29+
Add a new UIHelper method to send a JSON object as an event stream.
30+
31+
(UIHelper.EventStreamBuilder.prototype._reset):
32+
(UIHelper.EventStreamBuilder.prototype.begin):
33+
(UIHelper.EventStreamBuilder.prototype.move):
34+
(UIHelper.EventStreamBuilder.prototype.end):
35+
(UIHelper.EventStreamBuilder.prototype.takeResult):
36+
37+
Add a new helper class to make it easier to construct event streams, for the purposes of sending to
38+
UIScriptController::sendEventStream.
39+
140
2019-12-07 Ryosuke Niwa <[email protected]>
241

342
Unique origin's window must get its own event loop
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Verifies that calling preventDefault() on touchstart prevents scrolling when the touch event handler is unresponsive for an extended duration. To manually test, attempt to scroll the box; check that touchstart and touchend events were handled, and that the box did not scroll.
2+
3+
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
4+
5+
PASS Observed touchstart event
6+
PASS Observed touchend event
7+
PASS successfullyParsed is true
8+
9+
TEST COMPLETE
10+
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true internal:AsyncOverflowScrollingEnabled=true ] -->
2+
<html>
3+
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
4+
<head>
5+
<script src="../../../../resources/js-test.js"></script>
6+
<script src="../../../../resources/ui-helper.js"></script>
7+
<style>
8+
html, body {
9+
width: 100%;
10+
height: 100%;
11+
margin: 0;
12+
}
13+
14+
#scroller {
15+
overflow: scroll;
16+
width: 200px;
17+
height: 200px;
18+
border: 2px solid black;
19+
}
20+
21+
#content {
22+
width: 100%;
23+
height: 1000px;
24+
}
25+
</style>
26+
<script>
27+
jsTestIsAsync = true;
28+
29+
async function runTest() {
30+
description("Verifies that calling preventDefault() on touchstart prevents scrolling when the touch event handler is unresponsive for an extended duration. To manually test, attempt to scroll the box; check that touchstart and touchend events were handled, and that the box did not scroll.");
31+
32+
const scroller = document.getElementById("scroller");
33+
34+
scroller.addEventListener("touchstart", event => {
35+
const startTime = Date.now();
36+
while (1) {
37+
if (Date.now() - startTime > 400)
38+
break;
39+
}
40+
testPassed("Observed touchstart event");
41+
event.preventDefault();
42+
});
43+
44+
scroller.addEventListener("touchend", () => {
45+
testPassed("Observed touchend event");
46+
finishJSTest();
47+
});
48+
scroller.addEventListener("scroll", () => testFailed("Observed scroll event"), { once : true });
49+
50+
if (!window.testRunner)
51+
return;
52+
53+
const eventStreamData = new UIHelper.EventStreamBuilder()
54+
.begin(100, 190)
55+
.move(100, 10, 1)
56+
.move(100, 190, 1)
57+
.move(100, 100, 0.5)
58+
.end()
59+
.takeResult();
60+
61+
await UIHelper.sendEventStream(eventStreamData);
62+
}
63+
64+
addEventListener("load", runTest);
65+
</script>
66+
</head>
67+
<body>
68+
<div id="scroller">
69+
<div id="content"></div>
70+
</div>
71+
<pre id="description"></pre>
72+
<pre id="console"></pre>
73+
</body>
74+
</html>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Verifies that the user can scroll even when the touch event handler is unresponsive for an extended duration. To manually test, attempt to scroll the box; check that touchstart and touchend events were handled, and that the box was scrolled.
2+
3+
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
4+
5+
PASS Observed touchstart event
6+
PASS Observed scroll event
7+
PASS Observed touchend event
8+
PASS successfullyParsed is true
9+
10+
TEST COMPLETE
11+
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true internal:AsyncOverflowScrollingEnabled=true ] -->
2+
<html>
3+
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
4+
<head>
5+
<script src="../../../../resources/js-test.js"></script>
6+
<script src="../../../../resources/ui-helper.js"></script>
7+
<style>
8+
html, body {
9+
width: 100%;
10+
height: 100%;
11+
margin: 0;
12+
}
13+
14+
#scroller {
15+
overflow: scroll;
16+
width: 200px;
17+
height: 200px;
18+
border: 2px solid black;
19+
}
20+
21+
#content {
22+
width: 100%;
23+
height: 1000px;
24+
}
25+
</style>
26+
<script>
27+
jsTestIsAsync = true;
28+
29+
async function runTest() {
30+
description("Verifies that the user can scroll even when the touch event handler is unresponsive for an extended duration. To manually test, attempt to scroll the box; check that touchstart and touchend events were handled, and that the box was scrolled.");
31+
32+
const scroller = document.getElementById("scroller");
33+
34+
scroller.addEventListener("touchstart", event => {
35+
const startTime = Date.now();
36+
while (1) {
37+
if (Date.now() - startTime > 400)
38+
break;
39+
}
40+
testPassed("Observed touchstart event");
41+
});
42+
43+
scroller.addEventListener("touchend", () => {
44+
testPassed("Observed touchend event");
45+
finishJSTest();
46+
});
47+
scroller.addEventListener("scroll", () => testPassed("Observed scroll event"), { once : true });
48+
49+
if (!window.testRunner)
50+
return;
51+
52+
const eventStreamData = new UIHelper.EventStreamBuilder()
53+
.begin(100, 190)
54+
.move(100, 10, 1)
55+
.move(100, 190, 1)
56+
.move(100, 100, 0.5)
57+
.end()
58+
.takeResult();
59+
60+
await UIHelper.sendEventStream(eventStreamData);
61+
}
62+
63+
addEventListener("load", runTest);
64+
</script>
65+
</head>
66+
<body>
67+
<div id="scroller">
68+
<div id="content"></div>
69+
</div>
70+
<pre id="description"></pre>
71+
<pre id="console"></pre>
72+
</body>
73+
</html>

LayoutTests/fast/events/touch/ios/show-modal-alert-during-touch-start.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
}
3232

3333
target.addEventListener("touchstart", () => alert("This is a modal alert."));
34-
target.addEventListener("touchend", () => testRunner.notifyDone());
34+
target.addEventListener("touchend", () => testRunner.notifyDone(), { passive : true });
3535
addEventListener("load", async () => await UIHelper.activateAt(100, 100));
3636
</script>
3737
</body>

LayoutTests/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
CONSOLE MESSAGE: line 155: adcampaignid must have a non-negative value less than or equal to 63 for Ad Click Attribution.
2-
CONSOLE MESSAGE: line 155: adcampaignid must have a non-negative value less than or equal to 63 for Ad Click Attribution.
3-
CONSOLE MESSAGE: line 155: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
4-
CONSOLE MESSAGE: line 155: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
5-
CONSOLE MESSAGE: line 155: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
6-
CONSOLE MESSAGE: line 155: addestination could not be converted to a valid HTTP-family URL.
7-
CONSOLE MESSAGE: line 155: addestination could not be converted to a valid HTTP-family URL.
8-
CONSOLE MESSAGE: line 155: addestination could not be converted to a valid HTTP-family URL.
9-
CONSOLE MESSAGE: line 155: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
10-
CONSOLE MESSAGE: line 155: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
11-
CONSOLE MESSAGE: line 155: addestination can not be the same site as the current website.
1+
CONSOLE MESSAGE: line 169: adcampaignid must have a non-negative value less than or equal to 63 for Ad Click Attribution.
2+
CONSOLE MESSAGE: line 169: adcampaignid must have a non-negative value less than or equal to 63 for Ad Click Attribution.
3+
CONSOLE MESSAGE: line 169: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
4+
CONSOLE MESSAGE: line 169: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
5+
CONSOLE MESSAGE: line 169: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
6+
CONSOLE MESSAGE: line 169: addestination could not be converted to a valid HTTP-family URL.
7+
CONSOLE MESSAGE: line 169: addestination could not be converted to a valid HTTP-family URL.
8+
CONSOLE MESSAGE: line 169: addestination could not be converted to a valid HTTP-family URL.
9+
CONSOLE MESSAGE: line 169: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
10+
CONSOLE MESSAGE: line 169: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
11+
CONSOLE MESSAGE: line 169: addestination can not be the same site as the current website.
1212
Test for validity of ad click attribution attributes on anchor tags.
1313

1414
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".

LayoutTests/http/tests/security/anchor-download-block-crossorigin-expected.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CONSOLE MESSAGE: line 155: The download attribute on anchor was ignored because its href URL has a different security origin.
1+
CONSOLE MESSAGE: line 169: The download attribute on anchor was ignored because its href URL has a different security origin.
22
Tests that the download attribute is ignored if the link is cross origin.
33

44
It should navigate the subframe instead of downloading the file.

LayoutTests/resources/ui-helper.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ window.UIHelper = class UIHelper {
2929
eventSender.mouseUp();
3030
}
3131

32+
static sendEventStream(eventStream)
33+
{
34+
const eventStreamAsString = JSON.stringify(eventStream);
35+
return new Promise(resolve => {
36+
testRunner.runUIScript(`
37+
(function() {
38+
uiController.sendEventStream(\`${eventStreamAsString}\`, () => {
39+
uiController.uiScriptComplete();
40+
});
41+
})();
42+
`, resolve);
43+
});
44+
}
45+
3246
static tapAt(x, y, modifiers=[])
3347
{
3448
console.assert(this.isIOSFamily());
@@ -1105,3 +1119,89 @@ window.UIHelper = class UIHelper {
11051119
});
11061120
}
11071121
}
1122+
1123+
UIHelper.EventStreamBuilder = class {
1124+
constructor()
1125+
{
1126+
// FIXME: This could support additional customization options, such as interpolation, timestep, and different
1127+
// digitizer indices in the future. For now, just make it simpler to string together sequences of pan gestures.
1128+
this._reset();
1129+
}
1130+
1131+
_reset() {
1132+
this.events = [];
1133+
this.currentTimeOffset = 0;
1134+
this.currentX = 0;
1135+
this.currentY = 0;
1136+
}
1137+
1138+
begin(x, y) {
1139+
console.assert(this.currentTimeOffset === 0);
1140+
this.events.push({
1141+
interpolate : "linear",
1142+
timestep : 0.016,
1143+
coordinateSpace : "content",
1144+
startEvent : {
1145+
inputType : "hand",
1146+
timeOffset : this.currentTimeOffset,
1147+
touches : [{ inputType : "finger", phase : "began", id : 1, x : x, y : y, pressure : 0 }]
1148+
},
1149+
endEvent : {
1150+
inputType : "hand",
1151+
timeOffset : this.currentTimeOffset,
1152+
touches : [{ inputType : "finger", phase : "began", id : 1, x : x, y : y, pressure : 0 }]
1153+
}
1154+
});
1155+
this.currentX = x;
1156+
this.currentY = y;
1157+
return this;
1158+
}
1159+
1160+
move(x, y, duration = 0) {
1161+
const previousTimeOffset = this.currentTimeOffset;
1162+
this.currentTimeOffset += duration;
1163+
this.events.push({
1164+
interpolate : "linear",
1165+
timestep : 0.016,
1166+
coordinateSpace : "content",
1167+
startEvent : {
1168+
inputType : "hand",
1169+
timeOffset : previousTimeOffset,
1170+
touches : [{ inputType : "finger", phase : "moved", id : 1, x : this.currentX, y : this.currentY, pressure : 0 }]
1171+
},
1172+
endEvent : {
1173+
inputType : "hand",
1174+
timeOffset : this.currentTimeOffset,
1175+
touches : [{ inputType : "finger", phase : "moved", id : 1, x : x, y : y, pressure : 0 }]
1176+
}
1177+
});
1178+
this.currentX = x;
1179+
this.currentY = y;
1180+
return this;
1181+
}
1182+
1183+
end() {
1184+
this.events.push({
1185+
interpolate : "linear",
1186+
timestep : 0.016,
1187+
coordinateSpace : "content",
1188+
startEvent : {
1189+
inputType : "hand",
1190+
timeOffset : this.currentTimeOffset,
1191+
touches : [{ inputType : "finger", phase : "ended", id : 1, x : this.currentX, y : this.currentY, pressure : 0 }]
1192+
},
1193+
endEvent : {
1194+
inputType : "hand",
1195+
timeOffset : this.currentTimeOffset,
1196+
touches : [{ inputType : "finger", phase : "ended", id : 1, x : this.currentX, y : this.currentY, pressure : 0 }]
1197+
}
1198+
});
1199+
return this;
1200+
}
1201+
1202+
takeResult() {
1203+
const events = this.events;
1204+
this._reset();
1205+
return { "events": events };
1206+
}
1207+
}

0 commit comments

Comments
 (0)