이벤트 타이밍 API

W3C 워킹 드래프트,

이 문서에 대한 자세한 정보
이 버전:
https://www.w3.org/TR/2025/WD-event-timing-20250526/
최신 공개 버전:
https://www.w3.org/TR/event-timing/
에디터스 드래프트:
https://w3c.github.io/event-timing/
이전 버전:
히스토리:
https://www.w3.org/standards/history/event-timing/
테스트 스위트:
https://github.com/web-platform-tests/wpt/tree/master/event-timing
피드백:
GitHub
에디터:
(Google)
이전 에디터:
(Google)
(Google)

요약

이 문서는 웹 페이지 작성자가 사용자 상호작용에 의해 트리거된 특정 이벤트의 지연 시간에 대한 인사이트를 얻을 수 있는 API를 정의합니다.

이 문서의 상태

이 섹션은 이 문서가 발행된 시점의 상태를 설명합니다. 현행 W3C 출판 목록과 이 기술 보고서의 최신 개정판은 https://www.w3.org/TR/ W3C 표준 및 초안 색인에서 확인할 수 있습니다.

이 문서는 웹 성능 작업 그룹에서 작업 초안으로 권고 트랙을 사용해 발행되었습니다. 작업 초안으로 발행되었다고 해서 W3C 및 회원의 승인됨을 의미하지는 않습니다.

이 문서는 초안 문서이며, 언제든지 업데이트, 교체 또는 폐기될 수 있습니다. 진행 중인 작업이 아닌 것으로 이 문서를 인용하는 것은 적절하지 않습니다.

GitHub 이슈를 이 명세의 토론을 위해 우선적으로 사용합니다.

이 문서는 2023년 11월 3일 W3C 프로세스 문서의 적용을 받습니다.

이 문서는 W3C 특허 정책을 준수하는 그룹에서 작성되었습니다. W3C는 해당 그룹의 산출물과 관련하여 공개 특허 공개 목록을 유지합니다. 해당 페이지에는 특허 공개 방법에 대한 안내도 포함되어 있습니다. 개인이 본인이 실제로 알게 된 특허가 필수 청구항을 포함한다고 생각하는 경우, W3C 특허 정책 6절에 따라 정보를 공개해야 합니다.

1. 소개

1.1. 개요

이 섹션은 규범적인 내용이 아닙니다.

사용자가 웹사이트와 상호작용할 때, 자신의 행동이 빠르게 웹사이트에 변화를 일으키기를 기대합니다. 실제로 연구에 따르면 100ms 이내에 처리되지 않는 모든 사용자 입력은 느리다고 간주됩니다. 따라서 입력 이벤트가 해당 기준을 달성하지 못했을 때의 성능 타이밍 정보를 노출하는 것이 중요합니다.

이벤트 지연을 모니터링하는 일반적인 방법은 이벤트 리스너를 등록하는 것입니다. 이벤트가 생성된 시점은 이벤트의 timeStamp로 얻을 수 있습니다. 추가로 performance.now()를 이벤트 핸들러 로직의 시작과 끝에서 호출할 수 있습니다. 하드웨어 타임스탬프에서 이벤트 핸들러 시작 시점의 타임스탬프를 빼면 개발자는 입력 지연—입력이 처리되기 시작하는 데 걸리는 시간—을 계산할 수 있습니다. 이벤트 핸들러 시작 시점의 타임스탬프와 끝 시점의 타임스탬프의 차이를 통해 개발자는 이벤트 핸들러에서 수행된 동기 작업의 양을 계산할 수 있습니다. 마지막으로 입력이 동기적으로 처리되는 경우, 이벤트가 처리된 후 다음 페인트까지의 기간은 사용자 경험 측정에 유용한 지표가 됩니다.

이 방식에는 몇 가지 근본적인 결함이 있습니다. 첫째, 이벤트 리스너가 필요하므로 페이지 로드 초기에 이벤트 지연을 측정할 수 없습니다. 그 시점에는 리스너가 등록되지 않았기 때문입니다. 둘째, 입력 지연만 관심 있는 개발자도 원래 리스너가 없던 이벤트에 새로운 리스너를 추가해야 할 수 있습니다. 이는 이벤트 지연 계산에 불필요한 성능 오버헤드를 발생시킵니다. 마지막으로, 이 방식으로는 이벤트로 인해 발생한 비동기 작업의 측정이 매우 어렵습니다.

이 명세는 이러한 문제를 해결하는 이벤트 지연 모니터링의 대안을 제공합니다. 사용자 에이전트가 타임스탬프를 계산하므로, 성능 측정을 위해 이벤트 리스너가 필요하지 않습니다. 이는 페이지 로드 아주 초기에 발생하는 이벤트도 포착할 수 있음을 의미합니다. 또한 느린 이벤트에 대한 가시성을 높일 수 있어, 분석 제공자가 모든 이벤트에 대해 패치 및 구독을 시도할 필요가 없습니다. 웹사이트의 성능도 불필요한 이벤트 리스너로 인한 오버헤드 없이 유지됩니다. 마지막으로, 이 명세는 개발자가 이벤트 처리 직후 발생하는 렌더링의 타이밍 정보를 상세히 얻을 수 있도록 합니다. 이는 이벤트에 의해 트리거된 웹사이트 수정의 오버헤드를 측정하는 데 유용할 수 있습니다.

1.2. 상호작용

이 섹션은 규범적인 내용이 아닙니다.

하나의 사용자 Interaction (때때로 Gesture라고도 함)은 일반적으로 여러 물리적 하드웨어 입력 이벤트로 구성됩니다. 각 물리적 입력 이벤트는 사용자 에이전트가 여러 UIEvent를 디스패치하게 만들 수 있고, 각각은 여러 커스텀 이벤트 리스너를 트리거하거나 별도의 기본 동작을 트리거할 수 있습니다.

예를 들어, 터치스크린 디바이스에서 단일 사용자 "탭" 상호작용은 실제로 일련의 물리적 입력 이벤트로 이루어져 있습니다:

이 물리적 입력 이벤트들은 일련의 UIEvent를 디스패치할 수 있습니다:

이 개별 UIEvent들은 각각 자세한 타이밍 측정에 유용한 자신의 PerformanceEventTiming 엔트리 보고 후보가 됩니다.

참고: pointermovetouchmove 는 현재 이벤트 타이밍 대상에 포함되지 않습니다.

그러나 이 명세는 PerformanceEventTiming들을 Interaction으로 interactionId를 통해 그룹화하는 메커니즘도 정의합니다. 이 메커니즘은 Interaction to Next Paint (INP)라는 페이지 반응성 지표를 정의하는 데 사용할 수 있습니다.

1.3. 최초 입력

이 섹션은 규범적인 내용이 아닙니다.

가장 첫 번째 사용자 Interaction은 사용자 경험에 불균형적으로 큰 영향을 미치며, 대체로 상대적으로 느린 경우가 많습니다.

이런 맥락에서 Event Timing API는 Window최초 입력에 대한 타이밍 정보를 노출합니다. 여기서 최초 입력은 interactionId가 0이 아닌 첫 번째 PerformanceEventTiming 엔트리로 정의됩니다.

대부분의 PerformanceEventTiming과 달리, 최초 입력 엔트리는 제공된 durationThreshold를 초과하지 않아도 보고되며, 기본 지속 시간 임계값인 104ms를 초과하지 않아도 버퍼링됩니다. 이 메커니즘은 First Input Delay (FID)라는 페이지 반응성 지표를 정의하는 데 사용할 수 있습니다.

이로 인해 개발자는 항상 매우 반응성이 좋은 페이지의 데이터까지 포함하여 백분위수와 성능 개선을 더 잘 측정할 수 있습니다. 이벤트 핸들러를 등록하지 않아도 됩니다.

1.4. 노출되는 이벤트

Event Timing API는 특정 이벤트에 대해서만 타이밍 정보를 노출합니다.

event이벤트 타이밍 대상인지 판단하려면 다음 단계를 실행합니다:
  1. eventisTrusted 속성이 false로 설정되어 있으면 false를 반환한다.

  2. eventtype 이 다음 중 하나라면: auxclick, click, contextmenu, dblclick, mousedown, mouseenter, mouseleave, mouseout, mouseover, mouseup, pointerover, pointerenter, pointerdown, pointerup, pointercancel, pointerout, pointerleave, gotpointercapture, lostpointercapture, touchstart, touchend, touchcancel, keydown, keypress, keyup, beforeinput, input, compositionstart, compositionupdate, compositionend, dragstart, dragend, dragenter, dragleave, dragover, drop, true를 반환한다.

  3. false를 반환한다.

참고: mousemove, pointermove, pointerrawupdate, touchmove, wheel, 그리고 drag 는 "연속적인" 이벤트이기 때문에 제외됩니다. 현행 API는 이러한 이벤트를 개별 엔트리 기반의 의미 있는 성능 지표로 계산하고 집계하는 방법에 대한 충분한 가이드가 없습니다. 따라서 이러한 이벤트 타입은 노출되지 않습니다.

1.5. 이벤트가 측정되는 시점

이 섹션은 규범적인 내용이 아닙니다. § 3 처리 모델 섹션에서 노출되는 정보의 상위 수준 설명입니다.

이벤트 타이밍 정보는 특정 이벤트에 대해서만, 그리고 사용자 입력과 입력 처리 이후 페인트 작업 사이의 시간 차가 특정 지속 시간 임계값을 초과할 때만 노출됩니다.

