Skip to content

script: Use stylo dom apis for querySelector/querySelectorAll#42991

Merged
simonwuelker merged 5 commits into
servo:mainfrom
simonwuelker:query-selector
Mar 4, 2026
Merged

script: Use stylo dom apis for querySelector/querySelectorAll#42991
simonwuelker merged 5 commits into
servo:mainfrom
simonwuelker:query-selector

Conversation

@simonwuelker
Copy link
Copy Markdown
Member

@simonwuelker simonwuelker commented Mar 3, 2026

Stylo has ready-to-use apis for Node::querySelector and Node::querySelectorAll. We currently instead set up our own selector queries, which is neither as correct nor as performant, so this change makes servo use the stylo interfaces instead.

This change contains a fair amount of unsafe code - review with caution!

Notably, this makes the tab component from the astro framework work correctly in servo (https://starlight.astro.build/components/tabs/). I've seen that (broken) component on multiple websites.

Fixes #41105
Testing: New tests start to pass

@simonwuelker simonwuelker requested a review from gterzian as a code owner March 3, 2026 13:49
@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Mar 3, 2026
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.

Great stuff! Perhaps other want to look at this too.

Comment on lines +104 to +110

/// Get a reference to the internal value.
///
/// ## SAFETY
/// This function effectively circumvents all the safety provided by `LayoutDom` as it allows
/// performing arbitrary (potentially mutating) operations on the value. Use with caution!
pub(crate) unsafe fn as_ref(self) -> &'dom T {
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 not sure this has to be unsafe. It's pub(crate) which means it cannot be called from layout (the real danger as layout accesses nodes from multiple threads). In addition, this consumes the LayoutDom, which is great, because it ensure that LayoutDom can't later be passed to layout.

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.

Nothing prevents someone from calling this method on a different thread in script though. pub(crate) doesn't really give us any safety guarantees.

LayoutDom itself is not Send. But its possible to wrap a LayoutDom<Node> in a ServoLayoutNode (safe) which is Send, then pass it to another thread (safe), call get_jsmanaged on it (safe) and then use as_ref to obtain a reference to the node. At that point we have multiple references to a non-sync value on diferent threads, so something we did has to be marked as unsafe. All the steps that exist so far are safe, so let's make as_ref unsafe.

Comment thread components/script/layout_dom/element.rs
Comment thread components/script/layout_dom/node.rs
Comment thread components/script/layout_dom/node.rs Outdated

// Step 3. Return the result of match a selector against a tree with selector
// and node’s root using scoping root node.
query_selector::<ServoLayoutElement<'dom>, Q>(
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 you know if this uses multiple threads or not?

Copy link
Copy Markdown
Member Author

@simonwuelker simonwuelker Mar 3, 2026

Choose a reason for hiding this comment

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

I don't know for certain but from quickly glancing at the code it doesn't seem to use threads. The DOM node we pass to query_selector is also not required to implement Send or Sync

{
let elements_with_id = self.document.elements_with_id(&id.0);

// SAFETY: ServoLayoutElement is known to have the same layout and alignment as LayoutDom<Element>
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.

Spooky, but it makes sense. Is it possible to use a compile time assertion that the sizes of the two data structures are the same?

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. We already depend on static_assertions via stylo, so I'll use that.

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.

Unfortunately that doesn't work because these types are generic and generic_const_exprs is not stable yet.

Comment thread components/script/layout_dom/node.rs Outdated
@jdm
Copy link
Copy Markdown
Member

jdm commented Mar 3, 2026

Note to self: check if #42976 triggers new asserts in this code.

@simonwuelker
Copy link
Copy Markdown
Member Author

simonwuelker commented Mar 3, 2026

Note to self: check if #42976 triggers new asserts in this code.

I've tested this and the new asserts do indeed trigger. The call to to_layout fails because we're not in layout at that point - is with_layout_state (from #42976) a correct fix for that problem?

@jdm
Copy link
Copy Markdown
Member

jdm commented Mar 3, 2026

Yes, that should do it.

@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Mar 4, 2026
@simonwuelker simonwuelker added this pull request to the merge queue Mar 4, 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 4, 2026
Merged via the queue into servo:main with commit 78efa18 Mar 4, 2026
31 checks passed
@simonwuelker simonwuelker deleted the query-selector branch March 4, 2026 15:01
@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 4, 2026
github-merge-queue Bot pushed a commit that referenced this pull request Mar 5, 2026
…_layout_dom` (#43032)

As requested during the review of
#42991. These functions are currently
called `{to, from}_layout_js` or sometimes `get_jsmanaged` - this change
unifies them as `{to, from}_layout_dom` for clarity.

Testing: Covered by existing tests

Signed-off-by: Simon Wülker <[email protected]>
offline-ant pushed a commit to offline-ant/havi that referenced this pull request Jun 4, 2026
…42991)

Stylo has ready-to-use apis for `Node::querySelector` and
`Node::querySelectorAll`. We currently instead set up our own selector
queries, which is neither as correct nor as performant, so this change
makes servo use the stylo interfaces instead.

This change contains a fair amount of unsafe code - review with caution!

Notably, this makes the tab component from the astro framework work
correctly in servo (https://starlight.astro.build/components/tabs/).
I've seen that (broken) component on multiple websites.

Fixes servo#41105
Testing: New tests start to pass

---------

Signed-off-by: Simon Wülker <[email protected]>
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.

CSS :scope selector in querySelectorAll() doesn't work alongside commas in the selector (which results in jQuery.find() breaking)

4 participants