TouchList should be retargeted
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 30 Jan 2016 18:38:20 +0000 (18:38 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 30 Jan 2016 18:38:20 +0000 (18:38 +0000)
https://bugs.webkit.org/show_bug.cgi?id=149592

Reviewed by Antti Koivisto.

Source/WebCore:

Retarget touch target's using the same algorithm as the one used for related targets instead of
EventRelatedNodeResolver which is removed in this patch.

Also enable the retargeting on iOS.

Test: fast/shadow-dom/touch-event-ios.html

* dom/EventContext.cpp:
(WebCore::TouchEventContext::TouchEventContext):
(WebCore::TouchEventContext::handleLocalEvents):
(WebCore::TouchEventContext::checkReachability):
* dom/EventContext.h:
(WebCore::toTouchEventContext):
(WebCore::EventContext::isUnreachableNode):
* dom/EventDispatcher.cpp:
(WebCore::EventRelatedNodeResolver): Deleted.
(WebCore::EventPath::EventPath):
(WebCore::EventDispatcher::dispatchEvent):
(WebCore::addRelatedNodeResolversForTouchList): Deleted.
(WebCore::EventPath::updateTouchLists): Deleted.
(WebCore::EventPath::setRelatedTarget): Removed superfluous UNUSED_PARAM since the argument is always used.
(WebCore::EventPath::retargetTouch): Extracted from updateTouchLists/setRelatedTarget. Clones Touch object
with the new target for each event context just like related targets.
(WebCore::EventPath::retargetTouchLists): Renamed from updateTouchLists. Calls retargetTouch on each Touch
object in each TouchList.
* dom/TouchEvent.h:

Tools:

Added touchDownAtPoint and liftUpAtPoint to UIScriptController so that we can test touch events with
multiple touch targets on iOS. fast/shadow-dom/touch-event-ios.html uses this new testing feature.

* WebKitTestRunner/UIScriptContext/Bindings/UIScriptController.idl:
* WebKitTestRunner/UIScriptContext/UIScriptController.cpp:
(WTR::UIScriptController::touchDownAtPoint): Added.
(WTR::UIScriptController::liftUpAtPoint): Added.
* WebKitTestRunner/UIScriptContext/UIScriptController.h:
* WebKitTestRunner/ios/HIDEventGenerator.h:
* WebKitTestRunner/ios/HIDEventGenerator.mm:
(-[HIDEventGenerator touchDown:touchCount:completionBlock:]): Added. Sends touch down and waits.
(-[HIDEventGenerator liftUp:touchCount:completionBlock:]): Ditto for lift up.
* WebKitTestRunner/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::touchDownAtPoint): Added.
(WTR::UIScriptController::liftUpAtPoint): Added.

LayoutTests:

Added a regression test for retargeting touch targets on iOS.

* fast/shadow-dom/touch-event-ios-expected.txt: Added.
* fast/shadow-dom/touch-event-ios.html: Added.
* platform/mac/TestExpectations: Added the failing expectation on Mac since touch support is not enabled.

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

16 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/shadow-dom/touch-event-ios-expected.txt [new file with mode: 0644]
LayoutTests/fast/shadow-dom/touch-event-ios.html [new file with mode: 0644]
LayoutTests/platform/mac/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/dom/EventContext.cpp
Source/WebCore/dom/EventContext.h
Source/WebCore/dom/EventDispatcher.cpp
Source/WebCore/dom/TouchEvent.h
Tools/ChangeLog
Tools/WebKitTestRunner/UIScriptContext/Bindings/UIScriptController.idl
Tools/WebKitTestRunner/UIScriptContext/UIScriptController.cpp
Tools/WebKitTestRunner/UIScriptContext/UIScriptController.h
Tools/WebKitTestRunner/ios/HIDEventGenerator.h
Tools/WebKitTestRunner/ios/HIDEventGenerator.mm
Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm

index b928b1b..b2e88a6 100644 (file)
@@ -1,3 +1,16 @@
+2016-01-30  Ryosuke Niwa  <rniwa@webkit.org>
+
+        TouchList should be retargeted
+        https://bugs.webkit.org/show_bug.cgi?id=149592
+
+        Reviewed by Antti Koivisto.
+
+        Added a regression test for retargeting touch targets on iOS.
+
+        * fast/shadow-dom/touch-event-ios-expected.txt: Added.
+        * fast/shadow-dom/touch-event-ios.html: Added.
+        * platform/mac/TestExpectations: Added the failing expectation on Mac since touch support is not enabled.
+
 2016-01-30  Michael Catanzaro  <mcatanzaro@igalia.com>
 
         [GTK] Two disk cache tests are failing