Event Timing API는 duration 값을 노출합니다. 이는 실제 사용자 입력 발생 시점(EventtimeStamp로 추정) 부터 입력 처리 후 Event관련 글로벌 객체연결된 Document가 업데이트되는 시점까지의 시간입니다. 이 값은 8밀리초 단위로 제공됩니다.

기본적으로 Event Timing API는 duration이 104 이상일 때 버퍼링 및 노출하지만, 개발자는 PerformanceObserver를 설정하여 다른 임계값을 가진 향후 엔트리를 관찰할 수 있습니다. 이 설정은 버퍼링되는 엔트리에 영향을 주지 않으므로 buffered 플래그는 기본 임계값 이상인 과거 엔트리만 수신할 수 있도록 합니다.

Eventdelay란 브라우저가 해당 이벤트의 핸들러를 실행하려는 시점과 EventtimeStamp의 차이입니다. 앞선 시점은 PerformanceEventTimingprocessingStart로, 뒤의 시점은 PerformanceEventTimingstartTime으로 노출됩니다. 따라서 Event의 지연은 processingStart - startTime으로 계산할 수 있습니다.

Event Timing API는 이벤트에 리스너가 등록되어 있든 없든 엔트리를 생성합니다. 특히 최초 클릭이나 최초 키 입력은 사용자가 실제로 페이지 기능을 사용하려는 시도가 아닐 수도 있습니다; 많은 사용자가 텍스트를 선택하거나 빈 영역을 클릭해 포커스를 조정하는 행동을 합니다. 이는 이벤트 리스너가 너무 늦게 등록된 페이지의 문제를 포착하고, 리스너가 없어도 의미 있는 입력(예: hover 효과)의 성능을 포착하기 위한 설계 선택입니다. 개발자는 processingEnd - processingStart 값이 사실상 0인 엔트리를 무시함으로써 이러한 엔트리를 걸러낼 수 있습니다. processingEnd이벤트 디스패치 알고리즘이 종료된 시점을 나타냅니다.

1.6. 사용 예시

const observer = new PerformanceObserver(function(list, obs) {
    for (let entry of list.getEntries()) {
        // Input Delay
        const inputDelay = entry.processingStart - entry.startTime;
        // Processing duration
        const processingDuration = entry.processingEnd - entry.processingStart;
        // Presentation Delay (approximate)
        const presentationDelay = Math.max(0, entry.startTime + entry.duration - entry.processingEnd);

        // Obtain some information about the target of this event, such as the id.
        const targetId = entry.target ? entry.target.id : 'unknown-target';

        console.log(entry.entryType, entry.name, entry.duration, { inputDelay, processingDuration, presentationDelay });
    }
});
observer.observe({ type: 'first-input', buffered: true });
observer.observe({ type: 'event', buffered: true, durationThreshold: 40 });

아래 예시는 interactionId별로 각각의 이벤트 중 최대 지속 시간을 매핑하는 딕셔너리를 계산합니다. 이 딕셔너리는 나중에 집계되어 분석에 보고할 수 있습니다.

let maxDurations = {};
new PerformanceObserver(list => {
    for (let entry of list.getEntries()) {
        if (entry.interactionId > 0) {
            let id = entry.interactionId;
            if (!maxDurations[id]) {
                maxDurations[id] = entry.duration;
            } else {
                maxDurations[id] = Math.max(maxDurations[id], entry.duration);
            }
        }
    }
}).observe({ type: 'event', buffered: true, durationThreshold: 16 });

아래는 이 API를 이용해 달성할 수 있는 샘플 사용 사례입니다:

2. 이벤트 타이밍

이벤트 타이밍은 다음과 같은 인터페이스를 추가합니다:

2.1. PerformanceEventTiming 인터페이스

[Exposed=Window]
interface PerformanceEventTiming : PerformanceEntry {
    readonly attribute DOMHighResTimeStamp processingStart;
    readonly attribute DOMHighResTimeStamp processingEnd;
    readonly attribute boolean cancelable;
    readonly attribute Node? target;
    readonly attribute unsigned long long interactionId;
    [Default] object toJSON();
};

PerformanceEventTiming 객체는 하나의 연결된 Event에 대한 타이밍 정보를 보고합니다.

PerformanceEventTiming 객체는 다음과 같은 연결된 개념을 가지며, 모두 초기에 null로 설정됩니다:

target 속성의 getter는 다음 단계를 수행해야 합니다:

  1. thiseventTarget이 null로 주어졌을 때 페인트 타이밍에 대해 노출되지 않는 경우, null을 반환한다.

  2. thiseventTarget을 반환한다.

참고: Event Timing API를 구현하는 사용자 에이전트는 first-inputeventsupportedEntryTypes 에 포함해야 하며, Window 컨텍스트에 적용됩니다. 이는 개발자가 이벤트 타이밍 지원 여부를 감지할 수 있게 합니다.

이 섹션의 나머지는 규범적인 내용이 아닙니다. PerformanceEventTiming 속성의 값은 § 3 처리 모델에서 설정됩니다. 이 섹션은 그 값이 어떻게 설정되는지 정보 제공 차원의 요약을 포함합니다.

PerformanceEventTimingPerformanceEntry 인터페이스의 다음 속성을 확장합니다:

name
name 속성의 getter는 연결된 이벤트type을 제공합니다.
entryType
entryType 속성의 getter는 "event"(긴 이벤트) 또는 "first-input"(최초 사용자 상호작용) 중 하나를 반환합니다.
startTime
startTime 속성의 getter는 연결된 이벤트timeStamp를 반환합니다.
duration
duration 속성의 getter는 렌더링 업데이트 단계가 연결된 이벤트Document에서 해당 이벤트가 디스패치된 후 완료되는 다음 시점과 startTime 간의 차이를 8ms 단위로 반올림하여 반환합니다.

PerformanceEventTiming 은 다음 추가 속성을 가집니다:

processingStart
processingStart 속성의 getter는 이벤트 디스패치 알고리즘 시작 시점을 캡처한 타임스탬프를 반환합니다. 이 시점은 이벤트 핸들러가 실행되기 직전입니다.
processingEnd
processingEnd 속성의 getter는 이벤트 디스패치 알고리즘 종료 시점을 캡처한 타임스탬프를 반환합니다. 이 시점은 이벤트 핸들러 실행이 완료된 직후입니다. 해당 핸들러가 없으면 processingStart와 동일합니다.
cancelable
cancelable 속성의 getter는 연결된 이벤트cancelable 속성 값을 반환합니다.
target
target 속성의 getter는 연결된 이벤트의 마지막 target을 반환합니다. 이 Node가 분리되거나 섀도우 DOM에 속하지 않는 경우에만 해당합니다.
interactionId
interactionId 속성의 getter는 연결된 이벤트를 트리거한 사용자 Interaction을 고유하게 식별하는 숫자를 반환합니다. 이 속성은 연결된 이벤트type 속성 값이 다음 중 하나일 때만 0이 아닙니다:

2.2. EventCounts 인터페이스

[Exposed=Window]
interface EventCounts {
    readonly maplike<DOMString, unsigned long long>;
};

EventCounts 객체는 키가 이벤트 type이고 값이 해당 type의 이벤트가 디스패치된 횟수인 맵입니다. typePerformanceEventTiming 엔트리(섹션 § 1.4 노출되는 이벤트 참고)에서 지원되는 이벤트만 이 맵을 통해 집계됩니다.

2.3. Performance 인터페이스 확장

[Exposed=Window]
partial interface Performance {
    [SameObject] readonly attribute EventCounts eventCounts;
    readonly attribute unsigned long long interactionCount;
};

eventCounts 속성의 getter는 this관련 글로벌 객체eventCounts를 반환합니다.

interactionCount 속성의 getter는 this관련 글로벌 객체interactionCount를 반환합니다.

3. 처리 모델

3.1. DOM 명세 수정 사항

이 섹션은 [DOM]이 수정된 후 삭제될 예정입니다.

이벤트 디스패치 알고리즘을 다음과 같이 수정합니다.

1단계 바로 뒤에 다음 단계를 추가합니다:

  1. interactionIdcompute-interactionId 알고리즘을 event로 실행한 결과로 한다.

  2. timingEntryinitialize-event-timing 알고리즘을 event, 현재 고해상도 시각, interactionId로 실행한 결과로 한다.

해당 알고리즘의 반환 단계 바로 앞에 다음 단계를 추가합니다:

  1. finalize-event-timing 알고리즘을 timingEntry, event, target, 현재 고해상도 시각을 인자로 실행한다.

참고: 사용자 에이전트가 이벤트 디스패치 알고리즘을 생략하는 경우에도 해당 Event에 대한 엔트리를 포함할 수 있습니다. 이 경우 processingStart 값을 추정하고 processingEnd를 그 값과 동일하게 설정합니다.

3.2. HTML 명세 수정 사항

이 섹션은 [HTML]이 수정된 후 삭제될 예정입니다.

Window 는 다음과 같은 개념을 가집니다:

update-the-rendering 단계에서 이벤트 루프 처리 모델mark-paint-timing 호출 단계 바로 뒤에 다음 단계를 추가합니다:
  1. 완전히 활성화된 Document 에 대해 docs에서 dispatch-pending-event-timing-entries 알고리즘을 실행한다.

3.3. 성능 타임라인 명세 수정 사항

이 섹션은 [PERFORMANCE-TIMELINE-2]가 수정된 후 삭제될 예정입니다.

PerformanceObserverInit 딕셔너리에 다음 멤버가 추가됩니다:

partial dictionary PerformanceObserverInit {
    DOMHighResTimeStamp durationThreshold;
};

3.4. PerformanceEventTiming 추가 여부 결정

