Skip to content

layout: Add effective_visual_size to PaintTimingHandler - #43378

Merged
jdm merged 3 commits into
servo:mainfrom
shubhamg13:exclude_fallback_size
Aug 7, 2026
Merged

jdm merged 3 commits into
servo:mainfrom
shubhamg13:exclude_fallback_size

Conversation

@shubhamg13

@shubhamg13 shubhamg13 commented Mar 18, 2026 •

Copy link
Copy Markdown
Member

Align size calculations as much as possible close to W3C specs

Testing: More WPT Passed
Fixes: Part of #42000

@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Mar 18, 2026
@shubhamg13
shubhamg13 marked this pull request as draft March 18, 2026 03:44
@mrobinson

Copy link
Copy Markdown
Member

I think this just missing a test and perhaps a link to the specification if one is available.

@shubhamg13

Copy link
Copy Markdown
Member Author

I think this just missing a test and perhaps a link to the specification if one is available.

I need some more time on this, need little more research on this.

@shubhamg13
shubhamg13 force-pushed the exclude_fallback_size branch from b6b323e to 6de8bb6 Compare July 31, 2026 10:17
@codecov-commenter

codecov-commenter commented Jul 31, 2026 •

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
984 1 983 0
View the top 1 failed test(s) by shortest run time
servo::largest_contentful_paint::test_largest_contentful_paint_js_api
Stack Traces | 0.271s run time
thread 'test_largest_contentful_paint_js_api' (68361) panicked at .../servo/tests/largest_contentful_paint.rs:66:9:
assertion `left == right` failed
  left: Some(Number(4.0))
 right: Some(Number(2500.0))
stack backtrace:
   0: __rustc::rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::panicking::assert_failed_inner
   3: core::panicking::assert_failed
   4: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@shubhamg13
shubhamg13 force-pushed the exclude_fallback_size branch from 6de8bb6 to 1a2f2f6 Compare August 3, 2026 09:22
@shubhamg13 shubhamg13 changed the title layout: Do not consider for LCP if using fallback size layout: Introduce effective_visual_size to PaintTimingHandler Aug 3, 2026
@shubhamg13
shubhamg13 requested a review from xiaochengh August 3, 2026 09:26
@shubhamg13
shubhamg13 force-pushed the exclude_fallback_size branch 2 times, most recently from a4dda18 to a73828b Compare August 4, 2026 03:27
@shubhamg13 shubhamg13 changed the title layout: Introduce effective_visual_size to PaintTimingHandler layout: Add effective_visual_size to PaintTimingHandler Aug 4, 2026
@shubhamg13
shubhamg13 force-pushed the exclude_fallback_size branch 2 times, most recently from 03bb7de to ab0c320 Compare August 4, 2026 05:11
@@ -1,3 +1,4 @@
[image-full-viewport.html]
expected: TIMEOUT

@shubhamg13 shubhamg13 Aug 4, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Test checks for Text as LCP. Missing Implementation

@shubhamg13
shubhamg13 marked this pull request as ready for review August 4, 2026 05:17
@shubhamg13
shubhamg13 force-pushed the exclude_fallback_size branch from ab0c320 to 48bc8d3 Compare August 4, 2026 05:32
@shubhamg13 shubhamg13 added the T-linux-wpt Do a try run of the WPT label Aug 4, 2026
@github-actions github-actions Bot removed the T-linux-wpt Do a try run of the WPT label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

