優先度付きタスクスケジューリング

コミュニティグループドラフト報告書,

このバージョン:
https://wicg.github.io/scheduling-apis/
課題トラッキング:
GitHub
仕様内インライン
編集者:
(Google)

概要

この仕様は、優先度付きタスクのスケジューリングおよび制御のためのAPIを定義します。

この文書のステータス

この仕様はWeb Platform Incubator Community Groupによって発行されました。 W3C標準ではなく、W3C標準化の過程にもありません。 W3C Community Contributor License Agreement (CLA)の下で限定的なオプトアウトがあり、他の条件も適用されることにご注意ください。 W3Cコミュニティおよびビジネスグループについて詳しくは、こちらをご覧ください。

1. はじめに

このセクションは規範的ではありません。

スケジューリングはウェブサイトのパフォーマンス向上のための重要な開発ツールとなる場合があります。大きく分けると、スケジューリングによって効果のある領域は「ユーザー体感の待ち時間」と「応答性」の2つです。スケジューリングにより、ユーザー体感の待ち時間を短縮できるのは、品質に直接影響を与える高優先度の作業を優先し、低優先度作業を後回しにできるからです。たとえば、ページロード時に特定のサードパーティ ライブラリスクリプトの実行を後回しにすることで、ユーザーにより早く画面を表示させられます。同様に、ビューポート内のコンテンツに関する作業を優先することも有効です。メインスレッド上のスクリプトの場合、長時間タスクは入力やUIの更新処理をブロックし、入力・視覚的応答性を低下させます。これらのタスクを小さく分割し、その断片やタスク継続としてスケジューリングするのは、アプリケーションやフレームワーク開発者が応答性向上のために用いる実証された手法です。

ユーザースペースのスケジューラーは、通常タスクのスケジューリング・実行タイミングを制御するメソッドを提供します。タスクには通常優先度が設定され、それが他タスクとの関係で実行タイミングを大きく左右します。スケジューラーは一定時間(スケジューラークォンタム)タスクを実行し、その後またブラウザへ制御を戻す、という動作を繰り返します。スケジューラーは続きの処理用に継続タスク、例えばsetTimeout()やpostMessage()などをスケジューリングして再開します。

ユーザースペースのスケジューラーは成功してきましたが、集中管理型のブラウザスケジューラーとより良いスケジューリングプリミティブがあれば状況はさらに改善します。スケジューラーの優先度システムは、基本的にそのスケジューラーの適用範囲内に留まります。つまり、ユーザースペーススケジューラーの場合、UAがタスクの優先度を知ることは原則としてありません。ただしrequestIdleCallback()を使う場合だけ例外ですが、これは最低優先度の作業に限定されます。同様に、ページ上に複数のスケジューラーがあるケースも増えています。例えば、フレームワーク(例:React)が独自にスケジューラーを持ち、自前スケジューリングをし、さらにスケジューラーを持つ機能(例:埋め込み地図)を組み込むこともあります。ブラウザはグローバルな情報を持ち、イベントループがタスクを実行する責任を負っているため、理想的な協調ポイントといえます。

優先度以外にも、ユーザースペーススケジューラーが依存する既存プリミティブは現代的ユースケースには最適でありません。setTimeout(0)は非遅延タスクのスケジューリング手段として一般的ですが、(ネストしたタスクなどに対し)最小遅延値があることも多く、これが余計な待ち時間を発生させ、パフォーマンス低下につながる場合もあります。よく知られた回避策としてpostMessage()やMessageChannelを使う方法がありますが、これらAPIはスケジューリング向けに設計されておらず、コールバックをキューすることはできません。requestIdleCallback()は一部用途で効果的ですが、アイドルタスクのみに適用でき、優先度の変化(例:ユーザー入力に応じオフスクリーンコンテンツの再優先付け等)には対応できません。

本仕様は、開発者が優先度付きタスクおよび継続をスケジューリング・制御できる新しいインターフェースを導入します。この文脈でのタスクとは、自身のイベントループの独立したタスク内で非同期に実行されるJavaScriptコールバックです。継続とは、制御をいったんブラウザに戻した後に、新たなイベントループのタスクとしてJavaScriptコードが再開することです。Schedulerインターフェースは、タスクのスケジューリング用postTask()メソッドと継続スケジューリング用yield()メソッドを公開します。本仕様は、タスクおよび継続の実行順序制御に利用できるTaskPrioritiesを定義します。さらに、TaskControllerとそれに紐づくTaskSignalにより、スケジューリング済みタスクの中断や優先度制御が可能です。

2. タスクおよび継続のスケジューリング

2.1. タスクと継続の優先度

本仕様はタスクスケジューリングのために3つの優先度を正式化します:

enum TaskPriority {
  "user-blocking",
  "user-visible",
  "background"
};

user-blockingは最も高い優先度で、できるだけ早く実行する必要があるタスク向けです。これを低優先度で実行するとユーザー体験が劣化します。例えば、ユーザー入力への直接応答や、ビューポート内UI状態の更新など(チャンク化したもの含む)が該当します。

この優先度でスケジューリングされたタスクは、通常他のタスクより高いイベントループ優先度を持ちますが、必ずしもレンダーブロックにはなりません。即時中断なく行いたい作業は同期的に実行すべきですが、それが長引くと応答性が悪化します。一方、"user-blocking"タスクを使えば、作業を分割・スケジューリングしつつ入力・描画応答性を維持し、可能な限り早期に完了させやすくなります。

user-visibleは2番目に高い優先度で、ユーザーにとって有益な副作用が発生するが、即座にユーザーに観測されたりユーザー体験に不可欠でない処理向けです。この優先度のタスクは"user-blocking"タスクより重要性・緊急性が劣ります。これがデフォルトの優先度です。

backgroundは最も低い優先度で、たとえばバックグラウンドでのログ・メトリクス処理や特定のサードパーティライブラリの初期化など、即時でなくてよい作業向けです。

注記: 指定された Scheduler を通じてスケジュールされたタスクは 厳格な優先順位順 で実行されます。つまり、スケジューラは常に "user-blocking" タスクを "user-visible" タスクより先に実行し、これらは常に "background" タスクより先に実行されます。継続は、同じ TaskPriority を持つタスクよりも高い effective priority を持ちます。

2.2. Schedulerインターフェース

dictionary SchedulerPostTaskOptions {
  AbortSignal signal;
  TaskPriority priority;
  [EnforceRange] unsigned long long delay = 0;
};

callback SchedulerPostTaskCallback = any ();

[Exposed=(Window, Worker)]
interface Scheduler {
  Promise<any> postTask(SchedulerPostTaskCallback callback,
                        optional SchedulerPostTaskOptions options = {});
  Promise<undefined> yield();
};

注記: signal オプションは AbortSignal または TaskSignal を指定できるが、 AbortSignal の方が TaskSignal のスーパークラスであるため 定義されている。 優先度が変化する可能性がある場合は TaskSignal が必要となるが、 キャンセルのみの場合は AbortSignal だけでよいので、 AbortSignals を使った既存コードへも簡単に統合できる。

result = scheduler . postTask(callback, options )

callbackの戻り値でPromiseがfulfilled、タスクが中断された場合はAbortSignalのabort reasonでrejectedとなります。callback実行中エラーが発生した場合、postTask()が返すpromiseはそのエラーでrejectされます。

タスクのpriorityは、optionのpriorityとsignalの組合せで決まります:

  • optionのpriorityが指定されていれば、そのTaskPriorityでスケジューリングされ、タスクの優先度は不変です。

  • それ以外でoptionのsignalにTaskSignalオブジェクトが指定されていれば、そのsignalのpriorityがタスク優先度となります。この場合、タスクの優先度は動的としてTaskController関連のcontroller.setPriority()呼び出しで変更可能です。

  • それ以外はタスクの優先度は"user-visible"となります。

optionのsignalが指定されていれば、タスクの中断判定としてsignalをSchedulerが使います。

optionのdelayが0より大きく指定されている場合、タスク実行は最低delayミリ秒遅延されます。

result = scheduler . yield()

Promiseがundefinedでfulfilledされるか、継続処理が中断されるとAbortSignalのabort reasonでrejectedとなります。

継続処理の優先度と中断用signalは、元のタスクから受け継がれます。元タスクのスケジューリングにpostTask()+AbortSignalを指定した場合、そのsignalが継続の中断判定に使われます。元タスクの優先度(TaskSignalまたは固定優先度)も継続の優先度判定に使います。優先度の指定がなかった場合は"user-visible"になります。

Schedulerオブジェクトは、静的優先度タスクキューマップ(map)を持ちます。これは(TaskPriority, boolean)をキー、scheduler task queueを値とするmapで、空で初期化されます。

Schedulerオブジェクトは、動的優先度タスクキューマップ(map)も持ちます。これは(TaskSignal, boolean)をキー、scheduler task queueを値とするmapで、空で初期化されます。

注:動的優先度の実装は、特定TaskSignalに紐づくタスクを同一タスクキューにまとめ、prioritychangeイベントでそのキューの優先度を変える方式です。動的優先度タスクキューマップには、優先度が変わりうるタスクキューが格納され、キーは共通TaskSignalです。

静的優先度タスクキューマップの値は、優先度が変わらないタスクキューです。明示的priorityやnull/AbortSignalなsignalでスケジューリングされた静的優先度タスクは、マップのキーとなるTaskPriorityに基づいてここに振り分けられます。

別実装法としては各TaskPriorityごとにタスクキューを1つだけ持ち、TaskSignalのpriority変更時にタスクキュー間でエンキュー順序に従って挿入しながらタスク単位で移動するという方式もあり、この場合は次に実行するキューの決定が簡単になる反面、優先度変更時の処理が複雑になります。

postTask(callback, options) メソッドの手順は、callback と options を与えて this に対して postTask タスクをスケジュールした結果を返すことです。

yield() メソッドの手順は、 this に対してyield 継続をスケジュールした結果を返すことです。

2.3. 定義

スケジューラータスクは、追加の数値型enqueue順序(item・初期値0)を持つタスクです。

以下のタスクソースはスケジューラータスクソースとして定義され、スケジューラータスクでのみ使います。

posted task タスクソース

このタスクソースはpostTask()またはyield()でスケジューリングされたタスクに使います。


スケジューラータスクキューは、以下のitemを持つstructです:

priority

TaskPriority

is continuation

boolean

tasks

セット(スケジューラータスクの集合)

removal steps

アルゴリズム


スケジューリング状態は、以下のitemを持つstructです:

