CSSでheight:100%指定したimg、Safariで見ると画像がボックスをはみ出ていた意外な原因

テキストを画像化してimg要素としてdivに配置し、iPhoneやデスクトップ版のSafariで表示したら、なんだか画像のサイズがおかしい。CSSに指定したheightの寸法よりも大きく見える。インスペクタで要素の高さを確認したところ、問題のimg要素だけheightの計算値がおかしい！こんな現象にハマりました。発生条件が判ったのでメモ。

**動作環境**
macOS 10.14 (Mojave), Safari 13.1

## 現象
要素`div.tab`に固定の`height`と上下に`padding`を持たせ、子要素としてimg要素を`height: 100%`で埋め込みます（下図）。図で「LOREM IPSUM」はテキストを画像化したものです。デスクトップ版のChromeやFirefoxでは、上側のようにLOREM IPSUM画像が`div.tab`のコンテンツボックスにぴったり収まって表示されますが、Safariで表示すると、ある条件で画像がコンテンツボックスをはみ出してしまいます。
![fig1_w500.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/77e396ae-68d2-b09a-b932-5c0dacf215f4.png)

## サンプルのコード
下記サンプルでは、テキストを画像化したファイル`label.png`を２つの方法で親要素`div.tab` 内に表示しています。最初の`div.tab` は、img要素の高さを`height: 100%`で指定したもの。２つ目の`div.tab`は、img要素の高さをピクセル値で直接指定したものです。

```html:html
<div class="sample">
  <div class="tab"><img src="img/label.png" alt=""></div>
</div>
<div class="sample">
  <div class="tab px"><img src="img/label.png" alt=""></div>
</div>
```
```css:css
.sample {
  width: 300px;
  margin-bottom: 20px;
}
.tab {
  height: 26px;
  padding-top: 8px;
  padding-bottom: 8px;
  background-color: lightgoldenrodyellow;
}
.tab img {
  height: 100%;
  width: auto;
}
.tab.px img {
  height: 26px;
  width: auto;
}
```

## Safariで表示した場合
冒頭に示した現象は次の手順で再現させることができます。下図のアニメーションです。Safariのインスペクタを開いて、`.tab`セレクタの`padding-top`値を0から徐々に大きくしてみましょう。すると……
![Safari_1_3_crop.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/6b47fa0a-9682-a76d-e2a0-0ce763b1f4b4.gif)
`padding-top`が19pxになった時、「LOREM IPSUM」が下側のものより少し大きく表示されるのがわかります。
![Safari_1_3_overflow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/9ff486f4-0497-3fe1-4d86-9c4cc49f5e2b.png)
この時、img要素の高さの計算値は27pxで、親要素`div.tab`のコンテンツボックスの高さ26pxを超えてしまいました。そのまま`padding-top`値を増やしていくと、画像もどんどん大きくなっていきます。
しきい値27pxとは、どうやら

```
padding-top + padding-bottom > height
```

となる地点のようです。つまり、`height: 100%`を指定したimg要素に対して、Safariはimg要素の高さを次のように算出しているように見えます。

|  | padding-top + padding-bottom | img要素のheight計算値 |
|:-:|:-:|:-:|
| 1 | height以下  | divのcontent boxの高さ  |
| 2 | heightより大  | divのpadding boxの高さ  |

## CSSの仕様を確認
念のため、heightプロパティの値にパーセンテージを指定した時のCSSの仕様を確認しておきましょう。

**heightのパーセンテージの基準は？**
MDN: [https://developer.mozilla.org/ja/docs/Web/CSS/height](https://developer.mozilla.org/ja/docs/Web/CSS/height)
> &lt;percentage&gt;
>    包含ブロックの高さのパーセントで高さを定義します。

W3C: [10.5 Content height: the 'height' property](https://www.w3.org/TR/CSS22/visudet.html#the-height-property)
> The percentage is calculated with respect to the height of the generated box's containing block.

**包含ブロックとは？**
MDN: [https://developer.mozilla.org/ja/docs/Web/CSS/Containing_block#Identifying_the_containing_block](https://developer.mozilla.org/ja/docs/Web/CSS/Containing_block#Identifying_the_containing_block)
> 包含ブロックを識別するプロセスは、要素の position プロパティの値に全面的に依存します。
> 1. **position プロパティが static 又は relative の場合**、包含ブロックはブロックコンテナー (略) 又は整形文脈を確立する要素 (略) である**直近の祖先要素のコンテンツボックスの辺**によって構成されます。

W3C: [10.1 Definition of "containing block"](https://www.w3.org/TR/CSS22/visudet.html#containing-block-details)
> 2. For other elements, if the element's **position is 'relative' or 'static'**, the containing block is formed by the **content edge** of the nearest ancestor box that is a block container or which establishes a formatting context. 

今回のサンプルはこれに該当するので、img要素のheightをパーセンテージで指定した場合は、やはり`div.tab`のコンテンツボックス(paddingを含まない）の高さが基準になるはずです。[^1]
[^1]: `position: absolute`にした場合は、paddingを含んだ高さが基準になります。

## Chromeで表示した場合
実際、デスクトップ版のChrome(Blink), Opera(Blink)やFirefox(Gecko)では上記の仕様どおりに表示されます。IE11やEdgeブラウザでも問題なく表示されました。下図は、ChromeのDevToolsを使って同様に`div.tab`の`padding-top`の値を増やしていった時のアニメーションです。paddingの値によらず、img要素の高さは26pxのまま変わりません。
![Chrome_1_3_crop.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/b403ef89-7c42-dc92-15a6-6965e4db161e.gif)

## WebKitの問題？
上記の現象はSafariのレンダリングエンジンWebKitの問題か？と思って、WebKitのBugzillaを検索したところ、ありました！これです。同じ現象です。

[Bug 131486 - Percentage height on replaced elements is computed wildly incorrectly](https://bugs.webkit.org/show_bug.cgi?id=131486)

なんと2014年4月10日にBugzillaに登録されて以来、ステータスNEWのまま放置状態orz
今回、たまたまスマホ用にdivのサイズを「divのpaddig &gt; divの(content-boxの)height」になるまで小さく調整したため、このバグを踏んでしまったのでした。仕方なく、この要素だけimgのheightをpxで指定して回避しました。