Skip to content

Commit 980087a

Browse files
authored
Core: fix race condition in remote validation rules (jquery-validation#2435)
Fixes jquery-validation#2434 Co-authored-by: Sylvain Monné <sylvain@monne.me>
1 parent 24f2e27 commit 980087a

File tree

3 files changed

+67
-7
lines changed

3 files changed

+67
-7
lines changed

src/ajax.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Ajax mode: abort
22
// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]});
3+
// $.ajaxAbort( port );
34
// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort()
45

56
var pendingRequests = {},
@@ -10,9 +11,7 @@ if ( $.ajaxPrefilter ) {
1011
$.ajaxPrefilter( function( settings, _, xhr ) {
1112
var port = settings.port;
1213
if ( settings.mode === "abort" ) {
13-
if ( pendingRequests[ port ] ) {
14-
pendingRequests[ port ].abort();
15-
}
14+
$.ajaxAbort( port );
1615
pendingRequests[ port ] = xhr;
1716
}
1817
} );
@@ -24,12 +23,18 @@ if ( $.ajaxPrefilter ) {
2423
var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode,
2524
port = ( "port" in settings ? settings : $.ajaxSettings ).port;
2625
if ( mode === "abort" ) {
27-
if ( pendingRequests[ port ] ) {
28-
pendingRequests[ port ].abort();
29-
}
26+
$.ajaxAbort( port );
3027
pendingRequests[ port ] = ajax.apply( this, arguments );
3128
return pendingRequests[ port ];
3229
}
3330
return ajax.apply( this, arguments );
3431
};
3532
}
33+
34+
// Abort the previous request without sending a new one
35+
$.ajaxAbort = function( port ) {
36+
if ( pendingRequests[ port ] ) {
37+
pendingRequests[ port ].abort();
38+
delete pendingRequests[ port ];
39+
}
40+
};

src/core.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,9 @@ $.extend( $.validator, {
756756
val = this.elementValue( element ),
757757
result, method, rule, normalizer;
758758

759+
// Abort any pending Ajax request from a previous call to this method.
760+
this.abortRequest( element );
761+
759762
// Prioritize the local normalizer defined for this element over the global one
760763
// if the former exists, otherwise user the global one in case it exists.
761764
if ( typeof rules.normalizer === "function" ) {
@@ -1095,6 +1098,10 @@ $.extend( $.validator, {
10951098
return !$.validator.methods.required.call( this, val, element ) && "dependency-mismatch";
10961099
},
10971100

1101+
elementAjaxPort: function( element ) {
1102+
return "validate" + element.name;
1103+
},
1104+
10981105
startRequest: function( element ) {
10991106
if ( !this.pending[ element.name ] ) {
11001107
this.pendingRequest++;
@@ -1130,6 +1137,24 @@ $.extend( $.validator, {
11301137
}
11311138
},
11321139

1140+
abortRequest: function( element ) {
1141+
var port;
1142+
1143+
if ( this.pending[ element.name ] ) {
1144+
port = this.elementAjaxPort( element );
1145+
$.ajaxAbort( port );
1146+
1147+
this.pendingRequest--;
1148+
1149+
// Sometimes synchronization fails, make sure pendingRequest is never < 0
1150+
if ( this.pendingRequest < 0 ) {
1151+
this.pendingRequest = 0;
1152+
}
1153+
1154+
delete this.pending[ element.name ];
1155+
}
1156+
},
1157+
11331158
previousValue: function( element, method ) {
11341159
method = typeof method === "string" && method || "remote";
11351160

@@ -1570,7 +1595,7 @@ $.extend( $.validator, {
15701595
data[ element.name ] = value;
15711596
$.ajax( $.extend( true, {
15721597
mode: "abort",
1573-
port: "validate" + element.name,
1598+
port: this.elementAjaxPort( element ),
15741599
dataType: "json",
15751600
data: data,
15761601
context: validator.currentForm,

test/methods.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,36 @@ QUnit.test( "Fix #697: remote validation uses wrong error messages", function( a
801801
} );
802802
} );
803803

804+
QUnit.test( "Fix #2434: race condition in remote validation rules", function( assert ) {
805+
var e = $( "#username" ),
806+
done1 = assert.async(),
807+
v = $( "#userForm" ).validate( {
808+
rules: {
809+
username: {
810+
required: true,
811+
remote: {
812+
url: "users.php"
813+
}
814+
}
815+
},
816+
messages: {
817+
username: {
818+
remote: $.validator.format( "{0} in use" )
819+
}
820+
}
821+
} );
822+
823+
e.val( "Peter" );
824+
v.element( e );
825+
826+
e.val( "" );
827+
v.element( e );
828+
setTimeout( function() {
829+
assert.equal( v.errorList[ 0 ].message, "This field is required." );
830+
done1();
831+
} );
832+
} );
833+
804834
QUnit.module( "additional methods" );
805835

806836
QUnit.test( "phone (us)", function( assert ) {

0 commit comments

Comments
 (0)