Skip to content

Commit

Permalink
Event: Use only one focusin/out handler per matching window & document
Browse files Browse the repository at this point in the history
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 1cecf64 - the commit that
introduced the regression - to make sure we don't regress on either front.

Fixes gh-4652
Closes gh-4656
  • Loading branch information
mgol committed Apr 6, 2020
1 parent 966a709 commit 9e15d6b
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 10 deletions.
7 changes: 5 additions & 2 deletions src/event/focusin.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ if ( !support.focusin ) {

jQuery.event.special[ fix ] = {
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 );

if ( !attaches ) {
Expand All @@ -36,7 +39,7 @@ if ( !support.focusin ) {
dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
},
teardown: function() {
var doc = this.ownerDocument || this,
var doc = this.ownerDocument || this.document || this,
attaches = dataPriv.access( doc, fix ) - 1;

if ( !attaches ) {
Expand Down
51 changes: 43 additions & 8 deletions test/unit/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -2555,31 +2555,66 @@ testIframe(
function( assert, framejQuery, frameWin, frameDoc ) {
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
jQuery( "body" ).on( "focusin.iframeTest", function() {
assert.ok( false, "fired a focusin event in the parent document" );
} );

input.on( "focusin", function() {
focus = true;
assert.ok( true, "fired a focusin event in the iframe" );
} );

// Avoid a native event; Chrome can't force focus to another frame
input.trigger( "focusin" );

// Must manually remove handler to avoid leaks in our data store
input.remove();

// Be sure it was removed; nothing should happen
input.trigger( "focusin" );
input[ 0 ].focus();

// Remove body handler manually since it's outside the fixture
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(
"jQuery.ready promise",
"event/promiseReady.html",
Expand Down

0 comments on commit 9e15d6b

Please sign in to comment.