Skip to content

script: Wire element for LargestContentfulPaint - #46553

Merged
jdm merged 3 commits into
servo:mainfrom
shubhamg13:lcp_id
Aug 1, 2026
Merged

jdm merged 3 commits into
servo:mainfrom
shubhamg13:lcp_id

Conversation

@shubhamg13

@shubhamg13 shubhamg13 commented Jul 16, 2026 •

Copy link
Copy Markdown
Member

Wire the logic for element on LargestContentfulPaint using OpaqueNode

While processing the ReflowResult, OpaqueNode is resolved and Element is stored in HashMap located in document.

Testing: New WPT Passed (+ To be Added in #46851)
Fixes: Part of #42000

@shubhamg13
shubhamg13 marked this pull request as ready for review July 16, 2026 05:23
@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Jul 16, 2026
Comment on lines +48 to +50
let address = UntrustedNodeAddress(id as *const c_void);
let node = unsafe { node::from_untrusted_node_address(address) };
DomRoot::downcast::<Element>(node).map(|el| String::from(el.Id()))

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.

How do you know that the node is still alive?
In general one should by all means avoid round-tripping a pointer through an integer, since it loses / exposes provenance. Can't we use a different mechanism to lookup the node?

@shubhamg13 shubhamg13 Jul 17, 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.

The element was just painted to screen in same layout so it must be alive.

Please enlighten me on this. This is what I achieved by referencing already implemented components in InspectorHighlight or DocumentShadowRoot.

Here is a open Zulip Topic for same

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.

The ID is sent from layout to the constellation, then back to the script thread. The element could have been removed from the document in that time and GCed.

@shubhamg13 shubhamg13 Jul 17, 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.

Please enlighten me with better appraoch to this, i am not familiar with this that much.

@jdm jdm Jul 17, 2026 •

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.

In general:

  • choose a script thread struct that will store the underlying object until the async notification is received
  • create a new ID that can be sent around that is not the pointer (eg. a uuid)
  • make it possible to map the ID to the stored object

This could be something like
largest_contentful_paint_candidates: DomRefCell<HashMap<LargestContentfulPaintId, Dom<Element>>>
which could live in ScriptThread, or maybe a single DomRefCell<Option<(LargestContentfulPaintId, Dom<Element>)>> in Document, or maybe there's somewhere else that makes more sense.

@shubhamg13 shubhamg13 Jul 27, 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.

Please take a look at the latest changes. Extracted some changes in #46828

Sorry for Earlier, I think I mixed few concepts and created a mess.

@shubhamg13 shubhamg13 Jul 29, 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.

I explored that idea of having HashMap of largest_contentful_paint_candidates. But I think we need to come back to script layer in one way or other to resolve the element.

But IMO By using Node::is_connected_with_browsing_context, we make sure Node is still alive.

Let me know your thoughts. I am supporting my point with WPTs as well. Please take a look on #46851 as well.

P.S: I will rebase when WPT is merged.

jschwe
jschwe previously requested changes Jul 16, 2026
@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 Jul 16, 2026
@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 Jul 17, 2026
@jdm jdm 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 Jul 21, 2026
@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 Jul 27, 2026
@shubhamg13
shubhamg13 requested review from jdm, jschwe and xiaochengh July 27, 2026 08:03
@shubhamg13
shubhamg13 marked this pull request as draft July 28, 2026 07:58
@shubhamg13 shubhamg13 changed the title Expose id on LargestContentfulPaint script: Wire element for LargestContentfulPaint Jul 28, 2026
url: url.map(|u| DOMString::from(u.as_str())).unwrap_or_default(),
element: None,
element_id: element_id.map(DOMString::from).unwrap_or_default(),
element: element.map(|el| Dom::from_ref(&*el)),

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.

Do not abbreviate variable names here and everywhere else in this change: https://book.servo.org/contributing/style-guide.html#abbreviation

Suggested change
element: element.map(|el| Dom::from_ref(&*el)),
element: element.map(|element| Dom::from_ref(&*element)),

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.

Aye Aye!


/// <https://www.w3.org/TR/largest-contentful-paint/#dom-largestcontentfulpaint-id>
fn Id(&self) -> DOMString {
self.element_id.clone()

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't this just read the id attribute from self.element. Why must the id be passed into layout and then back out again?

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 Noted.

candidate_id,
} => {
let mut element: Option<DomRoot<Element>> = None;
if let Some(candidate) = candidate_id {

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's very confusing to rename this, so just call it candidate_id as well:

Suggested change
if let Some(candidate) = candidate_id {
if let Some(candidate_id) = candidate_id {

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.

Aye Aye!

// captured during display list construction is still
// valid in the DOM.
let address = UntrustedNodeAddress(candidate.0 as *const c_void);
let node = unsafe { node::from_untrusted_node_address(address) };

@mrobinson mrobinson Jul 28, 2026 •

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.

This is still an OpaqueNode that comes back to the script from an asynchronous message. How can you be sure that's it's still valid? You updated your change, but you didn't explain how your update responded to the concerns raised before, so it's hard to say what the mechanism is.

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.

By using Node::is_connected_with_browsing_context, we make sure Node is still alive.

IMo, missing WPT test case is the reason for proper testing of raised concerns. So, I added WPT #46851.

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.

The issue is that the Node might have been garbage collected at this point and the pointer you are dereferencing here might point to freed memory or even at some other allocated memory (even another Node). Nothing you can call on the Node after this dereference is reliable or can fix this issue. I think it's actually just impossible to get a reference to a node based something you calculate from the display list (for good reason...the display list shouldn't prevent garbage collection). It may be the case that to implement LCP properly, it needs to move to script from paint.

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.

Acknowledged. Yeah I agree. Let me process this information and I will get back to this.

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.

Now while processing the ReflowResult, OpaqueNode is resolved and Element is stored in HashMap located in document.

Almost identical to how we prcoess handle_pending_images_post_reflow.

@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

🤖 Opened new upstream WPT pull request (web-platform-tests/wpt#61595) with upstreamable changes.

@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

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

@shubhamg13
shubhamg13 force-pushed the lcp_id branch 2 times, most recently from 6b161a3 to 712d27f Compare July 31, 2026 07:02
Signed-off-by: Shubham Gupta <shubham.gupta@chromium.org>
Signed-off-by: Shubham Gupta <shubham.gupta@chromium.org>
@shubhamg13
shubhamg13 marked this pull request as ready for review July 31, 2026 08:01

@jdm jdm left a comment

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.

Almost there!

Comment thread components/script/dom/window/window.rs Outdated
fn process_lcp_candidate_post_reflow(
&self,
candidate: &LCPCandidate,
node_id: usize,

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.

Let's make this the UntrustedNodeAddres instead.

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. Done.

Comment thread components/shared/layout/lib.rs Outdated
/// The LCP candidate during this layout pass, if any.
pub lcp_candidate: Option<LCPCandidate>,
/// The OpaqueNode ID for the LCP candidate if any.
pub lcp_node_id: Option<usize>,

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.

This should be a UntrustedNodeAddress instead.

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.

Aye Aye!

@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 Jul 31, 2026
Signed-off-by: Shubham Gupta <shubham.gupta@chromium.org>
@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 Aug 1, 2026
@shubhamg13
shubhamg13 requested a review from jdm August 1, 2026 06:58

@jdm jdm left a comment

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.

Thanks!

@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Aug 1, 2026
@jdm
jdm dismissed stale reviews from mrobinson and jschwe August 1, 2026 09:06

Addressed.

@jdm
jdm added this pull request to the merge queue Aug 1, 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 1, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 1, 2026
@servo-highfive servo-highfive added S-tests-failed The changes caused existing tests to fail. and removed S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. labels Aug 1, 2026
@jdm
jdm added this pull request to the merge queue Aug 1, 2026
@servo-highfive servo-highfive added S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. and removed S-tests-failed The changes caused existing tests to fail. labels Aug 1, 2026
Merged via the queue into servo:main with commit 3f08ca6 Aug 1, 2026
42 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 1, 2026
@shubhamg13
shubhamg13 deleted the lcp_id branch August 2, 2026 03:09
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.

6 participants