우선순위 작업 스케줄링

초안 커뮤니티 그룹 보고서,

현재 버전:
https://wicg.github.io/scheduling-apis/
이슈 트래킹:
GitHub
명세 내 인라인
편집자:
(Google)

요약

이 명세는 우선순위가 지정된 작업을 스케줄링하고 제어하기 위한 API를 정의합니다.

이 문서의 상태

이 명세는 웹 플랫폼 인큐베이터 커뮤니티 그룹에서 발행했습니다. W3C 표준이 아니며 W3C 표준 트랙에도 포함되어 있지 않습니다. W3C 커뮤니티 기여자 라이선스 계약(CLA)에 따라 제한적인 옵트아웃 및 기타 조건이 적용될 수 있습니다. W3C 커뮤니티 및 비즈니스 그룹에 대해 더 알아보세요.

1. 소개

이 섹션은 비규범적입니다.

스케줄링은 웹사이트 성능을 개선하기 위한 중요한 개발자 도구가 될 수 있습니다. 넓은 의미에서 스케줄링은 사용자에게 인지되는 지연과 응답성이라는 두 영역에서 유의미한 영향을 줄 수 있습니다. 스케줄링은 낮은 우선순위 작업을 미루고 사용자 경험 품질에 직접적인 영향을 주는 높은 우선순위 작업을 우선시함으로써 사용자에게 인지되는 지연을 개선할 수 있습니다. 예를 들어, 페이지 로드 중 특정 서드파티(3P) 라이브러리 스크립트의 실행을 미루면 화면에 픽셀이 더 빨리 표시되어 사용자에게 이득이 될 수 있습니다. 동일하게 뷰포트 내 콘텐츠와 관련된 작업을 우선순위화하는 것도 적용됩니다. 메인 스레드에서 실행되는 스크립트의 경우, 긴 작업은 입력 및 시각적 응답성 모두에 부정적인 영향을 미쳐 입력과 UI 업데이트의 실행을 차단할 수 있습니다. 이러한 작업을 더 작은 조각으로 나누고 이 조각들(또는 작업의 연속)을 스케줄링하는 것은 응답성을 개선하기 위해 애플리케이션 및 프레임워크 개발자들이 사용하는 검증된 접근법입니다.

사용자 공간 스케줄러는 일반적으로 작업을 스케줄링하고 해당 작업이 언제 실행될지 제어하는 메서드를 제공함으로써 동작합니다. 작업은 보통 관련 우선순위를 가지며, 이는 주로 스케줄러가 제어하는 다른 작업들과 비교했을 때 해당 작업이 언제 실행될지를 결정합니다. 스케줄러는 일반적으로 일정 시간(스케줄러 퀀텀) 동안 작업을 실행한 뒤 브라우저에 제어를 반환하는 방식으로 동작합니다. 스케줄러는 setTimeout() 또는 postMessage() 호출과 같은 방식으로 연속 작업을 스케줄하여 다시 실행을 재개합니다.

사용자 공간 스케줄러가 성공을 거두었지만, 중앙집중식 브라우저 스케줄러와 더 나은 스케줄링 원시 기능이 있으면 상황이 개선될 수 있습니다. 스케줄러의 우선순위 시스템은 스케줄러의 도달 범위까지만 확장됩니다. 사용자 공간 스케줄러에 대한 결과로서, UA는 일반적으로 사용자 공간 작업 우선순위를 알지 못합니다. 예외는 스케줄러가 일부 작업에 대해 requestIdleCallback()을 사용하는 경우인데, 이는 최저 우선순위 작업으로 한정됩니다. 동일한 문제가 페이지에 여러 스케줄러가 있는 경우에도 존재하는데, 이는 점점 더 흔해지고 있습니다. 예를 들어, 애플리케이션은 자체 스케줄러를 가진 프레임워크(예: React)로 빌드되어 일부 스케줄링을 자체적으로 수행하고, 심지어 스케줄러가 있는 기능(예: 임베디드 지도)을 포함할 수도 있습니다. 브라우저는 전역 정보를 가지고 있고 이벤트 루프가 작업 실행을 담당하므로 이상적인 조정 지점입니다.

우선순위를 제외하더라도, 사용자 공간 스케줄러가 의존하는 현재의 원시 기능들은 현대적인 사용 사례에 이상적이지 않습니다. setTimeout(0)은 지연 없는 작업을 스케줄링하는 전형적인 방법이지만, 중첩된 작업 등에 대해 종종 최소 지연 값이 있어 지연이 증가하여 성능이 저하될 수 있습니다. 널리 알려진 해결책은 postMessage() 또는 MessageChannel를 사용하는 것이지만, 이러한 API는 스케줄링 용도로 설계된 것이 아니므로 콜백을 큐잉할 수 없습니다. requestIdleCallback()은 일부 사용 사례에 효과적일 수 있지만, 이는 유휴 작업에만 적용되며 우선순위가 변경될 수 있는 작업(예: 스크롤과 같은 사용자 입력에 반응하여 오프스크린 콘텐츠의 우선순위를 재조정하는 경우)을 고려하지 않습니다.

