<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[206798] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/206798">206798</a></dd>
<dt>Author</dt> <dd>simon.fraser@apple.com</dd>
<dt>Date</dt> <dd>2016-10-04 17:23:51 -0700 (Tue, 04 Oct 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>[iOS WK2] Make it possible for a test to describe a user gesture as a stream of events in JSON format
https://bugs.webkit.org/show_bug.cgi?id=162934

Reviewed by Dean Jackson.

Tools:

With this change, a test can describe a user gesture in an &quot;event stream&quot;, which is
some JSON describing an array of events with their underlying touches. The added
test describes a single tap.

The implementation fires up an NSThread, and sleeps the thread between events to dispatch
them at close to real time.

In future, HIDEventGenerator could use this internally for all of the &quot;compound&quot; interactions.

* DumpRenderTree/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::sendEventStream):
* TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
* TestRunnerShared/UIScriptContext/UIScriptController.cpp:
(WTR::UIScriptController::sendEventStream):
* TestRunnerShared/UIScriptContext/UIScriptController.h:
* WebKitTestRunner/ios/HIDEventGenerator.h:
* WebKitTestRunner/ios/HIDEventGenerator.mm:
(transducerTypeFromString):
(phaseFromString):
(-[HIDEventGenerator eventMaskFromEventInfo:]):
(-[HIDEventGenerator touchFromEventInfo:]):
(-[HIDEventGenerator _createIOHIDEventWithInfo:]):
(-[HIDEventGenerator dispatchEventWithInfo:]):
(-[HIDEventGenerator eventDispatchThreadEntry:]):
(-[HIDEventGenerator sendEventStream:completionBlock:]):
* WebKitTestRunner/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::sendEventStream):

LayoutTests:

