Performance Timeline (original) (raw)
Abstract
This specification extends the High Resolution Time specification [HR-TIME-3] by providing methods to store and retrieve high resolution performance metric data.
Status of This Document
This section describes the status of this document at the time of its publication. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.
This Performance Timeline specification replaces the first version of [PERFORMANCE-TIMELINE] and includes:
- Extends the base definition of the Performance interface defined by [HR-TIME-3];
- Exposes PerformanceEntry in Web Workers [WORKERS];
- Formalizes support for multiple navigation events over a document's lifetime.
- Adds support for PerformanceObserver.
This document was published by the Web Performance Working Group as a Candidate Recommendation Draft using theRecommendation track.
Publication as a Candidate Recommendation does not imply endorsement by W3C and its Members. A Candidate Recommendation Draft integrates changes from the previous Candidate Recommendation that the Working Group intends to include in a subsequent Candidate Recommendation Snapshot.
This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.
This document was produced by a group operating under theW3C Patent Policy.W3C maintains apublic list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes containsEssential Claim(s) must disclose the information in accordance withsection 6 of the W3C Patent Policy.
This document is governed by the03 November 2023 W3C Process Document.
Table of Contents
- Abstract
- Status of This Document
- 1. Introduction
- 2. Conformance
- 3. Performance Timeline
- 4. The PerformanceEntry interface
- 5. The PerformanceObserver interface
- 6. Processing
- 7. Privacy Considerations
- 8. Security Considerations
- 9. Dependencies
- A. IDL Index
- B. Acknowledgments
- C. References
- C.1 Normative references
- C.2 Informative references
This section is non-normative.
Accurately measuring performance characteristics of web applications is an important aspect of making web applications faster. This specification defines the necessary Performance Timeline primitives that enable web developers to access, instrument, and retrieve various performance metrics from the full lifecycle of a web application.
[NAVIGATION-TIMING-2], [RESOURCE-TIMING-2], and [USER-TIMING-2] are examples of specifications that define timing information related to the navigation of the document, resources on the page, and developer scripts, respectively. Together these and other performance interfaces define performance metrics that describe the Performance Timeline of a web application. For example, the following script shows how a developer can access the Performance Timeline to obtain performance metrics related to the navigation of the document, resources on the page, and developer scripts:
<!doctype html>
<html>
<head></head>
<body onload="init()">
<img id="image0" src="https://www.w3.org/Icons/w3c_main.png" />
<script>
function init() {
// see [[USER-TIMING-2]]
performance.mark("startWork");
doWork(); // Some developer code
performance.mark("endWork");
measurePerf();
}
function measurePerf() {
performance
.getEntries()
.map(entry => JSON.stringify(entry, null, 2))
.forEach(json => console.log(json));
}
</script>
</body>
</html>
Alternatively, the developer can observe the Performance Timeline and be notified of new performance metrics and, optionally, previously buffered performance metrics of specified type, via thePerformanceObserver interface.
The PerformanceObserver interface was added and is designed to address limitations of the buffer-based approach shown in the first example. By using the PerformanceObserver interface, the application can:
- Avoid polling the timeline to detect new metrics
- Eliminate costly deduplication logic to identify new metrics
- Eliminate race conditions with other consumers that may want to manipulate the buffer
The developer is encouraged to use PerformanceObserver where possible. Further, new performance API's and metrics may only be available through the PerformanceObserver interface. The observer works by specifying a callback in the constructor and specifying the performance entries it's interested in via the observe() method. The user agent chooses when to execute the callback, which receives performance entries that have been queued.
There are special considerations regarding initial page load when using the PerformanceObserver interface: a registration must be active to receive events but the registration script may not be available or may not be desired in the critical path. To address this, user agents buffer some number of events while the page is being constructed, and these buffered events can be accessed via the buffered flag when registering the observer. When this flag is set, the user agent retrieves and dispatches events that it has buffered, for the specified entry type, and delivers them in the first callback after the observe() call occurs.
Note
The number of buffered events is determined by the specification that defines the metric and buffering is intended to used for first-N events only; buffering is not unbounded or continuous.
<!doctype html>
<html>
<head></head>
<body>
<img id="image0" src="https://www.w3.org/Icons/w3c_main.png" />
<script>
// Know when the entry types we would like to use are not supported.
function detectSupport(entryTypes) {
for (const entryType of entryTypes) {
if (!PerformanceObserver.supportedEntryTypes.includes(entryType)) {
// Indicate to client-side analytics that |entryType| is not supported.
}
}
}
detectSupport(["resource", "mark", "measure"]);
const userTimingObserver = new PerformanceObserver(list => {
list
.getEntries()
// Get the values we are interested in
.map(({ name, entryType, startTime, duration }) => {
const obj = {
"Duration": duration,
"Entry Type": entryType,
"Name": name,
"Start Time": startTime,
};
return JSON.stringify(obj, null, 2);
})
// Display them to the console.
.forEach(console.log);
// Disconnect after processing the events.
userTimingObserver.disconnect();
});
// Subscribe to new events for User-Timing.
userTimingObserver.observe({entryTypes: ["mark", "measure"]});
const resourceObserver = new PerformanceObserver(list => {
list
.getEntries()
// Get the values we are interested in
.map(({ name, startTime, fetchStart, responseStart, responseEnd }) => {
const obj = {
"Name": name,
"Start Time": startTime,
"Fetch Start": fetchStart,
"Response Start": responseStart,
"Response End": responseEnd,
};
return JSON.stringify(obj, null, 2);
})
// Display them to the console.
.forEach(console.log);
// Disconnect after processing the events.
resourceObserver.disconnect();
});
// Retrieve buffered events and subscribe to newer events for Resource Timing.
resourceObserver.observe({type: "resource", buffered: true});
</script>
</body>
</html>
As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.
The key words MUST, MUST NOT, and SHOULD in this document are to be interpreted as described inBCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
Conformance requirements phrased as algorithms or specific steps may be implemented in any manner, so long as the end result is equivalent. (In particular, the algorithms defined in this specification are intended to be easy to follow, and not intended to be performant).
Each global object has:
- a performance observer task queued flag
- a list of registered performance observer objects that is initially empty
- a performance entry buffer map map, keyed on a
DOMString
, representing the entry type to which the buffer belongs. The map's value is the following tuple:- A performance entry buffer to storePerformanceEntry objects, that is initially empty.
- An integer maxBufferSize, initialized to the registry value for this entry type.
- A
boolean
availableFromTimeline, initialized to the registry value for this entry type. - An integer dropped entries count that is initially 0.
- An integer last performance entry id that is initially set to a random integer between 100 and 10000.
Each Document has:
- A most recent navigation, which is a PerformanceEntry, initially unset.
In order to get the relevant performance entry tuple, givenentryType and globalObject as input, run the following steps:
- Let map be the performance entry buffer map associated with globalObject.
- Return the result of getting the value of an entry from map, given entryType as the key.
This extends the Performance interface from [HR-TIME-3] and hosts performance related attributes and methods used to retrieve the performance metric data from the Performance Timeline.
partial interface Performance {
PerformanceEntryList getEntries ();
PerformanceEntryList getEntriesByType (DOMString type);
PerformanceEntryList getEntriesByName (DOMString name, optional DOMString type);
};
typedef sequence<PerformanceEntry> PerformanceEntryList;
The PerformanceEntryList
represents a sequence ofPerformanceEntry, providing developers with all the convenience methods found on JavaScript arrays.
Returns a PerformanceEntryList object returned by thefilter buffer map by name and type algorithm withname and type set to null
.
Returns a PerformanceEntryList object returned by filter buffer map by name and type algorithm with name set tonull
, and type set to the method's inputtype
parameter.
Returns a PerformanceEntryList object returned by filter buffer map by name and type algorithm with name set to the method input name
parameter, and type set to null
if optional entryType
is omitted, or set to the method's input type
parameter otherwise.
The PerformanceEntry interface hosts the performance data of various metrics.
[Exposed=(Window,Worker)]
interface PerformanceEntry {
readonly attribute unsigned long long id;
readonly attribute DOMString name;
readonly attribute DOMString entryType;
readonly attribute DOMHighResTimeStamp startTime;
readonly attribute DOMHighResTimeStamp duration;
readonly attribute unsigned long long navigationId;
[Default] object toJSON();
};
name
This attribute must return the value it is initialized to. It represents an identifier for this PerformanceEntry object. This identifier does not have to be unique.
entryType
This attribute must return the value it is initialized to.
Note
All entryType
values are defined in the relevantregistry. Examples include: "mark"
and "measure"
[USER-TIMING-2], "navigation"
[NAVIGATION-TIMING-2], and "resource"
[RESOURCE-TIMING-2].
startTime
This attribute must return the value it is initialized to. It represents the time value of the first recorded timestamp of this performance metric.
duration
The getter steps for the duration attribute are to return 0 if this's end time is 0; otherwise this's end time - this's startTime.
navigationId
This attribute MUST return the value it is initialized to.
When toJSON
is called, run [WebIDL]'s default toJSON steps.
A PerformanceEntry has a DOMHighResTimeStamp end time, initially 0.
To initialize a PerformanceEntry entry given a DOMHighResTimeStamp startTime, a DOMString
entryType, a DOMString
name, and an optional DOMHighResTimeStamp endTime (default 0
):
- Assert: entryType is defined in the entry type registry.
- Initialize entry's startTime to startTime.
- Initialize entry's entryType to entryType.
- Initialize entry's name to name.
- Initialize entry's end time to endTime.
The PerformanceObserver interface can be used to observe thePerformance Timeline to be notified of new performance metrics as they are recorded, and optionally buffered performance metrics.
Each PerformanceObserver has these associated concepts:
- A
PerformanceObserverCallback
observer callback set on creation. - A PerformanceEntryList object called the observer buffer that is initially empty.
- A
DOMString
observer type which is initially"undefined"
. - A boolean requires dropped entries which is initially set to false.
The PerformanceObserver(callback)
constructor must create a newPerformanceObserver object with its observer callback set to callback and then return it.
A registered performance observer is a struct consisting of an observer member (a PerformanceObserver object) and an options list member (a list ofPerformanceObserverInit dictionaries).
callback PerformanceObserverCallback = undefined (PerformanceObserverEntryList entries,
PerformanceObserver observer,
optional PerformanceObserverCallbackOptions options = {});
[Exposed=(Window,Worker)]
interface PerformanceObserver {
constructor(PerformanceObserverCallback callback);
undefined observe (optional PerformanceObserverInit options = {});
undefined disconnect ();
PerformanceEntryList takeRecords();
[SameObject] static readonly attribute FrozenArray<DOMString> supportedEntryTypes;
};
Note
To keep the performance overhead to minimum the application ought to only subscribe to event types that it is interested in, and disconnect the observer once it no longer needs to observe the performance data. Filtering by name is not supported, as it would implicitly require a subscription for all event types — this is possible, but discouraged, as it will generate a significant volume of events.
dictionary PerformanceObserverCallbackOptions {
unsigned long long droppedEntriesCount;
};
droppedEntriesCount
An integer representing the dropped entries count for the entry types that the observer is observing when the PerformanceObserver's requires dropped entries is set.
The observe() method instructs the user agent to register the observer and must run these steps:
- Let relevantGlobal be this's relevant global object.
- If options's entryTypes and type members are both omitted, then throw a "TypeError".
- If options's entryTypes is present and any other member is also present, then throw a "TypeError".
- Update or check this's observer type by running these steps:
- If this's observer type is
"undefined"
:- If options's entryTypes member is present, then set this's observer type to
"multiple"
. - If options's type member is present, then set this's observer type to
"single"
.
- If options's entryTypes member is present, then set this's observer type to
- If this's observer type is
"single"
and options's entryTypes member is present, then throw an "InvalidModificationError". - If this's observer type is
"multiple"
and options's type member is present, then throw an "InvalidModificationError".
- If this's observer type is
- Set this's requires dropped entries to true.
- If this's observer type is
"multiple"
, run the following steps:- Let entry types be options'sentryTypes sequence.
- Remove all types from entry types that are not contained in relevantGlobal's frozen array of supported entry types. The user agent SHOULD notify developers if entry types is modified. For example, a console warning listing removed types might be appropriate.
- If the resulting entry types sequence is an empty sequence, abort these steps. The user agent SHOULD notify developers when the steps are aborted to notify that registration has been aborted. For example, a console warning might be appropriate.
- If the list of registered performance observer objects of relevantGlobal contains a registered performance observer whose observer is this, replace its options list with a list containingoptions as its only item.
- Otherwise, create and append a registered performance observer object to the list of registered performance observer objects of relevantGlobal, withobserver set to this and options list set to a list containing options as its only item.
- Otherwise, run the following steps:
- Assert that this's observer type is
"single"
. - If options's type is not contained in therelevantGlobal's frozen array of supported entry types, abort these steps. The user agent SHOULD notify developers when this happens, for instance via a console warning.
- If the list of registered performance observer objects of relevantGlobal contains a registered performance observer obs whose observer is this:
- If obs's options list contains aPerformanceObserverInit item currentOptions whose type is equal to options's type, replace currentOptions with options inobs's options list.
- Otherwise, append options to obs'soptions list.
- Otherwise, create and append a registered performance observer object to the list of registered performance observer objects of relevantGlobal, withobserver set to the this and options list set to a list containing options as its only item.
- If options's buffered flag is set:
- Let tuple be the relevant performance entry tuple of options's type andrelevantGlobal.
- For each entry in tuple'sperformance entry buffer:
1. If should add entry with entry and options as parameters returns true, append entry to theobserver buffer. - Queue the PerformanceObserver task withrelevantGlobal as input.
- Assert that this's observer type is
Note
A PerformanceObserver object needs to always callobserve() with options's entryTypes set OR always callobserve() with options's type set. If one PerformanceObserver calls observe() with entryTypes and also calls observe withtype, then an exception is thrown. This is meant to avoid confusion with how calls would stack. When using entryTypes, no other parameters in PerformanceObserverInit can be used. In addition, multiple observe() calls will override for backwards compatibility and because a single call should suffice in this case. On the other hand, when using type, calls will stack because a single call can only specify one type. Callingobserve() with a repeated type will also override.
dictionary PerformanceObserverInit {
sequence<DOMString> entryTypes;
DOMString type;
boolean buffered;
};
entryTypes
A list of entry types to be observed. If present, the list MUST NOT be empty and all other members MUST NOT be present. Types not recognized by the user agent MUST be ignored.
type
A single entry type to be observed. A type that is not recognized by the user agent MUST be ignored. Other members may be present.
buffered
A flag to indicate whether buffered entries should be queued into observer's buffer.
[Exposed=(Window,Worker)]
interface PerformanceObserverEntryList {
PerformanceEntryList getEntries();
PerformanceEntryList getEntriesByType (DOMString type);
PerformanceEntryList getEntriesByName (DOMString name, optional DOMString type);
};
Each PerformanceObserverEntryList object has an associatedentry list, which consists of a PerformanceEntryList and is initialized upon construction.
Returns a PerformanceEntryList object returned by filter buffer by name and type algorithm with this's entry list,name and type set to null
.
Returns a PerformanceEntryList object returned by filter buffer by name and type algorithm with this's entry list,name set to null
, and type set to the method's input type
parameter.
Returns a PerformanceEntryList object returned by filter buffer by name and type algorithm with this's entry list,name set to the method input name
parameter, andtype set to null
if optional entryType
is omitted, or set to the method's input type
parameter otherwise.
The takeRecords() method must return a copy of this'sobserver buffer, and also empty this's observer buffer.
The disconnect() method must do the following:
- Remove this from the list of registered performance observer objects of relevant global object.
- Empty this's observer buffer.
- Empty this's options list.
Each global object has an associated frozen array of supported entry types, which is initialized to the FrozenArray created from the sequence of strings among the registry that are supported for the global object, in alphabetical order.
When supportedEntryTypes
's attribute getter is called, run the following steps:
- Let globalObject be the environment settings object's global object.
- Return globalObject's frozen array of supported entry types.
Note
This attribute allows web developers to easily know which entry types are supported by the user agent.
To queue a PerformanceEntry (newEntry), run these steps:
- If newEntry's id is unset:
- Let id be the result of running generate an id for newEntry.
- Set newEntry's id to id.
- Let interested observers be an initially empty set ofPerformanceObserver objects.
- Let entryType be newEntry’s entryType value.
- Let relevantGlobal be newEntry's relevant global object.
- If relevantGlobal has an associated document:
- Set newEntry's navigationId to the value ofrelevantGlobal's associated document's most recent navigation's id.
- Otherwise, set newEntry's navigationId to null.
- For each registered performance observer regObs inrelevantGlobal's list of registered performance observer objects:
- If regObs's options list contains aPerformanceObserverInit options whose entryTypes member includesentryType or whose type member equals toentryType:
- If should add entry with newEntry and options returns true, append regObs's observer tointerested observers.
- If regObs's options list contains aPerformanceObserverInit options whose entryTypes member includesentryType or whose type member equals toentryType:
- For each observer in interested observers:
- Append newEntry to observer's observer buffer.
- Let tuple be the relevant performance entry tuple of entryType and relevantGlobal.
- Let isBufferFull be the return value of the determine if a performance entry buffer is full algorithm with tuple as input.
- Let shouldAdd be the result of should add entry with newEntry as input.
- If isBufferFull is false and shouldAdd is true, append newEntry to tuple's performance entry buffer.
- Queue the PerformanceObserver task withrelevantGlobal as input.
To queue a navigation PerformanceEntry (newEntry), run these steps:
- Let id be the result of running generate an id for newEntry.
- Let relevantGlobal be newEntry's relevant global object.
- Set newEntry's id to id.
- Set newEntry's navigationId to id.
- If relevantGlobal has an associated document:
- Set relevantGlobal's associated document's most recent navigation to newEntry.
- Queue a PerformanceEntry with newEntry as input.
When asked to queue the PerformanceObserver task, givenrelevantGlobal as input, run the following steps:
- If relevantGlobal's performance observer task queued flag is set, terminate these steps.
- Set relevantGlobal's performance observer task queued flag.
- Queue a task that consists of running the following substeps. The task source for the queued task is the performance timeline task source.
- Unset performance observer task queued flag ofrelevantGlobal.
- Let notifyList be a copy ofrelevantGlobal's list of registered performance observer objects.
- For each registered performance observer object registeredObserver in notifyList, run these steps:
- Let po be registeredObserver's observer.
- Let entries be a copy of po’sobserver buffer.
- If entries is empty, return.
- Empty po’s observer buffer.
- Let observerEntryList be a newPerformanceObserverEntryList, with its entry list set to entries.
- Let droppedEntriesCount be null.
- If po's requires dropped entries is set, perform the following steps:
1. Set droppedEntriesCount to 0.
2. For each PerformanceObserverInit item inregisteredObserver's options list:
1. For each DOMString entryType that appears either as item's type or in item'sentryTypes:
1. Let map be relevantGlobal's performance entry buffer map.
2. Let tuple be the result of getting the value of entry on map given entryType as key.
3. Increase droppedEntriesCount by tuple'sdropped entries count.
3. Set po's requires dropped entries to false. - Let callbackOptions be a PerformanceObserverCallbackOptions with its droppedEntriesCount set to droppedEntriesCount if droppedEntriesCount is not null, otherwise unset.
- Call po’s observer callback withobserverEntryList as the first argument, with po as the second argument and as callback this value, and withcallbackOptions as the third argument. If this throws an exception, report the exception.
The performance timeline task queue is a low priority queue that, if possible, should be processed by the user agent during idle periods to minimize impact of performance monitoring code.
When asked to run the filter buffer map by name and type algorithm with optional name and type, run the following steps:
- Let result be an initially empty list.
- Let map be the performance entry buffer map associated with the relevant global object of this.
- Let tuple list be an empty list.
- If type is not null, append the result of getting the value of entry on map given type askey to tuple list. Otherwise, assign the result ofget the values onmap to tuple list.
- For each tuple in tuple list, run the following steps:
- Let buffer be tuple's performance entry buffer.
- If tuple's availableFromTimeline is false, continue to the next tuple.
- Let entries be the result of running filter buffer by name and type with buffer, name and type as inputs.
- For each entry in entries,append entry to result.
- Sort results's entries in chronological order with respect to startTime
- Return result.
When asked to run the filter buffer by name and type algorithm, with buffer, name, and type as inputs, run the following steps:
- Let result be an initially empty list.
- For each PerformanceEntry entry inbuffer, run the following steps:
- If type is not null and if type is notidentical to entry's
entryType
attribute, continue to next entry. - If name is not null and if name is notidentical to entry's
name
attribute, continue to next entry. - append entry to result.
- If type is not null and if type is notidentical to entry's
- Sort results's entries in chronological order with respect to startTime
- Return result.
To determine if a performance entry buffer is full, with tuple as input, run the following steps:
- Let num current entries be the size oftuple's performance entry buffer.
- If num current entries is less than tuples'smaxBufferSize, return false.
- Increase tuple's dropped entries count by 1.
- Return true.
When asked to generate an id for aPerformanceEntry entry, run the following steps:
- Let relevantGlobal be entry's relevant global object.
- Increase relevantGlobal's last performance entry id by a small number chosen by the user agent.
- Return relevantGlobal's last performance entry id.
A user agent may choose to increase the last performance entry idit by a small random integer every time. A user agent must not pick a single global random integer and increase the last performance entry id of all global objects by that amount because this could introduce cross origin leaks.
Note
The last performance entry id has an initial random value, and is increased by a small number chosen by the user agent instead of 1 to discourage developers from considering it as a counter of the number of entries that have been generated in the web application.
This specification extends the Performance interface defined by [HR-TIME-3] and provides methods to queue and retrieve entries from the performance timeline. Please refer to [HR-TIME-3] for privacy considerations of exposing high-resoluting timing information. Each new specification introducing new performance entries should have its own privacy considerations as well.
The last performance entry id is deliberately initialized to a random value, and is incremented by another small value every time a newPerformanceEntry is queued. User agents may choose to use a consistent increment for all users, or may pick a different increment for eachglobal object, or may choose a new random increment for eachPerformanceEntry. However, in order to prevent cross-origin leaks, and ensure that this does not enable fingerprinting, user agents must not just pick a unique random integer, and use it as a consistent increment for allPerformanceEntry objects across all global objects.
This specification extends the Performance interface defined by [HR-TIME-3] and provides methods to queue and retrieve entries from the performance timeline. Please refer to [HR-TIME-3] for security considerations of exposing high-resoluting timing information. Each new specification introducing new performance entries should have its own security considerations as well.
The [INFRA] specification defines the following: key, getting the value of an entry.
partial interface Performance {
PerformanceEntryList getEntries ();
PerformanceEntryList getEntriesByType (DOMString type);
PerformanceEntryList getEntriesByName (DOMString name, optional DOMString type);
};
typedef sequence<PerformanceEntry> PerformanceEntryList;
[Exposed=(Window,Worker)]
interface PerformanceEntry {
readonly attribute unsigned long long id;
readonly attribute DOMString name;
readonly attribute DOMString entryType;
readonly attribute DOMHighResTimeStamp startTime;
readonly attribute DOMHighResTimeStamp duration;
readonly attribute unsigned long long navigationId;
[Default] object toJSON();
};
callback PerformanceObserverCallback = undefined (PerformanceObserverEntryList entries,
PerformanceObserver observer,
optional PerformanceObserverCallbackOptions options = {});
[Exposed=(Window,Worker)]
interface PerformanceObserver {
constructor(PerformanceObserverCallback callback);
undefined observe (optional PerformanceObserverInit options = {});
undefined disconnect ();
PerformanceEntryList takeRecords();
[SameObject] static readonly attribute FrozenArray<DOMString> supportedEntryTypes;
};
dictionary PerformanceObserverCallbackOptions {
unsigned long long droppedEntriesCount;
};
dictionary PerformanceObserverInit {
sequence<DOMString> entryTypes;
DOMString type;
boolean buffered;
};
[Exposed=(Window,Worker)]
interface PerformanceObserverEntryList {
PerformanceEntryList getEntries();
PerformanceEntryList getEntriesByType (DOMString type);
PerformanceEntryList getEntriesByName (DOMString name, optional DOMString type);
};
Thanks to Arvind Jain, Boris Zbarsky, Jatinder Mann, Nat Duca, Philippe Le Hegaret, Ryosuke Niwa, Shubhie Panicker, Todd Reifsteck, Yoav Weiss, and Zhiheng Wang, for their contributions to this work.
[dom]
DOM Standard. Anne van Kesteren. WHATWG. Living Standard. URL: https://dom.spec.whatwg.org/
[HR-TIME-3]
High Resolution Time. Yoav Weiss. W3C. 19 July 2023. W3C Working Draft. URL: https://www.w3.org/TR/hr-time-3/
[HTML]
HTML Standard. Anne van Kesteren; Domenic Denicola; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Infra Standard. Anne van Kesteren; Domenic Denicola. WHATWG. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner. IETF. March 1997. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc2119
[RFC8174]
Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. B. Leiba. IETF. May 2017. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc8174
[WebIDL]
Web IDL Standard. Edgar Chen; Timothy Gu. WHATWG. Living Standard. URL: https://webidl.spec.whatwg.org/
[NAVIGATION-TIMING-2]
Navigation Timing Level 2. Yoav Weiss; Noam Rosenthal. W3C. 1 February 2024. W3C Working Draft. URL: https://www.w3.org/TR/navigation-timing-2/
[PERFORMANCE-TIMELINE]
Performance Timeline. Nicolas Pena Moreno. W3C. 2 February 2024. W3C Candidate Recommendation. URL: https://www.w3.org/TR/performance-timeline/
[RESOURCE-TIMING-2]
Resource Timing. Yoav Weiss; Noam Rosenthal. W3C. 4 October 2022. W3C Candidate Recommendation. URL: https://www.w3.org/TR/resource-timing-2/
[USER-TIMING-2]
User Timing Level 2. Ilya Grigorik. W3C. 26 February 2019. W3C Recommendation. URL: https://www.w3.org/TR/user-timing-2/
[WORKERS]
Web Workers. Ian Hickson. W3C. 28 January 2021. W3C Working Group Note. URL: https://www.w3.org/TR/workers/