diff --git a/LayoutTests/fast/shadow-dom/touch-event-ios-expected.txt b/LayoutTests/fast/shadow-dom/touch-event-ios-expected.txt
new file mode 100644 (file)
index 0000000..d1f5917
--- /dev/null
@@ -0,0 +1,117 @@
+Tests for retargeting touch targets. This test requires touch events support and runUIScript.
+
+touchstart at left-shadow with
+    target: left-shadow-target
+    touches: [0: left-shadow-target]
+    targetTouches: [0: left-shadow-target]
+    changedTouches: [0: left-shadow-target]
+
+touchstart at document with
+    target: left-host
+    touches: [0: left-host]
+    targetTouches: [0: left-host]
+    changedTouches: [0: left-host]
+
+touchstart at right-lower-shadow with
+    target: slot
+    touches: [0: left-host, 1: right-target]
+    targetTouches: [0: right-target]
+    changedTouches: [0: right-target]
+
+touchstart at right-upper-shadow with
+    target: slot
+    touches: [0: left-host, 1: right-target]
+    targetTouches: [0: right-target]
+    changedTouches: [0: right-target]
+
+touchstart at document with
+    target: right-target
+    touches: [0: left-host, 1: right-target]
+    targetTouches: [0: right-target]
+    changedTouches: [0: right-target]
+
+touchmove at left-shadow with
+    target: left-shadow-target
+    touches: [0: left-shadow-target, 1: right-target]
+    targetTouches: [0: left-shadow-target]
+    changedTouches: [0: left-shadow-target]
+
+touchmove at document with
+    target: left-host
+    touches: [0: left-host, 1: right-target]
+    targetTouches: [0: left-host]
+    changedTouches: [0: left-host]
+
+touchstart at document with
+    target: bottom-target
+    touches: [0: left-host, 1: right-target, 2: bottom-target]
+    targetTouches: [0: bottom-target]
+    changedTouches: [0: bottom-target]
+
+touchmove at left-shadow with
+    target: left-shadow-target
+    touches: [0: left-shadow-target, 1: right-target, 2: bottom-target]
+    targetTouches: [0: left-shadow-target]
+    changedTouches: [0: left-shadow-target]
+
+touchmove at document with
+    target: left-host
+    touches: [0: left-host, 1: right-target, 2: bottom-target]
+    targetTouches: [0: left-host]
+    changedTouches: [0: left-host]
+
+touchmove at right-lower-shadow with
+    target: slot
+    touches: [0: left-host, 1: right-target, 2: bottom-target]
+    targetTouches: [0: right-target]
+    changedTouches: [0: right-target]
+
+touchmove at right-upper-shadow with
+    target: slot
+    touches: [0: left-host, 1: right-target, 2: bottom-target]
+    targetTouches: [0: right-target]
+    changedTouches: [0: right-target]
+
+touchmove at document with
+    target: right-target
+    touches: [0: left-host, 1: right-target, 2: bottom-target]
+    targetTouches: [0: right-target]
+    changedTouches: [0: right-target]
+
+touchend at document with
+    target: bottom-target
+    touches: []
+    targetTouches: []
+    changedTouches: [0: bottom-target]
+
+touchend at left-shadow with
+    target: left-shadow-target
+    touches: []
+    targetTouches: []
+    changedTouches: [0: left-shadow-target]
+
+touchend at document with
+    target: left-host
+    touches: []
+    targetTouches: []
+    changedTouches: [0: left-host]
+
+touchend at right-lower-shadow with
+    target: slot
+    touches: []
+    targetTouches: []
+    changedTouches: [0: right-target]
+
+touchend at right-upper-shadow with
+    target: slot
+    touches: []
+    targetTouches: []
+    changedTouches: [0: right-target]
+
+touchend at document with
+    target: right-target
+    touches: []
+    targetTouches: []
+    changedTouches: [0: right-target]
+
+
diff --git a/LayoutTests/fast/shadow-dom/touch-event-ios.html b/LayoutTests/fast/shadow-dom/touch-event-ios.html
new file mode 100644 (file)
index 0000000..7aedb35
--- /dev/null
@@ -0,0 +1,154 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>Tests for retargeting touch targets. This test requires touch events support and runUIScript.</p>
+<script>
+
+function block(width, height, color) {
+    return {display: 'block', width: width + 'px', height: height + 'px', backgroundColor: color};
+}
+
+function inlineBlock(width, height, color) {
+    return {display: 'inline-block', width: width + 'px', height: height + 'px', backgroundColor: color};
+}
+
+function addChildren(parent, children) {
+    for (var child of children) {
+        if (typeof(child) == 'string')
+            child = document.createTextNode(child);
+        parent.appendChild(child);
+    }
+}
+
+function element(name, children) {
+    var element = document.createElement(name);
+    element.treeName = name;
+    if (children)
+        addChildren(element, children);
+    return element;
+}
+
+function elementWithStyle(name, style, children) {
+    var newElement = element(name, children);
+    for (var name in style)
+        newElement.style[name] = style[name];
+    return newElement;
+}
+
+function attachShadow(name, element, shadowChildren) {
+    var shadowRoot = element.attachShadow({mode: 'closed'});
+    shadowRoot.treeName = name;
+    element.shadowTree = shadowRoot;
+    addChildren(shadowRoot, shadowChildren);
+    return shadowRoot;
+}
+
+/*
+parent
+ + left-host -- (left-shadow)
+ |               + left-shadow-target
+ + right-upper-host --------------------------------- (right-upper-shadow)
+ | + right-lower-host -- (right-lower-shadow)          + right-shadow-child
+ |   + right-target       + right-lower-shadow-child     + slot
+ |                          + slot
+ + bottomTarget
+*/
+
+var leftHost = elementWithStyle('left-host', block(200, 150, 'yellow'));
+var leftTarget = elementWithStyle('left-shadow-target', block(100, 100, 'red'));
+var leftShadowRoot = attachShadow('left-shadow', leftHost, [leftTarget]);
+
+var rightTarget = elementWithStyle('right-target', block(50, 50, 'blue'));
+var rightLowerHost = elementWithStyle('right-lower-host', block(100, 100, 'purple'), [rightTarget]);
+var rightLowerShadowRoot = attachShadow('right-lower-shadow', rightLowerHost, [
+    elementWithStyle('right-lower-shadow-child', block(100, 100, 'pink'), [
+        element('slot')
+    ])]);
+
+var rightUpperHost = elementWithStyle('right-upper-host', block(250, 250, 'green'), [rightLowerHost]);
+var rightUpperShadowRoot = attachShadow('right-upper-shadow', rightUpperHost, [
+    elementWithStyle('right-shadow-child', block(200, 200, 'purple'), [
+        element('slot')
+    ])]);
+
+var bottomTarget = elementWithStyle('bottom-target', block(100, 100, '#666'));
+
+document.body.appendChild(elementWithStyle('host-parent', block(500, 500, '#eee'), [leftHost, rightUpperHost, bottomTarget]));
+
+function getUIScript()
+{
+    function xCenter(element) { return element.offsetLeft + element.offsetWidth / 2; }
+    function yCenter(element) { return element.offsetTop + element.offsetHeight / 2; }
+
+    return `
+    (function() {
+        uiController.touchDownAtPoint(${xCenter(leftTarget)}, ${yCenter(leftTarget)}, 1, function() {
+            uiController.touchDownAtPoint(${xCenter(rightTarget)}, ${yCenter(rightTarget)}, 2, function() {
+                uiController.touchDownAtPoint(${xCenter(bottomTarget)}, ${yCenter(bottomTarget)}, 3, function() {
+                    uiController.liftUpAtPoint(${xCenter(leftTarget)}, ${yCenter(leftTarget)}, 2, function () {
+                        uiController.liftUpAtPoint(${xCenter(rightTarget)}, ${yCenter(rightTarget)}, 1, function () {
+                            uiController.liftUpAtPoint(${xCenter(bottomTarget)}, ${yCenter(bottomTarget)}, 0, function () {
+                                uiController.uiScriptComplete("Done");
+                            });
+                        });
+                    });
+                });
+            });
+        });
+    })();`
+}
+
+window.onload = function runTest()
+{
+    if (window.testRunner)
+        testRunner.dumpAsText();
+
+    function formatTouchList(list) {
+        var text = '';
+        for (var i = 0; i < list.length; i++) {
+            if (text)
+                text += ', ';
+            text += `${i}: ${list[i].target.treeName}`;
+        }
+        return `[${text}]`;
+    }
+
+    var output = '';
+
+    function logger(event) {
+        output += `${event.type} at ${this.treeName} with
+    target: ${event.target.treeName}
+    touches: ${formatTouchList(event.touches)}
+    targetTouches: ${formatTouchList(event.targetTouches)}
+    changedTouches: ${formatTouchList(event.changedTouches)}
+
+`;
+    }
+
+    function attachLoggers(node) {
+        node.addEventListener('touchstart', logger);
+        node.addEventListener('touchmove', logger);
+        node.addEventListener('touchend', logger);
+    }
+
+    document.body.treeName = 'body';
+    document.treeName = 'document';
+    attachLoggers(document);
+    attachLoggers(leftShadowRoot);
+    attachLoggers(rightUpperShadowRoot);
+    attachLoggers(rightLowerShadowRoot);
+
+    if (window.testRunner && testRunner.runUIScript) {
+        testRunner.waitUntilDone();
+        testRunner.runUIScript(getUIScript(), function(result) {
+            var pre = document.createElement('pre');
+            pre.textContent = output;
+            document.body.appendChild(pre);
+            testRunner.notifyDone();
+        });
+    }
+}
+
+</script>
+</body>
+</html>
index 3c55715..1bef924 100644 (file)
@@ -1257,6 +1257,9 @@ webkit.org/b/149440 fast/shadow-dom/css-scoping-shadow-host-functional-rule.html
 webkit.org/b/149441 fast/shadow-dom/css-scoping-shadow-slotted-rule.html [ ImageOnlyFailure ]
 webkit.org/b/149441 fast/shadow-dom/css-scoping-shadow-slot-display-override.html [ ImageOnlyFailure ]
 
