# Monday, September 21, 2009

Opera 9.x, 10.x failing on Ajax history and the hack to fix it

Part 1 - Introduction
Part 2 - Basic Example
Part 3 - Complex Example
Part 4 - Final Notes
Bonus - Ajax History and the Memento Pattern
Extra Bonus - Issues with Opera

I dedicate this blog to Tomi, who brought this issue to my attention. Without I would be blissfully unaware that my ajax examples did not work with Opera.

To make a long story short, the basic Ajax history points that are demonstrated starting in this blog entry and on the site here fail miserably when used by Opera, even the most recent v 10. It would go forward once and then fail on either the next history point or going backwards. It worked fine if history was not stored.

At first I thought that it might have to do with Validation, so I turned off Event Validation and ValidateRequests but no dice. I tried modifying the LoadScriptBeforeUI setting on the ScriptManager thinking that maybe Opera was attempting to access the controls before they were instantiated. No dice.

Looking at the "Error Console" in Opera gave this confusing information:

JavaScript - http://www.myfriedmind.com/ajaxexamples/AjaxSimplehistory.aspx
Unknown thread
Error:
name: Sys.WebForms.PageRequestManagerServerErrorException
message: Sys.WebForms.PageRequestManagerServerErrorException: An unknown error occurred while processing the request on the server. The status code returned from the server was: 400
stacktrace:   Line 5 of linked script http://www.myfriedmind.com/ScriptResource.axd?d=Ca9Q30sQJzQkznL95cxsbDyIKgsvVPJZcb1rw8ROyNsMckgQG68w3ACHnEiANP
BR_VaW0NUFhahnNTwkTQZ39NfZ8sEeH0NKN9NzfqKtjhs1&t=40a63f28

    function(a,d,f){if(this._request===d.get_webRequest()){this._processingRequest=false;this._additionalInput=null;this._request=null}var e=this._get_eventHandlerList().getHandler("endRequest"),b=false;if(e){var c=new Sys.WebForms.EndRequestEventArgs(a,f?f.dataItems:{},d);e(this,c);b=c.get_errorHandled()}if(a&&!b)throw a}
.....

The key is that it was not returning the 200 that we all know and love, but a 400 status. Per this Msoft article (kb 247249) -

If the contents of a GET request are corrupt, then the server is unable to determine the URL or the host to which the GET request should be sent. IIS is unable to retrieve the host information from the GET request packet in order to look up the meta data for the custom error. This is by design. Errors that result from severely corrupted GET requests are not customizable.

Now I KNOW that they are not corrupt. It works perfectly fine with IE and Firefox. When I run it on Fiddler (http://www.fiddler2.com/) - my go-to tool for troubleshooting odd Ajax problems I do see the 400 returned. Note in the picture that the first three are for IE which work (all 200s) the second set of three is for opera - the final one being the 400 error.

When I look at the raw data of that entry it tells me that the response is that it is getting a Bad Request <Invalid URL> (see below). hmmmmm, should I trust it?

Hunting and pecking I find various entries dealing with Opera and its issues with Ajax, most of them from years ago. And then I discover this gem: http://www.webmasterworld.com/javascript/3195000.htm which has this notation near the end (italics mine)

opera has a rather annoying bug. This occurred in both 8 and 9: If the URL is exactly the same, the PHP file results are cached, and all the requests terminate as soon as the first result is returned. IE does the same, but I was able to fix it by spitting out a couple of "no-cache" headers. opera ignores these headers.

I fixed that by adding an "id=" parameter to the URL, which made each URL unique. This forced opera to stop caching.

Wha??? So I added a querystring to the end of my url (it does not matter what it is) and it worked! In other words http://www.myfriedmind.com/ajaxexamples/AjaxSimplehistory.aspx?x=y will work in Opera but http://www.myfriedmind.com/ajaxexamples/AjaxSimplehistory.aspx will not.

Now here is the odd part, and to display it I am going to direct you to a slight different simple ajax example I just whipped up -> http://www.myfriedmind.com/ajaxexamples/AjaxSimpleOpera.aspx. I am also going to give you a behind the scenes look (via fiddler) as to what, exactly, Opera is 'requesting'. The main modification I did was to change the length of the text used for the history point (I am only going to store hour, minutes, seconds to expedite this) to make it shorter and easier to view.

Again http://www.myfriedmind.com/ajaxexamples/AjaxSimpleOpera.aspx works for the first one and fails for the second.

First, let us look at what happens in IE per Fiddler (note that I only include the querystring here for comparison, this example works in IE without it).

Now the Opera per Fiddler -

Okay, okay, there was a long time between clicks (I was pasting it over), but do you notice anything different between the two (pay attention to the POST that is done via Ajax)? Internet Explorer ALWAYS Posts to the same URL - it ignores the hash entry. Opera prepends the previous hash entry after encoding it for the url, and so it gets longer and longer.

What is really, really odd is that Opera keeps prepending EVEN WHEN YOU HIT THE BACK BUTTON! So moving back and forth in your ajax history means that you are getting a longer and longer request entry. If you want to see this, snag fiddler -> http://www.fiddler2.com and try it. Your POST request gets longer and longer due to the prepending, but the URL does not reflect this at all. This ALL happens under the covers.

I finally went into Opera and tried turning off ALL caching (disk and otherwise). No luck! And when I set it to pretend to be IE it threw an error on the history point setting, claiming it was not asynchronous. I even went into opera:config under User Prefs and set the "History Navigation Mode" to 1 per this article http://www.opera.com/support/kb/view/827/ to prevent what it calls "fast history navigation". Still no dice! I even set the Opera to "Always Reload Https In History" and then connected via https to the page and that failed as well.

So why does adding a query string work? I honestly don't know. But this might be a clue - when it succeeded the hash was passed as a query string variable in Opera. When it failed, no query string was passed at all. There are a number of posts all over the web that refer to Opera caching when it should not, so I suspect that that may indeed be the issue - Opera is caching the page regardless of what it is told. Only by including the query string can you force Opera to not do whatever it is doing.

So there appear to be three solutions

  1. Don't use Ajax history for Opera unless you use a query string (it might work in systems other than .net, I have not tested this)
  2. Get Opera to fix the issue (good luck!)
  3. Make sure you have a querystring, even a nonsense one, if your users will be using Opera...

Happy coding!

----------

Further notes:

This article -> http://cfis.savagexi.com/2007/06/12/opera-ajax-and-bugs talks about an issue with Opera rejecting status codes other than 200. However that is probably not the issue since it should be returning a 200 code anyway. Just something to keep in mind.

Now I should note assuming that it was a caching issue I added a bunch of code to prevent caching - including the ones below:

Response.ClearHeaders();
Response.AppendHeader("Cache-Control", "no-cache");
Response.AppendHeader("Cache-Control", "private");
Response.AppendHeader("Cache-Control", "no-store");
Response.AppendHeader("Cache-Control", "must-revalidate");
Response.AppendHeader("Cache-Control", "max-stale=0"); 
Response.AppendHeader("Cache-Control", "post-check=0"); 
Response.AppendHeader("Cache-Control", "pre-check=0"); 
Response.AppendHeader("Pragma", "no-cache"); 
Response.AppendHeader("Keep-Alive", "timeout=3, max=993"); 
Response.AppendHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT");
Response.Cache.SetExpires(DateTime.UtcNow.AddMinutes(-1));
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetNoStore();

But again, the only thing that appears to resolve the issue is the inclusion of a query string.

All comments require the approval of the site owner before being displayed.
OpenID
Please login with either your OpenID above, or your details below.
Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, b, strike, strong) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview