Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add navigationId to PerformanceEntry #192

Merged
merged 11 commits into from
Oct 11, 2023
15 changes: 15 additions & 0 deletions explainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ The entry has the following attributes:
* `entryType`: a string representing the type of performance data being exposed. It is also used to filter entries in the `getEntriesByType()` method and in the `PerformanceObserver`.
* `startTime`: a timestamp representing the starting point for the performance data being recorded. The semantics of this attribute depend on the `entryType`.
* `duration`: a time duration representing the duration of the performance data being recorded. The semantics of this one also depend on the `entryType`.
* `navigationId`: a string identifying the `PerformanceEntry` object corresponding to the last navigation or navigation-like event that had occurred in the document at the time that this `PerformanceEntry` object was recorded.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

integer?


If these sound abstract, it’s because they are.
A specification whose goal is to expose new measurements to web developers will define a new interface which extends `PerformanceEntry`.
Expand Down Expand Up @@ -150,6 +151,20 @@ For entryTypes with a fixed amount of entries, like Paint Timing, using the poll
At the same time, the callback is more useful for cases where there is varying amount of entries of the given entryType, like in Resource Timing.
The web performance monitoring service can process all the performance information while the page is still running and only has to do very minimal work when the data needs to be reported.

## Page lifetime issues
When this API was originally designed, documents had a relatively simple lifecycle: they were
loaded when the user navigated to them, and unloaded when the user navigated away, with the
JavaScript environment being torn down at that time. Since then, the sitution has become more
complex, with many browsers introducing a back-forwards-cache, with which a user can return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: back-forward cache

a document wich they have previously navigated away from. The web has also seen a rise in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/wich/which/

popularity of Single Page Apps, where what appears to the user to be a navigation is actually
just a change in state of a running page. In both of these situations, a navigation (or what
appears to the user as a navigation) can occur without the performance timeline being reset.
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
In order to allow developers to reason about such events during the life of a page, some
PerformanceEntry objects mark navigations, or navigation-like events. All PerformanceEntry
objects include a navigation ID field, which links each PerformanceEntry to the most recent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/links/ties/

navigation entry which had occurred when the entry was generated.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/when/before/ ?


# Standards Status
The Performance Timeline specification is widely approved.
There are differences in what kinds of performance data is exposed on different user agents, but this specification does not concern itself with that, and that is delegated to the new specifications that describe new data.
Expand Down
92 changes: 81 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
</li>
<li>Exposes {{PerformanceEntry}} in Web Workers [[WORKERS]];
</li>
<li>Formalizes support for multiple navigation events over a document's
lifetime.
</li>
<li>Adds support for {{PerformanceObserver}}.
</li>
</ul>
Expand Down Expand Up @@ -244,6 +247,12 @@ <h2><dfn>Performance Timeline</dfn></h2>
</ul>
</li>
</ul>
<p>Each <a>Document</a> has:</p>
<ul>
<li>a <dfn>most recent navigation</dfn>, which is a <a>PerformanceEntry</a>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/a/A/

