🌐 AI搜索 & 代理 主页
Skip to content

Commit fe5f04d

Browse files
gibson042mgol
authored andcommitted
Event: Prevent leverageNative from double-firing focusin
Also, reduce size. Closes gh-4329 Ref gh-4279
1 parent 753d591 commit fe5f04d

File tree

2 files changed

+85
-93
lines changed

2 files changed

+85
-93
lines changed

src/event.js

Lines changed: 84 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,19 @@ function returnFalse() {
2929
return false;
3030
}
3131

32+
// Support: IE <=9 - 11+
33+
// focus() and blur() are asynchronous, except when they are no-op.
34+
// So expect focus to be synchronous when the element is already active,
35+
// and blur to be synchronous when the element is not already active.
36+
// (focus and blur are always synchronous in other supported browsers,
37+
// this just defines when we can count on it).
38+
function expectSync( elem, type ) {
39+
return ( elem === safeActiveElement() ) === ( type === "focus" );
40+
}
41+
3242
// Support: IE <=9 only
33-
// See #13393 for more info
43+
// Accessing document.activeElement can throw unexpectedly
44+
// https://bugs.jquery.com/ticket/13393
3445
function safeActiveElement() {
3546
try {
3647
return document.activeElement;
@@ -457,56 +468,6 @@ jQuery.event = {
457468
// Prevent triggered image.load events from bubbling to window.load
458469
noBubble: true
459470
},
460-
focus: {
461-
462-
// Utilize native event if possible so blur/focus sequence is correct
463-
setup: function() {
464-
465-
// Claim the first handler
466-
// dataPriv.set( this, "focus", ... )
467-
leverageNative( this, "focus", false, function( el ) {
468-
return el !== safeActiveElement();
469-
} );
470-
471-
// Return false to allow normal processing in the caller
472-
return false;
473-
},
474-
trigger: function() {
475-
476-
// Force setup before trigger
477-
leverageNative( this, "focus", returnTrue );
478-
479-
// Return non-false to allow normal event-path propagation
480-
return true;
481-
},
482-
483-
delegateType: "focusin"
484-
},
485-
blur: {
486-
487-
// Utilize native event if possible so blur/focus sequence is correct
488-
setup: function() {
489-
490-
// Claim the first handler
491-
// dataPriv.set( this, "blur", ... )
492-
leverageNative( this, "blur", false, function( el ) {
493-
return el === safeActiveElement();
494-
} );
495-
496-
// Return false to allow normal processing in the caller
497-
return false;
498-
},
499-
trigger: function() {
500-
501-
// Force setup before trigger
502-
leverageNative( this, "blur", returnTrue );
503-
504-
// Return non-false to allow normal event-path propagation
505-
return true;
506-
},
507-
508-
delegateType: "focusout"
509-
},
510471
click: {
511472

512473
// Utilize native event to ensure correct state for checkable inputs
@@ -522,7 +483,7 @@ jQuery.event = {
522483
dataPriv.get( el, "click" ) === undefined ) {
523484

524485
// dataPriv.set( el, "click", ... )
525-
leverageNative( el, "click", false, returnFalse );
486+
leverageNative( el, "click", returnTrue );
526487
}
527488

528489
// Return false to allow normal processing in the caller
@@ -539,7 +500,7 @@ jQuery.event = {
539500
el.click && nodeName( el, "input" ) &&
540501
dataPriv.get( el, "click" ) === undefined ) {
541502

542-
leverageNative( el, "click", returnTrue );
503+
leverageNative( el, "click" );
543504
}
544505

545506
// Return non-false to allow normal event-path propagation
@@ -574,55 +535,63 @@ jQuery.event = {
574535
// synthetic events by interrupting progress until reinvoked in response to
575536
// *native* events that it fires directly, ensuring that state changes have
576537
// already occurred before other listeners are invoked.
577-
function leverageNative( el, type, forceAdd, allowAsync ) {
538+
function leverageNative( el, type, expectSync ) {
578539

579-
// Setup must go through jQuery.event.add
580-
if ( forceAdd ) {
581-
jQuery.event.add( el, type, forceAdd );
540+
// Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add
541+
if ( !expectSync ) {
542+
jQuery.event.add( el, type, returnTrue );
582543
return;
583544
}
584545

585546
// Register the controller as a special universal handler for all event namespaces
586-
dataPriv.set( el, type, forceAdd );
547+
dataPriv.set( el, type, false );
587548
jQuery.event.add( el, type, {
588549
namespace: false,
589550
handler: function( event ) {
590-
var maybeAsync, result,
551+
var notAsync, result,
591552
saved = dataPriv.get( this, type );
592553

593-
// Interrupt processing of the outer synthetic .trigger()ed event
594-
if ( ( event.isTrigger & 1 ) && this[ type ] && !saved ) {
595-
596-
// Store arguments for use when handling the inner native event
597-
saved = slice.call( arguments );
598-
dataPriv.set( this, type, saved );
599-
600-
// Trigger the native event and capture its result
601-
// Support: IE <=9 - 11+
602-
// focus() and blur() are asynchronous
603-
maybeAsync = allowAsync( this, type );
604-
this[ type ]();
605-
result = dataPriv.get( this, type );
606-
if ( result !== saved ) {
607-
dataPriv.set( this, type, false );
608-
609-
// Cancel the outer synthetic event
610-
event.stopImmediatePropagation();
611-
event.preventDefault();
612-
return result;
613-
} else if ( maybeAsync ) {
614-
615-
// Cancel the outer synthetic event in expectation of a followup
616-
event.stopImmediatePropagation();
617-
event.preventDefault();
618-
return;
619-
} else {
620-
dataPriv.set( this, type, false );
554+
if ( ( event.isTrigger & 1 ) && this[ type ] ) {
555+
556+
// Interrupt processing of the outer synthetic .trigger()ed event
557+
if ( !saved ) {
558+
559+
// Store arguments for use when handling the inner native event
560+
saved = slice.call( arguments );
561+
dataPriv.set( this, type, saved );
562+
563+
// Trigger the native event and capture its result
564+
// Support: IE <=9 - 11+
565+
// focus() and blur() are asynchronous
566+
notAsync = expectSync( this, type );
567+
this[ type ]();
568+
result = dataPriv.get( this, type );
569+
if ( saved !== result || notAsync ) {
570+
dataPriv.set( this, type, false );
571+
} else {
572+
result = undefined;
573+
}
574+
if ( saved !== result ) {
575+
576+
// Cancel the outer synthetic event
577+
event.stopImmediatePropagation();
578+
event.preventDefault();
579+
return result;
580+
}
581+
582+
// If this is an inner synthetic event for an event with a bubbling surrogate
583+
// (focus or blur), assume that the surrogate already propagated from triggering the
584+
// native event and prevent that from happening again here.
585+
// This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the
586+
// bubbling surrogate propagates *after* the non-bubbling base), but that seems
587+
// less bad than duplication.
588+
} else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {
589+
event.stopPropagation();
621590
}
622591

623592
// If this is a native event triggered above, everything is now in order
624593
// Fire an inner synthetic event with the original arguments
625-
} else if ( !event.isTrigger && saved ) {
594+
} else if ( saved ) {
626595

627596
// ...and capture the result
628597
dataPriv.set( this, type, jQuery.event.trigger(
@@ -800,6 +769,33 @@ jQuery.each( {
800769
}
801770
}, jQuery.event.addProp );
802771

772+
jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
773+
jQuery.event.special[ type ] = {
774+
775+
// Utilize native event if possible so blur/focus sequence is correct
776+
setup: function() {
777+
778+
// Claim the first handler
779+
// dataPriv.set( this, "focus", ... )
780+
// dataPriv.set( this, "blur", ... )
781+
leverageNative( this, type, expectSync );
782+
783+
// Return false to allow normal processing in the caller
784+
return false;
785+
},
786+
trigger: function() {
787+
788+
// Force setup before trigger
789+
leverageNative( this, type );
790+
791+
// Return non-false to allow normal event-path propagation
792+
return true;
793+
},
794+
795+
delegateType: delegateType
796+
};
797+
} );
798+
803799
// Create mouseenter/leave events using mouseover/out and event-time checks
804800
// so that event delegation works in jQuery.
805801
// Do the same for pointerenter/pointerleave and pointerover/pointerout

test/unit/event.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2956,11 +2956,7 @@ QUnit.test( "Check order of focusin/focusout events", function( assert ) {
29562956
.on( "focus", function() {
29572957
focus = true;
29582958
} )
2959-
2960-
// PR gh-4279 fixed a lot of `focus`-related issues but made `focusin` fire twice.
2961-
// We've decided to accept this drawback for now. If it's fixed, change `one` to `on`
2962-
// in the following line:
2963-
.one( "focusin", function() {
2959+
.on( "focusin", function() {
29642960
assert.ok( !focus, "Focusin event should fire before focus does" );
29652961
focus = true;
29662962
} )

0 commit comments

Comments
 (0)