참고: 다음 알고리즘은 [PERFORMANCE-TIMELINE-2] 명세에서 PerformanceEventTiming 엔트리를 PerformanceObserver 버퍼 또는 성능 타임라인에 추가할지(상세 내용은 레지스트리 참고) 판단하는 데 사용됩니다.

PerformanceEventTiming entryPerformanceObserverInit options가 주어졌을 때, PerformanceEventTiming 추가 여부를 결정하려면 entry 및 필요에 따라 options를 인자로 다음 단계를 실행한다:
  1. entryentryType 속성 값이 "first-input"이면 true를 반환한다.

  2. entryentryType 속성 값이 "event"임을 단언한다.

  3. minDuration을 다음과 같이 계산한다:

    1. options가 없거나 optionsdurationThreshold 가 없으면 minDuration을 104로 한다.

    2. 그 외에는 minDuration을 16과 optionsdurationThreshold 값 중 큰 값으로 한다.

  4. entryduration 값이 minDuration 이상이면 true를 반환한다.

  5. 그렇지 않으면 false를 반환한다.

3.5. 상호작용 수 증가

Window window 객체에 대해 상호작용 수 증가를 요청받으면 다음 단계를 수행한다:
  1. windowuser interaction value 값을 사용자 에이전트가 선택한 작은 값만큼 증가시킨다.

  2. interactionCountwindowinteractionCount 값으로 한다.

  3. interactionCountinteractionCount + 1로 설정한다.

참고: user interaction value 값은 1이 아닌 사용자 에이전트가 선택한 작은 값만큼 증가시키며, 이는 개발자가 웹 애플리케이션의 실제 상호작용 횟수 카운터로 오용하지 않도록 하기 위함이다. 이 방식은 사용자 에이전트가 user interaction value를 미리 할당(예: pointerdown 시점)하고, 이후(pointercancel 등)에는 폐기할 수 있게 한다.

사용자 에이전트는 매번 작은 랜덤 정수로 증가시키거나, 상수를 사용할 수 있다. 모든 Window에 대해 공유된 글로벌 user interaction value를 사용해서는 안 되며, 이는 크로스 오리진 정보 누출을 일으킬 수 있기 때문이다.

3.6. interactionId 계산

event를 입력값으로 interactionId 계산을 요청받으면 다음 단계를 실행한다:
  1. eventisTrusted 속성이 false이면 0을 반환한다.

  2. typeeventtype 속성값으로 한다.

  3. typekeyup, compositionstart, input, pointercancel, pointerup, click, contextmenu 중 하나가 아니라면 0을 반환한다.

    참고: keydownpointerdownfinalize event timing 단계에서 pending으로 처리되며, 이후 interactionId 계산 알고리즘에서 처리된다 (keyup, pointerup 등).

  4. windowevent관련 글로벌 객체로 한다.

  5. pendingKeyDownswindowpending key downs로 한다.

  6. pointerMapwindowpointer interaction value map으로 한다.

  7. pendingPointerDownswindowpending pointer downs로 한다.

  8. typekeyup인 경우:

    1. eventisComposing 값이 true이면 0을 반환한다.

    2. codeeventkeyCode 값으로 한다.

    3. pendingKeyDowns[code]가 없으면 0을 반환한다.

    4. entrypendingKeyDowns[code]로 한다.

    5. 상호작용 수 증가window에 대해 실행한다.

    6. interactionIdwindowuser interaction value 값으로 한다.

    7. entryinteractionIdinteractionId로 설정한다.

    8. entrywindowentries to be queued에 추가한다.

    9. Remove pendingKeyDowns[code]을 실행한다.

    10. interactionId를 반환한다.

  9. typecompositionstart인 경우:

    1. pendingKeyDowns값들에 대해 각 entry마다:

      1. entrywindowentries to be queued에 추가한다.

    2. Clear pendingKeyDowns를 실행한다.

    3. 0을 반환한다.

  10. typeinput인 경우:

    1. eventInputEvent 인스턴스가 아니면 0을 반환한다. 참고: 이 검사는 Eventinput 타입이지만 실제 텍스트 변경과 관련 없는 이벤트를 제외하기 위한 것이다.

    2. eventisComposing 값이 false이면 0을 반환한다.

    3. 상호작용 수 증가window에 대해 실행한다.

    4. windowuser interaction value 값을 반환한다.

  11. 그 외 (typepointercancel, pointerup, click, contextmenu인 경우):

    1. pointerIdeventpointerId 값으로 한다.

    2. typeclick인 경우:

      1. pointerMap[pointerId]가 없으면 0을 반환한다.

      2. valuepointerMap[pointerId]로 한다.

      3. pointerMap[pointerId]를 제거한다.

      4. value를 반환한다.

    3. typepointerup, pointercancel, contextmenu 임을 단언한다.

    4. pendingPointerDowns[pointerId]가 없으면:

      1. typecontextmenu인 경우, windowuser interaction value를 반환한다.

      2. typepointerup 이고 windowis contextmenu triggered 플래그가 true이면:

        1. windowis contextmenu triggered 플래그를 false로 설정한다.

        2. windowuser interaction value를 반환한다.

      3. 그 외에는 0을 반환한다.

    5. pointerDownEntrypendingPointerDowns[pointerId]로 한다.

    6. pointerDownEntryPerformanceEventTiming 엔트리임을 단언한다.

    7. typepointerup 또는 contextmenu인 경우:

      1. 상호작용 수 증가window에 대해 실행한다.

      2. pointerMap[pointerId]를 windowuser interaction value 값으로 설정한다.

      3. pointerDownEntryinteractionIdpointerMap[pointerId] 값으로 설정한다.

    8. pointerDownEntrywindowentries to be queued에 추가한다.

    9. pendingPointerDowns[pointerId]를 제거한다.

    10. typecontextmenu이면, windowis contextmenu triggered를 true로 설정한다.

    11. typepointercancel이면, 0을 반환한다.

    12. pointerMap[pointerId] 값을 반환한다.