+# Touch events is not enabled on Mac
+webkit.org/b/149592 fast/shadow-dom/touch-event-ios.html [ Failure ]
+
 webkit.org/b/150225 fast/custom-elements [ Pass ]
 
 # Times out in debug.
index b910cbc..4d92e97 100644 (file)
@@ -1,3 +1,37 @@
+2016-01-30  Ryosuke Niwa  <rniwa@webkit.org>
+
+        TouchList should be retargeted
+        https://bugs.webkit.org/show_bug.cgi?id=149592
+
+        Reviewed by Antti Koivisto.
+
+        Retarget touch target's using the same algorithm as the one used for related targets instead of
+        EventRelatedNodeResolver which is removed in this patch.
+
+        Also enable the retargeting on iOS.
+
+        Test: fast/shadow-dom/touch-event-ios.html
+
+        * dom/EventContext.cpp:
+        (WebCore::TouchEventContext::TouchEventContext):
+        (WebCore::TouchEventContext::handleLocalEvents):
+        (WebCore::TouchEventContext::checkReachability):
+        * dom/EventContext.h:
+        (WebCore::toTouchEventContext):
+        (WebCore::EventContext::isUnreachableNode):
+        * dom/EventDispatcher.cpp:
+        (WebCore::EventRelatedNodeResolver): Deleted.
+        (WebCore::EventPath::EventPath):
+        (WebCore::EventDispatcher::dispatchEvent): 
+        (WebCore::addRelatedNodeResolversForTouchList): Deleted.
+        (WebCore::EventPath::updateTouchLists): Deleted.
+        (WebCore::EventPath::setRelatedTarget): Removed superfluous UNUSED_PARAM since the argument is always used.
+        (WebCore::EventPath::retargetTouch): Extracted from updateTouchLists/setRelatedTarget. Clones Touch object
+        with the new target for each event context just like related targets.
+        (WebCore::EventPath::retargetTouchLists): Renamed from updateTouchLists. Calls retargetTouch on each Touch
+        object in each TouchList.
+        * dom/TouchEvent.h:
+
 2016-01-30 Dave Hyatt  <hyatt@apple.com>
 
         Support break-after, break-before and break-inside.