initially unset.
</li>
</ul>
<p>In order to get the <dfn>relevant performance entry tuple</dfn>, given
<var>entryType</var> and <var>globalObject</var> as input, run the
following steps:</p>
Expand Down Expand Up @@ -305,6 +314,7 @@ <h2>The <dfn>PerformanceEntry</dfn> interface</h2>
readonly attribute DOMString entryType;
readonly attribute DOMHighResTimeStamp startTime;
readonly attribute DOMHighResTimeStamp duration;
readonly attribute DOMString navigationId;
[Default] object toJSON();
};</pre>
<dl>
Expand Down Expand Up @@ -341,6 +351,10 @@ <h2>The <dfn>PerformanceEntry</dfn> interface</h2>
duration concept doesn't apply, a performance metric may choose to
return a `duration` of <code>0</code>.
</dd>
<dt><dfn>navigationId</dfn></dt>
<dd>
This attribute MUST return the value of <a>this</a>'s <a>navigationId</a>.
</dd>
</dl>
<p>When <dfn>toJSON</dfn> is called, run [[WebIDL]]'s <a>default toJSON
steps</a>.</p>
Expand Down Expand Up @@ -584,6 +598,7 @@ <h2><dfn>PerformanceObserverEntryList</dfn> interface</h2>
PerformanceEntryList getEntries();
PerformanceEntryList getEntriesByType (DOMString type);
PerformanceEntryList getEntriesByName (DOMString name, optional DOMString type);
PerformanceEntryList getEntriesByNavigationId (DOMString navigationId);
};
</pre>
<p>Each {{PerformanceObserverEntryList}} object has an associated
Expand All @@ -592,24 +607,34 @@ <h2><dfn>PerformanceObserverEntryList</dfn> interface</h2>
<section>
<h2><dfn>getEntries()</dfn> method</h2>
<p>Returns a <a>PerformanceEntryList</a> object returned by <a>filter
buffer by name and type</a> algorithm with <a>this</a>'s <a>entry list</a>,
<var>name</var> and <var>type</var> set to <code>null</code>.</p>
buffer</a> algorithm with <a>this</a>'s <a>entry list</a>,
<var>name</var>, <var>type</var> and <var>navigationId</var> set to
<code>null</code>.</p>
</section>
<section>
<h2><dfn>getEntriesByType()</dfn> method</h2>
<p>Returns a <a>PerformanceEntryList</a> object returned by <a>filter
buffer by name and type</a> algorithm with <a>this</a>'s <a>entry list</a>,
<var>name</var> set to <code>null</code>, and <var>type</var> set to the
method's input <code>type</code> parameter.</p>
buffer</a> algorithm with <a>this</a>'s <a>entry list</a>,
<var>name</var> set to <code>null</code>, <var>type</var> set to the
method's input <code>type</code> parameter, and <var>navigationId</var>
set to <code>null</code>.</p>
</section>
<section>
<h2><dfn>getEntriesByName()</dfn> method</h2>
<p>Returns a <a>PerformanceEntryList</a> object returned by <a>filter
buffer by name and type</a> algorithm with <a>this</a>'s <a>entry list</a>,
buffer</a> algorithm with <a>this</a>'s <a>entry list</a>,
<var>name</var> set to the method input <code>name</code> parameter, and
<var>type</var> set to <code>null</code> if optional `entryType` is omitted,
or set to the method's input <code>type</code> parameter otherwise.</p>
</section>
<section>
<h2><dfn>getEntriesByNavigationId()</dfn> method</h2>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we discuss creation of a dedicated filter, compared to applying a JS filter on getEntries()? I'm guessing this would have performance benefits, but it's worthwhile to articulate the reasoning (maybe in the explainer)

