Event: Use only one focusin/out handler per matching window & document

The `doc` variable in:
https://github.com/jquery/jquery/blob/3.4.1/src/event/focusin.js#L30
matched `document` for `document` & `window` for `window`, creating two
separate wrapper event handlers & calling handlers twice if at least one
`focusout` or `focusin` handler was attached on *both* `window` & `document`,
or on `window` & another regular node.

Also, fix the "focusin from an iframe" test to actually verify the behavior
from commit 1cecf64e5a - the commit that
introduced the regression - to make sure we don't regress on either front.

Fixes gh-4652
Closes gh-4656
This commit is contained in:
Michał Gołębiowski-Owczarek 2020-04-06 20:34:40 +02:00 committed by GitHub
parent 966a709090
commit 9e15d6b469
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 48 additions and 10 deletions

View File

@ -27,7 +27,10 @@ if ( !support.focusin ) {
jQuery.event.special[ fix ] = { jQuery.event.special[ fix ] = {
setup: function() { setup: function() {
var doc = this.ownerDocument || this,
// Handle: regular nodes (via `this.ownerDocument`), window
// (via `this.document`) & document (via `this`).
var doc = this.ownerDocument || this.document || this,
attaches = dataPriv.access( doc, fix ); attaches = dataPriv.access( doc, fix );
if ( !attaches ) { if ( !attaches ) {
@ -36,7 +39,7 @@ if ( !support.focusin ) {
dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
}, },
teardown: function() { teardown: function() {
var doc = this.ownerDocument || this, var doc = this.ownerDocument || this.document || this,
attaches = dataPriv.access( doc, fix ) - 1; attaches = dataPriv.access( doc, fix ) - 1;
if ( !attaches ) { if ( !attaches ) {

View File

@ -2555,7 +2555,9 @@ testIframe(
function( assert, framejQuery, frameWin, frameDoc ) { function( assert, framejQuery, frameWin, frameDoc ) {
assert.expect( 1 ); assert.expect( 1 );
var input = jQuery( frameDoc ).find( "#frame-input" ); var done = assert.async(),
focus = false,
input = jQuery( frameDoc ).find( "#frame-input" );
// Create a focusin handler on the parent; shouldn't affect the iframe's fate // Create a focusin handler on the parent; shouldn't affect the iframe's fate
jQuery( "body" ).on( "focusin.iframeTest", function() { jQuery( "body" ).on( "focusin.iframeTest", function() {
@ -2563,23 +2565,56 @@ testIframe(
} ); } );
input.on( "focusin", function() { input.on( "focusin", function() {
focus = true;
assert.ok( true, "fired a focusin event in the iframe" ); assert.ok( true, "fired a focusin event in the iframe" );
} ); } );
// Avoid a native event; Chrome can't force focus to another frame // Avoid a native event; Chrome can't force focus to another frame
input.trigger( "focusin" ); input[ 0 ].focus();
// Must manually remove handler to avoid leaks in our data store
input.remove();
// Be sure it was removed; nothing should happen
input.trigger( "focusin" );
// Remove body handler manually since it's outside the fixture // Remove body handler manually since it's outside the fixture
jQuery( "body" ).off( "focusin.iframeTest" ); jQuery( "body" ).off( "focusin.iframeTest" );
setTimeout( function() {
// DOM focus is unreliable in TestSwarm
if ( QUnit.isSwarm && !focus ) {
assert.ok( true, "GAP: Could not observe focus change" );
}
done();
}, 50 );
} }
); );
QUnit.test( "focusin on document & window", function( assert ) {
assert.expect( 1 );
var counter = 0,
input = jQuery( "<input />" );
input.appendTo( "#qunit-fixture" );
input[ 0 ].focus();
jQuery( window ).on( "focusout", function() {
counter++;
} );
jQuery( document ).on( "focusout", function() {
counter++;
} );
input[ 0 ].blur();
// DOM focus is unreliable in TestSwarm
if ( QUnit.isSwarm && counter === 0 ) {
assert.ok( true, "GAP: Could not observe focus change" );
}
assert.strictEqual( counter, 2,
"focusout handlers on document/window fired once only" );
} );
testIframe( testIframe(
"jQuery.ready promise", "jQuery.ready promise",
"event/promiseReady.html", "event/promiseReady.html",