I have found a problem with Firefox 3 and AJAX, and give code to
replicate it below.

The problem is when you have a resource, e.g. /docs/1234, which is
fetched both directly as a web page and as AJAX (XMLHttpRequest). The
server detects these cases based on the X-Requested-With:
XMLHttpRequest header, and sends back different content, e.g. just a
raw div or wrapped in a full HTML page.

The problem is when you use a jQuery AJAX request to fetch the page
again. If Firefox has /docs/1234 in its cache, then load('/docs/1234')
wrongly gets the non-XHR version of the page, and this is the version
it will insert into the DOM. This screws up the page, since you get a
second copy of the layout wrapper HTML inside the original layout.

The code below replicates this:
* The home page (/) fetches /docs/1234 into the lower pane using XHR
* click on the link to fetch /docs/1234 as a full HTML page
* click back, and the main page ends up inserting the full /docs/1234
HTML into the lower pane

You can see that I've set a Cache-Control: header, which ought to
prevent /docs/1234 from being cached at all, but that doesn't seem to
make a difference. You can confirm it's present using curl -v
http://127.0.0.1:5984/docs/1234

This problem doesn't occur with Opera. Looking at the server logs,
when you hit Back you see that $.load fetches a new copy of the page,
so the AJAX request isn't using a local cached copy. (This is true
even without setting the Cache-Control header)

So arguably this is a Firefox problem. However, since part of the
purpose of jQuery is to abstract away and compensate for browser
differences, I thought I'd raise it here first.

The only workaround I can think of is to use a different URL for XHR
requests, e.g. add '?xhr=true' to the end. Any other thoughts or
suggestions?

Regards,

Brian.

To run this code, you'll need ruby and 'gem install sinatra'
----- 8< ----------------------
# Program to demonstrate an issue with AJAX getting wrong page from
cache.
# Make a directory public containing jquery.js, so that you have:
#
#    js-xhr.rb          (this file)
#    public/jquery.js
#
# Then:
#    ruby js-xhr.rb
#
# and point web browser to http://127.0.0.1:4567/

require 'rubygems'
require 'sinatra'

get '/' do
  head = <<'EOS'
<script type="text/javascript" src="/jquery.js">
</script>
<script language="javascript">
$( function() {
  $('#bottom').load('/docs/1234');
});
</script>
EOS

  content = <<'EOS'
<div id='top'>
  This is the top pane. The bottom is updated by AJAX.
  <br />
  Click to go to <a href="/docs/1234">the doc page</a>,
  then click your browser's Back button
</div>
<hr />
<div id='bottom'>
</div>
EOS

  layout(content, head)
end

get '/docs/:id' do
  response.headers['Cache-Control'] = 'max-age=0, private, must-
revalidate'
  layout("<div id='docs_#{params[:id]}'>This is document with id #
{params[:id]}</div>")
end

# Apply layout to object unless request is from AJAX
def layout(content, head=nil)
  return content if request.xhr?
  <<EOS
<html>
<head>
<title>My awesome app</title>
#{head}
</head>
<body>
  <h1>My awesome app layout page</h1>
  <hr />
  #{content}
</body>
EOS
end

Reply via email to