[iOS] Mouse/Touch/Pointer events are missing modifier keys
authordbates@webkit.org <dbates@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 11 Feb 2019 19:01:55 +0000 (19:01 +0000)
committerdbates@webkit.org <dbates@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 11 Feb 2019 19:01:55 +0000 (19:01 +0000)
https://bugs.webkit.org/show_bug.cgi?id=191446
<rdar://problem/45929460>

Reviewed by Tim Horton.

Source/WebCore:

Extract the modifier flags from the WebEvent. This code is only used by Legacy WebKit
on iOS and we will need to fix <rdar://problem/47929759> in order for modifier flags
to be passed to WebKit.

Tests: fast/events/touch/ios/mouse-events-with-modifiers.html
       fast/events/touch/ios/pointer-events-with-modifiers.html
       fast/events/touch/ios/touch-events-with-modifiers.html

* platform/ios/PlatformEventFactoryIOS.mm:
(WebCore::PlatformMouseEventBuilder::PlatformMouseEventBuilder):
* platform/ios/WebEvent.h:
* platform/ios/WebEvent.mm:
(-[WebEvent initWithMouseEventType:timeStamp:location:]):
(-[WebEvent initWithMouseEventType:timeStamp:location:modifiers:]):

Source/WebKit:

Make use of UIKit SPI to retreive the modifier flags when dispatching mouse and touch events.
Add new WebKit SPI for iOS, -[WKNavigationAction modifierFlags], to retrieve the the modifier
flags held when a navigation action was initiated.

* Platform/spi/ios/UIKitSPI.h: Expose SPI.
* Shared/NativeWebTouchEvent.h: Re-arrange macro guards so that we can expose the helper function
WebKit::webEventModifierFlags(). This is a bit more involved that usual since this header is included
from both C++ and Objective-C source files. It only makes sense to expose this function when
compiling as part of an Objective-C source file.
* Shared/ios/NativeWebTouchEventIOS.mm:
(WebKit::NativeWebTouchEvent::NativeWebTouchEvent): Modified to take the modifier flags held down
when the platform touch event was received and pass them through to the base constructor.
(WebKit::webEventModifierFlags): Added. Converts from the platform-speciifc UIKeyModifierFlags to
OptionSet<WebKit::WebEvent::Modifier>.
* Shared/ios/WebIOSEventFactory.h:
* Shared/ios/WebIOSEventFactory.mm:
(WebIOSEventFactory::toUIKeyModifierFlags): Added. Converts from OptionSet<WebKit::WebEvent::Modifier>
to the platform-specific UIKeyModifierFlags.
* UIProcess/API/Cocoa/WKNavigationAction.mm:
(-[WKNavigationAction modifierFlags]): Added.
* UIProcess/API/Cocoa/WKNavigationActionPrivate.h:
* UIProcess/WebPageProxy.h:
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(gestureRecognizerModifierFlags): Added.

(-[WKContentView _webTouchEventsRecognized:]):
(-[WKContentView _highlightLongPressRecognized:]):
(-[WKContentView _twoFingerSingleTapGestureRecognized:]):
(-[WKContentView _singleTapCommited:]):
Pass modifier flags through.

(-[WKContentView _attemptClickAtLocation:modifierFlags:]): Added.
(-[WKContentView actionSheetAssistant:openElementAtLocation:]): This is invoked when a person opens a link
via the action sheet. We don't have access to the modifier flags to pass. It also seems like an implementation
detail that this action is implemented via mouse click and we should re-evaluate this decision in light of
the fact tht the action sheet is browser UI and we tend to be very reserved on what UI actions are visible
to the page. On Mac, opening a link via the context menu is not visible to the page, at least from a mouse
event perspective.
(webEventFlagsForUIKeyModifierFlags): Added.
(-[WKContentView _hoverGestureRecognizerChanged:]): Pass modifier flags through.
(-[WKContentView _attemptClickAtLocation:]): Deleted.
* UIProcess/ios/WebPageProxyIOS.mm:
(WebKit::WebPageProxy::handleTwoFingerTapAtPoint):
(WebKit::WebPageProxy::commitPotentialTap):
(WebKit::WebPageProxy::handleTap):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::handleSyntheticClick):
(WebKit::WebPage::completePendingSyntheticClickForContentChangeObserver):
(WebKit::WebPage::completeSyntheticClick):
(WebKit::WebPage::handleTap):
(WebKit::WebPage::handleTwoFingerTapAtPoint):
(WebKit::WebPage::commitPotentialTap):
Pass modifier flags through.

Tools:

Add support infrastructure for testing touch and stylus taps when holding modifier keys.

* DumpRenderTree/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::singleTapAtPointWithModifiers): Added.
(WTR::UIScriptController::stylusTapAtPointWithModifiers): Added.
* TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
* TestRunnerShared/UIScriptContext/UIScriptController.cpp:
(WTR::UIScriptController::singleTapAtPointWithModifiers): Added.
(WTR::UIScriptController::stylusTapAtPointWithModifiers): Added.
* TestRunnerShared/UIScriptContext/UIScriptController.h:
* WebKitTestRunner/ios/UIScriptControllerIOS.mm:
(WTR::arrayLength):
(WTR::parseModifierArray):
(WTR::UIScriptController::singleTapAtPoint): Implemented in terms of singleTapAtPointWithModifiers().
(WTR::UIScriptController::singleTapAtPointWithModifiers): Added.
(WTR::UIScriptController::stylusTapAtPoint): Implemented in terms of stylusTapAtPointWithModifiers().
(WTR::UIScriptController::stylusTapAtPointWithModifiers): Added.

LayoutTests:

Refactor existing iOS key events tests to share code. Add new tests to ensure touch and mouse events
have accurate modifier key details.

* fast/events/ios/key-events-meta-alt-combinations.html:
* fast/events/ios/resources/key-tester.js:
(computeSubsets.compareByModifierOrder): Deleted.
* fast/events/resources/compute-subsets.js: Added.
(computeSubsets.compareByOriginalArrayOrder):
(computeSubsets):
* fast/events/touch/ios/mouse-events-with-modifiers-expected.txt: Added.
* fast/events/touch/ios/mouse-events-with-modifiers.html: Added.
* fast/events/touch/ios/pointer-events-with-modifiers-expected.txt: Added.
* fast/events/touch/ios/pointer-events-with-modifiers.html: Added.
* fast/events/touch/ios/touch-events-with-modifiers-expected.txt: Added.
* fast/events/touch/ios/touch-events-with-modifiers.html: Added.
* http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt: Update expected result
due to changes to ui-helper.js.
* http/tests/security/anchor-download-block-crossorigin-expected.txt: Ditto.
* platform/ios/TestExpectations:
* resources/ui-helper.js:
(window.UIHelper.tapAt.return.new.Promise):
(window.UIHelper.tapAt):
(window.UIHelper.stylusTapAt.return.new.Promise):
(window.UIHelper.stylusTapAt):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@241272 268f45cc-cd09-0410-ab3c-d52691b4dbfc

39 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/events/ios/key-events-meta-alt-combinations.html
LayoutTests/fast/events/ios/resources/key-tester.js
LayoutTests/fast/events/resources/compute-subsets.js [new file with mode: 0644]
LayoutTests/fast/events/touch/ios/mouse-events-with-modifiers-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/touch/ios/mouse-events-with-modifiers.html [new file with mode: 0644]
LayoutTests/fast/events/touch/ios/pointer-events-with-modifiers-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/touch/ios/pointer-events-with-modifiers.html [new file with mode: 0644]
LayoutTests/fast/events/touch/ios/touch-events-with-modifiers-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/touch/ios/touch-events-with-modifiers.html [new file with mode: 0644]
LayoutTests/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt
LayoutTests/http/tests/security/anchor-download-block-crossorigin-expected.txt
LayoutTests/platform/ios/TestExpectations
LayoutTests/resources/ui-helper.js
Source/WebCore/ChangeLog
Source/WebCore/platform/ios/PlatformEventFactoryIOS.mm
Source/WebCore/platform/ios/WebEvent.h
Source/WebCore/platform/ios/WebEvent.mm
Source/WebKit/ChangeLog
Source/WebKit/Platform/spi/ios/UIKitSPI.h
Source/WebKit/Shared/NativeWebTouchEvent.h
Source/WebKit/Shared/ios/NativeWebTouchEventIOS.mm
Source/WebKit/Shared/ios/WebIOSEventFactory.h
Source/WebKit/Shared/ios/WebIOSEventFactory.mm
Source/WebKit/UIProcess/API/Cocoa/WKNavigationAction.mm
Source/WebKit/UIProcess/API/Cocoa/WKNavigationActionPrivate.h
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in
Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
Tools/ChangeLog
Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm
Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl
Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp
Tools/TestRunnerShared/UIScriptContext/UIScriptController.h
Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm

index a492ad1..763770c 100644 (file)
@@ -1,3 +1,36 @@
+2019-02-11  Daniel Bates  <dabates@apple.com>
+
+        [iOS] Mouse/Touch/Pointer events are missing modifier keys
+        https://bugs.webkit.org/show_bug.cgi?id=191446
+        <rdar://problem/45929460>
+
+        Reviewed by Tim Horton.
+
+        Refactor existing iOS key events tests to share code. Add new tests to ensure touch and mouse events
+        have accurate modifier key details.
+
+        * fast/events/ios/key-events-meta-alt-combinations.html:
+        * fast/events/ios/resources/key-tester.js:
+        (computeSubsets.compareByModifierOrder): Deleted.
+        * fast/events/resources/compute-subsets.js: Added.
+        (computeSubsets.compareByOriginalArrayOrder):
+        (computeSubsets):
+        * fast/events/touch/ios/mouse-events-with-modifiers-expected.txt: Added.
+        * fast/events/touch/ios/mouse-events-with-modifiers.html: Added.
+        * fast/events/touch/ios/pointer-events-with-modifiers-expected.txt: Added.
+        * fast/events/touch/ios/pointer-events-with-modifiers.html: Added.
+        * fast/events/touch/ios/touch-events-with-modifiers-expected.txt: Added.
+        * fast/events/touch/ios/touch-events-with-modifiers.html: Added.
+        * http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt: Update expected result
+        due to changes to ui-helper.js.
+        * http/tests/security/anchor-download-block-crossorigin-expected.txt: Ditto.
+        * platform/ios/TestExpectations:
+        * resources/ui-helper.js:
+        (window.UIHelper.tapAt.return.new.Promise):
+        (window.UIHelper.tapAt):
+        (window.UIHelper.stylusTapAt.return.new.Promise):
+        (window.UIHelper.stylusTapAt):
+
 2019-02-11  Youenn Fablet  <youenn@apple.com>
 
         Filter out Overconstrainederror.constraint when getUserMedia is not granted
index fe88d34..4f0e5af 100644 (file)
@@ -2,11 +2,12 @@
 <html>
 <head>
 <script src="../../../resources/ui-helper.js"></script>
+<script src="../resources/compute-subsets.js"></script>
 <script src="resources/key-tester.js"></script>
 <script>
 const modiferKeySubsetsToTest = computeSubsets(["metaKey", "altKey"]);
 for (const k of keysExcludingDeadAndSkippedKeys) {
-    for (const modifiers of modiferKeySubsetsToTest)
+    for (const modifiers of computeSubsets(modifierKeys))
         tests.push(new KeyCommand(k, modifiers));
 }
 </script>
index 5748070..60dbe9e 100644 (file)
@@ -53,43 +53,6 @@ function keyCommandsHasCommand(keyCommands, command)
     return !!keyCommands.find((k) => areKeyCommandsEqual(k, command));
 }
 
