Wow what a challenging issue to troubleshoot this week.  Here was the scenario:

AbleCommerce 7 client was reporting certain shoppers could not complete checkout.  The catch was, it was only happening on Safari and IE8 users.  But mostly Safari.  You’d get an error after entering the credit card information and couldn’t proceed to the receipt page.

The shoppers using Safari could reach checkout.  They could hit the 2nd page of checkout.  But after clicking the Pay with Credit Card button, nothing happened.  Scrolling to the top of the page reveals a new message on the page stating the basket contents had changed.  Repeated clicks resulted in the same.

We had done some recent enhancements to the checkout page.  But they were purely visual.  There weren’t any changes to the functionality of the page, just added images to the basketgrid and some improved CSS.  We certainly weren’t manipulating basket contents during checkout (Big NO NO), so we were baffled as to the cause.

We walked back through the version-control repository looking for every change in the last 6 months.  Nothing affecting the basket was found.

We crawled through the rendered page source looking for anything out of the ordinary.  Nothing stood out.  We couldn’t even reproduce the issue on our local developer copies of the site.

Finally, after reloading Safari on my Windows 7 64-bit PC, I got the issue to happen.  Bingo!  Now I can light up the issue through the debugger and see what the heck is going on.

In the checkout code, the message only renders if the basket contents in the previous postback are different from the current postback basket contents.  Able accomplishes this by generating a hash value of the basket and storing it in the page ViewState.

While walking through the code, I noticed the ShipmethodId on the the basket shipments was getting reset to 0.  Having a ShipMethodId of 0 means no shipping method assigned.  But in the initial page_load(), able sets a default shipping method to each shipment.  So how was this possible?

After walking a bunch of code with the visual studio debugger, I finally notice something totally bizarre.  After clicking the Continue button on Page 1 of checkout, Page.IsPostback() was reporting FALSE.  Do what??  How can it be false??  It’s the same page???

A breakpoint on the Page_Load() routine gave me the answer.  I could actually see Page_Load() getting hit twice in the same single postback.  The first time, Page.IsPostBack() reported True as it should.  But the second hit in Page_Load() reported False, like it was a brand new hit to the whole page!

So Safari was firing it’s own postback after the initial postback from the Continue button.  And this postback did not include ViewState.  Without ViewState, Page.IsPostback() reports false and thinks it’s a first-time load of the page.  A first time load of the page causes Able to reset the ShipMethodId on the basket shipments.  Thus the error on Page 2 of checkout about the basket changing…..

About the same time, I just happened to notice something odd about Page 2 of checkout.  The basket summary didn’t have any images.  I had paused the code in Page_Load() during the rogue postback.  Everything else on the page was rendered, but the images were missing.  We had indeed modified that basket summary to render images.  But it was simple change – just an evaluation expression calling a code-behind return that returns an image URL for the Container.DataItem.  no biggie right?

I commented out the <asp:Image> object in the basket summary, and suddenly the rogue postback stopped firing.  The checkout page started working exactly as it should.

Now I know the cause.  But I don’t know the reason.  So I did a little digging and learned something.

ASP.Net stores the viewstate in a HTML Hidden Field tag embedded in the HTML response.  Apparently, in certain circumstances, Safari has a size limit to hidden fields.  My guess is, somehow adding the images to the page caused the viewstate to grow beyond this hidden field size limit imposed by Safari.

I wish I knew more about it.  But what a crazy amount of time it took to troubleshoot this issue.  At least we got it figured out and the client website is back to working normal again.