🔨 Triggering try run (#30881638487) for Linux (WPT)

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Test results for linux-wpt from try job (#30881638487):

Flaky unexpected result (37)
  • CRASH [expected OK] /_mozilla/mozilla/iframe-contentDocument.html
  • TIMEOUT [expected OK] /_webgl/conformance/reading/read-pixels-test.html
  • CRASH [expected OK] /_webgl/conformance/textures/misc/tex-image-and-sub-image-2d-with-array-buffer-view.html
  • CRASH [expected OK] /_webgl/conformance/uniforms/no-over-optimization-on-uniform-array-17.html
  • TIMEOUT [expected OK] /_webgl/conformance2/sync/sync-webgl-specific.html
  • CRASH [expected OK] /_webgl/conformance2/transform_feedback/transform_feedback.html
  • CRASH [expected OK] /css/compositing/canvas-composite-modes.html
  • OK /css/css-fonts/variations/at-font-face-font-matching.html (#20684)
    • FAIL [expected PASS] subtest: Matching font-weight: '400' should prefer '351 398' over '501 550'

      assert_equals: Unexpected font on test element expected 487 but got 532
      

    • FAIL [expected PASS] subtest: Matching font-weight: '430' should prefer '420 440' over '450 460'

      assert_equals: Unexpected font on test element expected 487 but got 532
      

    • FAIL [expected PASS] subtest: Matching font-weight: '399' should prefer '500 501' over '502 510'

      assert_equals: Unexpected font on test element expected 487 but got 532
      

    • FAIL [expected PASS] subtest: Matching font-style: 'oblique -10deg' should prefer 'oblique 0deg 10deg' over 'oblique 40deg 50deg'

      assert_equals: Unexpected font on test element expected 487 but got 532
      

  • FAIL [expected PASS] /css/css-image-animation/image-animation-img-running-to-normal.html (#46567)
  • FAIL [expected PASS] /css/css-text-decor/text-underline-position-from-font-variable.html (#45615)
  • TIMEOUT [expected OK] /fetch/content-encoding/br/big-br-body.https.any.worker.html
    • TIMEOUT [expected PASS] subtest: large br data should be decompressed successfully

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: large br data should be decompressed successfully with byte stream
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/accumulated-oversized-payload.https.window.html (#41705)
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/sandboxed-iframe.https.window.html (#41704)
  • OK /html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-window-open.html (#28691)
    • PASS [expected FAIL] subtest: load event does not fire on window.open('about:blank')
  • TIMEOUT /html/browsers/history/the-history-interface/001.html (#12580)
    • PASS [expected FAIL] subtest: traversing history must also traverse hash changes
  • OK /html/browsers/history/the-history-interface/traverse_the_history_2.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • OK /html/browsers/history/the-history-interface/traverse_the_history_4.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • CRASH [expected OK] /html/browsers/history/the-location-interface/assign-replace-from-iframe.html
  • TIMEOUT [expected ERROR] /html/browsers/history/the-location-interface/location_replace_session_history.html (#41896)
  • CRASH [expected OK] /html/dom/elements/the-innertext-and-outertext-properties/getter-first-letter-marker-multicol.html
  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/document-with-fragment-nonexistent.html (#28259)
    • TIMEOUT [expected FAIL] subtest: Autofocus elements in top-level browsing context's documents with non-existent fragments should work.

      Test timed out
      

  • TIMEOUT /html/semantics/embedded-content/media-elements/autoplay-disabled-by-permissions-policy.https.sub.html (#46012, #46804)
    • TIMEOUT [expected FAIL] subtest: Permissions-Policy header: autoplay "none" disallows same-origin iframes.

      Test timed out
      

  • OK /html/semantics/embedded-content/media-elements/seeking/seek-to-max-value.htm (#40626)
    • FAIL [expected PASS] subtest: seek to Number.MAX_VALUE

      assert_equals: expected 1 but got 0
      

  • TIMEOUT [expected OK] /html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_other_frame_popup.sub.html (#39702)
    • TIMEOUT [expected FAIL] subtest: Sandboxed iframe can not navigate other frame's popup

      Test timed out
      

  • TIMEOUT [expected OK] /html/syntax/parsing/html5lib_write_single.html?file=processing-instructions (#46206)
    • TIMEOUT [expected FAIL] subtest: <body><?_>

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: <body><?_ data>

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: <body><?A---------------->

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: <body><?A---------------- data>

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: <body><?z0123456789>

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: <body><?z0123456789 data>

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: <body><?m-0>

      Test timed out
      

  • OK /html/user-activation/propagation-same-and-cross-origin.sub.html
    • FAIL [expected PASS] subtest: Check that activating a same-origin navigable doesn't activate a cross origin navigable

      assert_true: top-frame navigator.userActivation.isActive must be true expected true got false
      

  • TIMEOUT [expected OK] /html/user-activation/propagation-sameorigin.html
    • NOTRUN [expected PASS] subtest: Propagation test
  • OK /infrastructure/testdriver/click_iframe.html
    • FAIL [expected PASS] subtest: TestDriver click on a document in an iframe

      Unhandled rejection: assert_unreached: click failed Reached unreachable code
      

  • OK /paint-timing/with-lcp/fcp-document-opacity-image.html (#43068)
    • FAIL [expected PASS] subtest: Test that FCP after opacity change is not a larger value than LCP

      assert_equals: First contentful paint marked too early.  expected 0 but got 1
      

  • TIMEOUT [expected OK] /pointerevents/compat/pointerevent_touch-action_two-finger_interaction.html (#44693)
    • NOTRUN [expected PASS] subtest: touch two-finger pan on 'touch-action: pan-x pan-y'
    • NOTRUN [expected FAIL] subtest: touch two-finger pan on 'touch-action: pinch-zoom'
  • OK /preload/prefetch-document.html (#37210)
    • FAIL [expected PASS] subtest: different-site document prefetch with 'as=document' should not be consumed

      assert_equals: expected 2 but got 1
      

  • OK /resource-timing/buffer-full-then-increased.html (#44408)
    • FAIL [expected PASS] subtest: Test that overflowing the buffer and immediately increasing its limit does not trigger the resourcetimingbufferfull event

      assert_equals: Number of entries does not match the expected value. expected 3 but got 1
      

  • OK /sanitizer-api/sanitizer-inert-document.html (#45260)
    • FAIL [expected PASS] subtest: Test whether setHTML loads the image.

      promise_test: Unhandled rejection with value: "The server observed a request. It shouldn't have."
      

    • PASS [expected FAIL] subtest: Test whether setHTMLUnsafe loads the image.
  • OK [expected TIMEOUT] /trusted-types/trusted-types-navigation.html?01-05 (#38975)
    • PASS [expected TIMEOUT] subtest: Navigate a window via anchor with javascript:-urls in report-only mode.
    • PASS [expected NOTRUN] subtest: Navigate a window via anchor with javascript:-urls w/ default policy in report-only mode.
    • PASS [expected NOTRUN] subtest: Navigate a frame via anchor with javascript:-urls in enforcing mode.
  • TIMEOUT [expected OK] /trusted-types/trusted-types-navigation.html?16-20 (#44835)
    • TIMEOUT [expected PASS] subtest: Navigate a frame via area with javascript:-urls w/ default policy in enforcing mode.

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: Navigate a frame via area with javascript:-urls in report-only mode.
    • NOTRUN [expected PASS] subtest: Navigate a frame via area with javascript:-urls w/ default policy in report-only mode.
  • CRASH [expected OK] /trusted-types/trusted-types-report-only.html
  • OK /webxr/xrSession_features_deviceSupport.https.html (#24357)
    • FAIL [expected PASS] subtest: Immersive XRSession requests with no supported device should reject

      assert_unreached: Should have rejected: undefined Reached unreachable code
      

Stable unexpected results that are known to be intermittent (15)
  • OK /_webgl/conformance/textures/misc/texture-upload-size.html (#21770)
    • PASS [expected FAIL] subtest: WebGL test #45
    • PASS [expected FAIL] subtest: WebGL test #47
    • PASS [expected FAIL] subtest: WebGL test #49
    • PASS [expected FAIL] subtest: WebGL test #51
    • FAIL [expected PASS] subtest: WebGL test #53

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #55

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #57

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #59

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • PASS [expected FAIL] subtest: WebGL test #61
    • PASS [expected FAIL] subtest: WebGL test #63
    • And 22 more unexpected results...
  • TIMEOUT /content-security-policy/inheritance/auxiliary-blank-document.html (#44821)
    • FAIL [expected PASS] subtest: For transient doc, violation event occurs in auxiliary, not opener window.

      assert_equals: expected Document node with 1 child but got Document node with 1 child
      

  • FAIL [expected PASS] /css/css-sizing/dynamic-available-size-iframe.html (#45763)
  • OK /dom/nodes/moveBefore/iframe-document-preserve.window.html (#43152)
    • PASS [expected FAIL] subtest: moveBefore(): cross-origin iframe is preserved: remove self
    • FAIL [expected PASS] subtest: moveBefore(): cross-origin iframe is preserved: remove self via innerHTML

      assert_equals: iframe does not fire a second load event expected 1 but got 0
      

  • TIMEOUT [expected OK] /fetch/api/redirect/redirect-keepalive.https.any.html (#32153)
    • TIMEOUT [expected PASS] subtest: [keepalive][iframe][load] mixed content redirect; setting up

      Test timed out
      

  • OK /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html (#28697)
    • FAIL [expected PASS] subtest: aElement.click() before the load event must NOT replace

      assert_equals: expected "http://web-platform.test:8000/common/blank.html?thereplacement" but got "http://web-platform.test:8000/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/code-injector.html?pipe=sub(none)&code=%0A%20%20%20%20const%20a%20%3D%20document.createElement(%22a%22)%3B%0A%20%20%20%20a.href%20%3D%20%22%2Fcommon%2Fblank.html%3Fthereplacement%22%3B%0A%20%20%20%20document.currentScript.before(a)%3B%0A%20%20%20%20a.click()%3B%0A%20%20"
      

  • TIMEOUT [expected OK] /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit.html (#44028)
  • OK /html/browsers/history/the-history-interface/traverse_the_history_5.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • OK /html/browsers/windows/embedded-opener-remove-frame.html (#23867)
    • PASS [expected FAIL] subtest: opener of discarded auxiliary browsing context
  • OK /html/interaction/focus/the-autofocus-attribute/autofocus-dialog.html (#29087)
    • PASS [expected TIMEOUT] subtest: <dialog>-contained autofocus element gets focused when the dialog is shown
  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/update-the-rendering.html (#24145)
    • TIMEOUT [expected FAIL] subtest: "Flush autofocus candidates" should be happen before a scroll event and animation frame callbacks

      Test timed out
      

  • OK /html/semantics/embedded-content/media-elements/media_fragment_seek.html (#24114)
    • PASS [expected FAIL] subtest: Video should seek to time specified in media fragment syntax
  • TIMEOUT [expected OK] /html/syntax/parsing/html5lib_write.html?file=processing-instructions (#46205)
    • TIMEOUT [expected FAIL] subtest: <body><?user_name data>

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: <body><?S_S-S_S>

      Test timed out
      

  • OK /html/webappapis/user-prompts/print-during-unload.html (#35944)
    • PASS [expected FAIL] subtest: print() during unload
  • TIMEOUT /trusted-types/trusted-types-navigation.html?31-35 (#38034)
    • PASS [expected TIMEOUT] subtest: Navigate a frame via form-submission with javascript:-urls in report-only mode.
    • TIMEOUT [expected NOTRUN] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in report-only mode.

      Test timed out
      

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

✨ Try run (#30881638487) succeeded.

Comment on lines +186 to +188
if result.is_none() {
return;
}

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.

Suggested change
if result.is_none() {
return;
}
let Some(result) = result else {
return;
}

@shubhamg13 shubhamg13 Aug 5, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done 💯

// If size is less than or equal to document’s largest contentful paint size, return.
if size <= self.lcp_size {
// Step 4.6. If result’s size is less than or equal to largestSize, continue.
if result.is_some_and(|size| size <= self.lcp_size) {

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.

Suggested change
if result.is_some_and(|size| size <= self.lcp_size) {
if result <= self.lcp_size {

@shubhamg13 shubhamg13 Aug 5, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done 💯

}

// Step 4.7. Set largestSize to result’s size.
self.lcp_size = result.unwrap();

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.

Suggested change
self.lcp_size = result.unwrap();
self.lcp_size = result;

@shubhamg13 shubhamg13 Aug 5, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done 💯

@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Aug 5, 2026
@shubhamg13
shubhamg13 force-pushed the exclude_fallback_size branch from 48bc8d3 to 1089c66 Compare August 5, 2026 06:19
@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Aug 5, 2026
@jdm
jdm added this pull request to the merge queue Aug 5, 2026
@servo-highfive servo-highfive added the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Aug 5, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 5, 2026
@servo-highfive servo-highfive removed the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Aug 5, 2026
@shubhamg13

shubhamg13 commented Aug 5, 2026 •

Copy link
Copy Markdown
Member Author

My previous patch created conflict. Rebased.

Please help to merge.

@shubhamg13
shubhamg13 force-pushed the exclude_fallback_size branch from 1089c66 to f7beb96 Compare August 6, 2026 02:07
@shubhamg13
shubhamg13 marked this pull request as draft August 6, 2026 02:11
shubhamg13 and others added 3 commits August 6, 2026 17:44
Signed-off-by: Shubham Gupta <shubham.gupta@chromium.org>
Signed-off-by: Shubham Gupta <shubhamg13.work@gmail.com>
Signed-off-by: Shubham Gupta <shubhamg13.work@gmail.com>
@shubhamg13
shubhamg13 force-pushed the exclude_fallback_size branch from f7beb96 to 3b5f18a Compare August 6, 2026 09:44
@shubhamg13
shubhamg13 marked this pull request as ready for review August 6, 2026 10:58
@shubhamg13

Copy link
Copy Markdown
Member Author

Results from last dry run: https://github.com/shubhamg13/servo/actions/runs/31090394766

@jdm
jdm added this pull request to the merge queue Aug 7, 2026
@servo-highfive servo-highfive added the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Aug 7, 2026
Merged via the queue into servo:main with commit 30bba6d Aug 7, 2026
33 checks passed
@servo-highfive servo-highfive removed the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Aug 7, 2026
@shubhamg13
shubhamg13 deleted the exclude_fallback_size branch August 7, 2026 03:53
RichardTjokroutomo pushed a commit to RichardTjokroutomo/servo that referenced this pull request Oct 6, 2026
)

Align size calculations as much as possible close to W3C specs

Testing: More WPT Passed
Fixes: Part of servo#42000

---------

Signed-off-by: Shubham Gupta <shubham.gupta@chromium.org>
Signed-off-by: Shubham Gupta <shubhamg13.work@gmail.com>
veyndan pushed a commit to veyndan/servo that referenced this pull request Oct 9, 2026
Hi, I'm a spec champion for the Container Timing feature and I wanted to
add it to Servo (behind a flag).

The Container Timing API enables monitoring when annotated sections of
the DOM are displayed on screen and have finished their initial paint. I
am one of the spec authors, and i've been helping this land in both
[Chromium](https://developer.chrome.com/blog/container-timing-origin-trial)
and [Firefox](https://phabricator.services.mozilla.com/D279373)

This PR implements the [Container Timing
API](https://github.com/WICG/container-timing), following the same
layout > paint > constellation pipeline already used for LCP.

  Steps for calculating Container Timing:

1. When an element has the `containertiming` attribute, a [flag is
propagated down through its
descendants](https://github.com/jasonwilliams/servo/blob/403908fa99f47da3e18faaca88d2abdb01498cf7/components/script/dom/node/node.rs#L378-L387)
(set on the Node/fragment) so layout can cheaply tell "is this inside a
container-timing root" without walking back up the tree on every
fragment.
2. While building the display list, each painted text/image fragment
that belongs to a container-timing root [reports its viewport-clipped
size and rect to a
PaintTimingHandler](https://github.com/jasonwilliams/servo/blob/403908fa99f47da3e18faaca88d2abdb01498cf7/components/layout/display_list/mod.rs#L706-L718),
similar to how LCP candidates are collected.
3. PaintTimingHandler keeps one running record per container root (keyed
by a stable node ID, since the containertiming value itself is optional
and not unique), accumulating size/rect across paints, and tracks which
containers were actually touched since the last hand-off.
4. Layout [hands the finished per-container records to
script](https://github.com/jasonwilliams/servo/blob/403908fa99f47da3e18faaca88d2abdb01498cf7/components/layout/layout_impl.rs#L1561-L1567)
as part of the reflow result
5. once webrender confirms that epoch composited, [it sends
PaintMetricEvent::ContainerTiming(paint_time,
ids)](https://github.com/jasonwilliams/servo/blob/403908fa99f47da3e18faaca88d2abdb01498cf7/components/paint/painter.rs#L585-L589)
to script, which stamps the already-stored records with the real
paint/first-render time and queues the finished
PerformanceContainerTiming entries.

Testing:
- There are plenty of [Web Platform
Tests](https://wpt.fyi/results/container-timing/tentative?label=master&label=experimental&aligned&q=container%20timing)
for Container Timing. I have been testing the build against these as i
have been going along, 19/29 tests pass.
- There are tests for some components, such as paintedRegion

Follow ups & investigation:
- `lastPaintedElement` selection picks by traversal order, not largest
area
- [Tidy Up] The `effective_visual_size` check was [originally written
for LCP](servo#43378), but is useful for
Container Timing as well, we should make this API generic

---------

Signed-off-by: jasonwilliams <jase.williams@gmail.com>
hhhwsqhhh pushed a commit to hhhwsqhhh/servo that referenced this pull request Oct 12, 2026
Hi, I'm a spec champion for the Container Timing feature and I wanted to
add it to Servo (behind a flag).

The Container Timing API enables monitoring when annotated sections of
the DOM are displayed on screen and have finished their initial paint. I
am one of the spec authors, and i've been helping this land in both
[Chromium](https://developer.chrome.com/blog/container-timing-origin-trial)
and [Firefox](https://phabricator.services.mozilla.com/D279373)

This PR implements the [Container Timing
API](https://github.com/WICG/container-timing), following the same
layout > paint > constellation pipeline already used for LCP.

  Steps for calculating Container Timing:

1. When an element has the `containertiming` attribute, a [flag is
propagated down through its
descendants](https://github.com/jasonwilliams/servo/blob/403908fa99f47da3e18faaca88d2abdb01498cf7/components/script/dom/node/node.rs#L378-L387)
(set on the Node/fragment) so layout can cheaply tell "is this inside a
container-timing root" without walking back up the tree on every
fragment.
2. While building the display list, each painted text/image fragment
that belongs to a container-timing root [reports its viewport-clipped
size and rect to a
PaintTimingHandler](https://github.com/jasonwilliams/servo/blob/403908fa99f47da3e18faaca88d2abdb01498cf7/components/layout/display_list/mod.rs#L706-L718),
similar to how LCP candidates are collected.
3. PaintTimingHandler keeps one running record per container root (keyed
by a stable node ID, since the containertiming value itself is optional
and not unique), accumulating size/rect across paints, and tracks which
containers were actually touched since the last hand-off.
4. Layout [hands the finished per-container records to
script](https://github.com/jasonwilliams/servo/blob/403908fa99f47da3e18faaca88d2abdb01498cf7/components/layout/layout_impl.rs#L1561-L1567)
as part of the reflow result
5. once webrender confirms that epoch composited, [it sends
PaintMetricEvent::ContainerTiming(paint_time,
ids)](https://github.com/jasonwilliams/servo/blob/403908fa99f47da3e18faaca88d2abdb01498cf7/components/paint/painter.rs#L585-L589)
to script, which stamps the already-stored records with the real
paint/first-render time and queues the finished
PerformanceContainerTiming entries.

Testing:
- There are plenty of [Web Platform
Tests](https://wpt.fyi/results/container-timing/tentative?label=master&label=experimental&aligned&q=container%20timing)
for Container Timing. I have been testing the build against these as i
have been going along, 19/29 tests pass.
- There are tests for some components, such as paintedRegion

Follow ups & investigation:
- `lastPaintedElement` selection picks by traversal order, not largest
area
- [Tidy Up] The `effective_visual_size` check was [originally written
for LCP](servo#43378), but is useful for
Container Timing as well, we should make this API generic

---------

Signed-off-by: jasonwilliams <jase.williams@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-awaiting-review There is new code that needs to be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants