Qiitaの質問フィードでなぜ一瞬アイコンが大きく表示されるのか

（※8月27日16時追記：修正されたらしく現在はアイコンが大きくなりません）

質問フィードを開くと、一瞬このような感じで質問者のアイコンが大きく表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71477/b102da9b-f84d-6e2f-bf6f-2503a6ec448f.png)
（アイコンなので公開情報だとは思いますが質問者を晒す意図はないのでモザイクをかけています）

どうしてこうなってしまうのか、HTMLの仕様を眺めて解説をしてみようと思います。

## まずはインスペクタで覗いてみよう

アイコンが大きくなるのは一瞬だけで、次の瞬間には小さくなっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71477/2f7778ce-8a4f-9d9c-24c6-e298afc6664f.png)

アイコンの部分をインスペクタで覗いてみると、こんな感じです（ユーザー名にマスクをしています）。

```html
<a class="HomeQuestionFeed__ItemContentUserImage-kvc571-10 bWegeW">
  <a href="/********">
    <img src="https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/********" alt="*********">
  </a>
</a>
```

一方、アイコンが大きく表示されている時（JavaScriptを無効にするとその状態のままになります）はこうなっています。

```html
<a class="HomeQuestionFeed__ItemContentUserImage-kvc571-10 bWegeW"></a>
<a href="/********">
  <img src="https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/********" alt="*********">
</a>
```

おや、内側の `<a>` タグが外側に出ていますね。一瞬後に（おそらくJavaScriptにより）中にしまわれて表示が小さくなるようです。

## ソースを見てみよう

JavaScriptを無効にすると大きなアイコンが表示されることから、サーバーサイドで生成したHTMLが表示されているようなので、ソースコードを見てみます。
※整形・マスク済み

```html
<a class="HomeQuestionFeed__ItemContentUserImage-kvc571-10 bWegeW">
  <a href="/********">
    <img src="https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/********" alt="********"/>
  </a>
</a>
```

あれれ～おかしいぞ～？　`<a>` タグが外側に出ていませんね。

## 仕様を見てみよう

はい、という訳でこれはHTMLの `a` 要素の仕様に何かありそうです。とりあえず MDN の[`a` 要素ページ](https://developer.mozilla.org/ja/docs/Web/HTML/Element/a)を見に行ってみます。
[プロパティ](https://developer.mozilla.org/ja/docs/Web/HTML/Element/a#Properties)の部分を見てみます。

>
|許可されている親要素|記述コンテンツを受け入れるすべての要素、フローコンテンツ を受け入れるすべての要素。ただし \<a\> 要素を除く。|
|:--|:--|

なるほど。
つまり、 **`a` 要素は `a` 要素の親になれない**訳ですね。

[Living Standard](https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-a-element) の方はこうなっています。

> Content model:
> Transparent, but there must be no interactive content descendant, `a` element descendant, or descendant with the `tabindex` attribute specified.

必要なところだけ抜き出すと、 "there must be no `a` element descendant"　つまり子孫要素に `a` 要素があってはいけない、となりますね。

とはいえ実際にそういうソースコードになってしまっていますから、パーサーはこれをパースしようとします。
パーサーが実際にどのような挙動を示すかは Living Standard の中の [The "in body" insertion mode](https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inbody) を追っていくとわかります。

わかります、と言っても複雑なルールなため全部読むのは大変ですが、上記リンクの部分から少し下に行くと、

> A start tag whose tag name is "a"

と書いてある部分があります。 The "in body" insertion mode の状態でパーサーが動作している時に `a` 要素の開始タグが見つかった時はこうしますよ、というルールが続けて書かれています。引用します。

> If the list of active formatting elements contains an `a` element between the end of the list and the last marker on the list (or the start of the list if there is no marker on the list), then this is a parse error; run the adoption agency algorithm for the token, then remove that element from the list of active formatting elements and the stack of open elements if the adoption agency algorithm didn't already remove it (it might not have if the element is not in table scope).

いろいろリンクが貼ってあってこの部分だけ抜粋しても意味を掴むのが大変なのですが、重要なキーワードは "list of active formatting elements" です。これはパーサーが持つ状態の一つで、現在パースしている位置より前に見つかった 「formatting カテゴリの要素」と「マーカー[^1]」が入れられた配列です。

[^1]: マーカーと言っても `mark` 要素のことではなく、リストの中に付けられた目印のようなものです。

[Formatting](https://html.spec.whatwg.org/multipage/parsing.html#formatting) カテゴリの要素が何かと言うと、

> Formatting
> The following HTML elements are those that end up in the list of active formatting elements: `a`, `b`, `big`, `code`, `em`, `font`, `i`, `nobr`, `s`, `small`, `strike`, `strong`, `tt`, and `u`.

となっていまして、 `a` 要素は formatting カテゴリに含まれます。

`a` 要素が `<a><a></a></a>` のように入れ子になっている時、まず外側の `a` 要素が見つかった時に list of active formatting elements に追加されます。そして次に内側の `a` 要素が見つかった時、先程の

> If the list of active formatting elements contains an `a` element between the end of the list and the last marker on the list (or the start of the list if there is no marker on the list), then this is a parse error; run the adoption agency algorithm for the token, then remove that element from the list of active formatting elements and the stack of open elements if the adoption agency algorithm didn't already remove it (it might not have if the element is not in table scope).

このルールを確認すると、リストの終わりと最後のマーカー（またはマーカーがリストに入っていない場合はリストの先頭）の間に `a` 要素が入っていた場合、パースエラーであると書かれています。
なおマーカーがリストに挿入されるタイミングも仕様を追えば書いてありますが、この例だとそのルールにはあたらないので割愛します。

ブラウザのHTMLパーサーはパースエラーになっても止まりません。多少おかしくても表示されることを優先するためです。そのため、その後に行うべき処理が書いてあります。 "run the adoption agency algorithm" からの文がそれです。

Adoption agency というのは養子縁組仲介者のことを指します。ここでは外側の `a` 要素が内側の `a` 要素の親になれないことが発覚したので、内側の `a` 要素を別の要素の子にする必要があります。これを養子縁組になぞらえている訳ですね。ここでは adoption agency algorithm の中身までは追いませんが、結論を言うと内側の `a` 要素は外側の `a` 要素の親の子要素となり、質問フィードの例だと

```html
<a class="HomeQuestionFeed__ItemContentUserImage-kvc571-10 bWegeW"></a>
<a href="/********">
  <img src="https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/********" alt="*********">
</a>
```

この形のDOMが構築されてしまうことになります。

## 結論

という訳で、端的に言うと

**`a` 要素の子に `a` 要素を入れてはいけない**

ということになります。質問フィードの事例では外側の `a` タグは `a` タグである必要がないので `span` などにしておけば良かったはずです。

もっと言うと、**HTMLパーサーは非直感的な挙動をすることがある**ということは留意しておくべきでしょう。多少ソースがおかしくても表示されるのはHTMLの強みですが、実際にソースがおかしかった時にパーサーが入れる修正が開発者の期待したものになるとは限りません。

ちなみに、私はパーサーの挙動に非直感的な点が少なく、文法的におかしな記述が現れたら問答無用でエラーにしてくれるXMLパーサーの方が好きです。XHTMLはいいぞ。