Bug Tracker

Ticket #8177 (closed bug: patchwelcome)

Opened 4 years ago

Last modified 2 years ago

Browser inconsistencies with ajax, ifmodified, notmodified and ApplicationCache

Reported by: blabber Owned by: blabber
Priority: low Milestone: 1.next
Component: ajax Version: 1.5
Keywords: Cc: jaubourg
Blocking: Blocked by:

Description

jQuery.ajax does not return consistent results for requests on files from the ApplicationCache, online files and when setting ifModified to true or false. I have done a number of test on different browsers / operating systems. A test consists of 4 different requests, each is executed two times in a row:

  1. Read file from application cache, ifModified = false.
  2. Read file from network, ifModified = false.
  3. Read file from application cache, ifModified = true.
  4. Read file from network, ifModified = true.

The results below show 6 different results. I discovered the problem because of the problems with Android 2.2 and Safari 5.0 returning no data and 'notmodified' status for normal request. This problem only occures, if the browser is closed with the application cache filled with the test case!

The fix this, ifModified can be checked before setting the textStatus to 'notmodified'. Otherwise jQuery can just return the responseText otherwise. The patch would be to change:
if ( status === 304 ) {
to
if ( s.ifModified && status === 304 ) {

I have not further investigated the problems with ifModified set to true and I am not even sure, what the correct result should be. I would guess the first result block is ok. Firefox 4.0b, Safari, Chrome and newer androud versions seem to have some bugs?

Firefox 3.6.13 Ubuntu 10.10, Firefox 3.6.12 Windows XP, IE 8.0.7600.16385 Windows 7,
IE 9.0.7930.16406 Windows 7, Android 1.6 Browser

cached1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
cached1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
cached2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
cached2.txt  ifModified: true    success   data: -      jqXHR.responseText: -      textStatus: notmodified
online2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
online2.txt  ifModified: true    success   data: -      jqXHR.responseText: -      textStatus: notmodified


Firefox 4.0b10 Windows 7

cached1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
cached1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
cached2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
cached2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
online2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
online2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success


Google Chrome 8.0.552.237 Windows XP, Google Chrome 9.0.597.84 beta Ubuntu 10.10,
Android Browser 2.2 (? Samsung Tab)

cached1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
cached1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
cached2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
cached2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
online2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
online2.txt  ifModified: true    success   data: -      jqXHR.responseText: -      textStatus: notmodified


Android Browser 2.2 (? Samsung Tab)

ApplicationCache loaded before browser start
cached1.txt  ifModified: false   success   data: -      jqXHR.responseText: ok     textStatus: notmodified
cached1.txt  ifModified: false   success   data: -      jqXHR.responseText: ok     textStatus: notmodified
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
cached2.txt  ifModified: true    success   data: -      jqXHR.responseText: ok     textStatus: notmodified
cached2.txt  ifModified: true    success   data: -      jqXHR.responseText: ok     textStatus: notmodified
online2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
online2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success


Safari 5.0 (7533.16) Windows XP, Safari 5.03 (6533.19.4) MacOSX

ApplicationCache loaded in same browser session
cached1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
cached1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
cached2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
cached2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
online2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
online2.txt  ifModified: true    success   data: -      jqXHR.responseText: -      textStatus: notmodified

ApplicationCache loaded before browser start
cached1.txt  ifModified: false   success   data: -      jqXHR.responseText: ok     textStatus: notmodified
cached1.txt  ifModified: false   success   data: -      jqXHR.responseText: ok     textStatus: notmodified
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
cached2.txt  ifModified: true    success   data: -      jqXHR.responseText: ok     textStatus: notmodified
cached2.txt  ifModified: true    success   data: -      jqXHR.responseText: ok     textStatus: notmodified
online2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
online2.txt  ifModified: true    success   data: -      jqXHR.responseText: -      textStatus: notmodified

Change History

comment:1 follow-up: ↓ 2 Changed 4 years ago by jitter

  • Cc jaubourg added
  • Owner set to blabber
  • Status changed from new to pending
  • Component changed from unfiled to ajax
  • Priority changed from undecided to low

Thanks for taking the time to contribute to the jQuery project by writing a bug report.

It would be nice if you could provide more information so that we are able to reproduce your tests by:

  • (if possible) recreating the tests as reduced test cases on  http://jsfiddle.net
  • providing links to the "live tests" on some homepage
  • providing a link where we can download your "test suite" (consisting of all files need to reproduce your tests)

and by including/posting detailed instructions on what steps need to be taken (opening which page, refreshing, steps in which order, emptying cache, ....) to reproduce your results and at the same time describing what are the expected results for your different tests.

That would allow us to investigate this issue further, as it's difficult to tell by your description and the "output of your tests" what exactly is going on. Also make sure to read the link given below, in order to provide a most useful bug report.


 How to report bugs

Last edited 4 years ago by jitter (previous) (diff)

comment:2 in reply to: ↑ 1 Changed 4 years ago by blabber

  • Status changed from pending to new

Replying to jitter:

I may have time to setup a running test case on a public server on monday. But I think I can also just post the code inline, as it is not too complicated or long.

Just create all files in a folder and access index.html. For Android and Safari it may change the results, if you close the browser and open it again with the application cache filled.

index.html:

<html manifest="cache.manifest">

<head>
	<script src="jquery.js"></script>
</head>

<body>
<pre id="log"></pre>

<script>
function log(text) { $('#log').append(text + '<br>'); };

var i = 0;
var urls = ['cached1.txt', 'online1.txt', 'cached2.txt', 'online2.txt'];
function test() {
	if (i > 7)
		return;

	load(urls[Math.floor(i / 2)], i < 4 ? false : true);
	++i;
};

function load(url, ifModified) {
	$.ajax({
		dataType: 'text',
		ifModified: ifModified,
		url: url
	}).success(function(data, textStatus, jqXHR) {
		log(url + '  ifModified: ' + (ifModified ? 'true ' : 'false') + '   success ' +
			'  data: ' + (data === url + 'x' ? 'ok   ' : (!data ? '-    ' : 'error')) +
			'  jqXHR.responseText: ' + (jqXHR.responseText === url + 'x' ? 'ok   ' : (!jqXHR.responseText ? '-    ' : 'error')) +
			'  textStatus: ' + textStatus);
		if (jqXHR.responseText && jqXHR.responseText !== url + 'x')
			log('responseText: ' + jqXHR.responseText);
	}).error(function(jqXHR, textStatus, errorThrown) {
		log(url + '  ifModified: ' + (ifModified ? 'true ' : 'false') + '   error   ' +
			'  data: -    ' +
			'  jqXHR.responseText: ' + (jqXHR.responseText === url + 'x' ? 'ok   ' : ((!jqXHR.responseText) ? '-    ' : 'error')) +
			'  textStatus: ' + textStatus);
		if (jqXHR.responseText && jqXHR.responseText !== url + 'x')
			log('responseText: ' + jqXHR.responseText);
	}).complete(function () {
		test();
	});
};

$(document).ready(function() {
	window.setTimeout(test, 2000);
});

</script>

</body>
</html>

cache.manifest:

CACHE MANIFEST
#v1

NETWORK:
online1.txt
online2.txt

CACHE:
index.html
jquery.js
cached1.txt
cached2.txt

cached1.txt, cached2.txt, online1.txt, online2.txt Content is filename + 'x', e.g.:

cached1.txtx

and obviously you will need jquery.js in the folder.

Last edited 4 years ago by blabber (previous) (diff)

comment:3 Changed 4 years ago by jaubourg

  • Status changed from new to closed
  • Resolution set to fixed

Fixes #8177. XHR transport now considers 304 Not Modified responses as 200 OK if no conditional request header was provided (as per the XMLHttpRequest specification).

Changeset: d6fbbe1080fdcaf8eb22753eddf000aeb7d99545

comment:4 follow-up: ↓ 5 Changed 4 years ago by jaubourg

  • Milestone changed from 1.next to 1.5.1

blabber, can you confirm this fixes your issue?

comment:5 in reply to: ↑ 4 ; follow-up: ↓ 8 Changed 4 years ago by blabber

Edit: Tested the wrong version, with the fix I mentioned above. Sorry. The current git-Version does not fix the bug, but seems to give empty data and textStatus:

Safari (page loaded, Safari closed, Safari opened, page loaded again):

cached1.txt  ifModified: false   error     data: -      jqXHR.responseText: ok     textStatus: 
cached1.txt  ifModified: false   error     data: -      jqXHR.responseText: ok     textStatus: 
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
cached2.txt  ifModified: true    error     data: -      jqXHR.responseText: ok     textStatus: 
cached2.txt  ifModified: true    error     data: -      jqXHR.responseText: ok     textStatus: 
online2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
online2.txt  ifModified: true    success   data: -      jqXHR.responseText: -      textStatus: notmodified

(Edit: removed old content)

Replying to jaubourg:

blabber, can you confirm this fixes your issue?

Last edited 4 years ago by blabber (previous) (diff)

comment:6 Changed 4 years ago by jaubourg

  • Status changed from closed to reopened
  • Resolution fixed deleted

comment:7 Changed 4 years ago by jaubourg

  • Owner changed from blabber to jaubourg
  • Status changed from reopened to assigned

comment:8 in reply to: ↑ 5 ; follow-up: ↓ 9 Changed 4 years ago by jaubourg

Replying to blabber:

Edit: Tested the wrong version, with the fix I mentioned above. Sorry. The current git-Version does not fix the bug, but seems to give empty data and textStatus:

Safari (page loaded, Safari closed, Safari opened, page loaded again):

cached1.txt  ifModified: false   error     data: -      jqXHR.responseText: ok     textStatus: 
cached1.txt  ifModified: false   error     data: -      jqXHR.responseText: ok     textStatus: 
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
online1.txt  ifModified: false   success   data: ok     jqXHR.responseText: ok     textStatus: success
cached2.txt  ifModified: true    error     data: -      jqXHR.responseText: ok     textStatus: 
cached2.txt  ifModified: true    error     data: -      jqXHR.responseText: ok     textStatus: 
online2.txt  ifModified: true    success   data: ok     jqXHR.responseText: ok     textStatus: success
online2.txt  ifModified: true    success   data: -      jqXHR.responseText: -      textStatus: notmodified

That was fast. :-)
The patch fixes the browser differences for the cases with ifModified=false. The differences found with ifModified=true remain as above.

Replying to jaubourg:

blabber, can you confirm this fixes your issue?

I'm confused as to what you say here. What is fixed? What isn't fixed?

Would also help a bunch if your log lines were a bit more compact and featured the status code and error message. For instance:

cached1.text(true/false): statusCode, status (thirdParameterOfErrorCallbackIfError), data, jqXHR.responseText

This would definitely help me tackle this while not having to duplicate your effort.

comment:9 in reply to: ↑ 8 Changed 4 years ago by blabber

This bug probably collects a number of differences in the way that browsers react to ajax request on files from the ApplicationCache and files from the network. The list in the original bug report gives the complete picture across a number of browser.

At least the different browser reactions to request with ifModified == true should be fixable, because the browsers provide all necessary information. I have provided a simple patch in the original bug report. I have no idea, if it is correct, but it works for my current project (giving success for all cases where ifModified == false).

There are more different cases, when ifModified == false, but I do not even know for sure, how jQuery should react in these cases.

Here are my current test results, Run 1 is identical for the current trunk and jQuery 1.5.

Run 1: Safari startet __without__ test page in ApplicationCache:

   File         ifMod  callback  status  data  respTxt  textStatus   errorThrown
1  cached1.txt  false  success   200     ok    ok       success
2  cached1.txt  false  success   200     ok    ok       success
3  online1.txt  false  success   200     ok    ok       success
4  online1.txt  false  success   200     ok    ok       success
5  cached2.txt  true   success   200     ok    ok       success
6  cached2.txt  true   success   200     ok    ok       success
7  online2.txt  true   success   200     ok    ok       success
8  online2.txt  true   success   304     -     -        notmodified


Run 2: Safari startet __with__ test page in ApplicationCache:

jQuery Trunk 534dbd660eaefbbc5827b1b61ba384e768562086
   File         ifMod  callback  status  data  respTxt  textStatus   errorThrown
1  cached1.txt  false  error     0       -     ok               
2  cached1.txt  false  error     0       -     ok               
3  online1.txt  false  success   200     ok    ok       success
4  online1.txt  false  success   200     ok    ok       success
5  cached2.txt  true   error     0       -     ok               
6  cached2.txt  true   error     0       -     ok               
7  online2.txt  true   success   200     ok    ok       success
8  online2.txt  true   success   304     -     -        notmodified

jQuery 1.5
   File         ifMod  callback  status  data  respTxt  textStatus   errorThrown
1  cached1.txt  false  success   304     -     ok       notmodified
2  cached1.txt  false  success   304     -     ok       notmodified
3  online1.txt  false  success   200     ok    ok       success
4  online1.txt  false  success   200     ok    ok       success
5  cached2.txt  true   success   304     -     ok       notmodified
6  cached2.txt  true   success   304     -     ok       notmodified
7  online2.txt  true   success   200     ok    ok       success
8  online2.txt  true   success   304     -     -        notmodified

index.html, other files as described above:

<html manifest="cache.manifest">

<head>
	<script src="jquery.js"></script>
</head>

<body>
<pre id="log">
   File         ifMod  callback  status  data  respTxt  textStatus   errorThrown
</pre>

<script>
function log(text) { $('#log').append(text + '<br>'); };

var i = 0;
var urls = ['cached1.txt', 'online1.txt', 'cached2.txt', 'online2.txt'];
function test() {
	if (i > 7)
		return;

	load(urls[Math.floor(i / 2)], i < 4 ? false : true);
	++i;
};

function load(url, ifModified) {
	$.ajax({
		dataType: 'text',
		ifModified: ifModified,
		url: url
	}).success(function(data, textStatus, jqXHR) {
		log(i + '  ' + url + '  ' + (ifModified ? 'true  ' : 'false ') + ' success   ' + jqXHR.status +
			'     ' + (data === url + 'x' ? 'ok ' : (!data ? '-  ' : 'err')) +
			'   ' + (jqXHR.responseText === url + 'x' ? 'ok ' : (!jqXHR.responseText ? '-  ' : 'err')) +
			'      ' + textStatus);
		if (jqXHR.responseText && jqXHR.responseText !== url + 'x')
			log('responseText: ' + jqXHR.responseText);
	}).error(function(jqXHR, textStatus, errorThrown) {
		log(i + '  ' + url + '  ' + (ifModified ? 'true  ' : 'false ') + ' error     ' + jqXHR.status +
			'       -  ' +
			'   ' + (jqXHR.responseText === url + 'x' ? 'ok ' : (!jqXHR.responseText ? '-  ' : 'err')) +
			'      ' + textStatus + '        ' + errorThrown);
		if (jqXHR.responseText && jqXHR.responseText !== url + 'x')
			log('responseText: ' + jqXHR.responseText);
	}).complete(function () {
		test();
	});
};

$(document).ready(function() {
	window.setTimeout(test, 1000);
});

</script>

</body>
</html>

comment:10 follow-up: ↓ 11 Changed 4 years ago by jaubourg

blabber, status for local files should always be 200 now. Be aware that empty files will trigger an error (that's due to some serious limitations in native xhr implementations). Waiting for your confirmation/infirmation.

comment:11 in reply to: ↑ 10 Changed 4 years ago by blabber

Replying to jaubourg:

blabber, status for local files should always be 200 now. Be aware that empty files will trigger an error (that's due to some serious limitations in native xhr implementations). Waiting for your confirmation/infirmation.

Unfortunately that did not work. I have written a new patch for the current trunk, that tries to handle some of the cases in xhr.js. The patch does not change as much, as it seems to change, but mostly restores old tests and combines them in a slightly different way.

With the patch most browsers seem to work with ifModified = false. Case 6 still differs between browsers or even between access to localhost or access to ip address (for Firefox 3.6.13). Opera also acts chaotic (success with ip, error with localhost, error with ip from virtual machine...). Perhaps all this are just ApplicationCache problems. After all my tests I do not really trust any browser in this regard... ;-)

I will use my patched version of the trunk for now and give further feedback, if I run into any obvious problems, but do not have the time at the moment to do more extensive cross-browser ... tests. :-/

diff --git a/src/ajax/xhr.js b/src/ajax/xhr.js
index 9c8790a..0bad811 100644
--- a/src/ajax/xhr.js
+++ b/src/ajax/xhr.js
@@ -166,14 +166,30 @@ if ( jQuery.support.ajax ) {
 									}
 
 									// Filter status for non standard behaviors
-									status =
-										// If the request is local and we have data: assume a success
-										// (success with no data won't get notified, that's the best we
-										// can do given current implementations)
-										!status && s.isLocal && !s.crossDomain ?
-										( responses.text ? 200 : 404 ) :
-										// IE - #1450: sometimes returns 1223 when it should be 204
-										( status === 1223 ? 204 : status );
+
+									// IE - #1450: sometimes returns 1223 when it should be 204
+									if ( status === 1223 ) {
+										status = 204;
+									// If the request is local and we have data: assume a success
+									// (success with no data won't get notified, that's the best we
+									// can do given current implementations)
+									} else if ( !status ) {
+										// Webkit, Firefox: filter out faulty cross-domain requests
+										if ( !s.crossDomain || statusText ) {
+											// Opera: filter out real aborts #6060
+											if (responseHeaders || xml) {
+												status =
+													!headers[ "if-modified-since" ] && !headers[ "if-none-match" ] ?
+														200
+														: 304;
+											} else {
+												status = 0;
+											}
+										// We assume 302 but could be anything cross-domain related
+										} else {
+											status = 302;
+										}
+									}
 								}
 							}
 						} catch( firefoxAccessException ) {

comment:12 follow-up: ↓ 14 Changed 4 years ago by jaubourg

  • Owner changed from jaubourg to blabber
  • Milestone changed from 1.5.1 to 1.next

blabber, with current Trunk, you'll always get 200 OK for local files as long as they are not empty. This is because browser's xhr implementations are completely uninformative when it comes to the file system (no headers, no meaningful status code -- always 0 --, etc).

As of today, there is no mean to differentiate between all the different cases encompassed by a zeroed status code without breaking something, elsewhere, for another browser (and in the case of Opera, for the same browser).

I'll close this as patchwelcome but I don't expect one until browsers fix their many xhr inconsistencies.

Last edited 4 years ago by jaubourg (previous) (diff)

comment:13 Changed 4 years ago by jaubourg

  • Status changed from assigned to closed
  • Resolution set to patchwelcome

comment:14 in reply to: ↑ 12 Changed 4 years ago by blabber

Replying to jaubourg:

blabber, with current Trunk, you'll always get 200 OK for local files as long as they are not empty.

I am not talking about local files, but about the ApplicationCache. That may be local, but is probably not handled the same way as local files. Have you changed the trunk again, to really return 200 Ok for all files from the application cache? If not, my test results from above are probably still valid. The last time I checked the trunk, the results where worse than in 1.5, because the request returned an error instead of success.

I'll close this as patchwelcome but I don't expect one until browsers fix their many xhr inconsistencies.

I have provided 2 patches, one for the trunk and one for 1.5, that let the browsers return 200 Ok for files from the application cache. Did you use the information from these patches, to fix the problems? Did you setup the test case? If not: How can I help? What is wrong with my fixes? Did I miss any important points?

I am really trying to help...

comment:15 Changed 4 years ago by jaubourg

@blabber,

I'm all for fixing bugs, believe me, but your patch worked only because of a very serious bug in 1.5 that made any failing ajax request from the local filesystem (and apparently Application Cache) issue a 304.

The situation with the native xhr status set to 0 with no mean to determine if it is a success or an error makes it impossible to lay out any kind of consistant logic that wouldn't break something elsewhere.

If the Application Cache issues a 0 status-code in a non local filesystem environment then we just can't assume it is a 200 OK: this would basically break every other environment.

Like I said, it's patch-welcome but I'm sorry to say it's probably up to the browsers' developpers at this point... and from what I've seen the last few months, I don't believe adding consistency to their xhr implementations to be a priority... which incidently happens to turn my life into a nightmare.

comment:16 Changed 3 years ago by chrelad@…

I am also seeing this problem. When I make a request for a resource that is in the ApplicationCache, I receive "success" the first time with the data being passed to the "success" callback. It even works if I hit refresh a bunch of times, but when I close the browser (android) and start it again; I get "notmodified" as the status text and "undefined" as the data.

Note: See TracTickets for help on using tickets.