From eabec62065ca694b48d23e4cc224ecdb2731522a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sylvain=20Monn=C3=A9?= Date: Fri, 15 Jul 2022 18:52:12 +0200 Subject: [PATCH] Core: fix race condition in remote validation rules Fixes #2434 --- src/ajax.js | 17 +++++++++++------ src/core.js | 27 ++++++++++++++++++++++++++- test/methods.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/ajax.js b/src/ajax.js index fb56de4b3..5f87bed20 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -1,5 +1,6 @@ // Ajax mode: abort // usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); +// $.ajaxAbort( port ); // if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() var pendingRequests = {}, @@ -10,9 +11,7 @@ if ( $.ajaxPrefilter ) { $.ajaxPrefilter( function( settings, _, xhr ) { var port = settings.port; if ( settings.mode === "abort" ) { - if ( pendingRequests[ port ] ) { - pendingRequests[ port ].abort(); - } + $.ajaxAbort( port ); pendingRequests[ port ] = xhr; } } ); @@ -24,12 +23,18 @@ if ( $.ajaxPrefilter ) { var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, port = ( "port" in settings ? settings : $.ajaxSettings ).port; if ( mode === "abort" ) { - if ( pendingRequests[ port ] ) { - pendingRequests[ port ].abort(); - } + $.ajaxAbort( port ); pendingRequests[ port ] = ajax.apply( this, arguments ); return pendingRequests[ port ]; } return ajax.apply( this, arguments ); }; } + +// Abort the previous request without sending a new one +$.ajaxAbort = function( port ) { + if ( pendingRequests[ port ] ) { + pendingRequests[ port ].abort(); + delete pendingRequests[ port ]; + } +}; diff --git a/src/core.js b/src/core.js index ddb258119..923b3012b 100644 --- a/src/core.js +++ b/src/core.js @@ -756,6 +756,9 @@ $.extend( $.validator, { val = this.elementValue( element ), result, method, rule, normalizer; + // Abort any pending Ajax request from a previous call to this method. + this.abortRequest( element ); + // Prioritize the local normalizer defined for this element over the global one // if the former exists, otherwise user the global one in case it exists. if ( typeof rules.normalizer === "function" ) { @@ -1095,6 +1098,10 @@ $.extend( $.validator, { return !$.validator.methods.required.call( this, val, element ) && "dependency-mismatch"; }, + elementAjaxPort: function( element ) { + return "validate" + element.name; + }, + startRequest: function( element ) { if ( !this.pending[ element.name ] ) { this.pendingRequest++; @@ -1130,6 +1137,24 @@ $.extend( $.validator, { } }, + abortRequest: function( element ) { + var port; + + if ( this.pending[ element.name ] ) { + port = this.elementAjaxPort( element ); + $.ajaxAbort( port ); + + this.pendingRequest--; + + // Sometimes synchronization fails, make sure pendingRequest is never < 0 + if ( this.pendingRequest < 0 ) { + this.pendingRequest = 0; + } + + delete this.pending[ element.name ]; + } + }, + previousValue: function( element, method ) { method = typeof method === "string" && method || "remote"; @@ -1570,7 +1595,7 @@ $.extend( $.validator, { data[ element.name ] = value; $.ajax( $.extend( true, { mode: "abort", - port: "validate" + element.name, + port: this.elementAjaxPort( element ), dataType: "json", data: data, context: validator.currentForm, diff --git a/test/methods.js b/test/methods.js index fa49939e4..2966ec25f 100644 --- a/test/methods.js +++ b/test/methods.js @@ -801,6 +801,36 @@ QUnit.test( "Fix #697: remote validation uses wrong error messages", function( a } ); } ); +QUnit.test( "Fix #2434: race condition in remote validation rules", function( assert ) { + var e = $( "#username" ), + done1 = assert.async(), + v = $( "#userForm" ).validate( { + rules: { + username: { + required: true, + remote: { + url: "users.php" + } + } + }, + messages: { + username: { + remote: $.validator.format( "{0} in use" ) + } + } + } ); + + e.val( "Peter" ); + v.element( e ); + + e.val( "" ); + v.element( e ); + setTimeout( function() { + assert.equal( v.errorList[ 0 ].message, "This field is required." ); + done1(); + } ); +} ); + QUnit.module( "additional methods" ); QUnit.test( "phone (us)", function( assert ) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy