Expose live computed CSS variables to scripts
This commit is contained in:
1 parent
2343681497
commit
5f67bad4fe
6 files changed
+264
-14
No files matched your search
@@ -955,7 +955,9 @@ impl Stylesheet {
|
||||
}
|
||||
let registration_changed_props = resolved_props != *parent_props;
|
||||
let has_own = !own.is_empty();
|
||||
let mut own_names = std::collections::HashSet::new();
|
||||
for (k, v) in own {
|
||||
own_names.insert(k.clone());
|
||||
let registration = self.registered_custom_properties.get(&k);
|
||||
let set_initial = |props: &mut HashMap<String, String>| {
|
||||
if let Some(initial) = registration.and_then(|entry| entry.initial_value.as_ref()) {
|
||||
@@ -997,6 +999,29 @@ impl Stylesheet {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Custom properties inherit their computed value, not their original
|
||||
// token stream. Resolve only declarations won on this element against
|
||||
// the complete same-element environment (so forward references work),
|
||||
// then pass those substituted values to descendants. Re-resolving an
|
||||
// inherited `--b:var(--a)` after a child overrides `--a` is observably
|
||||
// wrong: browsers keep the parent's already-computed `--b`.
|
||||
let resolution_environment = resolved_props.clone();
|
||||
for name in own_names {
|
||||
let Some(value) = resolution_environment.get(&name) else {
|
||||
continue;
|
||||
};
|
||||
if let Some(computed) = substitute_var_value(value, &resolution_environment, 0) {
|
||||
resolved_props.insert(name, computed);
|
||||
} else if let Some(initial) = self
|
||||
.registered_custom_properties
|
||||
.get(&name)
|
||||
.and_then(|entry| entry.initial_value.clone())
|
||||
{
|
||||
resolved_props.insert(name, initial);
|
||||
} else {
|
||||
resolved_props.remove(&name);
|
||||
}
|
||||
}
|
||||
let effective = if has_own || registration_changed_props {
|
||||
Some(resolved_props)
|
||||
} else {
|
||||
@@ -1419,7 +1444,11 @@ fn substitute_declarations(css: &str, props: &HashMap<String, String>) -> String
|
||||
/// represents CSS's guaranteed-invalid value. Crucially, invalidity propagates
|
||||
/// through an intermediate custom property so an outer var() can use its own
|
||||
/// fallback (`--toggle:var(--missing) dark; color:var(--toggle,light)`).
|
||||
fn substitute_var_value(input: &str, props: &HashMap<String, String>, depth: u8) -> Option<String> {
|
||||
pub(crate) fn substitute_var_value(
|
||||
input: &str,
|
||||
props: &HashMap<String, String>,
|
||||
depth: u8,
|
||||
) -> Option<String> {
|
||||
if depth > 16 {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -225,6 +225,13 @@ impl OverflowClip {
|
||||
pub struct DomLayout {
|
||||
pub rects: HashMap<NodeId, Rect>,
|
||||
pub styles: HashMap<NodeId, crate::LayoutStyle>,
|
||||
/// Computed custom properties in force for each element. Values are
|
||||
/// reference-counted because the overwhelmingly common case is inheritance
|
||||
/// without an override: an entire subtree can share one cascade map while
|
||||
/// CSSOM still exposes every inherited `--token` through
|
||||
/// `getComputedStyle()`.
|
||||
pub custom_properties:
|
||||
HashMap<NodeId, std::rc::Rc<HashMap<String, String>>>,
|
||||
/// The per-axis clip inherited from ancestor non-visible overflow, keyed
|
||||
/// per node, in SCREEN space (the clip owner's box shifted by the owner's
|
||||
/// accumulated translate; see `resolve_clip_rects`). `None` means
|
||||
@@ -1348,6 +1355,7 @@ fn cascade_walk(
|
||||
sheet: &crate::css::Stylesheet,
|
||||
matcher: &mut obscura_dom::selector::Matcher,
|
||||
styles: &mut HashMap<NodeId, crate::LayoutStyle>,
|
||||
custom_properties: &mut HashMap<NodeId, std::rc::Rc<HashMap<String, String>>>,
|
||||
parent_props: &std::rc::Rc<HashMap<String, String>>,
|
||||
container_evaluator: &mut Option<&mut crate::css::ContainerQueryEvaluator<'_>>,
|
||||
quirks_mode: bool,
|
||||
@@ -1471,6 +1479,7 @@ fn cascade_walk(
|
||||
if let Some(m) = effective_props {
|
||||
this_props = std::rc::Rc::new(m);
|
||||
}
|
||||
custom_properties.insert(id, this_props.clone());
|
||||
let (before_pseudo, after_pseudo) =
|
||||
if let Some(evaluator) = container_evaluator.as_deref_mut() {
|
||||
sheet.pseudo_styles_with_container_queries(
|
||||
@@ -1508,6 +1517,7 @@ fn cascade_walk(
|
||||
sheet,
|
||||
matcher,
|
||||
styles,
|
||||
custom_properties,
|
||||
&this_props,
|
||||
container_evaluator,
|
||||
quirks_mode,
|
||||
@@ -1960,6 +1970,7 @@ fn layout_dom_once(
|
||||
let t1 = std::time::Instant::now();
|
||||
let mut matcher = tree.matcher();
|
||||
let mut styles: HashMap<NodeId, crate::LayoutStyle> = HashMap::new();
|
||||
let mut custom_properties = HashMap::new();
|
||||
let root_props = std::rc::Rc::new(HashMap::new());
|
||||
let mut evaluator =
|
||||
snapshot.map(|snapshot| crate::css::ContainerQueryEvaluator::new(tree, snapshot));
|
||||
@@ -1980,6 +1991,7 @@ fn layout_dom_once(
|
||||
&sheet,
|
||||
&mut matcher,
|
||||
&mut styles,
|
||||
&mut custom_properties,
|
||||
&root_props,
|
||||
&mut evaluator_ref,
|
||||
quirks_mode,
|
||||
@@ -3948,6 +3960,7 @@ fn layout_dom_once(
|
||||
DomLayout {
|
||||
rects,
|
||||
styles,
|
||||
custom_properties,
|
||||
clip_rects,
|
||||
translates,
|
||||
text_runs,
|
||||
|
||||
@@ -461,6 +461,26 @@ impl PreparedRender {
|
||||
css_color(style.background_color.unwrap_or([0, 0, 0, 0])),
|
||||
);
|
||||
out.insert("color", css_color(style.color.unwrap_or([0, 0, 0, 255])));
|
||||
out.insert("font-size", css_px(style.font_size.unwrap_or(16.0)));
|
||||
out.insert(
|
||||
"font-weight",
|
||||
style
|
||||
.font_weight
|
||||
.clone()
|
||||
.unwrap_or_else(|| "400".to_string()),
|
||||
);
|
||||
if let Some(family) = &style.font_family {
|
||||
out.insert("font-family", family.clone());
|
||||
}
|
||||
out.insert(
|
||||
"line-height",
|
||||
match style.line_height.unwrap_or(crate::LineHeight::Normal) {
|
||||
crate::LineHeight::Normal => "normal".to_string(),
|
||||
crate::LineHeight::Ratio(value) => css_number(value),
|
||||
crate::LineHeight::Px(value) => css_px(value),
|
||||
crate::LineHeight::Relative(value) => dimension_css(value, "normal"),
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(rect) = rect {
|
||||
let horizontal_non_content =
|
||||
@@ -621,6 +641,18 @@ impl PreparedRender {
|
||||
Some(out)
|
||||
}
|
||||
|
||||
/// Cascaded custom properties exposed by CSSOM alongside the compact
|
||||
/// fixed-property snapshot above. These maps are shared across unchanged
|
||||
/// inherited subtrees by `DomLayout`, so reading one does not require a
|
||||
/// second cascade or duplicate every design token for every descendant.
|
||||
pub fn computed_custom_properties(
|
||||
&self,
|
||||
id: obscura_dom::tree::NodeId,
|
||||
) -> Option<HashMap<String, String>> {
|
||||
let properties = self.layout.custom_properties.get(&id)?;
|
||||
Some(properties.as_ref().clone())
|
||||
}
|
||||
|
||||
/// Border box in the current root viewport. This is the read-only geometry
|
||||
/// path used by a later CSSOM integration and shares paint's clamped scroll,
|
||||
/// fixed-subtree, and sticky-positioning derivatives.
|
||||
|
||||
Reference in new issue
Block a user