mirror of
https://github.com/lexmount/moli.git
synced 2026-09-28 08:01:37 +00:00
fix(selectors): use full validity state in DOM queries
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user