예제 #1
0
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();
}
예제 #2
0
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);
}
예제 #3
0
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();
}
예제 #4
0
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);
}
예제 #5
0
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
}
예제 #6
0
bool MediaControls::containsRelatedTarget(Event* event) {
  if (!event->isMouseEvent())
    return false;
  EventTarget* relatedTarget = toMouseEvent(event)->relatedTarget();
  if (!relatedTarget)
    return false;
  return contains(relatedTarget->toNode());
}
예제 #7
0
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());
}
예제 #8
0
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());
}
예제 #9
0
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;
}
예제 #10
0
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;
}
예제 #11
0
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()));
}
예제 #12
0
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();
}
예제 #13
0
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 && &currentTreeScope != 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 = &currentTreeScope;
    }
}
예제 #14
0
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());
    }
}
예제 #15
0
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 && &currentTreeScope != 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 = &currentTreeScope;
    }
}
예제 #16
0
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 && &currentTreeScope != previousTreeScope))
            retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope);

        Node* currentRelatedNode = retargeter.currentNode(currentTarget);
        downcast<TouchEventContext>(*context).touchList(touchListType)->append(touch.cloneWithNewTarget(currentRelatedNode));

        previousTreeScope = &currentTreeScope;
    }
}
예제 #17
0
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;
}
예제 #18
0
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();
    }
}