Hail2u.net

URLUtils.hash (location.hash)とその返す値

URLUtilsインターフェイスのブラウザーにおける実装であるLocationオブジェクトのhashプロパティーによりURLのフラグメント識別子(と#)を取得・代入できる。最近のブラウザーは非ASCII文字列もURLエンコードせずに扱えるようになっているが、この辺りの挙動がFirefoxだけ少し違うようだ。それはhashプロパティーの返す値で、常にデコードされた形で返ってくるようになっている。

例えば適当なウェブページで開発者ツール(的なもの)を開き、コンソール(などと呼ばれてるもの)で以下を実行してみる。

> location.hash = '#' + 'あいうえお'
< "#あいうえお"
> location.hash
< "#あいうえお"
> location.hash = '#' + encodeURIComponent('あいうえお')
< "#%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A"
> location.hash
< "#%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A"

Chrome 39やInternet Explorer 11ではこのようになる。location.hashに代入した文字列はそのまま返ってくるだけだ。対してFirefox 34ではURLエンコードした文字列を代入した場合、デコードされて返ってくる。

> location.hash = '#' + 'あいうえお'
< "#あいうえお"
> location.hash
< "#あいうえお"
> location.hash = '#' + encodeURIComponent('あいうえお')
< "#%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A"
> location.hash
< "#あいうえお"

もちろん開発者ツール上だけでなく通常のスクリプト上でも同じなので、この辺りを扱う場合はそれなりの注意が必要になる。常にURLエンコードされているのかどうかを意識するべきということだ。

例えば今のGitHubではこの辺りを使ってid属性の重複避けとhashchangeイベントの監視によるページ内ジャンプを実装している。しかし、URLエンコード済み(href属性の値)とそうでないもの(id属性の値)が混ざっているため、日本語の見出しではChrome 39やInternet Explorer 11だと比較に失敗してページ内ジャンプがうまく動かない。


どちらかというとFirefoxの挙動の方が開発者に優しいとは言えるかもしれない。常にデコードされていると想定できるので、二重にエンコードしてしまうことやデコードし忘れということがない。しかしその一方でURLエンコードされた文字列とそうでない文字列が含まれたフラグメント識別子を元に戻すことができないという欠陥もある。Bug 483304でかなり昔から議論されているようだ。

URL仕様では、とにかくフラグメント識別子は必要ならURLエンコードされているべきとなっている。そしてURLUtilsインターフェイスのhashプロパティーでもゲッターは#と連結して返せというだけになっているので、勝手にデコードして返すのはバグと言って良さそう。