スポンサーリンク

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.txt

In 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