이 명세는 개발자가 우선순위가 지정된 작업과 연속을 스케줄링하고 제어하기 위한 새로운 인터페이스를 도입합니다. 이 문맥에서 작업은 자체 이벤트 루프 이벤트 루프task로 비동기적으로 실행되는 JavaScript 콜백입니다. 연속은 제어를 브라우저에 양보한 후 새로운 이벤트 루프 이벤트 루프 task에서 JavaScript 코드가 재개되는 것을 의미합니다. Scheduler 인터페이스는 작업을 스케줄링하기 위한 postTask() 메서드와 연속을 스케줄링하기 위한 yield() 메서드를 노출합니다. 이 명세는 작업 및 연속 실행 순서를 제어하기 위한 여러 TaskPriorities를 정의합니다. 추가로, 예정된 작업을 중단하고 우선순위를 제어하는 데 사용할 수 있는 TaskController와 관련된 TaskSignal이 제공됩니다.

2. 작업 및 연속 스케줄링

2.1. 작업 및 연속 우선순위

이 명세는 작업 스케줄링을 지원하기 위해 세 가지 우선순위를 형식화합니다:

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

user-blocking는 가장 높은 우선순위로, 낮은 우선순위로 실행할 경우 사용자 경험이 저하되는 작업을 위해 사용됩니다. 예를 들어, 사용자 입력에 직접적으로 반응하는 (조각화된) 작업이나 뷰포트 내 UI 상태를 업데이트하는 작업이 해당될 수 있습니다.

이러한 우선순위로 스케줄된 작업은 일반적으로 다른 작업에 비해 이벤트 루프 우선순위가 높지만 반드시 렌더 차단(render-blocking)인 것은 아닙니다. 즉시 중단 없이 수행되어야 하는 작업은 일반적으로 동기적으로 처리하는 것이 적절하지만, 작업이 너무 오래 걸리면 응답성이 떨어질 수 있습니다. 반면 "user-blocking" 작업은 작업을 분할하여 입력 및 렌더링에 여전히 응답하면서 가능한 한 빨리 작업이 완료되도록 할 가능성을 높이는 데 사용할 수 있습니다.

user-visible는 두 번째로 높은 우선순위로, 사용자에게 관찰 가능한 유용한 부수 효과를 갖는 작업을 위한 것입니다. 다만 즉시 관찰 가능하지 않거나 사용자 경험에 필수적이지 않은 작업들입니다. 이 우선순위를 가지는 작업은 "user-blocking" 작업보다 덜 중요하거나 덜 긴급합니다. 이 우선순위가 기본값입니다.

background는 가장 낮은 우선순위로, 백그라운드 로그 처리나 측정(metric) 처리, 일부 서드파티 라이브러리 초기화와 같이 시간에 민감하지 않은 작업에 사용됩니다.

Note: 주어진 Scheduler를 통해 스케줄된 작업들은 엄격한 우선순위 순서로 실행됩니다. 즉, 스케줄러는 항상 "user-blocking" 작업을 먼저 실행하고, 그 다음에 "user-visible" 작업을 실행하며, 마지막으로 "background" 작업을 실행합니다. 연속(continuations)은 동일한 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();
};

Note: signal 옵션은 AbortSignal이나 TaskSignal 중 하나일 수 있지만, AbortSignalTaskSignal의 상위 클래스이므로 해당 타입으로 정의되어 있습니다. 우선순위가 변경될 수 있는 경우에는 TaskSignal이 필요합니다. 반면 취소만 필요하다면 AbortSignal이면 충분하여 기존에 AbortSignals을 사용하는 코드에 API를 통합하기 더 쉬울 수 있습니다.

result = scheduler . postTask( callback, options )

callback의 반환값으로 이행되거나, 태스크가 중단(abort)되는 경우 AbortSignalabort reason으로 거부되는 promise를 반환합니다. callback 실행 중 오류가 발생하면, postTask() 가 반환하는 promise는 해당 오류로 거부(reject)됩니다.

태스크의 priorityoptionprioritysignal 조합으로 결정됩니다:

  • 만약 optionpriority 가 명시되었다면, 해당 TaskPriority 가 태스크 예약에 사용되며 태스크의 우선순위는 변경할 수 없습니다(immutable).

  • 그렇지 않고, optionsignal 이 명시되었고 TaskSignal 객체라면, 태스크 우선순위는 optionsignalpriority에 의해 결정됩니다. 이 경우 태스크 우선순위는 동적이며, 연결된 controller.setPriority() 호출로 변경할 수 있습니다.

  • 그 밖의 경우, 태스크 우선순위는 기본값인 "user-visible" 이 사용됩니다.

optionsignal 이 명시된 경우, signal 은 태스크가 중단(abort)되었는지 Scheduler 가 판단하는 데 사용됩니다.

optiondelay 가 명시되고 0보다 크면, 태스크는 delay 밀리초 이상 지연되어 실행됩니다.

result = scheduler . yield()

undefined로 이행되거나, 연속동작(continuation)이 중단(abort)된 경우 AbortSignalabort reason으로 거부되는 promise를 반환합니다.

연속동작(continuation)의 우선순위와 abort에 사용할 signal은 시작 태스크(originating task)에서 상속합니다. 시작 태스크가 postTask()AbortSignal 을 함께 사용해 예약되었다면, 해당 signal을 사용해 연속동작의 abort 여부가 결정됩니다. 시작 태스크의 우선순위(TaskSignal 이나 고정 우선순위)가 연속동작의 우선순위에도 사용됩니다. 시작 태스크에 우선순위가 없었다면 "user-visible" 이 사용됩니다.

