Skip to content

Commit 0a3cab8

Browse files
committed
Fix #10489. Disconnected elements don't bubble to document.
1 parent 7bef99e commit 0a3cab8

File tree

2 files changed

+65
-36
lines changed

2 files changed

+65
-36
lines changed

src/event.js

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -261,17 +261,7 @@ jQuery.event = {
261261
// Event object or event type
262262
var type = event.type || event,
263263
namespaces = [],
264-
cache, exclusive, i, cur, old, ontype, special, doc, eventPath, bubbleType,
265-
addHandlers = function( elem, type ) {
266-
// Defer getting handler so we don't waste time in case propagation is stopped
267-
if ( (jQuery._data( elem, "events" ) || {})[ type ] ) {
268-
eventPath.push({ elem: elem, type: type /*, handler: jQuery._data( elem, "handle" ) */ });
269-
}
270-
// IE doesn't like method names with a colon (#3533, #8272)
271-
if ( ontype && jQuery.acceptData( elem ) && elem[ ontype ] ) {
272-
eventPath.push({ elem: elem, type: type, handler: elem[ ontype ] });
273-
}
274-
};
264+
cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;
275265

276266
if ( type.indexOf( "!" ) >= 0 ) {
277267
// Exclusive events trigger only for the exact event (no namespaces)
@@ -310,14 +300,14 @@ jQuery.event = {
310300
// triggerHandler() and global events don't bubble or run the default action
311301
if ( onlyHandlers || !elem ) {
312302
event.preventDefault();
313-
event.stopPropagation();
314303
}
315304

316305
// Handle a global trigger
317306
if ( !elem ) {
318307

319308
// TODO: Stop taunting the data cache; remove global events and always attach to document
320309
cache = jQuery.cache;
310+
event.stopPropagation();
321311
for ( i in cache ) {
322312
if ( cache[ i ].events && cache[ i ].events[ type ] ) {
323313
jQuery.event.trigger( event, data, cache[ i ].handle.elem );
@@ -344,23 +334,37 @@ jQuery.event = {
344334

345335
// Determine event propagation path in advance, per W3C events spec (#9951)
346336
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
347-
// Always fire handlers for the target, even if prop is stopped in advance
348-
eventPath = [];
349-
addHandlers( elem, special.bindType || type );
350-
doc = elem.ownerDocument;
351-
if ( doc && !special.noBubble && !jQuery.isWindow( elem ) & !event.isPropagationStopped() ) {
337+
eventPath = [[ elem, special.bindType || type ]];
338+
if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
339+
352340
bubbleType = special.delegateType || type;
341+
old = null;
353342
for ( cur = elem.parentNode; cur; cur = cur.parentNode ) {
354-
addHandlers( cur, bubbleType );
343+
eventPath.push([ cur, bubbleType ]);
344+
old = cur;
345+
}
346+
347+
// Only add window if we got to document (e.g., not plain obj or detached DOM)
348+
if ( old && old === elem.ownerDocument ) {
349+
eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
355350
}
356-
addHandlers( doc.defaultView || doc.parentWindow || window, bubbleType );
357351
}
358352

359-
// Bubble up the DOM tree
353+
// Fire handlers on the event path
360354
for ( i = 0; i < eventPath.length; i++ ) {
361-
cur = eventPath[ i ];
362-
event.type = cur.type;
363-
( cur.handler || jQuery._data( cur.elem, "handle" ) ).apply( cur.elem, data );
355+
356+
cur = eventPath[i][0];
357+
event.type = eventPath[i][1];
358+
359+
handle = (jQuery._data( cur, "events" ) || {})[ event.type ] && jQuery._data( cur, "handle" );
360+
if ( handle ) {
361+
handle.apply( cur, data );
362+
}
363+
handle = ontype && cur[ ontype ];
364+
if ( handle && jQuery.acceptData( cur ) ) {
365+
handle.apply( cur, data );
366+
}
367+
364368
if ( event.isPropagationStopped() ) {
365369
break;
366370
}

test/unit/event.js

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,30 +34,31 @@ test("bind(),live(),delegate() with non-null,defined data", function() {
3434

3535
});
3636

37-
/*
38-
Removed because Chrome 13 snaps/crashes on this 2011-09-07
39-
4037
test("Handler changes and .trigger() order", function() {
4138
expect(1);
4239

4340
var markup = jQuery(
44-
'<div><p><b class="a">b</b></p></div>'
45-
).appendTo( "body" );
41+
'<div><div><p><span><b class="a">b</b></span></p></div></div>'
42+
),
43+
path = "";
4644

47-
var path = "";
48-
jQuery( "b" ).parents().bind( "click", function(e){
49-
path += this.nodeName.toLowerCase() + " ";
50-
// Should not change the event triggering order
51-
$(this).parent().remove();
52-
});
45+
markup
46+
.find( "*" ).andSelf().on( "click", function( e ) {
47+
path += this.nodeName.toLowerCase() + " ";
48+
})
49+
.filter( "b" ).on( "click", function( e ) {
50+
// Removing span should not stop propagation to original parents
51+
if ( e.target === this ) {
52+
jQuery(this).parent().remove();
53+
}
54+
});
5355

5456
markup.find( "b" ).trigger( "click" );
5557

56-
equals( path, "p div body html ", "Delivered all events" )
58+
equals( path, "b p div div ", "Delivered all events" );
5759

5860
markup.remove();
5961
});
60-
*/
6162

6263
test("bind(), with data", function() {
6364
expect(4);
@@ -1110,6 +1111,30 @@ test("trigger(eventObject, [data], [fn])", function() {
11101111
$parent.unbind().remove();
11111112
});
11121113

1114+
test(".trigger() bubbling on disconnected elements (#10489)", function() {
1115+
expect(2);
1116+
1117+
jQuery( window ).on( "click", function(){
1118+
ok( false, "click fired on window" );
1119+
});
1120+
1121+
jQuery( "<div><p>hi</p></div>" )
1122+
.on( "click", function() {
1123+
ok( true, "click fired on div" );
1124+
})
1125+
.find( "p" )
1126+
.on( "click", function() {
1127+
ok( true, "click fired on p" );
1128+
})
1129+
.click()
1130+
.off( "click" )
1131+
.end()
1132+
.off( "click" )
1133+
.remove();
1134+
1135+
jQuery( window ).off( "click" );
1136+
});
1137+
11131138
test("jQuery.Event( type, props )", function() {
11141139

11151140
expect(5);

0 commit comments

Comments
 (0)