XPath: implement lang() and id() core functions (#34594)

XPath's `lang()` and `id()` functions were still unimplemented.

Also:
* Add WPT tests for `id()`.
* Fix uniqueness check in `NodesetHelpers::document_order_unique`.
* Tweak the AST a bit to make it clearer to express "no predicates".
* Fix a parsing bug where "/" was attempted before "//", leaving the
"//" branch as always unused.

---
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix #34593 
- [x] There are tests for these changes

---------

Signed-off-by: Ville Lindholm <ville@lindholm.dev>
This commit is contained in:
Ville Lindholm 2025-06-02 22:00:13 +03:00 committed by GitHub
parent 1dfc14d2fb
commit 8cfb6e33fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 276 additions and 61 deletions

View file

@ -4481,7 +4481,9 @@ impl SelectorsElement for SelectorWrapper<'_> {
// a string containing commas (separating each language tag in
// a list) but the pseudo-class instead should be parsing and
// storing separate <ident> or <string>s for each language tag.
NonTSPseudoClass::Lang(ref lang) => extended_filtering(&self.get_lang(), lang),
NonTSPseudoClass::Lang(ref lang) => {
extended_filtering(&self.upcast::<Node>().get_lang().unwrap_or_default(), lang)
},
NonTSPseudoClass::ReadOnly => {
!Element::state(self).contains(NonTSPseudoClass::ReadWrite.state_flag())
@ -4821,24 +4823,7 @@ impl Element {
}
}
// https://html.spec.whatwg.org/multipage/#language
pub(crate) fn get_lang(&self) -> String {
self.upcast::<Node>()
.inclusive_ancestors(ShadowIncluding::Yes)
.filter_map(|node| {
node.downcast::<Element>().and_then(|el| {
el.get_attribute(&ns!(xml), &local_name!("lang"))
.or_else(|| el.get_attribute(&ns!(), &local_name!("lang")))
.map(|attr| String::from(attr.Value()))
})
// TODO: Check meta tags for a pragma-set default language
// TODO: Check HTTP Content-Language header
})
.next()
.unwrap_or(String::new())
}
pub(crate) fn state(&self) -> ElementState {
pub fn state(&self) -> ElementState {
self.state.get()
}

View file

@ -48,7 +48,7 @@ use style::properties::ComputedValues;
use style::selector_parser::{SelectorImpl, SelectorParser};
use style::stylesheets::{Stylesheet, UrlExtraData};
use uuid::Uuid;
use xml5ever::serialize as xml_serialize;
use xml5ever::{local_name, serialize as xml_serialize};
use crate::conversions::Convert;
use crate::document_loader::DocumentLoader;
@ -1470,6 +1470,21 @@ impl Node {
.map(|data| data.element_data.borrow().styles.primary().clone())
}
/// <https://html.spec.whatwg.org/multipage/#language>
pub(crate) fn get_lang(&self) -> Option<String> {
self.inclusive_ancestors(ShadowIncluding::Yes)
.filter_map(|node| {
node.downcast::<Element>().and_then(|el| {
el.get_attribute(&ns!(xml), &local_name!("lang"))
.or_else(|| el.get_attribute(&ns!(), &local_name!("lang")))
.map(|attr| String::from(attr.Value()))
})
// TODO: Check meta tags for a pragma-set default language
// TODO: Check HTTP Content-Language header
})
.next()
}
/// <https://dom.spec.whatwg.org/#assign-slotables-for-a-tree>
pub(crate) fn assign_slottables_for_a_tree(&self) {
// NOTE: This method traverses all descendants of the node and is potentially very

View file

@ -54,7 +54,7 @@ impl TryFrom<u16> for XPathResultType {
}
}
#[derive(JSTraceable, MallocSizeOf)]
#[derive(Debug, JSTraceable, MallocSizeOf)]
pub(crate) enum XPathResultValue {
Boolean(bool),
/// A IEEE-754 double-precision floating point number