1/*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "EventRetargeter.h"
22
23#include "ContainerNode.h"
24#include "EventContext.h"
25#include "EventPathWalker.h"
26#include "FocusEvent.h"
27#include "MouseEvent.h"
28#include "ShadowRoot.h"
29#include "Touch.h"
30#include "TouchEvent.h"
31#include "TouchList.h"
32#include "TreeScope.h"
33#include <wtf/PassRefPtr.h>
34#include <wtf/RefPtr.h>
35#include <wtf/Vector.h>
36
37namespace WebCore {
38
39static inline bool inTheSameScope(ShadowRoot* shadowRoot, EventTarget* target)
40{
41    return target->toNode() && target->toNode()->treeScope()->rootNode() == shadowRoot;
42}
43
44static inline EventDispatchBehavior determineDispatchBehavior(Event* event, ShadowRoot* shadowRoot, EventTarget* target)
45{
46#if ENABLE(FULLSCREEN_API) && ENABLE(VIDEO)
47    // Video-only full screen is a mode where we use the shadow DOM as an implementation
48    // detail that should not be detectable by the web content.
49    if (Element* element = target->toNode()->document()->webkitCurrentFullScreenElement()) {
50        // FIXME: We assume that if the full screen element is a media element that it's
51        // the video-only full screen. Both here and elsewhere. But that is probably wrong.
52        if (element->isMediaElement() && shadowRoot && shadowRoot->host() == element)
53            return StayInsideShadowDOM;
54    }
55#else
56    UNUSED_PARAM(shadowRoot);
57#endif
58
59    // WebKit never allowed selectstart event to cross the the shadow DOM boundary.
60    // Changing this breaks existing sites.
61    // See https://bugs.webkit.org/show_bug.cgi?id=52195 for details.
62    const AtomicString eventType = event->type();
63    if (inTheSameScope(shadowRoot, target)
64        && (eventType == eventNames().abortEvent
65            || eventType == eventNames().changeEvent
66            || eventType == eventNames().errorEvent
67            || eventType == eventNames().loadEvent
68            || eventType == eventNames().resetEvent
69            || eventType == eventNames().resizeEvent
70            || eventType == eventNames().scrollEvent
71            || eventType == eventNames().selectEvent
72            || eventType == eventNames().selectstartEvent))
73        return StayInsideShadowDOM;
74
75    return RetargetEvent;
76}
77
78void EventRetargeter::calculateEventPath(Node* node, Event* event, EventPath& eventPath)
79{
80    bool inDocument = node->inDocument();
81    bool isSVGElement = node->isSVGElement();
82    bool isMouseOrFocusEvent = event->isMouseEvent() || event->isFocusEvent();
83#if ENABLE(TOUCH_EVENTS)
84    bool isTouchEvent = event->isTouchEvent();
85#endif
86    Vector<EventTarget*, 32> targetStack;
87    for (EventPathWalker walker(node); walker.node(); walker.moveToParent()) {
88        Node* node = walker.node();
89        if (targetStack.isEmpty())
90            targetStack.append(eventTargetRespectingTargetRules(node));
91        else if (walker.isVisitingInsertionPointInReprojection())
92            targetStack.append(targetStack.last());
93        if (isMouseOrFocusEvent)
94            eventPath.append(adoptPtr(new MouseOrFocusEventContext(node, eventTargetRespectingTargetRules(node), targetStack.last())));
95#if ENABLE(TOUCH_EVENTS)
96        else if (isTouchEvent)
97            eventPath.append(adoptPtr(new TouchEventContext(node, eventTargetRespectingTargetRules(node), targetStack.last())));
98#endif
99        else
100            eventPath.append(adoptPtr(new EventContext(node, eventTargetRespectingTargetRules(node), targetStack.last())));
101        if (!inDocument)
102            return;
103        if (!node->isShadowRoot())
104            continue;
105        if (determineDispatchBehavior(event, toShadowRoot(node), targetStack.last()) == StayInsideShadowDOM)
106            return;
107        if (!isSVGElement) {
108            ASSERT(!targetStack.isEmpty());
109            targetStack.removeLast();
110        }
111    }
112}
113
114void EventRetargeter::adjustForMouseEvent(Node* node, const MouseEvent& mouseEvent, EventPath& eventPath)
115{
116    adjustForRelatedTarget(node, mouseEvent.relatedTarget(), eventPath);
117}
118
119void EventRetargeter::adjustForFocusEvent(Node* node, const FocusEvent& focusEvent, EventPath& eventPath)
120{
121    adjustForRelatedTarget(node, focusEvent.relatedTarget(), eventPath);
122}
123
124#if ENABLE(TOUCH_EVENTS)
125void EventRetargeter::adjustForTouchEvent(Node* node, const TouchEvent& touchEvent, EventPath& eventPath)
126{
127    size_t eventPathSize = eventPath.size();
128
129    EventPathTouchLists eventPathTouches(eventPathSize);
130    EventPathTouchLists eventPathTargetTouches(eventPathSize);
131    EventPathTouchLists eventPathChangedTouches(eventPathSize);
132
133    for (size_t i = 0; i < eventPathSize; ++i) {
134        ASSERT(eventPath[i]->isTouchEventContext());
135        TouchEventContext* touchEventContext = toTouchEventContext(eventPath[i].get());
136        eventPathTouches[i] = touchEventContext->touches();
137        eventPathTargetTouches[i] = touchEventContext->targetTouches();
138        eventPathChangedTouches[i] = touchEventContext->changedTouches();
139    }
140
141    adjustTouchList(node, touchEvent.touches(), eventPath, eventPathTouches);
142    adjustTouchList(node, touchEvent.targetTouches(), eventPath, eventPathTargetTouches);
143    adjustTouchList(node, touchEvent.changedTouches(), eventPath, eventPathChangedTouches);
144}
145
146void EventRetargeter::adjustTouchList(const Node* node, const TouchList* touchList, const EventPath& eventPath, EventPathTouchLists& eventPathTouchLists)
147{
148    if (!touchList)
149        return;
150    size_t eventPathSize = eventPath.size();
151    ASSERT(eventPathTouchLists.size() == eventPathSize);
152    for (size_t i = 0; i < touchList->length(); ++i) {
153        const Touch& touch = *touchList->item(i);
154        AdjustedNodes adjustedNodes;
155        calculateAdjustedNodes(node, touch.target()->toNode(), DoesNotStopAtBoundary, const_cast<EventPath&>(eventPath), adjustedNodes);
156        ASSERT(adjustedNodes.size() == eventPathSize);
157        for (size_t j = 0; j < eventPathSize; ++j)
158            eventPathTouchLists[j]->append(touch.cloneWithNewTarget(adjustedNodes[j].get()));
159    }
160}
161#endif
162
163void EventRetargeter::adjustForRelatedTarget(const Node* node, EventTarget* relatedTarget, EventPath& eventPath)
164{
165    if (!node)
166        return;
167    if (!relatedTarget)
168        return;
169    Node* relatedNode = relatedTarget->toNode();
170    if (!relatedNode)
171        return;
172    AdjustedNodes adjustedNodes;
173    calculateAdjustedNodes(node, relatedNode, StopAtBoundaryIfNeeded, eventPath, adjustedNodes);
174    ASSERT(adjustedNodes.size() <= eventPath.size());
175    for (size_t i = 0; i < adjustedNodes.size(); ++i) {
176        ASSERT(eventPath[i]->isMouseOrFocusEventContext());
177        MouseOrFocusEventContext* mouseOrFocusEventContext = static_cast<MouseOrFocusEventContext*>(eventPath[i].get());
178        mouseOrFocusEventContext->setRelatedTarget(adjustedNodes[i]);
179    }
180}
181
182void EventRetargeter::calculateAdjustedNodes(const Node* node, const Node* relatedNode, EventWithRelatedTargetDispatchBehavior eventWithRelatedTargetDispatchBehavior, EventPath& eventPath, AdjustedNodes& adjustedNodes)
183{
184    RelatedNodeMap relatedNodeMap;
185    buildRelatedNodeMap(relatedNode, relatedNodeMap);
186
187    // Synthetic mouse events can have a relatedTarget which is identical to the target.
188    bool targetIsIdenticalToToRelatedTarget = (node == relatedNode);
189
190    TreeScope* lastTreeScope = 0;
191    Node* adjustedNode = 0;
192    for (EventPath::const_iterator iter = eventPath.begin(); iter < eventPath.end(); ++iter) {
193        TreeScope* scope = (*iter)->node()->treeScope();
194        if (scope == lastTreeScope) {
195            // Re-use the previous adjustedRelatedTarget if treeScope does not change. Just for the performance optimization.
196            adjustedNodes.append(adjustedNode);
197        } else {
198            adjustedNode = findRelatedNode(scope, relatedNodeMap);
199            adjustedNodes.append(adjustedNode);
200        }
201        lastTreeScope = scope;
202        if (eventWithRelatedTargetDispatchBehavior == DoesNotStopAtBoundary)
203            continue;
204        if (targetIsIdenticalToToRelatedTarget) {
205            if (node->treeScope()->rootNode() == (*iter)->node()) {
206                eventPath.shrink(iter + 1 - eventPath.begin());
207                break;
208            }
209        } else if ((*iter)->target() == adjustedNode) {
210            // Event dispatching should be stopped here.
211            eventPath.shrink(iter - eventPath.begin());
212            adjustedNodes.shrink(adjustedNodes.size() - 1);
213            break;
214        }
215    }
216}
217
218void EventRetargeter::buildRelatedNodeMap(const Node* relatedNode, RelatedNodeMap& relatedNodeMap)
219{
220    Vector<Node*, 32> relatedNodeStack;
221    TreeScope* lastTreeScope = 0;
222    for (EventPathWalker walker(relatedNode); walker.node(); walker.moveToParent()) {
223        Node* node = walker.node();
224        if (relatedNodeStack.isEmpty())
225            relatedNodeStack.append(node);
226        else if (walker.isVisitingInsertionPointInReprojection())
227            relatedNodeStack.append(relatedNodeStack.last());
228        TreeScope* scope = node->treeScope();
229        // Skips adding a node to the map if treeScope does not change. Just for the performance optimization.
230        if (scope != lastTreeScope)
231            relatedNodeMap.add(scope, relatedNodeStack.last());
232        lastTreeScope = scope;
233        if (node->isShadowRoot()) {
234            ASSERT(!relatedNodeStack.isEmpty());
235            relatedNodeStack.removeLast();
236        }
237    }
238}
239
240Node* EventRetargeter::findRelatedNode(TreeScope* scope, RelatedNodeMap& relatedNodeMap)
241{
242    Vector<TreeScope*, 32> parentTreeScopes;
243    Node* relatedNode = 0;
244    while (scope) {
245        parentTreeScopes.append(scope);
246        RelatedNodeMap::const_iterator found = relatedNodeMap.find(scope);
247        if (found != relatedNodeMap.end()) {
248            relatedNode = found->value;
249            break;
250        }
251        scope = scope->parentTreeScope();
252    }
253    for (Vector<TreeScope*, 32>::iterator iter = parentTreeScopes.begin(); iter < parentTreeScopes.end(); ++iter)
254        relatedNodeMap.add(*iter, relatedNode);
255    return relatedNode;
256}
257
258}
259