index b72eaff..d33634d 100644 (file)
@@ -90,7 +90,7 @@ bool MouseOrFocusEventContext::isMouseOrFocusEventContext() const
     return true;
 }
 
-#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
+#if ENABLE(TOUCH_EVENTS)
 TouchEventContext::TouchEventContext(PassRefPtr<Node> node, PassRefPtr<EventTarget> currentTarget, PassRefPtr<EventTarget> target)
     : EventContext(node, currentTarget, target)
     , m_touches(TouchList::create())
@@ -105,16 +105,16 @@ TouchEventContext::~TouchEventContext()
 
 void TouchEventContext::handleLocalEvents(Event& event) const
 {
-#ifndef NDEBUG
+#if !ASSERT_DISABLED
     checkReachability(m_touches.get());
     checkReachability(m_targetTouches.get());
     checkReachability(m_changedTouches.get());
 #endif
     ASSERT(is<TouchEvent>(event));
     TouchEvent& touchEvent = downcast<TouchEvent>(event);
-    touchEvent.setTouches(m_touches);
-    touchEvent.setTargetTouches(m_targetTouches);
-    touchEvent.setChangedTouches(m_changedTouches);
+    touchEvent.setTouches(m_touches.get());
+    touchEvent.setTargetTouches(m_targetTouches.get());
+    touchEvent.setChangedTouches(m_changedTouches.get());
     EventContext::handleLocalEvents(event);
 }
 
@@ -123,7 +123,7 @@ bool TouchEventContext::isTouchEventContext() const
     return true;
 }
 