-// This algorithm runs in O(2^N).
-function computeSubsets(anArray)
-{
-    function compareByModifierOrder(a, b) {
-        if (a.length < b.length)
-            return -1;
-        if (a.length > b.length)
-            return 1;
-        for (let i = 0; i < a.length; ++i) {
-            let rankA = anArray.indexOf(a[i]);
-            let rankB = anArray.indexOf(b[i]);
-            if (rankA < rankB)
-                return -1;
-            if (rankA > rankB)
-                return 1;
-        }
-        return 0;
-    }
-    let result = [];
-    const numberOfNonEmptyPermutations = (1 << anArray.length) - 1;
-    // For each ordinal 1, 2, ... numberOfNonEmptyPermutations we look at its binary representation
-    // and generate a permutation that consists of the entries in anArray at the indices where there
-    // is a one bit in the binary representation. For example, suppose anArray = ["metaKey", "altKey", "ctrlKey"].
-    // To generate the 5th permutation we look at the binary representation of i = 5 => 0b101. And
-    // compute the permutation to be [ anArray[0], anArray[2] ] = [ "metaKey", "ctrlKey" ] because
-    // the 0th and 2nd bits are ones in the binary representation.
-    for (let i = 1; i <= numberOfNonEmptyPermutations; ++i) {
-        let temp = [];
-        for (let bitmask = i, j = 0; bitmask; bitmask = Math.floor(bitmask / 2), ++j) {
-            if (bitmask % 2)
-                temp.push(anArray[j]);
-        }
-        result.push(temp);
-    }
-    return result.sort(compareByModifierOrder);
-}
-
 const keys = new Set("abcdefghijklmnopqrstuvwxyz0123456789-=[]\\;',./".split(""));
 const deadKeys = new Set("`euin".split(""));
 const keysExcludingDeadKeys = computeDifference(keys, deadKeys);
@@ -117,7 +80,6 @@ for (let i = 1; i <= 9; ++i)
     disallowedKeyCommands.push(new KeyCommand(i, ["metaKey"]));
 
 const modifierKeys = ["metaKey", "altKey", "ctrlKey", "shiftKey"];
-const modiferKeySubsets = computeSubsets(modifierKeys);
 
 let tests = [];
 
