Support simulated mouse events on iOS based on a PlatformTouchEvent
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 13 Feb 2019 13:34:43 +0000 (13:34 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 13 Feb 2019 13:34:43 +0000 (13:34 +0000)
https://bugs.webkit.org/show_bug.cgi?id=194501
<rdar://problem/46910790>

Reviewed by Dean Jackson.

Source/WebCore:

Add support for two new internal runtime flags to control whether simulated mouse events should be dipatched along with touch events and
whether simulated mousemove events dispatched should automatically trigger the behavior preventDefault() would also trigger. To facilitate
that, we allow for a MouseEvent to be created, much like a PointerEvent, based on a PlatformTouchEvent. Then, we set a flag on Event within
EventTarget::innerInvokeEventListeners() to see whether any page code has been evaluated as a result of a mousemove event being dispatched.
Finally, we also track mouse events when invalidating touch regions provided the required internal runtime flag is on.

Test: fast/events/touch/ios/mouse-events-dispatch-with-touch.html

* SourcesCocoa.txt:
* WebCore.xcodeproj/project.pbxproj:
* dom/Event.cpp:
* dom/Event.h:
(WebCore::Event::hasEncounteredListener const):
(WebCore::Event::setHasEncounteredListener):
* dom/EventNames.h:
(WebCore::EventNames::isTouchRelatedEventType const):
(WebCore::EventNames::touchRelatedEventNames const):
(WebCore::EventNames::extendedTouchRelatedEventNames const):
(WebCore::EventNames::isTouchEventType const): Deleted.
(WebCore::EventNames::touchAndPointerEventNames const): Deleted.
* dom/EventTarget.cpp:
(WebCore::EventTarget::innerInvokeEventListeners):
* dom/MouseEvent.h:
* dom/Node.cpp:
(WebCore::Node::moveNodeToNewDocument):
(WebCore::tryAddEventListener):
(WebCore::tryRemoveEventListener):
(WebCore::Node::defaultEventHandler):
* dom/ios/MouseEventIOS.cpp: Added.
(WebCore::mouseEventType):
(WebCore::MouseEvent::create):
* dom/ios/PointerEventIOS.cpp:
(WebCore::pointerEventType):
(WebCore::PointerEvent::create):
(WebCore::eventType): Deleted.
* page/DOMWindow.cpp:
(WebCore::DOMWindow::addEventListener):
(WebCore::DOMWindow::removeEventListener):
* page/EventHandler.h:
* page/RuntimeEnabledFeatures.h:
(WebCore::RuntimeEnabledFeatures::mouseEventsSimulationEnabled const):
(WebCore::RuntimeEnabledFeatures::setMouseEventsSimulationEnabled):
(WebCore::RuntimeEnabledFeatures::mousemoveEventHandlingPreventsDefaultEnabled const):
(WebCore::RuntimeEnabledFeatures::setMousemoveEventHandlingPreventsDefaultEnabled):

Source/WebKit:

Add two new internal runtime flags to control whether simulated mouse events should be dipatched along with touch events and whether
simulated mousemove events dispatched should automatically trigger the behavior preventDefault() would also trigger. We also ensure
that we correctly create touch tracking regions for mouse events.

* Shared/WebPreferences.yaml:
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::updateTouchEventTracking):

LayoutTests:

Add a new test to check that we correctly dispatch mouse events as touches occur.

* fast/events/touch/ios/mouse-events-dispatch-with-touch-expected.txt: Added.
* fast/events/touch/ios/mouse-events-dispatch-with-touch.html: Added.
* pointerevents/utils.js:
(prototype.handleEvent):
(prototype._handlePointerEvent):
(prototype._handleMouseEvent):

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

21 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/events/touch/ios/mouse-events-dispatch-with-touch-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/touch/ios/mouse-events-dispatch-with-touch.html [new file with mode: 0644]
LayoutTests/pointerevents/utils.js
Source/WebCore/ChangeLog
Source/WebCore/SourcesCocoa.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/dom/Event.cpp
Source/WebCore/dom/Event.h
Source/WebCore/dom/EventNames.h
Source/WebCore/dom/EventTarget.cpp
Source/WebCore/dom/MouseEvent.h
Source/WebCore/dom/Node.cpp
Source/WebCore/dom/ios/MouseEventIOS.cpp [new file with mode: 0644]
Source/WebCore/dom/ios/PointerEventIOS.cpp
Source/WebCore/page/DOMWindow.cpp
Source/WebCore/page/EventHandler.h
Source/WebCore/page/RuntimeEnabledFeatures.h
Source/WebKit/ChangeLog
Source/WebKit/Shared/WebPreferences.yaml
Source/WebKit/UIProcess/WebPageProxy.cpp