abort source

AbortSignalオブジェクトまたはnull(初期値: null)

priority source

TaskSignalオブジェクトまたはnull(初期値: null)


継続状態は、以下のitemを持つstructです:

state map

初期値は空のmap

注:state mapのキーとしてガベージコレクト付きオブジェクトを使う場合、WeakMapでの実装が可能です。


タスクハンドルは、以下のitemを持つstructです:

task

スケジューラータスクまたはnull

queue

スケジューラータスクキューまたはnull

abort steps

アルゴリズム

task complete steps

アルゴリズム

2.4. 処理モデル

スケジューラータスク t1 は より古い スケジューラータスク t2 である、もし t1 の enqueue順序 が t2 の enqueue順序 より小さい場合。
スケジューラータスクキューを作成する TaskPriority priority, boolean の isContinuation、およびアルゴリズム removalSteps を受け取り:
  1. queue を新しい スケジューラータスクキュー とする。

  2. queue の priority に priority を設定する。

  3. queue の is continuation に isContinuation を設定する。

  4. queue の tasks に新しい空の set を設定する。

  5. queue の removal steps に removalSteps を設定する。

  6. queue を返す。

タスクハンドルを作成する Promise result と AbortSignal または null の signal を受け取り:
  1. handle を新しい タスクハンドル とする。

  2. handle の task を null に設定する。

  3. handle の queue を null に設定する。

  4. handle の abort steps に次の手順を設定する:

    1. result を signal の abort reason で reject する。

    2. もし task が null でなければ、

      1. task を queue から削除する。

      2. もし queue が empty なら、queue の removal steps を実行する。

  5. handle の task complete steps に次の手順を設定する:

    1. もし signal が null でなければ、handle の abort steps を signal から削除する。

    2. もし queue が empty なら、queue の removal steps を実行する。

  6. handle を返す。

スケジューラータスクキュー queue の first runnable task は queue の tasks の中で最初の スケジューラータスク であり、runnable であるもの。
scheduler task queue queue の effective priority(有効優先度) は、 queue の priority および is continuation に一致する行の3列目の値で決定される:

優先度 継続か 有効優先度
"background" false 0
"background" true 1
"user-visible" false 2
"user-visible" true 3
"user-blocking" false 4
"user-blocking" true 5

2.4.1. スケジューラータスクのキューイングと削除

スケジューラータスクをキューする スケジューラータスクキュー queue 上で、一連の手順 steps を実行し、数値 enqueue order、タスクソース source、document document を受け取り:
  1. task を新しい スケジューラータスク とする。

  2. task の enqueue順序 に enqueue order を設定する。

  3. task の steps に steps を設定する。

  4. task の source に source を設定する。

  5. task の document に document を設定する。

  6. task の script evaluation environment settings object set に新しい空の set を設定する。

  7. task を queue の tasks に追加する。

  8. task を返す。

HTML仕様をリファクタリングして task のコンストラクタを追加することを検討すべきです。一つの問題点は新しいタスクが スケジューラータスク である必要がある点です(通常の task ではない)。

タスクを削除する スケジューラータスク task を スケジューラータスクキュー queue から 削除する。task を queue の tasks から削除。
スケジューラータスクキュー queue は empty、 すなわち queue の tasks が empty の場合。

2.4.2. タスクと継続のスケジューリング

To 現在のスケジューリング状態を設定する for scheduler (a Scheduler) を state (a スケジューリング状態) に設定するには:
  1. eventLoop を scheduler の 関連エージェント の イベントループ とする。

  2. 継続状態値を設定する、scheduler と state を eventLoop に渡して行う。

注: state map のキーは Scheduler に固有であれば何でもよい。

To 現在のスケジューリング状態を取得する for scheduler (a Scheduler):
  1. eventLoop を scheduler の 関連エージェント の イベントループ とする。

  2. 継続状態値を取得する、scheduler と eventLoop を渡して、その結果を返す。

To postTaskタスクをスケジューリングする for Scheduler scheduler、SchedulerPostTaskCallback callback および SchedulerPostTaskOptions options の場合:
  1. result を 新しいPromiseとする。

  2. signal を options["signal"] が 存在すれば それ、なければ null にする。

  3. signal が null でなく、かつ 中断されている 場合は、result を signal の abort理由 で reject し、result を返す。

  4. state を新しい スケジューリング状態 とする。

  5. state の abort source に signal を設定する。

  6. options["priority"]が存在する場合は、stateのpriority sourceに、options["priority"]を指定して固定優先度・中断不可タスクシグナルを作成した結果を設定する。

  7. それ以外で signal が null でなく、かつ TaskSignalインターフェースを実装している 場合は、state の priority source に signal を設定する。

  8. state の priority source が null の場合は、固定優先度・中断不可タスクシグナルを作成("user-visible"を指定)した結果をstateのpriority sourceに設定する。

  9. handle を タスクハンドル作成(resultとsignalを渡す)の結果とする。

  10. signal が null でなければ、handle の abort steps を signal に追加する。

  11. enqueueSteps に以下の手順を設定:

    1. handle の queue に スケジューラータスクキューの選択結果(scheduler, state の priority source, false を渡す)を設定。

    2. アルゴリズム実行タスクをスケジューリング(scheduler, handle, 下記手順):

      1. eventLoop を scheduler の 関連エージェント の イベントループ とする。

      2. 現在のスケジューリング状態を設定(scheduler, state)。

      3. callbackResult を callback を呼び出す結果とする(« » および「rethrow」指定)。 throwされた場合は result をその例外で reject、そうでなければ result を callbackResult で解決。

      4. eventLoop の current continuation state を null にする。

  12. delay を options["delay"] とする。

  13. delay が0より大きければ、timeout後の手順実行をする(scheduler の関連globalオブジェクト・"scheduler-postTask"・delay・下記手順):

    1. signal が null または 中断されていなければ、enqueueStepsを実行。

  14. それ以外は enqueueSteps を実行。

  15. result を返す。

