Skip to content

layout: Fix first-paint detection#42975

Merged
TimvdLippe merged 4 commits intoservo:mainfrom
shubhamg13:fix_fb_bg
Mar 20, 2026
Merged

layout: Fix first-paint detection#42975
TimvdLippe merged 4 commits intoservo:mainfrom
shubhamg13:fix_fb_bg

Conversation

@shubhamg13
Copy link
Copy Markdown
Member

@shubhamg13 shubhamg13 commented Mar 3, 2026

This PR comprises

  1. Segregate paintable and contentful.
  2. Adds Checking of non-default background for eligibility to mark first-paint.
  3. Adds a bool is_paintable to display_list and use same for marking first-paint.

TODO: This doesn't consider iframes. Will add PR after fixing WPT tests.

Raised an issue for some incomplete definitions: w3c/paint-timing#122

Testing:

  • wpt/tests/paint-timing/first-paint-only/first-paint-bg-color.html
  • Updated WPT tests expectations.
    • wpt/paint-timing/first-image-child.html
    • wpt/paint-timing/first-paint-only/sibling-painting-first-image.html
  • Unit Test: servo/tests/performance_paint_timings.rs. Following Tests Cases are covered:
Background Pref Root Background FP TC
Default Unset test_default_pref_with_unset_background
Non-Default Unset test_non_default_pref_with_unset_background
Default Transparent test_default_pref_with_transparent_background
Non-Default Unset test_non_default_pref_with_transparent_background
Default Set test_default_pref_with_background
Non-Default Set test_non_default_pref_with_background
Non-Default Set (same as pref) test_non_default_pref_with_same_background

Fixes: #42148

@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Mar 3, 2026
@shubhamg13 shubhamg13 requested a review from jdm March 3, 2026 04:00
@shubhamg13 shubhamg13 changed the title layout: Consider non-default background color for first-paint only layout: Consider non-default background color for first-paint Mar 3, 2026