Scheduler 객체는 연관된 정적 우선순위 작업 큐 맵을 가지며, 이는 (TaskPriority, boolean)에서 스케줄러 작업 큐로의 입니다. 이 맵은 새로운 빈 으로 초기화됩니다.

Scheduler 객체는 dynamic priority task queue map을 가진다. 이 맵은 (TaskSignal, boolean)에서 scheduler task queue로 매핑되는 map이다. 이 맵은 새로운 비어 있는 map으로 초기화된다.

참고: 우리는 동적 우선순위 지정을, 특정 TaskSignal이 연결된 태스크를 같은 스케줄러 태스크 큐에 넣고, prioritychange 이벤트에 따라 해당 큐의 우선순위를 변경함으로써 구현한다. dynamic priority task queue map은 우선순위가 변동 가능한 스케줄러 태스크 큐를 보관하며, 맵의 키는 큐 내 모든 태스크가 연결된 TaskSignal 이다.

static priority task queue map의 값은 우선순위가 변하지 않는 스케줄러 태스크 큐이다. 정적 우선순위 태스크 — 명시적으로 priority 옵션 혹은 signal 옵션이 null이거나 AbortSignal 인 경우 예약된 태스크 — 는 맵의 키 역할을 하는 TaskPriority에 따라 이 큐들에 들어간다.

논리적으로 동등한 또 다른 방식은, 각 TaskPriority 별로 하나의 스케줄러 태스크 큐만 유지하고, TaskSignalpriority가 바뀔 때마다 태스크를 스케줄러 태스크 큐 사이에서 옮기며, enqueue order에 따라 삽입하는 것이다. 이 방식은 모든 스케줄러에서 다음 스케줄러 태스크 큐 선택을 단순하게 만들 수 있지만, 우선순위 변동이 더 복잡해진다.

postTask(callback, options) 메서드의 단계는 주어진 callbackoptions에 대해 postTask 작업을 스케줄하는 것의 결과를 반환하는 것입니다.

yield() 메서드의 단계는 yield 연속을 스케줄하는 것의 결과를 반환하는 것입니다.

2.3. 정의

스케줄러 작업(scheduler task)은 추가적인 숫자형 enqueue order 항목을 가진 task이며, 이 값은 초기값이 0인 item입니다.

다음의 task sources들은 스케줄러 작업 소스(scheduler task sources)로 정의되며, 오직 스케줄러 작업에만 사용되어야 합니다.

The posted task task source

task sourcepostTask() 또는 yield()를 통해 스케줄된 작업에 사용됩니다.


스케줄러 작업 큐(scheduler task queue)는 다음 항목들을 가진 struct입니다:

priority

TaskPriority입니다.

is continuation

불리언입니다.

tasks

set 형태의 스케줄러 작업들입니다.

removal steps

알고리즘입니다.


스케줄링 상태(scheduling state)는 다음 항목들을 가진 struct입니다:

abort source

초기값이 null인 AbortSignal 객체 또는 null입니다.

priority source

초기값이 null인 TaskSignal 객체 또는 null입니다.


연속 상태(continuation state)는 다음 항목들을 가진 struct입니다:

state map

초기에는 비어 있는 입니다.

Note: state map는 키가 가비지 컬렉션되는 객체로 구현된다면 약한 맵(weak map)으로 구현할 수 있습니다.


작업 핸들(task handle)은 다음 항목들을 가진 struct입니다:

task

스케줄러 작업 또는 null입니다.

queue

스케줄러 작업 큐 또는 null입니다.

abort steps

알고리즘입니다.

task complete steps

알고리즘입니다.

2.4. 처리 모델

스케줄러 작업 t1더 오래됨(older than) 스케줄러 작업 t2일 때는 t1enqueue ordert2enqueue order보다 작을 때이다.
스케줄러 작업 큐를 생성하다 TaskPriority priority, 불리언 isContinuation, 알고리즘 removalSteps와 함께:
  1. queue를 새로운 스케줄러 작업 큐로 만든다.

  2. queueprioritypriority로 설정한다.

  3. queueis continuationisContinuation으로 설정한다.

  4. queuetasks를 새로운 빈 set으로 설정한다.

  5. queueremoval stepsremovalSteps로 설정한다.

  6. queue를 반환한다.

작업 핸들을 생성하다 프라미스 resultAbortSignal 또는 null signal이 주어졌을 때:
  1. handle을 새로운 작업 핸들로 만든다.

  2. handletask를 null로 설정한다.

  3. handlequeue를 null로 설정한다.

  4. handleabort steps를 아래 단계로 설정한다:

    1. Reject resultsignalabort reason으로 거부한다.

    2. task가 null이 아니라면,

      1. Remove taskqueue에서 삭제.

      2. queueempty 상태라면 queueremoval steps을 실행.

  5. handletask complete steps를 아래 단계로 설정한다:

    1. signal이 null이 아니면 remove handleabort stepssignal에서 제거한다.

    2. queueempty라면 queueremoval steps을 실행한다.

  6. handle을 반환한다.

스케줄러 작업 큐 queue첫 번째 실행 가능 작업(first runnable task)queuetasks 가운데 실행 가능(runnable)한 첫 번째 스케줄러 작업이다.
스케줄러 작업 큐 queue유효 우선순위(effective priority)queuepriorityis continuation이 일치하는 행의 세 번째 열로 산정된다:

