From 66b840618da4d8e7ac8a83b856df6dd07892947f Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Thu, 19 May 2016 21:56:39 +0400 Subject: [PATCH] Event: don't execute native stop(Immediate)Propagation from simulation In Firefox, called `stop(Immediate)Propagation` methods, in capturing phase prevents receiving focus Cherry-picked from 94efb7992911b6698f900f5b816d043b468bc277 Fixes gh-3111 --- src/event.js | 7 ++-- src/event/trigger.js | 18 +-------- test/unit/event.js | 96 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 95 insertions(+), 26 deletions(-) diff --git a/src/event.js b/src/event.js index 6d60b4c11..9ebbe2f7e 100644 --- a/src/event.js +++ b/src/event.js @@ -593,13 +593,14 @@ jQuery.Event.prototype = { isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse, + isSimulated: false, preventDefault: function() { var e = this.originalEvent; this.isDefaultPrevented = returnTrue; - if ( e ) { + if ( e && !this.isSimulated ) { e.preventDefault(); } }, @@ -608,7 +609,7 @@ jQuery.Event.prototype = { this.isPropagationStopped = returnTrue; - if ( e ) { + if ( e && !this.isSimulated ) { e.stopPropagation(); } }, @@ -617,7 +618,7 @@ jQuery.Event.prototype = { this.isImmediatePropagationStopped = returnTrue; - if ( e ) { + if ( e && !this.isSimulated ) { e.stopImmediatePropagation(); } diff --git a/src/event/trigger.js b/src/event/trigger.js index a6fac70ea..75b9dd285 100644 --- a/src/event/trigger.js +++ b/src/event/trigger.js @@ -148,6 +148,7 @@ jQuery.extend( jQuery.event, { }, // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events simulate: function( type, elem, event ) { var e = jQuery.extend( new jQuery.Event(), @@ -155,27 +156,10 @@ jQuery.extend( jQuery.event, { { type: type, isSimulated: true - - // Previously, `originalEvent: {}` was set here, so stopPropagation call - // would not be triggered on donor event, since in our own - // jQuery.event.stopPropagation function we had a check for existence of - // originalEvent.stopPropagation method, so, consequently it would be a noop. - // - // But now, this "simulate" function is used only for events - // for which stopPropagation() is noop, so there is no need for that anymore. - // - // For the 1.x branch though, guard for "click" and "submit" - // events is still used, but was moved to jQuery.event.stopPropagation function - // because `originalEvent` should point to the original event for the constancy - // with other events and for more focused logic } ); jQuery.event.trigger( e, null, elem ); - - if ( e.isDefaultPrevented() ) { - event.preventDefault(); - } } } ); diff --git a/test/unit/event.js b/test/unit/event.js index 04ca615eb..20c2a6daf 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -2846,6 +2846,81 @@ QUnit.test( "Donor event interference", function( assert ) { jQuery( "#donor-input" )[ 0 ].click(); } ); +QUnit.test( + "native stop(Immediate)Propagation/preventDefault methods shouldn't be called", + function( assert ) { + var userAgent = window.navigator.userAgent; + + if ( !( /firefox/i.test( userAgent ) || /safari/i.test( userAgent ) ) ) { + assert.expect( 1 ); + assert.ok( true, "Assertions should run only in Chrome, Safari, Fx & Edge" ); + return; + } + + assert.expect( 3 ); + + var checker = {}; + + var html = "
" + + "
" + + "" + + "
" + + "
"; + + jQuery( "#qunit-fixture" ).append( html ); + var outer = jQuery( "#donor-outer" ); + + outer + .on( "focusin", function( event ) { + checker.prevent = sinon.stub( event.originalEvent, "preventDefault" ); + event.preventDefault(); + } ) + .on( "focusin", function( event ) { + checker.simple = sinon.stub( event.originalEvent, "stopPropagation" ); + event.stopPropagation(); + } ) + .on( "focusin", function( event ) { + checker.immediate = sinon.stub( event.originalEvent, "stopImmediatePropagation" ); + event.stopImmediatePropagation(); + } ); + + jQuery( "#donor-input" ).trigger( "focus" ); + assert.strictEqual( checker.simple.called, false ); + assert.strictEqual( checker.immediate.called, false ); + assert.strictEqual( checker.prevent.called, false ); + + // We need to "off" it, since yes QUnit always update the fixtures + // but "focus" event listener is attached to document for focus(in | out) + // event and document doesn't get cleared obviously :) + outer.off( "focusin" ); + } +); + +QUnit.test( + "isSimulated property always exist on event object", + function( assert ) { + var userAgent = window.navigator.userAgent; + + if ( !( /firefox/i.test( userAgent ) || /safari/i.test( userAgent ) ) ) { + assert.expect( 1 ); + assert.ok( true, "Assertions should run only in Chrome, Safari, Fx & Edge" ); + return; + } + + assert.expect( 1 ); + + var element = jQuery( "" ); + + jQuery( "#qunit-fixture" ).append( element ); + + element.on( "focus", function( event ) { + assert.notOk( event.isSimulated ); + } ); + + element.trigger( "focus" ); + } +); + QUnit.test( "originalEvent property for Chrome, Safari, Fx & Edge of simulated event", function( assert ) { var userAgent = window.navigator.userAgent; @@ -2856,6 +2931,7 @@ QUnit.test( "originalEvent property for Chrome, Safari, Fx & Edge of simulated e } assert.expect( 4 ); + var done = assert.async(); var html = "
" + "
" + @@ -2864,17 +2940,25 @@ QUnit.test( "originalEvent property for Chrome, Safari, Fx & Edge of simulated e "
"; jQuery( "#qunit-fixture" ).append( html ); + var outer = jQuery( "#donor-outer" ); - jQuery( "#donor-outer" ).on( "focusin", function( event ) { - assert.ok( true, "focusin bubbled to outer div" ); - assert.equal( event.originalEvent.type, "focus", - "make sure originalEvent type is correct" ); - assert.equal( event.type, "focusin", "make sure type is correct" ); - } ); + outer + .on( "focusin", function( event ) { + assert.ok( true, "focusin bubbled to outer div" ); + assert.equal( event.originalEvent.type, "focus", + "make sure originalEvent type is correct" ); + assert.equal( event.type, "focusin", "make sure type is correct" ); + } ); jQuery( "#donor-input" ).on( "focus", function() { assert.ok( true, "got a focus event from the input" ); + done(); } ); jQuery( "#donor-input" ).trigger( "focus" ); + + // We need to "off" it, since yes QUnit always update the fixtures + // but "focus" event listener is attached to document for focus(in | out) + // event and document doesn't get cleared obviously :) + outer.off( "focusin" ); } ); QUnit[ jQuery.fn.click ? "test" : "skip" ]( "trigger() shortcuts", function( assert ) { -- 2.39.5