diff --git a/LayoutTests/fast/events/resources/compute-subsets.js b/LayoutTests/fast/events/resources/compute-subsets.js
new file mode 100644 (file)
index 0000000..a0f9287
--- /dev/null
@@ -0,0 +1,36 @@
+// This algorithm runs in O(2^N).
+function computeSubsets(anArray)
+{
+    function compareByOriginalArrayOrder(a, b) {
+        if (a.length < b.length)
+            return -1;
+        if (a.length > b.length)
+            return 1;
+        for (let i = 0; i < a.length; ++i) {
+            let rankA = anArray.indexOf(a[i]);
+            let rankB = anArray.indexOf(b[i]);
+            if (rankA < rankB)
+                return -1;
+            if (rankA > rankB)
+                return 1;
+        }
+        return 0;
+    }
+    let result = [];
+    const numberOfNonEmptyPermutations = (1 << anArray.length) - 1;
+    // For each ordinal 1, 2, ... numberOfNonEmptyPermutations we look at its binary representation
+    // and generate a permutation that consists of the entries in anArray at the indices where there
+    // is a one bit in the binary representation. For example, suppose anArray = ["metaKey", "altKey", "ctrlKey"].
+    // To generate the 5th permutation we look at the binary representation of i = 5 => 0b101. And
+    // compute the permutation to be [ anArray[0], anArray[2] ] = [ "metaKey", "ctrlKey" ] because
+    // the 0th and 2nd bits are ones in the binary representation.
+    for (let i = 1; i <= numberOfNonEmptyPermutations; ++i) {
+        let temp = [];
+        for (let bitmask = i, j = 0; bitmask; bitmask = Math.floor(bitmask / 2), ++j) {
+            if (bitmask % 2)
+                temp.push(anArray[j]);
+        }
+        result.push(temp);
+    }
+    return result.sort(compareByOriginalArrayOrder);
+}
diff --git a/LayoutTests/fast/events/touch/ios/mouse-events-with-modifiers-expected.txt b/LayoutTests/fast/events/touch/ios/mouse-events-with-modifiers-expected.txt
new file mode 100644 (file)
index 0000000..8051383
--- /dev/null
@@ -0,0 +1,78 @@
+This logs DOM mousedown, mousemove, and mouseup events that are dispatched when single tapping an element while holding modifier keys. Must be run in WebKitTestRunner.
+
+
+Test Command + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: true, shiftKey: false
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: true, shiftKey: false
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: true, shiftKey: false
+
+Test Option + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: false, shiftKey: false
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: false, shiftKey: false
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: false, shiftKey: false
+
+Test Control + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: false, shiftKey: false
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: false, shiftKey: false
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: false, shiftKey: false
+
+Test Shift + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: false, shiftKey: true
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: false, shiftKey: true
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: false, shiftKey: true
+
+Test Command + Option + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: true, shiftKey: false
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: true, shiftKey: false
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: true, shiftKey: false
+
+Test Command + Control + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: true, shiftKey: false
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: true, shiftKey: false
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: true, shiftKey: false
+
+Test Command + Shift + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: true, shiftKey: true
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: true, shiftKey: true
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: true, shiftKey: true
+
+Test Option + Control + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: false, shiftKey: false
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: false, shiftKey: false
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: false, shiftKey: false
+
+Test Option + Shift + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: false, shiftKey: true
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: false, shiftKey: true
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: false, shiftKey: true
+
+Test Control + Shift + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: false, shiftKey: true
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: false, shiftKey: true
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: false, shiftKey: true
+
+Test Command + Option + Control + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: true, shiftKey: false
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: true, shiftKey: false
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: true, shiftKey: false
+
+Test Command + Option + Shift + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: true, shiftKey: true
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: true, shiftKey: true
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: true, shiftKey: true
+
+Test Command + Control + Shift + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: true, shiftKey: true
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: true, shiftKey: true
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: true, shiftKey: true
+
+Test Option + Control + Shift + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: false, shiftKey: true
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: false, shiftKey: true
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: false, shiftKey: true
+
+Test Command + Option + Control + Shift + Single Tap:
+type: mousemove, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: true, shiftKey: true
+type: mousedown, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: true, shiftKey: true
+type: mouseup, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: true, shiftKey: true
+
diff --git a/LayoutTests/fast/events/touch/ios/mouse-events-with-modifiers.html b/LayoutTests/fast/events/touch/ios/mouse-events-with-modifiers.html
new file mode 100644 (file)
index 0000000..48afac7
--- /dev/null
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../../resources/ui-helper.js"></script>
+<script src="../../resources/compute-subsets.js"></script>
+<script>
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+
+class TouchTest {
+    constructor(elementToTouch, modifiers = [])
+    {
+        this.elementToTouch = elementToTouch;
+        this.modifiers = modifiers;
+    }
+
+    toString()
+    {
+        return `{ ${this.elementToTouch}, [${this.modifiers}] }`;
+    }
+
+    run()
+    {
+        let centerX = this.elementToTouch.offsetLeft + this.elementToTouch.offsetWidth / 2;
+        let centerY = this.elementToTouch.offsetTop + this.elementToTouch.offsetHeight / 2;
+        return UIHelper.tapAt(centerX, centerY, this.modifiers);
+    }
+}
+
+const modifierKeys = ["metaKey", "altKey", "ctrlKey", "shiftKey"];
+
+let currentTest;
+let tests = [];
+
+function handleMouseDown(event)
+{
+    logMouseEvent(event);
+}
+
+function handleMouseMove(event)
+{
+    logMouseEvent(event);
+}
+
+function handleMouseUp(event)
+{
+    logMouseEvent(event);
+    nextTouchPress();
+}
+
+function log(message)
+{
+    document.getElementById("console").appendChild(document.createTextNode(message + "\n"));
+}
+
+function logMouseEvent(event)
+{
+    let pieces = [];
+    for (let propertyName of ["type", "x", "y", "offsetX", "offsetY", "button", "buttons", "screenX", "screenY", "clientX", "clientY", "altKey", "ctrlKey", "metaKey", "shiftKey"])
+        pieces.push(`${propertyName}: ${event[propertyName]}`);
+    log(pieces.join(", "));
+}
+
+const modifierDisplayNameMap = {
+    "altKey": "Option",
+    "ctrlKey": "Control",
+    "metaKey": "Command",
+    "shiftKey": "Shift",
+    "capsLockKey": "Caps Lock",
+}
+
+function displayNameForTest(test)
+{
+    let displayNamesOfModifiers = [];
+    for (const modifier of test.modifiers)
+        displayNamesOfModifiers.push(modifierDisplayNameMap[modifier]);
+    let result = "";
+    if (displayNamesOfModifiers.length)
+        result += displayNamesOfModifiers.join(" + ") + " + ";
+    result += "Single Tap";
+    return result;
+}
+
+async function nextTouchPress()
+{
+    if (!tests.length) {
+        document.body.removeChild(document.getElementById("square"));
+        if (window.testRunner)
+            testRunner.notifyDone();
+        return;
+    }
+    currentTest = tests.shift();
+    log(`\nTest ${displayNameForTest(currentTest)}:`);
+    if (window.testRunner)
+        await currentTest.run();
+}
+
+function runTest()
+{
+    if (!window.testRunner)
+        return;
+    nextTouchPress();
+}
+
+function setUp()
+{
+    let square = document.getElementById("square");
+    square.addEventListener("mousedown", handleMouseDown, true);
+    square.addEventListener("mousemove", handleMouseMove, true);
+    square.addEventListener("mouseup", handleMouseUp, true);
+
+    for (const modifiers of computeSubsets(modifierKeys))
+        tests.push(new TouchTest(square, modifiers));
+
+    runTest();
+}
+
+window.addEventListener("load", setUp, true);
+</script>
+<style>
+#square {
+    background-color: blue;
+    color: white;
+    height: 128px;
+    width: 128px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    user-select: none;
+    -webkit-user-select: none;
+}
+</style>
+</head>
+<body>
+<p>This logs DOM mousedown, mousemove, and mouseup events that are dispatched when single tapping an element while holding modifier keys. Must be run in WebKitTestRunner.</p>
+<div id="square">Touch Me</div>
+<pre id="console"></pre>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/touch/ios/pointer-events-with-modifiers-expected.txt b/LayoutTests/fast/events/touch/ios/pointer-events-with-modifiers-expected.txt
new file mode 100644 (file)
index 0000000..09be81f
--- /dev/null
@@ -0,0 +1,63 @@
+This logs DOM pointerdown and pointerup events that are dispatched when single tapping an element while holding modifier keys. Must be run in WebKitTestRunner.
+
+
+Test Command + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: true, shiftKey: false
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: true, shiftKey: false
+
+Test Option + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: false, shiftKey: false
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: false, shiftKey: false
+
+Test Control + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: false, shiftKey: false
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: false, shiftKey: false
+
+Test Shift + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: false, shiftKey: true
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: false, shiftKey: true
+
+Test Command + Option + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: true, shiftKey: false
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: true, shiftKey: false
+
+Test Command + Control + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: true, shiftKey: false
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: true, shiftKey: false
+
+Test Command + Shift + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: true, shiftKey: true
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: false, metaKey: true, shiftKey: true
+
+Test Option + Control + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: false, shiftKey: false
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: false, shiftKey: false
+
+Test Option + Shift + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: false, shiftKey: true
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: false, shiftKey: true
+
+Test Control + Shift + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: false, shiftKey: true
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: false, shiftKey: true
+
+Test Command + Option + Control + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: true, shiftKey: false
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: true, shiftKey: false
+
+Test Command + Option + Shift + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: true, shiftKey: true
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: false, metaKey: true, shiftKey: true
+
+Test Command + Control + Shift + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: true, shiftKey: true
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: false, ctrlKey: true, metaKey: true, shiftKey: true
+
+Test Option + Control + Shift + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: false, shiftKey: true
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: false, shiftKey: true
+
+Test Command + Option + Control + Shift + Single Tap:
+type: pointerdown, pointerType: touch, isPrimary: true, width: 40, height: 40, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: true, shiftKey: true
+type: pointerup, pointerType: touch, isPrimary: false, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 90, tiltY: 0, twist: 0, x: 72, y: 136, offsetX: 64, offsetY: 64, button: 0, buttons: 0, screenX: 72, screenY: 136, clientX: 72, clientY: 136, altKey: true, ctrlKey: true, metaKey: true, shiftKey: true
+
diff --git a/LayoutTests/fast/events/touch/ios/pointer-events-with-modifiers.html b/LayoutTests/fast/events/touch/ios/pointer-events-with-modifiers.html
new file mode 100644 (file)
index 0000000..d475303
--- /dev/null
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../../resources/ui-helper.js"></script>
+<script src="../../resources/compute-subsets.js"></script>
+<script>
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+
+class TouchTest {
+    constructor(elementToTouch, modifiers = [])
+    {
+        this.elementToTouch = elementToTouch;
+        this.modifiers = modifiers;
+    }
+
+    toString()
+    {
+        return `{ ${this.elementToTouch}, [${this.modifiers}] }`;
+    }
+
+    run()
+    {
+        let centerX = this.elementToTouch.offsetLeft + this.elementToTouch.offsetWidth / 2;
+        let centerY = this.elementToTouch.offsetTop + this.elementToTouch.offsetHeight / 2;
+        return UIHelper.tapAt(centerX, centerY, this.modifiers);
+    }
+}
+
+const modifierKeys = ["metaKey", "altKey", "ctrlKey", "shiftKey"];
+
+let currentTest;
+let tests = [];
+
+function handlePointerDown(event)
+{
+    logPointerEvent(event);
+}
+
+function handlePointerUp(event)
+{
+    logPointerEvent(event);
+    nextTouchPress();
+}
+
+function log(message)
+{
+    document.getElementById("console").appendChild(document.createTextNode(message + "\n"));
+}
+
+function logPointerEvent(event)
+{
+    let pieces = [];
+    for (let propertyName of ["type", "pointerType", "isPrimary", "width", "height", "pressure", "tangentialPressure", "tiltX", "tiltY", "twist", "x", "y", "offsetX", "offsetY", "button", "buttons", "screenX", "screenY", "clientX", "clientY", "altKey", "ctrlKey", "metaKey", "shiftKey"])
+        pieces.push(`${propertyName}: ${event[propertyName]}`);
+    log(pieces.join(", "));
+}
+
+const modifierDisplayNameMap = {
+    "altKey": "Option",
+    "ctrlKey": "Control",
+    "metaKey": "Command",
+    "shiftKey": "Shift",
+    "capsLockKey": "Caps Lock",
+}
+
+function displayNameForTest(test)
+{
+    let displayNamesOfModifiers = [];
+    for (const modifier of test.modifiers)
+        displayNamesOfModifiers.push(modifierDisplayNameMap[modifier]);
+    let result = "";
+    if (displayNamesOfModifiers.length)
+        result += displayNamesOfModifiers.join(" + ") + " + ";
+    result += "Single Tap";
+    return result;
+}
+
+async function nextTouchPress()
+{
+    if (!tests.length) {
+        document.body.removeChild(document.getElementById("square"));
+        if (window.testRunner)
+            testRunner.notifyDone();
+        return;
+    }
+    currentTest = tests.shift();
+    log(`\nTest ${displayNameForTest(currentTest)}:`);
+    if (window.testRunner)
+        await currentTest.run();
+}
+
+function runTest()
+{
+    if (!window.testRunner)
+        return;
+    nextTouchPress();
+}
+
+function setUp()
+{
+    let square = document.getElementById("square");
+    square.addEventListener("pointerdown", handlePointerDown, true);
+    square.addEventListener("pointerup", handlePointerUp, true);
+
+    for (const modifiers of computeSubsets(modifierKeys))
+        tests.push(new TouchTest(square, modifiers));
+
+    runTest();
+}
+
+window.addEventListener("load", setUp, true);
+</script>
+<style>
+#square {
+    background-color: blue;
+    color: white;
+    height: 128px;
+    width: 128px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    user-select: none;
+    -webkit-user-select: none;
+}
+</style>
+</head>
+<body>
+<p>This logs DOM pointerdown and pointerup events that are dispatched when single tapping an element while holding modifier keys. Must be run in WebKitTestRunner.</p>
+<div id="square">Touch Me</div>
+<pre id="console"></pre>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/touch/ios/touch-events-with-modifiers-expected.txt b/LayoutTests/fast/events/touch/ios/touch-events-with-modifiers-expected.txt
new file mode 100644 (file)
index 0000000..4e5550c
--- /dev/null
@@ -0,0 +1,123 @@
+This logs DOM touchdown and touchup events that are dispatched when single tapping an element while holding modifier keys with and without a stylus. Must be run in WebKitTestRunner.
+
+
+Test Command + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: false, metaKey: true, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: false, metaKey: true, shiftKey: false
+
+Test Option + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: false, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: false, shiftKey: false
+
+Test Control + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: false, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: false, shiftKey: false
+
+Test Shift + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: false, metaKey: false, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: false, metaKey: false, shiftKey: true
+
+Test Command + Option + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: true, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: true, shiftKey: false
+
+Test Command + Control + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: true, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: true, shiftKey: false
+
+Test Command + Shift + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: false, metaKey: true, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: false, metaKey: true, shiftKey: true
+
+Test Option + Control + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: false, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: false, shiftKey: false
+
+Test Option + Shift + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: false, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: false, shiftKey: true
+
+Test Control + Shift + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: false, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: false, shiftKey: true
+
+Test Command + Option + Control + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: true, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: true, shiftKey: false
+
+Test Command + Option + Shift + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: true, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: true, shiftKey: true
+
+Test Command + Control + Shift + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: true, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: true, shiftKey: true
+
+Test Option + Control + Shift + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: false, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: false, shiftKey: true
+
+Test Command + Option + Control + Shift + Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: true, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: true, shiftKey: true
+
+Test Command + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: false, metaKey: true, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: false, metaKey: true, shiftKey: false
+
+Test Option + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: false, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: false, shiftKey: false
+
+Test Control + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: false, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: false, shiftKey: false
+
+Test Shift + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: false, metaKey: false, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: false, metaKey: false, shiftKey: true
+
+Test Command + Option + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: true, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: true, shiftKey: false
+
+Test Command + Control + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: true, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: true, shiftKey: false
+
+Test Command + Shift + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: false, metaKey: true, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: false, metaKey: true, shiftKey: true
+
+Test Option + Control + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: false, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: false, shiftKey: false
+
+Test Option + Shift + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: false, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: false, shiftKey: true
+
+Test Control + Shift + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: false, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: false, shiftKey: true
+
+Test Command + Option + Control + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: true, shiftKey: false
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: true, shiftKey: false
+
+Test Command + Option + Shift + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: true, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: false, metaKey: true, shiftKey: true
+
+Test Command + Control + Shift + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: true, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: false, ctrlKey: true, metaKey: true, shiftKey: true
+
+Test Option + Control + Shift + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: false, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: false, shiftKey: true
+
+Test Command + Option + Control + Shift + Stylus Single Tap:
+type: touchstart, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: true, shiftKey: true
+type: touchend, pageX: 72, pageX: 72, scale: 1, rotation: 0, altKey: true, ctrlKey: true, metaKey: true, shiftKey: true
+
diff --git a/LayoutTests/fast/events/touch/ios/touch-events-with-modifiers.html b/LayoutTests/fast/events/touch/ios/touch-events-with-modifiers.html
new file mode 100644 (file)
index 0000000..a52f509
--- /dev/null
@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../../resources/ui-helper.js"></script>
+<script src="../../resources/compute-subsets.js"></script>
+<script>
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+
+// FIXME: Add support for testing force touch.
+const TouchType = {
+    "SingleTap": "SingleTap",
+    "StylusSingleTap": "StylusSingleTap",
+};
+const TouchTypeDisplayName = {
+    "SingleTap": "Single Tap",
+    "StylusSingleTap": "Stylus Single Tap",
+};
+
+class TouchTest {
+    constructor(elementToTouch, touchType, modifiers = [])
+    {
+        this.elementToTouch = elementToTouch;
+        this.touchType = touchType;
+        this.modifiers = modifiers;
+    }
+
+    toString()
+    {
+        return `{ ${this.touchType}, ${this.elementToTouch}, [${this.modifiers}] }`;
+    }
+
+    run()
+    {
+        let centerX = this.elementToTouch.offsetLeft + this.elementToTouch.offsetWidth / 2;
+        let centerY = this.elementToTouch.offsetTop + this.elementToTouch.offsetHeight / 2;
+        if (this.touchType === TouchType.SingleTap)
+            return UIHelper.tapAt(centerX, centerY, this.modifiers);
+        return UIHelper.stylusTapAt(centerX, centerY, this.modifiers);
+    }
+}
+
+const modifierKeys = ["metaKey", "altKey", "ctrlKey", "shiftKey"];
+
+let currentTest;
+let tests = [];
+
+function handleTouchDown(event)
+{
+    logTouchEvent(event);
+}
+
+function handleTouchUp(event)
+{
+    logTouchEvent(event);
+    nextTouchPress();
+}
+
+function log(message)
+{
+    document.getElementById("console").appendChild(document.createTextNode(message + "\n"));
+}
+
+function logTouchEvent(event)
+{
+    let pieces = [];
+    for (let propertyName of ["type", "pageX", "pageX", "scale", "rotation", "altKey", "ctrlKey", "metaKey", "shiftKey"])
+        pieces.push(`${propertyName}: ${event[propertyName]}`);
+    log(pieces.join(", "));
+}
+
+const modifierDisplayNameMap = {
+    "altKey": "Option",
+    "ctrlKey": "Control",
+    "metaKey": "Command",
+    "shiftKey": "Shift",
+    "capsLockKey": "Caps Lock",
+}
+
+function displayNameForTest(test)
+{
+    let displayNamesOfModifiers = [];
+    for (const modifier of test.modifiers)
+        displayNamesOfModifiers.push(modifierDisplayNameMap[modifier]);
+    let result = "";
+    if (displayNamesOfModifiers.length)
+        result += displayNamesOfModifiers.join(" + ") + " + ";
+    result += TouchTypeDisplayName[test.touchType];
+    return result;
+}
+
+async function nextTouchPress()
+{
+    if (!tests.length) {
+        document.body.removeChild(document.getElementById("square"));
+        if (window.testRunner)
+            testRunner.notifyDone();
+        return;
+    }
+    currentTest = tests.shift();
+    log(`\nTest ${displayNameForTest(currentTest)}:`);
+    if (window.testRunner)
+        await currentTest.run();
+}
+
+function runTest()
+{
+    if (!window.testRunner)
+        return;
+    nextTouchPress();
+}
+
+function setUp()
+{
+    let square = document.getElementById("square");
+    square.addEventListener("touchstart", handleTouchDown, true);
+    square.addEventListener("touchend", handleTouchUp, true);
+
+    for (const touchType in TouchType) {
+        for (const modifiers of computeSubsets(modifierKeys))
+            tests.push(new TouchTest(square, touchType, modifiers));
+    }
+
+    runTest();
+}
+
+window.addEventListener("load", setUp, true);
+</script>
+<style>
+#square {
+    background-color: blue;
+    color: white;
+    height: 128px;
+    width: 128px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    user-select: none;
+    -webkit-user-select: none;
+}
+</style>
+</head>
+<body>
+<p>This logs DOM touchdown and touchup events that are dispatched when single tapping an element while holding modifier keys with and without a stylus. Must be run in WebKitTestRunner.</p>
+<div id="square">Touch Me</div>
+<pre id="console"></pre>
+</body>
+</html>
index 311084e..4de18cc 100644 (file)
@@ -1,13 +1,13 @@
-CONSOLE MESSAGE: line 107: adcampaignid must have a non-negative value less than 64 for Ad Click Attribution.
-CONSOLE MESSAGE: line 107: adcampaignid must have a non-negative value less than 64 for Ad Click Attribution.
-CONSOLE MESSAGE: line 107: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
-CONSOLE MESSAGE: line 107: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
-CONSOLE MESSAGE: line 107: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
-CONSOLE MESSAGE: line 107: adddestination could not be converted to a valid HTTP-family URL.
-CONSOLE MESSAGE: line 107: adddestination could not be converted to a valid HTTP-family URL.
-CONSOLE MESSAGE: line 107: adddestination could not be converted to a valid HTTP-family URL.
-CONSOLE MESSAGE: line 107: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
-CONSOLE MESSAGE: line 107: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
+CONSOLE MESSAGE: line 108: adcampaignid must have a non-negative value less than 64 for Ad Click Attribution.
+CONSOLE MESSAGE: line 108: adcampaignid must have a non-negative value less than 64 for Ad Click Attribution.
+CONSOLE MESSAGE: line 108: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
+CONSOLE MESSAGE: line 108: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
+CONSOLE MESSAGE: line 108: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
+CONSOLE MESSAGE: line 108: adddestination could not be converted to a valid HTTP-family URL.
+CONSOLE MESSAGE: line 108: adddestination could not be converted to a valid HTTP-family URL.
+CONSOLE MESSAGE: line 108: adddestination could not be converted to a valid HTTP-family URL.
+CONSOLE MESSAGE: line 108: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
+CONSOLE MESSAGE: line 108: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
 Test for validity of ad click attribution attributes on anchor tags.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