Priority Is Continuation Effective Priority
"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, 기본 작업 소스(task source) source, 그리고 문서(document) document가 주어졌을 때:
  1. task를 새로운 스케줄러 작업으로 한다.

  2. taskenqueue orderenqueue order로 설정한다.

  3. taskstepssteps로 설정한다.

  4. tasksourcesource로 설정한다.

  5. taskdocumentdocument로 설정한다.

  6. task스크립트 평가 환경 설정 객체 집합을 새 빈 set으로 설정한다.

  7. Append taskqueuetasks에 추가한다.

  8. task를 반환한다.

HTML 명세를 리팩토링해 task에 생성자를 추가하는 것을 고려해야 합니다. 문제는 새 작업이 일반 task가 아닌 scheduler task이어야 한다는 점입니다.

제거하다(remove) 스케줄러 작업 task스케줄러 작업 큐 queue에서 remove하여 taskqueuetasks에서 삭제한다.
스케줄러 작업 큐 queue비어있음(empty) 상태라는 것은 queuetasks빈 상태임을 의미한다.

2.4.2. 작업 및 연속 스케줄링

To 현재 스케줄링 상태 설정scheduler (Scheduler 타입)에 대해 state (스케줄링 상태 타입)로:
  1. eventLoopscheduler연관 에이전트(relevant agent)이벤트 루프(event loop)로 둔다.

  2. 연속 상태 값(continuation state value)scheduler에 대해 eventLoop를 인자로 하여 state로 설정한다.

Note: Scheduler에 대해 유일하면 어떤 키도 state map에 사용할 수 있다.

현재 스케줄링 상태 가져오기scheduler ( Scheduler 타입)에 대해 수행하려면:
  1. eventLoopschedulerrelevant agentevent loop로 설정한다.

  2. continuation 상태 값 가져오기 결과를 schedulereventLoop를 인자로 하여 반환한다.

postTask 작업 예약하기Scheduler scheduler에 대해, SchedulerPostTaskCallback callbackSchedulerPostTaskOptions options가 주어졌을 때:
  1. result새로운 promise로 설정한다.

  2. signaloptions["signal"] 값이 존재하면 해당 값으로, 그렇지 않으면 null로 설정한다.

  3. signal이 null이 아니면서 aborted라면, resultsignalabort reason으로 reject하고 result를 반환한다.

  4. state를 새로운 스케줄링 상태로 설정한다.

  5. stateabort sourcesignal로 설정한다.

  6. 만약 options["priority"] 존재하면, statepriority source고정 우선순위 비중단 태스크 시그널 생성 결과(options["priority"]로 생성)에 설정한다.

  7. 그 외에 signal이 null이 아니고 TaskSignal 인터페이스를 구현하면, statepriority sourcesignal로 설정한다.

  8. statepriority source가 null이면, statepriority source를 "user-visible" 값으로 고정 우선순위 비중단 태스크 시그널 생성 결과로 설정한다.

  9. handle태스크 핸들 생성 결과(resultsignal 인자)를 설정한다.

  10. signal이 null이 아니면, add handleabort stepssignal에 추가한다.

  11. enqueueSteps를 아래 단계로 정의한다:

    1. handlequeue스케줄러 작업 큐 선택 결과(scheduler, statepriority source, false 인자)에 설정한다.

    2. 알고리즘 호출용 태스크 예약scheduler, handle 및 다음 단계를 인자로 실행한다:

      1. eventLoopschedulerrelevant agentevent loop로 설정한다.

      2. 현재 스케줄링 상태 설정schedulerstate로 수행한다.

      3. callbackResultcallback 함수 호출(callback에 « » 및 "rethrow" 전달) 결과로 설정한다. 예외가 발생했다면 result를 해당 예외로 reject한다. 그렇지 않으면 resultcallbackResult로 resolve한다.

      4. eventLoopcurrent continuation state를 null로 설정한다.

  12. delayoptions["delay"] 값으로 설정한다.

  13. delay가 0보다 크면, 타임아웃 후 단계 실행schedulerrelevant global object, "scheduler-postTask", delay, 및 아래 단계로 실행한다:

    1. signal이 null이거나 signalaborted가 아니면 enqueueSteps 실행.

  14. 그 외에는 enqueueSteps를 실행한다.

  15. result를 반환한다.

참고: 여기서 생성된 고정 우선순위 비중단 신호는 메모리 할당을 줄이기 위해 캐시하고 재사용할 수 있습니다.

타임아웃 후 단계 실행(run steps after a timeout)은 suspension을 반드시 고려하지는 않는다. 자세한 사항은 whatwg/html#5925를 참조.

yield continuation 예약하기Scheduler scheduler에 대해:
  1. result새로운 promise로 설정한다.

  2. inheritedState현재 스케줄링 상태 가져오기 결과(scheduler 인자)를 설정한다.

  3. abortSourceinheritedState가 null이 아니면 inheritedStateabort source로, null이면 null로 설정한다.

  4. abortSource가 null이 아니고 abortSourceaborted면, resultabortSourceabort reason으로 reject하고 result를 반환한다.

  5. prioritySourceinheritedState가 null이 아니면 inheritedStatepriority source로, null이면 null로 설정한다.

  6. prioritySource가 null이면, prioritySource를 "user-visible"로 고정 우선순위 비중단 태스크 시그널 생성 결과로 설정한다.

  7. handle태스크 핸들 생성 결과(resultabortSource 인자)를 설정한다.

  8. abortSource가 null이 아니면, add handleabort stepsabortSource에 추가한다.

  9. handlequeue스케줄러 태스크 큐 선택 결과(scheduler, prioritySource, true 인자)로 설정한다.

  10. 알고리즘 실행 태스크 예약schedulerhandle, 그리고 아래 단계를 인자로 하여 실행한다:

    1. result를 resolve한다.

  11. result를 반환한다.

