import jQuery from "./core.js";
-import document from "./var/document.js";
import documentElement from "./var/documentElement.js";
import rnothtmlwhite from "./var/rnothtmlwhite.js";
import rcheckableType from "./var/rcheckableType.js";
import slice from "./var/slice.js";
+import isIE from "./var/isIE.js";
import acceptData from "./data/var/acceptData.js";
import dataPriv from "./data/var/dataPriv.js";
import nodeName from "./core/nodeName.js";
return false;
}
-// Support: IE <=9 - 11+
-// focus() and blur() are asynchronous, except when they are no-op.
-// So expect focus to be synchronous when the element is already active,
-// and blur to be synchronous when the element is not already active.
-// (focus and blur are always synchronous in other supported browsers,
-// this just defines when we can count on it).
-function expectSync( elem, type ) {
- return ( elem === document.activeElement ) === ( type === "focus" );
-}
-
function on( elem, types, selector, data, fn, one ) {
var origFn, type;
el.click && nodeName( el, "input" ) ) {
// dataPriv.set( el, "click", ... )
- leverageNative( el, "click", returnTrue );
+ leverageNative( el, "click", true );
}
// Return false to allow normal processing in the caller
// synthetic events by interrupting progress until reinvoked in response to
// *native* events that it fires directly, ensuring that state changes have
// already occurred before other listeners are invoked.
-function leverageNative( el, type, expectSync ) {
+function leverageNative( el, type, isSetup ) {
- // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add
- if ( !expectSync ) {
+ // Missing `isSetup` indicates a trigger call, which must force setup through jQuery.event.add
+ if ( !isSetup ) {
if ( dataPriv.get( el, type ) === undefined ) {
jQuery.event.add( el, type, returnTrue );
}
jQuery.event.add( el, type, {
namespace: false,
handler: function( event ) {
- var notAsync, result,
+ var result,
saved = dataPriv.get( this, type );
if ( ( event.isTrigger & 1 ) && this[ type ] ) {
// Interrupt processing of the outer synthetic .trigger()ed event
- // Saved data should be false in such cases, but might be a leftover capture object
- // from an async native handler (gh-4350)
- if ( !saved.length ) {
+ if ( !saved ) {
// Store arguments for use when handling the inner native event
// There will always be at least one argument (an event object), so this array
dataPriv.set( this, type, saved );
// Trigger the native event and capture its result
- // Support: IE <=9 - 11+
- // focus() and blur() are asynchronous
- notAsync = expectSync( this, type );
this[ type ]();
result = dataPriv.get( this, type );
- if ( saved !== result || notAsync ) {
- dataPriv.set( this, type, false );
- } else {
- result = {};
- }
+ dataPriv.set( this, type, false );
+
if ( saved !== result ) {
// Cancel the outer synthetic event
event.stopImmediatePropagation();
event.preventDefault();
- // Support: Chrome 86+
- // In Chrome, if an element having a focusout handler is blurred by
- // clicking outside of it, it invokes the handler synchronously. If
- // that handler calls `.remove()` on the element, the data is cleared,
- // leaving `result` undefined. We need to guard against this.
- return result && result.value;
+ return result;
}
// If this is an inner synthetic event for an event with a bubbling surrogate
} else if ( saved.length ) {
// ...and capture the result
- dataPriv.set( this, type, {
- value: jQuery.event.trigger(
-
- // Support: IE <=9 - 11+
- // Extend with the prototype to reset the above stopImmediatePropagation()
- jQuery.extend( saved[ 0 ], jQuery.Event.prototype ),
- saved.slice( 1 ),
- this
- )
- } );
+ dataPriv.set( this, type, jQuery.event.trigger(
+ saved[ 0 ],
+ saved.slice( 1 ),
+ this
+ ) );
// Abort handling of the native event
event.stopImmediatePropagation();
}, jQuery.event.addProp );
jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
+
+ // Support: IE 11+
+ // Attach a single focusin/focusout handler on the document while someone wants focus/blur.
+ // This is because the former are synchronous in IE while the latter are async. In other
+ // browsers, all those handlers are invoked synchronously.
+ function focusMappedHandler( nativeEvent ) {
+
+ // `eventHandle` would already wrap the event, but we need to change the `type` here.
+ var event = jQuery.event.fix( nativeEvent );
+ event.type = nativeEvent.type === "focusin" ? "focus" : "blur";
+ event.isSimulated = true;
+
+ // focus/blur don't bubble while focusin/focusout do; simulate the former by only
+ // invoking the handler at the lower level.
+ if ( event.target === event.currentTarget ) {
+
+ // The setup part calls `leverageNative`, which, in turn, calls
+ // `jQuery.event.add`, so event handle will already have been set
+ // by this point.
+ dataPriv.get( this, "handle" )( event );
+ }
+ }
+
jQuery.event.special[ type ] = {
// Utilize native event if possible so blur/focus sequence is correct
// Claim the first handler
// dataPriv.set( this, "focus", ... )
// dataPriv.set( this, "blur", ... )
- leverageNative( this, type, expectSync );
+ leverageNative( this, type, true );
+
+ if ( isIE ) {
+ this.addEventListener( delegateType, focusMappedHandler );
+ } else {
- // Return false to allow normal processing in the caller
- return false;
+ // Return false to allow normal processing in the caller
+ return false;
+ }
},
trigger: function() {
return true;
},
+ teardown: function() {
+ if ( isIE ) {
+ this.removeEventListener( delegateType, focusMappedHandler );
+ } else {
+
+ // Return false to indicate standard teardown should be applied
+ return false;
+ }
+ },
+
// Suppress native focus or blur if we're currently inside
// a leveraged native-event stack
_default: function( event ) {
// Removed since DOM focus is unreliable on test swarm
// DOM focus method
-// input[0].focus();
+// input[ 0 ].focus();
// To make the next focus test work, we need to take focus off the input.
// This will fire another focusin event, so set order to reflect that.
// order = 1;
-// jQuery("#text1")[0].focus();
+// jQuery( "#text1" )[ 0 ].focus();
// jQuery trigger, which calls DOM focus
order = 0;
jQuery( "body" ).off( "focusin.focusinBubblesTest" );
} );
+QUnit.test( "focus does not bubble", function( assert ) {
+ assert.expect( 1 );
+
+ var done = assert.async(),
+ input = jQuery( "<input type='text' />" ).prependTo( "body" );
+
+ // focus the element so DOM focus won't fire
+ input[ 0 ].focus();
+
+ jQuery( "body" ).on( "focus.focusDoesNotBubbleTest", function() {
+ assert.ok( false, "focus doesn't fire on body" );
+ } );
+
+ input.on( "focus.focusDoesNotBubbleTest", function() {
+ assert.ok( true, "focus on the element" );
+ } );
+
+// Removed since DOM focus is unreliable on test swarm
+ // DOM focus method
+// input[ 0 ].focus();
+
+ // To make the next focus test work, we need to take focus off the input.
+ // This will fire another focusin event, so set order to reflect that.
+// jQuery( "#text1" )[ 0 ].focus();
+
+ // jQuery trigger, which calls DOM focus
+ input.trigger( "focus" );
+
+ input.remove();
+ jQuery( "body" ).off( "focus.focusDoesNotBubbleTest" );
+
+ setTimeout( function() {
+ done();
+ }, 50 );
+} );
+
QUnit.test( "custom events with colons (trac-3533, trac-8272)", function( assert ) {
assert.expect( 1 );
button[ 0 ].blur = function() {
jQuery.cleanData( [ this ] );
this.parentNode.removeChild( this );
+
+ // Redefine `blur` to avoid a hard crash in Karma tests that stop
+ // the test runner in case this test fails.
+ this.blur = jQuery.noop;
};
button[ 0 ].click();
var focus,
parent = jQuery( "<div>" ),
input = jQuery( "<input>" ),
- inputExternal = jQuery( "<input>" ),
-
- // Support: IE <=9 - 11+
- // focus and blur events are asynchronous; this is the resulting mess.
- // The browser window must be topmost for this to work properly!!
- done = assert.async();
+ inputExternal = jQuery( "<input>" );
parent.append( input );
jQuery( "#qunit-fixture" ).append( parent ).append( inputExternal );
// initially, lose focus
inputExternal[ 0 ].focus();
- setTimeout( function() {
- parent
- .on( "focus", function() {
- assert.ok( false, "parent: focus not fired" );
- } )
- .on( "focusin", function() {
- assert.ok( true, "parent: focusin fired" );
- } )
- .on( "blur", function() {
- assert.ok( false, "parent: blur not fired" );
- } )
- .on( "focusout", function() {
- assert.ok( true, "parent: focusout fired" );
- } );
-
- input
- .on( "focus", function() {
- assert.ok( true, "element: focus fired" );
- } )
- .on( "focusin", function() {
- assert.ok( true, "element: focusin fired" );
- focus = true;
- } )
- .on( "blur", function() {
- assert.ok( true, "parent: blur fired" );
- } )
- .on( "focusout", function() {
- assert.ok( true, "element: focusout fired" );
- } );
-
- // gain focus
- input[ 0 ].focus();
+ parent
+ .on( "focus", function() {
+ assert.ok( false, "parent: focus not fired" );
+ } )
+ .on( "focusin", function() {
+ assert.ok( true, "parent: focusin fired" );
+ } )
+ .on( "blur", function() {
+ assert.ok( false, "parent: blur not fired" );
+ } )
+ .on( "focusout", function() {
+ assert.ok( true, "parent: focusout fired" );
+ } );
- // then lose it
- inputExternal[ 0 ].focus();
+ input
+ .on( "focus", function() {
+ assert.ok( true, "element: focus fired" );
+ } )
+ .on( "focusin", function() {
+ assert.ok( true, "element: focusin fired" );
+ focus = true;
+ } )
+ .on( "blur", function() {
+ assert.ok( true, "parent: blur fired" );
+ } )
+ .on( "focusout", function() {
+ assert.ok( true, "element: focusout fired" );
+ } );
- setTimeout( function() {
+ // gain focus
+ input[ 0 ].focus();
- // DOM focus is unreliable in TestSwarm
- if ( QUnit.isSwarm && !focus ) {
- assert.ok( true, "GAP: Could not observe focus change" );
- assert.ok( true, "GAP: Could not observe focus change" );
- assert.ok( true, "GAP: Could not observe focus change" );
- assert.ok( true, "GAP: Could not observe focus change" );
- assert.ok( true, "GAP: Could not observe focus change" );
- assert.ok( true, "GAP: Could not observe focus change" );
- }
+ // then lose it
+ inputExternal[ 0 ].focus();
- // cleanup
- parent.off();
- input.off();
+ // DOM focus is unreliable in TestSwarm
+ if ( QUnit.isSwarm && !focus ) {
+ assert.ok( true, "GAP: Could not observe focus change" );
+ assert.ok( true, "GAP: Could not observe focus change" );
+ assert.ok( true, "GAP: Could not observe focus change" );
+ assert.ok( true, "GAP: Could not observe focus change" );
+ assert.ok( true, "GAP: Could not observe focus change" );
+ assert.ok( true, "GAP: Could not observe focus change" );
+ }
- done();
- }, 50 );
- }, 50 );
+ // cleanup
+ parent.off();
+ input.off();
} );
QUnit.test( "focus-blur order (trac-12868)", function( assert ) {
var order,
$text = jQuery( "#text1" ),
- $radio = jQuery( "#radio1" ),
-
- // Support: IE <=9 - 11+
- // focus and blur events are asynchronous; this is the resulting mess.
- // The browser window must be topmost for this to work properly!!
- done = assert.async();
+ $radio = jQuery( "#radio1" );
$radio[ 0 ].focus();
- setTimeout( function() {
-
- $text
- .on( "focus", function() {
- assert.equal( order++, 1, "text focus" );
- } )
- .on( "blur", function() {
- assert.equal( order++, 0, "text blur" );
- } );
- $radio
- .on( "focus", function() {
- assert.equal( order++, 1, "radio focus" );
- } )
- .on( "blur", function() {
- assert.equal( order++, 0, "radio blur" );
- } );
+ $text
+ .on( "focus", function() {
+ assert.equal( order++, 1, "text focus" );
+ } )
+ .on( "blur", function() {
+ assert.equal( order++, 0, "text blur" );
+ } );
+ $radio
+ .on( "focus", function() {
+ assert.equal( order++, 1, "radio focus" );
+ } )
+ .on( "blur", function() {
+ assert.equal( order++, 0, "radio blur" );
+ } );
- // Enabled input getting focus
- order = 0;
- assert.equal( document.activeElement, $radio[ 0 ], "radio has focus" );
- $text.trigger( "focus" );
- setTimeout( function() {
+ // Enabled input getting focus
+ order = 0;
+ assert.equal( document.activeElement, $radio[ 0 ], "radio has focus" );
+ $text.trigger( "focus" );
- // DOM focus is unreliable in TestSwarm
- if ( QUnit.isSwarm && order === 0 ) {
- assert.ok( true, "GAP: Could not observe focus change" );
- assert.ok( true, "GAP: Could not observe focus change" );
- }
+ // DOM focus is unreliable in TestSwarm
+ if ( QUnit.isSwarm && order === 0 ) {
+ assert.ok( true, "GAP: Could not observe focus change" );
+ assert.ok( true, "GAP: Could not observe focus change" );
+ }
- assert.equal( document.activeElement, $text[ 0 ], "text has focus" );
+ assert.equal( document.activeElement, $text[ 0 ], "text has focus" );
- // Run handlers without native method on an input
- order = 1;
- $radio.triggerHandler( "focus" );
+ // Run handlers without native method on an input
+ order = 1;
+ $radio.triggerHandler( "focus" );
- // Clean up
- $text.off();
- $radio.off();
- done();
- }, 50 );
- }, 50 );
+ // Clean up
+ $text.off();
+ $radio.off();
} );
QUnit.test( "Event handling works with multiple async focus events (gh-4350)", function( assert ) {
var remaining = 3,
input = jQuery( "#name" ),
-
- // Support: IE <=9 - 11+
- // focus and blur events are asynchronous; this is the resulting mess.
- // The browser window must be topmost for this to work properly!!
done = assert.async();
input
if ( remaining > 0 ) {
input.trigger( "blur" );
} else {
+
+ if ( QUnit.isIE ) {
+
+ // Support: <=IE 11+
+ // In IE, one of the blurs sometimes triggers a focus on body
+ // which in turn restores focus to the input, leading to 4 assertions
+ // firing instead of three. This only happens if other tests are
+ // running on the same test page. Avoid this issue in tests by removing
+ // the handler early.
+ input.off( "focus" );
+ }
done();
}
} )
} );
} );
+// Support: IE <=9 - 11+
+// focus and blur events are asynchronous.
+// The browser window must be topmost for this to work properly!!
+QUnit.test( "async focus queues properly (gh-4859)", function( assert ) {
+ assert.expect( 1 );
+
+ var $text = jQuery( "#text1" ),
+ $radio = jQuery( "#radio1" ),
+ done = assert.async();
+
+ $text.trigger( "focus" );
+ $radio.trigger( "focus" );
+ $text.trigger( "focus" );
+
+ setTimeout( function() {
+ assert.equal( document.activeElement, $text[ 0 ], "focus follows the last trigger" );
+ done();
+ }, 500 );
+} );
+
+// Support: IE <=9 - 11+
+// focus and blur events are asynchronous.
+// The browser window must be topmost for this to work properly!!
+QUnit.test( "async focus queues properly with blur (gh-4856)", function( assert ) {
+ assert.expect( 1 );
+
+ var $text = jQuery( "#text1" ),
+ done = assert.async();
+
+ $text.trigger( "focus" );
+ $text.trigger( "blur" );
+ $text.trigger( "focus" );
+
+ setTimeout( function() {
+ assert.equal( document.activeElement, $text[ 0 ], "focus-after-blur is respected" );
+ done();
+ }, 500 );
+} );
+
QUnit.test( "native-backed events preserve trigger data (gh-1741, gh-4139)", function( assert ) {
assert.expect( 17 );
targets = jQuery( parent[ 0 ].childNodes ),
checkbox = jQuery( targets[ 0 ] ),
data = [ "arg1", "arg2" ],
- slice = data.slice,
-
- // Support: IE <=9 - 11+
- // focus and blur events are asynchronous; this is the resulting mess.
- // The browser window must be topmost for this to work properly!!
- done = assert.async();
+ slice = data.slice;
// click (gh-4139)
assert.strictEqual( targets[ 0 ].checked, false, "checkbox unchecked before click" );
var type = event.type;
assert.deepEqual( slice.call( arguments, 1 ), data,
type + " handler received correct data" );
+
+ if ( QUnit.isIE && type === "focus" ) {
+
+ // Support: <=IE 11+
+ // In IE, one of the blurs sometimes triggers a focus on body
+ // which in turn restores focus to the input, leading to 4 assertions
+ // firing instead of three. This only happens if other tests are
+ // running on the same test page. Avoid this issue in tests by removing
+ // the handler early.
+ checkbox.off( "focus" );
+ }
} );
checkbox.trigger( "focus", data );
- setTimeout( function() {
- assert.strictEqual( document.activeElement, checkbox[ 0 ],
- "element focused after focus event (default action)" );
- checkbox.trigger( "blur", data );
- setTimeout( function() {
- assert.notEqual( document.activeElement, checkbox[ 0 ],
- "element not focused after blur event (default action)" );
- done();
- }, 50 );
- }, 50 );
+
+ assert.strictEqual( document.activeElement, checkbox[ 0 ],
+ "element focused after focus event (default action)" );
+ checkbox.trigger( "blur", data );
+
+ assert.notEqual( document.activeElement, checkbox[ 0 ],
+ "element not focused after blur event (default action)" );
} );
QUnit.test( "focus change during a focus handler (gh-4382)", function( assert ) {
assert.equal( document.activeElement, input[ 0 ], "input has focus" );
} );
+QUnit.test( "trigger(focus) works after focusing when hidden (gh-4950)", function( assert ) {
+ assert.expect( 1 );
+
+ var input = jQuery( "<input />" );
+
+ input.appendTo( "#qunit-fixture" );
+
+ input
+ .css( "display", "none" )
+ .trigger( "focus" )
+ .css( "display", "" )
+ .trigger( "focus" );
+
+ assert.equal( document.activeElement, input[ 0 ], "input has focus" );
+} );
+
// TODO replace with an adaptation of
// https://github.com/jquery/jquery/pull/1367/files#diff-a215316abbaabdf71857809e8673ea28R2464
( function() {