12

在 OSS Web 应用程序中,我们有执行一些 Ajax 更新的 JS 代码(使用 jQuery,不相关)。页面更新后,调用 html5 历史接口History.pushState,代码如下:

var updateHistory = function(url) {
    var context = { state:1, rand:Math.random() };
    /* -----> bedfore the problem call <------- */
    History.pushState( context, "Questions", url );
    /* -----> after the problem call <------- */
    setTimeout(function (){
        /* HACK: For some weird reson, sometimes something overrides the above pushState so we re-aplly it
                 This might be caused by some other JS plugin.
                 The delay of 10msec allows the other plugin to override the URL.
        */
        History.replaceState( context, "Questions", url );
    }, 10);
};

[请注意:为上下文提供了完整的代码段,HACK 部分不是这个问题的问题]

该应用程序是 i18n'ed 并且在 URL 中使用 URL 编码的 Unicode 段,因此就在上述代码中标记的问题调用之前,URL 参数包含(在 Firebug 中检查):

"/%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9/scope:all/sort:activity-desc/page:1/"

编码段是 utf-8 百分比编码。浏览器窗口中的 URL 是:(只是为了完整性,并不重要)

http://<base-url>/%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9/

调用后,浏览器窗口中显示的 URL 变为:

http://<base-url>/%C3%98%C2%A7%C3%99%C2%84%C3%98%C2%A3%C3%98%C2%B3%C3%98%C2%A6%C3%99%C2%84%C3%98%C2%A9/scope:all/sort:activity-desc/page:1/

URL 编码段只是 mojibake,在某种程度上使用错误编码的结果。正确的 URL 应该是:

http://<base-url>/%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9/scope:all/sort:activity-desc/page:1/

此行为已在 FF 和 Chrome 上进行了测试。

历史接口规范没有提到任何关于编码 URL 的内容,但我假设 URL 形成的默认标准(utf-8 和百分比编码等)在使用接口的函数调用中的 URL 时将适用。

关于这里发生的事情的任何想法。

编辑

我没有注意 History 中的大写 H - 此代码实际上使用History.js包装器作为历史接口。我替换为直接调用history.pushState(注意小写的 h)而不通过包装器,据我所知,代码按预期工作。原始代码的问题仍然存在 - 所以 History.js 库似乎存在问题。

4

2 回答 2

9

更新

正如Doug S在下面的评论中所解释的那样,最新版本的 History.js 包含对此行为的修复。他还发现我的解决方案在需要哈希回退的浏览器(例如 IE 9 及以下版本)中使用时会导致双重编码,因此我建议不要使用下面详述的修复程序,而只需下载最新版本即可。

我在下面保留了我的原始答案,因为它确实更详细地解释了正在发生的事情。


巴塞尔找到了某种解决方案,但对于幕后发生的事情仍然有些困惑。该答案详细介绍了该问题并提出了更好的解决方案。(如果需要,您可以直接跳到修复程序。)

问题

首先,打开浏览器的 JS 控制台并运行:

window.encodeURI(window.unescape('%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9'))

是不是很眼熟?它应该——这就是你的 URL 被破坏的内容。问题在于 的实现History.unescapeString,特别是这一行:

tmp = window.unescape(result);

window.unescape是一个DOM Level 0函数——也就是说,从 Netscape 2 的古老时代开始的非标准化遗物。它使用RFC 2396中定义的转义规则,根据未保留范围之外的字符(字母数字和一小部分标点符号)符号)被编码为八位字节。

这适用于 US-ASCII 范围,但并非 UTF-8 中的所有(实际上是绝大多数)字符都可以用单个字节表示。由于 URI 没有内置的方式来表示正在使用的字符集,因此window.unescape只需假设每个字符都映射到单个八位字节并愉快地破坏任何没有的字符。

在此示例中,您的 URL 中的第一个字母是阿拉伯字母 alef (ا),由两个字节表示:0xD8 0xA7window.unescape将它们解释为两个单独的字符:0x00 0xD8(Ø- 大写 O 带笔划)0x00 0xA7(§- 节符号)

这是 History.js 的一个已知问题

修复

正如提问者在上面提到的,可以通过使用 History API 的本机实现而不是 History.js 包装器来回避这个问题,即,history.pushState而不是History.pushState.

这适用于支持 History API 的浏览器,但失去了为不支持的浏览器提供 polyfill 的好处。幸运的是,有一个更好的解决方法。打开您引用的 History.js 源代码并找到这一行(在我的副本中约为 1059):

tmp = window.unescape(result);

将其替换为:

tmp = window.unescape(encodeURIComponent(result));

或者,如果您使用的是压缩源,请替换a.unescape(c)a.unescape(encodeURIComponent(c)).

为了测试这种变化,我在本地 Web 服务器上以阿拉伯​​语命名的目录中运行 History.js HTML5 jQuery 测试套件。在进行更改之前,测试 14 失败;更改后,所有测试都通过了。

信用

尽管我独立地找到了问题和解决方案,但Damien Antipa值得称赞的是首先找到它并使用 fix 提出拉取请求

于 2013-05-16T11:57:07.970 回答
1

在以下情况下,我仍然可以重现此情况:

History.pushState(null, null, "?" + some_Unicode_String_Or_A_String_With_Whitespace);
document.location.hash += "&someStuff";

在这种情况下,_suid 参数和 &someStuff 也被删除。如果字符串不是 unicode 或没有空格(所以没有 % 字符) - 这不会发生。

这种解决方法对我有用:

History.pushState(null, null, "?" + some_Unicode_String_Or_A_String_With_Whitespace + "&someStuff");
于 2014-01-09T09:05:55.057 回答