참고: 여기서 생성된 고정 우선순위 비중단 신호는 메모리 할당을 줄이기 위해 캐시하고 재사용할 수 있다.

스케줄러 태스크 큐 선택Scheduler scheduler에 대해 TaskSignal 객체 signal과 불린 값 isContinuation이 주어진 경우:
  1. signal고정 우선순위가 없다면,

    1. scheduler동적 우선순위 작업 큐 맵이 (signal, isContinuation)을 포함하지 않는다면

      1. queue스케줄러 작업 큐 생성으로 만드는데, 인자는 signalpriority, isContinuation, 그리고 아래 단계.

        1. Remove 동적 우선순위 작업 큐 맵[(signal, isContinuation)]에서 제거.

      2. 동적 우선순위 작업 큐 맵[(signal, isContinuation)]에 queue를 설정.

      3. 우선순위 변경 알고리즘signal에 추가. 단계:

        1. queueprioritysignalpriority로 설정.

    2. 동적 우선순위 작업 큐 맵[(signal, isContinuation)]을 반환.

  2. 그렇지 않으면

    1. prioritysignalpriority로 설정.

    2. scheduler정적 우선순위 작업 큐 맵이 (priority, isContinuation)를 포함하지 않으면

      1. queue스케줄러 작업 큐 생성으로 만들며, 인자는 priority, isContinuation, 아래 단계.

        1. Remove 정적 우선순위 작업 큐 맵[(priority, isContinuation)]에서 제거.

      2. 정적 우선순위 작업 큐 맵[(priority, isContinuation)]에 queue를 설정.

    3. 정적 우선순위 작업 큐 맵[(priority, isContinuation)]을 반환.

알고리즘을 호출하기 위한 태스크 스케줄링Scheduler scheduler에 대해, 태스크 핸들 handle과 알고리즘 steps가 주어졌을 때:
  1. globalschedulerrelevant global object로 설정한다.

  2. documentglobalassociated Document로 설정한다. 단, globalWindow 오브젝트일 때만 해당하며, 그 외에는 null로 한다.

  3. event loopschedulerrelevant agentevent loop로 설정한다.

  4. enqueue orderevent loopnext enqueue order로 설정한다.

  5. event loopnext enqueue order를 1 증가시킨다.

  6. handletask스케줄러 태스크 큐잉의 결과로 설정한다. 큐잉 대상은 handle큐(queue)이고, 인자로는 enqueue order, posted task task source, document 및 아래 단계를 수행하는 태스크가 사용된다:

    1. steps를 실행한다.

    2. handle태스크 완료 단계(task complete steps)를 실행한다.

이 알고리즘은 병렬 단계에서 호출될 수 있기 때문에, 이와 다른 알고리즘의 일부가 경쟁 조건(racy condition)을 초래할 수 있습니다. 특히, next enqueue order는 원자적으로 업데이트되어야 하며, 스케줄러 태스크 큐에 접근도 원자적으로 진행되어야 합니다. 후자는 이벤트 루프의 태스크 큐에도 영향을 줍니다(자세한 내용은 이 이슈 참조).

2.4.3. 다음에 실행할 작업 선택

Scheduler scheduler실행 가능한 작업이 있을 때실행 가능한 작업 대기열 가져오기scheduler에 대해 수행한 결과가 비어있지 않을 때이다.
Scheduler scheduler실행 가능한 작업 대기열 가져오기는 다음과 같다:
  1. queues값 가져오기scheduler정적 우선순위 작업 대기열 맵에 대해 초기화한다.

  2. 확장 queues값 가져오기scheduler동적 우선순위 작업 대기열 맵에 대해 더한다.

  3. 제거 queues에서 queuetasks실행 가능한 스케줄러 작업을 포함하지 않는 queue를 모두 제거한다.

  4. queues를 반환한다.

이벤트 루프 event loop을 입력으로 받아, 모든 스케줄러 중 다음 스케줄러 작업 대기열 선택을 다음의 단계를 따라 수행한다. 이 단계들은 스케줄러 작업 대기열 혹은 null을 반환한다. Schedulerevent loop와 연관되어 실행 가능한 작업을 가지고 있지 않다면 null을 반환한다.
  1. queues를 빈 집합으로 둔다.

  2. schedulers집합으로, Scheduler 객체 중 관련 에이전트의 이벤트 루프event loop와 같고, 실행 가능한 작업이 있는 것들의 집합으로 설정한다.

  3. scheduler에 대해 확장 queues에, 실행 가능한 작업 대기열 가져오기의 결과를 추가한다.

  4. queues비어 있다면 null을 반환한다.

  5. 제거 queues에서, queue유효 우선순위queues의 그 어떤 항목보다도 작은 queue를 모두 제거한다.

  6. queue를, queues첫 실행 가능한 작업가장 오래된 작업스케줄러 작업 대기열로 둔다.
    두 작업은 큐잉 순서가 고유하므로 같은 나이를 가질 수 없다.

  7. queue를 반환한다.

