12

OSS Web アプリには、Ajax 更新を実行する JS コードがあります (jQuery を使用しますが、関係ありません)。History.pushStateページの更新後、次のコードでhtml5 history インターフェイスへの呼び出しが行われます。

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 部分はこの質問の問題ではありません]

アプリは国際化されており、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 エンコードされたセグメントは単なる文字化けであり、あるレベルで間違ったエンコードを使用した結果です。正しい 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 を使用する場合は、URL 形成のデフォルトの標準 (utf-8 やパーセント エンコーディングなど) が適用されると思います。

ここで何が起こっているかについての考え。

編集

History の大文字の H には注意を払っていませんでした。このコードは、実際には history インターフェースにHistory.jsラッパーを使用しています。ラッパーを介さずに (小文字の h に注意してください)への直接呼び出しに置き換えましたhistory.pushState。コードは、私が知る限り、期待どおりに機能しています。元のコードの問題はまだ残っているため、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.unescapeDOM レベル 0の機能です。つまり、Netscape 2 の白紙の時代の標準化されていない遺物です。RFC 2396で定義されているエスケープ規則を使用し、予約されていない範囲外の文字 (英数字と句読点の小さなセット)シンボル) はオクテットとしてエンコードされます。

これは US-ASCII 範囲では問題なく機能しますが、UTF-8 の文字のすべて (実際、大部分) を 1 バイトで表現できるわけではありません。URI には、使用されている文字セットを表す組み込みの方法がないため、window.unescape各文字が 1 つのオクテットにマップされていると想定し、そうでないものは無愛想にマングルします。

この例では、URL の最初の文字はアラビア文字のアレフ (ا)であり、2 バイトで表されます: 0xD8 0xA7. window.unescapeは、これらを 2 つの別個の文字として解釈します: 0x00 0xD8(Ø-ストロークのある大文字の O)0x00 0xA7(§-セクション記号)

これはHistory.jsの既知の問題です。

修正

上記の質問者が指摘したように、この問題は、History.js ラッパーの代わりに History API のネイティブ実装を使用することで回避できhistory.pushStateますHistory.pushState

これは、History API をサポートするブラウザーでは機能しますが、そうでないブラウザーではポリフィルを使用する利点が失われます。幸いなことに、より良い修正方法があります。参照している 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が最初にそれを見つけて修正をプル リクエストしたことは称賛に値します。

于 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 に答える