Skip to content

Commit 66b8406

Browse files
committedMay 19, 2016
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 94efb79 Fixes gh-3111
1 parent a15da41 commit 66b8406

File tree

3 files changed

+95
-26
lines changed

3 files changed

+95
-26
lines changed
 

‎src/event.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -593,13 +593,14 @@ jQuery.Event.prototype = {
593593
isDefaultPrevented: returnFalse,
594594
isPropagationStopped: returnFalse,
595595
isImmediatePropagationStopped: returnFalse,
596+
isSimulated: false,
596597

597598
preventDefault: function() {
598599
var e = this.originalEvent;
599600

600601
this.isDefaultPrevented = returnTrue;
601602

602-
if ( e ) {
603+
if ( e && !this.isSimulated ) {
603604
e.preventDefault();
604605
}
605606
},
@@ -608,7 +609,7 @@ jQuery.Event.prototype = {
608609

609610
this.isPropagationStopped = returnTrue;
610611

611-
if ( e ) {
612+
if ( e && !this.isSimulated ) {
612613
e.stopPropagation();
613614
}
614615
},
@@ -617,7 +618,7 @@ jQuery.Event.prototype = {
617618

618619
this.isImmediatePropagationStopped = returnTrue;
619620

620-
if ( e ) {
621+
if ( e && !this.isSimulated ) {
621622
e.stopImmediatePropagation();
622623
}
623624

‎src/event/trigger.js

+1-17
Original file line numberDiff line numberDiff line change
@@ -148,34 +148,18 @@ jQuery.extend( jQuery.event, {
148148
},
149149

150150
// Piggyback on a donor event to simulate a different one
151+
// Used only for `focus(in | out)` events
151152
simulate: function( type, elem, event ) {
152153
var e = jQuery.extend(
153154
new jQuery.Event(),
154155
event,
155156
{
156157
type: type,
157158
isSimulated: true
158-
159-
// Previously, `originalEvent: {}` was set here, so stopPropagation call
160-
// would not be triggered on donor event, since in our own
161-
// jQuery.event.stopPropagation function we had a check for existence of
162-
// originalEvent.stopPropagation method, so, consequently it would be a noop.
163-
//
164-
// But now, this "simulate" function is used only for events
165-
// for which stopPropagation() is noop, so there is no need for that anymore.
166-
//
167-
// For the 1.x branch though, guard for "click" and "submit"
168-
// events is still used, but was moved to jQuery.event.stopPropagation function
169-
// because `originalEvent` should point to the original event for the constancy
170-
// with other events and for more focused logic
171159
}
172160
);
173161

174162
jQuery.event.trigger( e, null, elem );
175-
176-
if ( e.isDefaultPrevented() ) {
177-
event.preventDefault();
178-
}
179163
}
180164

181165
} );

‎test/unit/event.js

+90-6
Original file line numberDiff line numberDiff line change
@@ -2846,6 +2846,81 @@ QUnit.test( "Donor event interference", function( assert ) {
28462846
jQuery( "#donor-input" )[ 0 ].click();
28472847
} );
28482848

2849+
QUnit.test(
2850+
"native stop(Immediate)Propagation/preventDefault methods shouldn't be called",
2851+
function( assert ) {
2852+
var userAgent = window.navigator.userAgent;
2853+
2854+
if ( !( /firefox/i.test( userAgent ) || /safari/i.test( userAgent ) ) ) {
2855+
assert.expect( 1 );
2856+
assert.ok( true, "Assertions should run only in Chrome, Safari, Fx & Edge" );
2857+
return;
2858+
}
2859+
2860+
assert.expect( 3 );
2861+
2862+
var checker = {};
2863+
2864+
var html = "<div id='donor-outer'>" +
2865+
"<form id='donor-form'>" +
2866+
"<input id='donor-input' type='radio' />" +
2867+
"</form>" +
2868+
"</div>";
2869+
2870+
jQuery( "#qunit-fixture" ).append( html );
2871+
var outer = jQuery( "#donor-outer" );
2872+
2873+
outer
2874+
.on( "focusin", function( event ) {
2875+
checker.prevent = sinon.stub( event.originalEvent, "preventDefault" );
2876+
event.preventDefault();
2877+
} )
2878+
.on( "focusin", function( event ) {
2879+
checker.simple = sinon.stub( event.originalEvent, "stopPropagation" );
2880+
event.stopPropagation();
2881+
} )
2882+
.on( "focusin", function( event ) {
2883+
checker.immediate = sinon.stub( event.originalEvent, "stopImmediatePropagation" );
2884+
event.stopImmediatePropagation();
2885+
} );
2886+
2887+
jQuery( "#donor-input" ).trigger( "focus" );
2888+
assert.strictEqual( checker.simple.called, false );
2889+
assert.strictEqual( checker.immediate.called, false );
2890+
assert.strictEqual( checker.prevent.called, false );
2891+
2892+
// We need to "off" it, since yes QUnit always update the fixtures
2893+
// but "focus" event listener is attached to document for focus(in | out)
2894+
// event and document doesn't get cleared obviously :)
2895+
outer.off( "focusin" );
2896+
}
2897+
);
2898+
2899+
QUnit.test(
2900+
"isSimulated property always exist on event object",
2901+
function( assert ) {
2902+
var userAgent = window.navigator.userAgent;
2903+
2904+
if ( !( /firefox/i.test( userAgent ) || /safari/i.test( userAgent ) ) ) {
2905+
assert.expect( 1 );
2906+
assert.ok( true, "Assertions should run only in Chrome, Safari, Fx & Edge" );
2907+
return;
2908+
}
2909+
2910+
assert.expect( 1 );
2911+
2912+
var element = jQuery( "<input/>" );
2913+
2914+
jQuery( "#qunit-fixture" ).append( element );
2915+
2916+
element.on( "focus", function( event ) {
2917+
assert.notOk( event.isSimulated );
2918+
} );
2919+
2920+
element.trigger( "focus" );
2921+
}
2922+
);
2923+
28492924
QUnit.test( "originalEvent property for Chrome, Safari, Fx & Edge of simulated event", function( assert ) {
28502925
var userAgent = window.navigator.userAgent;
28512926

@@ -2856,6 +2931,7 @@ QUnit.test( "originalEvent property for Chrome, Safari, Fx & Edge of simulated e
28562931
}
28572932

28582933
assert.expect( 4 );
2934+
var done = assert.async();
28592935

28602936
var html = "<div id='donor-outer'>" +
28612937
"<form id='donor-form'>" +
@@ -2864,17 +2940,25 @@ QUnit.test( "originalEvent property for Chrome, Safari, Fx & Edge of simulated e
28642940
"</div>";
28652941

28662942
jQuery( "#qunit-fixture" ).append( html );
2943+
var outer = jQuery( "#donor-outer" );
28672944

2868-
jQuery( "#donor-outer" ).on( "focusin", function( event ) {
2869-
assert.ok( true, "focusin bubbled to outer div" );
2870-
assert.equal( event.originalEvent.type, "focus",
2871-
"make sure originalEvent type is correct" );
2872-
assert.equal( event.type, "focusin", "make sure type is correct" );
2873-
} );
2945+
outer
2946+
.on( "focusin", function( event ) {
2947+
assert.ok( true, "focusin bubbled to outer div" );
2948+
assert.equal( event.originalEvent.type, "focus",
2949+
"make sure originalEvent type is correct" );
2950+
assert.equal( event.type, "focusin", "make sure type is correct" );
2951+
} );
28742952
jQuery( "#donor-input" ).on( "focus", function() {
28752953
assert.ok( true, "got a focus event from the input" );
2954+
done();
28762955
} );
28772956
jQuery( "#donor-input" ).trigger( "focus" );
2957+
2958+
// We need to "off" it, since yes QUnit always update the fixtures
2959+
// but "focus" event listener is attached to document for focus(in | out)
2960+
// event and document doesn't get cleared obviously :)
2961+
outer.off( "focusin" );
28782962
} );
28792963

28802964
QUnit[ jQuery.fn.click ? "test" : "skip" ]( "trigger() shortcuts", function( assert ) {

0 commit comments

Comments
 (0)