fix(selectors): use full validity state in DOM queries

This commit is contained in:
ldm0
2026-09-28 01:47:00 +08:00
parent 3299982421
commit dae2fde161
10 changed files with 494 additions and 69 deletions
@@ -20,26 +20,72 @@ impl DocumentRuntime {
&self,
root: Option<DomHandle>,
selector: &str,
) -> Result<Option<DomHandle>, SelectorError> {
self.query_selector_with_optional_validity_states(root, selector, None)
}
pub(crate) fn query_selector_with_validity_states(
&self,
root: Option<DomHandle>,
selector: &str,
validity_states: &HashMap<DomHandle, bool>,
) -> Result<Option<DomHandle>, SelectorError> {
self.query_selector_with_optional_validity_states(root, selector, Some(validity_states))
}
fn query_selector_with_optional_validity_states(
&self,
root: Option<DomHandle>,
selector: &str,
validity_states: Option<&HashMap<DomHandle, bool>>,
) -> Result<Option<DomHandle>, SelectorError> {
self.selector_debug.record_query_selector();
trace_hot_selector_api("querySelector", root, selector);
match root {
Some(root) => {
if self.dom_host.node(root).is_some_and(Node::is_document) {
let handles = self.selector_engine.query_selector_all_in_host(
&self.dom_host,
root,
selector,
)?;
let handles = match validity_states {
Some(states) => self
.selector_engine
.query_selector_all_in_host_with_validity_states(
&self.dom_host,
root,
selector,
states,
)?,
None => self.selector_engine.query_selector_all_in_host(
&self.dom_host,
root,
selector,
)?,
};
Ok(handles.into_iter().next())
} else {
self.selector_engine
.query_selector_in_host(&self.dom_host, root, selector)
match validity_states {
Some(states) => self
.selector_engine
.query_selector_in_host_with_validity_states(
&self.dom_host,
root,
selector,
states,
),
None => self.selector_engine.query_selector_in_host(
&self.dom_host,
root,
selector,
),
}
}
}
None => self
.selector_engine
.query_selector_host(&self.dom_host, selector),
None => match validity_states {
Some(states) => self
.selector_engine
.query_selector_host_with_validity_states(&self.dom_host, selector, states),
None => self
.selector_engine
.query_selector_host(&self.dom_host, selector),
},
}
}
@@ -47,40 +93,123 @@ impl DocumentRuntime {
&self,
root: Option<DomHandle>,
selector: &str,
) -> Result<Vec<DomHandle>, SelectorError> {
self.query_selector_all_with_optional_validity_states(root, selector, None)
}
pub(crate) fn query_selector_all_with_validity_states(
&self,
root: Option<DomHandle>,
selector: &str,
validity_states: &HashMap<DomHandle, bool>,
) -> Result<Vec<DomHandle>, SelectorError> {
self.query_selector_all_with_optional_validity_states(root, selector, Some(validity_states))
}
fn query_selector_all_with_optional_validity_states(
&self,
root: Option<DomHandle>,
selector: &str,
validity_states: Option<&HashMap<DomHandle, bool>>,
) -> Result<Vec<DomHandle>, SelectorError> {
self.selector_debug.record_query_selector_all();
trace_hot_selector_api("querySelectorAll", root, selector);
match root {
Some(root) => {
let handles = self.selector_engine.query_selector_all_in_host(
&self.dom_host,
root,
selector,
)?;
Ok(handles)
}
None => self
.selector_engine
.query_selector_all_host(&self.dom_host, selector),
Some(root) => match validity_states {
Some(states) => self
.selector_engine
.query_selector_all_in_host_with_validity_states(
&self.dom_host,
root,
selector,
states,
),
None => {
self.selector_engine
.query_selector_all_in_host(&self.dom_host, root, selector)
}
},
None => match validity_states {
Some(states) => self
.selector_engine
.query_selector_all_host_with_validity_states(&self.dom_host, selector, states),
None => self
.selector_engine
.query_selector_all_host(&self.dom_host, selector),
},
}
}
pub(crate) fn matches(&self, node: DomHandle, selector: &str) -> Result<bool, SelectorError> {
self.matches_with_optional_validity_states(node, selector, None)
}
pub(crate) fn matches_with_validity_states(
&self,
node: DomHandle,
selector: &str,
validity_states: &HashMap<DomHandle, bool>,
) -> Result<bool, SelectorError> {
self.matches_with_optional_validity_states(node, selector, Some(validity_states))
}
fn matches_with_optional_validity_states(
&self,
node: DomHandle,
selector: &str,
validity_states: Option<&HashMap<DomHandle, bool>>,
) -> Result<bool, SelectorError> {
self.selector_debug.record_matches();
trace_hot_selector_api("matches", Some(node), selector);
self.selector_engine
.matches_host(&self.dom_host, node, selector)
match validity_states {
Some(states) => self.selector_engine.matches_host_with_validity_states(
&self.dom_host,
node,
selector,
states,
),
None => self
.selector_engine
.matches_host(&self.dom_host, node, selector),
}
}
pub(crate) fn closest(
&self,
node: DomHandle,
selector: &str,
) -> Result<Option<DomHandle>, SelectorError> {
self.closest_with_optional_validity_states(node, selector, None)
}
pub(crate) fn closest_with_validity_states(
&self,
node: DomHandle,
selector: &str,
validity_states: &HashMap<DomHandle, bool>,
) -> Result<Option<DomHandle>, SelectorError> {
self.closest_with_optional_validity_states(node, selector, Some(validity_states))
}
fn closest_with_optional_validity_states(
&self,
node: DomHandle,
selector: &str,
validity_states: Option<&HashMap<DomHandle, bool>>,
) -> Result<Option<DomHandle>, SelectorError> {
self.selector_debug.record_closest();
trace_hot_selector_api("closest", Some(node), selector);
self.selector_engine
.closest_host(&self.dom_host, node, selector)
match validity_states {
Some(states) => self.selector_engine.closest_host_with_validity_states(
&self.dom_host,
node,
selector,
states,
),
None => self
.selector_engine
.closest_host(&self.dom_host, node, selector),
}
}
pub(crate) fn selector_debug_snapshot(&self) -> SelectorDebugSnapshot {
@@ -253,8 +253,8 @@ pub(in crate::native_bridge) use self::validation::{
control_check_validity_callback, control_matches_validity_pseudo,
control_report_validity_callback, control_set_custom_validity_callback,
control_validation_message_getter_function, control_validity_getter_function,
control_will_validate_getter_function, dispatch_invalid_event, form_check_validity_callback,
form_report_validity_callback, form_validate_for_submission,
control_validity_pseudo_state, control_will_validate_getter_function, dispatch_invalid_event,
form_check_validity_callback, form_report_validity_callback, form_validate_for_submission,
};
#[derive(strum::EnumString, strum::IntoStaticStr)]
@@ -240,6 +240,20 @@ pub(in crate::native_bridge) fn control_matches_validity_pseudo(
if !matches!(selector, ":valid" | ":invalid") {
return None;
}
let invalid = control_validity_pseudo_state(scope, runtime_ptr, handle)?;
Some(if selector == ":valid" {
!invalid
} else {
invalid
})
}
pub(in crate::native_bridge) fn control_validity_pseudo_state(
scope: &mut v8::PinScope<'_, '_>,
runtime_ptr: *mut JsContextHost,
handle: DomHandle,
) -> Option<bool> {
let runtime = unsafe { &*runtime_ptr };
if runtime
.dom_host()
.node(handle)
@@ -250,24 +264,21 @@ pub(in crate::native_bridge) fn control_matches_validity_pseudo(
let valid = controls
.into_iter()
.all(|control| control_satisfies_constraints(scope, runtime_ptr, control));
return Some(if selector == ":valid" { valid } else { !valid });
return Some(!valid);
}
let Some(element) = runtime.dom_host().node(handle).and_then(Node::as_element) else {
return Some(false);
};
let element = runtime.dom_host().node(handle).and_then(Node::as_element)?;
if is_form_associated_custom_element_handle(runtime, handle) {
if !control_will_validate(runtime, handle) {
return Some(false);
return None;
}
let valid = control_validity(scope, runtime_ptr, handle).valid();
return Some(if selector == ":valid" { valid } else { !valid });
return Some(!control_validity(scope, runtime_ptr, handle).valid());
}
if !element_matches_validity_pseudo(runtime, handle, element) {
return Some(false);
return None;
}
let valid = control_is_readonly_barred_from_constraint_validation(element)
|| control_validity(scope, runtime_ptr, handle).valid();
Some(if selector == ":valid" { valid } else { !valid })
Some(!valid)
}
pub(in crate::native_bridge) fn control_validation_message_getter_function<'s>(
@@ -1,5 +1,6 @@
use super::super::{
CollectionKind, LiveCollectionQueryKind, collections, encode_tag_name_ns_query,
CollectionKind, DomHandle, JsContextHost, LiveCollectionQueryKind, collections,
encode_tag_name_ns_query,
node::{
node_is_document, node_runtime_and_handle_from_args,
node_runtime_and_handle_from_args_or_detached,
@@ -8,9 +9,12 @@ use super::super::{
throw_incompatible_method_receiver, throw_native_selector_error_for_selector,
},
};
use super::forms::control_matches_validity_pseudo;
use std::collections::HashMap;
use super::forms::{control_matches_validity_pseudo, control_validity_pseudo_state};
use crate::native_bridge::set_wrapped_handle_or_null_for_receiver;
use crate::{
dom::native::Node,
util::{
call_object_method, object_number_property, object_property_as_object, v8_string, v8str,
},
@@ -119,14 +123,17 @@ pub(in crate::native_bridge) fn node_query_selector_callback<'s>(
let Some(parsed) = webidl::parse_args::<ElementQuerySelectorArgs>(scope, &args) else {
return;
};
match unsafe { &*runtime_ptr }.query_selector(Some(handle), &parsed.selectors) {
Ok(handle) => set_wrapped_handle_or_null_for_receiver(
scope,
&mut rv,
runtime_ptr,
args.this(),
handle,
let validity_states = selector_validity_states(scope, runtime_ptr, &parsed.selectors);
let result = match validity_states.as_ref() {
Some(states) => unsafe { &*runtime_ptr }.query_selector_with_validity_states(
Some(handle),
&parsed.selectors,
states,
),
None => unsafe { &*runtime_ptr }.query_selector(Some(handle), &parsed.selectors),
};
match result {
Ok(handle) => set_wrapped_handle_or_null_for_receiver(scope, &mut rv, runtime_ptr, args.this(), handle),
Err(error) => throw_native_selector_error_for_selector(scope, &parsed.selectors, &error),
}
}
@@ -175,7 +182,16 @@ pub(in crate::native_bridge) fn node_query_selector_all_callback<'s>(
let Some(parsed) = webidl::parse_args::<ElementQuerySelectorAllArgs>(scope, &args) else {
return;
};
match unsafe { &*runtime_ptr }.query_selector_all(Some(handle), &parsed.selectors) {
let validity_states = selector_validity_states(scope, runtime_ptr, &parsed.selectors);
let result = match validity_states.as_ref() {
Some(states) => unsafe { &*runtime_ptr }.query_selector_all_with_validity_states(
Some(handle),
&parsed.selectors,
states,
),
None => unsafe { &*runtime_ptr }.query_selector_all(Some(handle), &parsed.selectors),
};
match result {
Ok(handles) => {
let Some(context) = args.this().get_creation_context(scope) else {
rv.set_null();
@@ -234,8 +250,13 @@ pub(in crate::native_bridge) fn node_matches_callback<'s>(
rv.set_bool(is_match);
return;
}
let validity_states = selector_validity_states(scope, runtime_ptr, &parsed.selectors);
let runtime = unsafe { &*runtime_ptr };
match runtime.matches(handle, &parsed.selectors) {
let result = match validity_states.as_ref() {
Some(states) => runtime.matches_with_validity_states(handle, &parsed.selectors, states),
None => runtime.matches(handle, &parsed.selectors),
};
match result {
Ok(true) => rv.set_bool(true),
Ok(false) if node_matches_needs_owner_document_query(scope, args.this()) => rv.set_bool(
node_matches_owner_document_query(scope, args.this(), &parsed.selectors),
@@ -320,7 +341,14 @@ pub(in crate::native_bridge) fn node_closest_callback<'s>(
let Some(parsed) = webidl::parse_args::<ElementClosestArgs>(scope, &args) else {
return;
};
match unsafe { &*runtime_ptr }.closest(handle, &parsed.selectors) {
let validity_states = selector_validity_states(scope, runtime_ptr, &parsed.selectors);
let result = match validity_states.as_ref() {
Some(states) => {
unsafe { &*runtime_ptr }.closest_with_validity_states(handle, &parsed.selectors, states)
}
None => unsafe { &*runtime_ptr }.closest(handle, &parsed.selectors),
};
match result {
Ok(Some(handle)) if receiver_is_detached => {
match super::super::document::detached_native_object_for_handle(
scope,
@@ -342,6 +370,35 @@ pub(in crate::native_bridge) fn node_closest_callback<'s>(
}
}
fn selector_validity_states(
scope: &mut v8::PinScope<'_, '_>,
runtime_ptr: *mut JsContextHost,
selector: &str,
) -> Option<HashMap<DomHandle, bool>> {
if !crate::selector::dom_api_selector_uses_validity_pseudo(
unsafe { &*runtime_ptr }.dom_host(),
selector,
) {
return None;
}
let handles = unsafe { &*runtime_ptr }
.dom_host()
.dom()
.nodes()
.filter(|node| node.is_element())
.map(Node::id)
.collect::<Vec<_>>();
Some(
handles
.into_iter()
.filter_map(|handle| {
control_validity_pseudo_state(scope, runtime_ptr, handle)
.map(|invalid| (handle, invalid))
})
.collect(),
)
}
pub(in crate::native_bridge) fn node_get_elements_by_tag_name_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -540,6 +540,61 @@ fn live_input_pattern_uses_native_regexp_after_global_mutation() {
assert_eq!(result, "false/true|true/false|false/true");
}
#[test]
fn live_input_pattern_drives_dom_selector_apis() {
let mut vm = new_storage_test_vm("https://live-pattern-selectors.test/");
let result = vm
.eval(
r#"
(() => {
const root = document.createElement('div');
root.id = 'patterns';
root.innerHTML =
'<input id="bad" value="AAA" pattern="[0-9][A-Z]{3}">' +
'<input id="good" value="0AAA" pattern="[0-9][A-Z]{3}">';
const ids = selector =>
[...root.querySelectorAll(selector)].map(element => element.id).join(',');
const bad = root.querySelector('#bad');
const good = root.querySelector('#good');
const initial = [
ids(':valid'),
ids(String.raw`:\76 alid`),
ids(':invalid'),
root.querySelector('input:invalid').id,
bad.matches('input#bad:invalid'),
good.matches('#good:valid'),
bad.closest('div:has(> input#bad:invalid)').id
];
bad.value = '0BBB';
good.value = 'BBB';
const advanced = document.createElement('input');
advanced.id = 'advanced';
advanced.pattern = '([a-z])\\1';
advanced.value = 'ab';
root.append(advanced);
return [
...initial,
ids(':valid'),
ids(':invalid'),
advanced.matches('#advanced:invalid'),
root.querySelector('#advanced:invalid').id
].join('|');
})()
"#,
)
.expect("live input patterns should drive DOM selector APIs");
assert_eq!(
result,
"good|good|bad|bad|true|true|patterns|bad|good,advanced|true|advanced"
);
}
#[test]
fn detached_select_and_option_track_selection_state() {
let mut vm = new_storage_test_vm("https://detached-select-state.test/path/page.html");
+75 -9
View File
@@ -56,7 +56,8 @@ pub use stylo::{
StyloSourceStyleInvalidationTargetResultRecord, StyloStateInvalidationRoot,
StyloStyleInvalidationQuery, StyloStyleInvalidationSnapshot,
StyloStyleInvalidationSnapshotAttribute, StyloStyleSourceScope,
StyloStylesheetSourceScopeFallbackInput, is_svg_presentation_attribute_name,
StyloStylesheetSourceScopeFallbackInput, dom_api_selector_uses_validity_pseudo,
is_svg_presentation_attribute_name,
stylo_attribute_change_can_skip_fallback_without_dependency,
stylo_attribute_change_can_use_retained_invalidator, stylo_element_dependency_snapshot,
stylo_fallback_roots_plan, stylo_flat_tree_heading_descendants,
@@ -80,7 +81,7 @@ pub use stylo::{
use moli_dom::native::DomHost;
use moli_dom::{NodeId, native::NativeDom};
use std::sync::OnceLock;
use std::{collections::HashMap, sync::OnceLock};
// Temporary self-alias so the extracted module tree can keep compiling while
// `moli` keeps its historical `crate::selector::*` references.
@@ -147,7 +148,17 @@ impl QueryEngine {
host: &DomHost,
selector: &str,
) -> Result<Option<NodeId>, SelectorError> {
self.adapter().query_selector(host, selector)
self.adapter().query_selector(host, selector, None)
}
pub fn query_selector_host_with_validity_states(
&self,
host: &DomHost,
selector: &str,
validity_states: &HashMap<NodeId, bool>,
) -> Result<Option<NodeId>, SelectorError> {
self.adapter()
.query_selector(host, selector, Some(validity_states))
}
pub fn query_selector_all_host(
@@ -155,7 +166,17 @@ impl QueryEngine {
host: &DomHost,
selector: &str,
) -> Result<Vec<NodeId>, SelectorError> {
self.adapter().query_selector_all(host, selector)
self.adapter().query_selector_all(host, selector, None)
}
pub fn query_selector_all_host_with_validity_states(
&self,
host: &DomHost,
selector: &str,
validity_states: &HashMap<NodeId, bool>,
) -> Result<Vec<NodeId>, SelectorError> {
self.adapter()
.query_selector_all(host, selector, Some(validity_states))
}
pub fn query_selector_in_host(
@@ -164,7 +185,18 @@ impl QueryEngine {
root: NodeId,
selector: &str,
) -> Result<Option<NodeId>, SelectorError> {
self.adapter().query_selector_in(host, root, selector)
self.adapter().query_selector_in(host, root, selector, None)
}
pub fn query_selector_in_host_with_validity_states(
&self,
host: &DomHost,
root: NodeId,
selector: &str,
validity_states: &HashMap<NodeId, bool>,
) -> Result<Option<NodeId>, SelectorError> {
self.adapter()
.query_selector_in(host, root, selector, Some(validity_states))
}
pub fn query_selector_all_in_host(
@@ -173,7 +205,19 @@ impl QueryEngine {
root: NodeId,
selector: &str,
) -> Result<Vec<NodeId>, SelectorError> {
self.adapter().query_selector_all_in(host, root, selector)
self.adapter()
.query_selector_all_in(host, root, selector, None)
}
pub fn query_selector_all_in_host_with_validity_states(
&self,
host: &DomHost,
root: NodeId,
selector: &str,
validity_states: &HashMap<NodeId, bool>,
) -> Result<Vec<NodeId>, SelectorError> {
self.adapter()
.query_selector_all_in(host, root, selector, Some(validity_states))
}
pub fn matches_host(
@@ -182,7 +226,18 @@ impl QueryEngine {
node_id: NodeId,
selector: &str,
) -> Result<bool, SelectorError> {
self.adapter().matches(host, node_id, selector)
self.adapter().matches(host, node_id, selector, None)
}
pub fn matches_host_with_validity_states(
&self,
host: &DomHost,
node_id: NodeId,
selector: &str,
validity_states: &HashMap<NodeId, bool>,
) -> Result<bool, SelectorError> {
self.adapter()
.matches(host, node_id, selector, Some(validity_states))
}
pub fn matches_with_scope_host(
@@ -193,7 +248,7 @@ impl QueryEngine {
scope_root: NodeId,
) -> Result<bool, SelectorError> {
self.adapter()
.matches_with_scope(host, node_id, selector, scope_root)
.matches_with_scope(host, node_id, selector, scope_root, None)
}
pub fn closest_host(
@@ -202,7 +257,18 @@ impl QueryEngine {
start: NodeId,
selector: &str,
) -> Result<Option<NodeId>, SelectorError> {
self.adapter().closest(host, start, selector)
self.adapter().closest(host, start, selector, None)
}
pub fn closest_host_with_validity_states(
&self,
host: &DomHost,
start: NodeId,
selector: &str,
validity_states: &HashMap<NodeId, bool>,
) -> Result<Option<NodeId>, SelectorError> {
self.adapter()
.closest(host, start, selector, Some(validity_states))
}
pub fn query_selector(
+76 -11
View File
@@ -1,9 +1,17 @@
use selectors::{Element as SelectorsElement, matching::MatchingContext};
use std::collections::HashMap;
use selectors::{
Element as SelectorsElement,
matching::MatchingContext,
parser::{Component, RelativeSelector},
visitor::SelectorVisitor,
};
use style::{
dom::TDocument,
dom_apis::{
MayUseInvalidation, QueryAll, QueryFirst, element_closest, element_matches, query_selector,
},
selector_parser::{NonTSPseudoClass, SelectorImpl},
shared_lock::SharedRwLock,
};
@@ -21,6 +29,45 @@ pub fn stylo_flat_tree_heading_descendants(host: &DomHost, root: NodeId) -> Vec<
query::flat_tree_heading_descendants(host, root)
}
/// Reports whether a DOM API selector contains `:valid` or `:invalid`,
/// including escaped names and pseudos nested inside functional selectors.
pub fn dom_api_selector_uses_validity_pseudo(host: &DomHost, selector: &str) -> bool {
let Ok(ParsedDomApiSelectorList::Parsed(selectors)) =
parse_dom_api_selector_list(host, selector)
else {
return false;
};
let mut visitor = ValidityPseudoVisitor { found: false };
for selector in selectors.slice() {
if !selector.visit(&mut visitor) {
break;
}
}
visitor.found
}
struct ValidityPseudoVisitor {
found: bool,
}
impl SelectorVisitor for ValidityPseudoVisitor {
type Impl = SelectorImpl;
fn visit_simple_selector(&mut self, component: &Component<Self::Impl>) -> bool {
self.found = matches!(
component,
Component::NonTSPseudoClass(NonTSPseudoClass::Valid | NonTSPseudoClass::Invalid)
);
!self.found
}
fn visit_relative_selector_list(&mut self, selectors: &[RelativeSelector<Self::Impl>]) -> bool {
selectors
.iter()
.all(|selector| selector.selector.visit(self))
}
}
mod atoms;
mod invalidation;
mod presentation;
@@ -136,6 +183,7 @@ impl StyloDomApiAdapter {
&'a self,
host: &'a DomHost,
atom_cache: &'a QueryAtomCache,
validity_states: Option<&'a HashMap<NodeId, bool>>,
) -> QueryDocument<'a> {
QueryDocument::new(
host,
@@ -143,6 +191,7 @@ impl StyloDomApiAdapter {
&self.shared_lock,
None,
atom_cache,
validity_states,
)
}
@@ -151,15 +200,25 @@ impl StyloDomApiAdapter {
host: &'a DomHost,
handle: NodeId,
atom_cache: &'a QueryAtomCache,
validity_states: Option<&'a HashMap<NodeId, bool>>,
) -> Option<QueryNode<'a>> {
host.node(handle)
.map(|_| QueryNode::new(host, handle, &self.shared_lock, None, atom_cache))
host.node(handle).map(|_| {
QueryNode::new(
host,
handle,
&self.shared_lock,
None,
atom_cache,
validity_states,
)
})
}
pub(super) fn query_selector_all(
&self,
host: &DomHost,
selector: &str,
validity_states: Option<&HashMap<NodeId, bool>>,
) -> Result<Vec<NodeId>, SelectorError> {
let atom_cache = QueryAtomCache::default();
let selector_list = match parse_dom_api_selector_list(host, selector)? {
@@ -169,7 +228,7 @@ impl StyloDomApiAdapter {
let mut results =
<QueryAll as style::dom_apis::SelectorQuery<QueryElement<'_>>>::Output::default();
query_selector::<QueryElement<'_>, QueryAll>(
self.document(host, &atom_cache).as_node(),
self.document(host, &atom_cache, validity_states).as_node(),
&selector_list,
&mut results,
MayUseInvalidation::No,
@@ -182,6 +241,7 @@ impl StyloDomApiAdapter {
host: &DomHost,
root: NodeId,
selector: &str,
validity_states: Option<&HashMap<NodeId, bool>>,
) -> Result<Vec<NodeId>, SelectorError> {
if host.node(root).is_none() {
return Ok(Vec::new());
@@ -191,7 +251,7 @@ impl StyloDomApiAdapter {
ParsedDomApiSelectorList::EmptyKnownPseudoElement => return Ok(Vec::new()),
ParsedDomApiSelectorList::Parsed(selector_list) => selector_list,
};
let Some(root) = self.node(host, root, &atom_cache) else {
let Some(root) = self.node(host, root, &atom_cache, validity_states) else {
return Ok(Vec::new());
};
let mut results =
@@ -209,6 +269,7 @@ impl StyloDomApiAdapter {
&self,
host: &DomHost,
selector: &str,
validity_states: Option<&HashMap<NodeId, bool>>,
) -> Result<Option<NodeId>, SelectorError> {
let atom_cache = QueryAtomCache::default();
let selector_list = match parse_dom_api_selector_list(host, selector)? {
@@ -218,7 +279,7 @@ impl StyloDomApiAdapter {
let mut result =
<QueryFirst as style::dom_apis::SelectorQuery<QueryElement<'_>>>::Output::default();
query_selector::<QueryElement<'_>, QueryFirst>(
self.document(host, &atom_cache).as_node(),
self.document(host, &atom_cache, validity_states).as_node(),
&selector_list,
&mut result,
MayUseInvalidation::No,
@@ -231,6 +292,7 @@ impl StyloDomApiAdapter {
host: &DomHost,
root: NodeId,
selector: &str,
validity_states: Option<&HashMap<NodeId, bool>>,
) -> Result<Option<NodeId>, SelectorError> {
if host.node(root).is_none() {
return Ok(None);
@@ -240,7 +302,7 @@ impl StyloDomApiAdapter {
ParsedDomApiSelectorList::EmptyKnownPseudoElement => return Ok(None),
ParsedDomApiSelectorList::Parsed(selector_list) => selector_list,
};
let Some(root) = self.node(host, root, &atom_cache) else {
let Some(root) = self.node(host, root, &atom_cache, validity_states) else {
return Ok(None);
};
let mut result =
@@ -259,6 +321,7 @@ impl StyloDomApiAdapter {
host: &DomHost,
handle: NodeId,
selector: &str,
validity_states: Option<&HashMap<NodeId, bool>>,
) -> Result<bool, SelectorError> {
let atom_cache = QueryAtomCache::default();
let selector_list = match parse_dom_api_selector_list(host, selector)? {
@@ -266,7 +329,7 @@ impl StyloDomApiAdapter {
ParsedDomApiSelectorList::Parsed(selector_list) => selector_list,
};
let Some(element) = self
.node(host, handle, &atom_cache)
.node(host, handle, &atom_cache, validity_states)
.and_then(QueryNode::as_element)
else {
return Ok(false);
@@ -284,6 +347,7 @@ impl StyloDomApiAdapter {
handle: NodeId,
selector: &str,
scope_root: NodeId,
validity_states: Option<&HashMap<NodeId, bool>>,
) -> Result<bool, SelectorError> {
let selector_list = match parse_dom_api_selector_list(host, selector)? {
ParsedDomApiSelectorList::EmptyKnownPseudoElement => return Ok(false),
@@ -291,13 +355,13 @@ impl StyloDomApiAdapter {
};
let atom_cache = QueryAtomCache::default();
let Some(element) = self
.node(host, handle, &atom_cache)
.node(host, handle, &atom_cache, validity_states)
.and_then(QueryNode::as_element)
else {
return Ok(false);
};
let Some(scope_element) = self
.node(host, scope_root, &atom_cache)
.node(host, scope_root, &atom_cache, validity_states)
.and_then(QueryNode::as_element)
else {
return Ok(false);
@@ -325,6 +389,7 @@ impl StyloDomApiAdapter {
host: &DomHost,
handle: NodeId,
selector: &str,
validity_states: Option<&HashMap<NodeId, bool>>,
) -> Result<Option<NodeId>, SelectorError> {
let atom_cache = QueryAtomCache::default();
let selector_list = match parse_dom_api_selector_list(host, selector)? {
@@ -332,7 +397,7 @@ impl StyloDomApiAdapter {
ParsedDomApiSelectorList::Parsed(selector_list) => selector_list,
};
let Some(element) = self
.node(host, handle, &atom_cache)
.node(host, handle, &atom_cache, validity_states)
.and_then(QueryNode::as_element)
else {
return Ok(None);
+21
View File
@@ -1,4 +1,5 @@
use std::{
collections::HashMap,
fmt,
hash::{Hash, Hasher},
ptr::NonNull,
@@ -44,6 +45,7 @@ pub(in crate::stylo) struct QueryNode<'a> {
shared_lock: &'a SharedRwLock,
style_data: Option<&'a StyloElementDataStore>,
atom_cache: &'a QueryAtomCache,
validity_states: Option<&'a HashMap<NodeId, bool>>,
}
impl fmt::Debug for QueryNode<'_> {
@@ -74,6 +76,7 @@ impl<'a> QueryNode<'a> {
shared_lock: &'a SharedRwLock,
style_data: Option<&'a StyloElementDataStore>,
atom_cache: &'a QueryAtomCache,
validity_states: Option<&'a HashMap<NodeId, bool>>,
) -> Self {
Self {
host,
@@ -81,6 +84,7 @@ impl<'a> QueryNode<'a> {
shared_lock,
style_data,
atom_cache,
validity_states,
}
}
@@ -100,6 +104,7 @@ impl<'a> QueryNode<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -111,6 +116,7 @@ impl<'a> QueryNode<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -123,6 +129,7 @@ impl<'a> QueryNode<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -140,6 +147,7 @@ pub(in crate::stylo) struct QueryElement<'a> {
shared_lock: &'a SharedRwLock,
style_data: Option<&'a StyloElementDataStore>,
atom_cache: &'a QueryAtomCache,
validity_states: Option<&'a HashMap<NodeId, bool>>,
}
impl fmt::Debug for QueryElement<'_> {
@@ -170,6 +178,7 @@ pub(in crate::stylo) struct QueryDocument<'a> {
shared_lock: &'a SharedRwLock,
style_data: Option<&'a StyloElementDataStore>,
atom_cache: &'a QueryAtomCache,
validity_states: Option<&'a HashMap<NodeId, bool>>,
}
impl fmt::Debug for QueryDocument<'_> {
@@ -200,6 +209,7 @@ impl<'a> QueryDocument<'a> {
shared_lock: &'a SharedRwLock,
style_data: Option<&'a StyloElementDataStore>,
atom_cache: &'a QueryAtomCache,
validity_states: Option<&'a HashMap<NodeId, bool>>,
) -> Self {
Self {
host,
@@ -207,6 +217,7 @@ impl<'a> QueryDocument<'a> {
shared_lock,
style_data,
atom_cache,
validity_states,
}
}
@@ -226,6 +237,7 @@ pub(in crate::stylo) struct QueryShadowRoot<'a> {
shared_lock: &'a SharedRwLock,
style_data: Option<&'a StyloElementDataStore>,
atom_cache: &'a QueryAtomCache,
validity_states: Option<&'a HashMap<NodeId, bool>>,
}
impl fmt::Debug for QueryShadowRoot<'_> {
@@ -269,6 +281,7 @@ impl<'a> TDocument for QueryDocument<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
}
}
@@ -300,6 +313,7 @@ impl<'a> TNode for QueryNode<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -310,6 +324,7 @@ impl<'a> TNode for QueryNode<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -320,6 +335,7 @@ impl<'a> TNode for QueryNode<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -330,6 +346,7 @@ impl<'a> TNode for QueryNode<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -340,6 +357,7 @@ impl<'a> TNode for QueryNode<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -354,6 +372,7 @@ impl<'a> TNode for QueryNode<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
}
}
@@ -401,6 +420,7 @@ impl<'a> TShadowRoot for QueryShadowRoot<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
}
}
@@ -415,6 +435,7 @@ impl<'a> TShadowRoot for QueryShadowRoot<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
}
}
+12
View File
@@ -53,6 +53,7 @@ impl<'a> QueryElement<'a> {
shared_lock,
style_data,
atom_cache,
validity_states: None,
}
}
@@ -148,6 +149,7 @@ impl<'a> TElement for QueryElement<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
}
}
@@ -161,6 +163,7 @@ impl<'a> TElement for QueryElement<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
.collect::<Vec<_>>();
LayoutIterator(children.into_iter())
@@ -349,6 +352,7 @@ impl<'a> TElement for QueryElement<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -361,6 +365,7 @@ impl<'a> TElement for QueryElement<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -395,6 +400,7 @@ impl<'a> TElement for QueryElement<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
});
}
@@ -462,6 +468,7 @@ impl SelectorsElement for QueryElement<'_> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -480,6 +487,7 @@ impl SelectorsElement for QueryElement<'_> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -496,6 +504,7 @@ impl SelectorsElement for QueryElement<'_> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -508,6 +517,7 @@ impl SelectorsElement for QueryElement<'_> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -520,6 +530,7 @@ impl SelectorsElement for QueryElement<'_> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
@@ -534,6 +545,7 @@ impl SelectorsElement for QueryElement<'_> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
}
+10 -1
View File
@@ -130,6 +130,7 @@ impl<'a> QueryElement<'a> {
shared_lock: self.shared_lock,
style_data: self.style_data,
atom_cache: self.atom_cache,
validity_states: self.validity_states,
})
.is_locally_invalid()
{
@@ -140,6 +141,12 @@ impl<'a> QueryElement<'a> {
}
pub(super) fn is_invalid(self) -> bool {
if let Some(invalid) = self
.validity_states
.and_then(|states| states.get(&self.handle))
{
return *invalid;
}
if self.is_locally_invalid() {
return true;
}
@@ -434,7 +441,9 @@ impl<'a> QueryElement<'a> {
}
pub(super) fn matches_validity_pseudo(self) -> bool {
matches!(self.element().local_name(), "form" | "fieldset")
self.validity_states
.is_some_and(|states| states.contains_key(&self.handle))
|| matches!(self.element().local_name(), "form" | "fieldset")
|| self.is_constraint_validation_candidate()
}