참고: 다음에 실행할 작업은 모든 Scheduler와 연관된 이벤트 루프에서 가장 오래되고, 우선순위가 가장 높은 실행 가능한 스케줄러 작업이다.

2.5. 예시

TODO(shaseley): 예시 추가.

3. 작업 제어

Scheduler 인터페이스를 통해 예약된 작업은 TaskController를 사용해, TaskSignalcontroller.signal로 받아 option으로 postTask()를 호출할 때 전달하여 제어할 수 있다. TaskController 인터페이스는 하나의 작업 또는 여러 작업의 중단(abort) 및 우선순위 변경을 지원한다.

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를 반환한다.

TaskPriorityevent.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);
};

참고: TaskControllersignal getter는 AbortController로부터 상속되어, TaskSignal 객체를 반환한다.

controller = new TaskController( init )

새로운 TaskController 를 반환하며, 그 signalinitpriority로 초기화된 새로운 TaskSignal로 설정되어 있다.

controller . setPriority( priority )

이 메소드를 호출하면, 관련 TaskSignalpriority가 변경되며, 변경을 관찰 중인 모든 곳에 신호가 전달되고 prioritychange 이벤트가 발생한다.

new TaskController(init) 생성자 단계:
  1. signal을 새로운 TaskSignal 객체로 둔다.

  2. signalpriorityinit["priority"] 로 설정한다.

  3. thissignalsignal로 설정한다.

setPriority(priority) 메소드 단계는, 우선순위 변경 신호 보내기thissignalpriority를 넘겨 호출하는 것이다.

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;
};

참고: TaskSignalAbortSignal 에서 상속되며, AbortSignal 을 받는 API에서 사용할 수 있다. 또한, postTask() 역시 AbortSignal 을 받을 수 있으며, 이는 동적 우선순위 변경이 필요하지 않은 경우에 유용하다.

TaskSignal . any(signals, init)
TaskSignal 인스턴스를 반환한다. signals 중 하나라도 abort되면 이 인스턴스도 abort 된다. 그 abort reason은 abort를 유발한 signals의 reason으로 설정된다. 새 signal의 priorityinitpriority 값이 정해주는데, 이는 고정된 TaskPriority 값일 수도 있고, TaskSignal 일 수도 있다. 후자의 경우 새 signal의 priority가 해당 signal과 함께 변경된다.
signal . priority

signal의 TaskPriority 를 반환한다.

TaskSignal 객체는 연결된 priority 속성을 가진다 (TaskPriority 타입).

TaskSignal 객체는 연결된 priority changing (boolean, 기본값 false) 속성을 가진다.

TaskSignal 객체는 연결된 priority change algorithms (priority changing 값이 true일 때 실행되는 알고리즘을 담은 set, 처음엔 비어 있음)을 가진다.

TaskSignal 객체는 연결된 source signal (TaskSignal 객체에 대한 약한 참조로, priority를 결정함), 처음엔 null이다.

TaskSignal 객체는 연결된 dependent signals (TaskSignal 들의 약한 set, 이 객체를 참조하여 priority가 결정됨, 처음엔 비어 있음)을 가진다.

TaskSignal 객체는 연결된 dependent (boolean, 기본값 false) 속성을 가진다.


priority getter의 동작은 thispriority를 반환하는 것이다.

onprioritychange 속성은 이벤트 핸들러 IDL 속성이다. onprioritychange 이벤트 핸들러이고, 이벤트 타입prioritychange이다.

static any(signals, init) 메서드는 의존 TaskSignal 생성 알고리즘을 실행한다 (signals, init, 현재 realm 사용).


TaskSignal 고정된 priority가 있다dependent signal이면서 source signal이 null일 때이다.

priority change algorithm 추가에서는, algorithmTaskSignal 객체 signalpriority change algorithms에 추가한다.

TaskSignal 의존 인스턴스 생성 알고리즘:
  1. resultSignal = 의존 신호 생성 호출 결과(TaskSignal 인터페이스/realm 사용)

  2. resultSignaldependent를 true로 설정

  3. 만약 init["priority"] 가 TaskPriority 라면:

    1. resultSignalpriorityinit["priority"]로 설정

  4. 그 외:

    1. sourceSignal = init["priority"]

    2. resultSignalprioritysourceSignalpriority로 설정

    3. sourceSignal고정 priority가 없으면:

      1. sourceSignaldependent가 true라면 sourceSignalsourceSignalsource signal로 바꾼다.

      2. 단언: sourceSignaldependent가 아니다.

      3. resultSignalsource signalsourceSignal의 weak ref로 설정

      4. Append resultSignalsourceSignaldependent signals에 추가

  5. resultSignal 반환

TaskSignal의 priority를 변경 알고리즘:
  1. 만약 signalpriority changing이 true라면 "NotAllowedError" DOMException을 throw한다.

  2. 만약 signalprioritypriority와 같으면 종료

  3. signalpriority changing을 true로 설정

  4. previousPriority = signalpriority

  5. signalprioritypriority로 설정

  6. algorithmsignalpriority change algorithms마다 실행

  7. 이벤트를 prioritychange 이름으로 signal에 fire (이벤트 객체는 TaskPriorityChangeEvent 이고, previousPriority 속성을 previousPriority로 초기화)

  8. dependentSignalsignaldependent signals마다 signal priority changepriority와 함께 실행

  9. signalpriority changing을 false로 설정