참고: 이 알고리즘은 이벤트를 적절한 interactionId에 할당하려고 시도한다. 키보드 이벤트의 경우 keydown 은 새로운 interactionId를 트리거하고, keyup 은 반드시 이전 keydown과 일치하는 ID를 가져야 한다. 포인터 이벤트의 경우 pointerdown 이후 pointercancel 또는 pointerup이 발생해야 interactionId를 알 수 있다. click 은 이전 pointerdown의 interactionId와 매칭된다. pointercancel 또는 pointerup이 발생하면, 저장된 pointerdown 엔트리에 interactionId를 설정할 수 있다. pointercancel 이면 pointerdown에 interactionId를 할당하지 않는다. pointerup이면, 새로운 interactionId를 계산해 pointerdownpointerup에 설정하고, 이후 click에도 적용한다.

3.7. 이벤트 타이밍 초기화

이벤트 타이밍 초기화event, processingStart, interactionId로 요청받으면 다음 단계를 실행한다:
  1. event이벤트 타이밍 대상이 아니라면 null을 반환한다.

  2. timingEntryevent관련 realm에서 새 PerformanceEventTiming 객체로 생성한다.

  3. timingEntrynameeventtype 값으로 설정한다.

  4. timingEntryentryType 을 "event"로 설정한다.

  5. timingEntrystartTimeeventtimeStamp 값으로 설정한다.

  6. timingEntryprocessingStartprocessingStart로 설정한다.

  7. timingEntrycancelableeventcancelable 값으로 설정한다.

  8. timingEntryinteractionIdinteractionId로 설정한다.

  9. timingEntry를 반환한다.

3.8. 이벤트 타이밍 종료

이벤트 타이밍 종료timingEntry, event, target, processingEnd로 요청받으면 다음 단계를 실행한다:
  1. timingEntry가 null이면 반환한다.

  2. relevantGlobaltarget관련 글로벌 객체로 한다.

  3. relevantGlobalWindow구현하지 않으면 반환한다.

  4. timingEntryprocessingEndprocessingEnd로 설정한다.

  5. targetNode를 구현함을 단언한다.

    참고: 이 단언은 Event Timing API가 지원하는 이벤트 타입 때문에 항상 성립한다.

  6. timingEntry의 연결된 eventTargettarget으로 설정한다.

    참고: 마지막 이벤트 타겟으로 설정된다. eventTarget재타겟팅이 발생하면 루트에 가장 가까운, 마지막 타겟이 사용된다.

  7. eventtype 값이 pointerdown인 경우:

    1. pendingPointerDownsrelevantGlobalpending pointer downs로 한다.

    2. pointerIdeventpointerId로 한다.

    3. pendingPointerDowns[pointerId]가 존재하면:

      1. previousPointerDownEntrypendingPointerDowns[pointerId]로 한다.

      2. previousPointerDownEntryrelevantGlobalentries to be queued에 추가한다.

    4. pendingPointerDowns[pointerId]를 timingEntry로 설정한다.

    5. windowis contextmenu triggered를 false로 설정한다.

  8. 그 외, eventtype 값이 keydown인 경우:

    1. eventisComposing 값이 true라면:

      1. timingEntryrelevantGlobalentries to be queued에 추가한다.

      2. 반환한다.

    2. pendingKeyDownsrelevantGlobalpending key downs로 한다.

    3. codeeventkeyCode 값으로 한다.

    4. pendingKeyDowns[code]가 존재하면:

      1. previousKeyDownEntrypendingKeyDowns[code]로 한다.

      2. code가 229가 아니면:

        1. windowuser interaction value 값을 사용자 에이전트가 선택한 작은 값만큼 증가시킨다.

        2. previousKeyDownEntryinteractionIdwindowuser interaction value로 설정한다.

        참고: 229는 IME 키보드 이벤트로, 사용자 에이전트가 여러 개의 이벤트를 보낼 수 있으며 이는 키를 반복적으로 누르는 것과는 다르다.

      3. previousKeyDownEntrywindowentries to be queued에 추가한다.

    5. pendingKeyDowns[code]를 timingEntry로 설정한다.

  9. 그 외:

    1. timingEntryrelevantGlobalentries to be queued에 추가한다.

