1/*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 * Copyright (C) 2013-2017 Apple Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21#include "config.h"
22#include "EventPath.h"
23
24#include "DOMWindow.h"
25#include "Event.h"
26#include "EventContext.h"
27#include "EventNames.h"
28#include "FullscreenManager.h"
29#include "HTMLSlotElement.h"
30#include "MouseEvent.h"
31#include "Node.h"
32#include "PseudoElement.h"
33#include "ShadowRoot.h"
34#include "TouchEvent.h"
35
36namespace WebCore {
37
38class WindowEventContext final : public EventContext {
39public:
40 WindowEventContext(Node&, DOMWindow&, EventTarget&, int closedShadowDepth);
41private:
42 void handleLocalEvents(Event&, EventInvokePhase) const final;
43};
44
45inline WindowEventContext::WindowEventContext(Node& node, DOMWindow& currentTarget, EventTarget& target, int closedShadowDepth)
46 : EventContext(&node, &currentTarget, &target, closedShadowDepth)
47{
48}
49
50void WindowEventContext::handleLocalEvents(Event& event, EventInvokePhase phase) const
51{
52 event.setTarget(m_target.get());
53 event.setCurrentTarget(m_currentTarget.get());
54 m_currentTarget->fireEventListeners(event, phase);
55}
56
57static inline bool shouldEventCrossShadowBoundary(Event& event, ShadowRoot& shadowRoot, EventTarget& target)
58{
59#if ENABLE(FULLSCREEN_API) && ENABLE(VIDEO)
60 // Video-only full screen is a mode where we use the shadow DOM as an implementation
61 // detail that should not be detectable by the web content.
62 if (is<Node>(target)) {
63 if (auto* element = downcast<Node>(target).document().fullscreenManager().currentFullscreenElement()) {
64 // FIXME: We assume that if the full screen element is a media element that it's
65 // the video-only full screen. Both here and elsewhere. But that is probably wrong.
66 if (element->isMediaElement() && shadowRoot.host() == element)
67 return false;
68 }
69 }
70#endif
71
72 bool targetIsInShadowRoot = is<Node>(target) && &downcast<Node>(target).treeScope().rootNode() == &shadowRoot;
73 return !targetIsInShadowRoot || event.composed();
74}
75
76static Node* nodeOrHostIfPseudoElement(Node* node)
77{
78 return is<PseudoElement>(*node) ? downcast<PseudoElement>(*node).hostElement() : node;
79}
80
81class RelatedNodeRetargeter {
82public:
83 RelatedNodeRetargeter(Node& relatedNode, Node& target);
84
85 Node* currentNode(Node& currentTreeScope);
86 void moveToNewTreeScope(TreeScope* previousTreeScope, TreeScope& newTreeScope);
87
88private:
89 Node* nodeInLowestCommonAncestor();
90 void collectTreeScopes();
91
92 void checkConsistency(Node& currentTarget);
93
94 Node& m_relatedNode;
95 Node* m_retargetedRelatedNode;
96 Vector<TreeScope*, 8> m_ancestorTreeScopes;
97 unsigned m_lowestCommonAncestorIndex { 0 };
98 bool m_hasDifferentTreeRoot { false };
99};
100
101EventPath::EventPath(Node& originalTarget, Event& event)
102{
103 buildPath(originalTarget, event);
104
105 if (auto* relatedTarget = event.relatedTarget())
106 setRelatedTarget(originalTarget, *relatedTarget);
107
108#if ENABLE(TOUCH_EVENTS)
109 if (is<TouchEvent>(event))
110 retargetTouchLists(downcast<TouchEvent>(event));
111#endif
112}
113
114void EventPath::buildPath(Node& originalTarget, Event& event)
115{
116 using MakeEventContext = std::unique_ptr<EventContext> (*)(Node&, EventTarget*, EventTarget*, int closedShadowDepth);
117 MakeEventContext makeEventContext = [] (Node& node, EventTarget* currentTarget, EventTarget* target, int closedShadowDepth) {
118 return std::make_unique<EventContext>(&node, currentTarget, target, closedShadowDepth);
119 };
120 if (is<MouseEvent>(event) || event.isFocusEvent()) {
121 makeEventContext = [] (Node& node, EventTarget* currentTarget, EventTarget* target, int closedShadowDepth) -> std::unique_ptr<EventContext> {
122 return std::make_unique<MouseOrFocusEventContext>(node, currentTarget, target, closedShadowDepth);
123 };
124 }
125#if ENABLE(TOUCH_EVENTS)
126 if (is<TouchEvent>(event)) {
127 makeEventContext = [] (Node& node, EventTarget* currentTarget, EventTarget* target, int closedShadowDepth) -> std::unique_ptr<EventContext> {
128 return std::make_unique<TouchEventContext>(node, currentTarget, target, closedShadowDepth);
129 };
130 }
131#endif
132
133 Node* node = nodeOrHostIfPseudoElement(&originalTarget);
134 Node* target = node ? eventTargetRespectingTargetRules(*node) : nullptr;
135 int closedShadowDepth = 0;
136 // Depths are used to decided which nodes are excluded in event.composedPath when the tree is mutated during event dispatching.
137 // They could be negative for nodes outside the shadow tree of the target node.
138 while (node) {
139 while (node) {
140 m_path.append(makeEventContext(*node, eventTargetRespectingTargetRules(*node), target, closedShadowDepth));
141
142 if (is<ShadowRoot>(*node))
143 break;
144
145 ContainerNode* parent = node->parentNode();
146 if (UNLIKELY(!parent)) {
147 // https://dom.spec.whatwg.org/#interface-document
148 if (is<Document>(*node) && event.type() != eventNames().loadEvent) {
149 ASSERT(target);
150 if (target) {
151 if (auto* window = downcast<Document>(*node).domWindow())
152 m_path.append(std::make_unique<WindowEventContext>(*node, *window, *target, closedShadowDepth));
153 }
154 }
155 return;
156 }
157
158 auto* shadowRootOfParent = parent->shadowRoot();
159 if (UNLIKELY(shadowRootOfParent)) {
160 if (auto* assignedSlot = shadowRootOfParent->findAssignedSlot(*node)) {
161 if (shadowRootOfParent->mode() != ShadowRootMode::Open)
162 closedShadowDepth++;
163 // node is assigned to a slot. Continue dispatching the event at this slot.
164 parent = assignedSlot;
165 }
166 }
167 node = parent;
168 }
169
170 bool exitingShadowTreeOfTarget = &target->treeScope() == &node->treeScope();
171 ShadowRoot& shadowRoot = downcast<ShadowRoot>(*node);
172 if (!shouldEventCrossShadowBoundary(event, shadowRoot, originalTarget))
173 return;
174 node = shadowRoot.host();
175 if (shadowRoot.mode() != ShadowRootMode::Open)
176 closedShadowDepth--;
177 if (exitingShadowTreeOfTarget)
178 target = eventTargetRespectingTargetRules(*node);
179 }
180}
181
182void EventPath::setRelatedTarget(Node& origin, EventTarget& relatedTarget)
183{
184 if (!is<Node>(relatedTarget) || m_path.isEmpty())
185 return;
186
187 auto& relatedNode = downcast<Node>(relatedTarget);
188 RelatedNodeRetargeter retargeter(relatedNode, *m_path[0]->node());
189
190 bool originIsRelatedTarget = &origin == &relatedNode;
191 Node& rootNodeInOriginTreeScope = origin.treeScope().rootNode();
192 TreeScope* previousTreeScope = nullptr;
193 size_t originalEventPathSize = m_path.size();
194 for (unsigned contextIndex = 0; contextIndex < originalEventPathSize; contextIndex++) {
195 auto& ambgiousContext = *m_path[contextIndex];
196 if (!is<MouseOrFocusEventContext>(ambgiousContext))
197 continue;
198 auto& context = downcast<MouseOrFocusEventContext>(ambgiousContext);
199
200 Node& currentTarget = *context.node();
201 TreeScope& currentTreeScope = currentTarget.treeScope();
202 if (UNLIKELY(previousTreeScope && &currentTreeScope != previousTreeScope))
203 retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope);
204
205 Node* currentRelatedNode = retargeter.currentNode(currentTarget);
206 if (UNLIKELY(!originIsRelatedTarget && context.target() == currentRelatedNode)) {
207 m_path.shrink(contextIndex);
208 break;
209 }
210
211 context.setRelatedTarget(currentRelatedNode);
212
213 if (UNLIKELY(originIsRelatedTarget && context.node() == &rootNodeInOriginTreeScope)) {
214 m_path.shrink(contextIndex + 1);
215 break;
216 }
217
218 previousTreeScope = &currentTreeScope;
219 }
220}
221
222#if ENABLE(TOUCH_EVENTS)
223
224void EventPath::retargetTouch(TouchEventContext::TouchListType type, const Touch& touch)
225{
226 auto* eventTarget = touch.target();
227 if (!is<Node>(eventTarget))
228 return;
229
230 RelatedNodeRetargeter retargeter(downcast<Node>(*eventTarget), *m_path[0]->node());
231 TreeScope* previousTreeScope = nullptr;
232 for (auto& context : m_path) {
233 Node& currentTarget = *context->node();
234 TreeScope& currentTreeScope = currentTarget.treeScope();
235 if (UNLIKELY(previousTreeScope && &currentTreeScope != previousTreeScope))
236 retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope);
237
238 if (is<TouchEventContext>(*context)) {
239 Node* currentRelatedNode = retargeter.currentNode(currentTarget);
240 downcast<TouchEventContext>(*context).touchList(type).append(touch.cloneWithNewTarget(currentRelatedNode));
241 }
242
243 previousTreeScope = &currentTreeScope;
244 }
245}
246
247void EventPath::retargetTouchList(TouchEventContext::TouchListType type, const TouchList* list)
248{
249 for (unsigned i = 0, length = list ? list->length() : 0; i < length; ++i)
250 retargetTouch(type, *list->item(i));
251}
252
253void EventPath::retargetTouchLists(const TouchEvent& event)
254{
255 retargetTouchList(TouchEventContext::Touches, event.touches());
256 retargetTouchList(TouchEventContext::TargetTouches, event.targetTouches());
257 retargetTouchList(TouchEventContext::ChangedTouches, event.changedTouches());
258}
259
260#endif
261
262// https://dom.spec.whatwg.org/#dom-event-composedpath
263// Any node whose depth computed in EventPath::buildPath is greater than the context object is excluded.
264// Because we can exit out of a closed shadow tree and re-enter another closed shadow tree via a slot,
265// we decrease the *allowed depth* whenever we moved to a "shallower" (closer-to-document) tree.
266Vector<EventTarget*> EventPath::computePathUnclosedToTarget(const EventTarget& target) const
267{
268 Vector<EventTarget*> path;
269 auto pathSize = m_path.size();
270 RELEASE_ASSERT(pathSize);
271 path.reserveInitialCapacity(pathSize);
272
273 auto currentTargetIndex = m_path.findMatching([&target] (auto& context) {
274 return context->currentTarget() == &target;
275 });
276 RELEASE_ASSERT(currentTargetIndex != notFound);
277 auto currentTargetDepth = m_path[currentTargetIndex]->closedShadowDepth();
278
279 auto appendTargetWithLesserDepth = [&path] (const EventContext& currentContext, int& currentDepthAllowed) {
280 auto depth = currentContext.closedShadowDepth();
281 bool contextIsInsideInnerShadowTree = depth > currentDepthAllowed;
282 if (contextIsInsideInnerShadowTree)
283 return;
284 bool movedOutOfShadowTree = depth < currentDepthAllowed;
285 if (movedOutOfShadowTree)
286 currentDepthAllowed = depth;
287 path.uncheckedAppend(currentContext.currentTarget());
288 };
289
290 auto currentDepthAllowed = currentTargetDepth;
291 auto i = currentTargetIndex;
292 do {
293 appendTargetWithLesserDepth(*m_path[i], currentDepthAllowed);
294 } while (i--);
295 path.reverse();
296
297 currentDepthAllowed = currentTargetDepth;
298 for (auto i = currentTargetIndex + 1; i < pathSize; ++i)
299 appendTargetWithLesserDepth(*m_path[i], currentDepthAllowed);
300
301 return path;
302}
303
304EventPath::EventPath(const Vector<Element*>& targets)
305{
306 // FIXME: This function seems wrong. Why are we not firing events in the closed shadow trees?
307 for (auto* target : targets) {
308 ASSERT(target);
309 Node* origin = *targets.begin();
310 if (!target->isClosedShadowHidden(*origin))
311 m_path.append(std::make_unique<EventContext>(target, target, origin, 0));
312 }
313}
314
315EventPath::EventPath(const Vector<EventTarget*>& targets)
316{
317 for (auto* target : targets) {
318 ASSERT(target);
319 ASSERT(!is<Node>(target));
320 m_path.append(std::make_unique<EventContext>(nullptr, target, *targets.begin(), 0));
321 }
322}
323
324static Node* moveOutOfAllShadowRoots(Node& startingNode)
325{
326 Node* node = &startingNode;
327 while (node->isInShadowTree())
328 node = downcast<ShadowRoot>(node->treeScope().rootNode()).host();
329 return node;
330}
331
332RelatedNodeRetargeter::RelatedNodeRetargeter(Node& relatedNode, Node& target)
333 : m_relatedNode(relatedNode)
334 , m_retargetedRelatedNode(&relatedNode)
335{
336 auto& targetTreeScope = target.treeScope();
337 TreeScope* currentTreeScope = &m_relatedNode.treeScope();
338 if (LIKELY(currentTreeScope == &targetTreeScope && target.isConnected() && m_relatedNode.isConnected()))
339 return;
340
341 if (&currentTreeScope->documentScope() != &targetTreeScope.documentScope()) {
342 m_hasDifferentTreeRoot = true;
343 m_retargetedRelatedNode = nullptr;
344 return;
345 }
346 if (relatedNode.isConnected() != target.isConnected()) {
347 m_hasDifferentTreeRoot = true;
348 m_retargetedRelatedNode = moveOutOfAllShadowRoots(relatedNode);
349 return;
350 }
351
352 collectTreeScopes();
353
354 // FIXME: We should collect this while constructing the event path.
355 Vector<TreeScope*, 8> targetTreeScopeAncestors;
356 for (TreeScope* currentTreeScope = &targetTreeScope; currentTreeScope; currentTreeScope = currentTreeScope->parentTreeScope())
357 targetTreeScopeAncestors.append(currentTreeScope);
358 ASSERT_WITH_SECURITY_IMPLICATION(!targetTreeScopeAncestors.isEmpty());
359
360 unsigned i = m_ancestorTreeScopes.size();
361 unsigned j = targetTreeScopeAncestors.size();
362 ASSERT_WITH_SECURITY_IMPLICATION(m_ancestorTreeScopes.last() == targetTreeScopeAncestors.last());
363 while (m_ancestorTreeScopes[i - 1] == targetTreeScopeAncestors[j - 1]) {
364 i--;
365 j--;
366 if (!i || !j)
367 break;
368 }
369
370 bool lowestCommonAncestorIsDocumentScope = i + 1 == m_ancestorTreeScopes.size();
371 if (lowestCommonAncestorIsDocumentScope && !relatedNode.isConnected() && !target.isConnected()) {
372 Node& relatedNodeAncestorInDocumentScope = i ? *downcast<ShadowRoot>(m_ancestorTreeScopes[i - 1]->rootNode()).shadowHost() : relatedNode;
373 Node& targetAncestorInDocumentScope = j ? *downcast<ShadowRoot>(targetTreeScopeAncestors[j - 1]->rootNode()).shadowHost() : target;
374 if (&targetAncestorInDocumentScope.rootNode() != &relatedNodeAncestorInDocumentScope.rootNode()) {
375 m_hasDifferentTreeRoot = true;
376 m_retargetedRelatedNode = moveOutOfAllShadowRoots(relatedNode);
377 return;
378 }
379 }
380
381 m_lowestCommonAncestorIndex = i;
382 m_retargetedRelatedNode = nodeInLowestCommonAncestor();
383}
384
385inline Node* RelatedNodeRetargeter::currentNode(Node& currentTarget)
386{
387 checkConsistency(currentTarget);
388 return m_retargetedRelatedNode;
389}
390
391void RelatedNodeRetargeter::moveToNewTreeScope(TreeScope* previousTreeScope, TreeScope& newTreeScope)
392{
393 if (m_hasDifferentTreeRoot)
394 return;
395
396 auto& currentRelatedNodeScope = m_retargetedRelatedNode->treeScope();
397 if (previousTreeScope != &currentRelatedNodeScope) {
398 // currentRelatedNode is still outside our shadow tree. New tree scope may contain currentRelatedNode
399 // but there is no need to re-target it. Moving into a slot (thereby a deeper shadow tree) doesn't matter.
400 return;
401 }
402
403 bool enteredSlot = newTreeScope.parentTreeScope() == previousTreeScope;
404 if (enteredSlot) {
405 if (m_lowestCommonAncestorIndex) {
406 if (m_ancestorTreeScopes.isEmpty())
407 collectTreeScopes();
408 bool relatedNodeIsInSlot = m_ancestorTreeScopes[m_lowestCommonAncestorIndex - 1] == &newTreeScope;
409 if (relatedNodeIsInSlot) {
410 m_lowestCommonAncestorIndex--;
411 m_retargetedRelatedNode = nodeInLowestCommonAncestor();
412 ASSERT(&newTreeScope == &m_retargetedRelatedNode->treeScope());
413 }
414 } else
415 ASSERT(m_retargetedRelatedNode == &m_relatedNode);
416 } else {
417 ASSERT(previousTreeScope->parentTreeScope() == &newTreeScope);
418 m_lowestCommonAncestorIndex++;
419 ASSERT_WITH_SECURITY_IMPLICATION(m_ancestorTreeScopes.isEmpty() || m_lowestCommonAncestorIndex < m_ancestorTreeScopes.size());
420 m_retargetedRelatedNode = downcast<ShadowRoot>(currentRelatedNodeScope.rootNode()).host();
421 ASSERT(&newTreeScope == &m_retargetedRelatedNode->treeScope());
422 }
423}
424
425inline Node* RelatedNodeRetargeter::nodeInLowestCommonAncestor()
426{
427 if (!m_lowestCommonAncestorIndex)
428 return &m_relatedNode;
429 auto& rootNode = m_ancestorTreeScopes[m_lowestCommonAncestorIndex - 1]->rootNode();
430 return downcast<ShadowRoot>(rootNode).host();
431}
432
433void RelatedNodeRetargeter::collectTreeScopes()
434{
435 ASSERT(m_ancestorTreeScopes.isEmpty());
436 for (TreeScope* currentTreeScope = &m_relatedNode.treeScope(); currentTreeScope; currentTreeScope = currentTreeScope->parentTreeScope())
437 m_ancestorTreeScopes.append(currentTreeScope);
438 ASSERT_WITH_SECURITY_IMPLICATION(!m_ancestorTreeScopes.isEmpty());
439}
440
441#if ASSERT_DISABLED
442
443inline void RelatedNodeRetargeter::checkConsistency(Node&)
444{
445}
446
447#else
448
449void RelatedNodeRetargeter::checkConsistency(Node& currentTarget)
450{
451 if (!m_retargetedRelatedNode)
452 return;
453 ASSERT(!currentTarget.isClosedShadowHidden(*m_retargetedRelatedNode));
454 ASSERT(m_retargetedRelatedNode == &currentTarget.treeScope().retargetToScope(m_relatedNode));
455}
456
457#endif
458
459}
460