mirror of
https://github.com/lexmount/moli.git
synced 2026-10-06 00:00:56 +00:00
fix(content): consume resolved style semantics
This commit is contained in:
Generated
+13
-13
@@ -4489,7 +4489,7 @@ dependencies = [
|
||||
"errno 0.3.14",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4502,7 +4502,7 @@ dependencies = [
|
||||
"errno 0.3.14",
|
||||
"libc",
|
||||
"linux-raw-sys 0.12.1",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4624,7 +4624,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.40.0"
|
||||
source = "git+https://github.com/lexmount/stylo?branch=moli#3c8bbb450a52f9a4dcb2de51061216a1d2727def"
|
||||
source = "git+https://github.com/lexmount/stylo?rev=49506ee7365dac8708b314733bc2d99736c2a24a#49506ee7365dac8708b314733bc2d99736c2a24a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cssparser",
|
||||
@@ -4756,7 +4756,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "servo_arc"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/lexmount/stylo?branch=moli#3c8bbb450a52f9a4dcb2de51061216a1d2727def"
|
||||
source = "git+https://github.com/lexmount/stylo?rev=49506ee7365dac8708b314733bc2d99736c2a24a#49506ee7365dac8708b314733bc2d99736c2a24a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
@@ -5009,7 +5009,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "stylo"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/lexmount/stylo?branch=moli#3c8bbb450a52f9a4dcb2de51061216a1d2727def"
|
||||
source = "git+https://github.com/lexmount/stylo?rev=49506ee7365dac8708b314733bc2d99736c2a24a#49506ee7365dac8708b314733bc2d99736c2a24a"
|
||||
dependencies = [
|
||||
"app_units",
|
||||
"arrayvec",
|
||||
@@ -5065,7 +5065,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "stylo_atoms"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/lexmount/stylo?branch=moli#3c8bbb450a52f9a4dcb2de51061216a1d2727def"
|
||||
source = "git+https://github.com/lexmount/stylo?rev=49506ee7365dac8708b314733bc2d99736c2a24a#49506ee7365dac8708b314733bc2d99736c2a24a"
|
||||
dependencies = [
|
||||
"string_cache",
|
||||
"string_cache_codegen",
|
||||
@@ -5074,7 +5074,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "stylo_derive"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/lexmount/stylo?branch=moli#3c8bbb450a52f9a4dcb2de51061216a1d2727def"
|
||||
source = "git+https://github.com/lexmount/stylo?rev=49506ee7365dac8708b314733bc2d99736c2a24a#49506ee7365dac8708b314733bc2d99736c2a24a"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
@@ -5086,7 +5086,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "stylo_dom"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/lexmount/stylo?branch=moli#3c8bbb450a52f9a4dcb2de51061216a1d2727def"
|
||||
source = "git+https://github.com/lexmount/stylo?rev=49506ee7365dac8708b314733bc2d99736c2a24a#49506ee7365dac8708b314733bc2d99736c2a24a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"stylo_malloc_size_of",
|
||||
@@ -5095,7 +5095,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "stylo_malloc_size_of"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/lexmount/stylo?branch=moli#3c8bbb450a52f9a4dcb2de51061216a1d2727def"
|
||||
source = "git+https://github.com/lexmount/stylo?rev=49506ee7365dac8708b314733bc2d99736c2a24a#49506ee7365dac8708b314733bc2d99736c2a24a"
|
||||
dependencies = [
|
||||
"app_units",
|
||||
"cssparser",
|
||||
@@ -5112,7 +5112,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "stylo_static_prefs"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/lexmount/stylo?branch=moli#3c8bbb450a52f9a4dcb2de51061216a1d2727def"
|
||||
source = "git+https://github.com/lexmount/stylo?rev=49506ee7365dac8708b314733bc2d99736c2a24a#49506ee7365dac8708b314733bc2d99736c2a24a"
|
||||
dependencies = [
|
||||
"toml",
|
||||
]
|
||||
@@ -5130,7 +5130,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "stylo_traits"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/lexmount/stylo?branch=moli#3c8bbb450a52f9a4dcb2de51061216a1d2727def"
|
||||
source = "git+https://github.com/lexmount/stylo?rev=49506ee7365dac8708b314733bc2d99736c2a24a#49506ee7365dac8708b314733bc2d99736c2a24a"
|
||||
dependencies = [
|
||||
"app_units",
|
||||
"bitflags",
|
||||
@@ -5430,7 +5430,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
[[package]]
|
||||
name = "to_shmem"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/lexmount/stylo?branch=moli#3c8bbb450a52f9a4dcb2de51061216a1d2727def"
|
||||
source = "git+https://github.com/lexmount/stylo?rev=49506ee7365dac8708b314733bc2d99736c2a24a#49506ee7365dac8708b314733bc2d99736c2a24a"
|
||||
dependencies = [
|
||||
"cssparser",
|
||||
"servo_arc",
|
||||
@@ -5443,7 +5443,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "to_shmem_derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/lexmount/stylo?branch=moli#3c8bbb450a52f9a4dcb2de51061216a1d2727def"
|
||||
source = "git+https://github.com/lexmount/stylo?rev=49506ee7365dac8708b314733bc2d99736c2a24a#49506ee7365dac8708b314733bc2d99736c2a24a"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
|
||||
+7
-7
@@ -103,13 +103,13 @@ curl-sys = { git = "https://github.com/lexmount/curl-rust", rev = "a0ea59f5ca1b1
|
||||
cookie = { git = "https://github.com/ldm0/cookie-rs", branch = "priority" }
|
||||
v8 = { path = "vendor/v8-152.2.0" }
|
||||
deno_v8 = { path = "vendor/deno_v8-0.3.0" }
|
||||
selectors = { git = "https://github.com/lexmount/stylo", branch = "moli" }
|
||||
servo_arc = { git = "https://github.com/lexmount/stylo", branch = "moli" }
|
||||
stylo = { git = "https://github.com/lexmount/stylo", branch = "moli" }
|
||||
stylo_atoms = { git = "https://github.com/lexmount/stylo", branch = "moli" }
|
||||
stylo_dom = { git = "https://github.com/lexmount/stylo", branch = "moli" }
|
||||
stylo_static_prefs = { git = "https://github.com/lexmount/stylo", branch = "moli" }
|
||||
stylo_traits = { git = "https://github.com/lexmount/stylo", branch = "moli" }
|
||||
selectors = { git = "https://github.com/lexmount/stylo", rev = "49506ee7365dac8708b314733bc2d99736c2a24a" }
|
||||
servo_arc = { git = "https://github.com/lexmount/stylo", rev = "49506ee7365dac8708b314733bc2d99736c2a24a" }
|
||||
stylo = { git = "https://github.com/lexmount/stylo", rev = "49506ee7365dac8708b314733bc2d99736c2a24a" }
|
||||
stylo_atoms = { git = "https://github.com/lexmount/stylo", rev = "49506ee7365dac8708b314733bc2d99736c2a24a" }
|
||||
stylo_dom = { git = "https://github.com/lexmount/stylo", rev = "49506ee7365dac8708b314733bc2d99736c2a24a" }
|
||||
stylo_static_prefs = { git = "https://github.com/lexmount/stylo", rev = "49506ee7365dac8708b314733bc2d99736c2a24a" }
|
||||
stylo_traits = { git = "https://github.com/lexmount/stylo", rev = "49506ee7365dac8708b314733bc2d99736c2a24a" }
|
||||
taffy = { git = "https://github.com/ldm0/taffy", rev = "645bf5a49df607a8bff34ec90c5343901d04f10e" }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
|
||||
@@ -1478,26 +1478,13 @@ impl JsContextHost {
|
||||
.retained_stylesheet_query_snapshot_for_document(document)
|
||||
}
|
||||
|
||||
pub(crate) fn style_engine_author_shared_lock(&self) -> style::shared_lock::SharedRwLock {
|
||||
self.style_engine.author_shared_lock()
|
||||
}
|
||||
|
||||
pub(crate) fn effective_keyframe_rule_texts_for_elements(
|
||||
pub(crate) fn final_opacity_animation_names_for_elements(
|
||||
&self,
|
||||
document: DomHandle,
|
||||
elements: impl IntoIterator<Item = DomHandle>,
|
||||
) -> (
|
||||
Vec<Option<DomHandle>>,
|
||||
std::collections::HashMap<Option<DomHandle>, Vec<String>>,
|
||||
) {
|
||||
let tree_scopes: Vec<_> = elements
|
||||
.into_iter()
|
||||
.map(|element| self.dom_host().containing_shadow_root(element))
|
||||
.collect();
|
||||
let rules = self
|
||||
.style_engine
|
||||
.effective_keyframe_rule_texts_for_tree_scopes(document, tree_scopes.iter().copied());
|
||||
(tree_scopes, rules)
|
||||
elements: impl IntoIterator<Item = (DomHandle, Vec<String>)>,
|
||||
) -> std::collections::HashMap<DomHandle, std::collections::HashSet<String>> {
|
||||
self.style_engine
|
||||
.final_opacity_animation_names_for_elements(self.dom_host(), document, elements)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -121,7 +121,7 @@ pub(crate) use shadow_dom::{
|
||||
};
|
||||
pub(crate) use styles::{
|
||||
StyleMode, active_css_animation_transform_value,
|
||||
computed_style_own_inherited_property_values_for_document_snapshot,
|
||||
computed_style_own_visibility_values_for_document_snapshot,
|
||||
computed_style_properties_for_inspector_handle,
|
||||
computed_style_property_values_for_document_snapshot, css_animation_start_applies,
|
||||
cssom_style_entry_requires_structured_parser,
|
||||
|
||||
@@ -617,13 +617,11 @@ pub(crate) fn computed_style_property_values_for_document_snapshot(
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn computed_style_own_inherited_property_values_for_document_snapshot(
|
||||
pub(crate) fn computed_style_own_visibility_values_for_document_snapshot(
|
||||
host: &JsContextHost,
|
||||
handles: impl IntoIterator<Item = DomHandle>,
|
||||
property: &str,
|
||||
) -> Vec<bool> {
|
||||
let mut observation = StyleObservation::new(host);
|
||||
let shared_lock = host.style_engine_author_shared_lock();
|
||||
handles
|
||||
.into_iter()
|
||||
.map(|handle| {
|
||||
@@ -634,7 +632,7 @@ pub(crate) fn computed_style_own_inherited_property_values_for_document_snapshot
|
||||
};
|
||||
observation
|
||||
.read(handle)
|
||||
.computed_style_has_own_inherited_longhand_value(property, &shared_lock)
|
||||
.computed_style_has_own_visibility_value()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -224,14 +224,12 @@ impl<'a> ComputedStyleRead<'a> {
|
||||
Some(facts)
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::element) fn computed_style_has_own_inherited_longhand_value(
|
||||
pub(in crate::native_bridge::element) fn computed_style_has_own_visibility_value(
|
||||
&self,
|
||||
property: &str,
|
||||
shared_lock: &style::shared_lock::SharedRwLock,
|
||||
) -> bool {
|
||||
self.stylo_style
|
||||
.as_ref()
|
||||
.is_some_and(|style| style.has_own_inherited_longhand_value(property, shared_lock))
|
||||
.is_some_and(StyloComputedStyleSnapshot::has_own_visibility_value)
|
||||
}
|
||||
|
||||
pub(crate) fn computed_values(&self) -> Option<ServoArc<ComputedValues>> {
|
||||
|
||||
@@ -97,40 +97,20 @@ impl PageVm {
|
||||
);
|
||||
let own_visibility_values = self
|
||||
.vm()
|
||||
.computed_style_own_inherited_property_values_for_document_snapshot(
|
||||
nodes.iter().copied(),
|
||||
"visibility",
|
||||
);
|
||||
.computed_style_own_visibility_values_for_document_snapshot(nodes.iter().copied());
|
||||
let own_visibility: HashMap<_, _> =
|
||||
nodes.iter().copied().zip(own_visibility_values).collect();
|
||||
let (keyframe_scopes, keyframe_rules) = self
|
||||
.vm()
|
||||
.effective_keyframe_rule_texts_for_element_snapshots(nodes.iter().copied());
|
||||
let final_opacity_by_scope: HashMap<_, _> = keyframe_rules
|
||||
.into_iter()
|
||||
.map(|(scope, stylesheet_texts)| {
|
||||
(
|
||||
scope,
|
||||
visibility::final_opacity_animation_names(
|
||||
stylesheet_texts.iter().map(String::as_str),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let final_opacity_animations: HashMap<_, _> = nodes
|
||||
let animation_candidates = nodes
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(keyframe_scopes)
|
||||
.map(|(node, scope)| {
|
||||
(
|
||||
node,
|
||||
final_opacity_by_scope
|
||||
.get(&scope)
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
.zip(values.iter())
|
||||
.filter(|(_, values)| values.get(2).is_some_and(|value| value == "0"))
|
||||
.map(|(node, values)| {
|
||||
(node, visibility::computed_property_animation_names(values))
|
||||
});
|
||||
let final_opacity_animations = self
|
||||
.vm()
|
||||
.final_opacity_animation_names_for_element_snapshots(animation_candidates);
|
||||
let styles = nodes
|
||||
.into_iter()
|
||||
.zip(values)
|
||||
|
||||
@@ -415,10 +415,7 @@ fn has_bounded_final_opacity(
|
||||
values: &[String],
|
||||
final_opacity_animations: &HashSet<String>,
|
||||
) -> bool {
|
||||
let names: Vec<_> = values
|
||||
.get(14)
|
||||
.map(|value| value.split(',').map(str::trim).collect())
|
||||
.unwrap_or_default();
|
||||
let names = parse_animation_names(values.get(14));
|
||||
let durations: Vec<_> = values
|
||||
.get(15)
|
||||
.map(|value| value.split(',').map(str::trim).collect())
|
||||
@@ -444,7 +441,7 @@ fn has_bounded_final_opacity(
|
||||
.map(|value| value.split(',').map(str::trim).collect())
|
||||
.unwrap_or_default();
|
||||
names.iter().enumerate().any(|(index, name)| {
|
||||
final_opacity_animations.contains(*name)
|
||||
final_opacity_animations.contains(name)
|
||||
&& !durations.is_empty()
|
||||
&& animation_duration_is_positive(durations[index % durations.len()])
|
||||
&& !fill_modes.is_empty()
|
||||
@@ -461,6 +458,23 @@ fn has_bounded_final_opacity(
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn computed_property_animation_names(values: &[String]) -> Vec<String> {
|
||||
parse_animation_names(values.get(8))
|
||||
}
|
||||
|
||||
fn parse_animation_names(value: Option<&String>) -> Vec<String> {
|
||||
value
|
||||
.map(|value| {
|
||||
value
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|name| !name.is_empty() && *name != "none")
|
||||
.map(str::to_owned)
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn animation_duration_is_positive(duration: &str) -> bool {
|
||||
animation_time_seconds(duration).is_some_and(|duration| duration > 0.0)
|
||||
}
|
||||
@@ -477,79 +491,6 @@ fn animation_time_seconds(value: &str) -> Option<f32> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Return only animations whose declared final keyframe paints a nonzero
|
||||
/// opacity. The caller still requires a finite, forward, running animation
|
||||
/// before using that final authored state for the static document snapshot.
|
||||
pub(super) fn final_opacity_animation_names<'a>(
|
||||
stylesheets: impl IntoIterator<Item = &'a str>,
|
||||
) -> HashSet<String> {
|
||||
let mut result = HashSet::new();
|
||||
for source in stylesheets {
|
||||
let folded = source.to_ascii_lowercase();
|
||||
for offset in code_marker_offsets(&folded, "@keyframes") {
|
||||
let Some(relative_open) = source[offset..].find('{') else {
|
||||
continue;
|
||||
};
|
||||
let open = offset + relative_open;
|
||||
let Some(close) = matching_delimiter(source, open, '{', '}') else {
|
||||
continue;
|
||||
};
|
||||
let Some(rule) =
|
||||
moli_css_parse::parse_keyframes_rule_view_with_stylo(&source[offset..=close])
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(canonical_open) = rule.css_text.find('{') else {
|
||||
continue;
|
||||
};
|
||||
let Some(canonical_close) =
|
||||
matching_delimiter(&rule.css_text, canonical_open, '{', '}')
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let name = rule.name;
|
||||
if final_keyframe_reveals(&rule.css_text[canonical_open + 1..canonical_close]) {
|
||||
result.insert(name);
|
||||
} else {
|
||||
result.remove(&name);
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn final_keyframe_reveals(body: &str) -> bool {
|
||||
let mut offset = 0;
|
||||
while let Some(relative_open) = body[offset..].find('{') {
|
||||
let open = offset + relative_open;
|
||||
let selector = body[offset..open].trim();
|
||||
let Some(close) = matching_delimiter(body, open, '{', '}') else {
|
||||
return false;
|
||||
};
|
||||
let is_final = selector
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.any(|value| matches!(value, "to" | "100%" | "100.0%"));
|
||||
if is_final {
|
||||
for declaration in body[open + 1..close].split(';') {
|
||||
let Some((name, value)) = declaration.split_once(':') else {
|
||||
continue;
|
||||
};
|
||||
if name.trim().eq_ignore_ascii_case("opacity")
|
||||
&& value
|
||||
.trim()
|
||||
.parse::<f32>()
|
||||
.is_ok_and(|opacity| opacity > 0.0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
offset = close + 1;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn target_is_disclosure<D: Dom + ?Sized>(
|
||||
dom: &D,
|
||||
targets: &HashMap<&str, D::NodeId>,
|
||||
@@ -564,119 +505,6 @@ fn target_is_disclosure<D: Dom + ?Sized>(
|
||||
})
|
||||
}
|
||||
|
||||
fn code_marker_offsets(value: &str, marker: &str) -> Vec<usize> {
|
||||
let mut result = Vec::new();
|
||||
let mut offset = 0;
|
||||
let mut quote = None;
|
||||
let mut escaped = false;
|
||||
let mut line_comment = false;
|
||||
let mut block_comment = false;
|
||||
while offset < value.len() {
|
||||
let tail = &value[offset..];
|
||||
if line_comment {
|
||||
if tail.starts_with('\n') {
|
||||
line_comment = false;
|
||||
}
|
||||
} else if block_comment {
|
||||
if tail.starts_with("*/") {
|
||||
block_comment = false;
|
||||
offset += 2;
|
||||
continue;
|
||||
}
|
||||
} else if let Some(delimiter) = quote {
|
||||
let character = tail.chars().next().expect("offset is within value");
|
||||
if escaped {
|
||||
escaped = false;
|
||||
} else if character == '\\' {
|
||||
escaped = true;
|
||||
} else if character == delimiter {
|
||||
quote = None;
|
||||
}
|
||||
} else if tail.starts_with("//") {
|
||||
line_comment = true;
|
||||
offset += 2;
|
||||
continue;
|
||||
} else if tail.starts_with("/*") {
|
||||
block_comment = true;
|
||||
offset += 2;
|
||||
continue;
|
||||
} else {
|
||||
let character = tail.chars().next().expect("offset is within value");
|
||||
if matches!(character, '\'' | '"' | '`') {
|
||||
quote = Some(character);
|
||||
} else if tail.starts_with(marker) {
|
||||
result.push(offset);
|
||||
offset += marker.len();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
offset += tail
|
||||
.chars()
|
||||
.next()
|
||||
.expect("offset is within value")
|
||||
.len_utf8();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn matching_delimiter(value: &str, start: usize, open: char, close: char) -> Option<usize> {
|
||||
let mut depth = 0;
|
||||
let mut quote = None;
|
||||
let mut escaped = false;
|
||||
let mut line_comment = false;
|
||||
let mut block_comment = false;
|
||||
let mut offset = start;
|
||||
while offset < value.len() {
|
||||
let tail = &value[offset..];
|
||||
if line_comment {
|
||||
if tail.starts_with('\n') {
|
||||
line_comment = false;
|
||||
}
|
||||
} else if block_comment {
|
||||
if tail.starts_with("*/") {
|
||||
block_comment = false;
|
||||
offset += 2;
|
||||
continue;
|
||||
}
|
||||
} else if let Some(delimiter) = quote {
|
||||
let character = tail.chars().next().expect("offset is within value");
|
||||
if escaped {
|
||||
escaped = false;
|
||||
} else if character == '\\' {
|
||||
escaped = true;
|
||||
} else if character == delimiter {
|
||||
quote = None;
|
||||
}
|
||||
} else if tail.starts_with("//") {
|
||||
line_comment = true;
|
||||
offset += 2;
|
||||
continue;
|
||||
} else if tail.starts_with("/*") {
|
||||
block_comment = true;
|
||||
offset += 2;
|
||||
continue;
|
||||
} else {
|
||||
let character = tail.chars().next().expect("offset is within value");
|
||||
if matches!(character, '\'' | '"' | '`') {
|
||||
quote = Some(character);
|
||||
} else if character == open {
|
||||
depth += 1;
|
||||
} else if character == close {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
return Some(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += tail
|
||||
.chars()
|
||||
.next()
|
||||
.expect("offset is within value")
|
||||
.len_utf8();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn px(value: &str) -> Option<f32> {
|
||||
value.strip_suffix("px")?.trim().parse().ok()
|
||||
}
|
||||
|
||||
@@ -184,16 +184,23 @@ async fn markdown_uses_live_visibility_and_preserves_disclosure_content() {
|
||||
document.head.innerHTML = '<style>.reverted{visibility:hidden}.reverted{visibility:revert}@layer base{.layered{visibility:hidden}.layered{visibility:revert-layer}}</style>';
|
||||
document.body.innerHTML = `
|
||||
<main>
|
||||
<p id="variable-hidden" style="--v:hidden;visibility:var(--v)">Independent variable hidden content</p>
|
||||
<p id="fallback-hidden" style="visibility:var(--missing, hidden)">Hidden fallback content</p>
|
||||
<p style="visibility:var(--missing)">Invalid variable inherits the veil</p>
|
||||
<p style="visibility:var(--missing, inherit)">Variable fallback inherits the veil</p>
|
||||
<p class="reverted">Origin revert inherits the veil</p>
|
||||
<p class="layered">Layer revert inherits the veil</p>
|
||||
</main>`;
|
||||
"#).unwrap();
|
||||
assert_eq!(page.vm_mut().eval("getComputedStyle(document.getElementById('variable-hidden')).visibility").unwrap(), "hidden");
|
||||
assert_eq!(page.vm_mut().eval("getComputedStyle(document.getElementById('fallback-hidden')).visibility").unwrap(), "hidden");
|
||||
let inherited_visibility = page.render_page_dump(options(false));
|
||||
for text in ["Invalid variable inherits the veil", "Variable fallback inherits the veil", "Origin revert inherits the veil", "Layer revert inherits the veil"] {
|
||||
assert!(inherited_visibility.contains(text), "missing {text}: {inherited_visibility}");
|
||||
}
|
||||
for text in ["Independent variable hidden content", "Hidden fallback content"] {
|
||||
assert!(!inherited_visibility.contains(text), "leaked {text}: {inherited_visibility}");
|
||||
}
|
||||
page.vm_mut().eval(r#"
|
||||
document.body.style.visibility = '';
|
||||
document.head.innerHTML = '<style>@keyframes state{to{opacity:0}}@media (min-width:999999px){@keyframes state{to{opacity:1}}}</style>';
|
||||
@@ -216,6 +223,28 @@ async fn markdown_uses_live_visibility_and_preserves_disclosure_content() {
|
||||
let scoped_keyframes = page.render_page_dump(options(false));
|
||||
assert!(scoped_keyframes.contains("Scoped count 2"), "{scoped_keyframes}");
|
||||
assert!(!scoped_keyframes.contains("Scoped count 992"), "{scoped_keyframes}");
|
||||
page.vm_mut().eval(r#"
|
||||
document.head.innerHTML = '<style>@layer low,high;@layer high{@keyframes layered{to{opacity:0}}}@layer low{@keyframes layered{to{opacity:1}}}@keyframes repeated{to{opacity:1}to{opacity:0}}</style>';
|
||||
document.body.innerHTML = '<p>Layered count <span id="layered-count" style="opacity:0;animation:layered 1s forwards">99</span>2</p><p>Repeated count <span id="repeated-count" style="opacity:0;animation:repeated 1s forwards">99</span>2</p>';
|
||||
"#).unwrap();
|
||||
for id in ["layered-count", "repeated-count"] {
|
||||
assert_eq!(page.vm_mut().eval(&format!("getComputedStyle(document.getElementById('{id}')).opacity")).unwrap(), "0");
|
||||
}
|
||||
let keyframe_priority = page.render_page_dump(options(false));
|
||||
assert!(keyframe_priority.contains("Layered count 2"), "{keyframe_priority}");
|
||||
assert!(keyframe_priority.contains("Repeated count 2"), "{keyframe_priority}");
|
||||
assert!(!keyframe_priority.contains("Layered count 992"), "{keyframe_priority}");
|
||||
assert!(!keyframe_priority.contains("Repeated count 992"), "{keyframe_priority}");
|
||||
page.vm_mut().eval(r#"
|
||||
const unrelatedRules = Array.from({length:128}, (_, index) => `@keyframes unused${index}{to{opacity:1}}`).join('');
|
||||
const unrelatedNodes = Array.from({length:128}, (_, index) => `<span style="opacity:0">hidden${index}</span>`).join('');
|
||||
document.head.innerHTML = `<style>${unrelatedRules}@keyframes used{to{opacity:1}}</style>`;
|
||||
document.body.innerHTML = `<p>Scale ${unrelatedNodes}<span style="opacity:0;animation:used 1s forwards">kept</span></p>`;
|
||||
"#).unwrap();
|
||||
crate::style_engine::reset_final_opacity_animation_query_count_for_test();
|
||||
let scaled_keyframes = page.render_page_dump(options(false));
|
||||
assert!(scaled_keyframes.contains("Scale kept"), "{scaled_keyframes}");
|
||||
assert_eq!(crate::style_engine::final_opacity_animation_query_count_for_test(), 1);
|
||||
page.vm_mut().eval(r##"
|
||||
document.body.innerHTML = `
|
||||
<button onclick="console.log('tracking')">Save</button>
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
use super::*;
|
||||
|
||||
impl ScriptVm {
|
||||
pub(crate) fn effective_keyframe_rule_texts_for_element_snapshots(
|
||||
pub(crate) fn final_opacity_animation_names_for_element_snapshots(
|
||||
&self,
|
||||
elements: impl IntoIterator<Item = DomHandle>,
|
||||
) -> (
|
||||
Vec<Option<DomHandle>>,
|
||||
std::collections::HashMap<Option<DomHandle>, Vec<String>>,
|
||||
) {
|
||||
elements: impl IntoIterator<Item = (DomHandle, Vec<String>)>,
|
||||
) -> std::collections::HashMap<DomHandle, std::collections::HashSet<String>> {
|
||||
let host = self._context_host.borrow();
|
||||
let document = host.document_handle();
|
||||
host.effective_keyframe_rule_texts_for_elements(document, elements)
|
||||
host.final_opacity_animation_names_for_elements(document, elements)
|
||||
}
|
||||
|
||||
pub(crate) fn sync_live_document_style_sources(&mut self) {
|
||||
@@ -32,15 +29,13 @@ impl ScriptVm {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn computed_style_own_inherited_property_values_for_document_snapshot(
|
||||
pub(crate) fn computed_style_own_visibility_values_for_document_snapshot(
|
||||
&self,
|
||||
handles: impl IntoIterator<Item = DomHandle>,
|
||||
property: &str,
|
||||
) -> Vec<bool> {
|
||||
crate::native_bridge::element::computed_style_own_inherited_property_values_for_document_snapshot(
|
||||
crate::native_bridge::element::computed_style_own_visibility_values_for_document_snapshot(
|
||||
&self._context_host.borrow(),
|
||||
handles,
|
||||
property,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use std::collections::HashSet;
|
||||
#[cfg(test)]
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use dom::ElementState as StyloElementState;
|
||||
use style::{
|
||||
Atom,
|
||||
animation::DocumentAnimationSet,
|
||||
applicable_declarations::{CascadePriority, RevertKind},
|
||||
computed_value_flags::ComputedValueFlags,
|
||||
context::{
|
||||
QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters,
|
||||
SharedStyleContext, StyleContext, StyleSystemOptions, ThreadLocalStyleContext,
|
||||
@@ -12,7 +14,7 @@ use style::{
|
||||
data::ElementStyles,
|
||||
dom::{TElement, TNode},
|
||||
properties::{
|
||||
CSSWideKeyword, ComputedValues, PropertyDeclaration, PropertyDeclarationId, PropertyId,
|
||||
ComputedValues, PropertyId,
|
||||
longhands::{
|
||||
text_wrap_mode::computed_value::T as StyloTextWrapMode,
|
||||
visibility::computed_value::T as ComputedVisibility,
|
||||
@@ -21,8 +23,10 @@ use style::{
|
||||
parse_style_attribute,
|
||||
},
|
||||
selector_parser::{PseudoElement, SnapshotMap},
|
||||
servo::animation::final_keyframe_opacity,
|
||||
servo_arc::Arc as ServoArc,
|
||||
shared_lock::StylesheetGuards,
|
||||
style_resolver::{PseudoElementResolution, StyleResolverForElement},
|
||||
stylesheets::{CssRuleType, UrlExtraData},
|
||||
stylist::RuleInclusion,
|
||||
thread_state::{self, ThreadState},
|
||||
@@ -44,6 +48,19 @@ use crate::{
|
||||
|
||||
use moli_selector::StyloElement;
|
||||
|
||||
#[cfg(test)]
|
||||
static FINAL_OPACITY_ANIMATION_QUERY_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn reset_final_opacity_animation_query_count_for_test() {
|
||||
FINAL_OPACITY_ANIMATION_QUERY_COUNT.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn final_opacity_animation_query_count_for_test() -> usize {
|
||||
FINAL_OPACITY_ANIMATION_QUERY_COUNT.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
use super::{
|
||||
FullStyleWorldSnapshot, MoliStyleEngine, PreparedStyleWorldUpdate, StyleViewport,
|
||||
StyleWorldUpdate, StyleWorldUpdatePlan,
|
||||
@@ -283,65 +300,12 @@ impl StyloComputedStyleSnapshot {
|
||||
serialize_raw_computed_property(&self.primary, property_id)
|
||||
}
|
||||
|
||||
/// Whether this element's cascade supplies a value for an inherited
|
||||
/// longhand instead of taking the value from its parent.
|
||||
///
|
||||
/// Computed values alone cannot distinguish `visibility:hidden` declared
|
||||
/// on an element from the same value inherited from an ancestor. Consumers
|
||||
/// that selectively expose an otherwise hidden subtree need that source
|
||||
/// distinction, but should not parse inline style or stylesheet text a
|
||||
/// second time.
|
||||
pub(crate) fn has_own_inherited_longhand_value(
|
||||
&self,
|
||||
property: &str,
|
||||
shared_lock: &style::shared_lock::SharedRwLock,
|
||||
) -> bool {
|
||||
let Ok(property_id) = PropertyId::parse_enabled_for_all_content(property) else {
|
||||
return false;
|
||||
};
|
||||
let Some(longhand) = property_id.longhand_id() else {
|
||||
return false;
|
||||
};
|
||||
let guard = shared_lock.read();
|
||||
let guards = StylesheetGuards::same(&guard);
|
||||
let mut reverted: Option<(CascadePriority, RevertKind)> = None;
|
||||
for node in self.primary.rules().self_and_ancestors() {
|
||||
let priority = node.cascade_priority();
|
||||
if reverted.is_some_and(|(reverted_priority, kind)| {
|
||||
!reverted_priority.allows_when_reverted(&priority, kind)
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
let Some(source) = node.style_source() else {
|
||||
continue;
|
||||
};
|
||||
for (declaration, importance) in source
|
||||
.read(node.cascade_level().guard(&guards))
|
||||
.declaration_importance_iter()
|
||||
.rev()
|
||||
{
|
||||
if importance.important() != node.importance().important()
|
||||
|| declaration.id() != PropertyDeclarationId::Longhand(longhand)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
match declaration.get_css_wide_keyword() {
|
||||
Some(CSSWideKeyword::Inherit | CSSWideKeyword::Unset) => return false,
|
||||
Some(keyword) if keyword.revert_kind().is_some() => {
|
||||
reverted = Some((priority, keyword.revert_kind().unwrap()));
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// Variable substitution happens inside Stylo's cascade. A raw
|
||||
// `WithVariables` declaration cannot establish an independent
|
||||
// hidden boundary here: it may be invalid at computed-value
|
||||
// time or resolve to `inherit`. Treat it conservatively as
|
||||
// inherited instead of recreating the substitution engine.
|
||||
return !matches!(declaration, PropertyDeclaration::WithVariables(_));
|
||||
}
|
||||
}
|
||||
false
|
||||
/// Whether Stylo's completed cascade established `visibility` on this
|
||||
/// element instead of inheriting it from the parent.
|
||||
pub(crate) fn has_own_visibility_value(&self) -> bool {
|
||||
self.primary
|
||||
.flags
|
||||
.contains(ComputedValueFlags::HAS_OWN_VISIBILITY)
|
||||
}
|
||||
|
||||
pub(crate) fn resolved_property_value(&self, property: &str) -> Option<String> {
|
||||
@@ -370,6 +334,82 @@ pub(super) fn retained_current_element_state(
|
||||
})?
|
||||
}
|
||||
|
||||
pub(super) fn retained_final_opacity_animation_names(
|
||||
engine: &MoliStyleEngine,
|
||||
host: &DomHost,
|
||||
document: DomHandle,
|
||||
elements: impl IntoIterator<Item = (DomHandle, Vec<String>)>,
|
||||
) -> std::collections::HashMap<DomHandle, HashSet<String>> {
|
||||
let elements: Vec<_> = elements.into_iter().collect();
|
||||
if elements.is_empty() {
|
||||
return Default::default();
|
||||
}
|
||||
let Some(world) = engine.document_worlds.active_world(document) else {
|
||||
return Default::default();
|
||||
};
|
||||
engine.dom_adapter.with_bound_host(host, |dom_adapter| {
|
||||
install_shadow_cascade_data_for_resolution(&world, dom_adapter);
|
||||
let shared_lock = dom_adapter.shared_lock().clone();
|
||||
let guard = shared_lock.read();
|
||||
let guards = StylesheetGuards::same(&guard);
|
||||
let snapshot_map = SnapshotMap::new();
|
||||
let empty_painters = EmptyRegisteredSpeculativePainters;
|
||||
world.document_state.with_retained_style_system(|retained| {
|
||||
let shared = SharedStyleContext {
|
||||
stylist: &retained.stylist,
|
||||
visited_styles_enabled: false,
|
||||
options: StyleSystemOptions::default(),
|
||||
guards,
|
||||
current_time_for_animations: 0.0,
|
||||
traversal_flags: TraversalFlags::empty(),
|
||||
snapshot_map: &snapshot_map,
|
||||
animations: DocumentAnimationSet::default(),
|
||||
registered_speculative_painters: &empty_painters,
|
||||
};
|
||||
let _layout_thread_state = StyloLayoutThreadStateGuard::enter();
|
||||
let mut thread_local = ThreadLocalStyleContext::new();
|
||||
let mut context = StyleContext {
|
||||
shared: &shared,
|
||||
thread_local: &mut thread_local,
|
||||
};
|
||||
elements
|
||||
.into_iter()
|
||||
.filter_map(|(handle, names)| {
|
||||
let element = dom_adapter.element(host, handle)?;
|
||||
let base_style = element.borrow_data()?.styles.primary().clone();
|
||||
let revealing = names
|
||||
.into_iter()
|
||||
.filter(|name| {
|
||||
#[cfg(test)]
|
||||
FINAL_OPACITY_ANIMATION_QUERY_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
let atom = Atom::from(name.as_str());
|
||||
let Some(animation) = retained.stylist.lookup_keyframes(&atom, element)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let mut resolver = StyleResolverForElement::new(
|
||||
element,
|
||||
&mut context,
|
||||
RuleInclusion::All,
|
||||
PseudoElementResolution::IfApplicable,
|
||||
);
|
||||
let opacity = final_keyframe_opacity(
|
||||
element,
|
||||
animation,
|
||||
&shared,
|
||||
&base_style,
|
||||
&mut resolver,
|
||||
);
|
||||
opacity.is_some_and(|opacity| opacity > 0.0)
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
(!revealing.is_empty()).then_some((handle, revealing))
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn computed_style_property_value(
|
||||
engine: &MoliStyleEngine,
|
||||
host: &DomHost,
|
||||
|
||||
@@ -35,6 +35,11 @@ mod cache;
|
||||
mod cause;
|
||||
mod cleanup;
|
||||
mod computed;
|
||||
#[cfg(test)]
|
||||
pub(crate) use computed::{
|
||||
final_opacity_animation_query_count_for_test,
|
||||
reset_final_opacity_animation_query_count_for_test,
|
||||
};
|
||||
mod document_world;
|
||||
mod drain;
|
||||
mod eligibility;
|
||||
@@ -300,58 +305,13 @@ impl MoliStyleEngine {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn effective_keyframe_rule_texts_for_tree_scopes(
|
||||
pub(crate) fn final_opacity_animation_names_for_elements(
|
||||
&self,
|
||||
host: &DomHost,
|
||||
document: DomHandle,
|
||||
tree_scopes: impl IntoIterator<Item = Option<DomHandle>>,
|
||||
) -> HashMap<Option<DomHandle>, Vec<String>> {
|
||||
let Some(world) = self.document_worlds.active_world(document) else {
|
||||
return HashMap::new();
|
||||
};
|
||||
world
|
||||
.document_state
|
||||
.try_with_retained_style_system(|retained| {
|
||||
let device = retained.stylist.device();
|
||||
let mut document_rules = Vec::new();
|
||||
let document_custom_media = retained
|
||||
.stylist
|
||||
.cascade_data()
|
||||
.borrow_for_origin(style::stylesheets::Origin::Author)
|
||||
.custom_media_map();
|
||||
for entry in retained.document_stylesheets.entries() {
|
||||
document_rules.extend(stylesheet::native_effective_keyframe_rule_texts(
|
||||
entry.stylesheet(),
|
||||
device,
|
||||
document_custom_media,
|
||||
));
|
||||
}
|
||||
let mut shadow_rules = HashMap::new();
|
||||
for scope in &retained.shadow_scopes {
|
||||
let rules = shadow_rules.entry(scope.root()).or_insert_with(Vec::new);
|
||||
for entry in scope.active_stylesheets().entries() {
|
||||
rules.extend(stylesheet::native_effective_keyframe_rule_texts(
|
||||
entry.stylesheet(),
|
||||
device,
|
||||
scope.author_styles().data.custom_media_map(),
|
||||
));
|
||||
}
|
||||
}
|
||||
tree_scopes
|
||||
.into_iter()
|
||||
.collect::<HashSet<_>>()
|
||||
.into_iter()
|
||||
.map(|tree_scope| {
|
||||
let mut rules = document_rules.clone();
|
||||
if let Some(tree_scope) = tree_scope
|
||||
&& let Some(scoped_rules) = shadow_rules.get(&tree_scope)
|
||||
{
|
||||
rules.extend(scoped_rules.iter().cloned());
|
||||
}
|
||||
(tree_scope, rules)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
elements: impl IntoIterator<Item = (DomHandle, Vec<String>)>,
|
||||
) -> HashMap<DomHandle, HashSet<String>> {
|
||||
computed::retained_final_opacity_animation_names(self, host, document, elements)
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
|
||||
@@ -218,25 +218,6 @@ pub(super) fn native_effective_font_face_rule_addresses(
|
||||
native_effective_font_face_rule_addresses_with_guard(stylesheet, device, custom_media, &guard)
|
||||
}
|
||||
|
||||
pub(super) fn native_effective_keyframe_rule_texts(
|
||||
stylesheet: &DocumentStyleSheet,
|
||||
device: &Device,
|
||||
custom_media: &CustomMediaMap,
|
||||
) -> Vec<String> {
|
||||
let guard = stylesheet.0.shared_lock.read();
|
||||
if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, custom_media, &guard) {
|
||||
return Vec::new();
|
||||
}
|
||||
stylesheet
|
||||
.contents(&guard)
|
||||
.effective_rules(device, custom_media, &guard)
|
||||
.filter_map(|rule| match rule {
|
||||
CssRule::Keyframes(rule) => Some(rule.read_with(&guard).to_css_string(&guard)),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn native_effective_font_face_rule_addresses_with_guard(
|
||||
stylesheet: &DocumentStyleSheet,
|
||||
device: &Device,
|
||||
|
||||
Reference in New Issue
Block a user