]> source.dussan.org Git - jquery.git/commitdiff
Event: Make trigger(focus/blur/click) work with native handlers
authorMichał Gołębiowski-Owczarek <m.goleb@gmail.com>
Mon, 27 Mar 2023 19:47:01 +0000 (21:47 +0200)
committerMichał Gołębiowski-Owczarek <m.goleb@gmail.com>
Mon, 27 Mar 2023 19:47:24 +0000 (21:47 +0200)
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 6ad3651dbfea9e9bb56e608f72b4ef2f97bd4e70)

src/event.js
test/unit/event.js

index 1d5c495d5b31eb9344c19640262af0623d6a4dd9..0b364d6d9205984e214d77991d2a7faf0adc29ed 100644 (file)
@@ -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.
@@ -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;
                        }
                }
        } );
index c152bf6c0d79094c39693b7efc2598ec0894f906..9fbfa6468d7d9abe98e452ef6d67ac46a7e8e050 100644 (file)
@@ -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() {
@@ -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 );
@@ -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" );
+               } );
+       }
 } )();