forked from mirrors/gecko-dev
Bug 1899272 - Defer computation of registered custom color properties if needed. r=dshin,firefox-style-system-reviewers,zrhoffman
This is a bit less complicated than lengths because there's no cycle possible which could turn the color-scheme declaration invalid afaict. So it's just that we need to defer the colors when color-scheme is specified, which is slightly annoying, but maybe not too bad. I had to tweak a bit the code to defer properties to fix a bug that we were papering over accidentally. We were using the wrong registration here: https://searchfox.org/mozilla-central/rev/f60bb10a5fe6936f9e9f9e8a90d52c18a0ffd818/servo/components/style/custom_properties.rs#1613 That's the registration for reference.name, not for name, which papered over some issues. The fix is simple tho, which is storing a single CustomPropertiesMap. Differential Revision: https://phabricator.services.mozilla.com/D211860
This commit is contained in:
parent
6c1f865eac
commit
0c79149d0a
11 changed files with 287 additions and 160 deletions
|
|
@ -11,10 +11,11 @@ use crate::custom_properties_map::CustomPropertiesMap;
|
||||||
use crate::media_queries::Device;
|
use crate::media_queries::Device;
|
||||||
use crate::properties::{
|
use crate::properties::{
|
||||||
CSSWideKeyword, CustomDeclaration, CustomDeclarationValue, LonghandId, LonghandIdSet,
|
CSSWideKeyword, CustomDeclaration, CustomDeclarationValue, LonghandId, LonghandIdSet,
|
||||||
VariableDeclaration,
|
PropertyDeclaration,
|
||||||
};
|
};
|
||||||
use crate::properties_and_values::{
|
use crate::properties_and_values::{
|
||||||
registry::PropertyRegistrationData,
|
registry::PropertyRegistrationData,
|
||||||
|
syntax::data_type::DependentDataTypes,
|
||||||
value::{
|
value::{
|
||||||
AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
|
AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
|
||||||
SpecifiedValue as SpecifiedRegisteredValue,
|
SpecifiedValue as SpecifiedRegisteredValue,
|
||||||
|
|
@ -338,9 +339,9 @@ bitflags! {
|
||||||
/// At least one custom property depends on root element's line height units.
|
/// At least one custom property depends on root element's line height units.
|
||||||
const ROOT_LH_UNITS = 1 << 3;
|
const ROOT_LH_UNITS = 1 << 3;
|
||||||
/// All dependencies not depending on the root element.
|
/// All dependencies not depending on the root element.
|
||||||
const NON_ROOT_DEPENDENCIES = Self::FONT_UNITS.bits() | Self::LH_UNITS.bits();
|
const NON_ROOT_DEPENDENCIES = Self::FONT_UNITS.0 | Self::LH_UNITS.0;
|
||||||
/// All dependencies depending on the root element.
|
/// All dependencies depending on the root element.
|
||||||
const ROOT_DEPENDENCIES = Self::ROOT_FONT_UNITS.bits() | Self::ROOT_LH_UNITS.bits();
|
const ROOT_DEPENDENCIES = Self::ROOT_FONT_UNITS.0 | Self::ROOT_LH_UNITS.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -458,14 +459,11 @@ impl References {
|
||||||
!self.refs.is_empty()
|
!self.refs.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_non_custom_dependencies(&self, is_root_element: bool) -> NonCustomReferences {
|
fn non_custom_references(&self, is_root_element: bool) -> NonCustomReferences {
|
||||||
let mask = NonCustomReferences::NON_ROOT_DEPENDENCIES;
|
let mut mask = NonCustomReferences::NON_ROOT_DEPENDENCIES;
|
||||||
let mask = if is_root_element {
|
if is_root_element {
|
||||||
mask | NonCustomReferences::ROOT_DEPENDENCIES
|
mask |= NonCustomReferences::ROOT_DEPENDENCIES
|
||||||
} else {
|
}
|
||||||
mask
|
|
||||||
};
|
|
||||||
|
|
||||||
self.non_custom_references & mask
|
self.non_custom_references & mask
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -896,6 +894,7 @@ fn parse_declaration_value_block<'i, 't>(
|
||||||
pub struct CustomPropertiesBuilder<'a, 'b: 'a> {
|
pub struct CustomPropertiesBuilder<'a, 'b: 'a> {
|
||||||
seen: PrecomputedHashSet<&'a Name>,
|
seen: PrecomputedHashSet<&'a Name>,
|
||||||
may_have_cycles: bool,
|
may_have_cycles: bool,
|
||||||
|
has_color_scheme: bool,
|
||||||
custom_properties: ComputedCustomProperties,
|
custom_properties: ComputedCustomProperties,
|
||||||
reverted: PrecomputedHashMap<&'a Name, (CascadePriority, bool)>,
|
reverted: PrecomputedHashMap<&'a Name, (CascadePriority, bool)>,
|
||||||
stylist: &'a Stylist,
|
stylist: &'a Stylist,
|
||||||
|
|
@ -903,6 +902,28 @@ pub struct CustomPropertiesBuilder<'a, 'b: 'a> {
|
||||||
references_from_non_custom_properties: NonCustomReferenceMap<Vec<Name>>,
|
references_from_non_custom_properties: NonCustomReferenceMap<Vec<Name>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_non_custom_dependency(
|
||||||
|
registration: &PropertyRegistrationData,
|
||||||
|
value: &VariableValue,
|
||||||
|
may_have_color_scheme: bool,
|
||||||
|
is_root_element: bool,
|
||||||
|
) -> bool {
|
||||||
|
let dependent_types = registration.syntax.dependent_types();
|
||||||
|
if dependent_types.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if dependent_types.intersects(DependentDataTypes::COLOR) && may_have_color_scheme {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if dependent_types.intersects(DependentDataTypes::LENGTH) {
|
||||||
|
let value_dependencies = value.references.non_custom_references(is_root_element);
|
||||||
|
if !value_dependencies.is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
|
impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
|
||||||
/// Create a new builder, inheriting from a given custom properties map.
|
/// Create a new builder, inheriting from a given custom properties map.
|
||||||
///
|
///
|
||||||
|
|
@ -916,6 +937,7 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
|
||||||
seen: PrecomputedHashSet::default(),
|
seen: PrecomputedHashSet::default(),
|
||||||
reverted: Default::default(),
|
reverted: Default::default(),
|
||||||
may_have_cycles: false,
|
may_have_cycles: false,
|
||||||
|
has_color_scheme: false,
|
||||||
custom_properties,
|
custom_properties,
|
||||||
stylist,
|
stylist,
|
||||||
computed_context,
|
computed_context,
|
||||||
|
|
@ -973,23 +995,24 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
|
||||||
let registration = self.stylist.get_custom_property_registration(&name);
|
let registration = self.stylist.get_custom_property_registration(&name);
|
||||||
match value {
|
match value {
|
||||||
CustomDeclarationValue::Value(unparsed_value) => {
|
CustomDeclarationValue::Value(unparsed_value) => {
|
||||||
let has_custom_property_references = unparsed_value.references.any_var;
|
// At this point of the cascade we're not guaranteed to have seen the color-scheme
|
||||||
let registered_length_property =
|
// declaration, so need to assume the worst. We could track all system color
|
||||||
registration.syntax.may_reference_font_relative_length();
|
// keyword tokens + the light-dark() function, but that seems non-trivial /
|
||||||
|
// probably overkill.
|
||||||
|
let may_have_color_scheme = true;
|
||||||
// Non-custom dependency is really relevant for registered custom properties
|
// Non-custom dependency is really relevant for registered custom properties
|
||||||
// that require computed value of such dependencies.
|
// that require computed value of such dependencies.
|
||||||
let has_non_custom_dependencies = registered_length_property &&
|
let has_dependency = unparsed_value.references.any_var ||
|
||||||
!unparsed_value
|
has_non_custom_dependency(
|
||||||
.references
|
registration,
|
||||||
.get_non_custom_dependencies(self.computed_context.is_root_element())
|
unparsed_value,
|
||||||
.is_empty();
|
may_have_color_scheme,
|
||||||
self.may_have_cycles |=
|
self.computed_context.is_root_element(),
|
||||||
has_custom_property_references || has_non_custom_dependencies;
|
);
|
||||||
|
|
||||||
// If the variable value has no references to other properties, perform
|
// If the variable value has no references to other properties, perform
|
||||||
// substitution here instead of forcing a full traversal in `substitute_all`
|
// substitution here instead of forcing a full traversal in `substitute_all`
|
||||||
// afterwards.
|
// afterwards.
|
||||||
if !has_custom_property_references && !has_non_custom_dependencies {
|
if !has_dependency {
|
||||||
return substitute_references_if_needed_and_apply(
|
return substitute_references_if_needed_and_apply(
|
||||||
name,
|
name,
|
||||||
unparsed_value,
|
unparsed_value,
|
||||||
|
|
@ -998,6 +1021,7 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
|
||||||
self.computed_context,
|
self.computed_context,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
self.may_have_cycles = true;
|
||||||
let value = ComputedRegisteredValue::universal(Arc::clone(unparsed_value));
|
let value = ComputedRegisteredValue::universal(Arc::clone(unparsed_value));
|
||||||
map.insert(registration, name, value);
|
map.insert(registration, name, value);
|
||||||
},
|
},
|
||||||
|
|
@ -1032,11 +1056,7 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
|
||||||
|
|
||||||
/// Note a non-custom property with variable reference that may in turn depend on that property.
|
/// Note a non-custom property with variable reference that may in turn depend on that property.
|
||||||
/// e.g. `font-size` depending on a custom property that may be a registered property using `em`.
|
/// e.g. `font-size` depending on a custom property that may be a registered property using `em`.
|
||||||
pub fn note_potentially_cyclic_non_custom_dependency(
|
pub fn maybe_note_non_custom_dependency(&mut self, id: LonghandId, decl: &PropertyDeclaration) {
|
||||||
&mut self,
|
|
||||||
id: LonghandId,
|
|
||||||
decl: &VariableDeclaration,
|
|
||||||
) {
|
|
||||||
// With unit algebra in `calc()`, references aren't limited to `font-size`.
|
// With unit algebra in `calc()`, references aren't limited to `font-size`.
|
||||||
// For example, `--foo: 100ex; font-weight: calc(var(--foo) / 1ex);`,
|
// For example, `--foo: 100ex; font-weight: calc(var(--foo) / 1ex);`,
|
||||||
// or `--foo: 1em; zoom: calc(var(--foo) * 30px / 2em);`
|
// or `--foo: 1em; zoom: calc(var(--foo) * 30px / 2em);`
|
||||||
|
|
@ -1055,13 +1075,20 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
|
||||||
NonCustomReferences::LH_UNITS | NonCustomReferences::FONT_UNITS
|
NonCustomReferences::LH_UNITS | NonCustomReferences::FONT_UNITS
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
LonghandId::ColorScheme => {
|
||||||
|
// If we might change the color-scheme, we need to defer computation of colors.
|
||||||
|
self.has_color_scheme = true;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
let refs = match decl {
|
||||||
|
PropertyDeclaration::WithVariables(ref v) => &v.value.variable_value.references,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
let refs = &decl.value.variable_value.references;
|
|
||||||
if !refs.any_var {
|
if !refs.any_var {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let variables: Vec<Atom> = refs
|
let variables: Vec<Atom> = refs
|
||||||
.refs
|
.refs
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -1069,11 +1096,13 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
|
||||||
if !reference.is_var {
|
if !reference.is_var {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if !self
|
let registration = self
|
||||||
.stylist
|
.stylist
|
||||||
.get_custom_property_registration(&reference.name)
|
.get_custom_property_registration(&reference.name);
|
||||||
|
if !registration
|
||||||
.syntax
|
.syntax
|
||||||
.may_compute_length()
|
.dependent_types()
|
||||||
|
.intersects(DependentDataTypes::LENGTH)
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
@ -1087,7 +1116,7 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
|
||||||
if was_none {
|
if was_none {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
v.extend(variables.clone().into_iter());
|
v.extend(variables.iter().cloned());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1198,17 +1227,18 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
|
||||||
pub fn build(
|
pub fn build(
|
||||||
mut self,
|
mut self,
|
||||||
defer: DeferFontRelativeCustomPropertyResolution,
|
defer: DeferFontRelativeCustomPropertyResolution,
|
||||||
) -> Option<ComputedCustomProperties> {
|
) -> Option<CustomPropertiesMap> {
|
||||||
let mut deferred_custom_properties = None;
|
let mut deferred_custom_properties = None;
|
||||||
if self.may_have_cycles {
|
if self.may_have_cycles {
|
||||||
if defer == DeferFontRelativeCustomPropertyResolution::Yes {
|
if defer == DeferFontRelativeCustomPropertyResolution::Yes {
|
||||||
deferred_custom_properties = Some(ComputedCustomProperties::default());
|
deferred_custom_properties = Some(CustomPropertiesMap::default());
|
||||||
}
|
}
|
||||||
let mut invalid_non_custom_properties = LonghandIdSet::default();
|
let mut invalid_non_custom_properties = LonghandIdSet::default();
|
||||||
substitute_all(
|
substitute_all(
|
||||||
&mut self.custom_properties,
|
&mut self.custom_properties,
|
||||||
deferred_custom_properties.as_mut(),
|
deferred_custom_properties.as_mut(),
|
||||||
&mut invalid_non_custom_properties,
|
&mut invalid_non_custom_properties,
|
||||||
|
self.has_color_scheme,
|
||||||
&self.seen,
|
&self.seen,
|
||||||
&self.references_from_non_custom_properties,
|
&self.references_from_non_custom_properties,
|
||||||
self.stylist,
|
self.stylist,
|
||||||
|
|
@ -1252,21 +1282,16 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
|
||||||
/// Fully resolve all deferred custom properties, assuming that the incoming context
|
/// Fully resolve all deferred custom properties, assuming that the incoming context
|
||||||
/// has necessary properties resolved.
|
/// has necessary properties resolved.
|
||||||
pub fn build_deferred(
|
pub fn build_deferred(
|
||||||
deferred: ComputedCustomProperties,
|
deferred: CustomPropertiesMap,
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
computed_context: &mut computed::Context,
|
computed_context: &mut computed::Context,
|
||||||
) {
|
) {
|
||||||
if deferred.is_empty() {
|
if deferred.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Guaranteed to not have cycles at this point.
|
let mut custom_properties = std::mem::take(&mut computed_context.builder.custom_properties);
|
||||||
let substitute =
|
// Since `CustomPropertiesMap` preserves insertion order, we shouldn't have to worry about
|
||||||
|deferred: &CustomPropertiesMap,
|
// resolving in a wrong order.
|
||||||
stylist: &Stylist,
|
|
||||||
context: &computed::Context,
|
|
||||||
custom_properties: &mut ComputedCustomProperties| {
|
|
||||||
// Since `CustomPropertiesMap` preserves insertion order, we shouldn't
|
|
||||||
// have to worry about resolving in a wrong order.
|
|
||||||
for (k, v) in deferred.iter() {
|
for (k, v) in deferred.iter() {
|
||||||
let Some(v) = v else { continue };
|
let Some(v) = v else { continue };
|
||||||
let Some(v) = v.as_universal() else {
|
let Some(v) = v.as_universal() else {
|
||||||
|
|
@ -1275,25 +1300,11 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
|
||||||
substitute_references_if_needed_and_apply(
|
substitute_references_if_needed_and_apply(
|
||||||
k,
|
k,
|
||||||
v,
|
v,
|
||||||
custom_properties,
|
&mut custom_properties,
|
||||||
stylist,
|
stylist,
|
||||||
context,
|
computed_context,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
let mut custom_properties = std::mem::take(&mut computed_context.builder.custom_properties);
|
|
||||||
substitute(
|
|
||||||
&deferred.inherited,
|
|
||||||
stylist,
|
|
||||||
computed_context,
|
|
||||||
&mut custom_properties,
|
|
||||||
);
|
|
||||||
substitute(
|
|
||||||
&deferred.non_inherited,
|
|
||||||
stylist,
|
|
||||||
computed_context,
|
|
||||||
&mut custom_properties,
|
|
||||||
);
|
|
||||||
computed_context.builder.custom_properties = custom_properties;
|
computed_context.builder.custom_properties = custom_properties;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1304,8 +1315,9 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
|
||||||
/// It does cycle dependencies removal at the same time as substitution.
|
/// It does cycle dependencies removal at the same time as substitution.
|
||||||
fn substitute_all(
|
fn substitute_all(
|
||||||
custom_properties_map: &mut ComputedCustomProperties,
|
custom_properties_map: &mut ComputedCustomProperties,
|
||||||
mut deferred_properties_map: Option<&mut ComputedCustomProperties>,
|
mut deferred_properties_map: Option<&mut CustomPropertiesMap>,
|
||||||
invalid_non_custom_properties: &mut LonghandIdSet,
|
invalid_non_custom_properties: &mut LonghandIdSet,
|
||||||
|
has_color_scheme: bool,
|
||||||
seen: &PrecomputedHashSet<&Name>,
|
seen: &PrecomputedHashSet<&Name>,
|
||||||
references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Name>>,
|
references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Name>>,
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
|
|
@ -1354,6 +1366,8 @@ fn substitute_all(
|
||||||
stack: SmallVec<[usize; 5]>,
|
stack: SmallVec<[usize; 5]>,
|
||||||
/// References to non-custom properties in this strongly connected component.
|
/// References to non-custom properties in this strongly connected component.
|
||||||
non_custom_references: NonCustomReferences,
|
non_custom_references: NonCustomReferences,
|
||||||
|
/// Whether the builder has seen a non-custom color-scheme reference.
|
||||||
|
has_color_scheme: bool,
|
||||||
map: &'a mut ComputedCustomProperties,
|
map: &'a mut ComputedCustomProperties,
|
||||||
/// The stylist is used to get registered properties, and to resolve the environment to
|
/// The stylist is used to get registered properties, and to resolve the environment to
|
||||||
/// substitute `env()` variables.
|
/// substitute `env()` variables.
|
||||||
|
|
@ -1363,8 +1377,10 @@ fn substitute_all(
|
||||||
computed_context: &'a computed::Context<'b>,
|
computed_context: &'a computed::Context<'b>,
|
||||||
/// Longhand IDs that became invalid due to dependency cycle(s).
|
/// Longhand IDs that became invalid due to dependency cycle(s).
|
||||||
invalid_non_custom_properties: &'a mut LonghandIdSet,
|
invalid_non_custom_properties: &'a mut LonghandIdSet,
|
||||||
/// Properties that cannot yet be substituted.
|
/// Properties that cannot yet be substituted. Note we store both inherited and
|
||||||
deferred_properties: Option<&'a mut ComputedCustomProperties>,
|
/// non-inherited properties in the same map, since we need to make sure we iterate through
|
||||||
|
/// them in the right order.
|
||||||
|
deferred_properties: Option<&'a mut CustomPropertiesMap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function combines the traversal for cycle removal and value
|
/// This function combines the traversal for cycle removal and value
|
||||||
|
|
@ -1391,19 +1407,47 @@ fn substitute_all(
|
||||||
context: &mut Context<'a, 'b>,
|
context: &mut Context<'a, 'b>,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
// Some shortcut checks.
|
// Some shortcut checks.
|
||||||
let (value, should_substitute) = match var {
|
let value = match var {
|
||||||
VarType::Custom(ref name) => {
|
VarType::Custom(ref name) => {
|
||||||
let registration = context.stylist.get_custom_property_registration(name);
|
let registration = context.stylist.get_custom_property_registration(name);
|
||||||
let value = context.map.get(registration, name)?;
|
let value = context.map.get(registration, name)?.as_universal()?;
|
||||||
let value = value.as_universal()?;
|
let is_root = context.computed_context.is_root_element();
|
||||||
|
// We need to keep track of (potential) non-custom-references even on unregistered
|
||||||
let non_custom_references = value
|
// properties for cycle-detection purposes.
|
||||||
.references
|
let value_non_custom_references = value.references.non_custom_references(is_root);
|
||||||
.get_non_custom_dependencies(context.computed_context.is_root_element());
|
context.non_custom_references |= value_non_custom_references;
|
||||||
let has_custom_property_reference = value.references.any_var;
|
let has_dependency = value.references.any_var ||
|
||||||
|
!value_non_custom_references.is_empty() ||
|
||||||
|
has_non_custom_dependency(
|
||||||
|
registration,
|
||||||
|
value,
|
||||||
|
context.has_color_scheme,
|
||||||
|
is_root,
|
||||||
|
);
|
||||||
// Nothing to resolve.
|
// Nothing to resolve.
|
||||||
if !has_custom_property_reference && non_custom_references.is_empty() {
|
if !has_dependency {
|
||||||
debug_assert!(!value.references.any_env, "Should've been handled earlier");
|
debug_assert!(!value.references.any_env, "Should've been handled earlier");
|
||||||
|
if !registration.syntax.is_universal() {
|
||||||
|
// We might still need to compute the value if this is not an universal
|
||||||
|
// registration if we thought this had a dependency before but turned out
|
||||||
|
// not to be (due to has_color_scheme, for example). Note that if this was
|
||||||
|
// already computed we would've bailed out in the as_universal() check.
|
||||||
|
debug_assert!(
|
||||||
|
registration
|
||||||
|
.syntax
|
||||||
|
.dependent_types()
|
||||||
|
.intersects(DependentDataTypes::COLOR),
|
||||||
|
"How did an unresolved value get here otherwise?",
|
||||||
|
);
|
||||||
|
let value = value.clone();
|
||||||
|
substitute_references_if_needed_and_apply(
|
||||||
|
name,
|
||||||
|
&value,
|
||||||
|
&mut context.map,
|
||||||
|
context.stylist,
|
||||||
|
context.computed_context,
|
||||||
|
);
|
||||||
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1416,11 +1460,10 @@ fn substitute_all(
|
||||||
entry.insert(context.count);
|
entry.insert(context.count);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
context.non_custom_references |= value.as_ref().references.non_custom_references;
|
|
||||||
|
|
||||||
// Hold a strong reference to the value so that we don't
|
// Hold a strong reference to the value so that we don't
|
||||||
// need to keep reference to context.map.
|
// need to keep reference to context.map.
|
||||||
(Some(value.clone()), has_custom_property_reference)
|
Some(value.clone())
|
||||||
},
|
},
|
||||||
VarType::NonCustom(ref non_custom) => {
|
VarType::NonCustom(ref non_custom) => {
|
||||||
let entry = &mut context.non_custom_index_map[*non_custom];
|
let entry = &mut context.non_custom_index_map[*non_custom];
|
||||||
|
|
@ -1428,7 +1471,7 @@ fn substitute_all(
|
||||||
return Some(*v);
|
return Some(*v);
|
||||||
}
|
}
|
||||||
*entry = Some(context.count);
|
*entry = Some(context.count);
|
||||||
(None, false)
|
None
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1586,39 +1629,30 @@ fn substitute_all(
|
||||||
|
|
||||||
if let Some(ref v) = value {
|
if let Some(ref v) = value {
|
||||||
let registration = context.stylist.get_custom_property_registration(&name);
|
let registration = context.stylist.get_custom_property_registration(&name);
|
||||||
let registered_length_property =
|
|
||||||
registration.syntax.may_reference_font_relative_length();
|
|
||||||
let mut defer = false;
|
let mut defer = false;
|
||||||
if !context.non_custom_references.is_empty() && registered_length_property {
|
if let Some(ref mut deferred) = context.deferred_properties {
|
||||||
if let Some(deferred) = &mut context.deferred_properties {
|
// We need to defer this property if it has a non-custom property dependency, or
|
||||||
// This property directly depends on a non-custom property, defer resolving it.
|
// any variable that it references is already deferred.
|
||||||
let deferred_property = ComputedRegisteredValue::universal(Arc::clone(v));
|
defer =
|
||||||
deferred.insert(registration, &name, deferred_property);
|
has_non_custom_dependency(
|
||||||
|
registration,
|
||||||
|
v,
|
||||||
|
context.has_color_scheme,
|
||||||
|
context.computed_context.is_root_element(),
|
||||||
|
) || v.references.refs.iter().any(|reference| {
|
||||||
|
reference.is_var && deferred.get(&reference.name).is_some()
|
||||||
|
});
|
||||||
|
|
||||||
|
if defer {
|
||||||
|
let value = ComputedRegisteredValue::universal(Arc::clone(v));
|
||||||
|
deferred.insert(&name, value);
|
||||||
context.map.remove(registration, &name);
|
context.map.remove(registration, &name);
|
||||||
defer = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if should_substitute && !defer {
|
|
||||||
for reference in v.references.refs.iter() {
|
// If there are no var references we should already be computed and substituted by now.
|
||||||
if !reference.is_var {
|
if !defer && v.references.any_var {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(deferred) = &mut context.deferred_properties {
|
|
||||||
let registration = context
|
|
||||||
.stylist
|
|
||||||
.get_custom_property_registration(&reference.name);
|
|
||||||
if deferred.get(registration, &reference.name).is_some() {
|
|
||||||
// This property depends on a custom property that depends on a non-custom property, defer.
|
|
||||||
let deferred_property =
|
|
||||||
ComputedRegisteredValue::universal(Arc::clone(v));
|
|
||||||
deferred.insert(registration, &name, deferred_property);
|
|
||||||
context.map.remove(registration, &name);
|
|
||||||
defer = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !defer {
|
|
||||||
substitute_references_if_needed_and_apply(
|
substitute_references_if_needed_and_apply(
|
||||||
&name,
|
&name,
|
||||||
v,
|
v,
|
||||||
|
|
@ -1628,7 +1662,6 @@ fn substitute_all(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
context.non_custom_references = NonCustomReferences::default();
|
context.non_custom_references = NonCustomReferences::default();
|
||||||
|
|
||||||
// All resolved, so return the signal value.
|
// All resolved, so return the signal value.
|
||||||
|
|
@ -1647,6 +1680,7 @@ fn substitute_all(
|
||||||
var_info: SmallVec::new(),
|
var_info: SmallVec::new(),
|
||||||
map: custom_properties_map,
|
map: custom_properties_map,
|
||||||
non_custom_references: NonCustomReferences::default(),
|
non_custom_references: NonCustomReferences::default(),
|
||||||
|
has_color_scheme,
|
||||||
stylist,
|
stylist,
|
||||||
computed_context,
|
computed_context,
|
||||||
invalid_non_custom_properties,
|
invalid_non_custom_properties,
|
||||||
|
|
|
||||||
|
|
@ -240,9 +240,7 @@ fn iter_declarations<'builder, 'decls: 'builder>(
|
||||||
let id = declaration.id().as_longhand().unwrap();
|
let id = declaration.id().as_longhand().unwrap();
|
||||||
declarations.note_declaration(declaration, priority, id);
|
declarations.note_declaration(declaration, priority, id);
|
||||||
if let Some(ref mut builder) = custom_builder {
|
if let Some(ref mut builder) = custom_builder {
|
||||||
if let PropertyDeclaration::WithVariables(ref v) = declaration {
|
builder.maybe_note_non_custom_dependency(id, declaration);
|
||||||
builder.note_potentially_cyclic_non_custom_dependency(id, v);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ PRIORITARY_PROPERTIES = set(
|
||||||
"font-stretch",
|
"font-stretch",
|
||||||
"font-style",
|
"font-style",
|
||||||
"font-family",
|
"font-family",
|
||||||
# color-scheme affects how system colors resolve.
|
# color-scheme affects how system colors and light-dark() resolve.
|
||||||
"color-scheme",
|
"color-scheme",
|
||||||
# forced-color-adjust affects whether colors are adjusted.
|
# forced-color-adjust affects whether colors are adjusted.
|
||||||
"forced-color-adjust",
|
"forced-color-adjust",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,18 @@ use super::{Component, ComponentName, Multiplier};
|
||||||
use std::fmt::{self, Debug, Write};
|
use std::fmt::{self, Debug, Write};
|
||||||
use style_traits::{CssWriter, ToCss};
|
use style_traits::{CssWriter, ToCss};
|
||||||
|
|
||||||
|
/// Some types (lengths and colors) depend on other properties to resolve correctly.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, MallocSizeOf, ToShmem)]
|
||||||
|
pub struct DependentDataTypes(u8);
|
||||||
|
bitflags! {
|
||||||
|
impl DependentDataTypes: u8 {
|
||||||
|
/// <length> values depend on font-size/line-height/zoom...
|
||||||
|
const LENGTH = 1 << 0;
|
||||||
|
/// <color> values depend on color-scheme, etc..
|
||||||
|
const COLOR= 1 << 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <https://drafts.css-houdini.org/css-properties-values-api-1/#supported-names>
|
/// <https://drafts.css-houdini.org/css-properties-values-api-1/#supported-names>
|
||||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
|
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
|
||||||
pub enum DataType {
|
pub enum DataType {
|
||||||
|
|
@ -83,17 +95,16 @@ impl DataType {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if this data type requires deferring computation to properly
|
/// Returns which kinds of dependent data types this property might contain.
|
||||||
/// resolve font-dependent lengths.
|
pub fn dependent_types(&self) -> DependentDataTypes {
|
||||||
pub fn may_reference_font_relative_length(&self) -> bool {
|
|
||||||
match self {
|
match self {
|
||||||
DataType::Length |
|
DataType::Length |
|
||||||
DataType::LengthPercentage |
|
DataType::LengthPercentage |
|
||||||
DataType::TransformFunction |
|
DataType::TransformFunction |
|
||||||
DataType::TransformList => true,
|
DataType::TransformList => DependentDataTypes::LENGTH,
|
||||||
|
DataType::Color => DependentDataTypes::COLOR,
|
||||||
DataType::Number |
|
DataType::Number |
|
||||||
DataType::Percentage |
|
DataType::Percentage |
|
||||||
DataType::Color |
|
|
||||||
DataType::Image |
|
DataType::Image |
|
||||||
DataType::Url |
|
DataType::Url |
|
||||||
DataType::Integer |
|
DataType::Integer |
|
||||||
|
|
@ -101,7 +112,7 @@ impl DataType {
|
||||||
DataType::Time |
|
DataType::Time |
|
||||||
DataType::Resolution |
|
DataType::Resolution |
|
||||||
DataType::CustomIdent |
|
DataType::CustomIdent |
|
||||||
DataType::String => false,
|
DataType::String => DependentDataTypes::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ use style_traits::{
|
||||||
StyleParseErrorKind, ToCss,
|
StyleParseErrorKind, ToCss,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::data_type::DataType;
|
use self::data_type::{DataType, DependentDataTypes};
|
||||||
|
|
||||||
mod ascii;
|
mod ascii;
|
||||||
pub mod data_type;
|
pub mod data_type;
|
||||||
|
|
@ -94,35 +94,17 @@ impl Descriptor {
|
||||||
Ok(Self { components, specified })
|
Ok(Self { components, specified })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the syntax permits the value to be computed as a length.
|
/// Returns the dependent types this syntax might contain.
|
||||||
pub fn may_compute_length(&self) -> bool {
|
pub fn dependent_types(&self) -> DependentDataTypes {
|
||||||
|
let mut types = DependentDataTypes::empty();
|
||||||
for component in self.components.iter() {
|
for component in self.components.iter() {
|
||||||
match &component.name {
|
let t = match &component.name {
|
||||||
ComponentName::DataType(ref t) => {
|
ComponentName::DataType(ref t) => t,
|
||||||
if matches!(t, DataType::Length | DataType::LengthPercentage) {
|
ComponentName::Ident(_) => continue,
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ComponentName::Ident(_) => (),
|
|
||||||
};
|
};
|
||||||
|
types.insert(t.dependent_types());
|
||||||
}
|
}
|
||||||
false
|
types
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the syntax requires deferring computation to properly
|
|
||||||
/// resolve font-dependent lengths.
|
|
||||||
pub fn may_reference_font_relative_length(&self) -> bool {
|
|
||||||
for component in self.components.iter() {
|
|
||||||
match &component.name {
|
|
||||||
ComponentName::DataType(ref t) => {
|
|
||||||
if t.may_reference_font_relative_length() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ComponentName::Ident(_) => (),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
background-color: green;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div></div>
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css-properties-values-api/#calculation-of-computed-values" />
|
||||||
|
<link rel="match" href="registered-property-computation-color-001-ref.html">
|
||||||
|
<style>
|
||||||
|
@property --x {
|
||||||
|
inherits: true;
|
||||||
|
initial-value: black;
|
||||||
|
syntax: "<color>";
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
color-scheme: dark;
|
||||||
|
--x: light-dark(red, green);
|
||||||
|
background-color: var(--x);
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div></div>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css-properties-values-api/#calculation-of-computed-values" />
|
||||||
|
<link rel="match" href="registered-property-computation-color-001-ref.html">
|
||||||
|
<style>
|
||||||
|
@property --x {
|
||||||
|
inherits: true;
|
||||||
|
initial-value: black;
|
||||||
|
syntax: "<color>";
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
color-scheme: dark;
|
||||||
|
--x: light-dark(red, green);
|
||||||
|
--y: var(--x);
|
||||||
|
background-color: var(--y);
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div></div>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
color-scheme: dark;
|
||||||
|
background-color: Canvas;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div></div>
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css-properties-values-api/#calculation-of-computed-values" />
|
||||||
|
<link rel="match" href="registered-property-computation-color-003-ref.html">
|
||||||
|
<style>
|
||||||
|
@property --x {
|
||||||
|
inherits: true;
|
||||||
|
initial-value: black;
|
||||||
|
syntax: "<color>";
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
color-scheme: dark;
|
||||||
|
--x: Canvas;
|
||||||
|
background-color: var(--x);
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div></div>
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-css-registerproperty" />
|
||||||
|
<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1899272">
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<div id=element></div>
|
||||||
|
<script>
|
||||||
|
test(function() {
|
||||||
|
CSS.registerProperty({
|
||||||
|
name: '--length-calc',
|
||||||
|
syntax: '<length>',
|
||||||
|
initialValue: '0px',
|
||||||
|
inherits: true
|
||||||
|
});
|
||||||
|
CSS.registerProperty({
|
||||||
|
name: '--length-calc-reset',
|
||||||
|
syntax: '<length>',
|
||||||
|
initialValue: '0px',
|
||||||
|
inherits: false
|
||||||
|
});
|
||||||
|
let element = document.getElementById("element");
|
||||||
|
element.style = 'font-size: 11px; --length-calc: calc(10em + 10px); --unregistered:var(--length-calc-reset); --length-calc-reset: var(--length-calc)';
|
||||||
|
let cs = getComputedStyle(element);
|
||||||
|
for (let prop of ["--length-calc", "--length-calc-reset", "--unregistered"]) {
|
||||||
|
assert_equals(cs.getPropertyValue(prop), "120px", "Should resolve properly: " + prop);
|
||||||
|
}
|
||||||
|
}, "Property dependency tracking across inherited and non-inherited properties");
|
||||||
|
</script>
|
||||||
Loading…
Reference in a new issue