注: ここで作成される固定優先度の中断不可signalはメモリアロケーションを抑えるためキャッシュ再利用可能。

timeout後の手順はsuspensionを考慮しない場合あり。詳細は whatwg/html#5925 参照。

To yield継続をスケジューリングする for Scheduler scheduler:
  1. result を 新しいPromiseとする。

  2. inheritedState を 現在のスケジューリング状態を取得する(scheduler)結果とする。

  3. abortSource を inheritedState の abort source (nullでなければ)それ、なければnull。

  4. abortSource が null でなく 中断 されていれば、result を abortSource の abort理由で reject して返す。

  5. prioritySource を inheritedState の priority source (nullでなければ)それ、なければ null。

  6. prioritySource がnullなら、固定優先度・中断不可タスクシグナル作成("user-visible")の結果を設定。

  7. handle を タスクハンドル作成(result, abortSource)で得る。

  8. abortSource がnullでなければ、handle の abort steps を abortSource に追加。

  9. handle の queue に、スケジューラータスクキュー選択(scheduler, prioritySource, true)を設定。

  10. アルゴリズム実行タスクをスケジューリング(scheduler, handle, 下記手順):

    1. result を resolve する。

  11. result を返す。

注: 固定優先度・中断不可signalはキャッシュ再利用可。

To スケジューラータスクキューを選択する for a Scheduler scheduler、TaskSignal オブジェクト signal、boolean isContinuation の場合:
  1. signal が 固定優先度を持たないなら、

    1. scheduler の 動的優先度タスクキューマップ に (signal, isContinuation) が 含まれていなければ、

      1. queue を スケジューラータスクキュー作成(signal の priority・isContinuation・下記手順)とする。

        1. 動的優先度タスクキューマップ [(signal, isContinuation)] を削除。

      2. 動的優先度タスクキューマップ[(signal, isContinuation)] に queue を設定。

      3. 優先度変更アルゴリズム追加 を signal に対して次のステップで追加:

        1. queue の priority を signal の priority に設定。

    2. 動的優先度タスクキューマップ[(signal, isContinuation)] を返す。

  2. それ以外の場合

    1. priority を signal の priority とする。

    2. scheduler の 静的優先度タスクキューマップ に (priority, isContinuation) が 含まれていなければ、

      1. queue を スケジューラータスクキュー作成(priority, isContinuation, 下記手順)とする。

        1. 静的優先度タスクキューマップ [(priority, isContinuation)] を削除。

      2. 静的優先度タスクキューマップ[(priority, isContinuation)] に queue を設定。

    3. 静的優先度タスクキューマップ[(priority, isContinuation)] を返す。

To アルゴリズム実行タスクをスケジューリングする for Scheduler scheduler に タスクハンドル handle と アルゴリズム steps を渡す場合:
  1. global を 関連globalオブジェクト(schedulerの)とする。

  2. document を global の 関連Document とする(globalが Window オブジェクトなら、それ以外は null)。

  3. event loop を scheduler の 関連エージェント の イベントループ とする。

  4. enqueue order を event loop の next enqueue order とする。

  5. event loop の next enqueue order を1増やす。

  6. handle の task に、スケジューラータスクキューにタスクをキューする(handle の queue・enqueue order・posted task task source・document・下記手順)を設定:

    1. steps を実行する。

    2. handle の task complete steps を実行する。

このアルゴリズムは in parallel ステップから呼ばれるため一部競合状態になる可能性がある。特に next enqueue order の更新や スケジューラータスクキュー へのアクセスは原子的に行うべき。後者はイベントループのタスクキューにも影響(詳細は このissue 参照)。

2.4.3. 次に実行するタスクの選択

Scheduler scheduler は、実行可能なタスクを持つ、とする。これは 実行可能なタスクキューの取得を scheduler で行った結果が 空でない場合である。
実行可能なタスクキューの取得 Scheduler scheduler について:
  1. queues を scheduler の static priority task queue map の値取得 結果とする。

  2. queues を拡張。scheduler の dynamic priority task queue map の値取得結果で拡張する。

  3. queues から、queue の tasks に runnable な scheduler task が含まれないものを削除。

  4. queues を返す。

すべてのスケジューラーから次のスケジューラータスクキューを選択する。イベントループ event loop を渡し、次を実行する。スケジューラータスクキュー または関連する Scheduler が event loop に関連付けられていて 実行可能なタスクを持つ 場合のみ返す。なければ null。
  1. queues を空の セット とする。

  2. schedulers を、セットで、 すべてのScheduler のうち 関連エージェント の event loop が event loop かつ 実行可能なタスクを持つもの。

  3. 各 scheduler について、queues を、 実行可能なタスクキューの取得 (scheduler) の結果で拡張する。

  4. もし queues が 空 なら null を返す。

  5. queues から、queue の 実効優先度 が queues 内の他の item より小さいものを削除する。

  6. queue を queues 内で first runnable task が もっとも古い スケジューラータスクキュー とする。
    enqueue順序は一意なので2つのタスクが同じageを持つことはない。

  7. queue を返す。

注: 次に実行されるタスクは、全Schedulerの中から event loop に紐づく、最も古くて(enqueue順序が最小)、かつ最高優先度の runnable scheduler task である。

2.5. 例

TODO(shaseley): 例を追加予定。

3. タスクの制御

Scheduler インターフェースでスケジューリングされたタスクは、TaskController を使い、controller.signal が提供する TaskSignal を option に渡して postTask() 呼び出し時に制御できます。 TaskController インターフェースでは、タスクまたはグループ単位での中断と優先度変更がサポートされています。

3.1. TaskPriorityChangeEvent インターフェース

[Exposed=(Window, Worker)]
interface TaskPriorityChangeEvent : Event {
  constructor(DOMString type, TaskPriorityChangeEventInit priorityChangeEventInitDict);

  readonly attribute TaskPriority previousPriority;
};

dictionary TaskPriorityChangeEventInit : EventInit {
  required TaskPriority previousPriority;
};
event . previousPriority

このTaskSignalが本prioritychangeイベントの直前に持っていたTaskPriorityを返します。

新しいTaskPriorityはevent.target.priorityで取得できます。

previousPriorityのgetter手順は、対応属性の初期値を返すことです。

3.2. TaskControllerインターフェース

dictionary TaskControllerInit {
  TaskPriority priority = "user-visible";
};

[Exposed=(Window,Worker)]
interface TaskController : AbortController {
  constructor(optional TaskControllerInit init = {});

  undefined setPriority(TaskPriority priority);
};

注: TaskControllerの signal のgetterは AbortController から継承されており、 TaskSignal オブジェクトを返します。

controller = new TaskController( init )

新しいTaskController を返します。そのsignal は新しく生成されたTaskSignal に割り当てられ、priority は init の priority で初期化されます。

controller . setPriority( priority )

このメソッドを呼び出すと、関連するTaskSignalの priorityが変更され、オブザーバへ通知とprioritychangeイベントの発火が行われます。

new TaskController(init) コンストラクタ手順:
  1. signal を新しい TaskSignal オブジェクトとする。

  2. signal の priority を init["priority"] に設定する。

  3. this の signal を signal に設定する。

setPriority(priority)メソッド手順は、 signal priority change を this の signal で priority を与え実行します。

3.3. TaskSignal インターフェイス

dictionary TaskSignalAnyInit {
  (TaskPriority or TaskSignal) priority = "user-visible";
};

[Exposed=(Window, Worker)]
interface TaskSignal : AbortSignal {
  [NewObject] static TaskSignal _any(sequence<AbortSignal> signals, optional TaskSignalAnyInit init = {});

  readonly attribute TaskPriority priority;

  attribute EventHandler onprioritychange;
};

注記: TaskSignal は AbortSignal を継承し、AbortSignal を受け入れるAPIで利用できる。また、postTask() は AbortSignal も受け入れ、動的な優先順位付けが不要な場合に便利である。

TaskSignal . any(signals, init)
TaskSignal インスタンスを返す。このインスタンスは、signals のいずれかが中止された場合に中止される。その abort reason は、中止を引き起こした signals のものとなる。 シグナルのpriority は init の priority で決定される。 これは固定値の TaskPriority または TaskSignal であり、後者の場合、新しいシグナルの priority はこのシグナルの値に応じて変化する。
signal . priority

このシグナルの TaskPriority を返す。

TaskSignal オブジェクトは、関連付けられた priority(TaskPriority)を持つ。

TaskSignal オブジェクトは、関連付けられた priority changing(boolean、初期値は false)を持つ。

TaskSignal オブジェクトは、関連付けられた priority change algorithms (アルゴリズムの集合で、priority changing が true の時に実行される。初期値は空)。

TaskSignal オブジェクトは、関連付けられた source signal(TaskSignal への弱参照で、 このオブジェクトの priority の依存先)、初期値は null。

TaskSignal オブジェクトは、関連付けられた dependent signals(その優先度をこのオブジェクトに依存するTaskSignal オブジェクトへの弱い セット、初期値は空)がある。

TaskSignal オブジェクトは、関連付けられた dependent(boolean、初期値は false)を持つ。


priority ゲッタの手順は、this の priority を返すことである。

onprioritychange 属性は、イベントハンドラー IDL 属性であり、 onprioritychange イベントハンドラー用で、イベントハンドラーイベントタイプは prioritychange である。

static な any(signals, init) メソッドの手順は、 依存タスクシグナルの作成を signals, init, current realm の引数で呼び出し、その結果を返すことである。


TaskSignal 固定優先度を持つ のは、 dependent シグナルで、その source signal が null の場合である。

優先度変更アルゴリズムの追加は、 algorithm を TaskSignal オブジェクト signal の priority change algorithms に 追加する。

依存タスクシグナルの作成 の手順(リスト of AbortSignal オブジェクト signals、TaskSignalAnyInit init、realm):
  1. resultSignal を 依存シグナルの作成の結果として signals、 TaskSignal インターフェイスと realm で作成する。

  2. resultSignal の dependent を true に設定。

  3. init["priority"] が TaskPriority である場合:

    1. resultSignal の priority に init["priority"] を設定する。

  4. それ以外の場合:

    1. sourceSignal を init["priority"] とする。

    2. resultSignal の priority に sourceSignal の priority を設定する。

    3. sourceSignal が 固定優先度を持たない 場合:

      1. sourceSignal の dependent が true の場合、 sourceSignal を sourceSignal の source signal にする。

      2. アサート: sourceSignal は dependent ではない。

      3. resultSignal の source signal を主信号への弱参照に設定する。

      4. resultSignal を sourceSignal の dependent signals に追加。

  5. resultSignal を返す。

signal priority change を TaskSignal オブジェクト signal に対して、TaskPriority priority を指定して行う手順:
  1. もし signal の priority changing が true であれば、throw により「NotAllowedError」 という DOMException を発生させる。

  2. もし signal の priority が priority と等しければ、処理を終了する。

  3. signal の priority changing を true に設定する。

  4. previousPriority を signal の priority とする。

  5. signal の priority を priority に設定する。

  6. For each algorithm of signal’s priority change algorithms、各 algorithm を実行する。

  7. Fire an event named prioritychange を signal に対して TaskPriorityChangeEvent を使って発火し、その previousPriority 属性を previousPriority に初期化する。

  8. For each dependentSignal of signal’s dependent signals、signal priority change を dependentSignal に対して priority で実行する。

  9. signal の priority changing を false に設定する。

固定優先度・abort不可タスクシグナルの作成( TaskPriority priority、realm realm):
  1. init を新しい TaskSignalAnyInit とする。

  2. init["priority"] に priority を設定する。

  3. 依存タスクシグナル作成(« », init, realm)の結果を返す。

3.3.1. ガベージコレクション

dependent な TaskSignal オブジェクトは、source signal が null でなく、かつ prioritychange イベントのイベントリスナーまたは priority change algorithms が空でない限り ガベージコレクションされてはならない。

3.4. 例

TODO(shaseley): 例を追加すること。

4. 他規格への修正

4.1. HTML標準

4.1.1. WindowOrWorkerGlobalScope

WindowOrWorkerGlobalScope ミックスインを実装する各オブジェクトは、対応する scheduler (新しい Scheduler)で初期化される。

partial interface mixin WindowOrWorkerGlobalScope {
  [Replaceable] readonly attribute Scheduler scheduler;
};

scheduler属性のゲッタ手順は、 this の scheduler を返すこと。

4.1.2. イベントループ: 定義

置き換え: 各event loopについて、すべてのtask sourceが特定のtask queueと関連付けられていなければならない。

修正後: 各event loopについて、task sourceのうちscheduler task sourceでないものは 特定のtask queueと関連付けられていなければならない。

追加: event loopは数値型の next enqueue order を持ち、初期値は 1 である。

注記:next enqueue orderは、同じイベントループに関連付けられたすべてのSchedulerの、同じTaskPriorityを持つscheduler task queue間で、タスクの実行順序を決定するために使う厳密に増加する数値である。厳密に増加し一意であることが保証されるなら、タイムスタンプでも十分である。

追加: event loopは current continuation state(continuation state または null)を持ち、初期値は null である。

次のアルゴリズムを追加:

continuation state の値を設定(key、value、eventLoop(イベントループ)):
  1. eventLoop の current continuation state が null なら、 eventLoopのcurrent continuation stateに新しい continuation state をセットする。

  2. continuationState を eventLoop の current continuation state とする。

  3. アサート: continuationState の state map[key] は 存在しない。

  4. continuationState の state map[key] に value をセットする。

continuation state の値を取得( key、eventLoop(イベントループ)):
  1. continuationState を eventLoop の current continuation state とする。

  2. continuationState が null でなく、かつ state map[key] が 存在する場合、その値を返し、そうでなければ null を返す。

4.1.3. イベントループ: 処理モデル

イベントループ処理ステップ(step 2 の前)に次の手順を追加:

  1. queues を、セットとして、 event loop の task queues から 1 つ以上 実行可能 な task を含むものを集めたものとする。

  2. schedulerQueue を 全スケジューラから次のスケジューラタスクキューを選択の結果とする。

step 2 を次のように修正:

  1. schedulerQueue が null でない、または queues が 空でない場合:

step 2.1 を次のように修正:

  1. taskQueue を次のいずれかとして、実装依存な方法で選ぶ:

注記: HTML規格は各task source単位での優先度付けを可能にするため、イベントループ処理手順における 次の task の task queue の選択を 実装依存としている。 同様に、本仕様は Scheduler タスクとイベントループの task queues のどちらを次に選ぶかも 実装依存としており、最大のスケジューリング柔軟性をUAに与える。

ただし本仕様の意図は、TaskPriority に応じて Scheduler タスクの優先度をイベントループの優先度へ反映させることである。特に "background" タスクや継続処理は、ほとんどの他のイベントループタスクよりも重要度が低いとみなされる一方、 "user-blocking" タスクと継続、さらに "user-visible" 継続(タスクに対してではなく)はより重要とみなされる。

