JavaScriptの動かないコード(中級編)Aタグがリンクとして機能しないエラー (URL・hrefの%エンコードがRFC違反だと,IEで例外が発生)
以下のJavaScriptコード(というよりHTMLそのもの)が意図した動作をしないのは,なぜですか。(制限時間1分)
やりたい事:
- HTML上で,Aタグによりリンクを表示する。
- そのリンクのURLを, JavaScriptから取得する。
<a href="http://google.com/?q=%" id="a1">ブラウザは, このHTMLリンクを表示できるか?(1つ目)</a> <br> <a href="http://google.com/%" id="a2">ブラウザは, このHTMLリンクを表示できるか?(2つ目)</a> <br> <input type="button" value="test" onclick="f1()"> <script> function f1(){ alert( document.getElementById("a1").href ); alert( document.getElementById("a2").href ); } </script> </body> </html>
答え
発生する不具合
一つ目のリンクは,正常にHTMLとして動作する。
つまり,ブラウザ上でリンクをクリックすれば,該当するURLにジャンプする。
(※リンク先のURLを開いた結果がどうなるか,はここでは問題ではない。開けさえすればよい。)
また,リンクのURLをJavaScriptから取得することもできる。
ところが・・・。
二つ目のリンクは,IEの場合,クリックしても何も起こらない。
また,JavaScriptからURLを取得しようとすると「引数が無効です」のエラーになる。
HTML上で,ハイパーリンクとして機能できていないのだ。
IE以外のブラウザだと, 正常にリンクとして動作するし,JavaScriptでもエラーにはならない。
firefox, chrome, safariなどで検証した。
よく見ると,URLの末尾に「?q=%」と書いてある場合は動く。
しかし,URLの末尾に「%」とだけ書いてある場合,エラーになる。
同じ「%」という文字を使っているに過ぎないのに,なぜ,リンクが機能しないのか?
理由
じつは,URLには厳密な定義がある。
URLの厳密な定義に反しているので,IEでは動作しないリンクがあるのだ。
根拠を詳しく見てみよう。
まず,HTMLの文法について正確な仕様を確認する。
HTML4またはHTML5における「ハイパーリンクのhref属性」の仕様を見ると,
「妥当なURL」という用語が出てくる。
4.8 リンク ― HTML5 日本語訳
http://momdo.github.io/html5/links.ht...
- 4.8.1 aおよびarea要素によって作成されるリンク
- aおよびarea要素のhref属性は、潜在的にスペースで囲まれた妥当なURLである値でなければならない。
2 共通インフラ ― HTML5 日本語訳
http://momdo.github.io/html5/infrastr...
- 先頭と末尾の空白文字を取り除いたあとに妥当なURLである場合、文字列は潜在的にスペースで囲まれた妥当なURLである。
2 共通インフラ ― HTML5 日本語訳
http://momdo.github.io/html5/infrastr...
- URL標準でオーサリング適合性要件に準拠する場合、URLは妥当なURLである。
参考文献 ― HTML5 日本語訳
http://momdo.github.io/html5/referenc...
- HTML仕様で使用されるURL関連用語の多く(URL、絶対URL、相対URL、相対スキーム、スキームコンポーネント、スキームデータ、ユーザー名、パスワード、ホスト、ポート、パス、クエリ、フラグメント、パーセントエンコード、基底を得る, およびUTF-8パーセントエンコード)は、[RFC3986]および[RFC3987]の用語に直接マッピングされうる。
- ウェブブラウザやHTMLコンテキスト外の他のソフトウェアスタックは、URLの処理の仕方に顕著な違いがある。
上の仕様によると,
- 「URL(正確に言えばURI)の定義は,RFC文書を参照せよ」,
- さらに「%エンコードの使い方も,同じくRFCで決めたとおりにせよ」
ということが書いてある。
では,RFCでは「妥当なURL」とか
「パーセントエンコード」の正確な定義はどうなっているのか?
それを調べるには,文書番号でいうと「RFC2396」を参照すればよい。
HTML5メモ(1) href属性/URIとIRI の覚え書き - 血統の森+はてな
http://d.hatena.ne.jp/momdo/20100523/p1
- HTML4ではhref属性でURI(RFC 2396)が扱えるという規定
- おおざっぱに言えば、URIに使える文字列は、アルファベットと数字、決められた記号
URIに使ってよい文字の話 - RFC2396 と RFC3986 - 本当は怖い情報科学
http://freak-da.hatenablog.com/entry/...
- URIの構文はRFCで定義されている。これには2つあって、従来のRFC2396(1998年発行)と、RFC3986(2005年発行)だ。
RFC2396の原文には,こうある:
Uniform Resource Identifiers (URI): Generic Syntax
ftp://ftp.nic.ad.jp/rfc/rfc2396.txtIn the simplest case, the original character sequence contains only
characters that are defined in US-ASCII, and the two levels of
mapping are simple and easily invertible: each 'original character'
is represented as the octet for the US-ASCII code for it, which is,
in turn, represented as either the US-ASCII character, or else the
"%" escape sequence for that octet.
2.4.1. Escaped Encoding
An escaped octet is encoded as a character triplet, consisting of the
percent character "%" followed by the two hexadecimal digits
representing the octet code. For example, "%20" is the escaped
encoding for the US-ASCII space character.escaped = "%" hex hex
hex = digit | "A" | "B" | "C" | "D" | "E" | "F" |
"a" | "b" | "c" | "d" | "e" | "f"
太字で強調した部分にある通り,
- 「%」という文字は,エスケープシーケンスの目的でのみ利用できる。
- エスケープする場合,「%」の後ろには,16進数が2つ並ばなければならない。
ということだ。
冒頭で示した例では,URL内の「%」の後ろに
「16進数が2つ」並んでいなかった。
そのため,「妥当なURIではない」とIEが判断し,
リンクとしての機能を持てなかったのだ。
ただし,URL内で「?a=b」のような部分のことをクエリ文字列というが,
クエリ文字列の中には「%」が単独で存在してもよい。(IEで特にエラーにはならない)
なぜなら,クエリはGETリクエストのパラメータであり,
URLの主要部分ではないから。
こんな区別があるために,冒頭のエラーは生じたということになる。
このエラーにどのように苦しめられるか
このエラーは,ブラウザ依存で動いたり動かなかったりするので,何が悪いのか気づきづらい。
下記のように・・・
document.getElementById("a2").href;
という文があるだけで「引数が無効です」のエラーになり,
いったい何が悪いのかエラーメッセージだけでは判定できない。
しかも,そのエラーメッセージはtry〜catchブロックで囲んだり,
開発者ツールでコンソール表示しないと読めない。
とても厄介だ。
このエラーに出会う場面としては,
- アクセス解析の結果を,自動生成されたハイパーリンクでHTML表示している場合
なんかが挙げられる。
というか,まさにこの場面に「はてなカウンター」で,自分は直面した。
IEで画面を開くと,特定のAリンクだけがなぜか「押せない」・・・。
firefoxだと開けるのに。
誰が悪いのか?というと
- 妥当なURIの定義を,勝手に厳しく(というか仕様どおりに)受け止めているIEのせい
- 変なURLで動作している外部Webページのせい
- その変なURLからのアクセスをしっかりと記録して,しっかりと(IEで動かない形式で)ハイパーリンクでそのまま表示しているはてなカウンターのせい
などなど,いろんな要因が考えうるが・・・。
まあ,正解は「URIの仕様について知っている者勝ち」ではないかと。
このエラーの回避策
JavaScriptとしての挙動だけに注目すると,回避策はある。
以下のように書き換えればよい。
document.getElementById("a2").href ↓ document.getElementById("a2").getAttribute("href")
こうすれば,ハイパーリンクがどうのこうのという話ではなくなって
単なるXML操作になる。
URIとして文字列を認識するのではなく,
単に「aというXMLタグ内の一つの属性を, 文字列で取得」という操作になる。
なので,こうすれば「妥当でないURI」もJavaScriptで取得できるのだ。
相変わらず画面上でクリックしてもジャンプはしないけどね。
感想
よりによってIEなんだから,もっと仕様に不忠実に動いてもいいと思うんだけど…。
関連記事:
JavaScriptの動かないコード (中級編) innerHTMLを追記するとイベントハンドラが消える
http://language-and-engineering.hatenablog.jp/entry/20090903/p1
JavaScriptの動かないコード (中級編) nullが0以上0以下と認識されてしまう
http://language-and-engineering.hatenablog.jp/entry/20090906/p1
JavaScriptの動かないコード (中級編) テーブルに行追加できない
http://language-and-engineering.hatenablog.jp/entry/20080914/1221348848
JavaScriptの動かないコード (中級編) iframe内のDOM要素を別フレームにコピーできないエラー
http://language-and-engineering.hatenablog.jp/entry/20090214/p1