// From <https://www.w3.org/TR/paint-timing/#sec-terminology>:
// First paint ... includes non-default background paint and the enclosing box of an iframe.
if background_color != AbsoluteColor::WHITE {
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 do not understand this code. White is not the default background color in style. You should be checking the value from the style itself, not the final color. In addition, the specification text says "First paint excludes the default background paint, but includes non-default background paint." This isn't talking about the color, but the background itself, so my reading is that it should include all kinds of background paints, including images.

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.

IMO it is about color. We should mark default color in prefs or somewhere.

If I don't check for white presumably default, It will result in failing of first-contentful-paint.html

Copy link
Copy Markdown
Member

@mrobinson mrobinson Mar 3, 2026

Choose a reason for hiding this comment

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

Can you justify this interpretation of the specification somehow? A "default background color" of <div>s does not sound like the kind of feature that the web platform usually contains. Also does "default background paint" here refer to the background of individual fragments or the page canvas background of the HTML <body> element (https://drafts.csswg.org/css-backgrounds/#body-background)?

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 understand now that this code is only about the page canvas background.

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.

If you observe the behavior of this code in Chrome you can see that background images should trigger first-paint. I am pretty sure that "non-default" here should mean when any background that eventually propagates to be the HTML page canvas is not transparent and does not match the backdrop color (pref!(shell_background_color_rgba)) including images.

<!doctype html>
<html>
    <head>
        <style>
            html {
                background-image: url(data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2Ng+P//PwAF/wL+wMZ2tgAAAABJRU5ErkJggg==);
            }
        </style>
    </head>
    <body>

        <script>
            setTimeout(() => {
                console.log(performance.getEntriesByType('paint'));
            }, 2000);
        </script>
    </body>
</html>

Also observe that this works the same way with a 1 pixel by 1 pixel transparent or white image

<!doctype html>
<html>
    <head>
        <style>
            html {
                /* background-image: url(data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2Ng+P//PwAF/wL+wMZ2tgAAAABJRU5ErkJggg==);*/
                 background-image: url(data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2P4DwQACfsD/Z8fLAAAAAAASUVORK5CYII=);
            }
        </style>
    </head>
    <body>

        <script>
            setTimeout(() => {
                console.log(performance.getEntriesByType('paint'));
            }, 2000);
        </script>
    </body>
</html>

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.

All the other background like background-image, border-image are already considered for FP.

Only non-default background color is not considered. So, i marked it here.

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.

Still, you will need to consider the backdrop color from the preferences. Also, it's unclear what should happen here when the page is loading in the iframe. I suspect the behavior might end up being different depending on where the content is loading (main frame versus iframe).

Copy link
Copy Markdown
Member Author

@shubhamg13 shubhamg13 Mar 4, 2026

Choose a reason for hiding this comment

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

Still, you will need to consider the backdrop color from the preferences. Also, it's unclear what should happen here when the page is loading in the iframe. I suspect the behavior might end up being different depending on where the content is loading (main frame versus iframe).

Considered the backdrop color from preference. And consolidated all the changes here.

Yeah, for iframe, it is not considered here. Since, I'm fixing the WPT tests, I will submit it in a separate PR when WPT tests are updated after weekly sync. Added a TODO in this PR description for same.

Please review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The latest commit (4a6f9c4) looks correct to me in handling non-default background color of the root.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The spec is vague on this. We should provide some test cases to ensure that our behavior is correct in at least common cases.

@servo-highfive servo-highfive added S-needs-code-changes Changes have not yet been made that were requested by a reviewer. and removed S-awaiting-review There is new code that needs to be reviewed. labels Mar 3, 2026
@shubhamg13 shubhamg13 requested a review from mrobinson March 3, 2026 09:29
Copy link
Copy Markdown
Member

@mrobinson mrobinson left a comment

Choose a reason for hiding this comment

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

This doesn't look to me like what the specification is saying and I think we need to work out what is actually meant here by "non-default background paint" before landing.


// From <https://www.w3.org/TR/paint-timing/#sec-terminology>:
// First paint ... includes non-default background paint and the enclosing box of an iframe.
if background_color != AbsoluteColor::WHITE {
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.

If you observe the behavior of this code in Chrome you can see that background images should trigger first-paint. I am pretty sure that "non-default" here should mean when any background that eventually propagates to be the HTML page canvas is not transparent and does not match the backdrop color (pref!(shell_background_color_rgba)) including images.

<!doctype html>
<html>
    <head>
        <style>
            html {
                background-image: url(data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2Ng+P//PwAF/wL+wMZ2tgAAAABJRU5ErkJggg==);
            }
        </style>
    </head>
    <body>

        <script>
            setTimeout(() => {
                console.log(performance.getEntriesByType('paint'));
            }, 2000);
        </script>
    </body>
</html>

Also observe that this works the same way with a 1 pixel by 1 pixel transparent or white image

<!doctype html>
<html>
    <head>
        <style>
            html {
                /* background-image: url(data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2Ng+P//PwAF/wL+wMZ2tgAAAABJRU5ErkJggg==);*/
                 background-image: url(data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2P4DwQACfsD/Z8fLAAAAAAASUVORK5CYII=);
            }
        </style>
    </head>
    <body>

        <script>
            setTimeout(() => {
                console.log(performance.getEntriesByType('paint'));
            }, 2000);
        </script>
    </body>
</html>

// An element el is paintable when all of the following apply:
// > el is being rendered.
// > el’s used visibility is visible.
// This is sufficient to mark the first paint
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 know this is from the other PR, but I think these two should be combined into a single PR.

Why is this sufficient? Please provide specification text and a link so that those reading the code can understand why this is the case.

Copy link
Copy Markdown
Member Author

@shubhamg13 shubhamg13 Mar 4, 2026

Choose a reason for hiding this comment

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

Updated please take a look and review.


// From <https://www.w3.org/TR/paint-timing/#sec-terminology>:
// First paint ... includes non-default background paint and the enclosing box of an iframe.
if background_color != AbsoluteColor::WHITE {
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.

Still, you will need to consider the backdrop color from the preferences. Also, it's unclear what should happen here when the page is loading in the iframe. I suspect the behavior might end up being different depending on where the content is loading (main frame versus iframe).

@shubhamg13 shubhamg13 marked this pull request as draft March 4, 2026 03:09
@shubhamg13 shubhamg13 changed the title layout: Consider non-default background color for first-paint layout: Fixfirst-paint detection Mar 4, 2026
@shubhamg13 shubhamg13 changed the title layout: Fixfirst-paint detection layout: Fix first-paint detection Mar 4, 2026
@shubhamg13 shubhamg13 requested a review from mrobinson March 4, 2026 03:58
@shubhamg13 shubhamg13 marked this pull request as ready for review March 4, 2026 03:58
@servo-highfive servo-highfive added S-awaiting-review There is new code that needs to be reviewed. and removed S-needs-code-changes Changes have not yet been made that were requested by a reviewer. labels Mar 4, 2026
@shubhamg13
Copy link
Copy Markdown
Member Author

shubhamg13 commented Mar 6, 2026

This doesn't look to me like what the specification is saying and I think we need to work out what is actually meant here by "non-default background paint" before landing.

Yeah, As I understand anything other than default color is "non-default background paint". Whether it is border-image, background-image, background color or even gradient also.

@shubhamg13 shubhamg13 requested a review from xiaochengh March 12, 2026 07:57
Comment on lines +845 to +847
/// First paint
/// See <https://w3c.github.io/paint-timing/#first-paint>.
pub first_paint: bool,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IIUC this is marking whether anything is painted. If so, it seems better to change it into:

Suggested change
/// First paint
/// See <https://w3c.github.io/paint-timing/#first-paint>.
pub first_paint: bool,
/// Whether anything has been painted. Used to mark first paint.
/// See <https://w3c.github.io/paint-timing/#first-paint>.
pub has_paint: bool,

All the other relevant variables and methods should also be renamed.

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.

Sure, I will update.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Might be irrelevant to this PR but this doesn't match the definition of contentful. Something seems wrong here.

Do we really have a genuine implementation of FCP, or are we simply reporting FP as FCP?

Copy link
Copy Markdown
Member Author

@shubhamg13 shubhamg13 Mar 13, 2026

Choose a reason for hiding this comment

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

Yeah, It looks like that.

But FCP is quite consistent.

https://www.w3.org/TR/paint-timing/#should-report-first-contentful-paint

If document contains at least one element that is both paintable and contentful, then return true.

We are checking paintable for sure, and most elements from contentful list as we only call this API if element is of that relevant type.

And FP needed some extra addition, which are being handled in this one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

But we can reach here for non-contentful paints, like the background paint of the root context, or an element with a non-image background.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Discussed offline:

  • PaintDisplayListInfo should maintain two independent booleans, is_paintable and is_contentful, following exactly the definition in spec
  • check_if_paintable should mark paintable only
  • When painting a fragment, callers should never bypass check_if_paintable as otherwise some necessary checks will be bypassed
  • When painting an image or text fragment, callers should mark contentful

// > > From <https://w3c.github.io/paint-timing/#first-paint>:
// > > First paint entry contains a DOMHighResTimeStamp reporting the time when the user agent first rendered after navigation.
// > > Hence, This is sufficient to mark the first paint.
self.mark_first_paint();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Per spec, first paint should be marked only after all of visibility, opacity and bounding rect intersection have been checked and passed.

Copy link
Copy Markdown
Member Author

@shubhamg13 shubhamg13 Mar 13, 2026

Choose a reason for hiding this comment

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

Aaah right. Updating


// From <https://www.w3.org/TR/paint-timing/#sec-terminology>:
// First paint ... includes non-default background paint and the enclosing box of an iframe.
if background_color != AbsoluteColor::WHITE {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The latest commit (4a6f9c4) looks correct to me in handling non-default background color of the root.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Discussed offline:

  • PaintDisplayListInfo should maintain two independent booleans, is_paintable and is_contentful, following exactly the definition in spec
  • check_if_paintable should mark paintable only
  • When painting a fragment, callers should never bypass check_if_paintable as otherwise some necessary checks will be bypassed
  • When painting an image or text fragment, callers should mark contentful

@servo-highfive servo-highfive added S-needs-code-changes Changes have not yet been made that were requested by a reviewer. and removed S-awaiting-review There is new code that needs to be reviewed. labels Mar 13, 2026
@shubhamg13 shubhamg13 marked this pull request as draft March 17, 2026 08:22
@servo-wpt-sync
Copy link
Copy Markdown
Collaborator

📝 Transplanted new upstreamable changes to existing upstream WPT pull request (web-platform-tests/wpt#58544).

@shubhamg13 shubhamg13 marked this pull request as ready for review March 17, 2026 08:34
Copy link
Copy Markdown
Member

@jdm jdm left a comment

Choose a reason for hiding this comment

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

This looks reasonable!

@servo-highfive servo-highfive added S-needs-rebase There are merge conflict errors. and removed S-awaiting-review There is new code that needs to be reviewed. labels Mar 18, 2026
@servo-highfive servo-highfive added S-awaiting-review There is new code that needs to be reviewed. and removed S-needs-rebase There are merge conflict errors. labels Mar 18, 2026
@shubhamg13 shubhamg13 requested a review from jdm March 18, 2026 04:54
@servo-wpt-sync
Copy link
Copy Markdown
Collaborator

📝 Transplanted new upstreamable changes to existing upstream WPT pull request (web-platform-tests/wpt#58544).

@servo-wpt-sync
Copy link
Copy Markdown
Collaborator

✍ Updated existing upstream WPT pull request (web-platform-tests/wpt#58544) title and body.

@TimvdLippe TimvdLippe dismissed stale reviews from xiaochengh and mrobinson March 20, 2026 07:19

Comments have been addressed

@TimvdLippe TimvdLippe added this pull request to the merge queue Mar 20, 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 Mar 20, 2026
Merged via the queue into servo:main with commit a7d8f54 Mar 20, 2026
34 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 Mar 20, 2026
@shubhamg13 shubhamg13 deleted the fix_fb_bg branch March 20, 2026 07:57
github-merge-queue bot pushed a commit that referenced this pull request Mar 23, 2026
`iframe` should be considered for `first-paint`. 

TODO of: #42975
Synced: web-platform-tests/wpt#58243
Refer for some history: #42498

Testing: 

- `wpt/paint-timing/first-paint-only/child-painting-first-image.html`
- `wpt/paint-timing/first-paint-only/sibling-painting-first-image.html`

Fixes: #42455

---------

Signed-off-by: Shubham Gupta <[email protected]>
Gae24 pushed a commit to Gae24/servo that referenced this pull request Mar 26, 2026
This PR comprises
1. Segregate `paintable` and `contentful`.
2. Adds Checking of non-default background for eligibility to mark
`first-paint`.
3. Adds a bool `is_paintable` to `display_list` and use same for marking
`first-paint`.

TODO: This doesn't consider `iframes`. Will add PR after fixing WPT
tests.

Raised an issue for some incomplete definitions:
w3c/paint-timing#122




Testing: 
- `wpt/tests/paint-timing/first-paint-only/first-paint-bg-color.html`
- Updated WPT tests expectations.
  - `wpt/paint-timing/first-image-child.html`
- `wpt/paint-timing/first-paint-only/sibling-painting-first-image.html`
- Unit Test: `servo/tests/performance_paint_timings.rs`. Following Tests
Cases are covered:

Background Pref |Root Background|FP|TC
--|--|--|--
|Default|Unset|:x: | `test_default_pref_with_unset_background`
|Non-Default|Unset|:x: | `test_non_default_pref_with_unset_background`
|Default|Transparent|:x:|`test_default_pref_with_transparent_background`

|Non-Default|Unset|:x:|`test_non_default_pref_with_transparent_background`
|Default|Set|:white_check_mark:|`test_default_pref_with_background`

|Non-Default|Set|:white_check_mark:|`test_non_default_pref_with_background`
|Non-Default|Set (same as
pref)|:x:|`test_non_default_pref_with_same_background`

Fixes: servo#42148

---------

Signed-off-by: Shubham Gupta <[email protected]>
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.

Test with NO Paint Entries TIMEOUTs

8 participants