戦略例としては、Scheduler タスクで effective priority 3 以上のものを、たとえば入力・描画など urgent な処理より少し低いが他のほとんどのタスクソースより高い優先度で実行することや、 Scheduler タスクで effective priority 0や1のものは、イベントループの task queues に他に実行可能タスクがない時だけ実行する戦略や、effective priority 2のSchedulerタスクは、他のスケジューリング関連のタスクソース(たとえばタイマータスクソース)と同様の扱いにすることなどが挙げられる。

このstepのtaskQueueは tasksの集合 か scheduler tasksの集合のいずれかであり、以降のステップはどちらにも remove と item しか使わないので おおむね互換となっている。本来なら共通のタスクキューインターフェイスを設け、pop() メソッドがplainな task を返せるといいが、それには相応のリファクタが要る。

4.1.4. イベントループ: タスクのキューイング

マイクロタスクをキューするアルゴリズムを、オプションのブーリアンignoreContinuationState(デフォルトはfalse)を受け入れるように変更する。

ステップ5を次のように変更する:

  1. continuationState を null にする。

  2. ignoreContinuationState が false かつ eventLoop の current continuation state が null でない場合、 continuationState を event loop の current continuation state のクローンに設定する。

  3. microtask の steps を次のように設定する:

    1. ignoreContinuationState が false の場合、eventLoop の current continuation state を continuationState に設定する。

    2. steps を実行する。

    3. ignoreContinuationState が false の場合、eventLoop の current continuation state を null に設定する。

4.1.5. HostMakeJobCallback(callable)

ステップ5の前に次を追加:

  1. event loop を incumbent settings の realm の agent の event loop とする。

  2. state を、event loop の current continuation state のクローン(current continuation state が null なら null)に設定する。

ステップ5を次のように修正:

  1. JobCallback Record { [[Callback]]: callable, [[HostDefined]]: { [[IncumbentSettings]]: incumbent settings, [[ActiveScriptContext]]: script execution context, [[ContinuationState]]: state } } を返す。

4.1.6. HostCallJobCallback(callback, V, argumentsList)

ステップ5の前に次を追加:

  1. event loop を incumbent settings の realm の agent の event loop とする。

  2. event loop の current continuation state を callback.[[HostDefined]].[[ContinuationState]] に設定する。

ステップ7の後に次を追加:

  1. event loop の current continuation state を null に設定する。

4.1.7. HostEnqueuePromiseJob(job, realm)

ステップ2を次のように変更:

  1. ignoreContinuationState を true にして、以下の手順を実行するマイクロタスクをキューする。

4.2. requestIdleCallback()

4.2.1. アイドルコールバックの実行アルゴリズム

ステップ3.3の前に次を追加:

  1. realm を window の relevant realm とする。

  2. state を新しい scheduling state とする。

  3. state の priority source を、"background" および realm を用いた 固定優先度unabortableタスクシグナル作成 の結果に設定する。

  4. scheduler を Scheduler で、その relevant realm が realm であるものとする。

  5. scheduler の current scheduling state を state に設定する。

ステップ3.3の後に次を追加:

  1. event loop の current continuation state を null に設定する。

5. セキュリティの考慮事項

このセクションは規範的でない。

本仕様で定義されたAPIの主なセキュリティ上の考慮事項は、タイミングベースのサイドチャネル攻撃によってオリジン間で情報が漏洩する可能性があるかどうかである。

5.1. postTask を高精度タイミングソースとして用いる場合

このAPIは高精度なタイミングソースとしては使えない。setTimeout() の timeout値と同様に、postTask() の delay はミリ秒単位(最小非ゼロ遅延は1ms)で表されるため、呼び出し元は1ms未満の精度は指定できない。また、タスクは delay 終了時にキューイングされ即座に実行されるわけではないため、精度はさらに低下する。

5.2. 他オリジンのタスクの監視

本仕様における二つ目の考慮事項は、postTask() や yield() によって他オリジンのタスク情報が漏れないかである。1つのオリジンで攻撃者が、同じスレッドでスケジュールされる別のオリジン(つまり別イベントループ)で実行されるコードの情報を得ようとすることが想定される。

UAのスレッドは同時に一つのイベントループのタスクしか実行できないため、攻撃者は自分のタスクの実行時刻を観察して他イベントループのタスク実行を検知できる可能性がある。たとえばタスクを連続投入しギャップが大きければ他タスク(別イベントループかも)が割り込んだと推測でき、このとき露呈される情報は実装依存であり、実装側で漏洩量の制御も可能である。

得られる情報は?
攻撃者はタスク連投や再帰スケジューリングによりブラウザの他タスク実行タイミングを検知可能となる。これは 既知の攻撃 で、既存APIの postMessage() でも可能である。 割り込みタスクは、他イベントループや自分のイベントループの他タスク、UA内部タスク(GC等)でありうる。

攻撃者が大きな確率で「今動いているのは他イベントループのタスク」と判別できた場合、さらに何が分かるかが問題となる。イベントループ間のタスク選択は規定されておらず、これは実装依存であり、UAが複数イベントループのタスク順をどう決めるか次第。スレッド共有イベントループを一体として扱う優先度スキームのUAは、より多くの情報を露呈しやすい。

候補タスク集合を考えると分かりやすい。攻撃者がタスクを投げ続ける際、そのときUAがより高優先度とみなす全てのタスクが割り込み候補となる。これは静的スキーム(入力>ネット>…)でも、ダイナミック(一定周期で他タスクも実行)でもありえる。ダイナミック型ほど候補集合が大きく、漏洩精度が下がる。

