Bug 1840478 - Handle inherits flag and initial value for custom properties. r=emilio,zrhoffman

The CSS Properties and Values API allows to register CSS properties that
can be either inherited or non-inherited, and which can also have
initial values specified [1].

In [2], the representation of computed value for custom properties was
changed to a pair of CustomPropertiesMaps: one map which is ref-counted
(for properties unregistered or registered as inherited) and one which
is not (for properties registered as non-inherited). The latter map
is currently always None (i.e. all custom properties are handled as
inherited) and several parts of the code assume this condition holds.

This patch instead ensures that values for custom properties on a node
are properly placed in the inherit or non_inherit map according to the
inherits flag. Initial values for registered properties are taken
into account and missing implementations of functions assuming
non_inherited==None is completed.

In order to minimize the size of the maps, absent values for
non-inherited properties are interpreted as initial values during var
substitution or retrieval of computed values (for non-inherited
properties). This is used for `unset` and `initial` keywords while
a copy of the parent's value is used for `inherit`.

Last but not least, `CustomPropertiesBuilder` tries to perform lazy
copy of the inherited style, postponing actual deep clone when
necessary or falling back to a shallow copy when possible [3].
This is generalized a bit when the document contains custom properties
registered as non-inherited and current optimizations are preserved as
is for pages containing only non-registered custom properties. This
could be further improved later [4].

[1] https://drafts.css-houdini.org/css-properties-values-api-1/
[2] https://hg.mozilla.org/mozilla-central/rev/8a7d9524a1b9
[3] https://bugzilla.mozilla.org/show_bug.cgi?id=1840478
[4] https://bugzilla.mozilla.org/show_bug.cgi?id=1855887

Differential Revision: https://phabricator.services.mozilla.com/D188812
This commit is contained in:
Frederic Wang 2023-09-29 09:00:46 +00:00
parent f553129f78
commit 8638c84fb5
45 changed files with 425 additions and 376 deletions

View File

