Skip to content

Commit

Permalink
Event: Make trigger(focus/blur/click) work with native handlers
Browse files Browse the repository at this point in the history
In `leverageNative`, instead of calling `event.stopImmediatePropagation()`
which would abort both native & jQuery handlers, set the wrapper's
`isImmediatePropagationStopped` property to a function returning `true`.
Since for each element + type pair jQuery attaches only one native handler,
there is also only one wrapper jQuery event so this achieves the goal:
on the target element jQuery handlers don't fire but native ones do.

Unfortunately, this workaround doesn't work for handlers on ancestors
- since the native event is re-wrapped by a jQuery one on each level of
the propagation, the only way to stop it for jQuery was to stop it for
everyone via native `stopPropagation()`. This is not a problem for
`focus`/`blur` which don't bubble, but it does also stop `click` on
checkboxes and radios. We accept this limitation.

Fixes gh-5015
Closes gh-5228

(cherry picked from commit 6ad3651)
  • Loading branch information
mgol committed Mar 27, 2023
1 parent 59f7b55 commit 754108f
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 6 deletions.
16 changes: 12 additions & 4 deletions src/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,8 @@ function leverageNative( el, type, isSetup ) {
}

// If this is an inner synthetic event for an event with a bubbling surrogate
// (focus or blur), assume that the surrogate already propagated from triggering the
// native event and prevent that from happening again here.
// (focus or blur), assume that the surrogate already propagated from triggering
// the native event and prevent that from happening again here.
// This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the
// bubbling surrogate propagates *after* the non-bubbling base), but that seems
// less bad than duplication.
Expand All @@ -579,8 +579,16 @@ function leverageNative( el, type, isSetup ) {
this
) );

// Abort handling of the native event
event.stopImmediatePropagation();
// Abort handling of the native event by all jQuery handlers while allowing
// native handlers on the same element to run. On target, this is achieved
// by stopping immediate propagation just on the jQuery event. However,
// the native event is re-wrapped by a jQuery one on each level of the
// propagation so the only way to stop it for jQuery is to stop it for
// everyone via native `stopPropagation()`. This is not a problem for
// focus/blur which don't bubble, but it does also stop click on checkboxes
// and radios. We accept this limitation.
event.stopPropagation();
event.isImmediatePropagationStopped = returnTrue;
}
}
} );
Expand Down
63 changes: 61 additions & 2 deletions test/unit/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -3412,6 +3412,36 @@ QUnit.test( "trigger(focus) works after focusing when hidden (gh-4950)", functio
assert.equal( document.activeElement, input[ 0 ], "input has focus" );
} );

QUnit.test( "trigger(focus) fires native & jQuery handlers (gh-5015)", function( assert ) {
assert.expect( 3 );

var input = jQuery( "<input />" ),

// Support: IE 9 - 11+
// focus is async in IE; we now emulate it via sync focusin in jQuery
// but this test also attaches native handlers.
done = assert.async( 3 );

input.appendTo( "#qunit-fixture" );

input[ 0 ].addEventListener( "focus", function() {
assert.ok( true, "1st native handler fired" );
done();
} );

input.on( "focus", function() {
assert.ok( true, "jQuery handler fired" );
done();
} );

input[ 0 ].addEventListener( "focus", function() {
assert.ok( true, "2nd native handler fired" );
done();
} );

input.trigger( "focus" );
} );

// TODO replace with an adaptation of
// https://github.com/jquery/jquery/pull/1367/files#diff-a215316abbaabdf71857809e8673ea28R2464
( function() {
Expand All @@ -3420,10 +3450,13 @@ QUnit.test( "trigger(focus) works after focusing when hidden (gh-4950)", functio
checkbox: "<input type='checkbox'>",
radio: "<input type='radio'>"
},
makeTestFor3751
function( type, html ) {
makeTestForGh3751( type, html );
makeTestForGh5015( type, html );
}
);

function makeTestFor3751( type, html ) {
function makeTestForGh3751( type, html ) {
var testName = "native-backed namespaced clicks are handled correctly (gh-3751) - " + type;
QUnit.test( testName, function( assert ) {
assert.expect( 2 );
Expand All @@ -3450,4 +3483,30 @@ QUnit.test( "trigger(focus) works after focusing when hidden (gh-4950)", functio
target.trigger( "click.fired" );
} );
}

function makeTestForGh5015( type, html ) {
var testName = "trigger(click) fires native & jQuery handlers (gh-5015) - " + type;
QUnit.test( testName, function( assert ) {
assert.expect( 3 );

var parent = supportjQuery( "<div class='parent'>" + html + "</div>" ),
input = jQuery( parent[ 0 ].firstChild );

parent.appendTo( "#qunit-fixture" );

input[ 0 ].addEventListener( "click", function() {
assert.ok( true, "1st native handler fired" );
} );

input.on( "click", function() {
assert.ok( true, "jQuery handler fired" );
} );

input[ 0 ].addEventListener( "click", function() {
assert.ok( true, "2nd native handler fired" );
} );

input.trigger( "click" );
} );
}
} )();

0 comments on commit 754108f

Please sign in to comment.