Changeset 83386 in webkit


Ignore:
Timestamp:
Apr 9, 2011 7:33:19 PM (13 years ago)
Author:
Dimitri Glazkov
Message:

2011-04-02 Dimitri Glazkov <Dimitri Glazkov>

Reviewed by Ojan Vafai.

Implement proper handling of mouseover/mouseout events in regard to shadow DOM boundaries.
https://bugs.webkit.org/show_bug.cgi?id=55515

  • fast/events/shadow-boundary-crossing-expected.txt: Updated expectations.
  • fast/events/shadow-boundary-crossing.html: Added new test for mouseover/mouseout handling.

2011-04-08 Dimitri Glazkov <Dimitri Glazkov>

Reviewed by Ojan Vafai.

Implement proper handling of mouseover/mouseout events in regard to shadow DOM boundaries.
https://bugs.webkit.org/show_bug.cgi?id=55515

This implements XBL 2.0's specified handling of mouseover/mouseout events:
http://dev.w3.org/2006/xbl2/Overview.html#the-mouseover-and-mouseout-events

To do this, we:
1) calculate lowest common ancestor between relatedTarget and target, and

the nearest boundaries around them: the outer (common) boundary, and the
inner (specific to relatedTarget) boundary. Then, we

2) ensure that events only propagate up to the common boundary (or

all the way if boundary is not found), while

3) updating relatedTarget be the inner boundary.

We also detect the most common case when no common boundary could exist
and provide a fast path to short-circuit most of the boundary detection
logic.

Test: fast/events/shadow-boundary-crossing.html

  • dom/EventDispatcher.cpp: (WebCore::EventDispatcher::adjustToShadowBoundaries): Added a helper to determine lowest

common ancestor, the boundaries around it, and compute adjustments
to relatedTarget and event target ancestor chain.

(WebCore::ancestorsCrossShadowBoundaries): Added.
(WebCore::EventDispatcher::adjustRelatedTarget): Changed to calculate

inner/outer shadow DOM boundaries and adjust ancestors chain accordingly.

(WebCore::EventDispatcher::EventDispatcher): Added flag initializer
(WebCore::EventDispatcher::ensureEventAncestors): Renamed from getEventAncestors,

converted to use initialization flag, rather than testing for empty.

  • dom/EventDispatcher.h: Adjusted decls.
  • dom/MouseEvent.cpp: (WebCore::MouseEventDispatchMediator::dispatchEvent): Changed to send event

to adjustRelatedTarget.