<p>Returns a <a>PerformanceEntryList</a> object returned by <a>filter
buffer</a> algorithm with <a>this</a>'s <a>entry list</a>,
<var>name</var> and <var>type</var> set to <code>null</code>, and
<var>navigationId</var> set to the method's input
<code>navigationId</code> parameter.</p>
</section>
</section>
</section>
<section>
Expand Down Expand Up @@ -669,6 +694,15 @@ <h2>Queue a <code>PerformanceEntry</code></h2>
<li>Let <var>relevantGlobal</var> be <var>newEntry</var>'s <a>relevant
global object</a>.
</li>
<li>If <var>relevantGlobal</var> has an [=associated document=]:
<ol>
<li>Set <var>newEntry</var>'s <a
data-lt="PerformanceEntry.navigationId">navigationId</a> to the value of
<var>relevantGlobal</var>'s [=associated document=]'s [=most recent navigation=]'s {{PerformanceEntry/navigationId}}.</li>
</ol>
</li>
<li>Otherwise, set <var>newEntry</var>'s <a
data-lt="PerformanceEntry.navigationId">navigationId</a> to null.</li>
<li>For each <a>registered performance observer</a> <var>regObs</var> in
<var>relevantGlobal</var>'s <a>list of registered performance observer objects</a>:
<ol>
Expand Down Expand Up @@ -714,6 +748,24 @@ <h2>Queue a <code>PerformanceEntry</code></h2>
</li>
</ol>
</section>
<section data-link-for="PerformanceObserver">
<h2>Queue a navigation <code>PerformanceEntry</code></h2>
<p>To <dfn class="export">queue a navigation PerformanceEntry</dfn> (<var>newEntry</var>), run
these steps:</p>
<ol>
<li>Let <var>navigationId</var> be the result of running <a>generate a navigationId</a> for <var>newEntry</var>.</li>
<li>Let <var>relevantGlobal</var> be <var>newEntry</var>'s <a>relevant
global object</a>.
</li>
<li>Set <var>newEntry</var>'s {{PerformanceEntry/navigationId}} to <var>navigationId</var>.</li>
<li>If <var>relevantGlobal</var> has an [=associated document=]:
<ol>
<li>Set <var>relevantGlobal</var>'s [=associated document=]'s [=most recent navigation=] to <var>newEntry</var>.</li>
</ol>
</li>
<li><a>Queue a PerformanceEntry</a> with <var>newEntry</var> as input.</li>
</ol>
</section>
<section data-link-for="PerformanceObserver">
<h2>Queue the PerformanceObserver task</h2>
<p>When asked to <dfn>queue the PerformanceObserver task</dfn>, given
Expand Down Expand Up @@ -826,7 +878,7 @@ <h2>Filter buffer map by name and type</h2>
continue to the next <var>tuple</var>.
</li>
<li>Let <var>entries</var> be the result of running <a>filter
buffer by name and type</a> with <var>buffer</var>, <var>name</var>
buffer</a> with <var>buffer</var>, <var>name</var>
and <var>type</var> as inputs.
</li>
<li>For each <var>entry</var> in <var>entries</var>,
Expand All @@ -840,10 +892,10 @@ <h2>Filter buffer map by name and type</h2>
</ol>
</section>
<section data-link-for="PerformanceEntry">
<h2>Filter buffer by name and type</h2>
<p>When asked to run the <dfn>filter buffer by name and type</dfn>
algorithm, with <var>buffer</var>, <var>name</var>, and <var>type</var>
as inputs, run the following steps:</p>
<h2>Filter buffer</h2>
<p>When asked to run the <dfn>filter buffer</dfn>
algorithm, with <var>buffer</var>, <var>name</var>, <var>type</var>, and
<var>navigationId</var> as inputs, run the following steps:</p>
<ol>
<li>Let <var>result</var> be an initially empty <a>list</a>.
</li>
Expand All @@ -858,6 +910,12 @@ <h2>Filter buffer by name and type</h2>
<a data-cite=INFRA#string-is>identical to</a> <var>entry</var>'s
<code>name</code> attribute, continue to next <var>entry</var>.
</li>
<li>If <var>navigationId</var> is not null and if
<var>navigationId</var> is not
<a data-cite=INFRA#string-is>identical to</a> <var>entry</var>'s
<code>navigationId</code> attribute, continue to next
<var>entry</var>.
</li>
<li>[=list/append=] <var>entry</var> to <var>result</var>.</li>
</ol>
</li>
Expand All @@ -883,6 +941,18 @@ <h2>Determine if a performance entry buffer is full</h2>
<li>Return true.</li>
</ol>
</section>
<section data-link-for="PerformanceEntry">
<h2>Generate a navigationId</h2>
<p>When asked to <dfn class="export">generate a navigationId</dfn> for a
<a>PerformanceEntry</a> <var>entry</a>, run the following steps:</p>
<ol>
<li>Let <var>type</var> be <var>entry</var>'s <a>entryType</a>.</li>
<li>Let <var>startTime</var> be <var>entry</var>'s <a>startTime</a>.
</li>
<li>Return the [=string/concatenation=] of « <var>type</var>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we initially talked about a UUID, but this may be equivalent. A few things that may hamper that:

  • Can we have multiple navigations per ms? Unlikely, but I'm not sure it's currently prohibited.
  • Having a predictable structure may cause developers to create weird reliances on the ID's structure (but I'm not sure that would be the case)
    WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • We can't have two navigations at the same monotonic time.
  • I think it's OK that people rely on this if it's well defined. It's only the URL and the start time which are anyway available.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense!

FWIW, when working on resource timing initiator (which uses a similar ID concept), we got feedback that it might be more ergonomic to use an object rather than a concatenated string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we run into problems with object equality, if we use a structured object? Specifically, are there issues around key order or numeric precision, or just the fact that object equality is often conflated with object identity?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed at the WG; there probably are issues with identity/equality here, and a string has better comparison behaviour, as long as we ensure it is unique.
Sticking with - will allow this pattern to be reused for other cases where we want to refer to timeline entries as well.

<var>time</var> » using U+002D HYPHEN-MINUS.</li>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

concatenation is defined for a list of strings, so I think you need to convert the timestamp to a string first.

</ol>
</section>
</section>
<section id="privacy">
<h3>Privacy Considerations</h3>
Expand Down
Loading