Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

fix(jqLite): change implementation of mouseenter/mouseleave event #2383

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 34 additions & 14 deletions src/jqLite.js
Original file line number Diff line number Diff line change
Expand Up @@ -607,23 +607,43 @@ forEach({

if (!eventFns) {
if (type == 'mouseenter' || type == 'mouseleave') {
var counter = 0;
var contains = document.body.contains || document.body.compareDocumentPosition ?
function( a, b ) {
var adown = a.nodeType === 9 ? a.documentElement : a,
bup = b && b.parentNode;
return a === bup || !!( bup && bup.nodeType === 1 && (
adown.contains ?
adown.contains( bup ) :
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
));
} :
function( a, b ) {
if ( b ) {
while ( (b = b.parentNode) ) {
if ( b === a ) {
return true;
}
}
}
return false;
};

events.mouseenter = [];
events.mouseleave = [];
events[type] = [];

// Refer to jQuery's implementation of mouseenter & mouseleave
// Read about mouseenter and mouseleave:
// http://www.quirksmode.org/js/events_mouse.html#link8
var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}
bindFn(element, eventmap[type], function(event) {
var ret, target = this, related = event.relatedTarget;
// For mousenter/leave call the handler if related is outside the target.
// NB: No relatedTarget if the mouse left/entered the browser window
if ( !related || (related !== target && !contains(target, related)) ){
handle(event, type);
}

bindFn(element, 'mouseover', function(event) {
counter++;
if (counter == 1) {
handle(event, 'mouseenter');
}
});
bindFn(element, 'mouseout', function(event) {
counter --;
if (counter == 0) {
handle(event, 'mouseleave');
}
});

} else {
addEventListenerFn(element, type, handle);
events[type] = [];
Expand Down
49 changes: 37 additions & 12 deletions test/jqLiteSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -775,13 +775,9 @@ describe('jqLite', function() {

parent.bind('mouseenter', function() { log += 'parentEnter;'; });
parent.bind('mouseleave', function() { log += 'parentLeave;'; });
parent.mouseover = function() { browserTrigger(parent, 'mouseover'); };
parent.mouseout = function() { browserTrigger(parent, 'mouseout'); };

child.bind('mouseenter', function() { log += 'childEnter;'; });
child.bind('mouseleave', function() { log += 'childLeave;'; });
child.mouseover = function() { browserTrigger(child, 'mouseover'); };
child.mouseout = function() { browserTrigger(child, 'mouseout'); };
});

afterEach(function() {
Expand All @@ -790,20 +786,49 @@ describe('jqLite', function() {

it('should fire mouseenter when coming from outside the browser window', function() {
if (window.jQuery) return;
parent.mouseover();
var browserMoveTrigger = function(from, to){
var fireEvent = function(type, element, relatedTarget){
var msie = parseInt((/msie (\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1]);
if (msie < 9){
var evnt = document.createEventObject();
evnt.srcElement = element;
evnt.relatedTarget = relatedTarget;
element.fireEvent('on' + type, evnt);
return;
};
var evnt = document.createEvent('MouseEvents'),
originalPreventDefault = evnt.preventDefault,
appWindow = window,
fakeProcessDefault = true,
finalProcessDefault;

evnt.preventDefault = function() {
fakeProcessDefault = false;
return originalPreventDefault.apply(evnt, arguments);
};

var x = 0, y = 0;
evnt.initMouseEvent(type, true, true, window, 0, x, y, x, y, false, false,
false, false, 0, relatedTarget);

element.dispatchEvent(evnt);
};
fireEvent('mouseout', from[0], to[0]);
fireEvent('mouseover', to[0], from[0]);
};

browserMoveTrigger(root, parent);
expect(log).toEqual('parentEnter;');

child.mouseover();
expect(log).toEqual('parentEnter;childEnter;');
child.mouseover();
browserMoveTrigger(parent, child);
expect(log).toEqual('parentEnter;childEnter;');

child.mouseout();
expect(log).toEqual('parentEnter;childEnter;');
child.mouseout();
browserMoveTrigger(child, parent);
expect(log).toEqual('parentEnter;childEnter;childLeave;');
parent.mouseout();

browserMoveTrigger(parent, root);
expect(log).toEqual('parentEnter;childEnter;childLeave;parentLeave;');

});
});
});
Expand Down