Location:
trunk
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r83383 r83386  
     12011-04-02  Dimitri Glazkov  <dglazkov@chromium.org>
     2
     3        Reviewed by Ojan Vafai.
     4
     5        Implement proper handling of mouseover/mouseout events in regard to shadow DOM boundaries.
     6        https://bugs.webkit.org/show_bug.cgi?id=55515
     7
     8        * fast/events/shadow-boundary-crossing-expected.txt: Updated expectations.
     9        * fast/events/shadow-boundary-crossing.html: Added new test for mouseover/mouseout handling.
     10
    1112011-04-09  Dirk Pranke  <dpranke@chromium.org>
    212
  • trunk/LayoutTests/fast/events/shadow-boundary-crossing-expected.txt

    r78077 r83386  
    55Mutation events should not propagate out of the shadow DOM: PASS
    66The selectstart event should not propagate out of the shadow DOM: PASS
     7The mouseover/mouseout event between two elements inside the same shadow subtree should not propagate out of the shadow DOM: PASS
    78Label should look beyond shadow boundary to detect if it encloses its associated element: PASS
    89Events for default event handler should not be retargeted: PASS
  • trunk/LayoutTests/fast/events/shadow-boundary-crossing.html

    r78077 r83386  
    2020}
    2121
     22function moveOverLeftQuarterOf(element)
     23{
     24    if (!window.eventSender)
     25        return;
     26
     27    var x = element.offsetLeft + element.offsetWidth / 4;
     28    var y = element.offsetTop + element.offsetHeight / 2;
     29    eventSender.mouseMoveTo(x, y);
     30}
     31
     32function moveOverRightQuarterOf(element)
     33{
     34    if (!window.eventSender)
     35        return;
     36
     37    var x = element.offsetLeft + element.offsetWidth * 3 / 4;
     38    var y = element.offsetTop + element.offsetHeight / 2;
     39    eventSender.mouseMoveTo(x, y);
     40}
     41
    2242function clickOn(element)
    2343{
     
    3252        return;
    3353
    34     var x = element.offsetLeft + element.offsetWidth / 4;
    35     var y = element.offsetTop + element.offsetHeight / 2;
    36     eventSender.mouseMoveTo(x, y);
     54    moveOverLeftQuarterOf(element);
    3755    eventSender.mouseDown();
    3856    eventSender.mouseUp();
     
    7492        textInput.parentNode.removeChild(textInput);
    7593        document.selectstart = null;
     94    },
     95    mouseOverAndOutPropagation: function()
     96    {
     97        var count = 0;
     98        var fileInput = document.body.appendChild(document.createElement('input'));
     99        fileInput.setAttribute('type', 'file');
     100        var countEventDispatch = function()
     101        {
     102            count++;
     103        }
     104        moveOverLeftQuarterOf(fileInput);
     105
     106        document.body.addEventListener('mouseover', countEventDispatch, false);
     107        document.body.addEventListener('mouseout', countEventDispatch, false);
     108
     109        moveOverRightQuarterOf(fileInput);
     110
     111        log("The mouseover/mouseout event between two elements inside the same shadow subtree should not propagate out of the shadow DOM", count == 0);
     112
     113        document.body.removeEventListener('mouseover', countEventDispatch, false);
     114        document.body.removeEventListener('mouseout', countEventDispatch, false);
     115        fileInput.parentNode.removeChild(fileInput);
    76116    },
    77117    labelSyntheticClick: function()
  • trunk/Source/WebCore/ChangeLog

    r83385 r83386  
     12011-04-08  Dimitri Glazkov  <dglazkov@chromium.org>
     2
     3        Reviewed by Ojan Vafai.
     4
     5        Implement proper handling of mouseover/mouseout events in regard to shadow DOM boundaries.
     6        https://bugs.webkit.org/show_bug.cgi?id=55515
     7
     8        This implements XBL 2.0's specified handling of mouseover/mouseout events:
     9        http://dev.w3.org/2006/xbl2/Overview.html#the-mouseover-and-mouseout-events
     10
     11        To do this, we:
     12        1) calculate lowest common ancestor between relatedTarget and target, and
     13           the nearest boundaries around them: the outer (common) boundary, and the
     14           inner (specific to relatedTarget) boundary. Then, we
     15        2) ensure that events only propagate up to the common boundary (or
     16           all the way if boundary is not found), while
     17        3) updating relatedTarget be the inner boundary.
     18
     19        We also detect the most common case when no common boundary could exist
     20        and provide a fast path to short-circuit most of the boundary detection
     21        logic.
     22
     23        Test: fast/events/shadow-boundary-crossing.html
     24
     25        * dom/EventDispatcher.cpp:
     26        (WebCore::EventDispatcher::adjustToShadowBoundaries): Added a helper to determine lowest
     27            common ancestor, the boundaries around it, and compute adjustments
     28            to relatedTarget and event target ancestor chain.
     29        (WebCore::ancestorsCrossShadowBoundaries): Added.
     30        (WebCore::EventDispatcher::adjustRelatedTarget): Changed to calculate
     31            inner/outer shadow DOM boundaries and adjust ancestors chain accordingly.
     32        (WebCore::EventDispatcher::EventDispatcher): Added flag initializer
     33        (WebCore::EventDispatcher::ensureEventAncestors): Renamed from getEventAncestors,
     34            converted to use initialization flag, rather than testing for empty.
     35        * dom/EventDispatcher.h: Adjusted decls.
     36        * dom/MouseEvent.cpp:
     37        (WebCore::MouseEventDispatchMediator::dispatchEvent): Changed to send event
     38            to adjustRelatedTarget.
     39
    1402011-04-08  Geoffrey Garen  <ggaren@apple.com>
    241
  • trunk/Source/WebCore/dom/EventDispatcher.cpp

    r83298 r83386  
    123123}
    124124
     125PassRefPtr<EventTarget> EventDispatcher::adjustToShadowBoundaries(PassRefPtr<Node> relatedTarget, const Vector<Node*> relatedTargetAncestors)
     126{
     127    Vector<EventContext>::const_iterator lowestCommonBoundary = m_ancestors.end();
     128    // Assume divergent boundary is the relatedTarget itself (in other words, related target ancestor chain does not cross any shadow DOM boundaries).
     129    Vector<Node*>::const_iterator firstDivergentBoundary = relatedTargetAncestors.begin();
     130
     131    Vector<EventContext>::const_iterator targetAncestor = m_ancestors.end();
     132    // Walk down from the top, looking for lowest common ancestor, also monitoring shadow DOM boundaries.
     133    bool diverged = false;
     134    for (Vector<Node*>::const_iterator i = relatedTargetAncestors.end() - 1; i >= relatedTargetAncestors.begin(); --i) {
     135        if (diverged) {
     136            if ((*i)->isShadowRoot()) {
     137                firstDivergentBoundary = i + 1;
     138                break;
     139            }
     140            continue;
     141        }
     142
     143        if (targetAncestor == m_ancestors.begin()) {
     144            diverged = true;
     145            continue;
     146        }
     147
     148        targetAncestor--;
     149
     150        if ((*i)->isShadowRoot())
     151            lowestCommonBoundary = targetAncestor;
     152
     153        if ((*i) != (*targetAncestor).node())
     154            diverged = true;
     155    }
     156
     157    if (!diverged) {
     158        // The relatedTarget is a parent or shadowHost of the target.
     159        if (m_node->isShadowRoot())
     160            lowestCommonBoundary = m_ancestors.begin();
     161    } else if ((*firstDivergentBoundary) == m_node.get()) {
     162        // Since ancestors does not contain target itself, we must account
     163        // for the possibility that target is a shadowHost of relatedTarget
     164        // and thus serves as the lowestCommonBoundary.
     165        // Luckily, in this case the firstDivergentBoundary is target.
     166        lowestCommonBoundary = m_ancestors.begin();
     167    }
     168
     169    // Trim ancestors to lowestCommonBoundary to keep events inside of the common shadow DOM subtree.
     170    if (lowestCommonBoundary != m_ancestors.end())
     171        m_ancestors.shrink(lowestCommonBoundary - m_ancestors.begin());
     172    // Set event's related target to the first encountered shadow DOM boundary in the divergent subtree.
     173    return firstDivergentBoundary != relatedTargetAncestors.begin() ? *firstDivergentBoundary : relatedTarget;
     174}
     175
     176inline static bool ancestorsCrossShadowBoundaries(const Vector<EventContext>& ancestors)
     177{
     178    return ancestors.isEmpty() || ancestors.first().node() == ancestors.last().node();
     179}
     180
    125181// FIXME: Once https://bugs.webkit.org/show_bug.cgi?id=52963 lands, this should
    126182// be greatly improved. See https://bugs.webkit.org/show_bug.cgi?id=54025.
    127 PassRefPtr<EventTarget> EventDispatcher::adjustRelatedTarget(PassRefPtr<EventTarget> relatedTarget)
    128 {
     183PassRefPtr<EventTarget> EventDispatcher::adjustRelatedTarget(Event* event, PassRefPtr<EventTarget> prpRelatedTarget)
     184{
     185    if (!prpRelatedTarget)
     186        return 0;
     187
     188    RefPtr<Node> relatedTarget = prpRelatedTarget->toNode();
    129189    if (!relatedTarget)
    130190        return 0;
    131191
    132     Node* node = relatedTarget->toNode();
    133     if (!node)
    134         return relatedTarget;
    135 
    136     Node* outermostShadowBoundary = node;
    137     for (Node* n = node; n; n = n->parentOrHostNode()) {
     192    Node* target = m_node.get();
     193    if (!target)
     194        return prpRelatedTarget;
     195
     196    ensureEventAncestors(event);
     197
     198    // Calculate early if the common boundary is even possible by looking at
     199    // ancestors size and if the retargeting has occured (indicating the presence of shadow DOM boundaries).
     200    // If there are no boundaries detected, the target and related target can't have a common boundary.
     201    bool noCommonBoundary = ancestorsCrossShadowBoundaries(m_ancestors);
     202
     203    Vector<Node*> relatedTargetAncestors;
     204    Node* outermostShadowBoundary = relatedTarget.get();
     205    for (Node* n = outermostShadowBoundary; n; n = n->parentOrHostNode()) {
    138206        if (n->isShadowRoot())
    139207            outermostShadowBoundary = n->parentOrHostNode();
    140     }
    141     return outermostShadowBoundary;
     208        if (!noCommonBoundary)
     209            relatedTargetAncestors.append(n);
     210    }
     211
     212    // Short-circuit the fast case when we know there is no need to calculate a common boundary.
     213    if (noCommonBoundary)
     214        return outermostShadowBoundary;
     215
     216    return adjustToShadowBoundaries(relatedTarget.release(), relatedTargetAncestors);
    142217}
    143218
    144219EventDispatcher::EventDispatcher(Node* node)
    145220    : m_node(node)
     221    , m_ancestorsInitialized(false)
    146222{
    147223    ASSERT(node);
     
    149225}
    150226
    151 void EventDispatcher::getEventAncestors(EventTarget* originalTarget, EventDispatchBehavior behavior)
    152 {
     227void EventDispatcher::ensureEventAncestors(Event* event)
     228{
     229    EventDispatchBehavior behavior = determineDispatchBehavior(event);
     230
    153231    if (!m_node->inDocument())
    154232        return;
    155233
    156     if (ancestorsInitialized())
     234    if (m_ancestorsInitialized)
    157235        return;
    158236
    159     EventTarget* target = originalTarget;
     237    m_ancestorsInitialized = true;
     238
    160239    Node* ancestor = m_node.get();
     240    EventTarget* target = eventTargetRespectingSVGTargetRules(ancestor);
    161241    bool shouldSkipNextAncestor = false;
    162242    while (true) {
     
    193273
    194274    RefPtr<EventTarget> originalTarget = event->target();
    195     getEventAncestors(originalTarget.get(), determineDispatchBehavior(event.get()));
     275    ensureEventAncestors(event.get());
    196276
    197277    WindowEventContext windowContext(event.get(), m_node.get(), topEventContext());
     
    277357}
    278358
    279 
    280359const EventContext* EventDispatcher::topEventContext()
    281360{
    282361    return m_ancestors.isEmpty() ? 0 : &m_ancestors.last();
    283 }
    284 
    285 bool EventDispatcher::ancestorsInitialized() const
    286 {
    287     return m_ancestors.size();
    288362}
    289363
  • trunk/Source/WebCore/dom/EventDispatcher.h

    r83298 r83386  
    5555
    5656    bool dispatchEvent(PassRefPtr<Event>);
    57     PassRefPtr<EventTarget> adjustRelatedTarget(PassRefPtr<EventTarget>);
     57    PassRefPtr<EventTarget> adjustRelatedTarget(Event*, PassRefPtr<EventTarget>);
    5858    Node* node() const;
    5959
     
    6161    EventDispatcher(Node*);
    6262
     63    PassRefPtr<EventTarget> adjustToShadowBoundaries(PassRefPtr<Node> relatedTarget, const Vector<Node*> relatedTargetAncestors);
    6364    EventDispatchBehavior determineDispatchBehavior(Event*);
    64     void getEventAncestors(EventTarget* originalTarget, EventDispatchBehavior);
     65    void ensureEventAncestors(Event*);
    6566    const EventContext* topEventContext();
    66     bool ancestorsInitialized() const;
    6767
    6868    Vector<EventContext> m_ancestors;
     
    7070    RefPtr<EventTarget> m_originalTarget;
    7171    RefPtr<FrameView> m_view;
     72    bool m_ancestorsInitialized;
    7273};
    7374
  • trunk/Source/WebCore/dom/MouseEvent.cpp

    r82948 r83386  
    171171        return false; // Shouldn't happen.
    172172
    173     RefPtr<EventTarget> relatedTarget = dispatcher->adjustRelatedTarget(event()->relatedTarget());
     173    RefPtr<EventTarget> relatedTarget = dispatcher->adjustRelatedTarget(event(), event()->relatedTarget());
    174174    event()->setRelatedTarget(relatedTarget);
    175175
Note: See TracChangeset for help on using the changeset viewer.