* fast/events/ios/event-stream-single-tap-expected.txt: Added.
* fast/events/ios/event-stream-single-tap.html: Added.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkToolsChangeLog">trunk/Tools/ChangeLog</a></li>
<li><a href="#trunkToolsDumpRenderTreeiosUIScriptControllerIOSmm">trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm</a></li>
<li><a href="#trunkToolsTestRunnerSharedUIScriptContextBindingsUIScriptControlleridl">trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl</a></li>
<li><a href="#trunkToolsTestRunnerSharedUIScriptContextUIScriptControllercpp">trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp</a></li>
<li><a href="#trunkToolsTestRunnerSharedUIScriptContextUIScriptControllerh">trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h</a></li>
<li><a href="#trunkToolsWebKitTestRunneriosHIDEventGeneratorh">trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.h</a></li>
<li><a href="#trunkToolsWebKitTestRunneriosHIDEventGeneratormm">trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.mm</a></li>
<li><a href="#trunkToolsWebKitTestRunneriosIOKitSPIh">trunk/Tools/WebKitTestRunner/ios/IOKitSPI.h</a></li>
<li><a href="#trunkToolsWebKitTestRunneriosUIScriptControllerIOSmm">trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsfasteventsioseventstreamsingletapexpectedtxt">trunk/LayoutTests/fast/events/ios/event-stream-single-tap-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfasteventsioseventstreamsingletaphtml">trunk/LayoutTests/fast/events/ios/event-stream-single-tap.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (206797 => 206798)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2016-10-05 00:15:39 UTC (rev 206797)
+++ trunk/LayoutTests/ChangeLog        2016-10-05 00:23:51 UTC (rev 206798)
</span><span class="lines">@@ -1,3 +1,13 @@
</span><ins>+2016-10-04  Simon Fraser  &lt;simon.fraser@apple.com&gt;
+
+        [iOS WK2] Make it possible for a test to describe a user gesture as a stream of events in JSON format
+        https://bugs.webkit.org/show_bug.cgi?id=162934
+
+        Reviewed by Dean Jackson.
+
+        * fast/events/ios/event-stream-single-tap-expected.txt: Added.
+        * fast/events/ios/event-stream-single-tap.html: Added.
+
</ins><span class="cx"> 2016-10-04  Chris Dumez  &lt;cdumez@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Add support for KeyboardEvent.isComposing attribute
</span></span></pre></div>
<a id="trunkLayoutTestsfasteventsioseventstreamsingletapexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/events/ios/event-stream-single-tap-expected.txt (0 => 206798)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/events/ios/event-stream-single-tap-expected.txt                                (rev 0)
+++ trunk/LayoutTests/fast/events/ios/event-stream-single-tap-expected.txt        2016-10-05 00:23:51 UTC (rev 206798)
</span><span class="lines">@@ -0,0 +1 @@
</span><ins>+PASS: received click.
</ins></span></pre></div>
<a id="trunkLayoutTestsfasteventsioseventstreamsingletaphtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/events/ios/event-stream-single-tap.html (0 => 206798)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/events/ios/event-stream-single-tap.html                                (rev 0)
+++ trunk/LayoutTests/fast/events/ios/event-stream-single-tap.html        2016-10-05 00:23:51 UTC (rev 206798)
</span><span class="lines">@@ -0,0 +1,86 @@
</span><ins>+&lt;!DOCTYPE html&gt; &lt;!-- webkit-test-runner [ useFlexibleViewport=true ] --&gt;
+
+&lt;html&gt;
+&lt;meta name=&quot;viewport&quot; content=&quot;initial-scale=1.0, width=device-width&quot;&gt;
+&lt;head&gt;
+    &lt;style&gt;
+        #target {
+            height: 100px;
+            width: 100px;
+            background-color: gray;
+        }
+    &lt;/style&gt;
+    &lt;script&gt;
+
+        if (window.testRunner) {
+            testRunner.dumpAsText();
+            testRunner.waitUntilDone();
+        }
+
+        function getUIScript(x, y)
+        {
+            return `
+            (function() {
+                var eventStream = {
+                    events : [
+                        {
+                            inputType : &quot;hand&quot;,
+                            timeOffset : 0,
+                            touches : [
+                                {
+                                    inputType : &quot;finger&quot;,
+                                    phase : &quot;began&quot;,
+                                    id : 1,
+                                    x : ${x},
+                                    y : ${y}
+                                }
+                            ]
+                        },
+                        {
+                            inputType : &quot;hand&quot;,
+                            timeOffset : 0.0005,
+                            touches : [
+                                {
+                                    inputType : &quot;finger&quot;,
+                                    phase : &quot;ended&quot;,
+                                    id : 1,
+                                    x : ${x},
+                                    y : ${y}
+                                }
+                            ]
+                        },
+                    ]
+                };
+
+                uiController.sendEventStream(JSON.stringify(eventStream), function() {
+                    uiController.uiScriptComplete();
+                });
+            })();`
+        }
+        
+        function handleClick()
+        {
+            document.getElementById(&quot;result&quot;).textContent = &quot;PASS: received click.&quot;;
+            if (window.testRunner)
+                testRunner.notifyDone();
+        }
+
+        function runTest()
+        {
+            var target = document.getElementById(&quot;target&quot;);
+            target.addEventListener(&quot;click&quot;, handleClick, false);
+
+            if (!testRunner.runUIScript)
+                return;
+
+            testRunner.runUIScript(getUIScript(60, 60), function() {});
+        }
+        
+        window.addEventListener(&quot;load&quot;, runTest, false);
+    &lt;/script&gt;
+&lt;/head&gt;
+&lt;body&gt;
+    &lt;div id=&quot;target&quot;&gt;&lt;/div&gt;
+    &lt;div id=&quot;result&quot;&gt;FAIL: did not receive click event.&lt;/div&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkToolsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Tools/ChangeLog (206797 => 206798)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/ChangeLog        2016-10-05 00:15:39 UTC (rev 206797)
+++ trunk/Tools/ChangeLog        2016-10-05 00:23:51 UTC (rev 206798)
</span><span class="lines">@@ -1,3 +1,38 @@
</span><ins>+2016-10-04  Simon Fraser  &lt;simon.fraser@apple.com&gt;
+
+        [iOS WK2] Make it possible for a test to describe a user gesture as a stream of events in JSON format
+        https://bugs.webkit.org/show_bug.cgi?id=162934
+
+        Reviewed by Dean Jackson.
+
+        With this change, a test can describe a user gesture in an &quot;event stream&quot;, which is
+        some JSON describing an array of events with their underlying touches. The added
+        test describes a single tap.
+        
+        The implementation fires up an NSThread, and sleeps the thread between events to dispatch
+        them at close to real time.
+        
+        In future, HIDEventGenerator could use this internally for all of the &quot;compound&quot; interactions.
+
+        * DumpRenderTree/ios/UIScriptControllerIOS.mm:
+        (WTR::UIScriptController::sendEventStream):
+        * TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
+        * TestRunnerShared/UIScriptContext/UIScriptController.cpp:
+        (WTR::UIScriptController::sendEventStream):
+        * TestRunnerShared/UIScriptContext/UIScriptController.h:
+        * WebKitTestRunner/ios/HIDEventGenerator.h:
+        * WebKitTestRunner/ios/HIDEventGenerator.mm:
+        (transducerTypeFromString):
+        (phaseFromString):
+        (-[HIDEventGenerator eventMaskFromEventInfo:]):
+        (-[HIDEventGenerator touchFromEventInfo:]):
+        (-[HIDEventGenerator _createIOHIDEventWithInfo:]):
+        (-[HIDEventGenerator dispatchEventWithInfo:]):
+        (-[HIDEventGenerator eventDispatchThreadEntry:]):
+        (-[HIDEventGenerator sendEventStream:completionBlock:]):
+        * WebKitTestRunner/ios/UIScriptControllerIOS.mm:
+        (WTR::UIScriptController::sendEventStream):
+
</ins><span class="cx"> 2016-10-04  Megan Gardner  &lt;megan_gardner@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Add Megan to contributor's list
</span></span></pre></div>
<a id="trunkToolsDumpRenderTreeiosUIScriptControllerIOSmm"></a>
<div class="modfile"><h4>Modified: trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm (206797 => 206798)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm        2016-10-05 00:15:39 UTC (rev 206797)
+++ trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm        2016-10-05 00:23:51 UTC (rev 206798)
</span><span class="lines">@@ -108,6 +108,10 @@
</span><span class="cx"> {
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void UIScriptController::sendEventStream(JSStringRef eventsJSON, JSValueRef callback)
+{
+}
+
</ins><span class="cx"> void UIScriptController::typeCharacterUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
</span><span class="cx"> {
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkToolsTestRunnerSharedUIScriptContextBindingsUIScriptControlleridl"></a>
<div class="modfile"><h4>Modified: trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl (206797 => 206798)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl        2016-10-05 00:15:39 UTC (rev 206797)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl        2016-10-05 00:23:51 UTC (rev 206798)
</span><span class="lines">@@ -49,6 +49,40 @@
</span><span class="cx">     void keyDownUsingHardwareKeyboard(DOMString character, object callback);
</span><span class="cx">     void keyUpUsingHardwareKeyboard(DOMString character, object callback);
</span><span class="cx"> 
</span><ins>+    // eventsJSON describes a series of user events in JSON form. For the keys, see HIDEventGenerator.mm.
+    // For example, this JSON describes a touch down followed by a touch up (i.e. a single tap).
+    //  {
+    //      &quot;events&quot; : [
+    //          {
+    //              &quot;inputType&quot; : &quot;hand&quot;,
+    //              &quot;timeOffset&quot; : 0,
+    //              &quot;touches&quot; : [
+    //                  {
+    //                      &quot;inputType&quot; : &quot;finger&quot;,
+    //                      &quot;phase&quot; : &quot;began&quot;,
+    //                      &quot;id&quot; : 1,
+    //                      &quot;x&quot; : 100,
+    //                      &quot;y&quot; : 120
+    //                  }
+    //              ]
+    //          },
+    //          {
+    //              &quot;inputType&quot; : &quot;hand&quot;,
+    //              &quot;timeOffset&quot; : 0.002, // seconds relative to the first event
+    //              &quot;touches&quot; : [
+    //                  {
+    //                      &quot;inputType&quot; : &quot;finger&quot;,
+    //                      &quot;phase&quot; : &quot;ended&quot;,
+    //                      &quot;id&quot; : 1,
+    //                      &quot;x&quot; : 100,
+    //                      &quot;y&quot; : 120
+    //                  }
+    //              ]
+    //          },
+    //      ]
+    //  }
+    void sendEventStream(DOMString eventsJSON, object callback);
+
</ins><span class="cx">     // Equivalent of pressing the Done button in the form accessory bar.
</span><span class="cx">     void dismissFormAccessoryView();
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkToolsTestRunnerSharedUIScriptContextUIScriptControllercpp"></a>
<div class="modfile"><h4>Modified: trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp (206797 => 206798)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp        2016-10-05 00:15:39 UTC (rev 206797)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp        2016-10-05 00:23:51 UTC (rev 206798)
</span><span class="lines">@@ -180,6 +180,10 @@
</span><span class="cx"> {
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void UIScriptController::sendEventStream(JSStringRef eventsJSON, JSValueRef callback)
+{
+}
+
</ins><span class="cx"> void UIScriptController::typeCharacterUsingHardwareKeyboard(JSStringRef, JSValueRef)
</span><span class="cx"> {
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkToolsTestRunnerSharedUIScriptContextUIScriptControllerh"></a>
<div class="modfile"><h4>Modified: trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h (206797 => 206798)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h        2016-10-05 00:15:39 UTC (rev 206797)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h        2016-10-05 00:23:51 UTC (rev 206798)
</span><span class="lines">@@ -63,7 +63,9 @@
</span><span class="cx">     void stylusTapAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback);
</span><span class="cx"> 
</span><span class="cx">     void longPressAtPoint(long x, long y, JSValueRef callback);
</span><del>-    
</del><ins>+
+    void sendEventStream(JSStringRef eventsJSON, JSValueRef callback);
+
</ins><span class="cx">     void typeCharacterUsingHardwareKeyboard(JSStringRef character, JSValueRef callback);
</span><span class="cx">     void keyDownUsingHardwareKeyboard(JSStringRef character, JSValueRef callback);
</span><span class="cx">     void keyUpUsingHardwareKeyboard(JSStringRef character, JSValueRef callback);
</span></span></pre></div>
<a id="trunkToolsWebKitTestRunneriosHIDEventGeneratorh"></a>
<div class="modfile"><h4>Modified: trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.h (206797 => 206798)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.h        2016-10-05 00:15:39 UTC (rev 206797)
+++ trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.h        2016-10-05 00:23:51 UTC (rev 206798)
</span><span class="lines">@@ -27,6 +27,32 @@
</span><span class="cx"> 
</span><span class="cx"> #import &lt;CoreGraphics/CGGeometry.h&gt;
</span><span class="cx"> 
</span><ins>+// Keys for sendEventStream:completionBlock:.
+extern NSString* const TopLevelEventInfoKey;
+extern NSString* const HIDEventInputType;
+extern NSString* const HIDEventTimeOffsetKey;
+extern NSString* const HIDEventPhaseKey;
+extern NSString* const HIDEventTouchIDKey;
+extern NSString* const HIDEventPressureKey;
+extern NSString* const HIDEventXKey;
+extern NSString* const HIDEventYKey;
+extern NSString* const HIDEventTwistKey;
+extern NSString* const HIDEventMajorRadiusKey;
+extern NSString* const HIDEventMinorRadiusKey;
+extern NSString* const HIDEventTouchesKey;
+
+// Values for HIDEventInputType.
+extern NSString* const HIDEventInputTypeHand;
+extern NSString* const HIDEventInputTypeFinger;
+extern NSString* const HIDEventInputTypeStylus;
+
+// Values for HIDEventPhaseKey.
+extern NSString* const HIDEventPhaseBegan;
+extern NSString* const HIDEventPhaseMoved;
+extern NSString* const HIDEventPhaseEnded;
+extern NSString* const HIDEventPhaseCanceled;
+
+
</ins><span class="cx"> @interface HIDEventGenerator : NSObject
</span><span class="cx"> 
</span><span class="cx"> + (HIDEventGenerator *)sharedHIDEventGenerator;
</span><span class="lines">@@ -56,6 +82,9 @@
</span><span class="cx"> - (void)pinchCloseWithStartPoint:(CGPoint)startLocation endPoint:(CGPoint)endLocation duration:(double)seconds completionBlock:(void (^)(void))completionBlock;
</span><span class="cx"> - (void)pinchOpenWithStartPoint:(CGPoint)startLocation endPoint:(CGPoint)endLocation duration:(double)seconds completionBlock:(void (^)(void))completionBlock;
</span><span class="cx"> 
</span><ins>+// Event stream
+- (void)sendEventStream:(NSDictionary *)eventInfo completionBlock:(void (^)(void))completionBlock;
+
</ins><span class="cx"> - (void)markerEventReceived:(IOHIDEventRef)event;
</span><span class="cx"> 
</span><span class="cx"> // Keyboard
</span></span></pre></div>
<a id="trunkToolsWebKitTestRunneriosHIDEventGeneratormm"></a>
<div class="modfile"><h4>Modified: trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.mm (206797 => 206798)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.mm        2016-10-05 00:15:39 UTC (rev 206797)
+++ trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.mm        2016-10-05 00:23:51 UTC (rev 206798)
</span><span class="lines">@@ -37,6 +37,28 @@
</span><span class="cx"> SOFT_LINK_PRIVATE_FRAMEWORK(BackBoardServices)
</span><span class="cx"> SOFT_LINK(BackBoardServices, BKSHIDEventSetDigitizerInfo, void, (IOHIDEventRef digitizerEvent, uint32_t contextID, uint8_t systemGestureisPossible, uint8_t isSystemGestureStateChangeEvent, CFStringRef displayUUID, CFTimeInterval initialTouchTimestamp, float maxForce), (digitizerEvent, contextID, systemGestureisPossible, isSystemGestureStateChangeEvent, displayUUID, initialTouchTimestamp, maxForce));
</span><span class="cx"> 
</span><ins>+NSString* const TopLevelEventInfoKey = @&quot;events&quot;;
+NSString* const HIDEventInputType = @&quot;inputType&quot;;
+NSString* const HIDEventTimeOffsetKey = @&quot;timeOffset&quot;;
+NSString* const HIDEventTouchesKey = @&quot;touches&quot;;
+NSString* const HIDEventPhaseKey = @&quot;phase&quot;;
+NSString* const HIDEventTouchIDKey = @&quot;id&quot;;
+NSString* const HIDEventPressureKey = @&quot;pressure&quot;;
+NSString* const HIDEventXKey = @&quot;x&quot;;
+NSString* const HIDEventYKey = @&quot;y&quot;;
+NSString* const HIDEventTwistKey = @&quot;twist&quot;;
+NSString* const HIDEventMajorRadiusKey = @&quot;majorRadius&quot;;
+NSString* const HIDEventMinorRadiusKey = @&quot;minorRadius&quot;;
+
+NSString* const HIDEventInputTypeHand = @&quot;hand&quot;;
+NSString* const HIDEventInputTypeFinger = @&quot;finger&quot;;
+NSString* const HIDEventInputTypeStylus = @&quot;stylus&quot;;
+
+NSString* const HIDEventPhaseBegan = @&quot;began&quot;;
+NSString* const HIDEventPhaseMoved = @&quot;moved&quot;;
+NSString* const HIDEventPhaseEnded = @&quot;ended&quot;;
+NSString* const HIDEventPhaseCanceled = @&quot;canceled&quot;;
+
</ins><span class="cx"> static const NSTimeInterval fingerLiftDelay = 0.05;
</span><span class="cx"> static const NSTimeInterval multiTapInterval = 0.15;
</span><span class="cx"> static const NSTimeInterval fingerMoveInterval = 0.016;
</span><span class="lines">@@ -146,6 +168,130 @@
</span><span class="cx">     [self _sendHIDEvent:eventRef.get()];
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+static IOHIDDigitizerTransducerType transducerTypeFromString(NSString * transducerTypeString)
+{
+    if ([transducerTypeString isEqualToString:HIDEventInputTypeHand])
+        return kIOHIDDigitizerTransducerTypeHand;
+
+    if ([transducerTypeString isEqualToString:HIDEventInputTypeFinger])
+        return kIOHIDDigitizerTransducerTypeFinger;
+
+    if ([transducerTypeString isEqualToString:HIDEventInputTypeStylus])
+        return kIOHIDDigitizerTransducerTypeStylus;
+    
+    ASSERT_NOT_REACHED();
+    return 0;
+}
+
+static UITouchPhase phaseFromString(NSString *string)
+{
+    if ([string isEqualToString:HIDEventPhaseBegan])
+        return UITouchPhaseBegan;
+
+    if ([string isEqualToString:HIDEventPhaseMoved])
+        return UITouchPhaseMoved;
+
+    if ([string isEqualToString:HIDEventPhaseEnded])
+        return UITouchPhaseEnded;
+
+    if ([string isEqualToString:HIDEventPhaseCanceled])
+        return UITouchPhaseCancelled;
+
+    return UITouchPhaseStationary;
+}
+
+- (IOHIDDigitizerEventMask)eventMaskFromEventInfo:(NSDictionary *)info
+{
+    NSArray *childEvents = info[HIDEventTouchesKey];
+    for (NSDictionary *touchInfo in childEvents) {
+        UITouchPhase phase = phaseFromString(touchInfo[HIDEventPhaseKey]);
+        // If there are any new or ended events, mask includes touch.
+        if (phase == UITouchPhaseBegan || phase == UITouchPhaseEnded || phase == UITouchPhaseCancelled)
+            return kIOHIDDigitizerEventTouch;
+    }
+    
+    return 0;
+}
+
+// Returns 1 for all events where the fingers are on the glass (everything but enced and canceled).
+- (CFIndex)touchFromEventInfo:(NSDictionary *)info
+{
+    NSArray *childEvents = info[HIDEventTouchesKey];
+    for (NSDictionary *touchInfo in childEvents) {
+        UITouchPhase phase = phaseFromString(touchInfo[HIDEventPhaseKey]);
+        if (phase == UITouchPhaseBegan || phase == UITouchPhaseMoved || phase == UITouchPhaseStationary)
+            return 1;
+    }
+    
+    return 0;
+}
+
+// FIXME: callers of _createIOHIDEventType could switch to this.
+- (IOHIDEventRef)_createIOHIDEventWithInfo:(NSDictionary *)info
+{
+    uint64_t machTime = mach_absolute_time();
+
+    IOHIDDigitizerEventMask eventMask = [self eventMaskFromEventInfo:info];
+
+    CFIndex range = 0;
+    // touch is 1 if a finger is down.
+    CFIndex touch = [self touchFromEventInfo:info];
+
+    IOHIDEventRef eventRef = IOHIDEventCreateDigitizerEvent(kCFAllocatorDefault, machTime,
+        transducerTypeFromString(info[HIDEventInputType]),  // transducerType
+        0,                                                  // index
+        0,                                                  // identifier
+        eventMask,                                          // event mask
+        0,                                                  // button event
+        0,                                                  // x
+        0,                                                  // y
+        0,                                                  // z
+        0,                                                  // presure
+        0,                                                  // twist
+        range,                                              // range
+        touch,                                              // touch
+        kIOHIDEventOptionNone);
+
+    IOHIDEventSetIntegerValue(eventRef, kIOHIDEventFieldDigitizerIsDisplayIntegrated, 1);
+
+    NSArray *childEvents = info[HIDEventTouchesKey];
+    for (NSDictionary *touchInfo in childEvents) {
+
+        IOHIDDigitizerEventMask childEventMask = 0;
+
+        UITouchPhase phase = phaseFromString(touchInfo[HIDEventPhaseKey]);
+        if (phase != UITouchPhaseCancelled &amp;&amp; phase != UITouchPhaseBegan &amp;&amp; phase != UITouchPhaseEnded)
+            childEventMask |= kIOHIDDigitizerEventPosition;
+
+        if (phase == UITouchPhaseBegan || phase == UITouchPhaseEnded || phase == UITouchPhaseCancelled)
+            childEventMask |= (kIOHIDDigitizerEventTouch | kIOHIDDigitizerEventRange);
+
+        if (phase == UITouchPhaseCancelled)
+            childEventMask |= kIOHIDDigitizerEventCancel;
+
+        IOHIDEventRef subEvent = IOHIDEventCreateDigitizerFingerEvent(kCFAllocatorDefault, machTime,
+            [touchInfo[HIDEventTouchIDKey] intValue],               // index
+            2,                                                      // identifier (which finger we think it is). FIXME: this should come from the data.
+            childEventMask,
+            [touchInfo[HIDEventXKey] floatValue],
+            [touchInfo[HIDEventYKey] floatValue],
+            0, // z
+            [touchInfo[HIDEventPressureKey] floatValue],
+            [touchInfo[HIDEventTwistKey] floatValue],
+            touch,                                                  // range
+            touch,                                                  // touch
+            kIOHIDEventOptionNone);
+
+        IOHIDEventSetFloatValue(subEvent, kIOHIDEventFieldDigitizerMajorRadius, [touchInfo[HIDEventMajorRadiusKey] floatValue]);
+        IOHIDEventSetFloatValue(subEvent, kIOHIDEventFieldDigitizerMinorRadius, [touchInfo[HIDEventMinorRadiusKey] floatValue]);
+
+        IOHIDEventAppendEvent(eventRef, subEvent, 0);
+        CFRelease(subEvent);
+    }
+
+    return eventRef;
+}
+
</ins><span class="cx"> - (IOHIDEventRef)_createIOHIDEventType:(HandEventType)eventType
</span><span class="cx"> {
</span><span class="cx">     BOOL isTouching = (eventType == HandEventTouched || eventType == HandEventMoved || eventType == HandEventChordChanged || eventType == StylusEventTouched || eventType == StylusEventMoved);
</span><span class="lines">@@ -766,4 +912,62 @@
</span><span class="cx">     [self _sendMarkerHIDEventWithCompletionBlock:completionBlock];
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+- (void)dispatchEventWithInfo:(NSDictionary *)eventInfo
+{
+    ASSERT([NSThread isMainThread]);
+
+    RetainPtr&lt;IOHIDEventRef&gt; eventRef = adoptCF([self _createIOHIDEventWithInfo:eventInfo]);
+    [self _sendHIDEvent:eventRef.get()];
+}
+
+- (void)eventDispatchThreadEntry:(NSDictionary *)threadData
+{
+    NSDictionary *eventStream = threadData[@&quot;eventInfo&quot;];
+    void (^completionBlock)() = threadData[@&quot;completionBlock&quot;];
+
+    NSArray *events = eventStream[TopLevelEventInfoKey];
+    if (!events.count) {
+        NSLog(@&quot;No events found in event stream&quot;);
+        return;
+    }
+
+    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
+    
+    for (NSDictionary *eventInfo in events) {
+        NSTimeInterval eventRelativeTime = [eventInfo[HIDEventTimeOffsetKey] doubleValue];
+        CFAbsoluteTime targetTime = startTime + eventRelativeTime;
+        
+        CFTimeInterval waitTime = targetTime - CFAbsoluteTimeGetCurrent();
+        if (waitTime &gt; 0)
+            [NSThread sleepForTimeInterval:waitTime];
+
+        dispatch_async(dispatch_get_main_queue(), ^ {
+            [self dispatchEventWithInfo:eventInfo];
+        });
+    }
+
+    dispatch_async(dispatch_get_main_queue(), ^ {
+        [self _sendMarkerHIDEventWithCompletionBlock:completionBlock];
+    });
+}
+
+- (void)sendEventStream:(NSDictionary *)eventInfo completionBlock:(void (^)(void))completionBlock
+{
+    if (!eventInfo) {
+        NSLog(@&quot;eventInfo is nil&quot;);
+        if (completionBlock)
+            completionBlock();
+        return;
+    }
+    
+    NSDictionary* threadData = @{
+        @&quot;eventInfo&quot;: [eventInfo copy],
+        @&quot;completionBlock&quot;: [[completionBlock copy] autorelease]
+    };
+    
+    NSThread *eventDispatchThread = [[[NSThread alloc] initWithTarget:self selector:@selector(eventDispatchThreadEntry:) object:threadData] autorelease];
+    eventDispatchThread.qualityOfService = NSQualityOfServiceUserInteractive;
+    [eventDispatchThread start];
+}
+
</ins><span class="cx"> @end
</span></span></pre></div>
<a id="trunkToolsWebKitTestRunneriosIOKitSPIh"></a>
<div class="modfile"><h4>Modified: trunk/Tools/WebKitTestRunner/ios/IOKitSPI.h (206797 => 206798)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/WebKitTestRunner/ios/IOKitSPI.h        2016-10-05 00:15:39 UTC (rev 206797)
+++ trunk/Tools/WebKitTestRunner/ios/IOKitSPI.h        2016-10-05 00:23:51 UTC (rev 206798)
</span><span class="lines">@@ -117,6 +117,8 @@
</span><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> enum {
</span><ins>+    kIOHIDDigitizerTransducerTypeStylus  = 0,
+    kIOHIDDigitizerTransducerTypeFinger = 2,
</ins><span class="cx">     kIOHIDDigitizerTransducerTypeHand = 3
</span><span class="cx"> };
</span><span class="cx"> typedef uint32_t IOHIDDigitizerTransducerType;
</span></span></pre></div>
<a id="trunkToolsWebKitTestRunneriosUIScriptControllerIOSmm"></a>
<div class="modfile"><h4>Modified: trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm (206797 => 206798)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm        2016-10-05 00:15:39 UTC (rev 206797)
+++ trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm        2016-10-05 00:23:51 UTC (rev 206798)
</span><span class="lines">@@ -35,6 +35,7 @@
</span><span class="cx"> #import &quot;TestRunnerWKWebView.h&quot;
</span><span class="cx"> #import &quot;UIScriptContext.h&quot;
</span><span class="cx"> #import &lt;JavaScriptCore/JavaScriptCore.h&gt;
</span><ins>+#import &lt;JavaScriptCore/OpaqueJSString.h&gt;
</ins><span class="cx"> #import &lt;UIKit/UIKit.h&gt;
</span><span class="cx"> #import &lt;WebCore/FloatRect.h&gt;
</span><span class="cx"> #import &lt;WebKit/WKWebViewPrivate.h&gt;
</span><span class="lines">@@ -175,6 +176,24 @@
</span><span class="cx">     }];
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void UIScriptController::sendEventStream(JSStringRef eventsJSON, JSValueRef callback)
+{
+    unsigned callbackID = m_context-&gt;prepareForAsyncTask(callback, CallbackTypeNonPersistent);
+
+    String jsonString = eventsJSON-&gt;string();
+    auto eventInfo = dynamic_objc_cast&lt;NSDictionary&gt;([NSJSONSerialization JSONObjectWithData:[(NSString *)jsonString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]);
+    if (!eventInfo || ![eventInfo isKindOfClass:[NSDictionary class]]) {
+        WTFLogAlways(&quot;JSON is not convertible to a dictionary&quot;);
+        return;
+    }
+    
+    [[HIDEventGenerator sharedHIDEventGenerator] sendEventStream:eventInfo completionBlock:^{
+        if (!m_context)
+            return;
+        m_context-&gt;asyncTaskComplete(callbackID);
+    }];
+}
+
</ins><span class="cx"> void UIScriptController::dragFromPointToPoint(long startX, long startY, long endX, long endY, double durationSeconds, JSValueRef callback)
</span><span class="cx"> {
</span><span class="cx">     unsigned callbackID = m_context-&gt;prepareForAsyncTask(callback, CallbackTypeNonPersistent);
</span></span></pre>
</div>
</div>

</body>
</html>