-#ifndef NDEBUG
+#if !ASSERT_DISABLED
 void TouchEventContext::checkReachability(TouchList* touchList) const
 {
     size_t length = touchList->length();
index 3f62460..7d52daa 100644 (file)
@@ -35,7 +35,7 @@
 namespace WebCore {
 
 class Event;
-#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
+#if ENABLE(TOUCH_EVENTS)
 class TouchList;
 #endif
 
@@ -54,7 +54,7 @@ public:
     virtual bool isTouchEventContext() const;
 
 protected:
-#ifndef NDEBUG
+#if !ASSERT_DISABLED
     bool isUnreachableNode(EventTarget*);
     bool isReachable(Node*) const;
 #endif
@@ -77,7 +77,7 @@ private:
 };
 
 
-#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
+#if ENABLE(TOUCH_EVENTS)
 class TouchEventContext final : public EventContext {
 public:
     TouchEventContext(PassRefPtr<Node>, PassRefPtr<EventTarget> currentTarget, PassRefPtr<EventTarget> target);
@@ -111,7 +111,7 @@ private:
     RefPtr<TouchList> m_touches;
     RefPtr<TouchList> m_targetTouches;
     RefPtr<TouchList> m_changedTouches;
-#ifndef NDEBUG
+#if !ASSERT_DISABLED
     void checkReachability(TouchList*) const;
 #endif
 };
@@ -129,7 +129,7 @@ inline TouchEventContext* toTouchEventContext(EventContext* eventContext)
 }
 #endif // ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
 
-#ifndef NDEBUG
+#if !ASSERT_DISABLED
 inline bool EventContext::isUnreachableNode(EventTarget* target)
 {
     // FIXME: Checks also for SVG elements.
index d54c67f..3d78e6e 100644 (file)
@@ -87,7 +87,7 @@ public:
     EventContext& contextAt(size_t i) { return *m_path[i]; }
 
 #if ENABLE(TOUCH_EVENTS)
-    bool updateTouchLists(const TouchEvent&);
+    void retargetTouchLists(const TouchEvent&);
 #endif
     void setRelatedTarget(Node& origin, EventTarget&);
 
@@ -96,98 +96,14 @@ public:
     EventContext* lastContextIfExists() { return m_path.isEmpty() ? nullptr : m_path.last().get(); }
 
 private:
-#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
-    void updateTouchListsInEventPath(const TouchList*, TouchEventContext::TouchListType);
+#if ENABLE(TOUCH_EVENTS)
+    void retargetTouch(TouchEventContext::TouchListType, const Touch&);
 #endif
 
     Event& m_event;
     Vector<std::unique_ptr<EventContext>, 32> m_path;
 };
 
