static inline bool shouldStopAtShadowRoot(Event& event, ShadowRoot& shadowRoot, EventTarget& target) { // WebKit never allowed selectstart event to cross the the shadow DOM boundary. // Changing this breaks existing sites. // See https://bugs.webkit.org/show_bug.cgi?id=52195 for details. const AtomicString eventType = event.type(); return target.toNode() && target.toNode()->shadowHost() == shadowRoot.host() && event.scoped(); }
void EventPath::setRelatedTarget(Node& origin, EventTarget& relatedTarget) { Node* relatedNode = relatedTarget.toNode(); if (!relatedNode) return; EventRelatedNodeResolver resolver(*relatedNode); bool originIsRelatedTarget = &origin == relatedNode; Node& rootNodeInOriginTreeScope = origin.treeScope().rootNode(); size_t eventPathSize = m_path.size(); size_t i = 0; while (i < eventPathSize) { Node* contextNode = m_path[i]->node(); Node* currentRelatedNode = resolver.moveToParentOrShadowHost(*contextNode); if (!originIsRelatedTarget && m_path[i]->target() == currentRelatedNode) break; toMouseOrFocusEventContext(*m_path[i]).setRelatedTarget(currentRelatedNode); i++; if (originIsRelatedTarget && &rootNodeInOriginTreeScope == contextNode) break; } m_path.shrink(i); }
void MediaControlVolumeSliderContainerElement::defaultEventHandler(Event* event) { if (!event->isMouseEvent() || event->type() != eventNames().mouseoutEvent) return; // Poor man's mouseleave event detection. MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); EventTarget* relatedTarget = mouseEvent->relatedTarget(); if (!relatedTarget || !relatedTarget->toNode()) return; if (this->containsIncludingShadowDOM(relatedTarget->toNode())) return; hide(); }
static inline bool shouldEventCrossShadowBoundary(Event& event, ShadowRoot& shadowRoot, EventTarget& target) { Node* targetNode = target.toNode(); #if ENABLE(FULLSCREEN_API) && ENABLE(VIDEO) // Video-only full screen is a mode where we use the shadow DOM as an implementation // detail that should not be detectable by the web content. if (targetNode) { if (Element* element = targetNode->document().webkitCurrentFullScreenElement()) { // FIXME: We assume that if the full screen element is a media element that it's // the video-only full screen. Both here and elsewhere. But that is probably wrong. if (element->isMediaElement() && shadowRoot.hostElement() == element) return false; } } #endif // WebKit never allowed selectstart event to cross the the shadow DOM boundary. // Changing this breaks existing sites. // See https://bugs.webkit.org/show_bug.cgi?id=52195 for details. const AtomicString& eventType = event.type(); bool targetIsInShadowRoot = targetNode && targetNode->treeScope().rootNode() == &shadowRoot; return !targetIsInShadowRoot || !(eventType == eventNames().abortEvent || eventType == eventNames().changeEvent || eventType == eventNames().errorEvent || eventType == eventNames().loadEvent || eventType == eventNames().resetEvent || eventType == eventNames().resizeEvent || eventType == eventNames().scrollEvent || eventType == eventNames().selectEvent || eventType == eventNames().selectstartEvent); }
void ReplayController::willDispatchEvent(const Event& event, Frame* frame) { EventTarget* target = event.target(); if (!target && !frame) return; Document* document = frame ? frame->document() : nullptr; // Fetch the document from the event target, because the target could be detached. if (Node* node = target->toNode()) document = node->inDocument() ? &node->document() : node->ownerDocument(); else if (DOMWindow* window = target->toDOMWindow()) document = window->document(); ASSERT(document); InputCursor& cursor = document->inputCursor(); #if !LOG_DISABLED bool eventIsUnrelated = !cursor.isCapturing() && !cursor.isReplaying(); logDispatchedDOMEvent(event, eventIsUnrelated); #else UNUSED_PARAM(cursor); #endif #if ENABLE_AGGRESSIVE_DETERMINISM_CHECKS // To ensure deterministic JS execution, all DOM events must be dispatched deterministically. // If these assertions fail, then this DOM event is being dispatched by a nondeterministic EventLoop // cycle, and may cause program execution to diverge if any JS code runs because of the DOM event. if (cursor.isCapturing() || cursor.isReplaying()) ASSERT(cursor.withinEventLoopInputExtent()); else if (cursor.isReplaying()) ASSERT(dispatcher().isDispatching()); #endif }
bool MediaControls::containsRelatedTarget(Event* event) { if (!event->isMouseEvent()) return false; EventTarget* relatedTarget = toMouseEvent(event)->relatedTarget(); if (!relatedTarget) return false; return contains(relatedTarget->toNode()); }
bool MediaControlRootElement::containsRelatedTarget(Event* event) { if (!event->isMouseEvent()) return false; EventTarget* relatedTarget = static_cast<MouseEvent*>(event)->relatedTarget(); if (!relatedTarget) return false; return contains(relatedTarget->toNode()); }
bool MediaControls::containsRelatedTarget(Event* event) { if (!is<MouseEvent>(*event)) return false; EventTarget* relatedTarget = downcast<MouseEvent>(*event).relatedTarget(); if (!relatedTarget) return false; return contains(relatedTarget->toNode()); }
Node* MouseEvent::toElement() const { // MSIE extension - "the object toward which the user is moving the mouse pointer" if (type() == eventNames().mouseoutEvent || type() == eventNames().mouseleaveEvent) { EventTarget* relatedTarget = this->relatedTarget(); return relatedTarget ? relatedTarget->toNode() : nullptr; } return target() ? target()->toNode() : nullptr; }
Node* MouseEvent::fromElement() const { // MSIE extension - "object from which activation or the mouse pointer is exiting during the event" (huh?) if (type() != eventNames().mouseoutEvent && type() != eventNames().mouseleaveEvent) { EventTarget* relatedTarget = this->relatedTarget(); return relatedTarget ? relatedTarget->toNode() : nullptr; } return target() ? target()->toNode() : nullptr; }
void EventPath::setRelatedTarget(EventTarget& relatedTarget) { Node* relatedNode = relatedTarget.toNode(); if (!relatedNode) return; EventRelatedNodeResolver resolver(*relatedNode); size_t eventPathSize = m_path.size(); for (size_t i = 0; i < eventPathSize; i++) toMouseOrFocusEventContext(*m_path[i]).setRelatedTarget(resolver.moveToParentOrShadowHost(*m_path[i]->node())); }
static inline bool shouldEventCrossShadowBoundary(Event& event, ShadowRoot& shadowRoot, EventTarget& target) { Node* targetNode = target.toNode(); #if ENABLE(FULLSCREEN_API) && ENABLE(VIDEO) // Video-only full screen is a mode where we use the shadow DOM as an implementation // detail that should not be detectable by the web content. if (targetNode) { if (Element* element = targetNode->document().webkitCurrentFullScreenElement()) { // FIXME: We assume that if the full screen element is a media element that it's // the video-only full screen. Both here and elsewhere. But that is probably wrong. if (element->isMediaElement() && shadowRoot.host() == element) return false; } } #endif bool targetIsInShadowRoot = targetNode && &targetNode->treeScope().rootNode() == &shadowRoot; return !targetIsInShadowRoot || !event.scoped(); }
void EventPath::setRelatedTarget(Node& origin, EventTarget& relatedTarget) { UNUSED_PARAM(origin); Node* relatedNode = relatedTarget.toNode(); if (!relatedNode || m_path.isEmpty()) return; RelatedNodeRetargeter retargeter(*relatedNode, downcast<MouseOrFocusEventContext>(*m_path[0]).node()->treeScope()); bool originIsRelatedTarget = &origin == relatedNode; // FIXME: We should add a new flag on Event instead. bool shouldTrimEventPath = m_event.type() == eventNames().mouseoverEvent || m_event.type() == eventNames().mousemoveEvent || m_event.type() == eventNames().mouseoutEvent; Node& rootNodeInOriginTreeScope = origin.treeScope().rootNode(); TreeScope* previousTreeScope = nullptr; size_t originalEventPathSize = m_path.size(); for (unsigned contextIndex = 0; contextIndex < originalEventPathSize; contextIndex++) { auto& context = downcast<MouseOrFocusEventContext>(*m_path[contextIndex]); TreeScope& currentTreeScope = context.node()->treeScope(); if (UNLIKELY(previousTreeScope && ¤tTreeScope != previousTreeScope)) retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope); Node* currentRelatedNode = retargeter.currentNode(currentTreeScope); if (UNLIKELY(shouldTrimEventPath && !originIsRelatedTarget && context.target() == currentRelatedNode)) { m_path.shrink(contextIndex); break; } context.setRelatedTarget(currentRelatedNode); if (UNLIKELY(shouldTrimEventPath && originIsRelatedTarget && context.node() == &rootNodeInOriginTreeScope)) { m_path.shrink(contextIndex + 1); break; } previousTreeScope = ¤tTreeScope; } }
static void logDispatchedDOMEvent(const Event& event, bool eventIsUnrelated) { EventTarget* target = event.target(); if (!target) return; // A DOM event is unrelated if it is being dispatched to a document that is neither capturing nor replaying. if (Node* node = target->toNode()) { LOG(WebReplay, "%-20s --->%s DOM event: type=%s, target=%u/node[%p] %s\n", "ReplayEvents", (eventIsUnrelated) ? "Unrelated" : "Dispatching", event.type().string().utf8().data(), frameIndexFromDocument((node->inDocument()) ? &node->document() : node->ownerDocument()), node, node->nodeName().utf8().data()); } else if (DOMWindow* window = target->toDOMWindow()) { LOG(WebReplay, "%-20s --->%s DOM event: type=%s, target=%u/window[%p] %s\n", "ReplayEvents", (eventIsUnrelated) ? "Unrelated" : "Dispatching", event.type().string().utf8().data(), frameIndexFromDocument(window->document()), window, window->location()->href().utf8().data()); } }
void EventPath::setRelatedTarget(Node& origin, EventTarget& relatedTarget) { Node* relatedNode = relatedTarget.toNode(); if (!relatedNode || m_path.isEmpty()) return; RelatedNodeRetargeter retargeter(*relatedNode, *m_path[0]->node()); bool originIsRelatedTarget = &origin == relatedNode; bool relatedTargetScoped = m_event.relatedTargetScoped(); Node& rootNodeInOriginTreeScope = origin.treeScope().rootNode(); TreeScope* previousTreeScope = nullptr; size_t originalEventPathSize = m_path.size(); for (unsigned contextIndex = 0; contextIndex < originalEventPathSize; contextIndex++) { auto& context = downcast<MouseOrFocusEventContext>(*m_path[contextIndex]); Node& currentTarget = *context.node(); TreeScope& currentTreeScope = currentTarget.treeScope(); if (UNLIKELY(previousTreeScope && ¤tTreeScope != previousTreeScope)) retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope); Node* currentRelatedNode = retargeter.currentNode(currentTarget); if (UNLIKELY(relatedTargetScoped && !originIsRelatedTarget && context.target() == currentRelatedNode)) { m_path.shrink(contextIndex); break; } context.setRelatedTarget(currentRelatedNode); if (UNLIKELY(relatedTargetScoped && originIsRelatedTarget && context.node() == &rootNodeInOriginTreeScope)) { m_path.shrink(contextIndex + 1); break; } previousTreeScope = ¤tTreeScope; } }
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, *m_path[0]->node()); TreeScope* previousTreeScope = nullptr; for (auto& context : m_path) { Node& currentTarget = *context->node(); TreeScope& currentTreeScope = currentTarget.treeScope(); if (UNLIKELY(previousTreeScope && ¤tTreeScope != previousTreeScope)) retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope); Node* currentRelatedNode = retargeter.currentNode(currentTarget); downcast<TouchEventContext>(*context).touchList(touchListType)->append(touch.cloneWithNewTarget(currentRelatedNode)); previousTreeScope = ¤tTreeScope; } }
WebInputEventResult TouchEventManager::dispatchTouchEvents( const PlatformTouchEvent& event, const HeapVector<TouchInfo>& touchInfos, bool allTouchesReleased) { bool touchStartOrFirstTouchMove = false; if (event.type() == PlatformEvent::TouchStart) { m_waitingForFirstTouchMove = true; touchStartOrFirstTouchMove = true; } else if (event.type() == PlatformEvent::TouchMove) { touchStartOrFirstTouchMove = m_waitingForFirstTouchMove; m_waitingForFirstTouchMove = false; } // Build up the lists to use for the |touches|, |targetTouches| and // |changedTouches| attributes in the JS event. See // http://www.w3.org/TR/touch-events/#touchevent-interface for how these // lists fit together. // Holds the complete set of touches on the screen. TouchList* touches = TouchList::create(); // A different view on the 'touches' list above, filtered and grouped by // event target. Used for the |targetTouches| list in the JS event. using TargetTouchesHeapMap = HeapHashMap<EventTarget*, Member<TouchList>>; TargetTouchesHeapMap touchesByTarget; // Array of touches per state, used to assemble the |changedTouches| list. ChangedTouches changedTouches[PlatformTouchPoint::TouchStateEnd]; for (unsigned i = 0; i < touchInfos.size(); ++i) { const TouchInfo& touchInfo = touchInfos[i]; const PlatformTouchPoint& point = touchInfo.point; PlatformTouchPoint::TouchState pointState = point.state(); if (touchInfo.consumed) continue; Touch* touch = Touch::create( touchInfo.targetFrame.get(), touchInfo.touchNode.get(), point.id(), point.screenPos(), touchInfo.contentPoint, touchInfo.adjustedRadius, point.rotationAngle(), point.force(), touchInfo.region); // Ensure this target's touch list exists, even if it ends up empty, so // it can always be passed to TouchEvent::Create below. TargetTouchesHeapMap::iterator targetTouchesIterator = touchesByTarget.find(touchInfo.touchNode.get()); if (targetTouchesIterator == touchesByTarget.end()) { touchesByTarget.set(touchInfo.touchNode.get(), TouchList::create()); targetTouchesIterator = touchesByTarget.find(touchInfo.touchNode.get()); } // |touches| and |targetTouches| should only contain information about // touches still on the screen, so if this point is released or // cancelled it will only appear in the |changedTouches| list. if (pointState != PlatformTouchPoint::TouchReleased && pointState != PlatformTouchPoint::TouchCancelled) { touches->append(touch); targetTouchesIterator->value->append(touch); } // Now build up the correct list for |changedTouches|. // Note that any touches that are in the TouchStationary state (e.g. if // the user had several points touched but did not move them all) should // never be in the |changedTouches| list so we do not handle them // explicitly here. See https://bugs.webkit.org/show_bug.cgi?id=37609 // for further discussion about the TouchStationary state. if (pointState != PlatformTouchPoint::TouchStationary && touchInfo.knownTarget) { ASSERT(pointState < PlatformTouchPoint::TouchStateEnd); if (!changedTouches[pointState].m_touches) changedTouches[pointState].m_touches = TouchList::create(); changedTouches[pointState].m_touches->append(touch); changedTouches[pointState].m_targets.add(touchInfo.touchNode); } } if (allTouchesReleased) { m_touchSequenceDocument.clear(); m_touchSequenceUserGestureToken.clear(); } WebInputEventResult eventResult = WebInputEventResult::NotHandled; // Now iterate through the |changedTouches| list and |m_targets| within it, // sending TouchEvents to the targets as required. for (unsigned state = 0; state != PlatformTouchPoint::TouchStateEnd; ++state) { if (!changedTouches[state].m_touches) continue; const AtomicString& eventName(touchEventNameForTouchPointState(static_cast<PlatformTouchPoint::TouchState>(state))); for (const auto& eventTarget : changedTouches[state].m_targets) { EventTarget* touchEventTarget = eventTarget; TouchEvent* touchEvent = TouchEvent::create( touches, touchesByTarget.get(touchEventTarget), changedTouches[state].m_touches.get(), eventName, touchEventTarget->toNode()->document().domWindow(), event.getModifiers(), event.cancelable(), event.causesScrollingIfUncanceled(), event.timestamp()); DispatchEventResult domDispatchResult = touchEventTarget->dispatchEvent(touchEvent); // Only report for top level documents with a single touch on // touch-start or the first touch-move. if (touchStartOrFirstTouchMove && touchInfos.size() == 1 && event.cancelable() && m_frame->isMainFrame()) { DEFINE_STATIC_LOCAL(EnumerationHistogram, rootDocumentListenerHistogram, ("Event.Touch.TargetAndDispatchResult", TouchTargetAndDispatchResultTypeMax)); rootDocumentListenerHistogram.count(toTouchTargetHistogramValue(eventTarget, domDispatchResult)); // Count the handled touch starts and first touch moves before and after the page is fully loaded respectively. if (m_frame->document()->isLoadCompleted()) { DEFINE_STATIC_LOCAL(EnumerationHistogram, touchDispositionsAfterPageLoadHistogram, ("Event.Touch.TouchDispositionsAfterPageLoad", TouchEventDispatchResultTypeMax)); touchDispositionsAfterPageLoadHistogram.count((domDispatchResult != DispatchEventResult::NotCanceled) ? HandledTouches : UnhandledTouches); } else { DEFINE_STATIC_LOCAL(EnumerationHistogram, touchDispositionsBeforePageLoadHistogram, ("Event.Touch.TouchDispositionsBeforePageLoad", TouchEventDispatchResultTypeMax)); touchDispositionsBeforePageLoadHistogram.count((domDispatchResult != DispatchEventResult::NotCanceled) ? HandledTouches : UnhandledTouches); } } eventResult = EventHandler::mergeEventResult(eventResult, EventHandler::toWebInputEventResult(domDispatchResult)); } } return eventResult; }
EventPath::EventPath(Node& originalTarget, Event& event) : m_event(event) { #if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT) Vector<EventTarget*, 16> targetStack; #endif bool isMouseOrFocusEvent = event.isMouseEvent() || event.isFocusEvent(); #if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) bool isTouchEvent = event.isTouchEvent(); #endif EventTarget* target = nullptr; Node* node = nodeOrHostIfPseudoElement(&originalTarget); while (node) { if (!target) target = eventTargetRespectingTargetRules(*node); ContainerNode* parent; for (; node; node = parent) { EventTarget* currentTarget = eventTargetRespectingTargetRules(*node); if (isMouseOrFocusEvent) m_path.append(std::make_unique<MouseOrFocusEventContext>(node, currentTarget, target)); #if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) else if (isTouchEvent) m_path.append(std::make_unique<TouchEventContext>(node, currentTarget, target)); #endif else m_path.append(std::make_unique<EventContext>(node, currentTarget, target)); if (is<ShadowRoot>(*node)) break; parent = node->parentNode(); if (!parent) return; #if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT) if (ShadowRoot* shadowRootOfParent = parent->shadowRoot()) { if (auto* assignedSlot = shadowRootOfParent->findAssignedSlot(*node)) { // node is assigned to a slot. Continue dispatching the event at this slot. targetStack.append(target); parent = assignedSlot; target = assignedSlot; } } #endif node = parent; } ShadowRoot& shadowRoot = downcast<ShadowRoot>(*node); // At a shadow root. Continue dispatching the event at the shadow host. #if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT) if (!targetStack.isEmpty()) { // Move target back to a descendant of the shadow host if the event did not originate in this shadow tree or its inner shadow trees. target = targetStack.last(); targetStack.removeLast(); ASSERT(shadowRoot.host()->contains(target->toNode())); } else #endif target = nullptr; if (!shouldEventCrossShadowBoundary(event, shadowRoot, originalTarget)) return; node = shadowRoot.host(); } }