index 5e053fc..ade8e20 100644 (file)
@@ -1,3 +1,20 @@
+2019-02-13  Antoine Quint  <graouts@apple.com>
+
+        Support simulated mouse events on iOS based on a PlatformTouchEvent
+        https://bugs.webkit.org/show_bug.cgi?id=194501
+        <rdar://problem/46910790>
+
+        Reviewed by Dean Jackson.
+
+        Add a new test to check that we correctly dispatch mouse events as touches occur.
+
+        * fast/events/touch/ios/mouse-events-dispatch-with-touch-expected.txt: Added.
+        * fast/events/touch/ios/mouse-events-dispatch-with-touch.html: Added.
+        * pointerevents/utils.js:
+        (prototype.handleEvent):
+        (prototype._handlePointerEvent):
+        (prototype._handleMouseEvent):
+
 2019-02-13  Fujii Hironori  <Hironori.Fujii@sony.com>
 
         [GTK][WPE] Don't use DumpJSConsoleLogInStdErr expectation in platform TestExpectations
diff --git a/LayoutTests/fast/events/touch/ios/mouse-events-dispatch-with-touch-expected.txt b/LayoutTests/fast/events/touch/ios/mouse-events-dispatch-with-touch-expected.txt
new file mode 100644 (file)
index 0000000..28cf51e
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Mouse events are dispatched as touches start, move and end on the digitizer, with at most one touch at a time dispatching mouse events. 
+
diff --git a/LayoutTests/fast/events/touch/ios/mouse-events-dispatch-with-touch.html b/LayoutTests/fast/events/touch/ios/mouse-events-dispatch-with-touch.html
new file mode 100644 (file)
index 0000000..355ee1e
--- /dev/null
@@ -0,0 +1,54 @@
+<!DOCTYPE html><!-- webkit-test-runner [ internal:MouseEventsSimulationEnabled=true ] -->
+<html>
+<head>
+<meta charset=utf-8>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+</head>
+<body>
+<script src="../../../../resources/testharness.js"></script>
+<script src="../../../../resources/testharnessreport.js"></script>
+<script src="../../../../pointerevents/utils.js"></script>
+<script>
+
+'use strict';
+
+target_test((target, test) => {
+    const eventTracker = new EventTracker(target, ["mousedown", "mousemove", "mouseup"]);
+
+    const one = ui.finger();
+    const two = ui.finger();
+    const three = ui.finger();
+    ui.sequence([
+        // Touch "one" will yield mouse events since it's the first touch to begin.
+        one.begin({ x: 10, y: 10 }),
+        // Touch "two" will not yield mouse events since there was already a touch
+        // yielding mouse events when it began.
+        two.begin({ x: 50, y: 50 }),
+        two.move({ x: 70, y: 70 }),
+        one.move({ x: 30, y: 30 }),
+        one.end(),
+        two.move({ x: 50, y: 50 }),
+        // Touch "three" will yield mouse events since it's the first touch to begin
+        // since the last touch to yield mouse events ("one") ended.
+        three.begin({ x: 20, y: 20 }),
+        two.end(),
+        three.move({ x: 40, y: 40 }),
+        three.end()
+    ]).then(() => {
+        eventTracker.assertMatchesEvents([
+            // Events dispatched for touch "one".
+            { type: "mousedown", x: 10, y: 10 },
+            { type: "mousemove", x: 30, y: 30 },
+            { type: "mouseup", x: 30, y: 30 },
+            // Events dispatched for touch "three".
+            { type: "mousedown", x: 20, y: 20 },
+            { type: "mousemove", x: 40, y: 40 },
+            { type: "mouseup", x: 40, y: 40 }
+        ]);
+        test.done();
+    });
+}, "Mouse events are dispatched as touches start, move and end on the digitizer, with at most one touch at a time dispatching mouse events.");
+
+</script>
+</body>
+</html>
\ No newline at end of file
index c89f0c3..52e9a1e 100644 (file)
@@ -46,12 +46,19 @@ class EventTracker
 
     handleEvent(event)
     {
+        if (event instanceof PointerEvent)
+            this._handlePointerEvent(event);
+        else if (event instanceof MouseEvent)
+            this._handleMouseEvent(event);
+    }
+
+    _handlePointerEvent(event)
+    {
         if (!this.pointerIdToTouchIdMap[event.pointerId])
             this.pointerIdToTouchIdMap[event.pointerId] = Object.keys(this.pointerIdToTouchIdMap).length + 1;
 
-        const id = this.pointerIdToTouchIdMap[event.pointerId];
         this.events.push({
-            id,
+            id: this.pointerIdToTouchIdMap[event.pointerId],
             type: event.type,
             x: event.clientX,
             y: event.clientY,
@@ -59,6 +66,15 @@ class EventTracker
         });
     }
 