3.9. 대기 중인 이벤트 타이밍 엔트리 디스패치

대기 중인 이벤트 타이밍 엔트리 디스패치Document doc에 대해 요청받으면 다음 단계를 실행한다:
  1. windowdoc관련 글로벌 객체로 한다.

  2. renderingTimestamp현재 고해상도 시각으로 한다.

  3. windowentries to be queued의 각 timingEntry에 대해:

    1. 이벤트 타이밍 엔트리 duration 설정timingEntry, window, renderingTimestamp로 실행한다.

    2. timingEntryduration 값이 16 이상이면 queue timingEntry를 실행한다.

  4. windowentries to be queued를 비운다.

  5. windowpending pointer downs값들에 대해 각 pendingPointerDownEntry마다:

    1. 이벤트 타이밍 엔트리 duration 설정pendingPointerDownEntry, window, renderingTimestamp로 실행한다.

  6. windowpending key downs값들에 대해 각 pendingKeyDownEntry마다:

    1. 이벤트 타이밍 엔트리 duration 설정pendingKeyDownEntry, window, renderingTimestamp로 실행한다.

이벤트 타이밍 엔트리 duration 설정PerformanceEventTiming timingEntry, Window window, DOMHighResTimeStamp renderingTimestamp로 요청받으면 다음 단계를 실행한다:
  1. timingEntryduration 값이 0이 아니면 반환한다.

  2. starttimingEntrystartTime 값으로 한다.

  3. timingEntrydurationDOMHighResTimeStamp 타입의 renderingTimestamp - start 계산 결과(정밀도 8ms 이하)로 설정한다.

  4. nametimingEntryname 값으로 한다.

  5. 이벤트 카운트 업데이트를 위해 다음 단계를 실행한다:

    1. eventCountswindoweventCounts로 한다.

    2. eventCountsname을 포함함을 단언한다.

    3. Set eventCounts[name]를 eventCounts[name] + 1로 설정한다.

  6. windowhas dispatched input event가 false이고 timingEntryinteractionId 가 0이 아니면 다음 단계를 실행한다:

    1. firstInputEntrytimingEntry의 복사본으로 한다.

    2. firstInputEntryentryType 을 "first-input"로 설정한다.

    3. queue firstInputEntry를 실행한다.

    4. windowhas dispatched input event를 true로 설정한다.

4. 보안 및 개인정보 보호 고려사항

고해상도 타이머는 보안상 우려가 있기 때문에 웹 플랫폼에 더 많은 고해상도 타이머를 도입하고 싶지는 않습니다. 이벤트 핸들러 타임스탬프는 performance.now()와 동일한 정밀도를 가집니다. processingStartprocessingEnd 는 이 API를 사용하지 않고도 계산할 수 있으므로, 해당 속성을 노출해도 새로운 공격면이 생기지 않습니다. 따라서 duration 만이 추가적인 고려가 필요합니다.

duration 은 8밀리초 단위로 반올림되어 제공됩니다. 따라서 이 타임스탬프로 고해상도 타이머를 생성할 수 없습니다. 하지만 이 값은 웹 개발자가 쉽게 알 수 없는, 이벤트 처리 후 픽셀이 그려지는 시간이라는 새로운 정보를 제공합니다. 해당 타임스탬프 노출에 대해, 특히 그 정밀도에 비추어 볼 때 보안이나 프라이버시 측면의 우려는 발견되지 않았습니다. 유용한 새로운 정보를 최소한만 노출하기 위해 8밀리초 정밀도를 선택했습니다. 이는 120Hz 디스플레이에서도 비교적 정확한 타이밍 측정을 가능하게 합니다.

duration 의 기본 컷오프 값으로 104ms를 선택한 것은 100ms보다 큰 첫 번째 8의 배수이기 때문입니다. 반올림된 duration이 104ms 이상이면, 반올림 전 duration은 100ms 이상이 됩니다. 이런 이벤트는 100ms 이내에 처리되지 않아 사용자 경험에 부정적인 영향을 줄 가능성이 높습니다.

durationThreshold 의 최소값으로 16ms를 선택한 이유는 일반적인 사용 사례에서 반응이 부드럽게 이루어짐을 보장하기 위함입니다. 120Hz 디스플레이에서는 프레임 하나 이상 건너뛰면 16ms 이상이 되고, 해당 사용자 입력에 대응하는 엔트리는 최소값 기준으로 API에 노출됩니다.

적합성

문서 규칙