우선순위 고정, abort 불가 task signal 생성 알고리즘:
  1. init = TaskSignalAnyInit 새 인스턴스

  2. init["priority"] = priority으로 설정

  3. 의존 TaskSignal 생성 알고리즘을 « », 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 mixin을 구현한 각 객체는 대응하는 scheduler를 가진다. 이는 Scheduler 인스턴스로 초기화된다.

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

scheduler 속성의 getter 동작은 thisscheduler를 반환함

4.1.2. 이벤트 루프: 정의

다음으로 교체: 각 event loop에 대해, 각 task source는 특정 task queue와 연관되어야 한다.

다음으로 대체: 각 event loop에 대해, task sourcescheduler task source가 아니라면, 그 source는 특정 task queue와 연관되어야 한다.

추가: 이벤트 루프는 숫자형 next enqueue order를 가진다. 초기값은 1이다.

참고: next enqueue order 값은 scheduler task queue들의 실행 순서를 TaskPriority 별로 결정하는데 사용된다. 고유하고 증가하는 수로, timestamp도 쓸 수 있다(유일성과 단조성 보장 필요).

추가: 이벤트 루프현재 continuation state를 가진다 (처음엔 null).

아래 알고리즘들 추가:

continuation state 값 설정 알고리즘:
  1. eventLoopcurrent continuation state가 null이면 새 continuation state로 초기화

  2. continuationState = eventLoopcurrent continuation state

  3. 단언: continuationStatestate map[key]는 존재하지 않는다.

  4. continuationStatestate map[key] = value

continuation state 값 얻기 알고리즘:
  1. continuationState = eventLoopcurrent continuation state

  2. continuationState가 null이 아니고, state map[key]가 존재하면 해당 값을 반환. 아니면 null 반환

4.1.3. 이벤트 루프: 처리 모델

아래 단계들을 이벤트 루프 처리 단계의 2번 앞에 추가:

  1. queues = set (해당 이벤트 루프의 task queues 중 실행 가능한 runnable task을 포함하는 것들)

  2. schedulerQueue = 모든 scheduler에서 다음 task queue 선택 수행 결과

2단계를 다음과 같이 변경:

  1. schedulerQueue가 null이 아니거나, queues비어 있지 않다면:

2.1단계는 아래처럼 변경:

  1. taskQueue는 다음 중 하나, 방법은 구현 정의:

참고: HTML 명세에서는 이벤트 루프 처리 단계에서 다음 task source별 우선순위화가 가능하다. 본 명세 역시 다음 Scheduler task와 이벤트 루프의 task queue 중에서 어떤 걸 처리할지 구현정의하게 만들어, User Agent에 스케줄링의 융통성을 준다.

다만, 이 명세의 의도는 TaskPriority 값에 따라 Scheduler task의 이벤트 루프 우선순위에 영향을 주도록 하는 것이다. 예를 들어 "background" 작업·continuation은 대체로 중요도가 낮고, "user-blocking" task/continuation과 "user-visible" continuation(단, task는 아님)은 더 중요도가 높음.

한 전략은 Scheduler task의 effective priority가 3 이상이면 사용자 입력·렌더링·긴급작업보다 낮고 대부분의 queue보다 높게, 0~1이면 타 queue가 비었을 때만 실행, 2면 타 스케줄링 관련 task source와 비슷하게(예: timer task source) 실행할 수 있다.

이 단계의 taskQueueset of tasks 혹은 set of scheduler tasks임. 이후 단계들은 item 제거만 하므로 거의 호환됨. 이상적으로는 plain task를 반환하는 공용 task queue 인터페이스가 있어야 하지만 상당한 리팩터링이 필요함.

4.1.4. 이벤트 루프: 작업 큐잉

마이크로태스크 큐에 추가 알고리즘을 선택적 불리언 ignoreContinuationState(기본값 false)를 받도록 변경한다.

5단계를 다음과 같이 변경한다:

  1. continuationState를 null로 둔다.

  2. ignoreContinuationState가 false이고 eventLoopcurrent continuation state가 null이 아니면, continuationState클론event loopcurrent continuation state로 설정한다.

  3. microtasksteps를 다음과 같이 설정한다:

    1. ignoreContinuationState가 false이면 eventLoopcurrent continuation statecontinuationState로 설정한다.

    2. steps를 실행한다.

    3. ignoreContinuationState가 false이면 eventLoopcurrent continuation state를 null로 설정한다.

4.1.5. HostMakeJobCallback(callable)

5단계 이전에 다음을 추가한다:

  1. event loopincumbent settingsrealmagentevent loop로 설정한다.

  2. 클론event loopcurrent continuation state가 null이 아니라면 그 값으로, 그렇지 않으면 null로 state를 설정한다.

5단계를 다음과 같이 바꾼다:

  1. JobCallback 레코드 { [[Callback]]: callable, [[HostDefined]]: { [[IncumbentSettings]]: incumbent settings, [[ActiveScriptContext]]: script execution context, [[ContinuationState]]: state } }를 반환한다.

4.1.6. HostCallJobCallback(callback, V, argumentsList)