postTask() や yield() はスケジュールするタスクや継続の優先度をサポートしている。しかしこれらが他タスクソースとどうインターリーブされるかはやはり実装依存で、攻撃者が優先度を活用して割り込み候補集合を更に狭めるのも場合による。例えば、UAがスレッド単位で単純な静的優先度スキームなら "user-blocking" postTask() や "user-visible" 以上の yield() 継続を使えば、postMessage() タスクと比べて、タスク間の実行順や優先度次第で割り込み候補が絞れる場合もある。

対策は?
実装側でリスク低減のためとれる対策:

6. プライバシーの考慮事項

このセクションは規範的でない。

本仕様で定義されるAPIについてプライバシーの観点から検討したが、特筆すべき問題はないと結論付けている。

適合性

文書規約

適合要件は、記述的な主張と RFC 2119 の用語を組み合わせて表現される。 この文書の規範的部分におけるキーワード “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, “OPTIONAL” は RFC 2119 で説明されている通りに解釈される。 ただし、可読性のため、これらの語は本仕様書ではすべて大文字では登場しない。

本仕様のテキストは、明示的に非規範的と示された部分、例、ノートを除き、すべて規範的である。 [RFC2119]

この仕様書の例は “for example” という語句で始まるか、または class="example" により規範文書と区別されて記される。例:

これは情報提供用の例である。

情報ノートは “Note” という語で始まり、 class="note" により本文とは区別されて記述される。例:

Note, これは情報ノートの例である。

索引

本仕様で定義される用語

参照仕様で定義される用語

参考文献

規範参考文献

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

情報提供参考文献

[REQUESTIDLECALLBACK]
Scott Haseley. requestIdleCallback(). URL: https://w3c.github.io/requestidlecallback/

IDL 索引

enum TaskPriority {
  "user-blocking",
  "user-visible",
  "background"
};

dictionary SchedulerPostTaskOptions {
  AbortSignal signal;
  TaskPriority priority;
  [EnforceRange] unsigned long long delay = 0;
};

callback SchedulerPostTaskCallback = any ();

[Exposed=(Window, Worker)]
interface Scheduler {
  Promise<any> postTask(SchedulerPostTaskCallback callback,
                        optional SchedulerPostTaskOptions options = {});
  Promise<undefined> yield();
};

[Exposed=(Window, Worker)]
interface TaskPriorityChangeEvent : Event {
  constructor(DOMString type, TaskPriorityChangeEventInit priorityChangeEventInitDict);

  readonly attribute TaskPriority previousPriority;
};

dictionary TaskPriorityChangeEventInit : EventInit {
  required TaskPriority previousPriority;
};

dictionary TaskControllerInit {
  TaskPriority priority = "user-visible";
};

[Exposed=(Window,Worker)]
interface TaskController : AbortController {
  constructor(optional TaskControllerInit init = {});

  undefined setPriority(TaskPriority priority);
};

dictionary TaskSignalAnyInit {
  (TaskPriority or TaskSignal) priority = "user-visible";
};

[Exposed=(Window, Worker)]
interface TaskSignal : AbortSignal {
  [NewObject] static TaskSignal _any(sequence<AbortSignal> signals, optional TaskSignalAnyInit init = {});

  readonly attribute TaskPriority priority;

  attribute EventHandler onprioritychange;
};

partial interface mixin WindowOrWorkerGlobalScope {
  [Replaceable] readonly attribute Scheduler scheduler;
};

課題一覧

HTML仕様をリファクタリングして task のコンストラクターを追加すべきか検討する必要がある。一つの問題は、新しいタスクを scheduler task にする必要があり、task ではだめな点である。↵
タイムアウト後に手順を実行する は サスペンドを必ずしも考慮していない; whatwg/html#5925 も参照。↵
このアルゴリズムは in parallel 手順からも呼び出され得るため、この他の アルゴリズムともども競合条件がある。特に next enqueue order はアトミックに更新し、scheduler task queues のアクセスもアトミックに行う必要がある。後者はイベントループのタスクキューにも影響する(この issueも参照)。↵
この段階の taskQueue は set of tasks か、set of scheduler tasks のいずれかとなる。以降の手順は item を remove するだけなので おおよそ互換になる。理想としては、タスクキュー共通のインターフェイスがあり pop() メソッドで単なる task が返せればよいが、大幅なリファクタリングが必要になる。↵
MDN

Scheduler/postTask

Firefox🔰 101+SafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

Scheduler

Firefox🔰 101+SafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskController/TaskController

Firefox🔰 101+SafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskController/setPriority

Firefox🔰 101+SafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskController

Firefox🔰 101+SafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskPriorityChangeEvent/TaskPriorityChangeEvent

Firefox🔰 101+SafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskPriorityChangeEvent/previousPriority

Firefox🔰 101+SafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskPriorityChangeEvent

Firefox🔰 101+SafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskSignal/priority

Firefox🔰 101+SafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskSignal/prioritychange_event

Firefox🔰 101+SafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskSignal/prioritychange_event

Firefox🔰 101+SafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskSignal

Firefox🔰 101+SafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

Window/scheduler

Firefox🔰 101+SafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?