@@ -730,25 +730,41 @@ jQuery.each( {
730730
731731jQuery . each ( { focus : "focusin" , blur : "focusout" } , function ( type , delegateType ) {
732732
733- // Support: IE 11+
734- // Attach a single focusin/focusout handler on the document while someone wants focus/blur.
735- // This is because the former are synchronous in IE while the latter are async. In other
736- // browsers, all those handlers are invoked synchronously.
737733 function focusMappedHandler ( nativeEvent ) {
734+ if ( document . documentMode ) {
735+
736+ // Support: IE 11+
737+ // Attach a single focusin/focusout handler on the document while someone wants
738+ // focus/blur. This is because the former are synchronous in IE while the latter
739+ // are async. In other browsers, all those handlers are invoked synchronously.
740+
741+ // `handle` from private data would already wrap the event, but we need
742+ // to change the `type` here.
743+ var handle = dataPriv . get ( this , "handle" ) ,
744+ event = jQuery . event . fix ( nativeEvent ) ;
745+ event . type = nativeEvent . type === "focusin" ? "focus" : "blur" ;
746+ event . isSimulated = true ;
747+
748+ // First, handle focusin/focusout
749+ handle ( nativeEvent ) ;
750+
751+ // ...then, handle focus/blur
752+ //
753+ // focus/blur don't bubble while focusin/focusout do; simulate the former by only
754+ // invoking the handler at the lower level.
755+ if ( event . target === event . currentTarget ) {
756+
757+ // The setup part calls `leverageNative`, which, in turn, calls
758+ // `jQuery.event.add`, so event handle will already have been set
759+ // by this point.
760+ handle ( event ) ;
761+ }
762+ } else {
738763
739- // `eventHandle` would already wrap the event, but we need to change the `type` here.
740- var event = jQuery . event . fix ( nativeEvent ) ;
741- event . type = nativeEvent . type === "focusin" ? "focus" : "blur" ;
742- event . isSimulated = true ;
743-
744- // focus/blur don't bubble while focusin/focusout do; simulate the former by only
745- // invoking the handler at the lower level.
746- if ( event . target === event . currentTarget ) {
747-
748- // The setup part calls `leverageNative`, which, in turn, calls
749- // `jQuery.event.add`, so event handle will already have been set
750- // by this point.
751- dataPriv . get ( this , "handle" ) ( event ) ;
764+ // For non-IE browsers, attach a single capturing handler on the document
765+ // while someone wants focusin/focusout.
766+ jQuery . event . simulate ( delegateType , nativeEvent . target ,
767+ jQuery . event . fix ( nativeEvent ) ) ;
752768 }
753769 }
754770
@@ -757,13 +773,24 @@ jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateTyp
757773 // Utilize native event if possible so blur/focus sequence is correct
758774 setup : function ( ) {
759775
776+ var attaches ;
777+
760778 // Claim the first handler
761779 // dataPriv.set( this, "focus", ... )
762780 // dataPriv.set( this, "blur", ... )
763781 leverageNative ( this , type , true ) ;
764782
765783 if ( document . documentMode ) {
766- this . addEventListener ( delegateType , focusMappedHandler ) ;
784+
785+ // Support: IE 9 - 11+
786+ // We use the same native handler for focusin & focus (and focusout & blur)
787+ // so we need to coordinate setup & teardown parts between those events.
788+ // Use `delegateType` as the key as `type` is already used by `leverageNative`.
789+ attaches = dataPriv . get ( this , delegateType ) ;
790+ if ( ! attaches ) {
791+ this . addEventListener ( delegateType , focusMappedHandler ) ;
792+ }
793+ dataPriv . set ( this , delegateType , ( attaches || 0 ) + 1 ) ;
767794 } else {
768795
769796 // Return false to allow normal processing in the caller
@@ -780,8 +807,16 @@ jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateTyp
780807 } ,
781808
782809 teardown : function ( ) {
810+ var attaches ;
811+
783812 if ( document . documentMode ) {
784- this . removeEventListener ( delegateType , focusMappedHandler ) ;
813+ attaches = dataPriv . get ( this , delegateType ) - 1 ;
814+ if ( ! attaches ) {
815+ this . removeEventListener ( delegateType , focusMappedHandler ) ;
816+ dataPriv . remove ( this , delegateType ) ;
817+ } else {
818+ dataPriv . set ( this , delegateType , attaches ) ;
819+ }
785820 } else {
786821
787822 // Return false to indicate standard teardown should be applied
@@ -797,6 +832,58 @@ jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateTyp
797832
798833 delegateType : delegateType
799834 } ;
835+
836+ // Support: Firefox <=44
837+ // Firefox doesn't have focus(in | out) events
838+ // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
839+ //
840+ // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
841+ // focus(in | out) events fire after focus & blur events,
842+ // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
843+ // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
844+ //
845+ // Support: IE 9 - 11+
846+ // To preserve relative focusin/focus & focusout/blur event order guaranteed on the 3.x branch,
847+ // attach a single handler for both events in IE.
848+ jQuery . event . special [ delegateType ] = {
849+ setup : function ( ) {
850+
851+ // Handle: regular nodes (via `this.ownerDocument`), window
852+ // (via `this.document`) & document (via `this`).
853+ var doc = this . ownerDocument || this . document || this ,
854+ dataHolder = document . documentMode ? this : doc ,
855+ attaches = dataPriv . get ( dataHolder , delegateType ) ;
856+
857+ // Support: IE 9 - 11+
858+ // We use the same native handler for focusin & focus (and focusout & blur)
859+ // so we need to coordinate setup & teardown parts between those events.
860+ // Use `delegateType` as the key as `type` is already used by `leverageNative`.
861+ if ( ! attaches ) {
862+ if ( document . documentMode ) {
863+ this . addEventListener ( delegateType , focusMappedHandler ) ;
864+ } else {
865+ doc . addEventListener ( type , focusMappedHandler , true ) ;
866+ }
867+ }
868+ dataPriv . set ( dataHolder , delegateType , ( attaches || 0 ) + 1 ) ;
869+ } ,
870+ teardown : function ( ) {
871+ var doc = this . ownerDocument || this . document || this ,
872+ dataHolder = document . documentMode ? this : doc ,
873+ attaches = dataPriv . get ( dataHolder , delegateType ) - 1 ;
874+
875+ if ( ! attaches ) {
876+ if ( document . documentMode ) {
877+ this . removeEventListener ( delegateType , focusMappedHandler ) ;
878+ } else {
879+ doc . removeEventListener ( type , focusMappedHandler , true ) ;
880+ }
881+ dataPriv . remove ( dataHolder , delegateType ) ;
882+ } else {
883+ dataPriv . set ( dataHolder , delegateType , attaches ) ;
884+ }
885+ }
886+ } ;
800887} ) ;
801888
802889// Create mouseenter/leave events using mouseover/out and event-time checks
0 commit comments