5단계 이전에 다음을 추가한다:

  1. event loopincumbent settingsrealmagentevent loop로 설정한다.

  2. event loopcurrent continuation statecallback.[[HostDefined]].[[ContinuationState]]로 설정한다.

7단계 이후에 다음을 추가한다:

  1. event loopcurrent continuation state를 null로 설정한다.

4.1.7. HostEnqueuePromiseJob(job, realm)

2단계를 다음과 같이 변경한다:

  1. 다음 단계를 ignoreContinuationState를 true로 하여 마이크로태스크 큐에 추가한다:

4.2. requestIdleCallback()

4.2.1. 휴지 콜백 호출 알고리즘

3.3단계 이전에 다음 단계를 추가한다:

  1. realmwindowrelevant realm으로 설정한다.

  2. state를 새로운 scheduling state로 설정한다.

  3. statepriority source를 "background" 및 realm을 인자로 하여 고정 우선순위 비중단 태스크 시그널 생성 호출 결과로 설정한다.

  4. schedulerrealm을 relevant realm으로 갖는 Scheduler로 설정한다.

  5. Set the current scheduling statescheduler에 대해 state로 수행한다.

3.3단계 이후에 다음을 추가한다:

  1. event loopcurrent continuation state를 null로 설정한다.

5. 보안 고려 사항

이 섹션은 비규범적입니다.

이 명세에서 정의하는 API의 주요 보안 고려사항은, 타이밍 기반 부채널(side-channel) 공격으로 인한 출처(origin) 간 정보 유출 가능성 여부이다.

5.1. postTask의 고해상도 타이밍 소스 사용 방지

이 API는 고해상도 타이밍 소스로 사용될 수 없다. setTimeout()의 timeout 값과 마찬가지로, postTask()delay 는 전체 밀리초 단위(최소 비0 지연은 1ms)로 표현되며 호출자는 1ms보다 정밀한 타이밍을 지정할 수 없다. 또한, 태스크는 지연이 만료될 때 큐에 들어갈 뿐 즉시 실행되는 것이 아니어서, 호출자가 가질 수 있는 정밀도가 더 낮아진다.

5.2. 다른 출처의 태스크 모니터링

두 번째 고려사항은 postTask() 또는 yield() 가 다른 출처의 태스크에 대한 정보를 유출하는지 여부다. 우리는 한 출처에서 실행 중인 공격자가 같은 브라우저 내에서 다른 출처(즉, 별도의 이벤트 루프)에서 실행 중인 코드를(동일 스레드에서 스케줄된) 관찰하려는 상황을 가정한다.

UA 내의 한 스레드는 한 번에 한 이벤트 루프의 태스크만 실행할 수 있기 때문에, 공격자는 자신의 태스크가 실행되는 시점을 관찰하여 다른 이벤트 루프에서 실행된 태스크에 대한 정보를 획득할 수 있다. 예를 들어 공격자가 태스크로 시스템을 가득 채우고 이들이 연속으로 실행되기를 기대할 때 사이가 비면 다른 태스크가(아마 다른 이벤트 루프에서) 실행됐음을 추론할 수 있다. 이런 경우 유출되는 정보는 구현 세부사항에 따라 다르며, 아래 설명처럼 정보량을 줄이는 것도 가능하다.

공격자가 어떤 정보를 얻을 수 있는가?
구체적으로 공격자는 시스템을 태스크로 과부하시키거나 재귀적으로 태스크를 스케줄하여 브라우저가 다른 태스크를 실행하는 시점을 감지할 수 있다. 이는 이미 알려진 공격이며, postMessage() 등의 기존 API로도 실행 가능하다. 공격자의 태스크 대신 실행되는 것은 다른 이벤트 루프 또는 같은 이벤트 루프의 다른 태스크, 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]

이 규격의 예시는 “예를 들어”라는 말로 소개되거나, class="example"로 규범적 텍스트와 구분됩니다. 다음과 같이요:

이것은 정보 제공용 예시입니다.

정보 제공용 주석은 “참고”라는 단어로 시작하며, class="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)에 대한 생성자를 추가하는 것을 고려해야 합니다. 한 가지 문제는 새 작업이 스케줄러 작업이어야 하며, 작업(task)이어서는 안 된다는 점입니다.
타임아웃 이후 단계 실행은 반드시 중단(suspension)을 고려하지는 않습니다; whatwg/html#5925 참조.
이 알고리즘은 병렬 단계에서 호출될 수 있기 때문에, 이와 다른 알고리즘 일부가 경쟁 조건(race condition)이 발생할 수 있습니다. 특히, 다음 대기열 순서(next enqueue order)는 원자적으로 업데이트되어야 하며, 스케줄러 작업 큐에 접근하는 것도 원자적으로 이뤄져야 합니다. 후자는 이벤트 루프의 작업 큐에도 영향을 끼칩니다(자세한 내용은 이 이슈 참고).
이 단계의 taskQueueset 타입의 작업들(tasks) 또는 set 타입의 스케줄러 작업들일 수 있습니다. 뒤따르는 단계에서는 항목(item)제거(remove)하는 정도만 하므로 대체로 호환됩니다. 이상적으로는, 단순한 작업을 반환하는 pop() 메서드를 지원하는 공통 작업 큐 인터페이스가 있으면 좋겠지만, 이를 위해서는 상당한 리팩터링이 필요합니다.
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?