-#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
-// FIXME: Use RelatedNodeRetargeter instead.
-class EventRelatedNodeResolver {
-public:
-    EventRelatedNodeResolver(Touch& touch, TouchEventContext::TouchListType touchListType)
-        : m_relatedNode(*touch.target()->toNode())
-        , m_relatedNodeTreeScope(m_relatedNode.treeScope())
-        , m_relatedNodeInCurrentTreeScope(nullptr)
-        , m_currentTreeScope(nullptr)
-        , m_touch(&touch)
-        , m_touchListType(touchListType)
-    {
-        ASSERT(touch.target()->toNode());
-    }
-
-    Touch* touch() const { return m_touch; }
-    TouchEventContext::TouchListType touchListType() const { return m_touchListType; }
-
-    Node* moveToParentOrShadowHost(Node& newTarget)
-    {
-        TreeScope& newTreeScope = newTarget.treeScope();
-        if (&newTreeScope == m_currentTreeScope)
-            return m_relatedNodeInCurrentTreeScope;
-
-        if (m_currentTreeScope) {
-            ASSERT(is<ShadowRoot>(m_currentTreeScope->rootNode()));
-            ASSERT(&newTarget == downcast<ShadowRoot>(m_currentTreeScope->rootNode()).host());
-            ASSERT(m_currentTreeScope->parentTreeScope() == &newTreeScope);
-        }
-
-        if (&newTreeScope == &m_relatedNodeTreeScope)
-            m_relatedNodeInCurrentTreeScope = &m_relatedNode;
-        else if (m_relatedNodeInCurrentTreeScope) {
-            ASSERT(m_currentTreeScope);
-            m_relatedNodeInCurrentTreeScope = &newTarget;
-        } else {
-            if (!m_currentTreeScope) {
-                TreeScope* newTreeScopeAncestor = &newTreeScope;
-                do {
-                    m_relatedNodeInCurrentTreeScope = findHostOfTreeScopeInTargetTreeScope(m_relatedNodeTreeScope, *newTreeScopeAncestor);
-                    newTreeScopeAncestor = newTreeScopeAncestor->parentTreeScope();
-                    if (newTreeScopeAncestor == &m_relatedNodeTreeScope) {
-                        m_relatedNodeInCurrentTreeScope = &m_relatedNode;
-                        break;
-                    }
-                } while (newTreeScopeAncestor && !m_relatedNodeInCurrentTreeScope);
-            }
-            ASSERT(m_relatedNodeInCurrentTreeScope || findHostOfTreeScopeInTargetTreeScope(newTreeScope, m_relatedNodeTreeScope)
-                || &newTreeScope.documentScope() != &m_relatedNodeTreeScope.documentScope());
-        }
-
-        m_currentTreeScope = &newTreeScope;
-
-        return m_relatedNodeInCurrentTreeScope;
-    }
-
-    static Node* findHostOfTreeScopeInTargetTreeScope(const TreeScope& startingTreeScope, const TreeScope& targetScope)
-    {
-        ASSERT(&targetScope != &startingTreeScope);
-        Node* previousHost = nullptr;
-        for (const TreeScope* scope = &startingTreeScope; scope; scope = scope->parentTreeScope()) {
-            if (scope == &targetScope) {
-                ASSERT(previousHost);
-                ASSERT_WITH_SECURITY_IMPLICATION(&previousHost->treeScope() == &targetScope);
-                return previousHost;
-            }
-            if (is<ShadowRoot>(scope->rootNode()))
-                previousHost = downcast<ShadowRoot>(scope->rootNode()).host();
-            else
-                ASSERT_WITH_SECURITY_IMPLICATION(!scope->parentTreeScope());
-        }
-        return nullptr;
-    }
-
-private:
-    Node& m_relatedNode;
-    const TreeScope& m_relatedNodeTreeScope;
-    Node* m_relatedNodeInCurrentTreeScope;
-    TreeScope* m_currentTreeScope;
-    Touch* m_touch;
-    TouchEventContext::TouchListType m_touchListType;
-};
-#endif
-
 inline EventTarget* eventTargetRespectingTargetRules(Node& referenceNode)
 {
     if (is<PseudoElement>(referenceNode))
@@ -311,11 +227,9 @@ bool EventDispatcher::dispatchEvent(Node* origin, Event& event)
 
     if (EventTarget* relatedTarget = event.relatedTarget())
         eventPath.setRelatedTarget(*node, *relatedTarget);
-#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
-    if (is<TouchEvent>(event)) {
-        if (!eventPath.updateTouchLists(downcast<TouchEvent>(event)))
-            return true;
-    }
+#if ENABLE(TOUCH_EVENTS)
+    if (is<TouchEvent>(event))
+        eventPath.retargetTouchLists(downcast<TouchEvent>(event));
 #endif
 
     ChildNodesLazySnapshot::takeChildNodesLazySnapshot();
@@ -403,7 +317,7 @@ EventPath::EventPath(Node& originalTarget, Event& event)
 #endif
 
     bool isMouseOrFocusEvent = event.isMouseEvent() || event.isFocusEvent();
-#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
+#if ENABLE(TOUCH_EVENTS)
     bool isTouchEvent = event.isTouchEvent();
 #endif
     EventTarget* target = nullptr;
@@ -417,7 +331,7 @@ EventPath::EventPath(Node& originalTarget, Event& event)
             EventTarget* currentTarget = eventTargetRespectingTargetRules(*node);
             if (isMouseOrFocusEvent)
                 m_path.append(std::make_unique<MouseOrFocusEventContext>(node, currentTarget, target));
-#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
+#if ENABLE(TOUCH_EVENTS)
             else if (isTouchEvent)
                 m_path.append(std::make_unique<TouchEventContext>(node, currentTarget, target));
 #endif
@@ -459,42 +373,6 @@ EventPath::EventPath(Node& originalTarget, Event& event)
     }
 }
 
-#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
-static void addRelatedNodeResolversForTouchList(Vector<EventRelatedNodeResolver, 16>& touchTargetResolvers, TouchList* touchList, TouchEventContext::TouchListType type)
-{
-    const size_t touchListSize = touchList->length();
-    for (size_t i = 0; i < touchListSize; ++i)
-        touchTargetResolvers.append(EventRelatedNodeResolver(*touchList->item(i), type));
-}
-
-bool EventPath::updateTouchLists(const TouchEvent& touchEvent)
-{
-    if (!touchEvent.touches() || !touchEvent.targetTouches() || !touchEvent.changedTouches())
-        return false;
-    
-    Vector<EventRelatedNodeResolver, 16> touchTargetResolvers;
-    const size_t touchNodeCount = touchEvent.touches()->length() + touchEvent.targetTouches()->length() + touchEvent.changedTouches()->length();
-    touchTargetResolvers.reserveInitialCapacity(touchNodeCount);
-
-    addRelatedNodeResolversForTouchList(touchTargetResolvers, touchEvent.touches(), TouchEventContext::Touches);
-    addRelatedNodeResolversForTouchList(touchTargetResolvers, touchEvent.targetTouches(), TouchEventContext::TargetTouches);
-    addRelatedNodeResolversForTouchList(touchTargetResolvers, touchEvent.changedTouches(), TouchEventContext::ChangedTouches);
-
-    ASSERT(touchTargetResolvers.size() == touchNodeCount);
-    for (auto& eventPath : m_path) {
-        TouchEventContext& context = toTouchEventContext(*eventPath);
-        Node& nodeToMoveTo = *context.node();
-        for (size_t resolverIndex = 0; resolverIndex < touchNodeCount; ++resolverIndex) {
-            EventRelatedNodeResolver& currentResolver = touchTargetResolvers[resolverIndex];
-            Node* nodeInCurrentTreeScope = currentResolver.moveToParentOrShadowHost(nodeToMoveTo);
-            ASSERT(currentResolver.touch());
-            context.touchList(currentResolver.touchListType())->append(currentResolver.touch()->cloneWithNewTarget(nodeInCurrentTreeScope));
-        }
-    }
-    return true;
-}
-#endif
-
 class RelatedNodeRetargeter {
 public:
     RelatedNodeRetargeter(Node& relatedNode, TreeScope& targetTreeScope)
@@ -622,7 +500,6 @@ private:
 
 void EventPath::setRelatedTarget(Node& origin, EventTarget& relatedTarget)
 {
-    UNUSED_PARAM(origin);
     Node* relatedNode = relatedTarget.toNode();
     if (!relatedNode || m_path.isEmpty())
         return;
@@ -661,6 +538,50 @@ void EventPath::setRelatedTarget(Node& origin, EventTarget& relatedTarget)
     }
 }
 
