1. 소개
페이지가 로드되는 동안, 그리고 이후 사용자가 페이지와 상호작용하는 동안 애플리케이션과 브라우저는 브라우저가 실행할 다양한 이벤트를 큐에 넣는다. 예를 들어 사용자 에이전트는 사용자의 활동에 기반하여 입력 이벤트를 스케줄하고, 애플리케이션은 requestAnimationFrame 및 기타 콜백을 스케줄하는 식이다. 큐에 들어가면, 브라우저는 이러한 이벤트를 하나씩 큐에서 꺼내 실행한다.
하지만 일부 작업은 긴 시간(여러 프레임)이 걸릴 수 있으며, 그런 일이 발생하면 UI 스레드가 차단되어 다른 모든 작업도 함께 차단될 수 있다. 사용자에게 이는 흔히 브라우저가 사용자 입력에 응답할 수 없는 "멈춘" 페이지로 보이며, 오늘날 웹에서 나쁜 사용자 경험의 주요 원인이다:
- 지연된 "time to Interactive":
-
페이지가 로드되는 동안, 또는 완전히 시각적으로 렌더링된 뒤에도 긴 작업은 종종 메인 스레드를 점유하여 사용자가 페이지와 상호작용하지 못하게 한다. 잘못 설계된 서드파티 콘텐츠가 빈번히 그 원인이다.
- 높거나 가변적인 입력 지연 시간:
-
중요한 사용자 상호작용 이벤트(예: 탭, 클릭, 스크롤, 휠 등)가 긴 작업 뒤에 큐잉되어, 버벅이고 예측하기 어려운 사용자 경험을 낳는다.
- 높거나 가변적인 이벤트 처리 지연 시간:
-
입력과 마찬가지로 이벤트 콜백(예: 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: "long-animation-frame" , buffered: true }); // 이후 긴 스크립트 실행은 관찰자에서 // "long-animation-frame" 항목을 큐에 넣고 수신하게 만든다. // 이전 및 향후 긴 애니메이션 프레임 알림에 대해 관찰자를 등록한다. // 이후 메인 스레드가 바쁜 긴 기간은 관찰자에서 // "long-animation-frame" 항목을 큐에 넣고 수신하게 만든다. observer. observe({ type: "long-animation-frame" , buffered: true });
1.2. 긴 애니메이션 프레임과 긴 작업
긴 작업과 긴 애니메이션 프레임은 모두 혼잡과 버벅임을 측정하지만, 긴 애니메이션 프레임은 사용자가 이러한 유형의 혼잡을 어떻게 인식하는지와 더 잘 상관되는 정보를 제공한다. 이는 긴 애니메이션 프레임이 메인 스레드가 유휴 상태일 때 시작하고, 프레임이 렌더링되거나 사용자 에이전트가 렌더링할 것이 없다고 판단할 때 끝나는 시퀀스를 측정하기 때문이다.
task 용어는 어느 정도 구현 세부사항이며, 긴 애니메이션 프레임 추가는 동일한 메인 스레드 혼잡/버벅임 현상에 대해 더 사용자 중심적인 지표를 도입함으로써 이를 보완하려고 시도한다.
긴 애니메이션 프레임은 렌더링 단계가 최대 하나임이 보장되므로,
이를 사용하여 렌더링 단계 자체에 대한 추가 정보, 예를 들어
renderStart
및 styleAndLayoutStart를
노출할 수도 있다.
2. 긴 애니메이션 프레임 타이밍
긴 애니메이션 프레임은 지속 시간이 50ms를 초과하는 다음 발생 중 하나를 가리킨다:
긴 애니메이션 프레임 타이밍은 다음 새 인터페이스를 포함한다:
2.1. PerformanceLongAnimationFrameTiming
인터페이스
[Exposed =Window ]interface :PerformanceLongAnimationFrameTiming PerformanceEntry { /* Overloading PerformanceEntry */readonly attribute DOMHighResTimeStamp ;startTime readonly attribute DOMHighResTimeStamp ;duration readonly attribute DOMString ;name readonly attribute DOMString ;entryType readonly attribute DOMHighResTimeStamp ;renderStart readonly attribute DOMHighResTimeStamp ;styleAndLayoutStart readonly attribute DOMHighResTimeStamp ;blockingDuration readonly attribute DOMHighResTimeStamp ; [firstUIEventTimestamp SameObject ]readonly attribute FrozenArray <PerformanceScriptTiming >; [scripts Default ]object (); };toJSON PerformanceLongAnimationFrameTiming includes PaintTimingMixin ;
PerformanceLongAnimationFrameTiming은
프레임 타이밍 정보
타이밍 정보를 가진다.
entryType
속성의 getter 단계는 을 반환하는 것이다.
name
속성의 getter 단계는 을 반환하는 것이다.
startTime
속성의 getter 단계는 상대 고해상도 시간을 반환하는 것이다. 이때 this의 타이밍 정보의 시작 시간과 this의 관련 전역 객체가 주어진다.
duration
속성의 getter 단계는 지속 시간을 반환하는
것이다. 이는 this의
startTime과,
상대 고해상도 시간 사이의 지속 시간이다. 이때 this의 타이밍 정보의 종료 시간과 this의 관련 전역 객체가 주어진다.
renderStart
속성의 getter 단계는 상대 고해상도 시간을 반환하는 것이다. 이때 this의 타이밍 정보의 렌더링 업데이트 시작 시간과
this의 관련 전역 객체가 주어진다.
styleAndLayoutStart
속성의 getter 단계는 상대 고해상도 시간을 반환하는 것이다. 이때 this의 타이밍 정보의 스타일 및 레이아웃 시작 시간과 this의 관련 전역 객체가 주어진다.
firstUIEventTimestamp
속성의 getter 단계는 상대 고해상도 시간을 반환하는 것이다. 이때 this의 타이밍 정보의 첫 UI 이벤트 타임스탬프와 this의 관련 전역 객체가 주어진다.
blockingDuration
속성의 getter 단계들은 다음과 같다:
-
this의 타이밍 정보의 렌더링 업데이트 시작 시간이 0이 아니면:
-
renderDuration를 지속 시간으로 둔다. 이는 this의
renderStart와 상대 고해상도 시간 사이의 지속 시간이다. 이때 this의 타이밍 정보의 종료 시간이 주어진다. -
sortedTaskDurations[0]을 renderDuration만큼 증가시킨다.
참고: 이렇게 하면 가장 긴 작업 지속 시간 + 렌더링 지속 시간의 총 지속 시간이 >50ms인 경우 차단으로 간주된다.
-
-
totalBlockingDuration을 0으로 둔다.
-
sortedTaskDurations의 각 duration에 대해 반복하면서, duration이 50보다 크면 totalBlockingDuration을 duration - 50만큼 증가시킨다.
-
totalBlockingDuration을 반환한다.
scripts
속성의 getter 단계들은 다음과 같다:
-
scripts를 리스트 « »로 둔다.
-
entryWindow를 this의 관련 전역 객체로 둔다.
-
각 scriptInfo에 대해, 즉 this의 프레임 타이밍 정보의 scripts 안의 각 scriptInfo에 대해:
-
scriptWindow를 scriptInfo의 window로 둔다.
-
scriptEntry를 this의 관련 렐름 안의 새
PerformanceScriptTiming으로 둔다. 이 객체의 타이밍 정보는 scriptInfo이고, 그 window attribution은 처음으로 일치하는 문장에 대응하는 값이다:- scriptWindow가 undefined인 경우
- scriptWindow가 entryWindow인 경우
- entryWindow의 관련
Document의 노드 navigable의 조상 navigable이 scriptWindow의 관련Document의 노드 navigable을 포함하는 경우 - scriptWindow의 관련
Document의 노드 navigable의 조상 navigable이 entryWindow의 관련Document의 노드 navigable을 포함하는 경우 - entryWindow의 관련
Document의 노드 navigable의 최상위 traversable이 scriptWindow의 관련Document의 노드 navigable의 최상위 traversable인 경우 - 그 밖의 경우
-
scriptEntry를 scripts에 추가한다.
-
-
scripts를 반환한다.
2.2.
PerformanceScriptTiming
인터페이스
enum {ScriptInvokerType ,"classic-script" ,"module-script" ,"event-listener" ,"user-callback" ,"resolve-promise" };"reject-promise" enum {ScriptWindowAttribution ,"self" ,"descendant" ,"ancestor" ,"same-page" }; ["other" Exposed =Window ]interface :PerformanceScriptTiming PerformanceEntry { /* Overloading PerformanceEntry */readonly attribute DOMHighResTimeStamp ;startTime readonly attribute DOMHighResTimeStamp ;duration readonly attribute DOMString ;name readonly attribute DOMString ;entryType readonly attribute ScriptInvokerType ;invokerType readonly attribute DOMString ;invoker readonly attribute DOMHighResTimeStamp ;executionStart readonly attribute DOMString ;sourceURL readonly attribute DOMString ;sourceFunctionName readonly attribute long long ;sourceCharPosition readonly attribute DOMHighResTimeStamp ;pauseDuration readonly attribute DOMHighResTimeStamp ;forcedStyleAndLayoutDuration readonly attribute Window ?;window readonly attribute ScriptWindowAttribution ; [windowAttribution Default ]object (); };toJSON
PerformanceScriptTiming은
관련 스크립트
타이밍 정보 타이밍 정보를 가진다.
PerformanceScriptTiming은
관련 ScriptWindowAttribution
window attribution을 가진다.
entryType
속성의 getter 단계는 를 반환하는 것이다.
name
속성의 getter 단계는 를 반환하는 것이다.
invokerType
속성의 getter 단계는 this의 타이밍 정보의 호출자 유형을 반환하는
것이다.
invoker
속성의 getter 단계들은 다음과 같다:
-
this의
invokerType에 대해 전환한다:- "`classic-script`"
- "`module-script`"
- "`event-listener`"
-
-
this의 타이밍 정보의 이벤트 대상 요소 id가 빈 문자열이 아니면: targetName을 « targetName, "#", this의 타이밍 정보의 이벤트 대상 요소 id »의 연결로 설정한다.
-
그렇지 않고, this의 타이밍 정보의 이벤트 대상 요소 src 속성이 빈 문자열이 아니면: targetName을 « targetName, "[src=", this의 타이밍 정보의 이벤트 대상 요소 src 속성, "]" »의 연결로 설정한다.
- "`user-callback`"
- "`resolve-promise`"
- "`reject-promise`"
-
-
this의 타이밍 정보의 호출자 이름이 빈 문자열이면:
-
this의
invokerType이 "`resolve-promise`"이면 "`Promise.resolve`"를 반환한다. -
그렇지 않으면 "`Promise.reject`"를 반환한다.
-
-
invokerType이 "`resolve-promise`"이면 thenOrCatch를 "`then`"으로 두고, 그렇지 않으면 "`reject-promise`"로 둔다. -
« invoker name, ".", thenOrCatch »의 연결을 반환한다.
-
- "`classic-script`"
startTime
속성의 getter 단계는 상대 고해상도 시간을 반환하는 것이다. 이때 this의 타이밍
정보의 시작 시간과 this의 관련 전역 객체가 주어진다.
duration
속성의 getter 단계는 지속 시간을 반환하는
것이다. 이는 this의
startTime과
상대 고해상도 시간 사이의 지속 시간이다. 이때 this의 타이밍
정보의 종료 시간과 this의 관련 전역 객체가 주어진다.
executionStart
속성의 getter 단계는 this의 타이밍
정보의 실행 시작 시간이 0이면 0을 반환하고, 그렇지 않으면 상대 고해상도 시간을 반환하는 것이다. 이때 this의 타이밍
정보의 실행 시작 시간과 this의 관련 전역 객체가 주어진다.
forcedStyleAndLayoutDuration
속성의 getter 단계는 스타일과 레이아웃을 동기적으로 수행하는 데 소비된 시간을 나타내는
구현 정의 값을 반환하는 것이다. 예를 들어 getComputedStyle()
또는 getBoundingClientRect()를
호출하는 경우이다.
이것을 상호운용 가능하고 규범적으로 만드는 방법을 찾는다. 아마도 WebIDL에서 이러한 함수들을 동기식 스타일/레이아웃을 요구하는 것으로 표시할 수 있을 것이다. 또한 그것이 해결되면 타이밍 정보로 이동한다.
pauseDuration
속성의 getter 단계는 this의 타이밍 정보의 일시정지
지속 시간을 반환하는 것이다.
sourceURL
속성의 getter 단계는 this의 타이밍 정보의 소스 URL을 반환하는 것이다.
sourceFunctionName
속성의 getter 단계는 this의 타이밍 정보의 소스
함수 이름을 반환하는 것이다.
sourceCharPosition
속성의 getter 단계는 this의 타이밍 정보의 소스 문자 위치를 반환하는 것이다.
window
속성의 getter 단계들은 다음과 같다:
-
window가 undefined이면 null을 반환하고, 그렇지 않으면 window를 반환한다.
windowAttribution
속성의 getter 단계는 this의 window attribution을 반환하는 것이다.
3. 처리 모델
참고: Long Animation Frame
API를 구현하는 사용자 에이전트는 각각 Window
컨텍스트에 대해 을
supportedEntryTypes에
포함해야 한다.
3.1. 프레임 타이밍 정보
프레임 타이밍 정보는 긴 애니메이션 프레임 알고리즘에서 장부 기록 세부사항으로 사용되는 구조체이다. 이는 다음 항목들을 가진다:- 시작 시간
- 현재 작업 시작 시간
- 렌더링 업데이트 시작 시간
- 스타일 및 레이아웃 시작 시간
- 첫 UI 이벤트 타임스탬프
- 종료 시간
- 현재 작업 시작 시간
-
DOMHighResTimeStamp, 초기값은 0. 참고: 위의 모든 것은 안전하지 않으며, API를 통해 노출될 때는 거칠게 조정되어야 한다. - 작업 지속 시간
-
DOMHighResTimeStamp의 리스트, 초기값은 비어 있음. - scripts
-
스크립트 타이밍 정보의 리스트, 초기값은 비어 있음.
- 대기 중인 스크립트
-
Null 또는 스크립트 타이밍 정보, 초기값은 null.
스크립트 타이밍 정보는 구조체이다. 이는 다음 항목들을 가진다:
- 호출자 유형
- 시작 시간
- 종료 시간
- 실행 시작 시간
- 종료 시간
-
안전하지 않은
DOMHighResTimeStamp, 초기값은 0. - 일시정지 지속 시간
-
밀리초 수를 나타내는
DOMHighResTimeStamp, 초기값은 0. - 호출자 이름
- 소스 URL
- 소스 함수 이름
- 이벤트 유형
- 이벤트 대상 요소 id
- 이벤트 대상 요소 src 속성
- 소스 URL
-
문자열, 초기값은 빈 문자열.
- 소스 문자 위치
-
숫자, 초기값은 -1.
- window
Document는
null 또는 프레임 타이밍
정보인 현재
프레임 타이밍 정보를 가지며, 초기값은 null이다.
3.2. 긴 애니메이션 프레임 보고
3.2.1. 긴 애니메이션 프레임 모니터링 {#loaf-monitoring}
Document
document에 대한 가장 가까운
동일 출처 루트를 얻으려면:
-
ancestors를 document의 조상 navigable로 둔다.
-
ancestors 안의 각 ancestorNavigable에 대해 반복한다: ancestorNavigable의 활성 문서의 출처가 document의 출처와 동일 출처이고, ancestorNavigable의 활성 문서의 관련 에이전트가 document의 관련 에이전트라면, ancestorNavigable의 활성 문서를 반환한다.
-
document를 반환한다.
Document
document에 대한 관련
프레임 타이밍 정보는 그 가장 가까운 동일 출처 루트의 현재 프레임 타이밍
정보이다.
DOMHighResTimeStamp
unsafeTaskStartTime 및 Document
document가 주어졌을 때, 작업 시작
시간을 기록하려면:
-
root를 document의 가장 가까운 동일 출처 루트로 둔다.
-
root의 현재 프레임 타이밍 정보가 null이면, root의 현재 프레임 타이밍 정보를 새 프레임 타이밍 정보로 설정한다. 그 시작 시간은 unsafeTaskStartTime이다.
-
root의 현재 프레임 타이밍 정보의 현재 작업 시작 시간을 unsafeTaskStartTime으로 설정한다.
-
root의 현재 프레임 타이밍 정보의 's 시작 시간이 0이면, root의 현재 프레임 타이밍 정보의 시작 시간을 unsafeTaskStartTime으로 설정한다.
DOMHighResTimeStamp
unsafeTaskEndTime 및 Document
document가 주어졌을 때, 작업 종료
시간을 기록하려면:
-
timingInfo를 document의 관련 프레임 타이밍 정보로 둔다.
-
timingInfo가 null이면 반환한다.
참고: 이는 브라우저가 시퀀스 중에 hidden 상태가 되는 경우 발생할 수 있다.
-
safeTaskEndTime을 unsafeTaskEndTime 및 document의 관련 전역 객체가 주어졌을 때의 상대 고해상도 시간으로 둔다.
-
safeTaskStartTime을 timingInfo의 현재 작업 시작 시간 및 document의 관련 전역 객체가 주어졌을 때의 상대 고해상도 시간으로 둔다.
-
safeTaskStartTime과 safeTaskEndTime 사이의 지속 시간을 timingInfo의 작업 지속 시간에 추가한다.
-
사용자 에이전트가 document의 노드 navigable의 렌더링을 업데이트해도 시각적 효과가 없다고 판단하면:
-
document의 가장 가까운 동일 출처 루트의 현재 프레임 타이밍 정보를 null로 설정한다.
-
frameDuration을 지속 시간으로 둔다. 이는 timingInfo의 시작 시간과 global이 주어졌을 때의 상대 고해상도 시간과, unsafeTaskEndTime 및 global이 주어졌을 때의 상대 고해상도 시간 사이의 지속 시간이다.
-
frameDuration이 50밀리초 이상이면, document, timingInfo, 및 새 [/=paint timing info=]가 주어졌을 때 긴 애니메이션 프레임 항목을 큐에 넣는다.
참고: 실제 시각적 업데이트가 없었더라도, 관련 없는 시각적 업데이트와 겹쳤다면 차단했을 시나리오이므로 여기서 긴 애니메이션 프레임으로 표시한다.
-
Document
document 및 DOMHighResTimeStamp
unsafeStyleAndLayoutStart가 주어졌을 때, 렌더링
시간을 기록하려면:
-
timingInfo를 document의 관련 프레임 타이밍 정보로 둔다.
-
timingInfo가 null이면 반환한다.
참고: 이는 브라우저가 시퀀스 중에 hidden 상태가 되는 경우 발생할 수 있다.
-
frameDuration을 지속 시간으로 둔다. 이는 timingInfo의 시작 시간과 global이 주어졌을 때의 상대 고해상도 시간과, global이 주어졌을 때의 현재 고해상도 시간 사이의 지속 시간이다.
-
frameDuration이 50밀리초보다 작으면, document의 가장 가까운 동일 출처 루트의 현재 프레임 타이밍 정보를 null로 설정하고 반환한다.
-
timingInfo의 렌더링 업데이트 시작 시간을 timingInfo의 현재 작업 시작 시간으로 설정한다.
-
timingInfo의 스타일 및 레이아웃 시작 시간을 unsafeStyleAndLayoutStart로 설정한다.
Document
document, 프레임
타이밍 정보 timingInfo, 및 페인트 타이밍
정보 paintTimingInfo가 주어졌을 때,
긴
애니메이션 프레임 항목을 큐에 넣으려면,
document의 관련 렐름 안의 새 PerformanceLongAnimationFrameTiming을
큐에 넣는다.
이 객체의 타이밍 정보는 timingInfo이고,
그 페인트 타이밍 정보는 paintTimingInfo이다.
3.2.2. 긴 스크립트 모니터링
Function
handler, 환경 설정 객체 settings, 그리고
boolean repeat가 주어졌을 때, 타이머
핸들러에 대한 타이밍 정보를 기록하려면:
settings, "`user-callback`", 그리고 스크립트 타이밍 정보 scriptTimingInfo가 주어졌을 때의 다음
단계를 사용하여
스크립트
진입점을 생성한다:
Event
event 및 EventListener
listener가 주어졌을 때, 이벤트 리스너에 대한 타이밍 정보를 기록하려면:
listener의 관련 설정 객체, "`event-listener`", 그리고 스크립트 타이밍 정보 scriptTimingInfo와
프레임 타이밍 정보
frameTimingInfo가 주어졌을 때의 다음 단계를 사용하여 스크립트
진입점을 생성한다:
-
target을 event의
currentTarget으로 둔다. -
target이
Node이면:-
target이
Element이면:-
scriptTimingInfo의 이벤트 대상 요소 id를 target의 id로 설정한다.
-
scriptTimingInfo의 이벤트 대상 요소 src 속성을 target과 "`src`"를 사용하여 이름으로 속성 값을 얻은 결과로 설정한다.
-
-
그렇지 않으면, scriptTimingInfo의 호출자 이름을 target의 인터페이스 이름으로 설정한다.
-
listener의 callback이 주어졌을 때 scriptTimingInfo에 대해 소스 위치를 적용한다.
-
event가
UIEvent이고, frameTimingInfo의 첫 UI 이벤트 타임스탬프가 0이면, frameTimingInfo의 첫 UI 이벤트 타임스탬프를 event의timeStamp로 설정한다.
Promise
promise 및 "`resolve-promise`" 또는 "`reject-promise`" type이 주어졌을 때,
promise resolver에 대한 타이밍 정보를 기록하려면:
-
promise의 관련 렐름의 설정 객체, type, 그리고 스크립트 타이밍 정보 scriptTimingInfo가 주어졌을 때의 다음 단계를 사용하여 스크립트 진입점을 생성한다:
-
scriptTimingInfo의 호출자 이름을 promise의 생성 시 호출자 이름으로 설정한다.
-
scriptTimingInfo의 소스 URL을 promise의 생성 시 스크립트 URL로 설정한다.
-
-
script의 설정 객체, "`classic-script`", 그리고 스크립트 타이밍 정보 scriptTimingInfo가 주어졌을 때의 다음 단계를 사용하여 스크립트 진입점을 생성한다: scriptTimingInfo, script, 및 originalSourceURL가 주어졌을 때 스크립트 블록에 대한 소스 URL을 설정한다.
-
settings를 script의 설정 객체로 둔다.
-
script의 muted errors가 true이면 반환한다.
-
settings가
Window가 아니면 반환한다. -
document를 settings의
document로 둔다. -
frameTimingInfo를 document의 관련 프레임 타이밍 정보로 둔다.
-
frameTimingInfo가 null이거나 frameTimingInfo의 대기 중인 스크립트가 null이 아니면 반환한다.
-
Assert: frameTimingInfo의 대기 중인 스크립트의 호출자 유형은 "`classic-script`"이다.
-
frameTimingInfo의 대기 중인 스크립트의 실행 시작 시간을 안전하지 않은 공유 현재 시간으로 설정한다.
-
scriptTimingInfo의 실행 시작 시간을 script의 scriptTimingInfo의 시작 시간으로 설정한다.
-
scriptTimingInfo, script, 및 script의 기준 URL이 주어졌을 때 스크립트 블록에 대한 소스 URL을 설정한다.
ScriptInvokerType
invokerType, 그리고 steps가 주어졌을 때,
스크립트
진입점을 생성하려면,
여기서 steps는 스크립트 타이밍 정보와 선택적 프레임 타이밍 정보를 받는 알고리즘이다:
-
settings가
Window가 아니면 반환한다. -
document를 settings의
document로 둔다. -
document가 완전히 활성이 아니거나
이면, 반환한다. -
frameTimingInfo를 document의 관련 프레임 타이밍 정보로 둔다.
-
frameTimingInfo가 null이면 반환한다.
-
frameTimingInfo의 대기 중인 스크립트가 null이 아니면 반환한다.
-
scriptTimingInfo를 새 스크립트 타이밍 정보로 둔다. 그 시작 시간은 안전하지 않은 공유 현재 시간이고, 그 호출자 유형은 invokerType이다.
-
scriptTimingInfo 및 frameTimingInfo가 주어졌을 때 steps를 실행한다.
-
scriptTimingInfo의 window를 settings로 설정한다.
-
frameTimingInfo의 대기 중인 스크립트를 scriptTimingInfo로 설정한다.
-
script를 실행 중인 스크립트로 둔다.
-
settings를 script의 설정 객체로 둔다.
-
document를 settings의
document로 둔다. -
document가 완전히 활성이 아니거나
이면, 반환한다. -
frameTimingInfo를 document의 관련 프레임 타이밍 정보로 둔다.
-
scriptTimingInfo를 frameTimingInfo의 대기 중인 스크립트로 둔다.
-
frameTimingInfo의 대기 중인 스크립트를 null로 설정한다.
-
scriptTimingInfo가 null이면 반환한다.
-
scriptTimingInfo의 종료 시간을 안전하지 않은 공유 현재 시간으로 설정한다.
-
script가 classic script이고 그 muted errors가 true이면:
-
scriptTimingInfo의 시작 시간과 scriptTimingInfo의 종료 시간 사이의 지속 시간이 5밀리초보다 크면, scriptTimingInfo를 frameTimingInfo의 scripts에 추가한다.
-
script를 실행 중인 스크립트로 둔다.
-
settings를 script의 설정 객체로 둔다.
-
settings가
Window가 아니면 반환한다. -
document를 settings의
document로 둔다. -
document가 완전히 활성이 아니거나
이면, 반환한다. -
frameTimingInfo를 document의 관련 프레임 타이밍 정보로 둔다.
-
frameTimingInfo가 null이면 반환한다.
-
frameTimingInfo의 대기 중인 스크립트가 null이면 반환한다.
-
frameTimingInfo의 대기 중인 스크립트의 일시정지 지속 시간을 duration의 밀리초 값만큼 증가시킨다.
4. 기존 표준에 대한 추가
4.1. WebIDL 표준에 대한 몽키 패치
Promise
인터페이스는 관련 문자열 생성 시 호출자 이름을 가지며, 초기값은
"`Promise`"이다.
Promise
인터페이스는 관련 문자열 생성 시 스크립트 URL을 가지며, 초기값은 빈
문자열이다.
Promise를
반환하기 전에, 새 promise 생성에 다음 단계를
추가한다:
-
interfaceName을 이 promise 생성을 담당하는 인터페이스를 나타내는 문자열로 둔다.
-
attributeName을 이 promise 생성을 담당하는 인터페이스의 속성을 나타내는 문자열로 둔다.
-
생성된
Promise의 생성 시 스크립트 URL을 실행 중인 스크립트의 기준 URL로 설정한다. -
사용자 에이전트는 생성된
Promise의 생성 시 호출자 이름을 마지막으로 알려진 « interfaceName, ".", attributeName »의 연결로 설정할 수 있다.이것은 꽤 애매한데, 규범적인 방식으로 이를 수행하기 어렵기 때문이다. 개선할 수 있는지, 아니면 promise 핸들러의 소스 위치가 다소 구현 정의로 남을지 확인해야 한다.
Promise
p가 주어졌을 때 promise 해결에 다음 단계를
앞에 추가한다:
p 및 "`resolve-promise`"가 주어졌을 때
promise resolver에 대한 타이밍 정보를 기록한다.
Promise
p가 주어졌을 때 promise 거부에 다음 단계를
앞에 추가한다:
p 및 "`reject-promise`"가 주어졌을 때
promise resolver에 대한 타이밍 정보를 기록한다.
5. 보안 및 개인정보 보호 고려사항
Long Animation Frames API는 긴 작업의 출처에 대한 origin-safe 귀속 정보를 포함함으로써 동일 출처 정책을 준수한다. 긴 작업에는 50ms 임계값이 있다. 지속 시간은 1 ms 단위의 세분성으로만 제공된다. 이를 함께 적용하면 교차 출처 누출에 대한 적절한 보호를 제공한다.
Long Animation Frames API는 사용자가 실행한 작업의 지속 시간과 유형에 대한 타이밍 정보뿐 아니라, 함수 호출을 유발한 브라우징 컨텍스트 같은 귀속 정보도 제공한다. 이는 공격자가 사용자 동작을 추측하거나 사용자를 식별하기 위해 부채널 타이밍 공격을 수행할 수 있게 할 수 있다. 예를 들어 긴 스크립트 뒤에 긴 렌더가 이어지는 패턴을 조합하여 사용자가 소셜 위젯과 상호작용했는지 추측할 수 있다. 자세한 함수 호출 귀속은 사용자의 동작을 판단하는 데 사용될 수 있다.
5.1. 관찰자에게 무엇이 노출되는가?
최상위 페이지 안의 모든 관찰자(즉, 페이지 안의 모든 iframe과 메인 프레임)는 긴 애니메이션 프레임의 존재에 대한 알림을 받는다. 우리는 작업의 시작 시간, 지속 시간(1 ms 세분성), 그리고 원인 프레임을 가리키는 포인터를 노출한다. 이 정보는 오늘날에도 setTimeout을 사용해 더 높은 해상도로 이미 관찰할 수 있다. 공격자는 페이지의 다른 모든 것을 제거하고 취약한 교차 출처 리소스를 추가하여 setTimeout의 지연이 그 리소스에 의해 발생하도록 함으로써 이를 수행할 수 있다. 서로 다른 다른 페이지(탭 또는 창)의 관찰자는 사용자 에이전트의 아키텍처와 관계없이 알림을 받아서는 안 된다.
5.2. 고려된 공격 시나리오
고려된 타이밍 공격은 다음과 같다:
-
전통적인 타이밍 공격: 외부 리소스 로드 시간을 사용하여 비공개 데이터의 크기를 드러내는 것이다. 예를 들어 갤러리의 숨겨진 사진 수, 사용자 이름이 유효한지 여부 등이 있다. 예시를 참조하라.
-
부채널 타이밍 공격: 비디오 파싱, 스크립트 파싱, App Cache 읽기 또는 Cache API (service workers) 사용에 걸리는 시간을 이용해 사용자를 고유하게 식별하거나, 사용자의 나이, 성별, 위치, 관심사 등의 프로필을 만드는 것이다. 예를 들어, 소셜 네트워크의 상태 업데이트가 특정 인구통계(예: 20-30세 여성)로 제한될 수 있으며, permalink 페이지의 파일 크기를 사용해 사용자가 대상 인구통계에 속하는지 판단할 수 있다.
이러한 시나리오는 50ms 임계값과 교차 출처 경계를 존중하는 것, 즉 신뢰할 수 없는 교차 출처 관찰자에게 작업 유형이나 추가 귀속을 보이지 않음으로써 처리된다.
5.3. Long Animation Frames API가 노출하는 추가 정보
여러 교차 출처 문서가 동일한 이벤트 루프를 공유할 수 있으므로, 이들은 동일한 프레임 시퀀스의 일부로 렌더링되고 서로의 렌더링 시간에 영향을 줄 수도 있다. 이 때문에 이러한 타이밍은 이미 어느 정도 교차 출처에서 관찰 가능하다. 예를 들어 animation frame을 요청하고 그것이 지연되는지 관찰하는 방식이다. 다만 긴 애니메이션 프레임은 이를 더 높은 충실도로 노출한다.이를 완화하기 위해 긴 애니메이션 프레임은 "참여하는 로컬 루트"에만 보고된다. 즉, 시퀀스에 기여한 작업 태스크와 관련된 문서, 또는 프레임의 일부로 렌더링된 문서만 긴 애니메이션 프레임을 관찰할 수 있으며, 해당 긴 애니메이션 프레임은 최상위이거나 교차 출처 부모를 가진 가장 가까운 조상에서만 사용할 수 있다.
5.4. PerformanceScriptTiming과
불투명 스크립트
PerformanceScriptTiming은
스크립트 실행에 대한 정보를 노출하므로, 그렇지 않으면 쉽게 추론할 수 없는 CORS
교차 출처 스크립트에 대한 정보를 너무 많이 노출하지 않도록 해야 한다.
이를 위해 기존의 muted errors
boolean을 사용하고, 그런 경우 빈 sourceURL을
보고한다.