开发者

Why isn't popstate triggered in Safari when an iframe is loading?

In Safari, if there's an iframe loading, and the user changes the history state by going back or forward, the popstate event isn't triggered, causing the application state and the window location to be out of sync.

I think that an active XHR request will cause the same behavior, but I haven't been able to confirm that yet.

Here's a jsfiddle that will allow you to easily reproduce the issue: http://jsfiddle.net/neonsilk/muHk8/

You just have to click the links/buttons in order, from 1 to 6.

In Safari 5.0.5, the output is:

(reverse chronological, the important part is the state comparison at the top)

[1305665493096] /
[1305665493096] vs
[1305665493096] /node
---------------
[1305665489955] iframe loaded
---------------
[1305665489806] (did popstate or $.address change trigger?)
[1305665489805] called history.back()
[1305665489805] appended iframe
---------------
[1305665488428] popstate: /node
[1305665488427] $.address change: /node
---------------
[1305665487821] popstate: /
[1305665487821] $.address change: /
---------------
[1305665487179] $.address change: /node
-----开发者_StackOverflow----------
[1305665486606] $.address change: /
---------------
[1305665485732] iframe loaded
---------------
[1305665485569] $.address change: /neonsilk/muHk8/show/
[1305665485568] $.address init

Yet in Chrome (11) or FireFox (4.0), the output looks like:

(note that the states are in sync)

[1305665609499] /
[1305665609499] vs
[1305665609499] /
---------------
[1305665608360] iframe loaded
---------------
[1305665607770] popstate: /
[1305665607770] $.address change: /
[1305665607758] (did popstate or $.address change trigger?)
[1305665607758] called history.back()
[1305665607758] appended iframe
---------------
[1305665606870] popstate: /node
[1305665606869] $.address change: /node
---------------
[1305665606150] popstate: /
[1305665606149] $.address change: /
---------------
[1305665605551] $.address change: /node
---------------
[1305665604808] $.address change: /
---------------
[1305665603354] iframe loaded
---------------
[1305665602688] $.address change: /neonsilk/muHk8/show/
[1305665602682] $.address init
[1305665602676] popstate: /neonsilk/muHk8/show/

Is this a bug in Safari? And if it is, has anyone discovered a workaround?

(It's also interesting that both FireFox and Chrome fire a popstate event on page load.)


Update

This bug was, as far as I can tell, first reported by Ben Cherry.

Here is the corresponding bug report and changeset from WebKit.

And the issue from Chromium.

I haven't been able to figure out when the bug was fixed in Chrome. If anyone else has, I would greatly appreciate the info.

I do know that the bug exists in WebKit 533.21.1 (which is what Safari 5.0.5 uses), and has been fixed by 534.36 (Safari/WebKit nightly) -- but I don't know, and haven't been able to figure out, which intermediary build introduced the fix.

There is a helpful chart that maps the Chrome version to its WebKit version.

Most importantly, this bug apprently occurs when there is any active network traffic (images loading, Ajax requests, etc.), and not just iframes. If you are trying to implement History API support, or you are using the latest version of Asual's jQuery Address plugin (which has history support enabled by default), this bug could seriously impact your application.


2nd Update

I think I tracked down the exact WebKit version where the fix was introduced (534.10). So, if you are using jQuery and Asual's Address plugin, here's the practical solution:

if (!($.browser.webkit === true && parseFloat($.browser.version) < 534.10)) {
    // only enable state support if WebKit version >= 534.10
    $.address.state("/base/path");
}

I hope this helps someone!


I just tested the fiddle in the latest WebKit nightly (r86671), and the state stays in sync, so it must be a bug. Funny how spending a lot of time writing up a question and submitting it immediately inspires the answer.

Still, if anyone has a workaround, it would be greatly appreciated.

WebKit nightly (r86671):

[1305666598481] /
[1305666598481] vs
[1305666598480] /
---------------
[1305666597469] iframe loaded
---------------
[1305666597300] popstate: /
[1305666597298] $.address change: /
[1305666597270] (did popstate or $.address change trigger?)
[1305666597269] called history.back()
[1305666597269] appended iframe
---------------
[1305666596605] popstate: /node
[1305666596605] $.address change: /node
---------------
[1305666596008] popstate: /
[1305666596008] $.address change: /
---------------
[1305666595555] $.address change: /node
---------------
[1305666595142] $.address change: /
---------------
[1305666578600] iframe loaded
---------------
[1305666578400] $.address change: /_display/
[1305666578400] $.address init
---------------
[1305666577964] popstate: /_display/

Also interesting that Safari now matches the behavior of Chrome and FireFox, firing popstate on page load.


  1. Img/script/iframe loading will break popstate, but XHR is NOT affected by this bug!
  2. back/forward before document load will be delayed. We will get ONE popstate when load. (only the last state will be pop even there are multiple back/forward.)

(Tested under Android 2.2 and Safari on iOS 4.3.3)

So the workaround is:

Use XHR instead of img for tracking/preload whatever after load event.

We may still meet problem if user do back/forward twice or more before load event, but user will rarely do multiple actions if load time is short, and because the final state is correct, so I think it's ok.

And another workaround is:

Watching the url using timer, if it's changed without popstate, do dirty work by yourself. (As the comments of this bug, Facebook used to use this solution.)

But this method is too complicated because you should serialize/deserialize the state to url or localstorage/sessionstorage, just like you don't have History API. So I don't think it's a acceptable workaround.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