+#if ENABLE(TOUCH_EVENTS)
+void EventPath::retargetTouch(TouchEventContext::TouchListType touchListType, const Touch& touch)
+{
+    EventTarget* eventTarget = touch.target();
+    if (!eventTarget)
+        return;
+
+    Node* targetNode = eventTarget->toNode();
+    if (!targetNode)
+        return;
+
+    RelatedNodeRetargeter retargeter(*targetNode, downcast<MouseOrFocusEventContext>(*m_path[0]).node()->treeScope());
+    TreeScope* previousTreeScope = nullptr;
+    for (auto& context : m_path) {
+        TreeScope& currentTreeScope = context->node()->treeScope();
+        if (UNLIKELY(previousTreeScope && &currentTreeScope != previousTreeScope))
+            retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope);
+
+        Node* currentRelatedNode = retargeter.currentNode(currentTreeScope);
+        downcast<TouchEventContext>(*context).touchList(touchListType)->append(touch.cloneWithNewTarget(currentRelatedNode));
+
+        previousTreeScope = &currentTreeScope;
+    }
+}
+
+void EventPath::retargetTouchLists(const TouchEvent& touchEvent)
+{
+    if (touchEvent.touches()) {
+        for (size_t i = 0; i < touchEvent.touches()->length(); ++i)
+            retargetTouch(TouchEventContext::Touches, *touchEvent.touches()->item(i));
+    }
+
+    if (touchEvent.targetTouches()) {
+        for (size_t i = 0; i < touchEvent.targetTouches()->length(); ++i)
+            retargetTouch(TouchEventContext::TargetTouches, *touchEvent.targetTouches()->item(i));
+    }
+
+    if (touchEvent.changedTouches()) {
+        for (size_t i = 0; i < touchEvent.changedTouches()->length(); ++i)
+            retargetTouch(TouchEventContext::ChangedTouches, *touchEvent.changedTouches()->item(i));
+    }
+}
+#endif
+
 bool EventPath::hasEventListeners(const AtomicString& eventType) const
 {
     for (auto& eventPath : m_path) {
index 0162d57..a0ce6c6 100644 (file)
@@ -65,9 +65,9 @@ public:
     TouchList* targetTouches() const { return m_targetTouches.get(); }
     TouchList* changedTouches() const { return m_changedTouches.get(); }
 
-    void setTouches(PassRefPtr<TouchList> touches) { m_touches = touches; }
-    void setTargetTouches(PassRefPtr<TouchList> targetTouches) { m_targetTouches = targetTouches; }
-    void setChangedTouches(PassRefPtr<TouchList> changedTouches) { m_changedTouches = changedTouches; }
+    void setTouches(RefPtr<TouchList>&& touches) { m_touches = touches; }
+    void setTargetTouches(RefPtr<TouchList>&& targetTouches) { m_targetTouches = targetTouches; }
+    void setChangedTouches(RefPtr<TouchList>&& changedTouches) { m_changedTouches = changedTouches; }
 
     virtual bool isTouchEvent() const override;
 
index 515f545..9653689 100644 (file)
@@ -1,3 +1,26 @@
+2016-01-30  Ryosuke Niwa  <rniwa@webkit.org>
+
+        TouchList should be retargeted
+        https://bugs.webkit.org/show_bug.cgi?id=149592
+
+        Reviewed by Antti Koivisto.
+
+        Added touchDownAtPoint and liftUpAtPoint to UIScriptController so that we can test touch events with
+        multiple touch targets on iOS. fast/shadow-dom/touch-event-ios.html uses this new testing feature.
+
+        * WebKitTestRunner/UIScriptContext/Bindings/UIScriptController.idl:
+        * WebKitTestRunner/UIScriptContext/UIScriptController.cpp:
+        (WTR::UIScriptController::touchDownAtPoint): Added.
+        (WTR::UIScriptController::liftUpAtPoint): Added.
+        * WebKitTestRunner/UIScriptContext/UIScriptController.h:
+        * WebKitTestRunner/ios/HIDEventGenerator.h:
+        * WebKitTestRunner/ios/HIDEventGenerator.mm:
+        (-[HIDEventGenerator touchDown:touchCount:completionBlock:]): Added. Sends touch down and waits.
+        (-[HIDEventGenerator liftUp:touchCount:completionBlock:]): Ditto for lift up.
+        * WebKitTestRunner/ios/UIScriptControllerIOS.mm:
+        (WTR::UIScriptController::touchDownAtPoint): Added.
+        (WTR::UIScriptController::liftUpAtPoint): Added.
+
 2016-01-30  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         Enable SamplingProfiler on POSIX environment
index 763fb4b..53e9c8a 100644 (file)
@@ -30,8 +30,10 @@ interface UIScriptController {
     void zoomToScale(double scale, object callback);
 
     // Interaction.
-    // These functions post events asynchronously. The callback is fired when the events have been disptached, but any
+    // These functions post events asynchronously. The callback is fired when the events have been dispatched, but any
     // resulting behavior may also be asynchronous.
+    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 doubleTapAtPoint(long x, long y, object callback);
 
index a564382..6582402 100644 (file)
@@ -118,6 +118,14 @@ void UIScriptController::zoomToScale(double, JSValueRef)
 {
 }
 
+void UIScriptController::touchDownAtPoint(long x, long y, long touchCount, JSValueRef)
+{
+}
+
+void UIScriptController::liftUpAtPoint(long x, long y, long touchCount, JSValueRef)
+{
+}
+
 void UIScriptController::singleTapAtPoint(long x, long y, JSValueRef)
 {
 }
index 858439c..8acef60 100644 (file)
@@ -47,6 +47,8 @@ public:
     void doAsyncTask(JSValueRef callback);
     void zoomToScale(double scale, JSValueRef callback);
 
+    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 doubleTapAtPoint(long x, long y, JSValueRef callback);
     
index 38ba029..1da00ae 100644 (file)
@@ -35,6 +35,8 @@
 - (void)touchDown:(CGPoint)location;
 - (void)liftUp:(CGPoint)location;
 - (void)moveToPoints:(CGPoint*)locations touchCount:(NSUInteger)count duration:(NSTimeInterval)seconds;
+- (void)touchDown:(CGPoint)location touchCount:(NSUInteger)count completionBlock:(void (^)(void))completionBlock;
+- (void)liftUp:(CGPoint)location touchCount:(NSUInteger)count completionBlock:(void (^)(void))completionBlock;
 
 // Taps
 - (void)tap:(CGPoint)location completionBlock:(void (^)(void))completionBlock;
index 71cdbd2..294f36b 100644 (file)
@@ -370,6 +370,18 @@ static void delayBetweenMove(int eventIndex, double elapsed)
     [self _updateTouchPoints:newLocations count:touchCount];
 }
 
+- (void)touchDown:(CGPoint)location touchCount:(NSUInteger)count completionBlock:(void (^)(void))completionBlock
+{
+    [self touchDown:location touchCount:count];
+    [self _sendMarkerHIDEventWithCompletionBlock:completionBlock];
+}
+
+- (void)liftUp:(CGPoint)location touchCount:(NSUInteger)count completionBlock:(void (^)(void))completionBlock
+{
+    [self liftUp:location touchCount:count];
+    [self _sendMarkerHIDEventWithCompletionBlock:completionBlock];
+}
+
 - (void)sendTaps:(int)tapCount location:(CGPoint)location withNumberOfTouches:(int)touchCount completionBlock:(void (^)(void))completionBlock
 {
     struct timespec doubleDelay = { 0, static_cast<long>(multiTapInterval) };
index 131ec35..a0a60ad 100644 (file)
@@ -79,6 +79,30 @@ static CGPoint globalToContentCoordinates(TestRunnerWKWebView *webView, long x,
     return point;
 }
 
+void UIScriptController::touchDownAtPoint(long x, long y, long touchCount, JSValueRef callback)
+{
+    unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
+
+    auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
+    [[HIDEventGenerator sharedHIDEventGenerator] touchDown:location touchCount:touchCount completionBlock:^{
+        if (!m_context)
+            return;
+        m_context->asyncTaskComplete(callbackID);
+    }];
+}
+
+void UIScriptController::liftUpAtPoint(long x, long y, long touchCount, JSValueRef callback)
+{
+    unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
+    
+    auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
+    [[HIDEventGenerator sharedHIDEventGenerator] liftUp:location touchCount:touchCount completionBlock:^{
+        if (!m_context)
+            return;
+        m_context->asyncTaskComplete(callbackID);
+    }];
+}
+
 void UIScriptController::singleTapAtPoint(long x, long y, JSValueRef callback)
 {
     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);