index fc6d370..a070227 100644 (file)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: line 107: The download attribute on anchor was ignored because its href URL has a different security origin.
+CONSOLE MESSAGE: line 108: The download attribute on anchor was ignored because its href URL has a different security origin.
 Tests that the download attribute is ignored if the link is cross origin.
 
 It should navigate the subframe instead of downloading the file.
index f40161c..5a7435d 100644 (file)
@@ -3210,3 +3210,8 @@ webkit.org/b/193955 compositing/iframes/remove-reinsert-webview-with-iframe.html
 webkit.org/b/153337 pageoverlay/overlay-installation.html [ Pass Failure ]
 webkit.org/b/153337 pageoverlay/overlay-large-document-scrolled.html [ Pass Failure ]
 webkit.org/b/153337 pageoverlay/overlay-large-document.html [ Pass Failure ]
+
+# FIXME: Unskip the following test once we have the fix for <rdar://problem/45970040>.
+fast/events/touch/ios/touch-events-with-modifiers.html [ Skip ]
+fast/events/touch/ios/mouse-events-with-modifiers.html [ Skip ]
+fast/events/touch/ios/pointer-events-with-modifiers.html [ Skip ]
index fe546a8..5aeff04 100644 (file)
@@ -29,11 +29,12 @@ window.UIHelper = class UIHelper {
         eventSender.mouseUp();
     }
 
-    static tapAt(x, y)
+    static tapAt(x, y, modifiers=[])
     {
         console.assert(this.isIOS());
 
         if (!this.isWebKit2()) {
+            console.assert(!modifiers || !modifiers.length);
             eventSender.addTouchPoint(x, y);
             eventSender.touchStart();
             eventSender.releaseTouchPoint(0);
@@ -43,7 +44,7 @@ window.UIHelper = class UIHelper {
 
         return new Promise((resolve) => {
             testRunner.runUIScript(`
-                uiController.singleTapAtPoint(${x}, ${y}, function() {
+                uiController.singleTapAtPointWithModifiers(${x}, ${y}, ${JSON.stringify(modifiers)}, function() {
                     uiController.uiScriptComplete();
                 });`, resolve);
         });
@@ -534,14 +535,14 @@ window.UIHelper = class UIHelper {
         return new Promise(resolve => testRunner.runUIScript(`uiController.drawSquareInEditableImage()`, resolve));
     }
 
-    static stylusTapAt(x, y)
+    static stylusTapAt(x, y, modifiers=[])
     {
         if (!this.isWebKit2())
             return Promise.resolve();
 
         return new Promise((resolve) => {
             testRunner.runUIScript(`
-                uiController.stylusTapAtPoint(${x}, ${y}, 2, 1, 0.5, function() {
+                uiController.stylusTapAtPointWithModifiers(${x}, ${y}, 2, 1, 0.5, ${JSON.stringify(modifiers)}, function() {
                     uiController.uiScriptComplete();
                 });`, resolve);
         });
index e9fd440..1d09370 100644 (file)
@@ -1,5 +1,28 @@
 2019-02-11  Daniel Bates  <dabates@apple.com>
 
+        [iOS] Mouse/Touch/Pointer events are missing modifier keys
+        https://bugs.webkit.org/show_bug.cgi?id=191446
+        <rdar://problem/45929460>
+
+        Reviewed by Tim Horton.
+
+        Extract the modifier flags from the WebEvent. This code is only used by Legacy WebKit
+        on iOS and we will need to fix <rdar://problem/47929759> in order for modifier flags
+        to be passed to WebKit.
+
+        Tests: fast/events/touch/ios/mouse-events-with-modifiers.html
+               fast/events/touch/ios/pointer-events-with-modifiers.html
+               fast/events/touch/ios/touch-events-with-modifiers.html
+
+        * platform/ios/PlatformEventFactoryIOS.mm:
+        (WebCore::PlatformMouseEventBuilder::PlatformMouseEventBuilder):
+        * platform/ios/WebEvent.h:
+        * platform/ios/WebEvent.mm:
+        (-[WebEvent initWithMouseEventType:timeStamp:location:]):
+        (-[WebEvent initWithMouseEventType:timeStamp:location:modifiers:]):
+
+2019-02-11  Daniel Bates  <dabates@apple.com>
+
         Separate out outline-style: auto user-agent appearance from Mac animated focus ring drawing
         https://bugs.webkit.org/show_bug.cgi?id=193591
 
index be23f54..a171b9d 100644 (file)
@@ -95,6 +95,7 @@ public:
         m_globalPosition = globalPointForEvent(event);
         m_button = LeftButton; // This has always been the LeftButton on iOS.
         m_clickCount = 1; // This has always been 1 on iOS.
+        m_modifiers = modifiersForEvent(event);
     }
 };
 
index 97518df..3a2f0c9 100644 (file)
@@ -139,10 +139,13 @@ WEBCORE_EXPORT @interface WebEvent : NSObject {
     BOOL _wasHandled;
 }
 
+// Deprecated. Remove once UIKit adopts -initWithMouseEventType taking modifiers.
 - (WebEvent *)initWithMouseEventType:(WebEventType)type
                            timeStamp:(CFTimeInterval)timeStamp
                             location:(CGPoint)point;
 
+- (WebEvent *)initWithMouseEventType:(WebEventType)type timeStamp:(CFTimeInterval)timeStamp location:(CGPoint)point modifiers:(WebEventFlags)modifiers;
+
 - (WebEvent *)initWithScrollWheelEventWithTimeStamp:(CFTimeInterval)timeStamp
                                            location:(CGPoint)point
                                               deltaX:(float)deltaX
index 1d5f6a4..5443117 100644 (file)
@@ -49,19 +49,20 @@ using WebCore::windowsKeyCodeForCharCode;
 @synthesize timestamp = _timestamp;
 @synthesize wasHandled = _wasHandled;
 
-- (WebEvent *)initWithMouseEventType:(WebEventType)type
-                           timeStamp:(CFTimeInterval)timeStamp
-                            location:(CGPoint)point
+- (WebEvent *)initWithMouseEventType:(WebEventType)type timeStamp:(CFTimeInterval)timeStamp location:(CGPoint)point
+{
+    return [self initWithMouseEventType:type timeStamp:timeStamp location:point modifiers:0];
+}
+
+- (WebEvent *)initWithMouseEventType:(WebEventType)type timeStamp:(CFTimeInterval)timeStamp location:(CGPoint)point modifiers:(WebEventFlags)modifiers
 {
     self = [super init];
     if (!self)
         return nil;
-    
     _type = type;
     _timestamp = timeStamp;
-
     _locationInWindow = point;
-    
+    _modifierFlags = modifiers;
     return self;
 }
 
index 5530f16..130d555 100644 (file)
@@ -1,3 +1,68 @@
+2019-02-11  Daniel Bates  <dabates@apple.com>
+
+        [iOS] Mouse/Touch/Pointer events are missing modifier keys
+        https://bugs.webkit.org/show_bug.cgi?id=191446
+        <rdar://problem/45929460>
+
+        Reviewed by Tim Horton.
+
+        Make use of UIKit SPI to retreive the modifier flags when dispatching mouse and touch events.
+        Add new WebKit SPI for iOS, -[WKNavigationAction modifierFlags], to retrieve the the modifier
+        flags held when a navigation action was initiated.
+
+        * Platform/spi/ios/UIKitSPI.h: Expose SPI.
+        * Shared/NativeWebTouchEvent.h: Re-arrange macro guards so that we can expose the helper function
+        WebKit::webEventModifierFlags(). This is a bit more involved that usual since this header is included
+        from both C++ and Objective-C source files. It only makes sense to expose this function when
+        compiling as part of an Objective-C source file.
+        * Shared/ios/NativeWebTouchEventIOS.mm:
+        (WebKit::NativeWebTouchEvent::NativeWebTouchEvent): Modified to take the modifier flags held down
+        when the platform touch event was received and pass them through to the base constructor.
+        (WebKit::webEventModifierFlags): Added. Converts from the platform-speciifc UIKeyModifierFlags to
+        OptionSet<WebKit::WebEvent::Modifier>.
+        * Shared/ios/WebIOSEventFactory.h:
+        * Shared/ios/WebIOSEventFactory.mm:
+        (WebIOSEventFactory::toUIKeyModifierFlags): Added. Converts from OptionSet<WebKit::WebEvent::Modifier>
+        to the platform-specific UIKeyModifierFlags.
+        * UIProcess/API/Cocoa/WKNavigationAction.mm:
+        (-[WKNavigationAction modifierFlags]): Added.
+        * UIProcess/API/Cocoa/WKNavigationActionPrivate.h:
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (gestureRecognizerModifierFlags): Added.
+
+        (-[WKContentView _webTouchEventsRecognized:]):
+        (-[WKContentView _highlightLongPressRecognized:]):
+        (-[WKContentView _twoFingerSingleTapGestureRecognized:]):
+        (-[WKContentView _singleTapCommited:]):
+        Pass modifier flags through.
+
+        (-[WKContentView _attemptClickAtLocation:modifierFlags:]): Added.
+        (-[WKContentView actionSheetAssistant:openElementAtLocation:]): This is invoked when a person opens a link
+        via the action sheet. We don't have access to the modifier flags to pass. It also seems like an implementation
+        detail that this action is implemented via mouse click and we should re-evaluate this decision in light of
+        the fact tht the action sheet is browser UI and we tend to be very reserved on what UI actions are visible
+        to the page. On Mac, opening a link via the context menu is not visible to the page, at least from a mouse
+        event perspective.
+        (webEventFlagsForUIKeyModifierFlags): Added.
+        (-[WKContentView _hoverGestureRecognizerChanged:]): Pass modifier flags through.
+        (-[WKContentView _attemptClickAtLocation:]): Deleted.
+        * UIProcess/ios/WebPageProxyIOS.mm:
+        (WebKit::WebPageProxy::handleTwoFingerTapAtPoint):
+        (WebKit::WebPageProxy::commitPotentialTap):
+        (WebKit::WebPageProxy::handleTap):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::handleSyntheticClick):
+        (WebKit::WebPage::completePendingSyntheticClickForContentChangeObserver):
+        (WebKit::WebPage::completeSyntheticClick):
+        (WebKit::WebPage::handleTap):
+        (WebKit::WebPage::handleTwoFingerTapAtPoint):
+        (WebKit::WebPage::commitPotentialTap):
+        Pass modifier flags through.
+
 2019-02-11  Youenn Fablet  <youenn@apple.com>
 
         Filter out Overconstrainederror.constraint when getUserMedia is not granted
index 1190812..d9fef25 100644 (file)
@@ -358,6 +358,10 @@ typedef NS_ENUM(NSInteger, UIScrollViewIndicatorInsetAdjustmentBehavior) {
 - (CGSize)_legacy_sizeWithFont:(UIFont *)font minFontSize:(CGFloat)minFontSize actualFontSize:(CGFloat *)actualFontSize forWidth:(CGFloat)width lineBreakMode:(NSLineBreakMode)lineBreakMode;
 @end
 
+@interface UIGestureRecognizer ()
+@property (nonatomic, readonly, getter=_modifierFlags) UIKeyModifierFlags modifierFlags;
+@end
+
 @interface UITapGestureRecognizer ()
 @property (nonatomic, getter=_allowableSeparation, setter=_setAllowableSeparation:) CGFloat allowableSeparation;
 @property (nonatomic, readonly) CGPoint location;
index 3b7cedb..3910a48 100644 (file)
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef NativeWebTouchEvent_h
-#define NativeWebTouchEvent_h
-
-#if ENABLE(TOUCH_EVENTS)
+#pragma once
 
 #include "WebEvent.h"
 
+#if ENABLE(TOUCH_EVENTS)
+
 #if PLATFORM(IOS_FAMILY)
+#if defined(__OBJC__)
+#include <UIKit/UIKit.h>
 struct _UIWebTouchEvent;
+#endif
 #elif PLATFORM(GTK)
 #include <WebCore/GUniquePtrGtk.h>
 #elif USE(LIBWPE)
 #include <wpe/wpe.h>
 #endif
 
+#endif // ENABLE(TOUCH_EVENTS)
+
 namespace WebKit {
 
+#if ENABLE(TOUCH_EVENTS)
+
 class NativeWebTouchEvent : public WebTouchEvent {
 public:
 #if PLATFORM(IOS_FAMILY)
-    explicit NativeWebTouchEvent(const _UIWebTouchEvent*);
+#if defined(__OBJC__)
+    explicit NativeWebTouchEvent(const _UIWebTouchEvent*, UIKeyModifierFlags);
+#endif
 #elif PLATFORM(GTK)
     NativeWebTouchEvent(GdkEvent*, Vector<WebPlatformTouchPoint>&&);
     NativeWebTouchEvent(const NativeWebTouchEvent&);
@@ -56,7 +64,7 @@ public:
 #endif
 
 private:
-#if PLATFORM(IOS_FAMILY)
+#if PLATFORM(IOS_FAMILY) && defined(__OBJC__)
     Vector<WebPlatformTouchPoint> extractWebTouchPoint(const _UIWebTouchEvent*);
 #endif
 
@@ -67,8 +75,10 @@ private:
 #endif
 };
 
-} // namespace WebKit
-
 #endif // ENABLE(TOUCH_EVENTS)
 
-#endif // NativeWebTouchEvent_h
+#if PLATFORM(IOS_FAMILY) && defined(__OBJC__)
+OptionSet<WebEvent::Modifier> webEventModifierFlags(UIKeyModifierFlags);
+#endif
+
+} // namespace WebKit
index 995f640..323c9ec 100644 (file)
 #import "config.h"
 #import "NativeWebTouchEvent.h"
 
-#if PLATFORM(IOS_FAMILY) && ENABLE(TOUCH_EVENTS)
+#if PLATFORM(IOS_FAMILY)
 
 #import "UIKitSPI.h"
-#import "WebEvent.h"
 #import <UIKit/UITouch.h>
 #import <WebCore/IntPoint.h>
 #import <WebCore/WAKAppKitStubs.h>
 
 namespace WebKit {
 
+#if ENABLE(TOUCH_EVENTS)
+
 static inline WebEvent::Type webEventTypeForUIWebTouchEventType(UIWebTouchEventType type)
 {
     switch (type) {
@@ -117,10 +118,10 @@ Vector<WebPlatformTouchPoint> NativeWebTouchEvent::extractWebTouchPoint(const _U
     return touchPointList;
 }
 
-NativeWebTouchEvent::NativeWebTouchEvent(const _UIWebTouchEvent* event)
+NativeWebTouchEvent::NativeWebTouchEvent(const _UIWebTouchEvent* event, UIKeyModifierFlags flags)
     : WebTouchEvent(
         webEventTypeForUIWebTouchEventType(event->type),
-        OptionSet<Modifier> { },
+        webEventModifierFlags(flags),
         WallTime::fromRawSeconds(event->timestamp),
         extractWebTouchPoint(event),
         positionForCGPoint(event->locationInDocumentCoordinates),
@@ -135,6 +136,24 @@ NativeWebTouchEvent::NativeWebTouchEvent(const _UIWebTouchEvent* event)
 {
 }
 
+#endif // ENABLE(TOUCH_EVENTS)
+
+OptionSet<WebEvent::Modifier> webEventModifierFlags(UIKeyModifierFlags flags)
+{
+    OptionSet<WebEvent::Modifier> modifiers;
+    if (flags & UIKeyModifierShift)
+        modifiers.add(WebEvent::Modifier::ShiftKey);
+    if (flags & UIKeyModifierControl)
+        modifiers.add(WebEvent::Modifier::ControlKey);
+    if (flags & UIKeyModifierAlternate)
+        modifiers.add(WebEvent::Modifier::AltKey);
+    if (flags & UIKeyModifierCommand)
+        modifiers.add(WebEvent::Modifier::MetaKey);
+    if (flags & UIKeyModifierAlphaShift)
+        modifiers.add(WebEvent::Modifier::CapsLockKey);
+    return modifiers;
+}
+
 } // namespace WebKit
 
-#endif // PLATFORM(IOS_FAMILY) && ENABLE(TOUCH_EVENTS)
+#endif // PLATFORM(IOS_FAMILY)
index ab98d8f..8579c33 100644 (file)
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef WebIOSEventFactory_h
-#define WebIOSEventFactory_h
+#pragma once
 
 #if PLATFORM(IOS_FAMILY)
 
 #import "WebEvent.h"
+#import <UIKit/UIKit.h>
 #import <WebCore/WebEvent.h>
 
 class WebIOSEventFactory {
 public:
     static WebKit::WebKeyboardEvent createWebKeyboardEvent(::WebEvent *);
     static WebKit::WebMouseEvent createWebMouseEvent(::WebEvent *);
+
+    static UIKeyModifierFlags toUIKeyModifierFlags(OptionSet<WebKit::WebEvent::Modifier>);
 };
 
 #endif // PLATFORM(IOS_FAMILY)
-
-#endif // WebIOSEventFactory_h
index 84a7a5a..25c16ec 100644 (file)
 #import <WebCore/KeyEventCodesIOS.h>
 #import <WebCore/PlatformEventFactoryIOS.h>
 
+UIKeyModifierFlags WebIOSEventFactory::toUIKeyModifierFlags(OptionSet<WebKit::WebEvent::Modifier> modifiers)
+{
+    UIKeyModifierFlags modifierFlags = 0;
+    if (modifiers.contains(WebKit::WebEvent::Modifier::ShiftKey))
+        modifierFlags |= UIKeyModifierShift;
+    if (modifiers.contains(WebKit::WebEvent::Modifier::ControlKey))
+        modifierFlags |= UIKeyModifierControl;
+    if (modifiers.contains(WebKit::WebEvent::Modifier::AltKey))
+        modifierFlags |= UIKeyModifierAlternate;
+    if (modifiers.contains(WebKit::WebEvent::Modifier::MetaKey))
+        modifierFlags |= UIKeyModifierCommand;
+    if (modifiers.contains(WebKit::WebEvent::Modifier::CapsLockKey))
+        modifierFlags |= UIKeyModifierAlphaShift;
+    return modifierFlags;
+}
+
 static OptionSet<WebKit::WebEvent::Modifier> modifiersForEvent(::WebEvent *event)
 {
     OptionSet<WebKit::WebEvent::Modifier> modifiers;
index 44c8bb6..8ebba1f 100644 (file)
 #import <WebCore/FloatPoint.h>
 #import <wtf/RetainPtr.h>
 
+#if PLATFORM(IOS_FAMILY)
+#import "WebIOSEventFactory.h"
+#endif
+
 @implementation WKNavigationAction
 
 static WKNavigationType toWKNavigationType(WebCore::NavigationType navigationType)
@@ -127,6 +131,7 @@ static WKSyntheticClickType toWKSyntheticClickType(WebKit::WebMouseEvent::Synthe
 #endif
 
 #if PLATFORM(MAC)
+
 - (NSEventModifierFlags)modifierFlags
 {
     return WebKit::WebEventFactory::toNSEventModifierFlags(_navigationAction->modifiers());
@@ -136,6 +141,14 @@ static WKSyntheticClickType toWKSyntheticClickType(WebKit::WebMouseEvent::Synthe
 {
     return WebKit::WebEventFactory::toNSButtonNumber(_navigationAction->mouseButton());
 }
+
+#else
+
+- (UIKeyModifierFlags)modifierFlags
+{
+    return WebIOSEventFactory::toUIKeyModifierFlags(_navigationAction->modifiers());
+}
+
 #endif
 
 #pragma mark WKObject protocol implementation
index 5df7ae0..ad7422f 100644 (file)
@@ -31,6 +31,8 @@
 @class _WKUserInitiatedAction;
 
 #if TARGET_OS_IPHONE
+#include <UIKit/UIKit.h>
+
 typedef NS_ENUM(NSInteger, WKSyntheticClickType) {
     WKSyntheticClickTypeNoTap,
     WKSyntheticClickTypeOneFingerTap,
@@ -53,6 +55,8 @@ typedef NS_ENUM(NSInteger, WKSyntheticClickType) {
 #if TARGET_OS_IPHONE
 @property (nonatomic, readonly) WKSyntheticClickType _syntheticClickType WK_API_AVAILABLE(ios(10.0));
 @property (nonatomic, readonly) CGPoint _clickLocationInRootViewCoordinates WK_API_AVAILABLE(ios(11.0));
+
+@property (nonatomic, readonly) UIKeyModifierFlags modifierFlags WK_API_AVAILABLE(ios(WK_IOS_TBA));
 #endif
 
 @property (nonatomic, readonly) BOOL _isRedirect WK_API_AVAILABLE(macosx(10.13), ios(11.0));
index d99c2ad..0ba2936 100644 (file)
@@ -80,6 +80,7 @@
 #include <WebCore/LayoutSize.h>
 #include <WebCore/MediaPlaybackTargetContext.h>
 #include <WebCore/MediaProducer.h>
+#include <WebCore/PlatformEvent.h>
 #include <WebCore/PlatformScreen.h>
 #include <WebCore/PointerID.h>
 #include <WebCore/ScrollTypes.h>
@@ -672,7 +673,7 @@ public:
     void disableDoubleTapGesturesDuringTapIfNecessary(uint64_t requestID);
     void contentSizeCategoryDidChange(const String& contentSizeCategory);
     void getSelectionContext(WTF::Function<void(const String&, const String&, const String&, CallbackBase::Error)>&&);
-    void handleTwoFingerTapAtPoint(const WebCore::IntPoint&, uint64_t requestID);
+    void handleTwoFingerTapAtPoint(const WebCore::IntPoint&, OptionSet<WebKit::WebEvent::Modifier>, uint64_t requestID);
     void handleStylusSingleTapAtPoint(const WebCore::IntPoint&, uint64_t requestID);
     void setForceAlwaysUserScalable(bool);
     bool forceAlwaysUserScalable() const { return m_forceAlwaysUserScalable; }
@@ -1171,10 +1172,10 @@ public:
     void willStartUserTriggeredZooming();
 
     void potentialTapAtPosition(const WebCore::FloatPoint&, uint64_t& requestID);
-    void commitPotentialTap(uint64_t layerTreeTransactionIdAtLastTouchStart);
+    void commitPotentialTap(OptionSet<WebKit::WebEvent::Modifier>, uint64_t layerTreeTransactionIdAtLastTouchStart);
     void cancelPotentialTap();
     void tapHighlightAtPosition(const WebCore::FloatPoint&, uint64_t& requestID);
-    void handleTap(const WebCore::FloatPoint&, uint64_t layerTreeTransactionIdAtLastTouchStart);
+    void handleTap(const WebCore::FloatPoint&, OptionSet<WebKit::WebEvent::Modifier>, uint64_t layerTreeTransactionIdAtLastTouchStart);
 
     void inspectorNodeSearchMovedToPosition(const WebCore::FloatPoint&);
     void inspectorNodeSearchEndedAtPosition(const WebCore::FloatPoint&);
index 44c6bf7..9189ebd 100644 (file)
@@ -406,7 +406,7 @@ FOR_EACH_PRIVATE_WKCONTENTVIEW_ACTION(DECLARE_WKCONTENTVIEW_ACTION_FOR_WEB_VIEW)
 - (void)_updateChangedSelection;
 - (BOOL)_interpretKeyEvent:(::WebEvent *)theEvent isCharEvent:(BOOL)isCharEvent;
 - (void)_positionInformationDidChange:(const WebKit::InteractionInformationAtPosition&)info;
-- (void)_attemptClickAtLocation:(CGPoint)location;
+- (void)_attemptClickAtLocation:(CGPoint)location modifierFlags:(UIKeyModifierFlags)modifierFlags;
 - (void)_willStartScrollingOrZooming;
 - (void)_didScroll;
 - (void)_didEndScrollingOrZooming;
index 6803664..1838672 100644 (file)
@@ -1160,6 +1160,11 @@ static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedEl
 }
 #endif
 
+inline static UIKeyModifierFlags gestureRecognizerModifierFlags(UIGestureRecognizer *recognizer)
+{
+    return [recognizer respondsToSelector:@selector(_modifierFlags)] ? recognizer.modifierFlags : 0;
+}
+
 - (void)_webTouchEventsRecognized:(UIWebTouchEventsGestureRecognizer *)gestureRecognizer
 {
     if (!_page->isValid())
@@ -1172,7 +1177,7 @@ static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedEl
         _layerTreeTransactionIdAtLastTouchStart = downcast<WebKit::RemoteLayerTreeDrawingAreaProxy>(*_page->drawingArea()).lastCommittedLayerTreeTransactionID();
 
 #if ENABLE(TOUCH_EVENTS)
-    WebKit::NativeWebTouchEvent nativeWebTouchEvent(lastTouchEvent);
+    WebKit::NativeWebTouchEvent nativeWebTouchEvent { lastTouchEvent, gestureRecognizerModifierFlags(gestureRecognizer) };
     nativeWebTouchEvent.setCanPreventNativeGestures(!_canSendTouchEventsAsynchronously || [gestureRecognizer isDefaultPrevented]);
 
 #if ENABLE(POINTER_EVENTS)
@@ -2055,7 +2060,7 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
         break;
     case UIGestureRecognizerStateEnded:
         if (_highlightLongPressCanClick && _positionInformation.isElement) {
-            [self _attemptClickAtLocation:[gestureRecognizer startPoint]];
+            [self _attemptClickAtLocation:gestureRecognizer.startPoint modifierFlags:gestureRecognizerModifierFlags(gestureRecognizer)];
             [self _finishInteraction];
         } else
             [self _cancelInteraction];
@@ -2074,7 +2079,7 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
 {
     _isTapHighlightIDValid = YES;
     _isExpectingFastSingleTapCommit = YES;
-    _page->handleTwoFingerTapAtPoint(WebCore::roundedIntPoint(gestureRecognizer.centroid), ++_latestTapID);
+    _page->handleTwoFingerTapAtPoint(WebCore::roundedIntPoint(gestureRecognizer.centroid), WebKit::webEventModifierFlags(gestureRecognizerModifierFlags(gestureRecognizer)), ++_latestTapID);
 }
 
 - (void)_stylusSingleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer
@@ -2196,7 +2201,7 @@ static void cancelPotentialTapIfNecessary(WKContentView* contentView)
     }
 
     [_inputPeripheral endEditing];
-    _page->commitPotentialTap(_layerTreeTransactionIdAtLastTouchStart);
+    _page->commitPotentialTap(WebKit::webEventModifierFlags(gestureRecognizerModifierFlags(gestureRecognizer)), _layerTreeTransactionIdAtLastTouchStart);
 
     if (!_isExpectingFastSingleTapCommit)
         [self _finishInteraction];
@@ -2229,7 +2234,7 @@ static void cancelPotentialTapIfNecessary(WKContentView* contentView)
     _smartMagnificationController->handleResetMagnificationGesture(gestureRecognizer.location);
 }
 
-- (void)_attemptClickAtLocation:(CGPoint)location
+- (void)_attemptClickAtLocation:(CGPoint)location modifierFlags:(UIKeyModifierFlags)modifierFlags
 {
     if (![self isFirstResponder]) {
         if (!_inputViewUpdateDeferrer)
@@ -2238,7 +2243,7 @@ static void cancelPotentialTapIfNecessary(WKContentView* contentView)
     }
 
     [_inputPeripheral endEditing];
-    _page->handleTap(location, _layerTreeTransactionIdAtLastTouchStart);
+    _page->handleTap(location, WebKit::webEventModifierFlags(modifierFlags), _layerTreeTransactionIdAtLastTouchStart);
 }
 
 - (void)setUpTextSelectionAssistant
@@ -5411,7 +5416,7 @@ static const double minimumFocusedElementAreaForSuppressingSelectionAssistant =
 
 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant openElementAtLocation:(CGPoint)location
 {
-    [self _attemptClickAtLocation:location];
+    [self _attemptClickAtLocation:location modifierFlags:0];
 }
 
 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant shareElementWithURL:(NSURL *)url rect:(CGRect)boundingRect
@@ -6418,6 +6423,22 @@ ALLOW_DEPRECATED_DECLARATIONS_END
     _page->performDictionaryLookupAtLocation(WebCore::FloatPoint(locationInViewCoordinates));
 }
 
+static WebEventFlags webEventFlagsForUIKeyModifierFlags(UIKeyModifierFlags flags)
+{
+    WebEventFlags eventFlags = 0;
+    if (flags & UIKeyModifierShift)
+        eventFlags |= WebEventFlagMaskLeftShiftKey;
+    if (flags & UIKeyModifierControl)
+        eventFlags |= WebEventFlagMaskLeftControlKey;
+    if (flags & UIKeyModifierAlternate)
+        eventFlags |= WebEventFlagMaskLeftOptionKey;
+    if (flags & UIKeyModifierCommand)
+        eventFlags |= WebEventFlagMaskLeftCommandKey;
+    if (flags & UIKeyModifierAlphaShift)
+        eventFlags |= WebEventFlagMaskLeftCapsLockKey;
+    return eventFlags;
+}
+
 - (void)_hoverGestureRecognizerChanged:(UIGestureRecognizer *)gestureRecognizer
 {
     if (!_page->isValid())
@@ -6439,7 +6460,7 @@ ALLOW_DEPRECATED_DECLARATIONS_END
         break;
     }
 
-    auto event = adoptNS([[::WebEvent alloc] initWithMouseEventType:WebEventMouseMoved timeStamp:timestamp location:point]);
+    auto event = adoptNS([[::WebEvent alloc] initWithMouseEventType:WebEventMouseMoved timeStamp:timestamp location:point modifiers:webEventFlagsForUIKeyModifierFlags(gestureRecognizerModifierFlags(gestureRecognizer))]);
     _page->handleMouseEvent(WebKit::NativeWebMouseEvent(event.get()));
 }
 #endif
index fe1d7a3..e5006ed 100644 (file)
@@ -596,9 +596,9 @@ void WebPageProxy::getSelectionContext(WTF::Function<void(const String&, const S
     m_process->send(Messages::WebPage::GetSelectionContext(callbackID), m_pageID);
 }
 
-void WebPageProxy::handleTwoFingerTapAtPoint(const WebCore::IntPoint& point, uint64_t requestID)
+void WebPageProxy::handleTwoFingerTapAtPoint(const WebCore::IntPoint& point, OptionSet<WebEvent::Modifier> modifiers, uint64_t requestID)
 {
-    process().send(Messages::WebPage::HandleTwoFingerTapAtPoint(point, requestID), m_pageID);
+    process().send(Messages::WebPage::HandleTwoFingerTapAtPoint(point, modifiers, requestID), m_pageID);
 }
 
 void WebPageProxy::handleStylusSingleTapAtPoint(const WebCore::IntPoint& point, uint64_t requestID)
@@ -827,9 +827,9 @@ void WebPageProxy::potentialTapAtPosition(const WebCore::FloatPoint& position, u
     process().send(Messages::WebPage::PotentialTapAtPosition(requestID, position), m_pageID);
 }
 
-void WebPageProxy::commitPotentialTap(uint64_t layerTreeTransactionIdAtLastTouchStart)
+void WebPageProxy::commitPotentialTap(OptionSet<WebEvent::Modifier> modifiers, uint64_t layerTreeTransactionIdAtLastTouchStart)
 {
-    process().send(Messages::WebPage::CommitPotentialTap(layerTreeTransactionIdAtLastTouchStart), m_pageID);
+    process().send(Messages::WebPage::CommitPotentialTap(modifiers, layerTreeTransactionIdAtLastTouchStart), m_pageID);
 }
 
 void WebPageProxy::cancelPotentialTap()
@@ -842,9 +842,9 @@ void WebPageProxy::tapHighlightAtPosition(const WebCore::FloatPoint& position, u
     process().send(Messages::WebPage::TapHighlightAtPosition(requestID, position), m_pageID);
 }
 
-void WebPageProxy::handleTap(const FloatPoint& location, uint64_t layerTreeTransactionIdAtLastTouchStart)
+void WebPageProxy::handleTap(const FloatPoint& location, OptionSet<WebEvent::Modifier> modifiers, uint64_t layerTreeTransactionIdAtLastTouchStart)
 {
-    process().send(Messages::WebPage::HandleTap(roundedIntPoint(location), layerTreeTransactionIdAtLastTouchStart), m_pageID);
+    process().send(Messages::WebPage::HandleTap(roundedIntPoint(location), modifiers, layerTreeTransactionIdAtLastTouchStart), m_pageID);
 }
 
 void WebPageProxy::inspectorNodeSearchMovedToPosition(const WebCore::FloatPoint& position)
index b913332..18eea01 100644 (file)
@@ -614,9 +614,9 @@ public:
     bool allowsUserScaling() const;
     bool hasStablePageScaleFactor() const { return m_hasStablePageScaleFactor; }
 
-    void handleTap(const WebCore::IntPoint&, uint64_t lastLayerTreeTransactionId);
+    void handleTap(const WebCore::IntPoint&, OptionSet<WebKit::WebEvent::Modifier>, uint64_t lastLayerTreeTransactionId);
     void potentialTapAtPosition(uint64_t requestID, const WebCore::FloatPoint&);
-    void commitPotentialTap(uint64_t lastLayerTreeTransactionId);
+    void commitPotentialTap(OptionSet<WebKit::WebEvent::Modifier>, uint64_t lastLayerTreeTransactionId);
     void commitPotentialTapFailed();
     void cancelPotentialTap();
     void cancelPotentialTapInFrame(WebFrame&);
@@ -662,7 +662,7 @@ public:
     WebCore::IntRect rectForElementAtInteractionLocation();
     void updateSelectionAppearance();
     void getSelectionContext(CallbackID);
-    void handleTwoFingerTapAtPoint(const WebCore::IntPoint&, uint64_t requestID);
+    void handleTwoFingerTapAtPoint(const WebCore::IntPoint&, OptionSet<WebKit::WebEvent::Modifier>, uint64_t requestID);
     void handleStylusSingleTapAtPoint(const WebCore::IntPoint&, uint64_t requestID);
     void getRectsForGranularityWithSelectionOffset(uint32_t, int32_t, CallbackID);
     void getRectsAtSelectionOffsetWithText(int32_t, const String&, CallbackID);
@@ -1185,8 +1185,8 @@ private:
     RefPtr<WebCore::Range> rangeForWebSelectionAtPosition(const WebCore::IntPoint&, const WebCore::VisiblePosition&, SelectionFlags&);
     void getFocusedElementInformation(FocusedElementInformation&);
     void platformInitializeAccessibility();
-    void handleSyntheticClick(WebCore::Node* nodeRespondingToClick, const WebCore::FloatPoint& location);
-    void completeSyntheticClick(WebCore::Node* nodeRespondingToClick, const WebCore::FloatPoint& location, WebCore::SyntheticClickType);
+    void handleSyntheticClick(WebCore::Node* nodeRespondingToClick, const WebCore::FloatPoint& location, OptionSet<WebKit::WebEvent::Modifier>);
+    void completeSyntheticClick(WebCore::Node* nodeRespondingToClick, const WebCore::FloatPoint& location, OptionSet<WebKit::WebEvent::Modifier>, WebCore::SyntheticClickType);
     void sendTapHighlightForNodeIfNecessary(uint64_t requestID, WebCore::Node*);
     void resetTextAutosizing();
     WebCore::VisiblePosition visiblePositionInFocusedNodeForPoint(const WebCore::Frame&, const WebCore::IntPoint&, bool isInteractingWithFocusedElement);
@@ -1768,6 +1768,7 @@ private:
     RefPtr<WebCore::Node> m_pendingSyntheticClickNode;
     WebCore::FloatPoint m_pendingSyntheticClickLocation;
     WebCore::FloatRect m_previousExposedContentRect;
+    OptionSet<WebKit::WebEvent::Modifier> m_pendingSyntheticClickModifiers;
     FocusedElementIdentifier m_currentFocusedElementIdentifier { 0 };
     Optional<DynamicViewportSizeUpdateID> m_pendingDynamicViewportSizeUpdateID;
     double m_lastTransactionPageScaleFactor { 0 };
index a8c6176..2d5b7b5 100644 (file)
@@ -51,9 +51,9 @@ messages -> WebPage LegacyReceiver {
     SetOverrideViewportArguments(Optional<WebCore::ViewportArguments> arguments)
     DynamicViewportSizeUpdate(WebCore::FloatSize viewLayoutSize, WebCore::FloatSize maximumUnobscuredSize, WebCore::FloatRect targetExposedContentRect, WebCore::FloatRect targetUnobscuredRect, WebCore::FloatRect targetUnobscuredRectInScrollViewCoordinates, WebCore::RectEdges<float> targetUnobscuredSafeAreaInsets, double scale, int32_t deviceOrientation, uint64_t dynamicViewportSizeUpdateID)
 
-    HandleTap(WebCore::IntPoint point, uint64_t lastLayerTreeTransactionId)
+    HandleTap(WebCore::IntPoint point, OptionSet<WebKit::WebEvent::Modifier> modifiers, uint64_t lastLayerTreeTransactionId)
     PotentialTapAtPosition(uint64_t requestID, WebCore::FloatPoint point)
-    CommitPotentialTap(uint64_t lastLayerTreeTransactionId)
+    CommitPotentialTap(OptionSet<WebKit::WebEvent::Modifier> modifiers, uint64_t lastLayerTreeTransactionId)
     CancelPotentialTap()
     TapHighlightAtPosition(uint64_t requestID, WebCore::FloatPoint point)
     InspectorNodeSearchMovedToPosition(WebCore::FloatPoint point)
@@ -98,7 +98,7 @@ messages -> WebPage LegacyReceiver {
     ContentSizeCategoryDidChange(String contentSizeCategory)
     GetSelectionContext(WebKit::CallbackID callbackID)
     SetAllowsMediaDocumentInlinePlayback(bool allows)
-    HandleTwoFingerTapAtPoint(WebCore::IntPoint point, uint64_t requestID)
+    HandleTwoFingerTapAtPoint(WebCore::IntPoint point, OptionSet<WebKit::WebEvent::Modifier> modifiers, uint64_t requestID)
     HandleStylusSingleTapAtPoint(WebCore::IntPoint point, uint64_t requestID)
     SetForceAlwaysUserScalable(bool userScalable)
     GetRectsForGranularityWithSelectionOffset(uint32_t granularity, int32_t offset, WebKit::CallbackID callbackID)
index 7eb71ae..f9dfd0d 100644 (file)
@@ -534,7 +534,7 @@ void WebPage::updateSelectionAppearance()
         didChangeSelection();
 }
 
-void WebPage::handleSyntheticClick(Node* nodeRespondingToClick, const WebCore::FloatPoint& location)
+void WebPage::handleSyntheticClick(Node* nodeRespondingToClick, const WebCore::FloatPoint& location, OptionSet<WebEvent::Modifier> modifiers)
 {
     IntPoint roundedAdjustedPoint = roundedIntPoint(location);
     Frame& mainframe = m_page->mainFrame();
@@ -543,7 +543,12 @@ void WebPage::handleSyntheticClick(Node* nodeRespondingToClick, const WebCore::F
     WKStartObservingContentChanges();
     WKStartObservingDOMTimerScheduling();
 
-    mainframe.eventHandler().mouseMoved(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, NoButton, PlatformEvent::MouseMoved, 0, false, false, false, false, WallTime::now(), WebCore::ForceAtClick, WebCore::NoTap));
+    // FIXME: Pass caps lock state.
+    bool shiftKey = modifiers.contains(WebEvent::Modifier::ShiftKey);
+    bool ctrlKey = modifiers.contains(WebEvent::Modifier::ControlKey);
+    bool altKey = modifiers.contains(WebEvent::Modifier::AltKey);
+    bool metaKey = modifiers.contains(WebEvent::Modifier::MetaKey);
+    mainframe.eventHandler().mouseMoved(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, NoButton, PlatformEvent::MouseMoved, 0, shiftKey, ctrlKey, altKey, metaKey, WallTime::now(), WebCore::ForceAtClick, WebCore::NoTap));
     mainframe.document()->updateStyleIfNeeded();
 
     WKStopObservingDOMTimerScheduling();
@@ -551,6 +556,7 @@ void WebPage::handleSyntheticClick(Node* nodeRespondingToClick, const WebCore::F
 
     m_pendingSyntheticClickNode = nullptr;
     m_pendingSyntheticClickLocation = FloatPoint();
+    m_pendingSyntheticClickModifiers = { };
 
     if (m_isClosed)
         return;
@@ -560,15 +566,16 @@ void WebPage::handleSyntheticClick(Node* nodeRespondingToClick, const WebCore::F
         // The move event caused new contents to appear. Don't send the click event.
         LOG(ContentObservation, "handleSyntheticClick: Observed meaningful visible change -> hover.");
         return;
-    case WKContentIndeterminateChange:
+    case WKContentIndeterminateChange: {
         // Wait for callback to completePendingSyntheticClickForContentChangeObserver() to decide whether to send the click event.
         m_pendingSyntheticClickNode = nodeRespondingToClick;
         m_pendingSyntheticClickLocation = location;
+        m_pendingSyntheticClickModifiers = modifiers;
         LOG(ContentObservation, "handleSyntheticClick: Observed some change, but can't decide it yet -> wait.");
         return;
-    case WKContentNoChange:
+    case WKContentNoChange:
         LOG(ContentObservation, "handleSyntheticClick: No change was observed -> click.");
-        completeSyntheticClick(nodeRespondingToClick, location, WebCore::OneFingerTap);
+        completeSyntheticClick(nodeRespondingToClick, location, modifiers, WebCore::OneFingerTap);
         return;
     }
     ASSERT_NOT_REACHED();
@@ -582,15 +589,16 @@ void WebPage::completePendingSyntheticClickForContentChangeObserver()
     // Only dispatch the click if the document didn't get changed by any timers started by the move event.
     if (WKObservedContentChange() == WKContentNoChange) {
         LOG(ContentObservation, "No chage was observed -> click.");
-        completeSyntheticClick(m_pendingSyntheticClickNode.get(), m_pendingSyntheticClickLocation, WebCore::OneFingerTap);
+        completeSyntheticClick(m_pendingSyntheticClickNode.get(), m_pendingSyntheticClickLocation, m_pendingSyntheticClickModifiers, WebCore::OneFingerTap);
     } else
         LOG(ContentObservation, "Observed meaningful visible change -> hover.");
 
     m_pendingSyntheticClickNode = nullptr;
     m_pendingSyntheticClickLocation = FloatPoint();
+    m_pendingSyntheticClickModifiers = { };
 }
 
-void WebPage::completeSyntheticClick(Node* nodeRespondingToClick, const WebCore::FloatPoint& location, SyntheticClickType syntheticClickType)
+void WebPage::completeSyntheticClick(Node* nodeRespondingToClick, const WebCore::FloatPoint& location, OptionSet<WebEvent::Modifier> modifiers, SyntheticClickType syntheticClickType)
 {
     IntPoint roundedAdjustedPoint = roundedIntPoint(location);
     Frame& mainframe = m_page->mainFrame();
@@ -603,11 +611,17 @@ void WebPage::completeSyntheticClick(Node* nodeRespondingToClick, const WebCore:
     bool tapWasHandled = false;
     m_lastInteractionLocation = roundedAdjustedPoint;
 
-    tapWasHandled |= mainframe.eventHandler().handleMousePressEvent(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, LeftButton, PlatformEvent::MousePressed, 1, false, false, false, false, WallTime::now(), WebCore::ForceAtClick, syntheticClickType));
+    // FIXME: Pass caps lock state.
+    bool shiftKey = modifiers.contains(WebEvent::Modifier::ShiftKey);
+    bool ctrlKey = modifiers.contains(WebEvent::Modifier::ControlKey);
+    bool altKey = modifiers.contains(WebEvent::Modifier::AltKey);
+    bool metaKey = modifiers.contains(WebEvent::Modifier::MetaKey);
+
+    tapWasHandled |= mainframe.eventHandler().handleMousePressEvent(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, LeftButton, PlatformEvent::MousePressed, 1, shiftKey, ctrlKey, altKey, metaKey, WallTime::now(), WebCore::ForceAtClick, syntheticClickType));
     if (m_isClosed)
         return;
 
-    tapWasHandled |= mainframe.eventHandler().handleMouseReleaseEvent(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, LeftButton, PlatformEvent::MouseReleased, 1, false, false, false, false, WallTime::now(), WebCore::ForceAtClick, syntheticClickType));
+    tapWasHandled |= mainframe.eventHandler().handleMouseReleaseEvent(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, LeftButton, PlatformEvent::MouseReleased, 1, shiftKey, ctrlKey, altKey, metaKey, WallTime::now(), WebCore::ForceAtClick, syntheticClickType));
     if (m_isClosed)
         return;
 
@@ -627,7 +641,7 @@ void WebPage::completeSyntheticClick(Node* nodeRespondingToClick, const WebCore:
     send(Messages::WebPageProxy::DidCompleteSyntheticClick());
 }
 
-void WebPage::handleTap(const IntPoint& point, uint64_t lastLayerTreeTransactionId)
+void WebPage::handleTap(const IntPoint& point, OptionSet<WebEvent::Modifier> modifiers, uint64_t lastLayerTreeTransactionId)
 {
     FloatPoint adjustedPoint;
     Node* nodeRespondingToClick = m_page->mainFrame().nodeRespondingToClickEvents(point, adjustedPoint);
@@ -644,7 +658,7 @@ void WebPage::handleTap(const IntPoint& point, uint64_t lastLayerTreeTransaction
     }
 #endif
     else
-        handleSyntheticClick(nodeRespondingToClick, adjustedPoint);
+        handleSyntheticClick(nodeRespondingToClick, adjustedPoint, modifiers);
 }
 
 void WebPage::requestFocusedElementInformation(WebKit::CallbackID callbackID)
@@ -739,7 +753,7 @@ void WebPage::sendTapHighlightForNodeIfNecessary(uint64_t requestID, Node* node)
 #endif
 }
 
-void WebPage::handleTwoFingerTapAtPoint(const WebCore::IntPoint& point, uint64_t requestID)
+void WebPage::handleTwoFingerTapAtPoint(const WebCore::IntPoint& point, OptionSet<WebKit::WebEvent::Modifier> modifiers, uint64_t requestID)
 {
     FloatPoint adjustedPoint;
     Node* nodeRespondingToClick = m_page->mainFrame().nodeRespondingToClickEvents(point, adjustedPoint);
@@ -755,7 +769,7 @@ void WebPage::handleTwoFingerTapAtPoint(const WebCore::IntPoint& point, uint64_t
         send(Messages::WebPageProxy::DidNotHandleTapAsClick(roundedIntPoint(adjustedPoint)));
     } else
 #endif
-        completeSyntheticClick(nodeRespondingToClick, adjustedPoint, WebCore::TwoFingerTap);
+        completeSyntheticClick(nodeRespondingToClick, adjustedPoint, modifiers, WebCore::TwoFingerTap);
 }
 
 void WebPage::handleStylusSingleTapAtPoint(const WebCore::IntPoint& point, uint64_t requestID)
@@ -800,7 +814,7 @@ void WebPage::potentialTapAtPosition(uint64_t requestID, const WebCore::FloatPoi
 #endif
 }
 
-void WebPage::commitPotentialTap(uint64_t lastLayerTreeTransactionId)
+void WebPage::commitPotentialTap(OptionSet<WebEvent::Modifier> modifiers, uint64_t lastLayerTreeTransactionId)
 {
     if (!m_potentialTapNode || (!m_potentialTapNode->renderer() && !is<HTMLAreaElement>(m_potentialTapNode.get()))) {
         commitPotentialTapFailed();
@@ -824,7 +838,7 @@ void WebPage::commitPotentialTap(uint64_t lastLayerTreeTransactionId)
             commitPotentialTapFailed();
         } else
 #endif
-            handleSyntheticClick(nodeRespondingToClick, adjustedPoint);
+            handleSyntheticClick(nodeRespondingToClick, adjustedPoint, modifiers);
     } else
         commitPotentialTapFailed();
 
index 8628851..d8483ee 100644 (file)
@@ -1,3 +1,29 @@
+2019-02-11  Daniel Bates  <dabates@apple.com>
+
+        [iOS] Mouse/Touch/Pointer events are missing modifier keys
+        https://bugs.webkit.org/show_bug.cgi?id=191446
+        <rdar://problem/45929460>
+
+        Reviewed by Tim Horton.
+
+        Add support infrastructure for testing touch and stylus taps when holding modifier keys. 
+
+        * DumpRenderTree/ios/UIScriptControllerIOS.mm:
+        (WTR::UIScriptController::singleTapAtPointWithModifiers): Added.
+        (WTR::UIScriptController::stylusTapAtPointWithModifiers): Added.
+        * TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
+        * TestRunnerShared/UIScriptContext/UIScriptController.cpp:
+        (WTR::UIScriptController::singleTapAtPointWithModifiers): Added.
+        (WTR::UIScriptController::stylusTapAtPointWithModifiers): Added.
+        * TestRunnerShared/UIScriptContext/UIScriptController.h:
+        * WebKitTestRunner/ios/UIScriptControllerIOS.mm:
+        (WTR::arrayLength):
+        (WTR::parseModifierArray):
+        (WTR::UIScriptController::singleTapAtPoint): Implemented in terms of singleTapAtPointWithModifiers().
+        (WTR::UIScriptController::singleTapAtPointWithModifiers): Added.
+        (WTR::UIScriptController::stylusTapAtPoint): Implemented in terms of stylusTapAtPointWithModifiers().
+        (WTR::UIScriptController::stylusTapAtPointWithModifiers): Added.
+
 2019-02-11  Darshan Kadu  <dkadu@igalia.com>
 
         DumpRenderTree should report unknown options
index db11ad6..7a9b084 100644 (file)
@@ -115,6 +115,10 @@ void UIScriptController::singleTapAtPoint(long x, long y, JSValueRef callback)
 {
 }
 
+void UIScriptController::singleTapAtPointWithModifiers(long x, long y, JSValueRef modifierArray, JSValueRef callback)
+{
+}
+
 void UIScriptController::doubleTapAtPoint(long x, long y, JSValueRef callback)
 {
 }
@@ -143,6 +147,10 @@ void UIScriptController::stylusTapAtPoint(long x, long y, float azimuthAngle, fl
 {
 }
 
+void UIScriptController::stylusTapAtPointWithModifiers(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef modifierArray, JSValueRef callback)
+{
+}
+
 void UIScriptController::sendEventStream(JSStringRef eventsJSON, JSValueRef callback)
 {
 }
index e54467f..5a8d07a 100644 (file)
@@ -56,6 +56,7 @@ interface UIScriptController {
     void touchDownAtPoint(long x, long y, long touchCount, object callback);
     void liftUpAtPoint(long x, long y, long touchCount, object callback);
     void singleTapAtPoint(long x, long y, object callback);
+    void singleTapAtPointWithModifiers(long x, long y, object modifierArray, object callback);
     void doubleTapAtPoint(long x, long y, object callback);
     void dragFromPointToPoint(long startX, long startY, long endX, long endY, double durationSeconds, object callback);
 
@@ -65,6 +66,7 @@ interface UIScriptController {
     void stylusMoveToPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, object callback);
     void stylusUpAtPoint(long x, long y, object callback);
     void stylusTapAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, object callback);
+    void stylusTapAtPointWithModifiers(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, object modifierArray, object callback);
 
     void enterText(DOMString text);
     void typeCharacterUsingHardwareKeyboard(DOMString character, object callback);
index 4b45d56..0cdc700 100644 (file)
@@ -265,6 +265,10 @@ void UIScriptController::singleTapAtPoint(long x, long y, JSValueRef)
 {
 }
 
+void UIScriptController::singleTapAtPointWithModifiers(long x, long y, JSValueRef modifierArray, JSValueRef callback)
+{
+}
+
 void UIScriptController::doubleTapAtPoint(long x, long y, JSValueRef)
 {
 }
@@ -293,6 +297,10 @@ void UIScriptController::stylusTapAtPoint(long x, long y, float azimuthAngle, fl
 {
 }
 
+void UIScriptController::stylusTapAtPointWithModifiers(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef modifierArray, JSValueRef callback)
+{
+}
+
 void UIScriptController::sendEventStream(JSStringRef eventsJSON, JSValueRef callback)
 {
 }
index 956c345..11a1cb2 100644 (file)
@@ -78,6 +78,7 @@ public:
     void touchDownAtPoint(long x, long y, long touchCount, JSValueRef callback);
     void liftUpAtPoint(long x, long y, long touchCount, JSValueRef callback);
     void singleTapAtPoint(long x, long y, JSValueRef callback);
+    void singleTapAtPointWithModifiers(long x, long y, JSValueRef modifierArray, JSValueRef callback);
     void doubleTapAtPoint(long x, long y, JSValueRef callback);
     void dragFromPointToPoint(long startX, long startY, long endX, long endY, double durationSeconds, JSValueRef callback);
 
@@ -85,6 +86,7 @@ public:
     void stylusMoveToPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback);
     void stylusUpAtPoint(long x, long y, JSValueRef callback);
     void stylusTapAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback);
+    void stylusTapAtPointWithModifiers(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef modifierArray, JSValueRef callback);
 
     void longPressAtPoint(long x, long y, JSValueRef callback);
 
index 4952765..fbcc25b 100644 (file)
@@ -63,7 +63,45 @@ static NSDictionary *toNSDictionary(CGRect rect)
         @"height": @(rect.size.height)
     };
 }
-    
+
+static unsigned arrayLength(JSContextRef context, JSObjectRef array)
+{
+    auto lengthString = adopt(JSStringCreateWithUTF8CString("length"));
+    if (auto lengthValue = JSObjectGetProperty(context, array, lengthString.get(), nullptr))
+        return static_cast<unsigned>(JSValueToNumber(context, lengthValue, nullptr));
+    return 0;
+}
+
+static Vector<String> parseModifierArray(JSContextRef context, JSValueRef arrayValue)
+{
+    if (!arrayValue)
+        return { };
+
+    // The value may either be a string with a single modifier or an array of modifiers.
+    if (JSValueIsString(context, arrayValue)) {
+        auto string = toWTFString(toWK(adopt(JSValueToStringCopy(context, arrayValue, nullptr))));
+        return { string };
+    }
+
+    if (!JSValueIsObject(context, arrayValue))
+        return { };
+    JSObjectRef array = const_cast<JSObjectRef>(arrayValue);
+    unsigned length = arrayLength(context, array);
+    Vector<String> modifiers;
+    modifiers.reserveInitialCapacity(length);
+    for (unsigned i = 0; i < length; ++i) {
+        JSValueRef exception = nullptr;
+        JSValueRef value = JSObjectGetPropertyAtIndex(context, array, i, &exception);
+        if (exception)
+            continue;
+        auto string = adopt(JSValueToStringCopy(context, value, &exception));
+        if (exception)
+            continue;
+        modifiers.append(toWTFString(toWK(string.get())));
+    }
+    return modifiers;
+}
+
 void UIScriptController::checkForOutstandingCallbacks()
 {
     if (![[HIDEventGenerator sharedHIDEventGenerator] checkForOutstandingCallbacks])
@@ -193,12 +231,29 @@ void UIScriptController::liftUpAtPoint(long x, long y, long touchCount, JSValueR
 
 void UIScriptController::singleTapAtPoint(long x, long y, JSValueRef callback)
 {
+    singleTapAtPointWithModifiers(x, y, nullptr, callback);
+}
+
+void UIScriptController::singleTapAtPointWithModifiers(long x, long y, JSValueRef modifierArray, JSValueRef callback)
+{
     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
 
+    auto modifierFlags = parseModifierArray(m_context->jsContext(), modifierArray);
+    for (auto& modifierFlag : modifierFlags)
+        [[HIDEventGenerator sharedHIDEventGenerator] keyDown:modifierFlag];
+
     [[HIDEventGenerator sharedHIDEventGenerator] tap:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
         if (!m_context)
             return;
-        m_context->asyncTaskComplete(callbackID);
+        for (size_t i = modifierFlags.size(); i; ) {
+            --i;
+            [[HIDEventGenerator sharedHIDEventGenerator] keyUp:modifierFlags[i]];
+        }
+        [[HIDEventGenerator sharedHIDEventGenerator] sendMarkerHIDEventWithCompletionBlock:^{
+            if (!m_context)
+                return;
+            m_context->asyncTaskComplete(callbackID);
+        }];
     }];
 }
 
@@ -251,16 +306,33 @@ void UIScriptController::stylusUpAtPoint(long x, long y, JSValueRef callback)
 
 void UIScriptController::stylusTapAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
 {
+    stylusTapAtPointWithModifiers(x, y, azimuthAngle, altitudeAngle, pressure, nullptr, callback);
+}
+
+void UIScriptController::stylusTapAtPointWithModifiers(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef modifierArray, JSValueRef callback)
+{
     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
 
+    auto modifierFlags = parseModifierArray(m_context->jsContext(), modifierArray);
+    for (auto& modifierFlag : modifierFlags)
+        [[HIDEventGenerator sharedHIDEventGenerator] keyDown:modifierFlag];
+
     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
     [[HIDEventGenerator sharedHIDEventGenerator] stylusTapAtPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
         if (!m_context)
             return;
-        m_context->asyncTaskComplete(callbackID);
+        for (size_t i = modifierFlags.size(); i; ) {
+            --i;
+            [[HIDEventGenerator sharedHIDEventGenerator] keyUp:modifierFlags[i]];
+        }
+        [[HIDEventGenerator sharedHIDEventGenerator] sendMarkerHIDEventWithCompletionBlock:^{
+            if (!m_context)
+                return;
+            m_context->asyncTaskComplete(callbackID);
+        }];
     }];
 }
-    
+
 void convertCoordinates(NSMutableDictionary *event)
 {
     if (event[HIDEventTouchesKey]) {
@@ -349,44 +421,6 @@ void UIScriptController::typeCharacterUsingHardwareKeyboard(JSStringRef characte
     }];
 }
 
-static unsigned arrayLength(JSContextRef context, JSObjectRef array)
-{
-    auto lengthString = adopt(JSStringCreateWithUTF8CString("length"));
-    if (auto lengthValue = JSObjectGetProperty(context, array, lengthString.get(), nullptr))
-        return static_cast<unsigned>(JSValueToNumber(context, lengthValue, nullptr));
-    return 0;
-}
-
-static Vector<String> parseModifierArray(JSContextRef context, JSValueRef arrayValue)
-{
-    if (!arrayValue)
-        return { };
-
-    // The value may either be a string with a single modifier or an array of modifiers.
-    if (JSValueIsString(context, arrayValue)) {
-        auto string = toWTFString(toWK(adopt(JSValueToStringCopy(context, arrayValue, nullptr))));
-        return { string };
-    }
-
-    if (!JSValueIsObject(context, arrayValue))
-        return { };
-    JSObjectRef array = const_cast<JSObjectRef>(arrayValue);
-    unsigned length = arrayLength(context, array);
-    Vector<String> modifiers;
-    modifiers.reserveInitialCapacity(length);
-    for (unsigned i = 0; i < length; ++i) {
-        JSValueRef exception = nullptr;
-        JSValueRef value = JSObjectGetPropertyAtIndex(context, array, i, &exception);
-        if (exception)
-            continue;
-        auto string = adopt(JSValueToStringCopy(context, value, &exception));
-        if (exception)
-            continue;
-        modifiers.append(toWTFString(toWK(string.get())));
-    }
-    return modifiers;
-}
-
 static UIPhysicalKeyboardEvent *createUIPhysicalKeyboardEvent(NSString *hidInputString, NSString *uiEventInputString, UIKeyModifierFlags modifierFlags, UIKeyboardInputFlags inputFlags, bool isKeyDown)
 {
     auto* keyboardEvent = [getUIPhysicalKeyboardEventClass() _eventWithInput:uiEventInputString inputFlags:inputFlags];