document.write() に対する介入

最近、Chrome のデベロッパー コンソールに次のような警告が表示され、その内容が不明だった場合は、以下をご覧ください。

(index):34 A Parser-blocking, cross-origin script,
https://paul.kinlan.me/ad-inject.js, is invoked via document.write().
This may be blocked by the browser if the device has poor network connectivity.

コンポーザビリティはウェブの大きな強みの一つです。サードパーティが構築したサービスと簡単に統合して、優れた新しいプロダクトを構築できます。コンポーザビリティの欠点の一つは、ユーザー エクスペリエンスに対する責任の共有が示唆されることです。統合が最適でない場合、ユーザー エクスペリエンスに悪影響が及びます。

パフォーマンスが低下する原因として、ページ内での document.write() の使用(特にスクリプトを挿入する使用)が知られています。以下は問題ないように見えますが、ユーザーに実際の問題を引き起こす可能性があります。

document.write('<script src="https://example.com/ad-inject.js"></script>');

ブラウザはページをレンダリングする前に、HTML マークアップを解析して DOM ツリーを構築する必要があります。パーサーはスクリプトに遭遇するたびに、HTML の解析を続行する前に、スクリプトを停止して実行する必要があります。スクリプトが別のスクリプトを動的に挿入すると、パーサーはリソースのダウンロードをさらに長く待機する必要があります。これにより、1 つ以上のネットワーク ラウンドトリップが発生し、ページの最初のレンダリング時間が遅れる可能性があります。

2G などの低速の接続を利用するユーザーの場合、document.write() によって動的に挿入される外部スクリプトにより、メインページのコンテンツの表示が数十秒遅れたり、ページの読み込みに失敗したり、ユーザーが長時間利用を止めたりする可能性があります。Chrome のインストルメンテーションに基づいて、document.write() を介して挿入されたサードパーティ スクリプトを含むページでは、通常、2G で接続した場合の読み込み速度が他のページよりも 2 倍遅くなることがわかりました。

2G 接続のユーザーに限定して、Chrome 安定版ユーザーの 1% を対象とした 28 日間のフィールド トライアルからデータを収集しました。2G でのすべてのページ読み込みの 7.6% に、トップレベル ドキュメントの document.write() を介して挿入された、クロスサイトのパーサー ブロッキング スクリプトが 1 つ以上含まれていました。これらのスクリプトの読み込みをブロックした結果、読み込みに次のような改善が見られました。

  • コンテンツの初回ペイント(ページが効果的に読み込まれていることをユーザーに視覚的に確認させるもの)に到達するページ読み込みが 10% 増加し、完全に解析された状態に到達するページ読み込みが 25% 増加し、再読み込みが 10% 減少したことから、ユーザーの不満が軽減されたことがわかります。
  • First Contentful Paint までの平均時間が 21% 短縮(1 秒以上短縮)
  • ページの解析にかかる平均時間が 38% 短縮され、6 秒近く改善されました。これにより、ユーザーにとって重要な情報を表示するまでの時間が大幅に短縮されました。

このデータを踏まえ、Chrome バージョン 55 以降では、この既知の不正なパターンを検出すると、Chrome で document.write() の処理方法を変更して、すべてのユーザーに代わって介入します(Chrome ステータスを参照)。具体的には、次の条件がすべて満たされている場合、Chrome は document.write() を介して挿入された <script> 要素を実行しません。

  1. 接続速度が遅い場合(特に 2G を使用している場合)。(今後、この変更は、低速の 3G や低速の Wi-Fi など、低速の接続を使用している他のユーザーにも適用される可能性があります)。
  2. document.write() は最上位ドキュメントにあります。iframe 内の document.written スクリプトはメインページのレンダリングをブロックしないため、この介入は適用されません。
  3. document.write() のスクリプトはパーサー ブロックです。async 属性または defer 属性を持つスクリプトは引き続き実行されます。
  4. スクリプトが同じサイトにホストされていない。つまり、eTLD+1 が一致するスクリプト(js.example.org でホストされ、www.example.org に挿入されたスクリプトなど)に対して、Chrome は介入しません。
  5. スクリプトがブラウザの HTTP キャッシュにまだ含まれていない。キャッシュ内のスクリプトはネットワーク遅延を発生させず、引き続き実行されます。
  6. ページのリクエストは再読み込みではありません。ユーザーが再読み込みをトリガーした場合、Chrome は介入せず、通常どおりページを実行します。

サードパーティ スニペットでは、document.write() を使用してスクリプトを読み込むことがあります。幸い、ほとんどのサードパーティは非同期読み込みの代替手段を提供しています。これにより、ページの残りのコンテンツの表示をブロックすることなく、サードパーティ スクリプトを読み込むことができます。

どうすればよいですか?

シンプルな答えは、document.write() を使用してスクリプトを挿入しないことです。非同期ローダをサポートする既知のサービスがいくつか用意されているため、確認することをおすすめします。

ご利用のプロバイダがリストに記載されておらず、非同期スクリプト読み込みをサポートしている場合は、お知らせください。ページを更新してすべてのユーザーに役立てさせていただきます

ページにスクリプトを非同期で読み込む機能をプロバイダでサポートされていない場合は、プロバイダに連絡して、どのような影響を受けるかを Google に知らせていただくことをおすすめします。

プロバイダから document.write() を含むスニペットが提供されている場合は、スクリプト要素に async 属性を追加するか、document.appendChild()parentNode.insertBefore() などの DOM API を使用してスクリプト要素を追加できます。

サイトが影響を受けるタイミングを検出する方法

制限が適用されるかどうかを判断する基準は多数ありますが、影響を受けるかどうかを知るにはどうすればよいでしょうか。

ユーザーが 2G を使用しているかどうかを検出する

この変更がもたらす影響を把握するには、まず 2G を使用するユーザーの数を把握する必要があります。Chrome で利用可能な Network Information API を使用して、ユーザーの現在のネットワーク タイプと速度を検出し、分析システムまたはリアルユーザー測定(RUM)システムにヘッドアップを送信できます。

if(navigator.connection &&
    navigator.connection.type === 'cellular' &&
    navigator.connection.downlinkMax <= 0.115) {
    // Notify your service to indicate that you might be affected by this restriction.
}

Chrome DevTools で警告をキャッチする

Chrome 53 以降、DevTools では、問題のある document.write() ステートメントに対して警告が表示されます。具体的には、document.write() リクエストが条件 2 ~ 5 を満たしている場合(Chrome は、この警告を送信する際に接続条件を無視します)、警告は次のようになります。

ドキュメント書き込みの警告。

Chrome DevTools で警告を確認することはできますが、これを大規模に検出するにはどうすればよいでしょうか。介入が発生したときにサーバーに送信される HTTP ヘッダーを確認できます。

スクリプト リソースの HTTP ヘッダーを確認する

document.write 経由で挿入されたスクリプトがブロックされると、Chrome はリクエストされたリソースに次のヘッダーを送信します。

Intervention: <https://shorturl/relevant/spec>;

document.write を介して挿入されたスクリプトが見つかり、さまざまな状況でブロックされる可能性がある場合、Chrome は次のことを送信することがあります。

Intervention: <https://shorturl/relevant/spec>; level="warning"

介入ヘッダーは、スクリプトの GET リクエストの一部として送信されます(実際の介入の場合は非同期)。

未来に待ち受けているものとは

最初の計画では、条件が満たされたことを検出したときにこの介入を実行します。Chrome 53 では、Play Console に警告のみを表示することから始めました。 (ベータ版は 2016 年 7 月にリリースされました。安定版は 2016 年 9 月にすべてのユーザーが利用できる予定です)。

Google は、2016 年 10 月中旬にすべてのユーザーに安定版が提供される予定の Chrome 54 以降、一時的に 2G ユーザー向けに挿入されたスクリプトのブロックに介入します。最新情報については、Chrome のステータスのエントリをご覧ください。

今後、ユーザーの接続が遅い場合(3G や Wi-Fi が遅い場合など)に介入する予定です。こちらの Chrome ステータスのエントリを参照してください。

もっと詳しく知りたい場合

詳細については、以下のリソースをご覧ください。