적합성 요구사항은 설명적 단언과 RFC 2119 용어의 조합으로 표현됩니다. 본 명세의 규범적 부분에서 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, “OPTIONAL” 등의 키워드는 RFC 2119에서 정의된 대로 해석해야 합니다. 다만 가독성을 위해, 본 명세에서는 이러한 단어들이 모두 대문자로 나타나지 않습니다.

이 명세의 모든 텍스트는 규범적이며, 비규범적으로 표시된 섹션, 예시, 참고를 제외합니다. [RFC2119]

이 명세의 예시는 “예를 들어”라는 말로 시작하거나, class="example" 같이 규범적 텍스트와 구분되어 있습니다:

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

정보성 참고는 “참고”라는 말로 시작하며, class="note" 로 규범적 텍스트와 구분됩니다:

참고, 이것은 정보성 참고입니다.

적합한 알고리즘

알고리즘에서 명령문 형태로 표현된 요구사항 (예: "앞의 공백 문자를 제거한다" 또는 "false를 반환하고 이 단계들을 중단한다") 는 도입부에서 사용된 키워드 ("must", "should", "may" 등) 의 의미에 따라 해석해야 합니다.

알고리즘이나 구체적 단계로 표현된 적합성 요구사항은 최종 결과가 동등하다면 어떤 방식으로든 구현할 수 있습니다. 특히, 본 명세에 정의된 알고리즘은 이해하기 쉽게 설계된 것이며 성능을 염두에 둔 것이 아닙니다. 구현자는 최적화를 권장합니다.

색인

이 명세에서 정의된 용어

참조로 정의된 용어

참고문헌

규범적 참고문헌

[DOM]
Anne van Kesteren. DOM Standard. 현행 표준. URL: https://dom.spec.whatwg.org/
[HR-TIME-2]
Ilya Grigorik. High Resolution Time Level 2. 2019년 11월 21일. REC. URL: https://www.w3.org/TR/hr-time-2/
[HTML]
Anne van Kesteren; et al. HTML Standard. 현행 표준. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. 현행 표준. URL: https://infra.spec.whatwg.org/
[PAINT-TIMING]
Ian Clelland; Noam Rosenthal. Paint Timing. 2025년 5월 17일. WD. URL: https://www.w3.org/TR/paint-timing/
[PERFORMANCE-TIMELINE-2]
Nicolas Pena Moreno. Performance Timeline. 2025년 5월 21일. CRD. URL: https://www.w3.org/TR/performance-timeline/
[POINTEREVENTS]
Jacob Rossi; Matt Brubeck. Pointer Events. 2019년 4월 4일. REC. URL: https://www.w3.org/TR/pointerevents/
[POINTEREVENTS4]
Patrick Lauke; Robert Flack. Pointer Events. 2025년 5월 1일. WD. URL: https://www.w3.org/TR/pointerevents4/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. 1997년 3월. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[TOUCH-EVENTS]
Doug Schepers; et al. Touch Events. 2013년 10월 10일. REC. URL: https://www.w3.org/TR/touch-events/
[UIEVENTS]
Gary Kacmarcik; Travis Leithead. UI Events. 2024년 9월 7일. WD. URL: https://www.w3.org/TR/uievents/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. 현행 표준. URL: https://webidl.spec.whatwg.org/

IDL 색인

[Exposed=Window]
interface PerformanceEventTiming : PerformanceEntry {
    readonly attribute DOMHighResTimeStamp processingStart;
    readonly attribute DOMHighResTimeStamp processingEnd;
    readonly attribute boolean cancelable;
    readonly attribute Node? target;
    readonly attribute unsigned long long interactionId;
    [Default] object toJSON();
};

[Exposed=Window]
interface EventCounts {
    readonly maplike<DOMString, unsigned long long>;
};

[Exposed=Window]
partial interface Performance {
    [SameObject] readonly attribute EventCounts eventCounts;
    readonly attribute unsigned long long interactionCount;
};

partial dictionary PerformanceObserverInit {
    DOMHighResTimeStamp durationThreshold;
};

MDN

EventCounts

Firefox89+SafariNoneChrome85+
Opera?Edge85+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

Performance/eventCounts

Firefox89+SafariNoneChrome85+
Opera?Edge85+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
Node.jsNone
MDN

PerformanceEventTiming/cancelable

Firefox89+SafariNoneChrome76+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceEventTiming/interactionId

In only one current engine.

FirefoxNoneSafariNoneChrome96+
Opera?Edge96+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceEventTiming/processingEnd

Firefox89+SafariNoneChrome76+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceEventTiming/processingStart

Firefox89+SafariNoneChrome76+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceEventTiming/target

Firefox89+SafariNoneChrome85+
Opera?Edge85+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceEventTiming/toJSON

Firefox89+SafariNoneChrome76+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceEventTiming

Firefox89+SafariNoneChrome76+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?