Mouseenter and mouseleave events not supported

https://bugs.webkit.org/show_bug.cgi?id=18930

Reviewed by David Hyatt.

Source/WebCore:

Implements mouseenter and mouseleave events from W3C DOM Level 3 Events.
These event are already supported by all other major browsers.

To avoid performance regressions the new events are only dispatched when
there are event listeners for them.

Tests: fast/events/mouseenter-mouseleave-capture.html
       fast/events/mouseenter-mouseleave.html

* bindings/scripts/CodeGenerator.pm:
* dom/Document.cpp:
(WebCore::Document::prepareMouseEvent):
(WebCore::Document::updateHoverActiveState):
* dom/Document.h:
(Document):
* dom/Document.idl:
* dom/Element.h:
(Element):
* dom/Element.idl:
* dom/EventListenerMap.cpp:
(WebCore::EventListenerMap::containsCapturing):
* dom/EventListenerMap.h:
(EventListenerMap):
* dom/EventNames.h:
* dom/EventTarget.h:
(EventTarget):
(WebCore::EventTarget::hasCapturingEventListeners):
* dom/MouseEvent.cpp:
(WebCore::MouseEvent::create):
(WebCore::MouseEvent::toElement):
(WebCore::MouseEvent::fromElement):
* html/HTMLAttributeNames.in:
* html/HTMLElement.cpp:
(WebCore::HTMLElement::eventNameForAttributeName):
* page/DOMWindow.h:
(DOMWindow):
* page/DOMWindow.idl:
* svg/SVGElement.cpp:
(WebCore::SVGElement::parseAttribute):
* svg/SVGElementInstance.h:
(SVGElementInstance):
* svg/SVGElementInstance.idl:

LayoutTests:

To new tests that mouseenter and mouseleave works in both bubbling and capture phase.

* fast/events/mouseenter-mouseleave-capture-expected.txt: Added.
* fast/events/mouseenter-mouseleave-capture.html: Added.
* fast/events/mouseenter-mouseleave-expected.txt: Added.
* fast/events/mouseenter-mouseleave.html: Added.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@149173 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 361046d0
2013-04-26 Allan Sandfeld Jensen <allan.jensen@digia.com>
Mouseenter and mouseleave events not supported
https://bugs.webkit.org/show_bug.cgi?id=18930
Reviewed by David Hyatt.
To new tests that mouseenter and mouseleave works in both bubbling and capture phase.
* fast/events/mouseenter-mouseleave-capture-expected.txt: Added.
* fast/events/mouseenter-mouseleave-capture.html: Added.
* fast/events/mouseenter-mouseleave-expected.txt: Added.
* fast/events/mouseenter-mouseleave.html: Added.
2013-04-26 Christophe Dumez <ch.dumez@sisa.samsung.com>
[Qt] REGRESSION(r149001): It made two fast/dom/DeviceMotion tests fail.
......
mouseenter on outer1
mouseenter on inner1
mouseleave on inner1
mouseenter on inner3
mouseenter on inner2
mouseleave on inner3
mouseleave on inner2
mouseleave on outer1
<!DOCTYPE html>
<html>
<head>
<script>
function log(message) {
document.getElementById('console').innerHTML += (message + "\n");
}
function logMouseEvent(ev) {
var target = (ev.target)? ev.target : ev.srcElement;
if (target.id) {
log(ev.type + " on " + target.id);
}
}
function doTest() {
document.body.offsetLeft;
document.body.addEventListener('mouseenter',logMouseEvent,true);
document.body.addEventListener('mouseleave',logMouseEvent,true);
if (window.testRunner) {
eventSender.mouseMoveTo(1, 1);
eventSender.mouseMoveTo(90,140);
eventSender.mouseMoveTo(110,140);
eventSender.mouseMoveTo(130,140);
eventSender.mouseMoveTo(170,140);
eventSender.mouseMoveTo(180,140);
eventSender.mouseMoveTo(210,140);
testRunner.dumpAsText();
}
}
</script>
</head>
<body onload="doTest()">
<div id="outer1" style="width:100px; height:100px; background-color:blue; top:100px; left:100px; position:absolute">
<div id="inner1" style="width:50px; height:50px; background-color:red; top:20px; left:20px; position:absolute">
</div>
<div id="inner2" style="width:20px; height:20px; background-color:yellow; top:30px; left:60px; position:absolute">
<div id="inner3" style="margin:5px; width:10px; height:10px; background-color:green;">
</div>
</div>
</div>
<pre id="console"></pre>
</body>
</html>
mouseover on outer1
mouseout on outer1
mouseover on inner1
mouseover on inner1
mouseout on inner1
mouseout on inner1
mouseover on inner3
mouseover on inner3
mouseover on inner3
mouseout on inner3
mouseout on inner3
mouseout on inner3
mouseover on outer1
mouseout on outer1
mouseenter on outer1
mouseenter on inner1
mouseleave on inner1
mouseenter on inner3
mouseenter on inner2
mouseleave on inner3
mouseleave on inner2
mouseleave on outer1
<!DOCTYPE html>
<html>
<head>
<script>
function log(message, console) {
document.getElementById(console).innerHTML += (message + "\n");
}
function logMouseOverOutEvent(ev) {
var target = (ev.target)? ev.target : ev.srcElement;
log(ev.type + " on " + target.id, 'console1');
}
function logMouseEnterLeaveEvent(ev) {
var target = (ev.target)? ev.target : ev.srcElement;
log(ev.type + " on " + target.id, 'console2');
}
function doTest() {
document.body.offsetLeft;
if (window.testRunner) {
eventSender.mouseMoveTo(1, 1);
eventSender.mouseMoveTo(90,140);
eventSender.mouseMoveTo(110,140);
eventSender.mouseMoveTo(130,140);
eventSender.mouseMoveTo(170,140);
eventSender.mouseMoveTo(180,140);
eventSender.mouseMoveTo(210,140);
testRunner.dumpAsText();
}
}
</script>
</head>
<body onload="doTest()">
<div id="outer1" style="width:100px; height:100px; background-color:blue; top:100px; left:100px; position:absolute"
onMouseOver="logMouseOverOutEvent(event)" onMouseOut="logMouseOverOutEvent(event)"
onMouseEnter="logMouseEnterLeaveEvent(event)" onMouseLeave="logMouseEnterLeaveEvent(event)">
<div id="inner1" style="width:50px; height:50px; background-color:red; top:20px; left:20px; position:absolute"
onMouseOver="logMouseOverOutEvent(event)" onMouseOut="logMouseOverOutEvent(event)"
onMouseEnter="logMouseEnterLeaveEvent(event)" onMouseLeave="logMouseEnterLeaveEvent(event)">
</div>
<div id="inner2" style="width:20px; height:20px; background-color:yellow; top:30px; left:60px; position:absolute"
onMouseOver="logMouseOverOutEvent(event)" onMouseOut="logMouseOverOutEvent(event)"
onMouseEnter="logMouseEnterLeaveEvent(event)" onMouseLeave="logMouseEnterLeaveEvent(event)">
<div id="inner3" style="margin:5px; width:10px; height:10px; background-color:green;"
onMouseOver="logMouseOverOutEvent(event)" onMouseOut="logMouseOverOutEvent(event)"
onMouseEnter="logMouseEnterLeaveEvent(event)" onMouseLeave="logMouseEnterLeaveEvent(event)">
</div>
</div>
</div>
<pre id="console1"></pre>
<br>
<pre id="console2"></pre>
</body>
</html>
2013-04-26 Allan Sandfeld Jensen <allan.jensen@digia.com>
Mouseenter and mouseleave events not supported
https://bugs.webkit.org/show_bug.cgi?id=18930
Reviewed by David Hyatt.
Implements mouseenter and mouseleave events from W3C DOM Level 3 Events.
These event are already supported by all other major browsers.
To avoid performance regressions the new events are only dispatched when
there are event listeners for them.
Tests: fast/events/mouseenter-mouseleave-capture.html
fast/events/mouseenter-mouseleave.html
* bindings/scripts/CodeGenerator.pm:
* dom/Document.cpp:
(WebCore::Document::prepareMouseEvent):
(WebCore::Document::updateHoverActiveState):
* dom/Document.h:
(Document):
* dom/Document.idl:
* dom/Element.h:
(Element):
* dom/Element.idl:
* dom/EventListenerMap.cpp:
(WebCore::EventListenerMap::containsCapturing):
* dom/EventListenerMap.h:
(EventListenerMap):
* dom/EventNames.h:
* dom/EventTarget.h:
(EventTarget):
(WebCore::EventTarget::hasCapturingEventListeners):
* dom/MouseEvent.cpp:
(WebCore::MouseEvent::create):
(WebCore::MouseEvent::toElement):
(WebCore::MouseEvent::fromElement):
* html/HTMLAttributeNames.in:
* html/HTMLElement.cpp:
(WebCore::HTMLElement::eventNameForAttributeName):
* page/DOMWindow.h:
(DOMWindow):
* page/DOMWindow.idl:
* svg/SVGElement.cpp:
(WebCore::SVGElement::parseAttribute):
* svg/SVGElementInstance.h:
(SVGElementInstance):
* svg/SVGElementInstance.idl:
2013-04-26 Christophe Dumez <ch.dumez@sisa.samsung.com>
Add support for Web IDL partial interfaces to the bindings generator
......
......@@ -67,6 +67,7 @@ my %svgAnimatedTypeHash = ("SVGAnimatedAngle" => 1, "SVGAnimatedBoolean" => 1,
my %svgAttributesInHTMLHash = ("class" => 1, "id" => 1, "onabort" => 1, "onclick" => 1,
"onerror" => 1, "onload" => 1, "onmousedown" => 1,
"onmouseenter" => 1, "onmouseleave" => 1,
"onmousemove" => 1, "onmouseout" => 1, "onmouseover" => 1,
"onmouseup" => 1, "onresize" => 1, "onscroll" => 1,
"onunload" => 1);
......
......@@ -3051,7 +3051,7 @@ MouseEventWithHitTestResults Document::prepareMouseEvent(const HitTestRequest& r
renderView()->hitTest(request, result);
if (!request.readOnly())
updateHoverActiveState(request, result.innerElement());
updateHoverActiveState(request, result.innerElement(), &event);
return MouseEventWithHitTestResults(event, result);
}
......@@ -5901,7 +5901,7 @@ static RenderObject* nearestCommonHoverAncestor(RenderObject* obj1, RenderObject
return 0;
}
void Document::updateHoverActiveState(const HitTestRequest& request, Element* innerElement)
void Document::updateHoverActiveState(const HitTestRequest& request, Element* innerElement, const PlatformMouseEvent* event)
{
ASSERT(!request.readOnly());
......@@ -5970,6 +5970,26 @@ void Document::updateHoverActiveState(const HitTestRequest& request, Element* in
Vector<RefPtr<Node>, 32> nodesToRemoveFromChain;
Vector<RefPtr<Node>, 32> nodesToAddToChain;
// mouseenter and mouseleave events are only dispatched if there is a capturing eventhandler on an ancestor
// or a normal eventhandler on the element itself (they don't bubble).
// This optimization is necessary since these events can cause O(n²) capturing event-handler checks.
bool hasCapturingMouseEnterListener = false;
bool hasCapturingMouseLeaveListener = false;
if (event && newHoverNode != oldHoverNode.get()) {
for (Node* curr = newHoverNode; curr; curr = curr->parentOrShadowHostNode()) {
if (curr->hasCapturingEventListeners(eventNames().mouseenterEvent)) {
hasCapturingMouseEnterListener = true;
break;
}
}
for (Node* curr = oldHoverNode.get(); curr; curr = curr->parentOrShadowHostNode()) {
if (curr->hasCapturingEventListeners(eventNames().mouseleaveEvent)) {
hasCapturingMouseLeaveListener = true;
break;
}
}
}
if (oldHoverObj != newHoverObj) {
// The old hover path only needs to be cleared up to (and not including) the common ancestor;
for (RenderObject* curr = oldHoverObj; curr && curr != ancestor; curr = curr->hoverAncestor()) {
......@@ -5990,14 +6010,25 @@ void Document::updateHoverActiveState(const HitTestRequest& request, Element* in
}
size_t removeCount = nodesToRemoveFromChain.size();
for (size_t i = 0; i < removeCount; ++i)
for (size_t i = 0; i < removeCount; ++i) {
nodesToRemoveFromChain[i]->setHovered(false);
if (event && (hasCapturingMouseLeaveListener || nodesToRemoveFromChain[i]->hasEventListeners(eventNames().mouseleaveEvent)))
nodesToRemoveFromChain[i]->dispatchMouseEvent(*event, eventNames().mouseleaveEvent, 0, newHoverNode);
}
bool sawCommonAncestor = false;
size_t addCount = nodesToAddToChain.size();
for (size_t i = 0; i < addCount; ++i) {
if (allowActiveChanges)
nodesToAddToChain[i]->setActive(true);
nodesToAddToChain[i]->setHovered(true);
if (ancestor && nodesToAddToChain[i] == ancestor->node())
sawCommonAncestor = true;
if (!sawCommonAncestor) {
// Elements after the common hover ancestor does not change hover state, but are iterated over because they may change active state.
nodesToAddToChain[i]->setHovered(true);
if (event && (hasCapturingMouseEnterListener || nodesToAddToChain[i]->hasEventListeners(eventNames().mouseenterEvent)))
nodesToAddToChain[i]->dispatchMouseEvent(*event, eventNames().mouseenterEvent, 0, oldHoverNode.get());
}
}
updateStyleIfNeeded();
......
......@@ -265,6 +265,8 @@ public:
DEFINE_ATTRIBUTE_EVENT_LISTENER(keypress);
DEFINE_ATTRIBUTE_EVENT_LISTENER(keyup);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mousedown);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseenter);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseleave);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mousemove);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseout);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseover);
......@@ -698,7 +700,7 @@ public:
void hoveredNodeDetached(Node*);
void activeChainNodeDetached(Node*);
void updateHoverActiveState(const HitTestRequest&, Element*);
void updateHoverActiveState(const HitTestRequest&, Element*, const PlatformMouseEvent* = 0);
// Updates for :target (CSS3 selector).
void setCSSTarget(Element*);
......
......@@ -290,6 +290,8 @@
[NotEnumerable] attribute EventListener onkeyup;
[NotEnumerable] attribute EventListener onload;
[NotEnumerable] attribute EventListener onmousedown;
[NotEnumerable] attribute EventListener onmouseenter;
[NotEnumerable] attribute EventListener onmouseleave;
[NotEnumerable] attribute EventListener onmousemove;
[NotEnumerable] attribute EventListener onmouseout;
[NotEnumerable] attribute EventListener onmouseover;
......
......@@ -199,6 +199,8 @@ public:
DEFINE_ATTRIBUTE_EVENT_LISTENER(keypress);
DEFINE_ATTRIBUTE_EVENT_LISTENER(keyup);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mousedown);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseenter);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseleave);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mousemove);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseout);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseover);
......
......@@ -182,6 +182,8 @@
[NotEnumerable] attribute EventListener onkeyup;
[NotEnumerable] attribute EventListener onload;
[NotEnumerable] attribute EventListener onmousedown;
[NotEnumerable] attribute EventListener onmouseenter;
[NotEnumerable] attribute EventListener onmouseleave;
[NotEnumerable] attribute EventListener onmousemove;
[NotEnumerable] attribute EventListener onmouseout;
[NotEnumerable] attribute EventListener onmouseover;
......
......@@ -78,6 +78,20 @@ bool EventListenerMap::contains(const AtomicString& eventType) const
return false;
}
bool EventListenerMap::containsCapturing(const AtomicString& eventType) const
{
for (unsigned i = 0; i < m_entries.size(); ++i) {
if (m_entries[i].first == eventType) {
const EventListenerVector* vector = m_entries[i].second.get();
for (unsigned j = 0; j < vector->size(); ++j) {
if (vector->at(j).useCapture)
return true;
}
}
}
return false;
}
void EventListenerMap::clear()
{
assertNoActiveIterators();
......
......@@ -50,6 +50,7 @@ public:
bool isEmpty() const { return m_entries.isEmpty(); }
bool contains(const AtomicString& eventType) const;
bool containsCapturing(const AtomicString& eventType) const;
void clear();
bool add(const AtomicString& eventType, PassRefPtr<EventListener>, bool useCapture);
......
......@@ -89,6 +89,8 @@ namespace WebCore {
macro(loadstart) \
macro(message) \
macro(mousedown) \
macro(mouseenter) \
macro(mouseleave) \
macro(mousemove) \
macro(mouseout) \
macro(mouseover) \
......
......@@ -126,6 +126,7 @@ namespace WebCore {
bool hasEventListeners();
bool hasEventListeners(const AtomicString& eventType);
bool hasCapturingEventListeners(const AtomicString& eventType);
const EventListenerVector& getEventListeners(const AtomicString& eventType);
bool fireEventListeners(Event*);
......@@ -206,6 +207,14 @@ namespace WebCore {
return d->eventListenerMap.contains(eventType);
}
inline bool EventTarget::hasCapturingEventListeners(const AtomicString& eventType)
{
EventTargetData* d = eventTargetData();
if (!d)
return false;
return d->eventListenerMap.containsCapturing(eventType);
}
} // namespace WebCore
#endif // EventTarget_h
......@@ -57,9 +57,11 @@ PassRefPtr<MouseEvent> MouseEvent::create(const AtomicString& eventType, PassRef
{
ASSERT(event.type() == PlatformEvent::MouseMoved || event.button() != NoButton);
bool isCancelable = eventType != eventNames().mousemoveEvent;
bool isMouseEnterOrLeave = eventType == eventNames().mouseenterEvent || eventType == eventNames().mouseleaveEvent;
bool isCancelable = eventType != eventNames().mousemoveEvent && !isMouseEnterOrLeave;
bool canBubble = !isMouseEnterOrLeave;
return MouseEvent::create(eventType, true, isCancelable, view,
return MouseEvent::create(eventType, canBubble, isCancelable, view,
detail, event.globalPosition().x(), event.globalPosition().y(), event.position().x(), event.position().y(),
#if ENABLE(POINTER_LOCK)
event.movementDelta().x(), event.movementDelta().y(),
......@@ -202,7 +204,7 @@ int MouseEvent::which() const
Node* MouseEvent::toElement() const
{
// MSIE extension - "the object toward which the user is moving the mouse pointer"
if (type() == eventNames().mouseoutEvent)
if (type() == eventNames().mouseoutEvent || type() == eventNames().mouseleaveEvent)
return relatedTarget() ? relatedTarget()->toNode() : 0;
return target() ? target()->toNode() : 0;
......@@ -211,7 +213,7 @@ Node* MouseEvent::toElement() const
Node* MouseEvent::fromElement() const
{
// MSIE extension - "object from which activation or the mouse pointer is exiting during the event" (huh?)
if (type() != eventNames().mouseoutEvent)
if (type() != eventNames().mouseoutEvent && type() != eventNames().mouseleaveEvent)
return relatedTarget() ? relatedTarget()->toNode() : 0;
return target() ? target()->toNode() : 0;
......
......@@ -208,6 +208,8 @@ onloadeddata
onloadedmetadata
onloadstart
onmousedown
onmouseenter
onmouseleave
onmousemove
onmouseout
onmouseover
......
......@@ -226,6 +226,8 @@ AtomicString HTMLElement::eventNameForAttributeName(const QualifiedName& attrNam
attributeNameToEventNameMap.set(oncontextmenuAttr.localName(), eventNames().contextmenuEvent);
attributeNameToEventNameMap.set(ondblclickAttr.localName(), eventNames().dblclickEvent);
attributeNameToEventNameMap.set(onmousedownAttr.localName(), eventNames().mousedownEvent);
attributeNameToEventNameMap.set(onmouseenterAttr.localName(), eventNames().mouseenterEvent);
attributeNameToEventNameMap.set(onmouseleaveAttr.localName(), eventNames().mouseleaveEvent);
attributeNameToEventNameMap.set(onmousemoveAttr.localName(), eventNames().mousemoveEvent);
attributeNameToEventNameMap.set(onmouseoutAttr.localName(), eventNames().mouseoutEvent);
attributeNameToEventNameMap.set(onmouseoverAttr.localName(), eventNames().mouseoverEvent);
......
......@@ -316,6 +316,8 @@ namespace WebCore {
DEFINE_ATTRIBUTE_EVENT_LISTENER(loadstart);
DEFINE_ATTRIBUTE_EVENT_LISTENER(message);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mousedown);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseenter);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseleave);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mousemove);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseout);
DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseover);
......
......@@ -246,6 +246,8 @@
attribute EventListener onloadstart;
attribute EventListener onmessage;
attribute EventListener onmousedown;
attribute EventListener onmouseenter;
attribute EventListener onmouseleave;
attribute EventListener onmousemove;
attribute EventListener onmouseout;
attribute EventListener onmouseover;
......
......@@ -317,6 +317,10 @@ void SVGElement::parseAttribute(const QualifiedName& name, const AtomicString& v
setAttributeEventListener(eventNames().clickEvent, createAttributeEventListener(this, name, value));
else if (name == onmousedownAttr)
setAttributeEventListener(eventNames().mousedownEvent, createAttributeEventListener(this, name, value));
else if (name == onmouseenterAttr)
setAttributeEventListener(eventNames().mouseenterEvent, createAttributeEventListener(this, name, value));
else if (name == onmouseleaveAttr)
setAttributeEventListener(eventNames().mouseleaveEvent, createAttributeEventListener(this, name, value));
else if (name == onmousemoveAttr)
setAttributeEventListener(eventNames().mousemoveEvent, createAttributeEventListener(this, name, value));
else if (name == onmouseoutAttr)
......
......@@ -116,6 +116,8 @@ public:
DEFINE_FORWARDING_ATTRIBUTE_EVENT_LISTENER(correspondingElement(), keyup);
DEFINE_FORWARDING_ATTRIBUTE_EVENT_LISTENER(correspondingElement(), load);
DEFINE_FORWARDING_ATTRIBUTE_EVENT_LISTENER(correspondingElement(), mousedown);
DEFINE_FORWARDING_ATTRIBUTE_EVENT_LISTENER(correspondingElement(), mouseenter);
DEFINE_FORWARDING_ATTRIBUTE_EVENT_LISTENER(correspondingElement(), mouseleave);
DEFINE_FORWARDING_ATTRIBUTE_EVENT_LISTENER(correspondingElement(), mousemove);
DEFINE_FORWARDING_ATTRIBUTE_EVENT_LISTENER(correspondingElement(), mouseout);
DEFINE_FORWARDING_ATTRIBUTE_EVENT_LISTENER(correspondingElement(), mouseover);
......
......@@ -62,6 +62,8 @@
attribute [NotEnumerable] EventListener onkeyup;
attribute [NotEnumerable] EventListener onload;
attribute [NotEnumerable] EventListener onmousedown;
attribute [NotEnumerable] EventListener onmouseenter;
attribute [NotEnumerable] EventListener onmouseleave;
attribute [NotEnumerable] EventListener onmousemove;
attribute [NotEnumerable] EventListener onmouseout;
attribute [NotEnumerable] EventListener onmouseover;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment