このページはコミュニティーの尽力で英語から翻訳されました。MDN Web Docs コミュニティーについてもっと知り、仲間になるにはこちらから。

View in English Always switch to English

遅延フェッチの使用

fetchLater() API は、指定した時間が経過した後、またはページが閉じられたとき、あるいは別のページに移動したときに送信される遅延フェッチをリクエストするためのインターフェイスを提供します。

概要

開発者は、例えば分析サービスのため、特にユーザーがページを離れる際など、サーバーへデータを送信(またはビーコンとして送信)する必要があることがよくあります。これを行う方法はいくつかあります。ページに 1 ピクセルの <img> 要素を追加する方法から、XMLHttpRequest、専用の ビーコン API、さらにはフェッチ APIそ のものまで、さまざまな方法があります。

課題は、これらのメソッドのすべてにおいて、セッション終了時のビーコン送信に関して信頼性の問題があることです。ビーコン API やフェッチ API の keepalive プロパティは、ドキュメントが破棄された場合でも(このシナリオにおいて可能な限りの努力を払って)データを送信しますが、これは課題の一部しか解決していません。

それ以外にも、解決がより難しい点は、データをいつ送信するかを決めることです。ページのライフサイクルにおいて、ビーコンを送信するための JavaScript 呼び出しを行うのに理想的な時点は存在しないからです。

  • unload および beforeunload イベントは信頼性が低く、いくつかの主要なブラウザーでは完全に無視されています。
  • pagehide および visibilitychange イベントは信頼性がより高いものの、モバイルプラットフォームでは課題が残ります。

つまり、ビーコンを介して確実にデータを送信したい開発者は、理想的な頻度よりも頻繁に送信を行う必要があります。例えば、ページの最終値がまだ到達していない場合でも、変更があるたびにビーコンを送信することになるかもしれません。これには、ネットワーク使用量、サーバー処理、およびサーバー側での古いビーコンの統合や破棄といったコストがかかります。

また、開発者は、以下のいずれかの方法により、ある程度の欠損データを受け入れることを選択することも可能です。

  • 指定された締め切り時刻後にビーコンを送信し、それ以降のデータは収集しない。
  • ページのライフサイクルの終了時にビーコンを送信するが、これが必ずしも信頼できるとは限らないことを受け入れる。

fetchLater() API は、フェッチ API を拡張し、フェッチリクエストを事前に設定することができるものです。これらの遅延フェッチは、送信される前に更新することが可能で、これにより、送信される内容に最新のデータが反映されるようになります。

その後、タブが閉じられたり、別のページに移動したりした際、あるいは指定されている場合は設定された時点が経過した後に、ブラウザーはビーコンを送信します。これにより、ビーコンの重複送信を避けることができますが、妥当な範囲内で(つまり、クラッシュによってブラウザープロセスが予期せず終了した場合を除き)、確実にビーコンを送信します。

必要なくなった場合は、AbortController を使用して遅延フェッチを中止することもでき、それによって不必要なオーバーヘッドを避けることができます。

クォータ

遅延フェッチはバッチ処理され、タブが閉じられた時点で一括送信されます。この時点で、ユーザーがこれを中止する方法はありません。文書が帯域幅を悪用してネットワーク経由で無制限のデータを送信してしまう事態を避けるため、最上位文書の全体的なクォータは 640KiB に制限されています。

fetchLater()を呼び出し側は、特にサードパーティー製の JavaScript を埋め込んでいる場合は、ほぼすべての場合で QuotaExceededError エラーを捕捉するなどの予防措置を講じる必要があります。

この上限により、遅延フェッチの帯域幅は希少なリソースとなり、複数のレポート元(例えば、複数の RUM ライブラリー)や複数のオリジンからのサブフレーム間で共有される必要があるため、プラットフォームではこのクォータについて妥当なデフォルトの配分を提供しています。さらに、必要に応じて異なる割り当てを行うことができるよう、deferred-fetch および deferred-fetch-minimal権限ポリシーディレクティブを提供しており、必要に応じて異なる方法で割り当てを調整することができます。

fetchLater() の全体的なクォータは、文書あたり 640KiB です。デフォルトで、これは 512KiB の最上位クォータと 128KiB の共有クォータに分割されます。

  • デフォルトで 512KiB の最上位クォータは、最上位文書およびそのオリジンを使用する直接のサブフレームから行われる fetchLater() リクエストすべてに適用されます。
  • デフォルトで 128KiB の共有クォータが適用されるのは、オリジンを越えるサブフレーム(例えば <iframe><object><embed>、および <frame> 要素)内で行われる fetchLater() リクエストすべてです。

fetchLater() によるリクエストは任意の URL に対して行うことができ、ドキュメントやサブフレームと同じオリジンに限定されるわけではないため、最上位の文書コンテンツ内で行われるリクエスト(ファーストパーティまたはサードパーティーのオリジンへのリクエストを問わず)と、サブフレーム内で行われるリクエストとを区別することが重要です。

例えば、最上位の a.com 文書が <script> を含み、それが analytics.example.com に対して fetchLater() リクエストを行う場合、このリクエストは最上位レベルの 512KiB の制限の対象となります。一方、最上位の文書に、ソースが analytics.example.comfetchLater() リクエストを行う <iframe> が埋め込まれている場合、そのリクエストには 128KiB の制限が適用されます。

報告元およびサブフレームごとの割当制限

最上位の 512KiB のクォータのうち、同一のレポート送信元(リクエスト URL のオリジン)に対して同時に使用できるのは 64KiB のみです。これにより、サードパーティ製ライブラリーが、送信するデータがない段階で、機会を捉えてクォータを予約してしまうことを防ぎます。

それぞれのオリジン横断サブフレームには、デフォルトで共有の 128KiB のクォータのうち 8KiB が割り当てられます。この割り当ては、サブフレームが DOM に追加された時点で実行されます(そのサブフレームで fetchLater() が使用されるかどうかに関係なく)。つまり、一般的に、ページに追加されたオリジン横断サブフレームのうち、最初の 16 個のみがfetchLater()を使用することができます。これは、これら 16 個のサブフレームで 128KiB のクォータを使い切ってしまうためです。

最上位クォータを共有することで、サブフレームのクォータを増やす

最上位のオリジンは、選択されたオリジン横断サブフレームに対して、64KiB の追加クォータを付与できます。これにより、そのサブフレームは、最上位の 512KiB という制限から除外されます。この権限ポリシーディレクティブに、それらのオリジンを出力することで実現されます。これは、サブフレームが DOM に追加された際に割り当てられ、最上位の文書および直接の同一オリジンサブフレームに割り当てられるクォータを減少させます。複数の同一オリジンサブドメインが、それぞれ 64KiB のクォータを取得できます。

共有クォータの制限

最上位のオリジンは、deferred-fetch-minimal 権限ポリシーに特定のオリジンを記載することで、128KiB の共有クォータを名前付きオリジン横断サブフレームに制限することも可能です。同時に、deferred-fetch-minimal 権限ポリシーを () に設定することで、128KiB のデフォルトのサブフレームクォータ全体を取り消し、代わりに自身および名前付き deferred-fetch のクロスオリジンに対して 640KiB のクォータを全量確保することも可能です。

サブフレームのサブフレームへのクォータの委譲

デフォルトで、サブフレームのサブフレームにはクォータが割り当てられていないため、fetchLater() を使用することはできません。64KiB に増量されたクォータが割り当てられたサブフレームは、自分自身で deferred-fetch 権限ポリシーを設定することで、64KiB のクォータ全体をさらに下位のサブフレームに委譲し、それらのサブフレームが fetchLater() を使用することができるようにすることができます。ただし、クォータの一部ではなく、全量をさらなるサブフレームに委譲することしかできず、新しいクォータを指定することはできません。最小の 8KiB クォータを使用しているサブフレームは、サブフレームにクォータを委譲することはできません。クォータを委譲されるためには、サブサブフレームがトップレベルおよびサブフレームの deferred-fetch Permissions-Policy ディレクティブの両方に含まれている必要があります。

クォータを超過した場合

クォータを超過した場合、遅延リクエストを開始するために fetchLater() メソッドが呼び出されると、QuotaExceededError が発生します。

権限ポリシーのチェックは、クォータのチェックと区別がつきません。fetchLater() を呼び出すと、実際にクォータを超過している場合だけでなく、権限ポリシーによってそのオリジンに対してクォータが制限されている場合にも、QuotaExceededError が発生します。

fetchLater() を呼び出し側は、特にサードパーティー製の JavaScript を埋め込んでいる場合は、ほぼすべての場合で QuotaExceededError エラーをキャッチするなど、予防的な措置を講じる必要があります。

クォータの例

最低限のクォータをすべて使用する

http
Permissions-Policy: deferred-fetch=(self "https://b.com")
  1. <iframe src="https://b.com/page"> は、最上位文書に追加された際、その最上位文書の 512KiB の制限枠から 64KiB を割り当てられます。
  2. <iframe src="https://c.com/page"> は掲載されていないため、最上位文書に追加された際、128KiB の共有制限枠から 8KiB が割り当てられます。
  3. さらに 15 個の異なるオリジンからの iframe は、それぞれ最上位文書に追加された際に 8KiB が割り当てられます(c.com の場合と同様)。
  4. 次の異なるオリジンからの iframe には、割り当てが一切行われません。
  5. 異なるオリジンからの iframe のいずれかが除去された場合、その保留されていたフェッチが送信されます。
  6. 再びクォータが利用できるため、次の異なるオリジンからの iframe には 8KiB のクォータが割り当てられることになります。

名前付きオリジンに対する最低クォータの制限を解除する

http
Permissions-Policy: deferred-fetch-minimal=("https://b.com")
  1. <iframe src="https://b.com/page"> は、最上位文書に追加された際に 8KiB の割り当てを受けます。
  2. <iframe src="https://c.com/page"> は、最上位文書に追加された際、割り当てを受けません。
  3. 最上位文書およびその同一オリジンの子要素は、最大 512KiB まで使用することができます。

最上位の例外を除き、最低クォータを完全に撤廃する

http
Permissions-Policy: deferred-fetch=(self "https://b.com")
Permissions-Policy: deferred-fetch-minimal=()
  1. <iframe src="https://b.com/page"> は、最上位文書に追加された時点で 64KiB の割り当てを受けます。
  2. <iframe src="https://c.com/page"> は、最上位文書に追加された時点で割り当てを受けません。
  3. 最上位文書およびその同一オリジンの子要素は、最大で 640KiB まで使用することができますが、b.com のサブフレームが作成されると、そのクォータは 574KiB に縮小されます(複数の b.com サブフレームが作成された場合は、それぞれのサブフレームに 64KiB のクォータが割り当てられるため、さらに少なくなります)。

例外なく、最低クォータを完全に撤廃する

http
Permissions-Policy: deferred-fetch-minimal=()
  1. 最上位の文書と、その同一オリジンの子要素は、640KiB の全容量を使用することができます。
  2. サブフレームには割り当てられたクォータがなく、fetchLater() を使用することはできません。

同一オリジンのサブフレームは、最上位レベルとクォータを共有し、サブフレームに権限を委譲することが可能

a.com 上の最上位文書があり、そこに a.com のサブフレームが埋め込まれ、さらにその中に b.com のサブフレームが埋め込まれているが、明示的な権限ポリシーは存在しない場合を想定します。

  1. a.com の最上位文書に、デフォルトで 512KiB のクォータが割り当てられています。
  2. <iframe src="https://a.com/embed"> は、最上位文書に追加されると、512KiB のクォータを共有します。
  3. <iframe src="https://b.com/embed"> は、最上位文書に追加されると、8KiB のクォータが割り当てられます。

同一オリジンのサブフレームは、オリジンを越えるサブフレームによって分離されている場合、最上位とクォータを共有することはできない

a.com 上の最上位文書があり、そこに <iframe src="https://b.com/"> が埋め込まれており、その中に <iframe src="https://a.com/embed"> のサブフレームが埋め込まれているが、明示的な権限ポリシーは設定されていない場合を想定します。

  1. a.com の最上位文書には、デフォルトで 512KiB のクォータが割り当てられています。
  2. <iframe src="https://b.com/"> は、8KiB のクォータを共有します。
  3. <iframe src="https://a.com/embed"> にはクォータが割り当てられません。たとえ最上位オリジンと同じオリジンであったとしても、オリジンを越えることで別個のオリジンとして分離されているためです。

サブフレームの二次サブフレームは、デフォルトでクォータを取得しない

a.com 上の最上位文書があり、そこに <iframe src="https://b.com/"> が埋め込まれており、その中にさらに <iframe src="https://c.com/"> が埋め込まれているが、明示的な権限ポリシーは設定されていない場合を想定します。

  1. a.com の最上位フレームには、デフォルトの 512KiB のクォータが割り当てられています。
  2. <iframe src="https://b.com/"> には、デフォルトの共有クォータから 8KiB が割り当てられます。
  3. <iframe src="https://c.com/"> にはクォータが割り当てられません。

別のサブフレームに全クォータを割り当てる

a.com 上の最上位文書があり、そこに <iframe src="https://b.com/"> が埋め込まれており、その中にさらに <iframe src="https://c.com/"> が埋め込まれていると仮定します。

a.com が以下の権限ポリシーを持つことを想定します。

http
Permissions-Policy: deferred-fetch=("https://c.com" "https://c.com")

また、b.com が以下の権限ポリシーを持っていることを想定します。

http
Permissions-Policy: deferred-fetch=("https://c.com")
  1. a.com の最上位フレームは、デフォルトで 512KiB の割り当て量を持っています。
  2. <iframe src="https://b.com/"> には、デフォルトの割り当て量から 64KiB が割り当てられます。
  3. <iframe src="https://b.com/"> は、自身のクォータ全量である 8KiB を c.com に委譲します。b.comfetchLater() を使用できません。
  4. <iframe src="https://c.com/"> は 8KiB のクォータを受け取ります。

リダイレクトではクォータは移譲されない

a.com 上の最上位文書があり、そこに <iframe src="https://b.com/"> が埋め込まれており、これが c.com にリダイレクトされ、明示的な最上位の権限ポリシーが存在しない場合を想定します。

  1. a.com の最上位フレームは、デフォルトで 512KiB の割り当て量を保有しています。
  2. <iframe src="https://b.com/"> には、デフォルトの共有クォータから 8KiB が割り当てられます。
  3. <iframe src="https://b.com/">c.com にリダイレクトされた場合、この 8KiB は c.com には引き継がれませんが、解放されることもありません。

サンドボックス化された同じオリジンからの iframe は、実質的に別個のオリジンとなる

例えば、以下の <iframe>https://www.example.com に埋め込まれている場合、

html
<iframe src="https://www.example.com/iframe" sandbox="allow-scripts"></iframe>

これは、最上位の文書と同じオリジンでホストされているにもかかわらず、「同一オリジン」とは見なされません。なぜなら、<iframe> はサンドボックス化された環境内にあるからです。したがって、デフォルトで、共有されている合計 128KiB のクォータから、8KiB のクォータが割り当てられることになります。

iframe からの fetchLater() の実行を禁止する

<iframe> に対して fetchLater() のクォータが割り当てられないようにするには、<iframe>allow 属性を使用することができます。

html
<iframe
  src="https://www.example.com/iframe"
  allow="deferred-fetch;deferred-fetch-minimal;"></iframe>

同一オリジンの iframe が 512KiB の割り当てを消費してしまうのを防ぐには allow=「deferred-fetch」 ディレクティブが必要であり、別のオリジンの iframe が 128KiB の割り当てを消費してしまうのを防ぐには allow="deferred-fetch-minimal" ディレクティブが必要です。両方のディレクティブを記載することで、src の値にかかわらず、両方の割り当てが使用されるのを防ぐことができます。

QuotaExceededError が発生する例

js
// オリジンあたり 64KiB
const url = "<72KiB of characters>";
fetchLater(url);

// オリジンあたりヘッダーを含めて 64KiB
fetchLater("https://origin.example.com", { headers: headersExceeding64KiB });

// オリジンあたり、本体とヘッダーを含め最大 64KiB
fetchLater("<32KiB of characters>", { headers: headersExceeding32KiB });

// オリジンあたり、本体を含め最大 64KiB
fetchLater("https://origin.example.com", {
  method: "POST",
  body: bodyExceeding64KiB,
});

// オリジンあたり、本体および自動的に追加するヘッダーを含めて最大 64KiB
fetchLater("<62KiB of characters>" /* 3kb のリファラー付き */);

最終的に QuotaExceededError を発生させる例

最上位の文書に記述されている以下のシーケンスでは、最初の 2 つのリクエストは成功しますが、3 つ目のリクエストでは例外が発生します。これは、たとえ全体としての 640KiB のクォータは超過していないものの、3 つ目のリクエストが https://a.example.com のレポート対象オリジンごとのクォータを超過しているため、例外が発生するからです。

js
fetchLater("https://a.example.com", { method: "POST", body: a40KiBBody });
fetchLater("https://b.example.com", { method: "POST", body: a40KiBBody });
fetchLater("https://a.example.com", { method: "POST", body: a40KiBBody });

サブフレームから最上位のオリジンへのリダイレクトにより、最上位のクォータを使用できる

a.com にある最上位文書に、<iframe src="https://b.com/"> が埋め込まれており、これが a.com にリダイレクトされ、明示的な最上位の権限ポリシーが存在しない場合を想定します。

  1. a.com の最上位フレームには、デフォルトの 512KiB のクォータが割り当てられています。
  2. <iframe src="https://b.com/"> は、デフォルトの共有クォータ 128KiB のうち 8KiB を割り当てられます。
  3. <iframe src="https://b.com/">a.com にリダイレクトされた際、この 8KiB は a.com には移譲されませんが、a.com は再び最上位のクォータを全量共有可能になり、前回割り当てられていた 8KiB のクォータは解放されます。