Skip to content

Commit

Permalink
Inclujde PaintMixin in PerformanceElementTiming (#81)
Browse files Browse the repository at this point in the history
Given w3c/paint-timing#62, we now expose paint timestamps as part
of PaintTimingMixin.

This PR extends PerformanceElementTiming to include that mixin and propagates
the right calls.

Also revise security and privacy section to remove TAO
 * See w3c/paint-timing#104
  • Loading branch information
noamr authored Dec 16, 2024
1 parent a16851f commit 5e609be
Showing 1 changed file with 20 additions and 21 deletions.
41 changes: 20 additions & 21 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ urlPrefix: https://drafts.csswg.org/css-backgrounds-3; spec: CSS-BACKGROUNDS-3;
type: dfn; url: #propdef-background-image; text: background-image;
urlPrefix: https://wicg.github.io/largest-contentful-paint/; spec: LARGEST-CONTENTFUL-PAINT;
type: dfn; url:#potentially-add-a-largestcontentfulpaint-entry; text: potentially add a LargestContentfulPaint entry;
urlPrefix: https://fetch.spec.whatwg.org/; spec: FETCH;
type: dfn; url:#concept-tao-check; text: timing allow check;
urlPrefix: https://w3c.github.io/paint-timing/; spec: PAINT-TIMING;
type: dfn; url:#set-of-owned-text-nodes; text: set of owned text nodes;
type: dfn; url:#process-image-that-finished-loading; text: process image that finished loading;
Expand All @@ -46,7 +44,12 @@ urlPrefix: https://w3c.github.io/paint-timing/; spec: PAINT-TIMING;
type: dfn; for:pending image record; url:#pending-image-record-element; text: element
type: dfn; for:pending image record; url:#pending-image-record-loadtime; text: loadTime
type: dfn; for:pending image record; url:#pending-image-record-request; text: request
type: dfn; url:#paint-timing-info; text: paint timing info
type: dfn; for: paint timing info; text: default paint timestamp;
type: dfn; for:PaintTimingMixin; url:#painttimingmixin-paint-timing-info; text: paint timing info
type: interface; text:PaintTimingMixin; url:#painttimingmixin
</pre>

<pre class=link-defaults>
spec:dom; type:dfn; text:descendant
</pre>
Expand Down Expand Up @@ -125,6 +128,8 @@ interface PerformanceElementTiming : PerformanceEntry {
readonly attribute DOMString url;
[Default] object toJSON();
};

PerformanceElementTiming includes PaintTimingMixin;
</pre>

A {{PerformanceElementTiming}} object reports timing information about one associated element.
Expand All @@ -143,7 +148,7 @@ The {{PerformanceEntry/startTime}} attribute's getter must return the value of <

The {{PerformanceEntry/duration}} attribute's getter must return 0.

The {{PerformanceElementTiming/renderTime}} attribute must return the value it was initialized to.
The {{PerformanceElementTiming/renderTime}} attribute getter step is to return the [=default paint timestamp=] given [=this=]'s [=PaintTimingMixin/paint timing info=].

The {{PerformanceElementTiming/loadTime}} attribute's getter must return the the value it was initialized to.

Expand Down Expand Up @@ -201,27 +206,26 @@ Report Element Timing {#sec-report-element-timing}
--------------------------------------------------

<div export algorithm="report element timing">
When asked to <dfn>report element timing</dfn> given a {{Document}} |doc|, a timestamp |now|, an [=ordered set=] of [=pending image records=] |paintedImages|, and an [=ordered set=] of [=/elements=] |paintedTextNodes|, perform the following steps:
When asked to <dfn>report element timing</dfn> given a {{Document}} |doc|, a [/=paint timing info=] |paintTimingInfo|, an [=ordered set=] of [=pending image records=] |paintedImages|, and an [=ordered set=] of [=/elements=] |paintedTextNodes|, perform the following steps:

1. For each |record| in |paintedImages|:
1. Run the <a>report image element timing</a> algorithm passing in |record|, |now|, and |doc|.
1. Run the <a>report image element timing</a> algorithm passing in |record|, |paintTimingInfo|, and |doc|.
1. For each {{Element}} |element| in |paintedTextNodes|:
1. Run the <a>report text element timing</a> given |element|, |now|, and |doc|.
1. Run the <a>report text element timing</a> given |element|, |paintTimingInfo|, and |doc|.
</div>

Report Image Element Timing {#sec-report-image-element}
--------------------------------------------------------

<div algorithm="report image element timing">
When asked to <dfn>report image element timing</dfn> given a [=pending image record=] |record|, a DOMHighResTimestamp |renderTime| and a {{Document}} |document|, perform the following steps:
When asked to <dfn>report image element timing</dfn> given a [=pending image record=] |record|, a [=/paint timing info=] |paintTimingInfo| and a {{Document}} |document|, perform the following steps:

1. If |record|'s [=pending image record/element=]'s "<code>elementtiming</code>" content attribute is absent, then abort these steps.
1. Let |intersectionRect| be the value returned by the <a>intersection rect algorithm</a> using |record|'s [=pending image record/element=] as the target and viewport as the root.
1. Create and initialize a {{PerformanceElementTiming}} object |entry| with |document|'s [=relevant realm=].
1. Create and initialize a {{PerformanceElementTiming}} object |entry| with |document|'s [=relevant realm=], whose [=PaintTimingMixin/paint timing info=] is |paintTimingInfo|.
1. Initialize |entry|'s <a>request</a> to |record|'s [=pending image record/request=].
1. Initialize |entry|'s <a>element</a> to |record|'s [=pending image record/element=].
1. Initialize |entry|'s {{PerformanceEntry/name}} to the {{DOMString}} "image-paint".
1. Initialize |entry|'s {{renderTime}} to |renderTime|.
1. Initialize |entry|'s {{loadTime}} to |record|'s [=pending image record/loadTime=].
1. Initialize |entry|'s {{intersectionRect}} to |intersectionRect|.
1. Initialize |entry|'s {{identifier}} to |record|'s [=pending image record/element=]'s "<code>elementtiming</code>" content attribute.
Expand All @@ -234,17 +238,16 @@ Report Text Element Timing {#sec-report-text}
--------------------------------------------------------

<div algorithm="report text element timing">
When asked to <dfn>report text element timing</dfn> given an {{Element}} |element|, a DOMHighResTimestamp |renderTime| and a {{Document}} |document|, perform the following steps:
When asked to <dfn>report text element timing</dfn> given an {{Element}} |element|, a [=/paint timing info=] |paintTimingInfo| and a {{Document}} |document|, perform the following steps:

1. If |element|'s "<code>elementtiming</code>" content attribute is absent, then abort these steps.
1. Let |intersectionRect| be an empty rectangle.
1. For each {{Text}} <a>node</a> |text| in |element|'s <a>set of owned text nodes</a>:
1. Augment |intersectionRect| to be smallest rectangle containing the border box of |text| and |intersectionRect|.
1. Intersect |intersectionRect| with the visual viewport.
1. Create and initialize a {{PerformanceElementTiming}} object |entry| with |document|'s [=relevant realm=].
1. Create and initialize a {{PerformanceElementTiming}} object |entry| with |document|'s [=relevant realm=], whose [=PaintTimingMixin/paint timing info=] is |paintTimingInfo|.
1. Initialize |entry|'s <a>element</a> to |element|.
1. Initialize |entry|'s {{PerformanceEntry/name}} to the {{DOMString}} "text-paint".
1. Initialize |entry|'s {{renderTime}} to |renderTime|.
1. Initialize |entry|'s {{loadTime}} to 0.
1. Initialize |entry|'s {{intersectionRect}} to |intersectionRect|.
1. Initialize |entry|'s {{identifier}} to |element|'s "<code>elementtiming</code>" content attribute.
Expand All @@ -257,20 +260,18 @@ Security & privacy considerations {#sec-security}
===============================================

This API exposes some information about cross-origin images.
In particular, images that do not pass the <a>timing allow check</a> still have their resource load time exposed, which could be a source of privacy concerns.
In particular, images have their resource load time exposed, which could be a source of privacy concerns.

However, this is considered to not add new attacks to the web platform because the ResourceTiming API exposes a similar timestamp already.
In addition, the onload handler exposes load timing when it is available, and the resource load time is a close proxy to this.
The <a>current high resolution time</a> computed at the beginning of the onload handler would provide the image load time.
We choose to expose the {{loadTime}} because it is very easy to obtain even without an onload handler.
In addition, we believe any fix to remove the leak provided by image onload handlers or ResourceTiming could also fix the leak provided by this API.

The {{renderTime}} (display timestamp) can also be polyfilled via the PaintTiming API.
To do this, add an iframe that contains trivial content on the onload handler of the target image or text content.
Then, query the first paint of that iframe to obtain the rendering timestamp of the content.
This is quite inefficient and the polyfill itself might affect the timing obtained.
Due to the difficulty in obtaining this information today, we choose not to expose the display timestamp for images that fail the <a>timing allow check</a>.
For clarity, here is a code snippet using the PaintTiming API:
The {{renderTime}} (display timestamp) is indeed newly exposed information. Implementations are advised to coarsen that timestamp further,
o a 4 milliseconds resolution at least, to avoid exposing differences in decoding time between cross-origin images. Note that other checks,
such as `Timing-Allow-Origin`, does not work here due to same-origin and cross-origin images being rendered at the same time.
Exposing a coarse {{renderTime}} is anyway not a substantial attack vector, given that image [=natural size=] and loading time are exposed in other ways.

<xmp class="example highlight" highlight=html>
// In the attacker frame.
Expand All @@ -297,8 +298,6 @@ The other nontrivial parameter being exposed here is the {{intersectionRect}}.
This can already be polyfilled, for example using {{IntersectionObserver}}.
The polyfill process would be similar: add an {{IntersectionObserver}} on the onload handler of the target image or text content.
This solution is inefficient because it requires registering the observer once the content has loaded, but it should still provide the same level of accuracy.
For images, we compute the {{intersectionRect}} once the image has loaded if it does not pass the <a>timing allow check</a>.
Computing it at this point in time allows exposing the entry at that time.
If we were to compute the rect only until the image is fully displayed, we'd only be able to expose the entry after that time.

If we do not want to expose the rendering timetamp of an image, it's preferable to dispatch the entry to the {{PerformanceObserver}} right away.
Expand Down

0 comments on commit 5e609be

Please sign in to comment.