@ -466,7 +466,8 @@ void nsComputedDOMStyle::GetPropertyValue(
MOZ_ASSERT(nsCSSProps::IsCustomPropertyName(aMaybeCustomPropertyName));
const nsACString& name =
Substring(aMaybeCustomPropertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
Servo_GetCustomPropertyValue(mComputedStyle, &name, &aReturn);
Servo_GetCustomPropertyValue(
mComputedStyle, mPresShell->StyleSet()->RawData(), &name, &aReturn);
return;
}

View File

@ -9,6 +9,7 @@
use crate::applicable_declarations::CascadePriority;
use crate::media_queries::Device;
use crate::properties::{CSSWideKeyword, CustomDeclaration, CustomDeclarationValue};
use crate::properties_and_values::registry::PropertyRegistration;
use crate::properties_and_values::value::ComputedValue as ComputedRegisteredValue;
use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, PrecomputedHasher};
use crate::stylist::Stylist;
@ -207,13 +208,30 @@ impl ToCss for SpecifiedValue {
pub type CustomPropertiesMap =
IndexMap<Name, Arc<VariableValue>, BuildHasherDefault<PrecomputedHasher>>;
// IndexMap equality doesn't consider ordering, which we have to account for.
// Also, for the same reason, IndexMap equality comparisons are slower than needed.
//
// See https://github.com/bluss/indexmap/issues/153
fn maps_equal(l: Option<&CustomPropertiesMap>, r: Option<&CustomPropertiesMap>) -> bool {
match (l, r) {
(Some(l), Some(r)) => {
l.len() == r.len() &&
l.iter()
.zip(r.iter())
.all(|((k1, v1), (k2, v2))| k1 == k2 && v1 == v2)
},
(None, None) => true,
_ => false,
}
}
/// A pair of separate CustomPropertiesMaps, split between custom properties
/// that have the inherit flag set and those with the flag unset.
#[repr(C)]
#[derive(Clone, Debug, Default)]
pub struct ComputedCustomProperties {
/// Map for custom properties with inherit flag set, including classical CSS
/// variables. Defined as ref-counted for cheap copy.
/// Map for custom properties with inherit flag set, including non-registered
/// ones. Defined as ref-counted for cheap copy.
pub inherited: Option<Arc<CustomPropertiesMap>>,
/// Map for custom properties with inherit flag unset.
pub non_inherited: Option<Box<CustomPropertiesMap>>,
@ -225,31 +243,47 @@ impl ComputedCustomProperties {
self.inherited.is_none() && self.non_inherited.is_none()
}
/// Return the name and value of the property at specified index, if any.
pub fn property_at(&self, index: usize) -> Option<(&Name, &Arc<VariableValue>)> {
// Just expose the custom property items from
// custom_properties.inherited, followed by custom property items from
// custom_properties.non_inherited.
// TODO(bug 1855629): We should probably expose all properties that
// don't have the guaranteed-invalid-value (including non-inherited
// properties with an initial value), not just the ones in the maps.
// TODO(bug 1855629): In which order should we expose these properties?
match (&self.inherited, &self.non_inherited) {
(Some(p1), Some(p2)) => p1
.get_index(index)
.or_else(|| p2.get_index(index - p1.len())),
(Some(p1), None) => p1.get_index(index),
(None, Some(p2)) => p2.get_index(index),
(None, None) => None,
}
}
fn read(&self) -> ReadOnlyCustomProperties {
ReadOnlyCustomProperties {
inherited: self.inherited.as_deref(),
non_inherited: self.non_inherited.as_deref(),
}
}
fn inherited_equal(&self, other: &Self) -> bool {
maps_equal(self.inherited.as_deref(), other.inherited.as_deref())
}
fn non_inherited_equal(&self, other: &Self) -> bool {
maps_equal(
self.non_inherited.as_deref(),
other.non_inherited.as_deref(),
)
}
}
impl PartialEq for ComputedCustomProperties {
fn eq(&self, other: &Self) -> bool {
// IndexMap equality doesn't consider ordering, which we have to account for.
// Also, for the same reason, IndexMap equality comparisons are slower than needed.
//
// See https://github.com/bluss/indexmap/issues/153
// TODO(bug 1840478): Handle non-inherited properties.
match (&self.inherited, &other.inherited) {
(Some(l), Some(r)) => {
l.len() == r.len() &&
l.iter()
.zip(r.iter())
.all(|((k1, v1), (k2, v2))| k1 == k2 && v1 == v2)
},
(None, None) => true,
_ => false,
}
self.inherited_equal(other) && self.non_inherited_equal(other)
}
}
@ -265,31 +299,44 @@ impl MutableCustomProperties {
/// map, depending on whether the inherit flag is set or unset.
fn insert(
&mut self,
registration: Option<&PropertyRegistration>,
name: Name,
value: Arc<VariableValue>,
) -> Option<Arc<VariableValue>> {
// TODO(bug 1840478): Handle non-inherited properties.
let map = self
.inherited
.get_or_insert_with(|| UniqueArc::new(CustomPropertiesMap::default()));
map.insert(name, value)
self.get_map(registration).insert(name, value)
}
/// Remove a custom property from the corresponding inherited/non_inherited
/// map, depending on whether the inherit flag is set or unset.
fn remove(&mut self, name: &Name) -> Option<Arc<VariableValue>> {
// TODO(bug 1840478): Handle non-inherited properties.
self.inherited.as_mut()?.remove(name)
fn remove(
&mut self,
registration: Option<&PropertyRegistration>,
name: &Name,
) -> Option<Arc<VariableValue>> {
self.get_map(registration).remove(name)
}
/// Shrink the capacity of the inherited/non_inherited maps as much as
/// possible.
fn shrink_to_fit(&mut self) {
/// Shrink the capacity of the inherited map as much as possible. An empty
/// map is just replaced with None.
fn inherited_shrink_to_fit(&mut self) {
if let Some(ref mut map) = self.inherited {
map.shrink_to_fit();
if map.is_empty() {
self.inherited = None;
} else {
map.shrink_to_fit();
}
}
}
/// Shrink the capacity of the non_inherited map as much as possible. An
/// empty map is just replaced with None.
fn non_inherited_shrink_to_fit(&mut self) {
if let Some(ref mut map) = self.non_inherited {
map.shrink_to_fit();
if map.is_empty() {
self.non_inherited = None;
} else {
map.shrink_to_fit();
}
}
}
@ -299,6 +346,16 @@ impl MutableCustomProperties {
non_inherited: self.non_inherited.as_deref(),
}
}
fn get_map(&mut self, registration: Option<&PropertyRegistration>) -> &mut CustomPropertiesMap {
if registration.map_or(true, |r| r.inherits) {
self.inherited
.get_or_insert_with(|| UniqueArc::new(Default::default()))
} else {
self.non_inherited
.get_or_insert_with(|| Box::new(Default::default()))
}
}
}
#[derive(Copy, Clone, Default)]
@ -308,9 +365,13 @@ struct ReadOnlyCustomProperties<'a> {
}
impl<'a> ReadOnlyCustomProperties<'a> {
fn get(&self, name: &Name) -> Option<&Arc<VariableValue>> {
// TODO(bug 1840478): Handle non-inherited properties.
self.inherited?.get(name)
fn get(&self, stylist: &Stylist, name: &Name) -> Option<&Arc<VariableValue>> {
let registration = stylist.get_custom_property_registration(&name);
if registration.map_or(true, |r| r.inherits) {
self.inherited?.get(name)
} else {
self.non_inherited?.get(name)
}
}
}
@ -697,7 +758,6 @@ fn parse_and_substitute_fallback<'i>(
custom_properties: ReadOnlyCustomProperties,
stylist: &Stylist,
) -> Result<ComputedValue, ParseError<'i>> {
debug_assert!(custom_properties.non_inherited.is_none());
input.skip_whitespace();
let after_comma = input.state();
let first_token_type = input
@ -766,12 +826,29 @@ pub struct CustomPropertiesBuilder<'a> {
impl<'a> CustomPropertiesBuilder<'a> {
/// Create a new builder, inheriting from a given custom properties map.
pub fn new(inherited: &'a ComputedCustomProperties, stylist: &'a Stylist) -> Self {
pub fn new(
inherited: &'a ComputedCustomProperties,
stylist: &'a Stylist,
is_root_element: bool,
) -> Self {
Self {
seen: PrecomputedHashSet::default(),
reverted: Default::default(),
may_have_cycles: false,
custom_properties: MutableCustomProperties::default(),
custom_properties: MutableCustomProperties {
inherited: if is_root_element {
debug_assert!(inherited.is_empty());
stylist
.get_custom_property_initial_values()
.map(UniqueArc::new)
} else {
// This should just be a copy of inherited.inherited, but
// do it lazily and postpone that to when that actually
// becomes necessary in the cascade and build methods.
None
},
non_inherited: None,
},
inherited,
stylist,
}
@ -799,15 +876,16 @@ impl<'a> CustomPropertiesBuilder<'a> {
return;
}
// TODO(bug 1840478): Handle non-inherited properties.
// TODO(bug 1855887): Try to postpone cloning of inherited.inherited.
if self.custom_properties.inherited.is_none() {
self.custom_properties.inherited = Some(match &self.inherited.inherited {
Some(inherited) => UniqueArc::new((**inherited).clone()),
None => UniqueArc::new(CustomPropertiesMap::default()),
});
self.custom_properties.inherited = self
.inherited
.inherited
.as_ref()
.map(|i| UniqueArc::new((**i).clone()));
}
let map = &mut self.custom_properties;
let custom_registration = self.stylist.get_custom_property_registration(&name);
match *value {
CustomDeclarationValue::Value(ref unparsed_value) => {
let has_custom_property_references =
@ -828,17 +906,20 @@ impl<'a> CustomPropertiesBuilder<'a> {
);
return;
}
if let Some(registration) = self.stylist.get_custom_property_registration(&name)
{
if let Some(registration) = custom_registration {
let mut input = ParserInput::new(&unparsed_value.css);
let mut input = Parser::new(&mut input);
if ComputedRegisteredValue::compute(&mut input, registration).is_err() {
map.remove(name);
map.remove(custom_registration, name);
return;
}
}
}
map.insert(name.clone(), Arc::clone(unparsed_value));
map.insert(
custom_registration,
name.clone(),
Arc::clone(unparsed_value),
);
},
CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword {
CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => {
@ -847,43 +928,130 @@ impl<'a> CustomPropertiesBuilder<'a> {
self.reverted.insert(name, (priority, origin_revert));
},
CSSWideKeyword::Initial => {
map.remove(name);
// For non-inherited custom properties, 'initial' was handled in value_may_affect_style.
debug_assert!(
custom_registration.map_or(true, |r| r.inherits),
"Should've been handled earlier"
);
map.remove(custom_registration, name);
if let Some(registration) = custom_registration {
if let Some(ref initial_value) = registration.initial_value {
map.insert(custom_registration, name.clone(), initial_value.clone());
}
}
},
CSSWideKeyword::Inherit => {
// For inherited custom properties, 'inherit' was handled in value_may_affect_style.
debug_assert!(
!custom_registration.map_or(true, |r| r.inherits),
"Should've been handled earlier"
);
if let Some(inherited_value) = self
.inherited
.non_inherited
.as_ref()
.and_then(|m| m.get(name))
{
map.insert(custom_registration, name.clone(), inherited_value.clone());
}
},
// handled in value_may_affect_style
CSSWideKeyword::Unset | CSSWideKeyword::Inherit => unreachable!(),
CSSWideKeyword::Unset => unreachable!(),
},
}
}
fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool {
let custom_registration = self.stylist.get_custom_property_registration(&name);
match *value {
CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) |
CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
// Custom properties are inherited by default. So
// explicit 'inherit' or 'unset' means we can just use
// any existing value in the inherited CustomPropertiesMap.
// For inherited custom properties, explicit 'inherit' means we
// can just use any existing value in the inherited
// CustomPropertiesMap.
if custom_registration.map_or(true, |r| r.inherits) {
return false;
}
},
CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial) => {
// For non-inherited custom properties, explicit 'initial' means
// we can just use any initial value in the registration.
if !custom_registration.map_or(true, |r| r.inherits) {
return false;
}
},
CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) => {
// Explicit 'unset' means we can either just use any existing
// value in the inherited CustomPropertiesMap or the initial
// value in the registration.
return false;
},
_ => {},
}
// TODO(bug 1840478): Handle non-inherited properties.
let existing_value = self
.custom_properties
.inherited
.as_ref()
.and_then(|m| m.get(name))
.or_else(|| self.inherited.inherited.as_ref().and_then(|m| m.get(name)));
let existing_value = if custom_registration.map_or(true, |r| r.inherits) {
self.custom_properties
.inherited
.as_ref()
.and_then(|m| m.get(name))
.or_else(|| self.inherited.inherited.as_ref().and_then(|m| m.get(name)))
} else {
debug_assert!(self
.custom_properties
.non_inherited
.as_ref()
.map_or(true, |m| !m.contains_key(name)));
custom_registration.and_then(|m| m.initial_value.as_ref())
};
match (existing_value, value) {
(None, &CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)) => {
// The initial value of a custom property is the same as it
debug_assert!(
custom_registration.map_or(true, |r| r.inherits),
"Should've been handled earlier"
);
// The initial value of a custom property without a
// guaranteed-invalid initial value is the same as it
// not existing in the map.
return false;
if custom_registration.map_or(true, |r| r.initial_value.is_none()) {
return false;
}
},
(
Some(existing_value),
&CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial),
) => {
debug_assert!(
custom_registration.map_or(true, |r| r.inherits),
"Should've been handled earlier"
);
// Don't bother overwriting an existing value with the initial
// value specified in the registration.
if let Some(registration) = custom_registration {
if Some(existing_value) == registration.initial_value.as_ref() {
return false;
}
}
},
(Some(_), &CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit)) => {
debug_assert!(
!custom_registration.map_or(true, |r| r.inherits),
"Should've been handled earlier"
);
// existing_value is the registered initial value.
// Don't bother adding it to self.custom_properties.non_inherited
// if the key is also absent from self.inherited.non_inherited.
if self
.inherited
.non_inherited
.as_ref()
.map_or(true, |m| !m.contains_key(name))
{
return false;
}
},
(Some(existing_value), &CustomDeclarationValue::Value(ref value)) => {
// Don't bother overwriting an existing inherited value with
// the same specified value.
// Don't bother overwriting an existing value with the same
// specified value.
if existing_value == value {
return false;
}
@ -895,16 +1063,18 @@ impl<'a> CustomPropertiesBuilder<'a> {
}
fn inherited_properties_match(&self, custom_properties: &MutableCustomProperties) -> bool {
// TODO(bug 1840478): Handle non-inherited properties.
let (inherited, map) = match (&self.inherited.inherited, &custom_properties.inherited) {
(Some(inherited), Some(map)) => (inherited, map),
(None, None) => return true,
(_, None) => return true,
_ => return false,
};
if inherited.len() != map.len() {
return false;
}
for name in self.seen.iter() {
// IndexMap.get() returns None if the element is not in the map, so
// we don't bother checking whether name actually corresponds to an
// inherited custom property here.
if inherited.get(*name) != map.get(*name) {
return false;
}
@ -914,19 +1084,36 @@ impl<'a> CustomPropertiesBuilder<'a> {
/// Returns the final map of applicable custom properties.
///
/// If there was any specified property, we've created a new map and now we
/// If there was any specified property or non-inherited custom property
/// with an initial value, we've created a new map and now we
/// need to remove any potential cycles, and wrap it in an arc.
///
/// Otherwise, just use the inherited custom properties map.
pub fn build(mut self) -> ComputedCustomProperties {
// TODO(bug 1840478): Handle non-inherited properties.
debug_assert!(self.custom_properties.non_inherited.is_none());
debug_assert!(self.inherited.non_inherited.is_none());
if self.custom_properties.inherited.is_none() {
return self.inherited.clone();
// Return early when self.custom_properties is empty, covering the
// common case of nodes without any custom property specified. In
// that situation, inherited properties will just use the value from
// self.inherited while non-inherited properties will use their
// initial values. These initial values are required to be
// 'computationally independent' but may still differ from the ones
// in self.inherited, so ensure they are used in the parent too.
if self.custom_properties.non_inherited.is_none() &&
self.inherited.non_inherited.is_none()
{
return self.inherited.clone();
}
}
if self.may_have_cycles {
// TODO(bug 1855887): Try to postpone cloning of inherited.inherited.
if self.custom_properties.inherited.is_none() {
self.custom_properties.inherited = self
.inherited
.inherited
.as_ref()
.map(|i| UniqueArc::new((**i).clone()));
}
substitute_all(
&mut self.custom_properties,
self.inherited,
@ -940,10 +1127,15 @@ impl<'a> CustomPropertiesBuilder<'a> {
// haven't really changed, and save some memory by reusing the inherited
// map in that case.
if self.inherited_properties_match(&self.custom_properties) {
return self.inherited.clone();
self.custom_properties.non_inherited_shrink_to_fit();
return ComputedCustomProperties {
inherited: self.inherited.inherited.clone(),
non_inherited: self.custom_properties.non_inherited.take(),
};
}
self.custom_properties.shrink_to_fit();
self.custom_properties.inherited_shrink_to_fit();
self.custom_properties.non_inherited_shrink_to_fit();
ComputedCustomProperties {
inherited: self
.custom_properties
@ -1028,7 +1220,7 @@ fn substitute_all(
// Some shortcut checks.
let (name, value) = {
let props = context.map.read();
let value = props.get(name)?;
let value = props.get(context.stylist, name)?;
// Nothing to resolve.
if value.references.custom_properties.is_empty() {
@ -1127,12 +1319,18 @@ fn substitute_all(
// Anything here is in a loop which can traverse to the
// variable we are handling, so remove it from the map, it's invalid
// at computed-value time.
context.map.remove(&var_name);
context.map.remove(
context.stylist.get_custom_property_registration(&var_name),
&var_name,
);
in_loop = true;
}
if in_loop {
// This variable is in loop. Resolve to invalid.
context.map.remove(&name);
context.map.remove(
context.stylist.get_custom_property_registration(&name),
&name,
);
return None;
}
@ -1177,6 +1375,7 @@ fn substitute_references_in_value_and_apply(
) {
debug_assert!(value.has_references());
let custom_registration = stylist.get_custom_property_registration(&name);
let mut computed_value = ComputedValue::empty();
{
@ -1196,7 +1395,7 @@ fn substitute_references_in_value_and_apply(
Ok(t) => t,
Err(..) => {
// Invalid at computed value time.
custom_properties.remove(name);
custom_properties.remove(custom_registration, name);
return;
},
};
@ -1205,7 +1404,7 @@ fn substitute_references_in_value_and_apply(
.push_from(&input, position, last_token_type)
.is_err()
{
custom_properties.remove(name);
custom_properties.remove(custom_registration, name);
return;
}
}
@ -1215,34 +1414,53 @@ fn substitute_references_in_value_and_apply(
let mut input = Parser::new(&mut input);
// If variable fallback results in a wide keyword, deal with it now.
let inherits = custom_registration.map_or(true, |r| r.inherits);
if let Ok(kw) = input.try_parse(CSSWideKeyword::parse) {
match kw {
CSSWideKeyword::Initial => {
custom_properties.remove(name);
match (kw, inherits) {
(CSSWideKeyword::Initial, _) |
(CSSWideKeyword::Revert, false) |
(CSSWideKeyword::RevertLayer, false) |
(CSSWideKeyword::Unset, false) => {
// TODO(bug 1273706): Do we really need to insert the initial value if inherits==false?
custom_properties.remove(custom_registration, name);
if let Some(registration) = custom_registration {
if let Some(ref initial_value) = registration.initial_value {
custom_properties.insert(
custom_registration,
name.clone(),
Arc::clone(initial_value),
);
}
}
},
CSSWideKeyword::Revert |
CSSWideKeyword::RevertLayer |
CSSWideKeyword::Inherit |
CSSWideKeyword::Unset => {
(CSSWideKeyword::Revert, true) |
(CSSWideKeyword::RevertLayer, true) |
(CSSWideKeyword::Inherit, _) |
(CSSWideKeyword::Unset, true) => {
// TODO(bug 1273706): Do we really need to insert the inherited value if inherits==true?
// TODO: It's unclear what this should do for revert / revert-layer, see
// https://github.com/w3c/csswg-drafts/issues/9131. For now treating as unset
// seems fine?
// TODO(bug 1840478): Handle non-inherited properties.
match inherited.inherited.as_ref().and_then(|map| map.get(name)) {
match inherited.read().get(stylist, name) {
Some(value) => {
custom_properties.insert(name.clone(), Arc::clone(value));
custom_properties.insert(
custom_registration,
name.clone(),
Arc::clone(value),
);
},
None => {
custom_properties.remove(name);
custom_properties.remove(custom_registration, name);
},
};
},
}
false
} else {
if let Some(registration) = stylist.get_custom_property_registration(&name) {
if let Some(registration) = custom_registration {
if ComputedRegisteredValue::compute(&mut input, registration).is_err() {
custom_properties.remove(name);
custom_properties.remove(custom_registration, name);
return;
}
}
@ -1251,7 +1469,7 @@ fn substitute_references_in_value_and_apply(
};
if should_insert {
computed_value.css.shrink_to_fit();
custom_properties.insert(name.clone(), Arc::new(computed_value));
custom_properties.insert(custom_registration, name.clone(), Arc::new(computed_value));
}
}
@ -1272,7 +1490,6 @@ fn substitute_block<'i>(
custom_properties: ReadOnlyCustomProperties,
stylist: &Stylist,
) -> Result<TokenSerializationType, ParseError<'i>> {
debug_assert!(custom_properties.non_inherited.is_none());
let mut last_token_type = TokenSerializationType::nothing();
let mut set_position_at_next_iteration = false;
loop {
@ -1328,9 +1545,15 @@ fn substitute_block<'i>(
None
}
} else {
// TODO(bug 1840478): Handle non-inherited properties.
registration = stylist.get_custom_property_registration(&name);
custom_properties.get(&name).map(|v| &**v)
let value = custom_properties.get(stylist, &name);
if registration.map_or(true, |r| r.inherits) {
value.map(|v| &**v)
} else {
value
.or_else(|| registration.and_then(|m| m.initial_value.as_ref()))
.map(|v| &**v)
}
};
if let Some(v) = value {

View File

@ -290,7 +290,7 @@ where
{
let mut builder =
CustomPropertiesBuilder::new(inherited_style.custom_properties(), stylist);
CustomPropertiesBuilder::new(inherited_style.custom_properties(), stylist, is_root_element);
for (declaration, priority) in iter {
if let PropertyDeclaration::Custom(ref declaration) = *declaration {
builder.cascade(declaration, priority);

View File

@ -946,7 +946,7 @@ impl PropertyDeclarationBlock {
inherited_custom_properties: &crate::custom_properties::ComputedCustomProperties,
stylist: &Stylist,
) -> crate::custom_properties::ComputedCustomProperties {
let mut builder = CustomPropertiesBuilder::new(inherited_custom_properties, stylist);
let mut builder = CustomPropertiesBuilder::new(inherited_custom_properties, stylist, false);
for declaration in self.normal_declaration_iter() {
if let PropertyDeclaration::Custom(ref declaration) = *declaration {

View File

@ -3258,11 +3258,18 @@ impl ComputedValues {
s
}
PropertyDeclarationId::Custom(name) => {
// TODO(bug 1840478): Handle non-inherited properties.
self.custom_properties
.as_ref().inherited.unwrap()
// FIXME(bug 1273706): This should use a stylist to determine
// whether the name corresponds to an inherited custom property
// and then choose the inherited/non_inherited map accordingly.
// It should also fallback to registered initial values for
// non-inherited properties. See Servo_GetCustomPropertyValue.
let p = &self.custom_properties;
let value = p
.inherited
.as_ref()
.and_then(|map| map.get(name))
.map_or(String::new(), |value| value.to_css_string())
.or_else(|| p.non_inherited.as_ref().and_then(|map| map.get(name)));
value.map_or(String::new(), |value| value.to_css_string())
}
}
}

View File

@ -39,6 +39,12 @@ impl ScriptRegistry {
self.properties.get(name)
}
/// Gets already-registered custom properties via script.
#[inline]
pub fn properties(&self) -> &PrecomputedHashMap<Atom, PropertyRegistration> {
&self.properties
}
/// Register a given property. As per
/// <https://drafts.css-houdini.org/css-properties-values-api-1/#the-registerproperty-function>
/// we don't allow overriding the registration.

View File

@ -8,6 +8,7 @@ use crate::applicable_declarations::{
ApplicableDeclarationBlock, ApplicableDeclarationList, CascadePriority,
};
use crate::context::{CascadeInputs, QuirksMode};
use crate::custom_properties::CustomPropertiesMap;
use crate::dom::TElement;
#[cfg(feature = "gecko")]
use crate::gecko_bindings::structs::{ServoStyleSetSizes, StyleRuleInclusion};
@ -695,6 +696,39 @@ impl Stylist {
None
}
/// Returns a map of initial values for registered properties with the
/// inherits flag set and a specified initial value.
/// https://drafts.css-houdini.org/css-properties-values-api-1/#determining-registration
pub fn get_custom_property_initial_values(&self) -> Option<CustomPropertiesMap> {
let mut seen_names = PrecomputedHashSet::default();
let mut map = CustomPropertiesMap::default();
for (k, v) in self.custom_property_script_registry().properties().iter() {
seen_names.insert(k.clone());
if v.inherits {
if let Some(value) = &v.initial_value {
map.insert(k.clone(), value.clone());
}
}
}
for (data, _) in self.iter_origins() {
for (k, v) in data.custom_property_registrations.iter() {
if seen_names.insert(k.clone()) {
let last_value = &v.last().unwrap().0;
if last_value.inherits {
if let Some(ref value) = last_value.initial_value {
map.insert(k.clone(), value.clone());
}
}
}
}
}
if map.is_empty() {
None
} else {
map.shrink_to_fit();
Some(map)
}
}
/// Rebuilds (if needed) the CascadeData given a sheet collection.
pub fn rebuild_author_data<S>(

View File

@ -7420,32 +7420,39 @@ pub unsafe extern "C" fn Servo_GetResolvedValue(
#[no_mangle]
pub unsafe extern "C" fn Servo_GetCustomPropertyValue(
computed_values: &ComputedValues,
raw_style_set: &PerDocumentStyleData,
name: &nsACString,
value: &mut nsACString,
) -> bool {
// TODO(bug 1840478): Handle non-inherited properties.
let inherited = match &computed_values.custom_properties.inherited {
Some(p) => p,
None => return false,
};
let doc_data = raw_style_set.borrow();
let name = Atom::from(name.as_str_unchecked());
let computed_value = match inherited.get(&name) {
Some(v) => v,
None => return false,
let stylist = &doc_data.stylist;
let custom_registration = stylist.get_custom_property_registration(&name);
let computed_value = if custom_registration.map_or(true, |r| r.inherits) {
computed_values.custom_properties.inherited.as_ref().and_then(|m| m.get(&name))
} else {
computed_values.custom_properties.non_inherited.as_ref().and_then(|m| m.get(&name))
.or_else(|| custom_registration.and_then(|m| m.initial_value.as_ref()))
};
computed_value.to_css(&mut CssWriter::new(value)).unwrap();
true
if let Some(v) = computed_value {
v.to_css(&mut CssWriter::new(value)).unwrap();
true
} else {
false
}
}
#[no_mangle]
pub extern "C" fn Servo_GetCustomPropertiesCount(computed_values: &ComputedValues) -> u32 {
// TODO(bug 1840478): Handle non-inherited properties.
match &computed_values.custom_properties().inherited {
Some(m) => m.len() as u32,
None => 0,
}
// Just expose the custom property items from custom_properties.inherited,
// and custom_properties.non_inherited.
// TODO(bug 1855629): We should probably expose all properties that don't
// have the guaranteed-invalid-value (including non-inherited properties
// with an initial value), not just the ones in the maps.
let properties = computed_values.custom_properties();
(properties.inherited.as_ref().map_or(0, |m| m.len()) +
properties.non_inherited.as_ref().map_or(0, |m| m.len())) as u32
}
#[no_mangle]
@ -7453,18 +7460,10 @@ pub extern "C" fn Servo_GetCustomPropertyNameAt(
computed_values: &ComputedValues,
index: u32,
) -> *mut nsAtom {
// TODO(bug 1840478): Handle non-inherited properties.
let inherited = match &computed_values.custom_properties.inherited {
Some(p) => p,
None => return ptr::null_mut(),
};
let property_name = match inherited.get_index(index as usize) {
Some((key, _value)) => key,
None => return ptr::null_mut(),
};
property_name.as_ptr()
match &computed_values.custom_properties.property_at(index as usize) {
Some((name, _value)) => name.as_ptr(),
None => ptr::null_mut(),
}
}
#[no_mangle]

View File

@ -1,8 +1,3 @@
[layer-cssom-order-reverse-at-property.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Insert layer invalidates @property]
expected: FAIL
[Delete layer invalidates @property]
expected: FAIL

View File

@ -1,14 +1,3 @@
[layer-property-override.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[@property override between layers]
expected: FAIL
[@property override update with appended sheet 1]
expected: FAIL
[@property override update with appended sheet 2]
expected: FAIL
[@property unlayered overrides layered]
expected: FAIL

View File

@ -1,4 +0,0 @@
[animate-invalid.html]
[Do not crash when animating to unresolved var()]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <angle># with iterationComposite]
expected: FAIL
[Animating a custom property of type <angle># with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <angle>+ with iterationComposite]
expected: FAIL
[Animating a custom property of type <angle>+ with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <color># with iterationComposite]
expected: FAIL
[Animating a custom property of type <color># with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <color>+ with iterationComposite]
expected: FAIL
[Animating a custom property of type <color>+ with different lengths is discrete]
expected: FAIL

View File

@ -1,9 +0,0 @@
[custom-property-animation-custom-ident.html]
[Animating a custom property of type <custom-ident> is discrete]
expected: FAIL
[Animating a custom property of type <custom-ident>+ is discrete]
expected: FAIL
[Animating a custom property of type <custom-ident># is discrete]
expected: FAIL

View File

@ -1,9 +0,0 @@
[custom-property-animation-image.html]
[Animating a custom property of type <image> is discrete]
expected: FAIL
[Animating a custom property of type <image>+ is discrete]
expected: FAIL
[Animating a custom property of type <image># is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <integer># with iterationComposite]
expected: FAIL
[Animating a custom property of type <integer># with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <integer>+ with iterationComposite]
expected: FAIL
[Animating a custom property of type <integer>+ with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <length># with iterationComposite]
expected: FAIL
[Animating a custom property of type <length># with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <length-percentage># with iterationComposite]
expected: FAIL
[Animating a custom property of type <length-percentage># with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <length-percentage>+ with iterationComposite]
expected: FAIL
[Animating a custom property of type <length-percentage>+ with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <length>+ with iterationComposite]
expected: FAIL
[Animating a custom property of type <length>+ with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <number># with iterationComposite]
expected: FAIL
[Animating a custom property of type <number># with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <number>+ with iterationComposite]
expected: FAIL
[Animating a custom property of type <number>+ with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <percentage># with iterationComposite]
expected: FAIL
[Animating a custom property of type <percentage># with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <percentage>+ with iterationComposite]
expected: FAIL
[Animating a custom property of type <percentage>+ with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <resolution># with iterationComposite]
expected: FAIL
[Animating a custom property of type <resolution># with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <resolution>+ with iterationComposite]
expected: FAIL
[Animating a custom property of type <resolution>+ with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <time># with iterationComposite]
expected: FAIL
[Animating a custom property of type <time># with different lengths is discrete]
expected: FAIL

View File

@ -13,6 +13,3 @@
[Animating a custom property of type <time>+ with iterationComposite]
expected: FAIL
[Animating a custom property of type <time>+ with different lengths is discrete]
expected: FAIL

View File

@ -1,9 +0,0 @@
[custom-property-animation-url.html]
[Animating a custom property of type <url> is discrete]
expected: FAIL
[Animating a custom property of type <url>+ is discrete]
expected: FAIL
[Animating a custom property of type <url># is discrete]
expected: FAIL

View File

@ -1,16 +0,0 @@
[at-property-stylesheets.html]
[@property removal detected when last @property rule disappears]
expected: FAIL
[@property removal detected with removal of second stylesheet]
expected: FAIL
[@property detected in second stylesheet]
expected: FAIL
[@property removal detected with removal of first stylesheet]
expected: FAIL
[@property detected when stylesheet appears]
expected: FAIL

View File

@ -1,78 +1,21 @@
[at-property.html]
[Rule applied [<transform-list>, rotateX(0deg), false\]]
expected: FAIL
[Rule applied [<color>, rgb(1, 2, 3), false\]]
expected: FAIL
[Rule applied [<number>, 2.5, false\]]
expected: FAIL
[Rule applied [<angle>, 42deg, false\]]
expected: FAIL
[Rule applied [<angle>, 1turn, false\]]
expected: FAIL
[Rule applied [<length-percentage>, 10px, false\]]
expected: FAIL
[Rule applied [<length-percentage>, 10%, false\]]
expected: FAIL
[Rule applied [<integer>, 5, false\]]
expected: FAIL
[Rule applied [*, if(){}, false\]]
expected: FAIL
[Rule applied [<color>, green, false\]]
expected: FAIL
[Rule applied [<resolution>, 96dpi, false\]]
expected: FAIL
[Non-inherited properties do not inherit]
expected: FAIL
[Rule applied [<color>, tomato, false\]]
expected: FAIL
[Rule applied [<time>, 10s, false\]]
expected: FAIL
[Rule applied [<length>, 10px, false\]]
expected: FAIL
[Rule applied [<transform-list>, rotateX(0deg) translateX(10px), false\]]
expected: FAIL
[Rule applied [<length-percentage>, calc(10% + 10px), false\]]
expected: FAIL
[Rule applied [<time>, 1000ms, false\]]
expected: FAIL
[Rule applied [<image>, url("http://a/"), false\]]
expected: FAIL
[Rule applied [<percentage>, 10%, false\]]
expected: FAIL
[Rule applied [<url>, url("http://a/"), false\]]
expected: FAIL
[Initial value may be omitted for universal registration]
expected: FAIL
[Rule applied [<color>, tomato, true\]]
expected: FAIL
[Rule applied [<resolution>, 50dppx, false\]]
expected: FAIL
[Initial values substituted as computed value]
expected: FAIL
[Rule applied [<transform-function>, rotateX(0deg), false\]]
expected: FAIL

View File

@ -1,40 +1,12 @@
[determine-registration.html]
[@property registrations are cleared when rule removed]
expected: FAIL
[Previous invalid rule does not prevent valid rule from causing registration]
expected: FAIL
[CSS.registerProperty determines the registration when uncontested]
expected: FAIL
[@property later in stylesheet wins]
expected: FAIL
[Invalid @property rule (missing syntax) does not overwrite previous valid rule]
expected: FAIL
[@property determines the registration when uncontested]
expected: FAIL
[CSS.registerProperty wins over @property]
expected: FAIL
[Computed value becomes token sequence when @property is removed]
expected: FAIL
[@property later in document order wins]
expected: FAIL
[Invalid @property rule (missing inherits descriptor) does not overwrite previous valid rule]
expected: FAIL
[Invalid @property rule (missing initial-value) does not overwrite previous valid rule]
expected: FAIL
[Unknown descriptors are ignored and do not invalidate rule]
expected: FAIL
[Inherited status is reflected in computed styles when @property is removed]
expected: FAIL

View File

@ -2,12 +2,6 @@
[Registered properties are correctly inherited (or not) depending on the inherits flag.]
expected: FAIL
[Explicitly inheriting from a parent with an invalid value results in initial value.]
expected: FAIL
[Explicitly inheriting from a parent with no value results in initial value.]
expected: FAIL
[Reference to syntax-incompatible variable results in inherited value]
expected: FAIL

View File

@ -1,7 +1,6 @@
[registered-property-change-style-001.html]
[Registered property overrides a previous declaration ]
expected: FAIL
[New registered property declaration]
expected: FAIL
[Registered property overrides a previous declaration ]
expected: FAIL

View File

@ -1,3 +0,0 @@
[registered-property-crosstalk.html]
[Only #c should be affected by --x:42]
expected: FAIL

View File

@ -5,9 +5,6 @@
[Values can be removed from inline styles]
expected: FAIL
[Stylesheets can be modified by CSSOM]
expected: FAIL
[Valid values can be set on inline styles]
expected: FAIL

View File

@ -1,7 +1,4 @@
[registered-property-initial.html]
[Initial non-inherited value can be substituted [pink, background-color\]]
expected: FAIL
[Initial value for <length-percentage> correctly computed [calc(1in + 10% + 4px)\]]
expected: FAIL
@ -14,12 +11,6 @@
[Initial value for <transform-list> correctly computed [scale(calc(2 + 1)) translateX(calc(3px + 1px))\]]
expected: FAIL
[Initial value for <transform-function> correctly computed [rotate(42deg)\]]
expected: FAIL
[Initial inherited value can be substituted [purple, color\]]
expected: FAIL
[Initial value for <color> correctly computed [pink, inherits\]]
expected: FAIL
@ -35,9 +26,6 @@
[Initial non-inherited value can be substituted [calc(20 + 20 + 10), --x\]]
expected: FAIL
[Initial non-inherited value can be substituted [\tcalc(13% + 37px), --x\]]
expected: FAIL
[Initial non-inherited value can be substituted [scale(calc(2 + 2)) translateX(calc(3px + 1px)), --x\]]
expected: FAIL
@ -59,9 +47,6 @@
[Initial non-inherited value can be substituted [\tfoo\t, --x\]]
expected: FAIL
[Initial non-inherited value can be substituted [\ttest, --x\]]
expected: FAIL
[Initial value for <length> correctly computed [2.54cm\]]
expected: FAIL
@ -86,3 +71,5 @@
[Initial value for <url>+ correctly computed [url(a) url(a)\]]
expected: FAIL
[Initial inherited value can be substituted [purple, color\]]
expected: FAIL

View File

@ -1,7 +1,4 @@
[registered-property-revert.html]
[Non-inherited registered custom property can be reverted]
expected: FAIL
[Non-inherited registered custom property can be reverted in animation]
expected: FAIL

View File

@ -1,3 +0,0 @@
[self-utils.html]
[Generated properties respect inherits flag]
expected: FAIL

View File

@ -1,9 +1,3 @@
[var-reference-registered-properties-cycles.html]
[A var() cycle between two registered properties is handled correctly.]
expected: FAIL
[A var() cycle between a registered properties and an unregistered property is handled correctly.]
expected: FAIL
[A var() cycle between a two unregistered properties is handled correctly.]
[A var() cycle between a syntax:'*' property and an unregistered property is handled correctly.]
expected: FAIL

View File

@ -13,3 +13,24 @@
[Values are absolutized when substituting into properties with universal syntax]
expected: FAIL
[Fallback must adhere to registered syntax [<length>, 10px\]]
expected: FAIL
[Fallback must adhere to registered syntax [<length> | none, none\]]
expected: FAIL
[Fallback must adhere to registered syntax [<length>, var(--length-1)\]]
expected: FAIL
[References to registered var()-properties work in registered lists]
expected: FAIL
[References to mixed registered and unregistered var()-properties work in registered lists]
expected: FAIL
[Registered lists may be concatenated]
expected: FAIL
[Invalid values for registered properties are serialized as the empty string]
expected: FAIL

View File

@ -1,2 +0,0 @@
[highlight-cascade-004.html]
expected: FAIL