Add markPaintTime() explainer#1303
Add markPaintTime() explainer#1303JosephJin0815 wants to merge 22 commits intoMicrosoftEdge:mainfrom
Conversation
| Without an on-demand API, developers resort to workarounds like double-rAF or rAF+setTimeout to approximate when the rendering update completes, but these workarounds are unreliable (see [Nolan Lawson's analysis](https://nolanlawson.com/2018/09/25/accurately-measuring-layout-on-the-web/)). Furthermore, no workaround can provide `presentationTime` — the actual time when pixels appear on screen. For example, a developer wants to measure when a chat input box appears after the page loads, but the component is rendered asynchronously by a framework. A typical pattern uses `IntersectionObserver` to detect when the element enters the viewport, then `requestAnimationFrame` to approximate the paint time: | ||
|
|
||
| ### Single requestAnimationFrame | ||
|
|
There was a problem hiding this comment.
It would be helpful to include an end-to-end example covering a page, the area of interest, and the solution.
| | `entryType` | Always `"mark-paint-time"` | | ||
| | `name` | The mark name passed to `markPaintTime()` | | ||
| | `startTime` | `performance.now()` at the time `markPaintTime()` was called | | ||
| | `duration` | Always `0` | |
There was a problem hiding this comment.
What is the purpose of duration if it's always zero?
There was a problem hiding this comment.
I'm following the same convention used by LCP and FP/FCP, which are also delivered asynchronously but set duration to 0. We considered setting duration to a computed interval (e.g., presentationTime - startTime), but since the entry already exposes startTime, paintTime, and presentationTime individually, developers can compute whichever interval is meaningful to their use case — making a pre-computed duration redundant.
Existing duration set to 0 cases:
| |-----------|-------------| | ||
| | `entryType` | Always `"mark-paint-time"` | | ||
| | `name` | The mark name passed to `markPaintTime()` | | ||
| | `startTime` | `performance.now()` at the time `markPaintTime()` was called | |
There was a problem hiding this comment.
Can you please mark startTime in the Rendering Pipeline figure?
There was a problem hiding this comment.
startTime is recorded at the moment markPaintTime() is called. So there's no corresponding fixed point.
| |-----------|-------------| | ||
| | `entryType` | Always `"mark-paint-time"` | | ||
| | `name` | The mark name passed to `markPaintTime()` | | ||
| | `startTime` | `performance.now()` at the time `markPaintTime()` was called | |
There was a problem hiding this comment.
I’m unclear about the meaning of startTime. If it’s recorded from an IntersectionObserver callback, it seems more like a pre‑paint timestamp, since it wouldn’t include earlier stages such as animation, styling, and layout. In Chromium, this would effectively correspond to pre‑paint timing. However, if markPaintTime() is invoked from a different point in the pipeline, the semantics of startTime could vary.
There was a problem hiding this comment.
startTime follows the same semantics as performance.mark() — it's always performance.now() at the time markPaintTime() is called, not a rendering-pipeline timestamp. It doesn't change based on where in the event loop the call occurs. I've updated the explainer to make this clearer and link to the User Timing spec. See step 5: https://w3c.github.io/user-timing/#the-performancemark-constructor
I think other paint timing entries (FP, FCP, LCP) set startTime to a rendering timestamp because they are browser-detected milestones — there's no developer call site to anchor to. Our API is on-demand, so startTime as call time is meaningful: it lets developers measure paintTime - startTime to see how long from their code to the rendering update ended.
Link Largest Contentful Paint, PaintTimingMixin, FP, FCP, LCP, Event Timing, and LoAF to their respective W3C specifications. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Multiple markPaintTime() calls within the same rendering opportunity produce entries with shared paintTime/presentationTime but distinct name and startTime. Calls spanning different rendering opportunities produce entries with distinct paintTime and presentationTime. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
markPaintTime() sounds synchronous like performance.mark() but schedules future observation. Discuss markNextPaint() and observeNextPaint() as alternatives that better signal deferred capture. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Update startTime description to clarify it is not a rendering-pipeline timestamp and can be overridden via options.startTime - Add behavior bullet for startTime override with TypeError on negative - Add MarkPaintTimeOptions dictionary to WebIDL - Update markPaintTime() signature to accept optional options parameter Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
presentationTime may be null when the UA does not support implementation-defined presentation timestamps, consistent with PaintTimingMixin spec. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use 'no existing workaround' instead of 'no workaround can' - Qualify presentationTime references with 'when available' / 'non-null' - Update presentationTime table description to mention null case - Use spec-aligned 'implementation-defined' for presentationTime - Soften rPAF comparison wording Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
presentationTime depends on compositor frame presentation and may vary independently from paintTime across rendering opportunities. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
It measures time from the API call to the end of the rendering update, not purely main-thread rendering cost. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace IntersectionObserver-based examples with fetch + DOM update to tell a consistent async content loading story across all examples. Add full HTML end-to-end example for markPaintTime with PerformanceObserver and presentationTime null check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Explainer for
performance.markPaintTime()— a new Performance API that lets developers capturepaintTimeandpresentationTimefor visual update on demand, extending the timing capability currently available only through browser-detected entries (FP, FCP, LCP, Event Timing, LoAF)