1. 소개
페이지가 로드되는 동안과 이후 사용자가 페이지와 상호작용할 때, 애플리케이션과 브라우저는 다양한 이벤트를 큐에 추가하며, 이러한 이벤트들은 브라우저에 의해 실행됩니다. 예를 들어, 사용자 에이전트는 사용자의 활동에 따라 입력 이벤트를 스케줄하고, 애플리케이션은 requestAnimationFrame과 기타 콜백을 위한 콜백을 스케줄합니다. 큐에 들어가면 브라우저는 이 이벤트들을 하나씩 꺼내 실행합니다.
하지만 어떤 작업은 오랜 시간(여러 프레임) 걸릴 수 있으며, 이런 일이 발생하면 UI 스레드가 막혀 다른 모든 작업도 막히게 됩니다. 사용자는 이를 "멈춘" 페이지로 경험하게 되며, 브라우저가 사용자 입력에 반응하지 못하는 현상으로 나타납니다. 이는 오늘날 웹에서 나쁜 사용자 경험의 주요 원인입니다:
- 지연된 "인터랙티브 가능 시간":
-
페이지가 로딩 중이거나 시각적으로 완전히 렌더링된 경우에도, 롱 태스크가 메인 스레드를 점유하여 사용자가 페이지와 상호작용하지 못하게 합니다. 설계가 잘못된 써드파티 콘텐츠가 종종 원인입니다.
- 높거나 불규칙한 입력 지연:
-
중요한 사용자 상호작용 이벤트(예: 탭, 클릭, 스크롤, 휠 등)가 롱 태스크 뒤에 큐잉되어 끊기고 예측 불가한 사용자 경험을 초래합니다.
- 높거나 불규칙한 이벤트 처리 지연:
-
입력과 마찬가지로, 이벤트 콜백(onload 이벤트 등) 처리도 애플리케이션 업데이트를 지연시킵니다.
- 끊기는 애니메이션과 스크롤:
-
일부 애니메이션 및 스크롤 상호작용은 컴포지터 스레드와 메인 스레드 간의 협력이 필요합니다. 롱 태스크가 메인 스레드를 막으면 애니메이션과 스크롤의 반응성이 저하될 수 있습니다.
일부 애플리케이션(그리고 RUM 벤더)은 이미 "롱 태스크"가 발생하는 경우를 식별하고 추적하려고 시도하고 있습니다. 예를 들어, 잘 알려진 패턴 중 하나는 짧은 주기의 타이머를 설치하고 연속 만료 사이의 경과 시간을 측정하는 것입니다. 만약 경과 시간이 타이머 주기보다 길다면, 하나 이상의 롱 태스크가 이벤트 루프 실행을 지연시켰을 가능성이 높습니다. 이 방식은 대체로 동작하지만 몇 가지 성능상 문제점이 있습니다: 롱 태스크를 감지하기 위해 폴링하면 애플리케이션은 정지 상태와 긴 유휴 블록(requestIdleCallback 참조)을 방해하게 되며, 배터리 수명에도 나쁘고, 지연의 원인(예: 퍼스트파티 또는 써드파티 코드)을 알 방법이 없습니다.
RAIL 성능 모델에 따르면 애플리케이션은 사용자 입력에 100ms 이내에 반응해야 하며(터치 이동과 스크롤의 경우 임계값은 16ms입니다). 이 API의 목적은 애플리케이션이 이런 목표를 달성하지 못하게 하는 작업에 대한 알림을 제공하는 것입니다. 이 API는 50ms 이상 걸리는 작업을 노출합니다. 이러한 작업이 없는 웹사이트는 100ms 이내에 사용자 입력에 반응할 수 있습니다: 사용자가 입력했을 때 실행 중인 작업을 끝내는 데 50ms 미만, 사용자 입력에 반응하는 작업을 실행하는 데에도 50ms 미만이 소요됩니다.
1.1. 사용 예시
const observer= new PerformanceObserver( function ( list) { for ( const entryof list. getEntries()) { // 롱 태스크 알림 처리: // 분석 및 모니터링을 위해 리포트 // ... } }); // 이전 및 이후 롱 태스크 알림에 대해 옵저버 등록. observer. observe({ type: "longtask" , buffered: true }); // 이 이후 긴 스크립트 실행은 큐잉 및 // 옵저버에서 "longtask" 엔트리 수신을 초래합니다.
2. 용어 정의
롱 태스크는 50ms를 초과하는 다음과 같은 발생을 의미합니다:
-
이벤트 루프 작업과 그 직후 수행되는 마이크로태스크 체크포인트 수행. 이는 이벤트 루프 작업의 기간을 포착하며, 이에 연관된 마이크로태스크도 포함됩니다.
-
렌더링 업데이트 단계가 이벤트 루프 처리 모델 내에서 수행되는 경우.
-
마지막 단계와 다음 첫 단계 사이의 일시정지 이벤트 루프 처리 모델. 이는 사용자 에이전트가 이벤트 루프 외의 UI 스레드에서 수행하는 작업을 포착합니다.
브라우징 컨텍스트 컨테이너는 브라우징 컨텍스트 bc의 활성 문서의 노드 내비게이블의 컨테이너를 의미합니다.
참고: 이 용어는 오래되었으며, 개편 시 새 용어를 재사용해야 합니다.
문제 브라우징
컨텍스트 컨테이너는 브라우징 컨텍스트 컨테이너(iframe
,
object
,
등)가 전체적으로 롱 태스크의 원인으로 지목되는 경우를 의미합니다.
기여 정보는 롱 태스크에 크게 기여한 작업 유형(스크립트, 레이아웃 등)을 식별하고, 해당 작업에 책임이 있는 문제 브라우징 컨텍스트 컨테이너를 식별하는 것을 의미합니다.
3. 롱 태스크 타이밍
롱 태스크 타이밍은 다음과 같은 새로운 인터페이스를 포함합니다:
3.1.
PerformanceLongTaskTiming
인터페이스
[Exposed =Window ]interface :
PerformanceLongTaskTiming PerformanceEntry { /* Overloading PerformanceEntry */readonly attribute DOMHighResTimeStamp ;
startTime readonly attribute DOMHighResTimeStamp ;
duration readonly attribute DOMString ;
name readonly attribute DOMString ;
entryType readonly attribute FrozenArray <TaskAttributionTiming >attribution ; [Default ]object (); };
toJSON
PerformanceLongTaskTiming
의
속성 값들은 § 4.1 롱 태스크 보고의 처리 모델에서 설정됩니다. 아래는 이 값들이 어떻게 설정되는지에 대한 참고 요약입니다.
name
속성의 getter는 다음 문자열 중 하나를 반환합니다:
- "
unknown
" -
롱 태스크가 사용자 에이전트가 이벤트 루프 외부에서 수행한 작업에서 발생했습니다.
- "
self
" - "
same
"- origin- ancestor - "
same
"- origin- descendant -
롱 태스크가 이벤트 루프 작업에서 발생했으며, 동일 출처의 하위 브라우징 컨텍스트 내에서 발생했습니다.
- "
same
"- origin -
롱 태스크가 이벤트 루프 작업에서 발생했으며, 동일 출처의 브라우징 컨텍스트 내에서 발생했으나 상위 또는 하위가 아닙니다.
- "
cross
"- origin- ancestor - "
cross
"- origin- descendant -
롱 태스크가 이벤트 루프 작업에서 발생했으며, 교차 출처의 하위 브라우징 컨텍스트 내에서 발생했습니다.
- "
cross
"- origin- unreachable -
롱 태스크가 이벤트 루프 작업에서 발생했으며, 교차 출처의 브라우징 컨텍스트 내에서 발생했으나 상위 또는 하위가 아닙니다.
- "
multiple
"- contexts
참고: 이러한 이름들에는 "-unreachable"과 "-contexts"와 같은 접미사가 있어 일관성이 떨어질 수 있습니다. 하지만 이전 버전과의 호환성을 위해 그대로 유지됩니다.
entryType
속성의 getter 단계는
를 반환하는 것입니다.
startTime
속성의 getter 단계는 작업이 시작된 시점의 DOMHighResTimeStamp
를
반환합니다.
duration
속성의 getter 단계는 작업 시작과 종료 사이의 경과 시간을 1ms 단위로 나타내는 DOMHighResTimeStamp
를
반환합니다.
attribution
속성의 getter는 TaskAttributionTiming
엔트리로 구성된 고정 배열을 반환합니다.
3.2.
TaskAttributionTiming
인터페이스
[Exposed =Window ]interface :
TaskAttributionTiming PerformanceEntry { /* Overloading PerformanceEntry */readonly attribute DOMHighResTimeStamp ;
startTime readonly attribute DOMHighResTimeStamp ;
duration readonly attribute DOMString ;
name readonly attribute DOMString ;
entryType readonly attribute DOMString containerType ;readonly attribute DOMString containerSrc ;readonly attribute DOMString containerId ;readonly attribute DOMString containerName ; [Default ]object (); };
toJSON
TaskAttributionTiming
의
속성 값들은 § 4.1 롱 태스크 보고의 처리 모델에서 설정됩니다. 아래는 값들이 어떻게 설정되는지에 대한 참고 요약입니다.
name
속성의 getter는 항상 "unknown
"을 반환합니다.
entryType
속성의 getter는 항상 "taskattribution
"을 반환합니다.
startTime
속성의 getter는 항상 0을 반환합니다.
duration
속성의 getter는 항상 0을 반환합니다.
containerType
속성의
getter는 문제 브라우징 컨텍스트 컨테이너의 타입(예: "iframe
", "embed
", "object
")을 반환합니다. 단일 문제 브라우징 컨텍스트
컨테이너가 없다면 "window
"를 반환합니다.
containerName
속성의
getter는 컨테이너의 name
콘텐츠 속성 값을
반환합니다. 단일 문제 브라우징 컨텍스트 컨테이너가 없다면 빈 문자열을 반환합니다.
containerId
속성의 getter는 컨테이너의 id
콘텐츠 속성 값을
반환합니다. 단일 문제 브라우징 컨텍스트 컨테이너가 없다면 빈 문자열을 반환합니다.
containerSrc
속성의
getter는 컨테이너의 src
콘텐츠 속성 값을
반환합니다. 단일 문제 브라우징 컨텍스트 컨테이너가 없다면 빈 문자열을 반환합니다.
3.3. 문제 지점 표시
이 섹션은 비규범적입니다.
롱 태스크는 스크립트, 레이아웃, 스타일 등 다양한 유형의 작업을 포함할 수 있고, 여러 브라우징 컨텍스트 내에서 실행될 수 있으며, 전체 에이전트 클러스터 또는 브라우징 컨텍스트 그룹 세트에 걸친 긴 가비지 컬렉션 등 전역적으로 발생할 수도 있습니다.
따라서 기여 정보에는 여러 측면이 있습니다:
-
롱 태스크의 원점 또는 문제 브라우징 컨텍스트의 전체 위치를 지목: 이를 최소 문제 지목이라고 하며,
name
필드에 반영됩니다. -
롱 태스크와 관련된 작업 유형과 그 문제 브라우징 컨텍스트 컨테이너를 지목: 이는
TaskAttributionTiming
객체로attribution
필드에 담깁니다(PerformanceLongTaskTiming
에서).
따라서 name
및 attribution
필드는 PerformanceLongTaskTiming
에서
롱 태스크의 책임 소재를 보여줍니다.
이 정보를 제공할 때 웹의 same-origin 정책을 반드시 따라야 합니다.
이 필드들은 독립적이지 않습니다. 아래는 이들이 어떻게 연관되는지 개요를 보여줍니다:
name
| 문제 브라우징 컨텍스트 컨테이너
(attribution 으로
지목됨)
|
---|---|
"self "
| empty |
"same "
| same-origin 문제 지점 |
"same "
| same-origin 문제 지점 |
"same "
| same-origin 문제 지점 |
"cross "
| empty |
"cross "
| empty |
"cross "
| empty |
"multiple "
| empty |
"unknown "
| empty |
4. 처리 모델
참고: Long Tasks API를 구현하는 사용자 에이전트는
를 supportedEntryTypes
에 각각 Window
컨텍스트에 포함해야 합니다.
이로써 개발자는 롱 태스크 지원 여부를 감지할 수 있습니다.
4.1. 롱 태스크 보고
-
작업 종료 시간 기록을 end time과 task의 document로 수행합니다.
-
end time에서 start time을 뺀 값이 롱 태스크 임계값 50ms 미만이면 이 단계들을 중단합니다.
-
destinationRealms를 빈 집합으로 둡니다.
-
보고가 전달될 JavaScript Realm의 집합을 결정합니다:
각 최상위 브라우징 컨텍스트 topmostBC에 대해 top-level browsing contexts에서:
-
descendantBCs를 topmostBC의 활성 문서의 하위 브라우징 컨텍스트 목록으로 둡니다.
-
document를 descendantBC의 활성 문서로 둡니다.
-
각 descendantBC에 대해 descendantBCs에서, (document의 관련 Realm, document의 관련 설정 객체의 cross-origin 격리 기능)을 destinationRealms에 추가합니다.
-
사용자 에이전트는 JavaScript Realm 일부를 destinationRealms에서 제거할 수 있습니다.
참고: 이러한 제거는 사용자 에이전트가 별도의 프로세스에서 처리하는 JavaScript Realm에 대해 롱 태스크 보고를 피하기 위해 사용될 수 있습니다. 하지만 이 개념은 정확하게 명세되어 있지 않습니다.
어떤 문서
가 어떤 롱 태스크에 대한 가시성을 갖는지에 대한 범위에 대해 지속적인 논의가 있으므로 이 논리가 앞으로 바뀔 수 있습니다. [Issue #75]
-
각 (destinationRealm, crossOriginIsolatedCapability)에 대해 destinationRealms에서:
-
name을 빈 문자열로 둡니다. 이는 아래에서 최소 문제 지목 보고에 사용됩니다.
-
culpritSettings를
로 둡니다.null -
task의 스크립트 평가 환경 설정 객체 집합을 처리하여 name과 culpritSettings를 다음과 같이 결정합니다:
-
만약 task의 스크립트 평가 환경 설정 객체 집합이 비어 있다면: name을 "
unknown
"으로, culpritSettings를
로 설정합니다.null -
그렇지 않고 task의 스크립트 평가 환경 설정 객체 집합 길이가 1보다 크면: name을 "
multiple
"로, culpritSettings를- contexts
로 설정합니다.null -
그렇지 않고 task의 스크립트 평가 환경 설정 객체 집합 길이가 1이면:
-
culpritSettings를 task의 스크립트 평가 환경 설정 객체 집합의 단일 항목으로 설정합니다.
-
destinationSettings를 destinationRealm의 관련 설정 객체로 둡니다.
-
destinationOrigin을 destinationSettings의 origin으로 둡니다.
-
destinationBC를 destinationSettings의 global object의 브라우징 컨텍스트로 둡니다.
-
culpritBC를 culpritSettings의 global object의 브라우징 컨텍스트로 둡니다.
-
Assert: culpritBC는
이 아님.null -
만약 culpritSettings가 destinationSettings와 동일하다면, name을 "
self
"로 설정합니다. -
그렇지 않고 culpritSettings의 origin과 destinationOrigin이 동일 출처라면:
-
destinationBC가
이면, name을 "null same
"로 설정합니다.- origin -
그렇지 않고 culpritBC가 상위 브라우징 컨텍스트라면 destinationBC에 대해, name을 "
same
"로 설정합니다.- origin- ancestor -
그렇지 않고 destinationBC가 상위 브라우징 컨텍스트라면 culpritBC에 대해, name을 "
same
"로 설정합니다.- origin- descendant -
그 밖의 경우, name을 "
same
"로 설정합니다.- origin
-
-
그 밖의 경우:
-
destinationBC가
이면, name을 "null cross
"로 설정합니다.- origin- unreachable -
그렇지 않고 culpritBC가 상위 브라우징 컨텍스트라면 destinationBC에 대해, name을 "
cross
" 로 설정하고 culpritSettings를- origin- ancestor
로 설정합니다.null 참고: 이는 보안을 위해 보고되지 않습니다. 개발자는 직접 확인해야 합니다.
-
그렇지 않고 destinationBC가 상위 브라우징 컨텍스트라면 culpritBC에 대해, name을 "
cross
"로 설정합니다.- origin- descendant -
그 밖의 경우, name을 "
cross
"로 설정합니다.- origin- unreachable
-
-
-
-
attribution을 destinationRealm으로 새
TaskAttributionTiming
객체로 생성하고, 다음과 같이 속성을 설정합니다:-
attribution의
name
속성을 "unknown
"으로 설정합니다.참고: 향후 API 버전에서는
name
속성에 더 많은 값을 추가할 예정이지만, 현재는 단일 값만 지원합니다. -
attribution의
entryType
속성을
으로 설정합니다."taskattribution" -
attribution의
containerType
속성을
로 설정합니다."window" -
attribution의
containerName
및containerSrc
속성을 빈 문자열로 설정합니다. -
culpritSettings가
이 아니라면:null -
culpritBC를 culpritSettings의 global object의 브라우징 컨텍스트로 둡니다.
-
Assert: culpritBC는
이 아님.null -
container를 culpritBC의 브라우징 컨텍스트 컨테이너로 둡니다.
-
Assert: container는
이 아님.null -
attribution의
containerId
속성을 container의 ID 값 또는 ID가 없으면 빈 문자열로 설정합니다. -
container가
iframe
요소라면:-
attribution의
containerType
속성을 "iframe
"으로 설정합니다. -
attribution의
containerName
속성을 container의name
콘텐츠 속성 값 또는 없으면 빈 문자열로 설정합니다. -
attribution의
containerSrc
속성을 container의src
콘텐츠 속성 값 또는 없으면 빈 문자열로 설정합니다.
참고: 프레임의
src
속성을 현재 URL 대신 기록하는 것은 일부러입니다. 이는 프레임 식별에 도움이 되며, 교차 출처 iframe의 현재 URL을 알 수 있게 하는 것은 보안상 문제가 있기 때문입니다. -
-
container가
frame
요소라면:-
attribution의
containerType
속성을 "frame
"으로 설정합니다. -
attribution의
containerName
속성을 container의name
콘텐츠 속성 값 또는 없으면 빈 문자열로 설정합니다. -
attribution의
containerSrc
속성을 container의src
콘텐츠 속성 값 또는 없으면 빈 문자열로 설정합니다.
-
-
container가
object
요소라면:-
attribution의
containerType
속성을 "object
"로 설정합니다. -
attribution의
containerName
속성을 container의 name 콘텐츠 속성 값 또는 없으면 빈 문자열로 설정합니다. -
attribution의
containerSrc
속성을 container의data
콘텐츠 속성 값 또는 없으면 빈 문자열로 설정합니다.
-
-
container가
embed
요소라면:-
attribution의
containerType
속성을 "embed
"로 설정합니다. -
attribution의
containerName
속성을 빈 문자열로 설정합니다. -
attribution의
containerSrc
속성을 container의src
콘텐츠 속성 값 또는 없으면 빈 문자열로 설정합니다.
-
-
-
-
newEntry라는
PerformanceLongTaskTiming
객체를 destinationRealm으로 새로 생성하고, 다음과 같이 속성을 설정합니다:-
newEntry의
name
속성을 name으로 설정합니다. -
newEntry의
entryType
속성을 "longtask
"로 설정합니다. -
newEntry의
startTime
속성을 coarsening start time의 결과로, crossOriginIsolatedCapability를 기준으로 설정합니다. -
dur를 coarsening end time의 결과로, crossOriginIsolatedCapability를 기준으로 계산한 값에서 newEntry의
startTime
을 뺀 값을 둡니다. -
newEntry의
duration
속성을 dur의 정수 부분으로 설정합니다. -
attribution이
이 아니면, newEntry의null attribution
속성을 attribution 단일 값을 담은 새 고정 배열로 설정합니다.참고: 향후 API 버전에서는
attribution
속성에 더 많은 값을 추가할 예정이지만, 현재는 단일 값만 포함됩니다.
-
-
PerformanceEntry를 큐에 추가 합니다: newEntry.
-
5. 보안 및 프라이버시 고려사항
Long Tasks API는 롱 태스크의 출처에 대해 출처 안전한 기여 정보를 포함함으로써 same-origin 정책을 준수합니다. 롱 태스크에는 50ms의 임계값이 적용되며, 지속 시간은 1ms 단위로만 제공됩니다. 이로써 교차 출처 정보 유출로부터 충분한 보호가 제공됩니다.
Long Tasks API는 사용자가 실행한 태스크의 지속 시간, 유형, 그리고 함수 호출을 유발한 브라우징 컨텍스트와 같은 기여 정보를 제공합니다. 이는 공격자가 부채널 타이밍 공격을 통해 사용자의 행동을 추측하거나 신원을 식별하는 데 악용할 수 있습니다. 예를 들어, 긴 스크립트 다음에 긴 렌더가 발생하는 패턴을 통해 사용자가 소셜 위젯과 상호작용했음을 추측할 수 있습니다. 상세한 함수 호출 기여 정보는 사용자의 행동을 결정하는 데 활용될 수 있습니다.
이 API가 새로운 프라이버시 공격을 도입하는 것은 아니지만, 기존 프라이버시 공격을 더 빠르게 할 수 있습니다. 이를 완화하는 방법은 필요에 따라 구현할 수 있습니다:
-
API가 제공하는 롱 태스크 지속 시간을 추가로 조정하거나 랜덤 지터를 더해 공격을 어렵게 만듭니다.
-
API가 롱 태스크를 노출하는 출처의 수를 제한하고, 이후 모든 태스크의 기여 정보를 난독화합니다. 예를 들어, 5개의 iframe이 있는 페이지는 그 중 3개의 iframe에 대해서만 기여 정보를 받고, 나머지 2개의 태스크에는 기여 정보가 제공되지 않으며 (
name
값이unknown
으로 설정됨) 처리됩니다. -
일정 임계값 이후에는 문제/기여 정보를 제공하지 않게 허용합니다. 예를 들어, 10개의 롱 태스크 이후에는 모든 엔트리가 기여 정보 없이
name
값이 "unknown
"이 됩니다. -
API가 노출하는 타이밍 정보에 내장 지연을 추가하여 롱 태스크가 많을 때 공격이 더 어렵게 만듭니다.
5.1. 옵저버에 노출되는 정보
최상위 페이지 내 모든 옵저버(즉, 페이지 내 모든 iframe과 메인 프레임)는 롱 태스크의 존재에 대한 알림을 받습니다. 우리는 태스크의 시작 시간, 지속 시간(1ms 단위), 그리고 문제 프레임에 대한 포인터를 노출합니다. 이 정보는 오늘날에도 setTimeout을 통해 더 높은 해상도로 관찰할 수 있습니다. 공격자는 페이지의 다른 요소를 모두 제거하고 취약한 교차 출처 리소스를 추가하여 setTimeout의 지연이 해당 리소스에 의해 발생했음을 보장할 수 있습니다. 다른 페이지(탭 또는 창)의 옵저버는 사용자 에이전트의 구조와 관계없이 알림을 받지 않아야 합니다.
교차 출처 노출 규칙:
-
교차 출처 옵저버는 문제 지점의 방향을 볼 수 있습니다. 예를 들어, 문제가 깊게 중첩된 iframe이라면, 호스트 페이지는 자신과 문제 지점 사이의 첫 교차 출처를 볼 수 있습니다.
-
반대로, 문제가 최상위 페이지라면, 깊게 중첩된 iframe은 교차 출처 상위에서 롱 태스크가 발생했다는 사실만 볼 수 있고 그 밖의 정보는 받지 못합니다.
5.2. 고려된 공격 시나리오
다음은 고려된 타이밍 공격입니다:
-
전통적인 타이밍 공격: 외부 리소스의 로드 시간을 이용해 비공개 데이터의 크기를 밝히는 공격. 예를 들어 갤러리의 숨겨진 사진 수, 사용자 이름이 유효한지 등. 예시 참고.
-
부채널 타이밍 공격: 동영상 파싱, 스크립트 파싱, App Cache 읽기 또는 Cache API(서비스 워커) 사용 시 소요 시간을 이용해 사용자를 식별하거나 사용자 프로필(나이, 성별, 위치, 관심사 등)을 만드는 공격. 예시에서는, 소셜 네트워크의 상태 업데이트가 특정 인구 집단(예: 20~30세 여성)에게만 제한된다면, 퍼머링크 페이지의 파일 크기를 통해 사용자가 타깃 인구 집단에 속하는지 확인할 수 있습니다.
이러한 시나리오는 50ms 임계값과 교차 출처 경계 준수(즉, 신뢰할 수 없는 교차 출처 옵저버에게 작업 유형이나 추가 기여 정보를 보여주지 않음)로 해결됩니다.