なん(NaN)でSafariクラッシュするの?👀
こんにちは、株式会社スペースマーケットでWebエンジニアをしております。wado63です。
SNSのWebviewでLPのフォーム入力中にクラッシュするというバグを踏みまして調査したところ、SafariにおいてCSSのcalcでNaNがあるとクラッシュするという結果に辿り着きましたので共有したいと思います。
何でクラッシュするの??って沼っていましたら、NaNが原因でした😇
バグの発見
ある日、SNSのWebview内のLPでフォーム入力中でクラッシュしてリロードしてしまう報告が社内からありました。
ユーザーが興味を持って問い合わせの入力してくれているというのに、その途中でリロードが入って入力内容がリセットされてしまうというのは、UXとしては致命的な問題です。
ノーコードツールで作られたLPですが、フォームの部分だけはJavaScriptでカスタマイズしている部分があったのとWebview内で発生しているということもあり、当初はJavaScriptが原因だと想定していました。
そのLPにはGTM(Google Tag Manager)が組み込まれていたので、GTM経由でSentryを入れてみたましたがエラーは拾えません。
手元では再現しているのにエラーは拾えない。
色々ためしていたところWebviewではなく、普通のiOS Safariでも再現することがわかりました。
再現する手段もわかってきて、LPを閲覧してから1分ぐらい待つとクラッシュするようです。
しかし、Macに繋げて開発者ツールでコンソールログを見てもエラーは出ていません。
わからない。。。わからない。。。🫠
異常なスタイルの再計算
最初はJavascriptが原因かと考えていたので、使い慣れているChromeのdevtoolsのProfilerなど使い、異常な動作などないかを調査しておりました。
再現しないブラウザなのでもちろん原因はわかりません。
Safariで同様の機能がないかと探してみると、Safariの開発者ツールにはタイムラインというタブが用意されており、そこが異常な挙動をしております。
(gifはサンプルのページですが、実際にはLPでこのようなものが起こってました。)
ちなみにLPは複数あって、今回のバグが発生するLPではこの異常な挙動は見られません。
コレだ!!!!!
レンダリングということは、JavascriptではなくHTMLなのか・・・?というあたりをつけて、古典的な要素を消していくというデバッグを行いました。
開発者ツールで要素を削除してはタイムラインに戻るという繰り返しで、問題を発生させる要素を絞り込んでいきます。
ついに発見しました。(画像はバグ見つけた当時のslackから発掘)
cssにNaNが入っているじゃないですか😱
ノーコードツール上の設定ミスで、本来heightをautoとすべきところを0%というような指定をしており計算できなかったようです。
作成したデザイナーさんにこの原因を共有、heightにautoを指定してもらうことで解決しました。
めでたしめでたし。
詳細な調査
この記事を書くにあたって再現するhtmlを用意してみました。
こちらで動作しますので、safariの開発者ツールを開いて、タイムラインを確認してみてください。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
.img {
height: calc(0% - (1px * NaN));
transition: 0.3s ease;
}
</style>
</head>
<body>
<img class="img" src="https://placehold.jp/200x100.png" />
<p id="time"></p>
<script>
// 経過時刻を表示
const time = document.getElementById('time')
const now = new Date()
setInterval(() => {
const date = new Date()
const diff = date - now
const sec = Math.floor(diff / 1000)
const min = Math.floor(sec / 60)
const hour = Math.floor(min / 60)
time.innerText = `${hour}時間${min % 60}分${sec % 60}秒`
}, 1000)
const img = document.querySelector('.img')
// visibility: hidden を付与し直後に削除
img.style.visibility = 'hidden'
setTimeout(() => {
img.style.visibility = ''
}, 1)
</script>
</body>
</html>
再現ポイントは
1. calcの中にパーセント、px、NaNが複合して入っている。
2. transitionが指定されている。
3. imgタグ
そして。。。
4. visibility: hiddenをつけはずしする
分かるかぁ!!!💢
サンプルを作る際に、NaNだけでは再現せず追加調査で発覚しました。
この記事読んでいらっしゃる方、普通こんなことしないと思いますよね。
ですが、ノーコードツールで使用されているWebフォントの読み込みScriptの中でこのような処理があります。
フォントを読み込むまではhiddenにしておいて、読み込んだら表示するという処理のようです。
まとめ
Safariで急にクラッシュしてリロードすることがあれば、NaNが原因かもしれません。
先入観で調査するのは気をつけましょう。
今回のバグに関して相当マイナーなバグだなと思いつつも、一応Appleのバグレポートに報告してあります。

スペースを簡単に貸し借りできるサービス「スペースマーケット」のエンジニアによる公式ブログです。 弊社採用技術スタックはこちら -> whatweuse.dev/company/spacemarket
Discussion