+    _handleMouseEvent(event)
+    {
+        this.events.push({
+            type: event.type,
+            x: event.clientX,
+            y: event.clientY,
+        });
+    }
+
     assertMatchesEvents(expectedEvents)
     {
         assert_true(!!this.events.length, "Event tracker saw some events.");
index c010fe9..bf2be86 100644 (file)
@@ -1,3 +1,56 @@
+2019-02-13  Antoine Quint  <graouts@apple.com>
+
+        Support simulated mouse events on iOS based on a PlatformTouchEvent
+        https://bugs.webkit.org/show_bug.cgi?id=194501
+        <rdar://problem/46910790>
+
+        Reviewed by Dean Jackson.
+
+        Add support for two new internal runtime flags to control whether simulated mouse events should be dipatched along with touch events and
+        whether simulated mousemove events dispatched should automatically trigger the behavior preventDefault() would also trigger. To facilitate
+        that, we allow for a MouseEvent to be created, much like a PointerEvent, based on a PlatformTouchEvent. Then, we set a flag on Event within
+        EventTarget::innerInvokeEventListeners() to see whether any page code has been evaluated as a result of a mousemove event being dispatched.
+        Finally, we also track mouse events when invalidating touch regions provided the required internal runtime flag is on.
+
+        Test: fast/events/touch/ios/mouse-events-dispatch-with-touch.html
+
+        * SourcesCocoa.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * dom/Event.cpp:
+        * dom/Event.h:
+        (WebCore::Event::hasEncounteredListener const):
+        (WebCore::Event::setHasEncounteredListener):
+        * dom/EventNames.h:
+        (WebCore::EventNames::isTouchRelatedEventType const):
+        (WebCore::EventNames::touchRelatedEventNames const):
+        (WebCore::EventNames::extendedTouchRelatedEventNames const):
+        (WebCore::EventNames::isTouchEventType const): Deleted.
+        (WebCore::EventNames::touchAndPointerEventNames const): Deleted.
+        * dom/EventTarget.cpp:
+        (WebCore::EventTarget::innerInvokeEventListeners):
+        * dom/MouseEvent.h:
+        * dom/Node.cpp:
+        (WebCore::Node::moveNodeToNewDocument):
+        (WebCore::tryAddEventListener):
+        (WebCore::tryRemoveEventListener):
+        (WebCore::Node::defaultEventHandler):
+        * dom/ios/MouseEventIOS.cpp: Added.
+        (WebCore::mouseEventType):
+        (WebCore::MouseEvent::create):
+        * dom/ios/PointerEventIOS.cpp:
+        (WebCore::pointerEventType):
+        (WebCore::PointerEvent::create):
+        (WebCore::eventType): Deleted.
+        * page/DOMWindow.cpp:
+        (WebCore::DOMWindow::addEventListener):
+        (WebCore::DOMWindow::removeEventListener):
+        * page/EventHandler.h:
+        * page/RuntimeEnabledFeatures.h:
+        (WebCore::RuntimeEnabledFeatures::mouseEventsSimulationEnabled const):
+        (WebCore::RuntimeEnabledFeatures::setMouseEventsSimulationEnabled):
+        (WebCore::RuntimeEnabledFeatures::mousemoveEventHandlingPreventsDefaultEnabled const):
+        (WebCore::RuntimeEnabledFeatures::setMousemoveEventHandlingPreventsDefaultEnabled):
+
 2019-02-13  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         [FreeType] Unable to render some Hebrew characters
index 4be0f2c..2aa2367 100644 (file)
@@ -71,6 +71,7 @@ crypto/mac/SerializedCryptoKeyWrapMac.mm
 
 dom/DataTransferMac.mm
 
+dom/ios/MouseEventIOS.cpp
 dom/ios/PointerEventIOS.cpp
 dom/ios/TouchEvents.cpp
 
index 13e6df6..22e0c2f 100644 (file)
                71C5BB1B1FB611EA0007A2AE /* Animatable.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Animatable.idl; sourceTree = "<group>"; };
                71C916071D1483A300ACA47D /* UserInterfaceLayoutDirection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserInterfaceLayoutDirection.h; sourceTree = "<group>"; };
                71CC7A1F152A0BFE009EEAF9 /* SVGAnimatedEnumeration.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SVGAnimatedEnumeration.cpp; sourceTree = "<group>"; };
+               71CE2C512209DC7F00C494BD /* MouseEventIOS.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MouseEventIOS.cpp; sourceTree = "<group>"; };
                71D02D901DB55C4E00DD5CF5 /* main.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = "<group>"; };
                71D02D921DB55C4E00DD5CF5 /* media-controller.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "media-controller.js"; sourceTree = "<group>"; };
                71D2554F1DB900020004D76B /* skip-back-support.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "skip-back-support.js"; sourceTree = "<group>"; };
                CE2616A4187E65C1007955F3 /* ios */ = {
                        isa = PBXGroup;
                        children = (
+                               71CE2C512209DC7F00C494BD /* MouseEventIOS.cpp */,
                                315574CC218F66D000D88F66 /* PointerEventIOS.cpp */,
                                A1DE712B18612AC100734192 /* TouchEvents.cpp */,
                        );
index 90baa97..c0db291 100644 (file)
@@ -46,6 +46,7 @@ ALWAYS_INLINE Event::Event(MonotonicTime createTime, const AtomicString& type, I
     , m_isDefaultEventHandlerIgnored { false }
     , m_isTrusted { isTrusted == IsTrusted::Yes }
     , m_isExecutingPassiveEventListener { false }
+    , m_hasEncounteredListener { false }
     , m_eventPhase { NONE }
     , m_type { type }
     , m_createTime { createTime }
index 1136085..a6c9382 100644 (file)
@@ -128,6 +128,9 @@ public:
 
     void setInPassiveListener(bool value) { m_isExecutingPassiveEventListener = value; }
 
+    bool hasEncounteredListener() const { return m_hasEncounteredListener; }
+    void setHasEncounteredListener() { m_hasEncounteredListener = true; }
+
     bool cancelBubble() const { return propagationStopped(); }
     void setCancelBubble(bool);
 
@@ -166,6 +169,7 @@ private:
     unsigned m_isDefaultEventHandlerIgnored : 1;
     unsigned m_isTrusted : 1;
     unsigned m_isExecutingPassiveEventListener : 1;
+    unsigned m_hasEncounteredListener : 1;
 
     unsigned m_eventPhase : 2;
 
index 463ed43..068014d 100644 (file)
 #include <functional>
 #include <wtf/text/AtomicString.h>
 
+#if ENABLE(TOUCH_EVENTS)
+#include "RuntimeEnabledFeatures.h"
+#endif
+
 namespace WebCore {
 
 #if !defined(ADDITIONAL_DOM_EVENT_NAMES_FOR_EACH)
@@ -355,13 +359,14 @@ public:
     // We should choose one term and stick to it.
     bool isWheelEventType(const AtomicString& eventType) const;
     bool isGestureEventType(const AtomicString& eventType) const;
-    bool isTouchEventType(const AtomicString& eventType) const;
+    bool isTouchRelatedEventType(const AtomicString& eventType) const;
     bool isTouchScrollBlockingEventType(const AtomicString& eventType) const;
 #if ENABLE(GAMEPAD)
     bool isGamepadEventType(const AtomicString& eventType) const;
 #endif
 
-    std::array<std::reference_wrapper<const AtomicString>, 9> touchAndPointerEventNames() const;
+    std::array<std::reference_wrapper<const AtomicString>, 9> touchRelatedEventNames() const;
+    std::array<std::reference_wrapper<const AtomicString>, 12> extendedTouchRelatedEventNames() const;
     std::array<std::reference_wrapper<const AtomicString>, 3> gestureEventNames() const;
 
 private:
@@ -389,8 +394,14 @@ inline bool EventNames::isTouchScrollBlockingEventType(const AtomicString& event
         || eventType == touchmoveEvent;
 }
 
-inline bool EventNames::isTouchEventType(const AtomicString& eventType) const
+inline bool EventNames::isTouchRelatedEventType(const AtomicString& eventType) const
 {
+#if ENABLE(TOUCH_EVENTS)
+    if (RuntimeEnabledFeatures::sharedFeatures().mouseEventsSimulationEnabled()) {
+        if (eventType == mousedownEvent || eventType == mousemoveEvent || eventType == mouseupEvent)
+            return true;
+    }
+#endif
     return eventType == touchstartEvent
         || eventType == touchmoveEvent
         || eventType == touchendEvent
@@ -408,11 +419,16 @@ inline bool EventNames::isWheelEventType(const AtomicString& eventType) const
         || eventType == mousewheelEvent;
 }
 
-inline std::array<std::reference_wrapper<const AtomicString>, 9> EventNames::touchAndPointerEventNames() const
+inline std::array<std::reference_wrapper<const AtomicString>, 9> EventNames::touchRelatedEventNames() const
 {
     return { { touchstartEvent, touchmoveEvent, touchendEvent, touchcancelEvent, touchforcechangeEvent, pointerdownEvent, pointermoveEvent, pointerupEvent, pointercancelEvent } };
 }
 
+inline std::array<std::reference_wrapper<const AtomicString>, 12> EventNames::extendedTouchRelatedEventNames() const
+{
+    return { { touchstartEvent, touchmoveEvent, touchendEvent, touchcancelEvent, touchforcechangeEvent, pointerdownEvent, pointermoveEvent, pointerupEvent, pointercancelEvent, mousedownEvent, mousemoveEvent, mouseupEvent } };
+}
+    
 inline std::array<std::reference_wrapper<const AtomicString>, 3> EventNames::gestureEventNames() const
 {
     return { { gesturestartEvent, gesturechangeEvent, gestureendEvent } };
index 7ae5344..2db2efd 100644 (file)
 #include "DOMWrapperWorld.h"
 #include "EventNames.h"
 #include "HTMLBodyElement.h"
+#include "HTMLHtmlElement.h"
 #include "InspectorInstrumentation.h"
 #include "JSEventListener.h"
+#include "RuntimeEnabledFeatures.h"
 #include "ScriptController.h"
 #include "ScriptDisallowedScope.h"
 #include "Settings.h"
@@ -299,6 +301,16 @@ void EventTarget::innerInvokeEventListeners(Event& event, EventListenerVector li
         registeredListener->callback().handleEvent(context, event);
         InspectorInstrumentation::didHandleEvent(context);
 
+#if ENABLE(TOUCH_EVENTS)
+        if (RuntimeEnabledFeatures::sharedFeatures().mousemoveEventHandlingPreventsDefaultEnabled() && event.type() == eventNames().mousemoveEvent) {
+            if (is<Element>(event.currentTarget())) {
+                auto* element = downcast<Element>(event.currentTarget());
+                if (!is<HTMLBodyElement>(element) && !is<HTMLHtmlElement>(element))
+                    event.setHasEncounteredListener();
+            }
+        }
+#endif
+
         if (registeredListener->isPassive())
             event.setInPassiveListener(false);
     }
index d7b3f12..09b7b74 100644 (file)
 #include "MouseEventInit.h"
 #include "MouseRelatedEvent.h"
 
+#if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS_FAMILY)
+#include "PlatformTouchEventIOS.h"
+#endif
+
 namespace WebCore {
 
 class DataTransfer;
@@ -48,6 +52,10 @@ public:
 
     static Ref<MouseEvent> create(const AtomicString& eventType, const MouseEventInit&);
 
+#if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS_FAMILY)
+    static Ref<MouseEvent> create(const PlatformTouchEvent&, unsigned touchIndex, Ref<WindowProxy>&&);
+#endif
+
     virtual ~MouseEvent();
 
     WEBCORE_EXPORT void initMouseEvent(const AtomicString& type, bool canBubble, bool cancelable, RefPtr<WindowProxy>&&,
index 019caff..078bce0 100644 (file)
@@ -2079,8 +2079,17 @@ void Node::moveNodeToNewDocument(Document& oldDocument, Document& newDocument)
         }
 
         unsigned numTouchEventListeners = 0;
-        for (auto& name : eventNames().touchAndPointerEventNames())
-            numTouchEventListeners += eventListeners(name).size();
+#if ENABLE(TOUCH_EVENTS)
+        if (RuntimeEnabledFeatures::sharedFeatures().mouseEventsSimulationEnabled()) {
+            for (auto& name : eventNames().extendedTouchRelatedEventNames())
+                numTouchEventListeners += eventListeners(name).size();
+        } else {
+#endif
+            for (auto& name : eventNames().touchRelatedEventNames())
+                numTouchEventListeners += eventListeners(name).size();
+#if ENABLE(TOUCH_EVENTS)
+        }
+#endif
 
         for (unsigned i = 0; i < numTouchEventListeners; ++i) {
             oldDocument.didRemoveTouchEventHandler(*this);
@@ -2125,7 +2134,7 @@ static inline bool tryAddEventListener(Node* targetNode, const AtomicString& eve
     targetNode->document().addListenerTypeIfNeeded(eventType);
     if (eventNames().isWheelEventType(eventType))
         targetNode->document().didAddWheelEventHandler(*targetNode);
-    else if (eventNames().isTouchEventType(eventType))
+    else if (eventNames().isTouchRelatedEventType(eventType))
         targetNode->document().didAddTouchEventHandler(*targetNode);
 
 #if PLATFORM(IOS_FAMILY)
@@ -2140,7 +2149,7 @@ static inline bool tryAddEventListener(Node* targetNode, const AtomicString& eve
         targetNode->document().domWindow()->addEventListener(eventType, WTFMove(listener), options);
 
 #if ENABLE(TOUCH_EVENTS)
-    if (eventNames().isTouchEventType(eventType))
+    if (eventNames().isTouchRelatedEventType(eventType))
         targetNode->document().addTouchEventListener(*targetNode);
 #endif
 #endif // PLATFORM(IOS_FAMILY)
@@ -2167,7 +2176,7 @@ static inline bool tryRemoveEventListener(Node* targetNode, const AtomicString&
     // listeners for each type, not just a bool - see https://bugs.webkit.org/show_bug.cgi?id=33861
     if (eventNames().isWheelEventType(eventType))
         targetNode->document().didRemoveWheelEventHandler(*targetNode);
-    else if (eventNames().isTouchEventType(eventType))
+    else if (eventNames().isTouchRelatedEventType(eventType))
         targetNode->document().didRemoveTouchEventHandler(*targetNode);
 
 #if PLATFORM(IOS_FAMILY)
@@ -2181,7 +2190,7 @@ static inline bool tryRemoveEventListener(Node* targetNode, const AtomicString&
         targetNode->document().domWindow()->removeEventListener(eventType, listener, options);
 
 #if ENABLE(TOUCH_EVENTS)
-    if (eventNames().isTouchEventType(eventType))
+    if (eventNames().isTouchRelatedEventType(eventType))
         targetNode->document().removeTouchEventListener(*targetNode);
 #endif
 #endif // PLATFORM(IOS_FAMILY)
@@ -2477,7 +2486,7 @@ void Node::defaultEventHandler(Event& event)
             if (Frame* frame = document().frame())
                 frame->eventHandler().defaultWheelEventHandler(startNode, downcast<WheelEvent>(event));
 #if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS_FAMILY)
-    } else if (is<TouchEvent>(event) && eventNames().isTouchEventType(eventType)) {
+    } else if (is<TouchEvent>(event) && eventNames().isTouchRelatedEventType(eventType)) {
         RenderObject* renderer = this->renderer();
         while (renderer && (!is<RenderBox>(*renderer) || !downcast<RenderBox>(*renderer).canBeScrolledAndHasScrollableArea()))
             renderer = renderer->parent();
diff --git a/Source/WebCore/dom/ios/MouseEventIOS.cpp b/Source/WebCore/dom/ios/MouseEventIOS.cpp
new file mode 100644 (file)
index 0000000..302e1fe
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+#import "MouseEvent.h"
+
+#if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS_FAMILY)
+
+#import "EventNames.h"
+
+namespace WebCore {
+
+static AtomicString mouseEventType(PlatformTouchPoint::TouchPhaseType phase)
+{
+    switch (phase) {
+    case PlatformTouchPoint::TouchPhaseBegan:
+        return eventNames().mousedownEvent;
+    case PlatformTouchPoint::TouchPhaseMoved:
+    case PlatformTouchPoint::TouchPhaseStationary:
+        return eventNames().mousemoveEvent;
+    case PlatformTouchPoint::TouchPhaseEnded:
+    case PlatformTouchPoint::TouchPhaseCancelled:
+        return eventNames().mouseupEvent;
+    }
+    ASSERT_NOT_REACHED();
+    return nullAtom();
+}
+
+Ref<MouseEvent> MouseEvent::create(const PlatformTouchEvent& event, unsigned index, Ref<WindowProxy>&& view)
+{
+    return adoptRef(*new MouseEvent(mouseEventType(event.touchPhaseAtIndex(index)), CanBubble::Yes, IsCancelable::Yes, IsComposed::Yes, event.timestamp().approximateMonotonicTime(), WTFMove(view), 0, event.touchLocationAtIndex(index), event.touchLocationAtIndex(index), { }, event.modifiers(), 0, 0, nullptr, 0, 0, nullptr, IsSimulated::No, IsTrusted::Yes));
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(TOUCH_EVENTS) && PLATFORM(IOS_FAMILY)
index 4502400..b191d19 100644 (file)
@@ -32,7 +32,7 @@
 
 namespace WebCore {
 
-static AtomicString eventType(PlatformTouchPoint::TouchPhaseType phase)
+static AtomicString pointerEventType(PlatformTouchPoint::TouchPhaseType phase)
 {
     switch (phase) {
     case PlatformTouchPoint::TouchPhaseBegan:
@@ -60,7 +60,7 @@ static PointerEvent::IsCancelable phaseIsCancelable(PlatformTouchPoint::TouchPha
 Ref<PointerEvent> PointerEvent::create(const PlatformTouchEvent& event, unsigned index, bool isPrimary, Ref<WindowProxy>&& view)
 {
     auto phase = event.touchPhaseAtIndex(index);
-    return adoptRef(*new PointerEvent(eventType(phase), event, phaseIsCancelable(phase), index, isPrimary, WTFMove(view)));
+    return adoptRef(*new PointerEvent(pointerEventType(phase), event, phaseIsCancelable(phase), index, isPrimary, WTFMove(view)));
 }
 
 PointerEvent::PointerEvent(const AtomicString& type, const PlatformTouchEvent& event, IsCancelable isCancelable, unsigned index, bool isPrimary, Ref<WindowProxy>&& view)
index 0979336..a7db07b 100644 (file)
@@ -1811,7 +1811,7 @@ bool DOMWindow::addEventListener(const AtomicString& eventType, Ref<EventListene
         document->addListenerTypeIfNeeded(eventType);
         if (eventNames().isWheelEventType(eventType))
             document->didAddWheelEventHandler(*document);
-        else if (eventNames().isTouchEventType(eventType))
+        else if (eventNames().isTouchRelatedEventType(eventType))
             document->didAddTouchEventHandler(*document);
         else if (eventType == eventNames().storageEvent)
             didAddStorageEventListener(*this);
@@ -1826,7 +1826,7 @@ bool DOMWindow::addEventListener(const AtomicString& eventType, Ref<EventListene
         incrementScrollEventListenersCount();
 #endif
 #if ENABLE(IOS_TOUCH_EVENTS)
-    else if (eventNames().isTouchEventType(eventType))
+    else if (eventNames().isTouchRelatedEventType(eventType))
         ++m_touchAndGestureEventListenerCount;
 #endif
 #if ENABLE(IOS_GESTURE_EVENTS)
@@ -1920,7 +1920,7 @@ bool DOMWindow::removeEventListener(const AtomicString& eventType, EventListener
     if (Document* document = this->document()) {
         if (eventNames().isWheelEventType(eventType))
             document->didRemoveWheelEventHandler(*document);
-        else if (eventNames().isTouchEventType(eventType))
+        else if (eventNames().isTouchRelatedEventType(eventType))
             document->didRemoveTouchEventHandler(*document);
     }
 
@@ -1949,7 +1949,7 @@ bool DOMWindow::removeEventListener(const AtomicString& eventType, EventListener
         decrementScrollEventListenersCount();
 #endif
 #if ENABLE(IOS_TOUCH_EVENTS)
-    else if (eventNames().isTouchEventType(eventType)) {
+    else if (eventNames().isTouchRelatedEventType(eventType)) {
         ASSERT(m_touchAndGestureEventListenerCount > 0);
         --m_touchAndGestureEventListenerCount;
     }
index ce6ea32..9ea9e0c 100644 (file)
@@ -606,6 +606,10 @@ private:
     bool m_touchPressed { false };
 #endif
 
+#if ENABLE(IOS_TOUCH_EVENTS)
+    unsigned touchIdentifierForMouseEvents { 0 };
+#endif
+
     double m_maxMouseMovedDuration { 0 };
     bool m_didStartDrag { false };
     bool m_isHandlingWheelEvent { false };
index 0f7e7a5..7176d2f 100644 (file)
@@ -344,6 +344,14 @@ public:
     bool adClickAttributionEnabled() const { return m_adClickAttributionEnabled; }
     void setAdClickAttributionEnabled(bool isEnabled) { m_adClickAttributionEnabled = isEnabled; }
 
+#if ENABLE(TOUCH_EVENTS)
+    bool mouseEventsSimulationEnabled() const { return m_mouseEventsSimulationEnabled; }
+    void setMouseEventsSimulationEnabled(bool isEnabled) { m_mouseEventsSimulationEnabled = isEnabled; }
+
+    bool mousemoveEventHandlingPreventsDefaultEnabled() const { return m_mousemoveEventHandlingPreventsDefaultEnabled; }
+    void setMousemoveEventHandlingPreventsDefaultEnabled(bool isEnabled) { m_mousemoveEventHandlingPreventsDefaultEnabled = isEnabled; }
+#endif
+    
     WEBCORE_EXPORT static RuntimeEnabledFeatures& sharedFeatures();
 
 private:
@@ -521,6 +529,11 @@ private:
 
     bool m_adClickAttributionEnabled { false };
 
+#if ENABLE(TOUCH_EVENTS)
+    bool m_mouseEventsSimulationEnabled { false };
+    bool m_mousemoveEventHandlingPreventsDefaultEnabled { false };
+#endif
+
     friend class WTF::NeverDestroyed<RuntimeEnabledFeatures>;
 };
 
index c09c79e..2d48f8f 100644 (file)
@@ -1,3 +1,19 @@
+2019-02-13  Antoine Quint  <graouts@apple.com>
+
+        Support simulated mouse events on iOS based on a PlatformTouchEvent
+        https://bugs.webkit.org/show_bug.cgi?id=194501
+        <rdar://problem/46910790>
+
+        Reviewed by Dean Jackson.
+
+        Add two new internal runtime flags to control whether simulated mouse events should be dipatched along with touch events and whether
+        simulated mousemove events dispatched should automatically trigger the behavior preventDefault() would also trigger. We also ensure
+        that we correctly create touch tracking regions for mouse events.
+
+        * Shared/WebPreferences.yaml:
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::updateTouchEventTracking):
+
 2019-02-13  Ryosuke Niwa  <rniwa@webkit.org>
 
         Crash in Page::setActivityState because m_page is null
index 405b7a1..ee9dbaf 100644 (file)
@@ -1541,6 +1541,24 @@ DOMPasteAccessRequestsEnabled:
   humanReadableDescription: "Enable DOM Paste Access Requests"
   category: internal
 
+MouseEventsSimulationEnabled:
+  type: bool
+  defaultValue: false
+  humanReadableName: "Mouse events simulation"
+  humanReadableDescription: "Enable mouse events dispatch along with touch events on iOS"
+  webcoreBinding: RuntimeEnabledFeatures
+  category: internal
+  condition: ENABLE(TOUCH_EVENTS)
+
+MousemoveEventHandlingPreventsDefaultEnabled:
+  type: bool
+  defaultValue: false
+  humanReadableName: "Prevent default for mousemove events"
+  humanReadableDescription: "Allows handling of mousemove events to implicitly call preventDefault() on iOS"
+  webcoreBinding: RuntimeEnabledFeatures
+  category: internal
+  condition: ENABLE(TOUCH_EVENTS)
+
 # Deprecated
 
 ICECandidateFilteringEnabled:
index 17c3397..fd4cde0 100644 (file)
@@ -2509,6 +2509,9 @@ void WebPageProxy::updateTouchEventTracking(const WebTouchEvent& touchStartEvent
         updateTrackingType(m_touchAndPointerEventTracking.touchStartTracking, names.pointerdownEvent);
         updateTrackingType(m_touchAndPointerEventTracking.touchMoveTracking, names.pointermoveEvent);
         updateTrackingType(m_touchAndPointerEventTracking.touchEndTracking, names.pointerupEvent);
+        updateTrackingType(m_touchAndPointerEventTracking.touchStartTracking, names.mousedownEvent);
+        updateTrackingType(m_touchAndPointerEventTracking.touchMoveTracking, names.mousemoveEvent);
+        updateTrackingType(m_touchAndPointerEventTracking.touchEndTracking, names.mouseupEvent);
     }
 #else
     UNUSED_PARAM(touchStartEvent);