Skip to content

Add markPaintTime() explainer#1303

Open
JosephJin0815 wants to merge 22 commits intoMicrosoftEdge:mainfrom
JosephJin0815:user/wangsongj/mark-paint-time
Open

Add markPaintTime() explainer#1303
JosephJin0815 wants to merge 22 commits intoMicrosoftEdge:mainfrom
JosephJin0815:user/wangsongj/mark-paint-time

Conversation

@JosephJin0815
Copy link
Copy Markdown

Explainer for performance.markPaintTime() — a new Performance API that lets developers capture paintTime and presentationTime for visual update on demand, extending the timing capability currently available only through browser-detected entries (FP, FCP, LCP, Event Timing, LoAF)

@JosephJin0815 JosephJin0815 marked this pull request as ready for review April 16, 2026 22:37
Comment thread performanceMarkPaintTime/explainer.md Outdated
Comment thread performanceMarkPaintTime/explainer.md Outdated
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be helpful to include an end-to-end example covering a page, the area of interest, and the solution.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated. Thanks!

| `entryType` | Always `"mark-paint-time"` |
| `name` | The mark name passed to `markPaintTime()` |
| `startTime` | `performance.now()` at the time `markPaintTime()` was called |
| `duration` | Always `0` |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is the purpose of duration if it's always zero?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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:

Comment thread performanceMarkPaintTime/explainer.md Outdated
|-----------|-------------|
| `entryType` | Always `"mark-paint-time"` |
| `name` | The mark name passed to `markPaintTime()` |
| `startTime` | `performance.now()` at the time `markPaintTime()` was called |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you please mark startTime in the Rendering Pipeline figure?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

startTime is recorded at the moment markPaintTime() is called. So there's no corresponding fixed point.

Comment thread performanceMarkPaintTime/explainer.md Outdated
|-----------|-------------|
| `entryType` | Always `"mark-paint-time"` |
| `name` | The mark name passed to `markPaintTime()` |
| `startTime` | `performance.now()` at the time `markPaintTime()` was called |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Author

@JosephJin0815 JosephJin0815 Apr 28, 2026

Choose a reason for hiding this comment

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

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.

Wangsong Jin and others added 12 commits April 27, 2026 14:01
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants