在 Safari 中,如果存在 iframe
加载,并且用户通过后退或前进更改历史记录状态,则不会触发 popstate
事件,从而导致应用程序状态和窗口位置不同步。
我认为活跃的 XHR 请求会导致相同的行为,但我还无法确认这一点。
这是一个 jsfiddle,可以让您轻松重现该问题:
http://jsfiddle.net/neonsilk/muHk8/
您只需单击中的链接/按钮顺序,从 1 到 6。
在 Safari 5.0.5 中,输出为:(
按时间倒序,重要部分是顶部的状态比较)
[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
---------------
[1305665486606] $.address change: /
---------------
[1305665485732] iframe loaded
---------------
[1305665485569] $.address change: /neonsilk/muHk8/show/
[1305665485568] $.address init
然而在 Chrome (11) 或 FireFox 中(4.0),输出如下所示:(
请注意,状态是同步的)
[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/
这是 Safari 中的错误吗?如果是的话,有人发现了解决方法吗?
(同样有趣的是,FireFox 和 Chrome 都会在页面加载时触发 popstate
事件。)
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
---------------
[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.)
发布评论
评论(2)
更新
据我所知,这个错误是首先由 Ben Cherry 报告的< /a>.
这是相应的错误报告和变更集。
以及来自 Chromium 的问题。
我一直无法弄清楚 Chrome 中的错误何时被修复。如果其他人有,我将非常感激这些信息。
我确实知道该错误存在于 WebKit 533.21.1(Safari 5.0.5 使用的)中,并且已被 534.36(Safari/WebKit nightly)修复——但我不知道,也无法解决找出哪个中间版本引入了修复程序。
有一个一个有用的图表,将 Chrome 版本映射到其 WebKit 版本。
最重要,当存在任何活动网络流量(图像加载、Ajax 请求等)而不仅仅是 iframe 时,此错误显然会发生。如果您正在尝试实现 History API 支持,或者您正在使用最新版本的 Asual 的 jQuery 地址插件(默认情况下启用历史记录支持),此错误可能会严重影响您的应用程序。
第二次更新
我想我找到了引入修复程序的确切 WebKit 版本 (534.10)。所以,如果您正在使用 jQuery 和 Asual 的 Address 插件,这里是实用的解决方案:
我希望这对某人有帮助!
我刚刚在最新的 WebKit nightly (r86671) 中测试了 fiddle,并且状态保持同步,所以它一定是一个错误。有趣的是,花费大量时间写下问题并立即提交会激发答案。
尽管如此,如果有人有解决方法,我们将不胜感激。
WebKit nightly (r86671):
同样有趣的是,Safari 现在与 Chrome 和 FireFox 的行为相匹配,在页面加载时触发 popstate。
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:
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):
Also interesting that Safari now matches the behavior of Chrome and FireFox, firing popstate on page load.
(在 Android 2.2 和 iOS 4.3.3 上的 Safari 下测试)
因此解决方法是:
使用 XHR 而不是 img 来跟踪/预加载加载事件后的任何内容。
如果用户在加载事件之前执行后退/前进两次或更多次,我们可能仍然会遇到问题,但是如果加载时间很短,用户很少会执行多个操作,并且因为最终状态是正确的,所以我认为这是可以的。
另一个解决方法是:
使用计时器监视 url,如果在没有 popstate 的情况下更改了 url,请自行完成脏工作。 (正如这个bug的评论,Facebook曾经使用过这个解决方案。)
但是这个方法太复杂了,因为你应该将状态序列化/反序列化为url或localstorage/sessionstorage,就像你没有History API一样。所以我认为这不是一个可以接受的解决方法。
(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.