コフス技術ブログ

document.write()後にclose()を忘れるとブラウザのローディングが永遠に完了しない場合がある

例えばページ内に存在するiframe要素に対しdocument.write()何らかの要素を書き込んだ後に、close()を忘れストリームが開かれた状態のままにするとChromium系のブラウザではページのローディングが永遠に完了しない場合があります。

以下はその1例で、iframe要素に対し何らかの要素=YouTubeのembedを動的に埋め込んだものです。

StackBlitzのサーバーが起動した後にPreviewのURLをChromium系のブラウザにて直接開くと、永遠にローディングが完了しない様子(永遠にスピナーが回転する状態)が確認出来ます。

原因は冒頭に述べたとおりdocument.write()で開けたストリームをclose()しなかった為で、48行目のコメントアウトを外し明示的にclose()することで解消されます。


先程はYouTube埋め込みでしたが、今度はmain.tsの37〜45行目を以下に変更し画像埋め込みに変えた例が以下です。

- // Add Youtube video
- const video = iframeDocument.createElement("iframe");
- video.src = "https://www.youtube.com/embed/mkggXE5e2yk";
- video.width = "100%";
- video.height = "100%";
- video.style.border = "none";
- 
- // Add the video to the iframe
- iframeDocument.body.appendChild(video);

+ // Add Dummy Image
+ const image = iframeDocument.createElement("img");
+ image.src = "https://picsum.photos/720/480";
+ image.style.width = "100%";
+ image.style.height = "100%";
+ image.style.objectFit = "cover";
+ 
+ // Add the image to the iframe
+ iframeDocument.body.appendChild(image);

同様にStackBlitzのサーバーが起動した後にPreviewのURLを新規タブなどで直接開くも、YouTube埋め込みとは異なりスピナーは適切に回転を止めます。
勿論48行目のclose()は同様にコメントアウトされたままです。

これはどういうことなのでしょうか。

考察(都度更新予定)

上記2例で挙動が異なる理由として、ブラウザによる継続的なローディングの必要性に対する解釈の違いが存在するのではないか^1と考えます。

そもそもclose()をするまでiframeはloadイベントを発火しません。(ちなみに発火すると7行目のconsole.log()が表示されます)

画像の埋め込みの場合は確かに永遠にスピナーが回転する様子は見られませんが、やはりclose()を省略している為にiframeのloadイベントが発火されることはありません。
loadイベントを発火しないということは、詰まるところローディングが完了されていないという事に他なりません。

要するに2例ともiframeのローディングは完了しておらず、ストリームは開かれっぱなしでローディングステータスは永久的に待機中のままになっているはずですなのです。

ですが画像埋め込みの場合はスピナーは回転を見せませんし、loadイベントも発火されません。これは私にとって想定外の挙動で、出来れば2例ともclose()するまでスピナーは回転していて欲しいのですがそうとはなりません。もしかするとブラウザ上のUIのスピナーの状態=ローディングステータスではない可能性も考えられます。(そんな事はないと思っていますが)

ですがこの辺りをブラウザ側で暗黙的に処理しているのであれば合点がいきます。

YouTubeの埋め込みの場合はブラウザ側によって継続的にデータの読み込みの必要性があると判断され、一方画像埋め込みの場合は画像以外に継続的な読み込みの必要性が無いと判断されたと考えた時、ローディングステータスは両者とも待機中のままに(loadイベントは発火されていないので)、後者のみUI上のスピナーの回転を暗黙的に止めたと考えると自然です。

どちらにしてもスピナーが回転せずとも、真にローディングステータスが完了(loadイベントが発火される状態)となっているわけではない事象が存在する事が上記2例から見えてきました。

^1: この辺りについて解説されたドキュメントを探すことが出来ませんでしたので、これはあくまでも執筆時点での私の解釈で、不確かな解釈を多く含んだ内容の可能性があります。この章は知見が溜まり次第正しいと思われる内容へ都度更新していきます。

FirefoxやSafariではまた違った結果に

document.write()についてMDNを確認するとclose()の呼び出しは必須ではなく推奨されているとの記載があります。

一度出力し終わったとき、ブラウザーにページの読み込みの終了を伝えるために、 document.close() を呼び出すことが推奨されます。
https://developer.mozilla.org/ja/docs/Web/API/Document/write

実際にclose()をコメントアウトし省略した場合でもChromium系とは異なりFirefoxやSafariなどでは上記2例ともスピナーが永遠に回転する様子は見られません。(ただしloadイベントは発火されませんしローディングステータスが完了している訳ではないです)

Chromium系では書き込む要素によっては明示的にストリームを閉じなければスピナーが永遠に回転しますが、これがバグなのか仕様に忠実なのかは不明です。

ただ総じてdocument.write()する際は開いたら閉じるを意識しておくと無難そうです。
そもそも使用を推奨されていないので安易に使用しないように気をつけるべきとも言えそうです。