diff options
Diffstat (limited to 'servo/components/style/properties/properties.mako.rs')
-rw-r--r-- | servo/components/style/properties/properties.mako.rs | 4273 |
1 files changed, 4273 insertions, 0 deletions
diff --git a/servo/components/style/properties/properties.mako.rs b/servo/components/style/properties/properties.mako.rs new file mode 100644 index 0000000000..63b3f971f9 --- /dev/null +++ b/servo/components/style/properties/properties.mako.rs @@ -0,0 +1,4273 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// This file is a Mako template: http://www.makotemplates.org/ + +// Please note that valid Rust syntax may be mangled by the Mako parser. +// For example, Vec<&Foo> will be mangled as Vec&Foo>. To work around these issues, the code +// can be escaped. In the above example, Vec<<&Foo> or Vec< &Foo> achieves the desired result of Vec<&Foo>. + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +use app_units::Au; +use arrayvec::{ArrayVec, Drain as ArrayVecDrain}; +use servo_arc::{Arc, UniqueArc}; +use std::borrow::Cow; +use std::{ops, ptr}; +use std::fmt::{self, Write}; +use std::mem; + +use cssparser::{Parser, TokenSerializationType}; +use cssparser::ParserInput; +#[cfg(feature = "servo")] use euclid::SideOffsets2D; +use crate::context::QuirksMode; +#[cfg(feature = "gecko")] use crate::gecko_bindings::structs::{self, nsCSSPropertyID}; +#[cfg(feature = "servo")] use crate::logical_geometry::LogicalMargin; +#[cfg(feature = "servo")] use crate::computed_values; +use crate::logical_geometry::WritingMode; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use crate::computed_value_flags::*; +use fxhash::FxHashMap; +use crate::media_queries::Device; +use crate::parser::ParserContext; +use crate::selector_parser::PseudoElement; +#[cfg(feature = "servo")] use servo_config::prefs; +use style_traits::{CssWriter, KeywordsCollectFn, ParseError, ParsingMode}; +use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; +use to_shmem::impl_trivial_to_shmem; +use crate::stylesheets::{CssRuleType, CssRuleTypes, Origin, UrlExtraData}; +use crate::use_counters::UseCounters; +use crate::values::generics::text::LineHeight; +use crate::values::{computed, resolved, serialize_atom_name}; +use crate::values::specified::font::SystemFont; +use crate::rule_tree::StrongRuleNode; +use crate::str::{CssString, CssStringWriter}; +use std::cell::Cell; +use super::declaration_block::AppendableValue; + +<%! + from collections import defaultdict + from data import Method, PropertyRestrictions, Keyword, to_rust_ident, \ + to_camel_case, RULE_VALUES, SYSTEM_FONT_LONGHANDS + import os.path +%> + +/// Conversion with fewer impls than From/Into +pub trait MaybeBoxed<Out> { + /// Convert + fn maybe_boxed(self) -> Out; +} + +impl<T> MaybeBoxed<T> for T { + #[inline] + fn maybe_boxed(self) -> T { self } +} + +impl<T> MaybeBoxed<Box<T>> for T { + #[inline] + fn maybe_boxed(self) -> Box<T> { Box::new(self) } +} + +macro_rules! expanded { + ( $( $name: ident: $value: expr ),+ ) => { + expanded!( $( $name: $value, )+ ) + }; + ( $( $name: ident: $value: expr, )+ ) => { + Longhands { + $( + $name: MaybeBoxed::maybe_boxed($value), + )+ + } + } +} + +/// A module with all the code for longhand properties. +#[allow(missing_docs)] +pub mod longhands { + % for style_struct in data.style_structs: + include!("${repr(os.path.join(OUT_DIR, 'longhands/{}.rs'.format(style_struct.name_lower)))[1:-1]}"); + % endfor + pub const ANIMATABLE_PROPERTY_COUNT: usize = ${sum(1 for prop in data.longhands if prop.animatable)}; +} + +macro_rules! unwrap_or_initial { + ($prop: ident) => (unwrap_or_initial!($prop, $prop)); + ($prop: ident, $expr: expr) => + ($expr.unwrap_or_else(|| $prop::get_initial_specified_value())); +} + +/// A module with code for all the shorthand css properties, and a few +/// serialization helpers. +#[allow(missing_docs)] +pub mod shorthands { + use cssparser::Parser; + use crate::parser::{Parse, ParserContext}; + use style_traits::{ParseError, StyleParseErrorKind}; + use crate::values::specified; + + use style_traits::{CssWriter, ToCss}; + use crate::values::specified::{BorderStyle, Color}; + use std::fmt::{self, Write}; + + fn serialize_directional_border<W, I,>( + dest: &mut CssWriter<W>, + width: &I, + style: &BorderStyle, + color: &Color, + ) -> fmt::Result + where + W: Write, + I: ToCss, + { + width.to_css(dest)?; + // FIXME(emilio): Should we really serialize the border style if it's + // `solid`? + dest.write_char(' ')?; + style.to_css(dest)?; + if *color != Color::CurrentColor { + dest.write_char(' ')?; + color.to_css(dest)?; + } + Ok(()) + } + + % for style_struct in data.style_structs: + include!("${repr(os.path.join(OUT_DIR, 'shorthands/{}.rs'.format(style_struct.name_lower)))[1:-1]}"); + % endfor + + // We didn't define the 'all' shorthand using the regular helpers:shorthand + // mechanism, since it causes some very large types to be generated. + // + // Also, make sure logical properties appear before its physical + // counter-parts, in order to prevent bugs like: + // + // https://bugzilla.mozilla.org/show_bug.cgi?id=1410028 + // + // FIXME(emilio): Adopt the resolution from: + // + // https://github.com/w3c/csswg-drafts/issues/1898 + // + // when there is one, whatever that is. + <% + logical_longhands = [] + other_longhands = [] + + for p in data.longhands: + if p.name in ['direction', 'unicode-bidi']: + continue; + if not p.enabled_in_content() and not p.experimental(engine): + continue; + if "Style" not in p.rule_types_allowed_names(): + continue; + if p.logical: + logical_longhands.append(p.name) + else: + other_longhands.append(p.name) + + data.declare_shorthand( + "all", + logical_longhands + other_longhands, + engines="gecko servo-2013 servo-2020", + spec="https://drafts.csswg.org/css-cascade-3/#all-shorthand" + ) + %> + + /// The max amount of longhands that the `all` shorthand will ever contain. + pub const ALL_SHORTHAND_MAX_LEN: usize = ${len(logical_longhands + other_longhands)}; +} + +<% + from itertools import groupby + + # After this code, `data.longhands` is sorted in the following order: + # - first all keyword variants and all variants known to be Copy, + # - second all the other variants, such as all variants with the same field + # have consecutive discriminants. + # The variable `variants` contain the same entries as `data.longhands` in + # the same order, but must exist separately to the data source, because + # we then need to add three additional variants `WideKeywordDeclaration`, + # `VariableDeclaration` and `CustomDeclaration`. + + variants = [] + for property in data.longhands: + variants.append({ + "name": property.camel_case, + "type": property.specified_type(), + "doc": "`" + property.name + "`", + "copy": property.specified_is_copy(), + }) + + groups = {} + keyfunc = lambda x: x["type"] + sortkeys = {} + for ty, group in groupby(sorted(variants, key=keyfunc), keyfunc): + group = list(group) + groups[ty] = group + for v in group: + if len(group) == 1: + sortkeys[v["name"]] = (not v["copy"], 1, v["name"], "") + else: + sortkeys[v["name"]] = (not v["copy"], len(group), ty, v["name"]) + variants.sort(key=lambda x: sortkeys[x["name"]]) + + # It is extremely important to sort the `data.longhands` array here so + # that it is in the same order as `variants`, for `LonghandId` and + # `PropertyDeclarationId` to coincide. + data.longhands.sort(key=lambda x: sortkeys[x.camel_case]) +%> + +// WARNING: It is *really* important for the variants of `LonghandId` +// and `PropertyDeclaration` to be defined in the exact same order, +// with the exception of `CSSWideKeyword`, `WithVariables` and `Custom`, +// which don't exist in `LonghandId`. + +<% + extra_variants = [ + { + "name": "CSSWideKeyword", + "type": "WideKeywordDeclaration", + "doc": "A CSS-wide keyword.", + "copy": False, + }, + { + "name": "WithVariables", + "type": "VariableDeclaration", + "doc": "An unparsed declaration.", + "copy": False, + }, + { + "name": "Custom", + "type": "CustomDeclaration", + "doc": "A custom property declaration.", + "copy": False, + }, + ] + for v in extra_variants: + variants.append(v) + groups[v["type"]] = [v] +%> + +/// Servo's representation for a property declaration. +#[derive(ToShmem)] +#[repr(u16)] +pub enum PropertyDeclaration { + % for variant in variants: + /// ${variant["doc"]} + ${variant["name"]}(${variant["type"]}), + % endfor +} + +// There's one of these for each parsed declaration so it better be small. +size_of_test!(PropertyDeclaration, 32); + +#[repr(C)] +struct PropertyDeclarationVariantRepr<T> { + tag: u16, + value: T +} + +impl Clone for PropertyDeclaration { + #[inline] + fn clone(&self) -> Self { + use self::PropertyDeclaration::*; + + <% + [copy, others] = [list(g) for _, g in groupby(variants, key=lambda x: not x["copy"])] + %> + + let self_tag = unsafe { + (*(self as *const _ as *const PropertyDeclarationVariantRepr<()>)).tag + }; + if self_tag <= LonghandId::${copy[-1]["name"]} as u16 { + #[derive(Clone, Copy)] + #[repr(u16)] + enum CopyVariants { + % for v in copy: + _${v["name"]}(${v["type"]}), + % endfor + } + + unsafe { + let mut out = mem::MaybeUninit::uninit(); + ptr::write( + out.as_mut_ptr() as *mut CopyVariants, + *(self as *const _ as *const CopyVariants), + ); + return out.assume_init(); + } + } + + // This function ensures that all properties not handled above + // do not have a specified value implements Copy. If you hit + // compile error here, you may want to add the type name into + // Longhand.specified_is_copy in data.py. + fn _static_assert_others_are_not_copy() { + struct Helper<T>(T); + trait AssertCopy { fn assert() {} } + trait AssertNotCopy { fn assert() {} } + impl<T: Copy> AssertCopy for Helper<T> {} + % for ty in sorted(set(x["type"] for x in others)): + impl AssertNotCopy for Helper<${ty}> {} + Helper::<${ty}>::assert(); + % endfor + } + + match *self { + ${" |\n".join("{}(..)".format(v["name"]) for v in copy)} => { + unsafe { debug_unreachable!() } + } + % for ty, vs in groupby(others, key=lambda x: x["type"]): + <% + vs = list(vs) + %> + % if len(vs) == 1: + ${vs[0]["name"]}(ref value) => { + ${vs[0]["name"]}(value.clone()) + } + % else: + ${" |\n".join("{}(ref value)".format(v["name"]) for v in vs)} => { + unsafe { + let mut out = mem::MaybeUninit::uninit(); + ptr::write( + out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${ty}>, + PropertyDeclarationVariantRepr { + tag: *(self as *const _ as *const u16), + value: value.clone(), + }, + ); + out.assume_init() + } + } + % endif + % endfor + } + } +} + +impl PartialEq for PropertyDeclaration { + #[inline] + fn eq(&self, other: &Self) -> bool { + use self::PropertyDeclaration::*; + + unsafe { + let this_repr = + &*(self as *const _ as *const PropertyDeclarationVariantRepr<()>); + let other_repr = + &*(other as *const _ as *const PropertyDeclarationVariantRepr<()>); + if this_repr.tag != other_repr.tag { + return false; + } + match *self { + % for ty, vs in groupby(variants, key=lambda x: x["type"]): + ${" |\n".join("{}(ref this)".format(v["name"]) for v in vs)} => { + let other_repr = + &*(other as *const _ as *const PropertyDeclarationVariantRepr<${ty}>); + *this == other_repr.value + } + % endfor + } + } + } +} + +impl MallocSizeOf for PropertyDeclaration { + #[inline] + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + use self::PropertyDeclaration::*; + + match *self { + % for ty, vs in groupby(variants, key=lambda x: x["type"]): + ${" | ".join("{}(ref value)".format(v["name"]) for v in vs)} => { + value.size_of(ops) + } + % endfor + } + } +} + + +impl PropertyDeclaration { + /// Returns whether this is a variant of the Longhand(Value) type, rather + /// than one of the special variants in extra_variants. + fn is_longhand_value(&self) -> bool { + match *self { + % for v in extra_variants: + PropertyDeclaration::${v["name"]}(..) => false, + % endfor + _ => true, + } + } + + /// Like the method on ToCss, but without the type parameter to avoid + /// accidentally monomorphizing this large function multiple times for + /// different writers. + pub fn to_css(&self, dest: &mut CssStringWriter) -> fmt::Result { + use self::PropertyDeclaration::*; + + let mut dest = CssWriter::new(dest); + match *self { + % for ty, vs in groupby(variants, key=lambda x: x["type"]): + ${" | ".join("{}(ref value)".format(v["name"]) for v in vs)} => { + value.to_css(&mut dest) + } + % endfor + } + } + + /// Returns the color value of a given property, for high-contrast-mode + /// tweaks. + pub(super) fn color_value(&self) -> Option<<&crate::values::specified::Color> { + ${static_longhand_id_set("COLOR_PROPERTIES", lambda p: p.predefined_type == "Color")} + <% + # sanity check + assert data.longhands_by_name["background-color"].predefined_type == "Color" + + color_specified_type = data.longhands_by_name["background-color"].specified_type() + %> + let id = self.id().as_longhand()?; + if !COLOR_PROPERTIES.contains(id) || !self.is_longhand_value() { + return None; + } + let repr = self as *const _ as *const PropertyDeclarationVariantRepr<${color_specified_type}>; + Some(unsafe { &(*repr).value }) + } +} + +/// A module with all the code related to animated properties. +/// +/// This needs to be "included" by mako at least after all longhand modules, +/// given they populate the global data. +pub mod animated_properties { + <%include file="/helpers/animated_properties.mako.rs" /> +} + +/// A longhand or shorthand property. +#[derive(Clone, Copy, Debug)] +pub struct NonCustomPropertyId(usize); + +/// The length of all the non-custom properties. +pub const NON_CUSTOM_PROPERTY_ID_COUNT: usize = + ${len(data.longhands) + len(data.shorthands) + len(data.all_aliases())}; + +/// The length of all counted unknown properties. +pub const COUNTED_UNKNOWN_PROPERTY_COUNT: usize = ${len(data.counted_unknown_properties)}; + +% if engine == "gecko": +#[allow(dead_code)] +unsafe fn static_assert_nscsspropertyid() { + % for i, property in enumerate(data.longhands + data.shorthands + data.all_aliases()): + std::mem::transmute::<[u8; ${i}], [u8; ${property.nscsspropertyid()} as usize]>([0; ${i}]); // ${property.name} + % endfor +} +% endif + +impl NonCustomPropertyId { + /// Returns the underlying index, used for use counter. + pub fn bit(self) -> usize { + self.0 + } + + /// Convert a `NonCustomPropertyId` into a `nsCSSPropertyID`. + #[cfg(feature = "gecko")] + #[inline] + pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { + // unsafe: guaranteed by static_assert_nscsspropertyid above. + unsafe { std::mem::transmute(self.0 as i32) } + } + + /// Convert an `nsCSSPropertyID` into a `NonCustomPropertyId`. + #[cfg(feature = "gecko")] + #[inline] + pub fn from_nscsspropertyid(prop: nsCSSPropertyID) -> Result<Self, ()> { + let prop = prop as i32; + if prop < 0 { + return Err(()); + } + if prop >= NON_CUSTOM_PROPERTY_ID_COUNT as i32 { + return Err(()); + } + // unsafe: guaranteed by static_assert_nscsspropertyid above. + Ok(unsafe { std::mem::transmute(prop as usize) }) + } + + /// Get the property name. + #[inline] + pub fn name(self) -> &'static str { + static MAP: [&'static str; NON_CUSTOM_PROPERTY_ID_COUNT] = [ + % for property in data.longhands + data.shorthands + data.all_aliases(): + "${property.name}", + % endfor + ]; + MAP[self.0] + } + + /// Returns whether this property is transitionable. + #[inline] + pub fn is_transitionable(self) -> bool { + ${static_non_custom_property_id_set("TRANSITIONABLE", lambda p: p.transitionable)} + TRANSITIONABLE.contains(self) + } + + /// Returns whether this property is animatable. + #[inline] + pub fn is_animatable(self) -> bool { + ${static_non_custom_property_id_set("ANIMATABLE", lambda p: p.animatable)} + ANIMATABLE.contains(self) + } + + #[inline] + fn enabled_for_all_content(self) -> bool { + ${static_non_custom_property_id_set( + "EXPERIMENTAL", + lambda p: p.experimental(engine) + )} + + ${static_non_custom_property_id_set( + "ALWAYS_ENABLED", + lambda p: (not p.experimental(engine)) and p.enabled_in_content() + )} + + let passes_pref_check = || { + % if engine == "gecko": + unsafe { structs::nsCSSProps_gPropertyEnabled[self.0] } + % else: + static PREF_NAME: [Option< &str>; ${ + len(data.longhands) + len(data.shorthands) + len(data.all_aliases()) + }] = [ + % for property in data.longhands + data.shorthands + data.all_aliases(): + <% + attrs = {"servo-2013": "servo_2013_pref", "servo-2020": "servo_2020_pref"} + pref = getattr(property, attrs[engine]) + %> + % if pref: + Some("${pref}"), + % else: + None, + % endif + % endfor + ]; + let pref = match PREF_NAME[self.0] { + None => return true, + Some(pref) => pref, + }; + + prefs::pref_map().get(pref).as_bool().unwrap_or(false) + % endif + }; + + if ALWAYS_ENABLED.contains(self) { + return true + } + + if EXPERIMENTAL.contains(self) && passes_pref_check() { + return true + } + + false + } + + /// Returns whether a given rule allows a given property. + #[inline] + pub fn allowed_in_rule(self, rule_types: CssRuleTypes) -> bool { + debug_assert!( + rule_types.contains(CssRuleType::Keyframe) || + rule_types.contains(CssRuleType::Page) || + rule_types.contains(CssRuleType::Style), + "Declarations are only expected inside a keyframe, page, or style rule." + ); + + static MAP: [u32; NON_CUSTOM_PROPERTY_ID_COUNT] = [ + % for property in data.longhands + data.shorthands + data.all_aliases(): + % for name in RULE_VALUES: + % if property.rule_types_allowed & RULE_VALUES[name] != 0: + CssRuleType::${name}.bit() | + % endif + % endfor + 0, + % endfor + ]; + MAP[self.0] & rule_types.bits() != 0 + } + + fn allowed_in(self, context: &ParserContext) -> bool { + if !self.allowed_in_rule(context.rule_types()) { + return false; + } + + self.allowed_in_ignoring_rule_type(context) + } + + + fn allowed_in_ignoring_rule_type(self, context: &ParserContext) -> bool { + // The semantics of these are kinda hard to reason about, what follows + // is a description of the different combinations that can happen with + // these three sets. + // + // Experimental properties are generally controlled by prefs, but an + // experimental property explicitly enabled in certain context (UA or + // chrome sheets) is always usable in the context regardless of the + // pref value. + // + // Non-experimental properties are either normal properties which are + // usable everywhere, or internal-only properties which are only usable + // in certain context they are explicitly enabled in. + if self.enabled_for_all_content() { + return true; + } + + ${static_non_custom_property_id_set( + "ENABLED_IN_UA_SHEETS", + lambda p: p.explicitly_enabled_in_ua_sheets() + )} + ${static_non_custom_property_id_set( + "ENABLED_IN_CHROME", + lambda p: p.explicitly_enabled_in_chrome() + )} + + if context.stylesheet_origin == Origin::UserAgent && + ENABLED_IN_UA_SHEETS.contains(self) + { + return true + } + + if context.chrome_rules_enabled() && ENABLED_IN_CHROME.contains(self) { + return true + } + + false + } + + /// The supported types of this property. The return value should be + /// style_traits::CssType when it can become a bitflags type. + fn supported_types(&self) -> u8 { + const SUPPORTED_TYPES: [u8; ${len(data.longhands) + len(data.shorthands)}] = [ + % for prop in data.longhands: + <${prop.specified_type()} as SpecifiedValueInfo>::SUPPORTED_TYPES, + % endfor + % for prop in data.shorthands: + % if prop.name == "all": + 0, // 'all' accepts no value other than CSS-wide keywords + % else: + <shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>::SUPPORTED_TYPES, + % endif + % endfor + ]; + SUPPORTED_TYPES[self.0] + } + + /// See PropertyId::collect_property_completion_keywords. + fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) { + fn do_nothing(_: KeywordsCollectFn) {} + const COLLECT_FUNCTIONS: [fn(KeywordsCollectFn); + ${len(data.longhands) + len(data.shorthands)}] = [ + % for prop in data.longhands: + <${prop.specified_type()} as SpecifiedValueInfo>::collect_completion_keywords, + % endfor + % for prop in data.shorthands: + % if prop.name == "all": + do_nothing, // 'all' accepts no value other than CSS-wide keywords + % else: + <shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>:: + collect_completion_keywords, + % endif + % endfor + ]; + COLLECT_FUNCTIONS[self.0](f); + } + + /// Turns this `NonCustomPropertyId` into a `PropertyId`. + #[inline] + pub fn to_property_id(self) -> PropertyId { + use std::mem::transmute; + if self.0 < ${len(data.longhands)} { + return unsafe { + PropertyId::Longhand(transmute(self.0 as u16)) + } + } + if self.0 < ${len(data.longhands) + len(data.shorthands)} { + return unsafe { + PropertyId::Shorthand(transmute((self.0 - ${len(data.longhands)}) as u16)) + } + } + assert!(self.0 < NON_CUSTOM_PROPERTY_ID_COUNT); + let alias_id: AliasId = unsafe { + transmute((self.0 - ${len(data.longhands) + len(data.shorthands)}) as u16) + }; + + match alias_id.aliased_property() { + AliasedPropertyId::Longhand(longhand) => PropertyId::LonghandAlias(longhand, alias_id), + AliasedPropertyId::Shorthand(shorthand) => PropertyId::ShorthandAlias(shorthand, alias_id), + } + } +} + +impl From<LonghandId> for NonCustomPropertyId { + #[inline] + fn from(id: LonghandId) -> Self { + NonCustomPropertyId(id as usize) + } +} + +impl From<ShorthandId> for NonCustomPropertyId { + #[inline] + fn from(id: ShorthandId) -> Self { + NonCustomPropertyId((id as usize) + ${len(data.longhands)}) + } +} + +impl From<AliasId> for NonCustomPropertyId { + #[inline] + fn from(id: AliasId) -> Self { + NonCustomPropertyId(id as usize + ${len(data.longhands) + len(data.shorthands)}) + } +} + +/// A set of all properties +#[derive(Clone, PartialEq, Default)] +pub struct NonCustomPropertyIdSet { + storage: [u32; (NON_CUSTOM_PROPERTY_ID_COUNT - 1 + 32) / 32] +} + +impl NonCustomPropertyIdSet { + /// Creates an empty `NonCustomPropertyIdSet`. + pub fn new() -> Self { + Self { + storage: Default::default(), + } + } + + /// Insert a non-custom-property in the set. + #[inline] + pub fn insert(&mut self, id: NonCustomPropertyId) { + let bit = id.0; + self.storage[bit / 32] |= 1 << (bit % 32); + } + + /// Return whether the given property is in the set + #[inline] + pub fn contains(&self, id: NonCustomPropertyId) -> bool { + let bit = id.0; + (self.storage[bit / 32] & (1 << (bit % 32))) != 0 + } +} + +<%def name="static_non_custom_property_id_set(name, is_member)"> +static ${name}: NonCustomPropertyIdSet = NonCustomPropertyIdSet { + <% + storage = [0] * int((len(data.longhands) + len(data.shorthands) + len(data.all_aliases()) - 1 + 32) / 32) + for i, property in enumerate(data.longhands + data.shorthands + data.all_aliases()): + if is_member(property): + storage[int(i / 32)] |= 1 << (i % 32) + %> + storage: [${", ".join("0x%x" % word for word in storage)}] +}; +</%def> + +<%def name="static_longhand_id_set(name, is_member)"> +static ${name}: LonghandIdSet = LonghandIdSet { + <% + storage = [0] * int((len(data.longhands) - 1 + 32) / 32) + for i, property in enumerate(data.longhands): + if is_member(property): + storage[int(i / 32)] |= 1 << (i % 32) + %> + storage: [${", ".join("0x%x" % word for word in storage)}] +}; +</%def> + +<% + logical_groups = defaultdict(list) + for prop in data.longhands: + if prop.logical_group: + logical_groups[prop.logical_group].append(prop) + + for group, props in logical_groups.items(): + logical_count = sum(1 for p in props if p.logical) + if logical_count * 2 != len(props): + raise RuntimeError("Logical group {} has ".format(group) + + "unbalanced logical / physical properties") + + FIRST_LINE_RESTRICTIONS = PropertyRestrictions.first_line(data) + FIRST_LETTER_RESTRICTIONS = PropertyRestrictions.first_letter(data) + MARKER_RESTRICTIONS = PropertyRestrictions.marker(data) + PLACEHOLDER_RESTRICTIONS = PropertyRestrictions.placeholder(data) + CUE_RESTRICTIONS = PropertyRestrictions.cue(data) + + def restriction_flags(property): + name = property.name + flags = [] + if name in FIRST_LINE_RESTRICTIONS: + flags.append("APPLIES_TO_FIRST_LINE") + if name in FIRST_LETTER_RESTRICTIONS: + flags.append("APPLIES_TO_FIRST_LETTER") + if name in PLACEHOLDER_RESTRICTIONS: + flags.append("APPLIES_TO_PLACEHOLDER") + if name in MARKER_RESTRICTIONS: + flags.append("APPLIES_TO_MARKER") + if name in CUE_RESTRICTIONS: + flags.append("APPLIES_TO_CUE") + return flags + +%> + +/// A group for properties which may override each other +/// via logical resolution. +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +#[repr(u8)] +pub enum LogicalGroup { + % for i, group in enumerate(logical_groups.keys()): + /// ${group} + ${to_camel_case(group)} = ${i}, + % endfor +} + + +/// A set of logical groups. +#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)] +pub struct LogicalGroupSet { + storage: [u32; (${len(logical_groups)} - 1 + 32) / 32] +} + +impl LogicalGroupSet { + /// Creates an empty `NonCustomPropertyIdSet`. + pub fn new() -> Self { + Self { + storage: Default::default(), + } + } + + /// Return whether the given group is in the set + #[inline] + pub fn contains(&self, g: LogicalGroup) -> bool { + let bit = g as usize; + (self.storage[bit / 32] & (1 << (bit % 32))) != 0 + } + + /// Insert a group the set. + #[inline] + pub fn insert(&mut self, g: LogicalGroup) { + let bit = g as usize; + self.storage[bit / 32] |= 1 << (bit % 32); + } +} + +/// A set of longhand properties +#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)] +pub struct LonghandIdSet { + storage: [u32; (${len(data.longhands)} - 1 + 32) / 32] +} + +impl_trivial_to_shmem!(LonghandIdSet); + +/// An iterator over a set of longhand ids. +pub struct LonghandIdSetIterator<'a> { + longhands: &'a LonghandIdSet, + cur: usize, +} + +impl<'a> Iterator for LonghandIdSetIterator<'a> { + type Item = LonghandId; + + fn next(&mut self) -> Option<Self::Item> { + use std::mem; + + loop { + if self.cur >= ${len(data.longhands)} { + return None; + } + + let id: LonghandId = unsafe { mem::transmute(self.cur as u16) }; + self.cur += 1; + + if self.longhands.contains(id) { + return Some(id); + } + } + } +} + +<% + +CASCADE_GROUPS = { + # The writing-mode group has the most priority of all property groups, as + # sizes like font-size can depend on it. + "writing_mode": [ + "writing-mode", + "direction", + "text-orientation", + ], + # The fonts and colors group has the second priority, as all other lengths + # and colors depend on them. + # + # There are some interdependencies between these, but we fix them up in + # Cascade::fixup_font_stuff. + "fonts_and_color": [ + # Needed to properly compute the zoomed font-size. + "-x-text-scale", + # Needed to do font-size computation in a language-dependent way. + "-x-lang", + # Needed for ruby to respect language-dependent min-font-size + # preferences properly, see bug 1165538. + "-moz-min-font-size-ratio", + # font-size depends on math-depth's computed value. + "math-depth", + # Needed to compute the first available font and its used size, + # in order to compute font-relative units correctly. + "font-size", + "font-size-adjust", + "font-weight", + "font-stretch", + "font-style", + "font-family", + # color-scheme affects how system colors resolve. + "color-scheme", + "forced-color-adjust", + ], +} +def in_late_group(p): + return p.name not in CASCADE_GROUPS["writing_mode"] and p.name not in CASCADE_GROUPS["fonts_and_color"] + +def is_visited_dependent(p): + return p.name in [ + "column-rule-color", + "text-emphasis-color", + "-webkit-text-fill-color", + "-webkit-text-stroke-color", + "text-decoration-color", + "fill", + "stroke", + "caret-color", + "background-color", + "border-top-color", + "border-right-color", + "border-bottom-color", + "border-left-color", + "border-block-start-color", + "border-inline-end-color", + "border-block-end-color", + "border-inline-start-color", + "outline-color", + "color", + ] + +%> + +impl LonghandIdSet { + #[inline] + fn reset() -> &'static Self { + ${static_longhand_id_set("RESET", lambda p: not p.style_struct.inherited)} + &RESET + } + + #[inline] + fn animatable() -> &'static Self { + ${static_longhand_id_set("ANIMATABLE", lambda p: p.animatable)} + &ANIMATABLE + } + + #[inline] + fn discrete_animatable() -> &'static Self { + ${static_longhand_id_set("DISCRETE_ANIMATABLE", lambda p: p.animation_value_type == "discrete")} + &DISCRETE_ANIMATABLE + } + + #[inline] + fn logical() -> &'static Self { + ${static_longhand_id_set("LOGICAL", lambda p: p.logical)} + &LOGICAL + } + + /// Returns the set of longhands that are ignored when document colors are + /// disabled. + #[inline] + fn ignored_when_colors_disabled() -> &'static Self { + ${static_longhand_id_set( + "IGNORED_WHEN_COLORS_DISABLED", + lambda p: p.ignored_when_colors_disabled + )} + &IGNORED_WHEN_COLORS_DISABLED + } + + /// Only a few properties are allowed to depend on the visited state of + /// links. When cascading visited styles, we can save time by only + /// processing these properties. + pub(super) fn visited_dependent() -> &'static Self { + ${static_longhand_id_set( + "VISITED_DEPENDENT", + lambda p: is_visited_dependent(p) + )} + debug_assert!(Self::late_group().contains_all(&VISITED_DEPENDENT)); + &VISITED_DEPENDENT + } + + #[inline] + pub(super) fn writing_mode_group() -> &'static Self { + ${static_longhand_id_set( + "WRITING_MODE_GROUP", + lambda p: p.name in CASCADE_GROUPS["writing_mode"] + )} + &WRITING_MODE_GROUP + } + + #[inline] + pub(super) fn fonts_and_color_group() -> &'static Self { + ${static_longhand_id_set( + "FONTS_AND_COLOR_GROUP", + lambda p: p.name in CASCADE_GROUPS["fonts_and_color"] + )} + &FONTS_AND_COLOR_GROUP + } + + #[inline] + pub(super) fn late_group_only_inherited() -> &'static Self { + ${static_longhand_id_set("LATE_GROUP_ONLY_INHERITED", lambda p: p.style_struct.inherited and in_late_group(p))} + &LATE_GROUP_ONLY_INHERITED + } + + #[inline] + pub(super) fn late_group() -> &'static Self { + ${static_longhand_id_set("LATE_GROUP", lambda p: in_late_group(p))} + &LATE_GROUP + } + + /// Returns the set of properties that are declared as having no effect on + /// Gecko <scrollbar> elements or their descendant scrollbar parts. + #[cfg(debug_assertions)] + #[cfg(feature = "gecko")] + #[inline] + pub fn has_no_effect_on_gecko_scrollbars() -> &'static Self { + // data.py asserts that has_no_effect_on_gecko_scrollbars is True or + // False for properties that are inherited and Gecko pref controlled, + // and is None for all other properties. + ${static_longhand_id_set( + "HAS_NO_EFFECT_ON_SCROLLBARS", + lambda p: p.has_effect_on_gecko_scrollbars is False + )} + &HAS_NO_EFFECT_ON_SCROLLBARS + } + + /// Returns the set of border properties for the purpose of disabling native + /// appearance. + #[inline] + pub fn border_background_properties() -> &'static Self { + ${static_longhand_id_set( + "BORDER_BACKGROUND_PROPERTIES", + lambda p: (p.logical_group and p.logical_group.startswith("border")) or \ + p.name in ["background-color", "background-image"] + )} + &BORDER_BACKGROUND_PROPERTIES + } + + /// Iterate over the current longhand id set. + pub fn iter(&self) -> LonghandIdSetIterator { + LonghandIdSetIterator { longhands: self, cur: 0, } + } + + /// Returns whether this set contains at least every longhand that `other` + /// also contains. + pub fn contains_all(&self, other: &Self) -> bool { + for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) { + if (*self_cell & *other_cell) != *other_cell { + return false; + } + } + true + } + + /// Returns whether this set contains any longhand that `other` also contains. + pub fn contains_any(&self, other: &Self) -> bool { + for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) { + if (*self_cell & *other_cell) != 0 { + return true; + } + } + false + } + + /// Remove all the given properties from the set. + #[inline] + pub fn remove_all(&mut self, other: &Self) { + for (self_cell, other_cell) in self.storage.iter_mut().zip(other.storage.iter()) { + *self_cell &= !*other_cell; + } + } + + /// Create an empty set + #[inline] + pub fn new() -> LonghandIdSet { + LonghandIdSet { storage: [0; (${len(data.longhands)} - 1 + 32) / 32] } + } + + /// Return whether the given property is in the set + #[inline] + pub fn contains(&self, id: LonghandId) -> bool { + let bit = id as usize; + (self.storage[bit / 32] & (1 << (bit % 32))) != 0 + } + + /// Return whether this set contains any reset longhand. + #[inline] + pub fn contains_any_reset(&self) -> bool { + self.contains_any(Self::reset()) + } + + /// Add the given property to the set + #[inline] + pub fn insert(&mut self, id: LonghandId) { + let bit = id as usize; + self.storage[bit / 32] |= 1 << (bit % 32); + } + + /// Remove the given property from the set + #[inline] + pub fn remove(&mut self, id: LonghandId) { + let bit = id as usize; + self.storage[bit / 32] &= !(1 << (bit % 32)); + } + + /// Clear all bits + #[inline] + pub fn clear(&mut self) { + for cell in &mut self.storage { + *cell = 0 + } + } + + /// Returns whether the set is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.storage.iter().all(|c| *c == 0) + } +} + +/// An enum to represent a CSS Wide keyword. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, + ToCss, ToShmem)] +pub enum CSSWideKeyword { + /// The `initial` keyword. + Initial, + /// The `inherit` keyword. + Inherit, + /// The `unset` keyword. + Unset, + /// The `revert` keyword. + Revert, + /// The `revert-layer` keyword. + RevertLayer, +} + +impl CSSWideKeyword { + fn to_str(&self) -> &'static str { + match *self { + CSSWideKeyword::Initial => "initial", + CSSWideKeyword::Inherit => "inherit", + CSSWideKeyword::Unset => "unset", + CSSWideKeyword::Revert => "revert", + CSSWideKeyword::RevertLayer => "revert-layer", + } + } +} + +impl CSSWideKeyword { + /// Parses a CSS wide keyword from a CSS identifier. + pub fn from_ident(ident: &str) -> Result<Self, ()> { + Ok(match_ignore_ascii_case! { ident, + "initial" => CSSWideKeyword::Initial, + "inherit" => CSSWideKeyword::Inherit, + "unset" => CSSWideKeyword::Unset, + "revert" => CSSWideKeyword::Revert, + "revert-layer" => CSSWideKeyword::RevertLayer, + _ => return Err(()), + }) + } + + fn parse(input: &mut Parser) -> Result<Self, ()> { + let keyword = { + let ident = input.expect_ident().map_err(|_| ())?; + Self::from_ident(ident)? + }; + input.expect_exhausted().map_err(|_| ())?; + Ok(keyword) + } +} + +bitflags! { + /// A set of flags for properties. + #[derive(Clone, Copy)] + pub struct PropertyFlags: u16 { + /// This longhand property applies to ::first-letter. + const APPLIES_TO_FIRST_LETTER = 1 << 1; + /// This longhand property applies to ::first-line. + const APPLIES_TO_FIRST_LINE = 1 << 2; + /// This longhand property applies to ::placeholder. + const APPLIES_TO_PLACEHOLDER = 1 << 3; + /// This longhand property applies to ::cue. + const APPLIES_TO_CUE = 1 << 4; + /// This longhand property applies to ::marker. + const APPLIES_TO_MARKER = 1 << 5; + /// This property is a legacy shorthand. + /// + /// https://drafts.csswg.org/css-cascade/#legacy-shorthand + const IS_LEGACY_SHORTHAND = 1 << 6; + + /* The following flags are currently not used in Rust code, they + * only need to be listed in corresponding properties so that + * they can be checked in the C++ side via ServoCSSPropList.h. */ + /// This property can be animated on the compositor. + const CAN_ANIMATE_ON_COMPOSITOR = 0; + /// This shorthand property is accessible from getComputedStyle. + const SHORTHAND_IN_GETCS = 0; + } +} + +/// An identifier for a given longhand property. +#[derive(Clone, Copy, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] +#[repr(u16)] +pub enum LonghandId { + % for i, property in enumerate(data.longhands): + /// ${property.name} + ${property.camel_case} = ${i}, + % endfor +} + +impl ToCss for LonghandId { + #[inline] + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + dest.write_str(self.name()) + } +} + +impl fmt::Debug for LonghandId { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(self.name()) + } +} + +impl LonghandId { + /// Get the name of this longhand property. + #[inline] + pub fn name(&self) -> &'static str { + NonCustomPropertyId::from(*self).name() + } + + /// Returns whether the longhand property is inherited by default. + #[inline] + pub fn inherited(self) -> bool { + !LonghandIdSet::reset().contains(self) + } + + /// Returns an iterator over all the shorthands that include this longhand. + pub fn shorthands(&self) -> NonCustomPropertyIterator<ShorthandId> { + // first generate longhand to shorthands lookup map + // + // NOTE(emilio): This currently doesn't exclude the "all" shorthand. It + // could potentially do so, which would speed up serialization + // algorithms and what not, I guess. + <% + from functools import cmp_to_key + longhand_to_shorthand_map = {} + num_sub_properties = {} + for shorthand in data.shorthands: + num_sub_properties[shorthand.camel_case] = len(shorthand.sub_properties) + for sub_property in shorthand.sub_properties: + if sub_property.ident not in longhand_to_shorthand_map: + longhand_to_shorthand_map[sub_property.ident] = [] + + longhand_to_shorthand_map[sub_property.ident].append(shorthand.camel_case) + + def cmp(a, b): + return (a > b) - (a < b) + + def preferred_order(x, y): + # Since we want properties in order from most subproperties to least, + # reverse the arguments to cmp from the expected order. + result = cmp(num_sub_properties.get(y, 0), num_sub_properties.get(x, 0)) + if result: + return result + # Fall back to lexicographic comparison. + return cmp(x, y) + + # Sort the lists of shorthand properties according to preferred order: + # https://drafts.csswg.org/cssom/#concept-shorthands-preferred-order + for shorthand_list in longhand_to_shorthand_map.values(): + shorthand_list.sort(key=cmp_to_key(preferred_order)) + %> + + // based on lookup results for each longhand, create result arrays + % for property in data.longhands: + static ${property.ident.upper()}: &'static [ShorthandId] = &[ + % for shorthand in longhand_to_shorthand_map.get(property.ident, []): + ShorthandId::${shorthand}, + % endfor + ]; + % endfor + + NonCustomPropertyIterator { + filter: NonCustomPropertyId::from(*self).enabled_for_all_content(), + iter: match *self { + % for property in data.longhands: + LonghandId::${property.camel_case} => ${property.ident.upper()}, + % endfor + }.iter(), + } + } + + fn parse_value<'i, 't>( + &self, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<PropertyDeclaration, ParseError<'i>> { + type ParsePropertyFn = for<'i, 't> fn( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<PropertyDeclaration, ParseError<'i>>; + static PARSE_PROPERTY: [ParsePropertyFn; ${len(data.longhands)}] = [ + % for property in data.longhands: + longhands::${property.ident}::parse_declared, + % endfor + ]; + (PARSE_PROPERTY[*self as usize])(context, input) + } + + /// Returns whether this property is animatable. + #[inline] + pub fn is_animatable(self) -> bool { + LonghandIdSet::animatable().contains(self) + } + + /// Returns whether this property is animatable in a discrete way. + #[inline] + pub fn is_discrete_animatable(self) -> bool { + LonghandIdSet::discrete_animatable().contains(self) + } + + /// Converts from a LonghandId to an adequate nsCSSPropertyID. + #[cfg(feature = "gecko")] + #[inline] + pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { + NonCustomPropertyId::from(self).to_nscsspropertyid() + } + + #[cfg(feature = "gecko")] + #[allow(non_upper_case_globals)] + /// Returns a longhand id from Gecko's nsCSSPropertyID. + pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Result<Self, ()> { + match PropertyId::from_nscsspropertyid(id) { + Ok(PropertyId::Longhand(id)) | + Ok(PropertyId::LonghandAlias(id, _)) => Ok(id), + _ => Err(()), + } + } + + /// Return whether this property is logical. + #[inline] + pub fn is_logical(self) -> bool { + LonghandIdSet::logical().contains(self) + } + + /// If this is a logical property, return the corresponding physical one in + /// the given writing mode. + /// + /// Otherwise, return unchanged. + #[inline] + pub fn to_physical(&self, wm: WritingMode) -> Self { + match *self { + % for property in data.longhands: + % if property.logical: + <% logical_group = property.logical_group %> + LonghandId::${property.camel_case} => { + <%helpers:logical_setter_helper name="${property.name}"> + <%def name="inner(physical_ident)"> + <% + physical_name = physical_ident.replace("_", "-") + physical_property = data.longhands_by_name[physical_name] + assert logical_group == physical_property.logical_group + %> + LonghandId::${to_camel_case(physical_ident)} + </%def> + </%helpers:logical_setter_helper> + } + % endif + % endfor + _ => *self + } + } + + /// Return the logical group of this longhand property. + pub fn logical_group(&self) -> Option<LogicalGroup> { + const LOGICAL_GROUPS: [Option<LogicalGroup>; ${len(data.longhands)}] = [ + % for prop in data.longhands: + % if prop.logical_group: + Some(LogicalGroup::${to_camel_case(prop.logical_group)}), + % else: + None, + % endif + % endfor + ]; + LOGICAL_GROUPS[*self as usize] + } + + /// Returns PropertyFlags for given longhand property. + #[inline(always)] + pub fn flags(self) -> PropertyFlags { + // TODO(emilio): This can be simplified further as Rust gains more + // constant expression support. + const FLAGS: [u16; ${len(data.longhands)}] = [ + % for property in data.longhands: + % for flag in property.flags + restriction_flags(property): + PropertyFlags::${flag}.bits | + % endfor + 0, + % endfor + ]; + PropertyFlags::from_bits_retain(FLAGS[self as usize]) + } + + /// Returns true if the property is one that is ignored when document + /// colors are disabled. + #[inline] + pub fn ignored_when_document_colors_disabled(self) -> bool { + LonghandIdSet::ignored_when_colors_disabled().contains(self) + } +} + +/// An iterator over all the property ids that are enabled for a given +/// shorthand, if that shorthand is enabled for all content too. +pub struct NonCustomPropertyIterator<Item: 'static> { + filter: bool, + iter: std::slice::Iter<'static, Item>, +} + +impl<Item> Iterator for NonCustomPropertyIterator<Item> +where + Item: 'static + Copy + Into<NonCustomPropertyId>, +{ + type Item = Item; + + fn next(&mut self) -> Option<Self::Item> { + loop { + let id = *self.iter.next()?; + if !self.filter || id.into().enabled_for_all_content() { + return Some(id) + } + } + } +} + +/// An identifier for a given shorthand property. +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] +#[repr(u16)] +pub enum ShorthandId { + % for i, property in enumerate(data.shorthands): + /// ${property.name} + ${property.camel_case} = ${i}, + % endfor +} + +impl ToCss for ShorthandId { + #[inline] + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + dest.write_str(self.name()) + } +} + +impl ShorthandId { + /// Get the name for this shorthand property. + #[inline] + pub fn name(&self) -> &'static str { + NonCustomPropertyId::from(*self).name() + } + + /// Converts from a ShorthandId to an adequate nsCSSPropertyID. + #[cfg(feature = "gecko")] + #[inline] + pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { + NonCustomPropertyId::from(self).to_nscsspropertyid() + } + + /// Converts from a nsCSSPropertyID to a ShorthandId. + #[cfg(feature = "gecko")] + #[inline] + pub fn from_nscsspropertyid(prop: nsCSSPropertyID) -> Result<Self, ()> { + PropertyId::from_nscsspropertyid(prop)?.as_shorthand().map_err(|_| ()) + } + + /// Get the longhand ids that form this shorthand. + pub fn longhands(&self) -> NonCustomPropertyIterator<LonghandId> { + % for property in data.shorthands: + static ${property.ident.upper()}: &'static [LonghandId] = &[ + % for sub in property.sub_properties: + LonghandId::${sub.camel_case}, + % endfor + ]; + % endfor + NonCustomPropertyIterator { + filter: NonCustomPropertyId::from(*self).enabled_for_all_content(), + iter: match *self { + % for property in data.shorthands: + ShorthandId::${property.camel_case} => ${property.ident.upper()}, + % endfor + }.iter() + } + } + + /// Try to serialize the given declarations as this shorthand. + /// + /// Returns an error if writing to the stream fails, or if the declarations + /// do not map to a shorthand. + pub fn longhands_to_css( + &self, + declarations: &[&PropertyDeclaration], + dest: &mut CssStringWriter, + ) -> fmt::Result { + type LonghandsToCssFn = for<'a, 'b> fn(&'a [&'b PropertyDeclaration], &mut CssStringWriter) -> fmt::Result; + fn all_to_css(_: &[&PropertyDeclaration], _: &mut CssStringWriter) -> fmt::Result { + // No need to try to serialize the declarations as the 'all' + // shorthand, since it only accepts CSS-wide keywords (and variable + // references), which will be handled in + // get_shorthand_appendable_value. + Ok(()) + } + + static LONGHANDS_TO_CSS: [LonghandsToCssFn; ${len(data.shorthands)}] = [ + % for shorthand in data.shorthands: + % if shorthand.ident == "all": + all_to_css, + % else: + shorthands::${shorthand.ident}::to_css, + % endif + % endfor + ]; + + LONGHANDS_TO_CSS[*self as usize](declarations, dest) + } + + /// Finds and returns an appendable value for the given declarations. + /// + /// Returns the optional appendable value. + pub fn get_shorthand_appendable_value<'a, 'b: 'a>( + self, + declarations: &'a [&'b PropertyDeclaration], + ) -> Option<AppendableValue<'a, 'b>> { + let first_declaration = declarations.get(0)?; + let rest = || declarations.iter().skip(1); + + // https://drafts.csswg.org/css-variables/#variables-in-shorthands + if let Some(css) = first_declaration.with_variables_from_shorthand(self) { + if rest().all(|d| d.with_variables_from_shorthand(self) == Some(css)) { + return Some(AppendableValue::Css(css)); + } + return None; + } + + // Check whether they are all the same CSS-wide keyword. + if let Some(keyword) = first_declaration.get_css_wide_keyword() { + if rest().all(|d| d.get_css_wide_keyword() == Some(keyword)) { + return Some(AppendableValue::Css(keyword.to_str())) + } + return None; + } + + if self == ShorthandId::All { + // 'all' only supports variables and CSS wide keywords. + return None; + } + + // Check whether all declarations can be serialized as part of shorthand. + if declarations.iter().all(|d| d.may_serialize_as_part_of_shorthand()) { + return Some(AppendableValue::DeclarationsForShorthand(self, declarations)); + } + + None + } + + /// Returns PropertyFlags for the given shorthand property. + #[inline] + pub fn flags(self) -> PropertyFlags { + const FLAGS: [u16; ${len(data.shorthands)}] = [ + % for property in data.shorthands: + % for flag in property.flags: + PropertyFlags::${flag}.bits | + % endfor + 0, + % endfor + ]; + PropertyFlags::from_bits_retain(FLAGS[self as usize]) + } + + /// Returns whether this property is a legacy shorthand. + #[inline] + pub fn is_legacy_shorthand(self) -> bool { + self.flags().contains(PropertyFlags::IS_LEGACY_SHORTHAND) + } + + /// Returns the order in which this property appears relative to other + /// shorthands in idl-name-sorting order. + #[inline] + pub fn idl_name_sort_order(self) -> u32 { + <% + from data import to_idl_name + ordered = {} + sorted_shorthands = sorted(data.shorthands, key=lambda p: to_idl_name(p.ident)) + for order, shorthand in enumerate(sorted_shorthands): + ordered[shorthand.ident] = order + %> + static IDL_NAME_SORT_ORDER: [u32; ${len(data.shorthands)}] = [ + % for property in data.shorthands: + ${ordered[property.ident]}, + % endfor + ]; + IDL_NAME_SORT_ORDER[self as usize] + } + + fn parse_into<'i, 't>( + &self, + declarations: &mut SourcePropertyDeclaration, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>> { + type ParseIntoFn = for<'i, 't> fn( + declarations: &mut SourcePropertyDeclaration, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>>; + + fn parse_all<'i, 't>( + _: &mut SourcePropertyDeclaration, + _: &ParserContext, + input: &mut Parser<'i, 't> + ) -> Result<(), ParseError<'i>> { + // 'all' accepts no value other than CSS-wide keywords + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + + + static PARSE_INTO: [ParseIntoFn; ${len(data.shorthands)}] = [ + % for shorthand in data.shorthands: + % if shorthand.ident == "all": + parse_all, + % else: + shorthands::${shorthand.ident}::parse_into, + % endif + % endfor + ]; + + (PARSE_INTO[*self as usize])(declarations, context, input) + } +} + +/// An unparsed property value that contains `var()` functions. +#[derive(Debug, Eq, PartialEq, ToShmem)] +pub struct UnparsedValue { + /// The css serialization for this value. + css: String, + /// The first token type for this serialization. + first_token_type: TokenSerializationType, + /// The url data for resolving url values. + url_data: UrlExtraData, + /// The shorthand this came from. + from_shorthand: Option<ShorthandId>, +} + +impl ToCss for UnparsedValue { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + // https://drafts.csswg.org/css-variables/#variables-in-shorthands + if self.from_shorthand.is_none() { + dest.write_str(&*self.css)?; + } + Ok(()) + } +} + +/// A simple cache for properties that come from a shorthand and have variable +/// references. +/// +/// This cache works because of the fact that you can't have competing values +/// for a given longhand coming from the same shorthand (but note that this is +/// why the shorthand needs to be part of the cache key). +pub type ShorthandsWithPropertyReferencesCache = + FxHashMap<(ShorthandId, LonghandId), PropertyDeclaration>; + +impl UnparsedValue { + pub(super) fn substitute_variables<'cache>( + &self, + longhand_id: LonghandId, + writing_mode: WritingMode, + custom_properties: Option<<&Arc<crate::custom_properties::CustomPropertiesMap>>, + quirks_mode: QuirksMode, + device: &Device, + shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache, + ) -> Cow<'cache, PropertyDeclaration> { + let invalid_at_computed_value_time = || { + let keyword = if longhand_id.inherited() { + CSSWideKeyword::Inherit + } else { + CSSWideKeyword::Initial + }; + Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword)) + }; + + if let Some(shorthand_id) = self.from_shorthand { + let key = (shorthand_id, longhand_id); + if shorthand_cache.contains_key(&key) { + // FIXME: This double lookup should be avoidable, but rustc + // doesn't like that, see: + // + // https://github.com/rust-lang/rust/issues/82146 + return Cow::Borrowed(&shorthand_cache[&key]); + } + } + + let css = match crate::custom_properties::substitute( + &self.css, + self.first_token_type, + custom_properties, + device, + ) { + Ok(css) => css, + Err(..) => return invalid_at_computed_value_time(), + }; + + // As of this writing, only the base URL is used for property + // values. + // + // NOTE(emilio): we intentionally pase `None` as the rule type here. + // If something starts depending on it, it's probably a bug, since + // it'd change how values are parsed depending on whether we're in a + // @keyframes rule or not, for example... So think twice about + // whether you want to do this! + // + // FIXME(emilio): ParsingMode is slightly fishy... + let context = ParserContext::new( + Origin::Author, + &self.url_data, + None, + ParsingMode::DEFAULT, + quirks_mode, + /* namespaces = */ Default::default(), + None, + None, + ); + + let mut input = ParserInput::new(&css); + let mut input = Parser::new(&mut input); + input.skip_whitespace(); + + if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) { + return Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword)); + } + + let shorthand = match self.from_shorthand { + None => { + return match input.parse_entirely(|input| longhand_id.parse_value(&context, input)) { + Ok(decl) => Cow::Owned(decl), + Err(..) => invalid_at_computed_value_time(), + } + }, + Some(shorthand) => shorthand, + }; + + let mut decls = SourcePropertyDeclaration::default(); + // parse_into takes care of doing `parse_entirely` for us. + if shorthand.parse_into(&mut decls, &context, &mut input).is_err() { + return invalid_at_computed_value_time(); + } + + for declaration in decls.declarations.drain(..) { + let longhand = declaration.id().as_longhand().unwrap(); + if longhand.is_logical() { + shorthand_cache.insert((shorthand, longhand.to_physical(writing_mode)), declaration.clone()); + } + shorthand_cache.insert((shorthand, longhand), declaration); + } + + let key = (shorthand, longhand_id); + match shorthand_cache.get(&key) { + Some(decl) => Cow::Borrowed(decl), + None => { + // FIXME: We should always have the key here but it seems + // sometimes we don't, see bug 1696409. + #[cfg(feature = "gecko")] + { + if structs::GECKO_IS_NIGHTLY { + panic!("Expected {:?} to be in the cache but it was not!", key); + } + } + invalid_at_computed_value_time() + } + } + } +} + +/// An identifier for a given property declaration, which can be either a +/// longhand or a custom property. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +pub enum PropertyDeclarationId<'a> { + /// A longhand. + Longhand(LonghandId), + /// A custom property declaration. + Custom(&'a crate::custom_properties::Name), +} + +impl<'a> ToCss for PropertyDeclarationId<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + match *self { + PropertyDeclarationId::Longhand(id) => dest.write_str(id.name()), + PropertyDeclarationId::Custom(ref name) => { + dest.write_str("--")?; + serialize_atom_name(name, dest) + } + } + } +} + +impl<'a> PropertyDeclarationId<'a> { + /// Whether a given declaration id is either the same as `other`, or a + /// longhand of it. + pub fn is_or_is_longhand_of(&self, other: &PropertyId) -> bool { + match *self { + PropertyDeclarationId::Longhand(id) => { + match *other { + PropertyId::Longhand(other_id) | + PropertyId::LonghandAlias(other_id, _) => id == other_id, + PropertyId::Shorthand(shorthand) | + PropertyId::ShorthandAlias(shorthand, _) => self.is_longhand_of(shorthand), + PropertyId::Custom(_) => false, + } + } + PropertyDeclarationId::Custom(name) => { + matches!(*other, PropertyId::Custom(ref other_name) if name == other_name) + } + } + } + + /// Whether a given declaration id is a longhand belonging to this + /// shorthand. + pub fn is_longhand_of(&self, shorthand: ShorthandId) -> bool { + match *self { + PropertyDeclarationId::Longhand(ref id) => id.shorthands().any(|s| s == shorthand), + _ => false, + } + } + + /// Returns the name of the property without CSS escaping. + pub fn name(&self) -> Cow<'static, str> { + match *self { + PropertyDeclarationId::Longhand(id) => id.name().into(), + PropertyDeclarationId::Custom(name) => { + let mut s = String::new(); + write!(&mut s, "--{}", name).unwrap(); + s.into() + } + } + } + + /// Returns longhand id if it is, None otherwise. + #[inline] + pub fn as_longhand(&self) -> Option<LonghandId> { + match *self { + PropertyDeclarationId::Longhand(id) => Some(id), + _ => None, + } + } +} + +/// Servo's representation of a CSS property, that is, either a longhand, a +/// shorthand, or a custom property. +#[derive(Clone, Eq, PartialEq)] +pub enum PropertyId { + /// A longhand property. + Longhand(LonghandId), + /// A shorthand property. + Shorthand(ShorthandId), + /// An alias for a longhand property. + LonghandAlias(LonghandId, AliasId), + /// An alias for a shorthand property. + ShorthandAlias(ShorthandId, AliasId), + /// A custom property. + Custom(crate::custom_properties::Name), +} + +impl fmt::Debug for PropertyId { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + self.to_css(&mut CssWriter::new(formatter)) + } +} + +impl ToCss for PropertyId { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + match *self { + PropertyId::Longhand(id) => dest.write_str(id.name()), + PropertyId::Shorthand(id) => dest.write_str(id.name()), + PropertyId::LonghandAlias(id, _) => dest.write_str(id.name()), + PropertyId::ShorthandAlias(id, _) => dest.write_str(id.name()), + PropertyId::Custom(ref name) => { + dest.write_str("--")?; + serialize_atom_name(name, dest) + } + } + } +} + +/// The counted unknown property list which is used for css use counters. +/// +/// FIXME: This should be just #[repr(u8)], but can't be because of ABI issues, +/// see https://bugs.llvm.org/show_bug.cgi?id=44228. +#[derive(Clone, Copy, Debug, Eq, FromPrimitive, Hash, PartialEq)] +#[repr(u32)] +pub enum CountedUnknownProperty { + % for prop in data.counted_unknown_properties: + /// ${prop.name} + ${prop.camel_case}, + % endfor +} + +impl CountedUnknownProperty { + /// Parse the counted unknown property, for testing purposes only. + pub fn parse_for_testing(property_name: &str) -> Option<Self> { + ascii_case_insensitive_phf_map! { + unknown_id -> CountedUnknownProperty = { + % for property in data.counted_unknown_properties: + "${property.name}" => CountedUnknownProperty::${property.camel_case}, + % endfor + } + } + unknown_id(property_name).cloned() + } + + /// Returns the underlying index, used for use counter. + #[inline] + pub fn bit(self) -> usize { + self as usize + } +} + +impl PropertyId { + /// Return the longhand id that this property id represents. + #[inline] + pub fn longhand_id(&self) -> Option<LonghandId> { + Some(match *self { + PropertyId::Longhand(id) => id, + PropertyId::LonghandAlias(id, _) => id, + _ => return None, + }) + } + + /// Returns a given property from the given name, _regardless of whether it + /// is enabled or not_, or Err(()) for unknown properties. + /// + /// Do not use for non-testing purposes. + pub fn parse_unchecked_for_testing(name: &str) -> Result<Self, ()> { + Self::parse_unchecked(name, None) + } + + /// Returns a given property from the given name, _regardless of whether it + /// is enabled or not_, or Err(()) for unknown properties. + fn parse_unchecked( + property_name: &str, + use_counters: Option< &UseCounters>, + ) -> Result<Self, ()> { + // A special id for css use counters. + // ShorthandAlias is not used in the Servo build. + // That's why we need to allow dead_code. + #[allow(dead_code)] + pub enum StaticId { + Longhand(LonghandId), + Shorthand(ShorthandId), + LonghandAlias(LonghandId, AliasId), + ShorthandAlias(ShorthandId, AliasId), + CountedUnknown(CountedUnknownProperty), + } + ascii_case_insensitive_phf_map! { + static_id -> StaticId = { + % for (kind, properties) in [("Longhand", data.longhands), ("Shorthand", data.shorthands)]: + % for property in properties: + "${property.name}" => StaticId::${kind}(${kind}Id::${property.camel_case}), + % for alias in property.aliases: + "${alias.name}" => { + StaticId::${kind}Alias( + ${kind}Id::${property.camel_case}, + AliasId::${alias.camel_case}, + ) + }, + % endfor + % endfor + % endfor + % for property in data.counted_unknown_properties: + "${property.name}" => { + StaticId::CountedUnknown(CountedUnknownProperty::${property.camel_case}) + }, + % endfor + } + } + + if let Some(id) = static_id(property_name) { + return Ok(match *id { + StaticId::Longhand(id) => PropertyId::Longhand(id), + StaticId::Shorthand(id) => { + #[cfg(feature = "gecko")] + { + // We want to count `zoom` even if disabled. + if matches!(id, ShorthandId::Zoom) { + if let Some(counters) = use_counters { + counters.non_custom_properties.record(id.into()); + } + } + } + + PropertyId::Shorthand(id) + }, + StaticId::LonghandAlias(id, alias) => PropertyId::LonghandAlias(id, alias), + StaticId::ShorthandAlias(id, alias) => PropertyId::ShorthandAlias(id, alias), + StaticId::CountedUnknown(unknown_prop) => { + if let Some(counters) = use_counters { + counters.counted_unknown_properties.record(unknown_prop); + } + + // Always return Err(()) because these aren't valid custom property names. + return Err(()); + } + }); + } + + let name = crate::custom_properties::parse_name(property_name)?; + Ok(PropertyId::Custom(crate::custom_properties::Name::from(name))) + } + + /// Parses a property name, and returns an error if it's unknown or isn't + /// enabled for all content. + #[inline] + pub fn parse_enabled_for_all_content(name: &str) -> Result<Self, ()> { + let id = Self::parse_unchecked(name, None)?; + + if !id.enabled_for_all_content() { + return Err(()); + } + + Ok(id) + } + + + /// Parses a property name, and returns an error if it's unknown or isn't + /// allowed in this context. + #[inline] + pub fn parse(name: &str, context: &ParserContext) -> Result<Self, ()> { + let id = Self::parse_unchecked(name, context.use_counters)?; + + if !id.allowed_in(context) { + return Err(()); + } + + Ok(id) + } + + /// Parses a property name, and returns an error if it's unknown or isn't + /// allowed in this context, ignoring the rule_type checks. + /// + /// This is useful for parsing stuff from CSS values, for example. + #[inline] + pub fn parse_ignoring_rule_type( + name: &str, + context: &ParserContext, + ) -> Result<Self, ()> { + let id = Self::parse_unchecked(name, None)?; + + if !id.allowed_in_ignoring_rule_type(context) { + return Err(()); + } + + Ok(id) + } + + /// Returns a property id from Gecko's nsCSSPropertyID. + #[cfg(feature = "gecko")] + #[allow(non_upper_case_globals)] + #[inline] + pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Result<Self, ()> { + Ok(NonCustomPropertyId::from_nscsspropertyid(id)?.to_property_id()) + } + + /// Returns true if the property is a shorthand or shorthand alias. + #[inline] + pub fn is_shorthand(&self) -> bool { + self.as_shorthand().is_ok() + } + + /// Given this property id, get it either as a shorthand or as a + /// `PropertyDeclarationId`. + pub fn as_shorthand(&self) -> Result<ShorthandId, PropertyDeclarationId> { + match *self { + PropertyId::ShorthandAlias(id, _) | + PropertyId::Shorthand(id) => Ok(id), + PropertyId::LonghandAlias(id, _) | + PropertyId::Longhand(id) => Err(PropertyDeclarationId::Longhand(id)), + PropertyId::Custom(ref name) => Err(PropertyDeclarationId::Custom(name)), + } + } + + /// Returns the `NonCustomPropertyId` corresponding to this property id. + pub fn non_custom_id(&self) -> Option<NonCustomPropertyId> { + Some(match *self { + PropertyId::Custom(_) => return None, + PropertyId::Shorthand(shorthand_id) => shorthand_id.into(), + PropertyId::Longhand(longhand_id) => longhand_id.into(), + PropertyId::ShorthandAlias(_, alias_id) => alias_id.into(), + PropertyId::LonghandAlias(_, alias_id) => alias_id.into(), + }) + } + + /// Returns non-alias NonCustomPropertyId corresponding to this + /// property id. + fn non_custom_non_alias_id(&self) -> Option<NonCustomPropertyId> { + Some(match *self { + PropertyId::Custom(_) => return None, + PropertyId::Shorthand(id) => id.into(), + PropertyId::Longhand(id) => id.into(), + PropertyId::ShorthandAlias(id, _) => id.into(), + PropertyId::LonghandAlias(id, _) => id.into(), + }) + } + + /// Whether the property is enabled for all content regardless of the + /// stylesheet it was declared on (that is, in practice only checks prefs). + #[inline] + pub fn enabled_for_all_content(&self) -> bool { + let id = match self.non_custom_id() { + // Custom properties are allowed everywhere + None => return true, + Some(id) => id, + }; + + id.enabled_for_all_content() + } + + /// Converts this PropertyId in nsCSSPropertyID, resolving aliases to the + /// resolved property, and returning eCSSPropertyExtra_variable for custom + /// properties. + #[cfg(feature = "gecko")] + #[inline] + pub fn to_nscsspropertyid_resolving_aliases(&self) -> nsCSSPropertyID { + match self.non_custom_non_alias_id() { + Some(id) => id.to_nscsspropertyid(), + None => nsCSSPropertyID::eCSSPropertyExtra_variable, + } + } + + fn allowed_in(&self, context: &ParserContext) -> bool { + let id = match self.non_custom_id() { + // Custom properties are allowed everywhere + None => return true, + Some(id) => id, + }; + id.allowed_in(context) + } + + #[inline] + fn allowed_in_ignoring_rule_type(&self, context: &ParserContext) -> bool { + let id = match self.non_custom_id() { + // Custom properties are allowed everywhere + None => return true, + Some(id) => id, + }; + id.allowed_in_ignoring_rule_type(context) + } + + /// Whether the property supports the given CSS type. + /// `ty` should a bitflags of constants in style_traits::CssType. + pub fn supports_type(&self, ty: u8) -> bool { + let id = self.non_custom_non_alias_id(); + id.map_or(0, |id| id.supported_types()) & ty != 0 + } + + /// Collect supported starting word of values of this property. + /// + /// See style_traits::SpecifiedValueInfo::collect_completion_keywords for more + /// details. + pub fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) { + if let Some(id) = self.non_custom_non_alias_id() { + id.collect_property_completion_keywords(f); + } + CSSWideKeyword::collect_completion_keywords(f); + } +} + +/// A declaration using a CSS-wide keyword. +#[cfg_attr(feature = "gecko", derive(MallocSizeOf))] +#[derive(Clone, PartialEq, ToCss, ToShmem)] +pub struct WideKeywordDeclaration { + #[css(skip)] + id: LonghandId, + /// The CSS-wide keyword. + pub keyword: CSSWideKeyword, +} + +/// An unparsed declaration that contains `var()` functions. +#[cfg_attr(feature = "gecko", derive(MallocSizeOf))] +#[derive(Clone, PartialEq, ToCss, ToShmem)] +pub struct VariableDeclaration { + /// The id of the property this declaration represents. + #[css(skip)] + pub id: LonghandId, + /// The unparsed value of the variable. + #[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")] + pub value: Arc<UnparsedValue>, +} + +/// A custom property declaration value is either an unparsed value or a CSS +/// wide-keyword. +#[derive(Clone, PartialEq, ToCss, ToShmem)] +pub enum CustomDeclarationValue { + /// A value. + Value(Arc<crate::custom_properties::SpecifiedValue>), + /// A wide keyword. + CSSWideKeyword(CSSWideKeyword), +} + +/// A custom property declaration with the property name and the declared value. +#[cfg_attr(feature = "gecko", derive(MallocSizeOf))] +#[derive(Clone, PartialEq, ToCss, ToShmem)] +pub struct CustomDeclaration { + /// The name of the custom property. + #[css(skip)] + pub name: crate::custom_properties::Name, + /// The value of the custom property. + #[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")] + pub value: CustomDeclarationValue, +} + +impl fmt::Debug for PropertyDeclaration { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.id().to_css(&mut CssWriter::new(f))?; + f.write_str(": ")?; + + // Because PropertyDeclaration::to_css requires CssStringWriter, we can't write + // it directly to f, and need to allocate an intermediate string. This is + // fine for debug-only code. + let mut s = CssString::new(); + self.to_css(&mut s)?; + write!(f, "{}", s) + } +} + +impl PropertyDeclaration { + /// Given a property declaration, return the property declaration id. + #[inline] + pub fn id(&self) -> PropertyDeclarationId { + match *self { + PropertyDeclaration::Custom(ref declaration) => { + return PropertyDeclarationId::Custom(&declaration.name) + } + PropertyDeclaration::CSSWideKeyword(ref declaration) => { + return PropertyDeclarationId::Longhand(declaration.id); + } + PropertyDeclaration::WithVariables(ref declaration) => { + return PropertyDeclarationId::Longhand(declaration.id); + } + _ => {} + } + // This is just fine because PropertyDeclaration and LonghandId + // have corresponding discriminants. + let id = unsafe { *(self as *const _ as *const LonghandId) }; + debug_assert_eq!(id, match *self { + % for property in data.longhands: + PropertyDeclaration::${property.camel_case}(..) => LonghandId::${property.camel_case}, + % endfor + _ => id, + }); + PropertyDeclarationId::Longhand(id) + } + + /// Given a declaration, convert it into a declaration for a corresponding + /// physical property. + #[inline] + pub fn to_physical(&self, wm: WritingMode) -> Self { + match *self { + PropertyDeclaration::WithVariables(VariableDeclaration { + id, + ref value, + }) => { + return PropertyDeclaration::WithVariables(VariableDeclaration { + id: id.to_physical(wm), + value: value.clone(), + }) + } + PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration { + id, + keyword, + }) => { + return PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration { + id: id.to_physical(wm), + keyword, + }) + } + PropertyDeclaration::Custom(..) => return self.clone(), + % for prop in data.longhands: + PropertyDeclaration::${prop.camel_case}(..) => {}, + % endfor + } + + let mut ret = self.clone(); + + % for prop in data.longhands: + % for physical_property in prop.all_physical_mapped_properties(data): + % if physical_property.specified_type() != prop.specified_type(): + <% raise "Logical property %s should share specified value with physical property %s" % \ + (prop.name, physical_property.name) %> + % endif + % endfor + % endfor + + unsafe { + let longhand_id = *(&mut ret as *mut _ as *mut LonghandId); + + debug_assert_eq!( + PropertyDeclarationId::Longhand(longhand_id), + ret.id() + ); + + // This is just fine because PropertyDeclaration and LonghandId + // have corresponding discriminants. + *(&mut ret as *mut _ as *mut LonghandId) = longhand_id.to_physical(wm); + + debug_assert_eq!( + PropertyDeclarationId::Longhand(longhand_id.to_physical(wm)), + ret.id() + ); + } + + ret + } + + fn with_variables_from_shorthand(&self, shorthand: ShorthandId) -> Option< &str> { + match *self { + PropertyDeclaration::WithVariables(ref declaration) => { + let s = declaration.value.from_shorthand?; + if s != shorthand { + return None; + } + Some(&*declaration.value.css) + }, + _ => None, + } + } + + /// Returns a CSS-wide keyword declaration for a given property. + #[inline] + pub fn css_wide_keyword(id: LonghandId, keyword: CSSWideKeyword) -> Self { + Self::CSSWideKeyword(WideKeywordDeclaration { id, keyword }) + } + + /// Returns a CSS-wide keyword if the declaration's value is one. + #[inline] + pub fn get_css_wide_keyword(&self) -> Option<CSSWideKeyword> { + match *self { + PropertyDeclaration::CSSWideKeyword(ref declaration) => { + Some(declaration.keyword) + }, + _ => None, + } + } + + /// Returns whether or not the property is set by a system font + pub fn get_system(&self) -> Option<SystemFont> { + match *self { + % if engine == "gecko": + % for prop in SYSTEM_FONT_LONGHANDS: + PropertyDeclaration::${to_camel_case(prop)}(ref prop) => { + prop.get_system() + } + % endfor + % endif + _ => None, + } + } + + /// Is it the default value of line-height? + pub fn is_default_line_height(&self) -> bool { + match *self { + PropertyDeclaration::LineHeight(LineHeight::Normal) => true, + _ => false + } + } + + /// Returns whether the declaration may be serialized as part of a shorthand. + /// + /// This method returns false if this declaration contains variable or has a + /// CSS-wide keyword value, since these values cannot be serialized as part + /// of a shorthand. + /// + /// Caller should check `with_variables_from_shorthand()` and whether all + /// needed declarations has the same CSS-wide keyword first. + /// + /// Note that, serialization of a shorthand may still fail because of other + /// property-specific requirement even when this method returns true for all + /// the longhand declarations. + pub fn may_serialize_as_part_of_shorthand(&self) -> bool { + match *self { + PropertyDeclaration::CSSWideKeyword(..) | + PropertyDeclaration::WithVariables(..) => false, + PropertyDeclaration::Custom(..) => + unreachable!("Serializing a custom property as part of shorthand?"), + _ => true, + } + } + + /// Return whether the value is stored as it was in the CSS source, + /// preserving whitespace (as opposed to being parsed into a more abstract + /// data structure). + /// + /// This is the case of custom properties and values that contain + /// unsubstituted variables. + pub fn value_is_unparsed(&self) -> bool { + match *self { + PropertyDeclaration::WithVariables(..) => true, + PropertyDeclaration::Custom(ref declaration) => { + matches!(declaration.value, CustomDeclarationValue::Value(..)) + } + _ => false, + } + } + + /// Returns true if this property declaration is for one of the animatable + /// properties. + pub fn is_animatable(&self) -> bool { + match self.id() { + PropertyDeclarationId::Longhand(id) => id.is_animatable(), + PropertyDeclarationId::Custom(..) => false, + } + } + + /// Returns true if this property is a custom property, false + /// otherwise. + pub fn is_custom(&self) -> bool { + matches!(*self, PropertyDeclaration::Custom(..)) + } + + /// The `context` parameter controls this: + /// + /// <https://drafts.csswg.org/css-animations/#keyframes> + /// > The <declaration-list> inside of <keyframe-block> accepts any CSS property + /// > except those defined in this specification, + /// > but does accept the `animation-play-state` property and interprets it specially. + /// + /// This will not actually parse Importance values, and will always set things + /// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser, + /// we only set them here so that we don't have to reallocate + pub fn parse_into<'i, 't>( + declarations: &mut SourcePropertyDeclaration, + id: PropertyId, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>> { + assert!(declarations.is_empty()); + debug_assert!(id.allowed_in(context), "{:?}", id); + + let non_custom_id = id.non_custom_id(); + input.skip_whitespace(); + + let start = input.state(); + match id { + PropertyId::Custom(property_name) => { + let value = match input.try_parse(CSSWideKeyword::parse) { + Ok(keyword) => CustomDeclarationValue::CSSWideKeyword(keyword), + Err(()) => CustomDeclarationValue::Value( + crate::custom_properties::SpecifiedValue::parse(input)? + ), + }; + declarations.push(PropertyDeclaration::Custom(CustomDeclaration { + name: property_name, + value, + })); + return Ok(()); + } + PropertyId::LonghandAlias(id, _) | + PropertyId::Longhand(id) => { + input.try_parse(CSSWideKeyword::parse).map(|keyword| { + PropertyDeclaration::css_wide_keyword(id, keyword) + }).or_else(|()| { + input.look_for_var_or_env_functions(); + input.parse_entirely(|input| id.parse_value(context, input)) + .or_else(|err| { + while let Ok(_) = input.next() {} // Look for var() after the error. + if !input.seen_var_or_env_functions() { + return Err(err); + } + input.reset(&start); + let (first_token_type, css) = + crate::custom_properties::parse_non_custom_with_var(input)?; + Ok(PropertyDeclaration::WithVariables(VariableDeclaration { + id, + value: Arc::new(UnparsedValue { + css: css.into_owned(), + first_token_type, + url_data: context.url_data.clone(), + from_shorthand: None, + }), + })) + }) + }).map(|declaration| { + declarations.push(declaration) + })?; + } + PropertyId::ShorthandAlias(id, _) | + PropertyId::Shorthand(id) => { + if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) { + if id == ShorthandId::All { + declarations.all_shorthand = AllShorthand::CSSWideKeyword(keyword) + } else { + for longhand in id.longhands() { + declarations.push(PropertyDeclaration::css_wide_keyword(longhand, keyword)); + } + } + } else { + input.look_for_var_or_env_functions(); + // Not using parse_entirely here: each + // ${shorthand.ident}::parse_into function needs to do so + // *before* pushing to `declarations`. + id.parse_into(declarations, context, input).or_else(|err| { + while let Ok(_) = input.next() {} // Look for var() after the error. + if !input.seen_var_or_env_functions() { + return Err(err); + } + + input.reset(&start); + let (first_token_type, css) = + crate::custom_properties::parse_non_custom_with_var(input)?; + let unparsed = Arc::new(UnparsedValue { + css: css.into_owned(), + first_token_type, + url_data: context.url_data.clone(), + from_shorthand: Some(id), + }); + if id == ShorthandId::All { + declarations.all_shorthand = AllShorthand::WithVariables(unparsed) + } else { + for id in id.longhands() { + declarations.push( + PropertyDeclaration::WithVariables(VariableDeclaration { + id, + value: unparsed.clone(), + }) + ) + } + } + Ok(()) + })?; + } + } + } + debug_assert!(non_custom_id.is_some(), "Custom properties should've returned earlier"); + if let Some(use_counters) = context.use_counters { + use_counters.non_custom_properties.record(non_custom_id.unwrap()); + } + Ok(()) + } +} + +const SUB_PROPERTIES_ARRAY_CAP: usize = + ${max(len(s.sub_properties) for s in data.shorthands_except_all()) \ + if data.shorthands_except_all() else 0}; + +/// An ArrayVec of subproperties, contains space for the longest shorthand except all. +pub type SubpropertiesVec<T> = ArrayVec<T, SUB_PROPERTIES_ARRAY_CAP>; + +/// A stack-allocated vector of `PropertyDeclaration` +/// large enough to parse one CSS `key: value` declaration. +/// (Shorthands expand to multiple `PropertyDeclaration`s.) +#[derive(Default)] +pub struct SourcePropertyDeclaration { + /// The storage for the actual declarations (except for all). + pub declarations: SubpropertiesVec<PropertyDeclaration>, + /// Stored separately to keep SubpropertiesVec smaller. + pub all_shorthand: AllShorthand, +} + +// This is huge, but we allocate it on the stack and then never move it, +// we only pass `&mut SourcePropertyDeclaration` references around. +size_of_test!(SourcePropertyDeclaration, 632); + +impl SourcePropertyDeclaration { + /// Create one with a single PropertyDeclaration. + #[inline] + pub fn with_one(decl: PropertyDeclaration) -> Self { + let mut result = Self::default(); + result.declarations.push(decl); + result + } + + /// Similar to Vec::drain: leaves this empty when the return value is dropped. + pub fn drain(&mut self) -> SourcePropertyDeclarationDrain { + SourcePropertyDeclarationDrain { + declarations: self.declarations.drain(..), + all_shorthand: mem::replace(&mut self.all_shorthand, AllShorthand::NotSet), + } + } + + /// Reset to initial state + pub fn clear(&mut self) { + self.declarations.clear(); + self.all_shorthand = AllShorthand::NotSet; + } + + fn is_empty(&self) -> bool { + self.declarations.is_empty() && matches!(self.all_shorthand, AllShorthand::NotSet) + } + + fn push(&mut self, declaration: PropertyDeclaration) { + let _result = self.declarations.try_push(declaration); + debug_assert!(_result.is_ok()); + } +} + +/// Return type of SourcePropertyDeclaration::drain +pub struct SourcePropertyDeclarationDrain<'a> { + /// A drain over the non-all declarations. + pub declarations: ArrayVecDrain<'a, PropertyDeclaration, SUB_PROPERTIES_ARRAY_CAP>, + /// The all shorthand that was set. + pub all_shorthand: AllShorthand, +} + +/// A parsed all-shorthand value. +pub enum AllShorthand { + /// Not present. + NotSet, + /// A CSS-wide keyword. + CSSWideKeyword(CSSWideKeyword), + /// An all shorthand with var() references that we can't resolve right now. + WithVariables(Arc<UnparsedValue>) +} + +impl Default for AllShorthand { + fn default() -> Self { + Self::NotSet + } +} + +impl AllShorthand { + /// Iterates property declarations from the given all shorthand value. + #[inline] + pub fn declarations(&self) -> AllShorthandDeclarationIterator { + AllShorthandDeclarationIterator { + all_shorthand: self, + longhands: ShorthandId::All.longhands(), + } + } +} + +/// An iterator over the all shorthand's shorthand declarations. +pub struct AllShorthandDeclarationIterator<'a> { + all_shorthand: &'a AllShorthand, + longhands: NonCustomPropertyIterator<LonghandId>, +} + +impl<'a> Iterator for AllShorthandDeclarationIterator<'a> { + type Item = PropertyDeclaration; + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + match *self.all_shorthand { + AllShorthand::NotSet => None, + AllShorthand::CSSWideKeyword(ref keyword) => { + Some(PropertyDeclaration::css_wide_keyword(self.longhands.next()?, *keyword)) + } + AllShorthand::WithVariables(ref unparsed) => { + Some(PropertyDeclaration::WithVariables( + VariableDeclaration { + id: self.longhands.next()?, + value: unparsed.clone() + } + )) + } + } + } +} + +#[cfg(feature = "gecko")] +pub use super::gecko::style_structs; + +/// The module where all the style structs are defined. +#[cfg(feature = "servo")] +pub mod style_structs { + use fxhash::FxHasher; + use super::longhands; + use std::hash::{Hash, Hasher}; + use crate::logical_geometry::WritingMode; + use crate::media_queries::Device; + use crate::values::computed::NonNegativeLength; + + % for style_struct in data.active_style_structs(): + % if style_struct.name == "Font": + #[derive(Clone, Debug, MallocSizeOf)] + #[cfg_attr(feature = "servo", derive(Serialize, Deserialize))] + % else: + #[derive(Clone, Debug, MallocSizeOf, PartialEq)] + % endif + /// The ${style_struct.name} style struct. + pub struct ${style_struct.name} { + % for longhand in style_struct.longhands: + % if not longhand.logical: + /// The ${longhand.name} computed value. + pub ${longhand.ident}: longhands::${longhand.ident}::computed_value::T, + % endif + % endfor + % if style_struct.name == "InheritedText": + /// The "used" text-decorations that apply to this box. + /// + /// FIXME(emilio): This is technically a box-tree concept, and + /// would be nice to move away from style. + pub text_decorations_in_effect: crate::values::computed::text::TextDecorationsInEffect, + % endif + % if style_struct.name == "Font": + /// The font hash, used for font caching. + pub hash: u64, + % endif + % if style_struct.name == "Box": + /// The display value specified by the CSS stylesheets (without any style adjustments), + /// which is needed for hypothetical layout boxes. + pub original_display: longhands::display::computed_value::T, + % endif + } + % if style_struct.name == "Font": + impl PartialEq for Font { + fn eq(&self, other: &Font) -> bool { + self.hash == other.hash + % for longhand in style_struct.longhands: + && self.${longhand.ident} == other.${longhand.ident} + % endfor + } + } + % endif + + impl ${style_struct.name} { + % for longhand in style_struct.longhands: + % if longhand.logical: + ${helpers.logical_setter(name=longhand.name)} + % else: + % if longhand.ident == "display": + /// Set `display`. + /// + /// We need to keep track of the original display for hypothetical boxes, + /// so we need to special-case this. + #[allow(non_snake_case)] + #[inline] + pub fn set_display(&mut self, v: longhands::display::computed_value::T) { + self.display = v; + self.original_display = v; + } + % else: + /// Set ${longhand.name}. + #[allow(non_snake_case)] + #[inline] + pub fn set_${longhand.ident}(&mut self, v: longhands::${longhand.ident}::computed_value::T) { + self.${longhand.ident} = v; + } + % endif + % if longhand.ident == "display": + /// Set `display` from other struct. + /// + /// Same as `set_display` above. + /// Thus, we need to special-case this. + #[allow(non_snake_case)] + #[inline] + pub fn copy_display_from(&mut self, other: &Self) { + self.display = other.display.clone(); + self.original_display = other.display.clone(); + } + % else: + /// Set ${longhand.name} from other struct. + #[allow(non_snake_case)] + #[inline] + pub fn copy_${longhand.ident}_from(&mut self, other: &Self) { + self.${longhand.ident} = other.${longhand.ident}.clone(); + } + % endif + /// Reset ${longhand.name} from the initial struct. + #[allow(non_snake_case)] + #[inline] + pub fn reset_${longhand.ident}(&mut self, other: &Self) { + self.copy_${longhand.ident}_from(other) + } + + /// Get the computed value for ${longhand.name}. + #[allow(non_snake_case)] + #[inline] + pub fn clone_${longhand.ident}(&self) -> longhands::${longhand.ident}::computed_value::T { + self.${longhand.ident}.clone() + } + % endif + % if longhand.need_index: + /// If this longhand is indexed, get the number of elements. + #[allow(non_snake_case)] + pub fn ${longhand.ident}_count(&self) -> usize { + self.${longhand.ident}.0.len() + } + + /// If this longhand is indexed, get the element at given + /// index. + #[allow(non_snake_case)] + pub fn ${longhand.ident}_at(&self, index: usize) + -> longhands::${longhand.ident}::computed_value::SingleComputedValue { + self.${longhand.ident}.0[index].clone() + } + % endif + % endfor + % if style_struct.name == "Border": + % for side in ["top", "right", "bottom", "left"]: + /// Whether the border-${side} property has nonzero width. + #[allow(non_snake_case)] + pub fn border_${side}_has_nonzero_width(&self) -> bool { + use crate::Zero; + !self.border_${side}_width.is_zero() + } + % endfor + % elif style_struct.name == "Font": + /// Computes a font hash in order to be able to cache fonts + /// effectively in GFX and layout. + pub fn compute_font_hash(&mut self) { + // Corresponds to the fields in + // `gfx::font_template::FontTemplateDescriptor`. + let mut hasher: FxHasher = Default::default(); + self.font_weight.hash(&mut hasher); + self.font_stretch.hash(&mut hasher); + self.font_style.hash(&mut hasher); + self.font_family.hash(&mut hasher); + self.hash = hasher.finish() + } + + /// (Servo does not handle MathML, so this just calls copy_font_size_from) + pub fn inherit_font_size_from(&mut self, parent: &Self, + _: Option<NonNegativeLength>, + _: &Device) { + self.copy_font_size_from(parent); + } + /// (Servo does not handle MathML, so this just calls set_font_size) + pub fn apply_font_size(&mut self, + v: longhands::font_size::computed_value::T, + _: &Self, + _: &Device) -> Option<NonNegativeLength> { + self.set_font_size(v); + None + } + /// (Servo does not handle MathML, so this does nothing) + pub fn apply_unconstrained_font_size(&mut self, _: NonNegativeLength) { + } + + % elif style_struct.name == "Outline": + /// Whether the outline-width property is non-zero. + #[inline] + pub fn outline_has_nonzero_width(&self) -> bool { + use crate::Zero; + !self.outline_width.is_zero() + } + % elif style_struct.name == "Box": + /// Sets the display property, but without touching original_display, + /// except when the adjustment comes from root or item display fixups. + pub fn set_adjusted_display( + &mut self, + dpy: longhands::display::computed_value::T, + is_item_or_root: bool + ) { + self.display = dpy; + if is_item_or_root { + self.original_display = dpy; + } + } + % endif + } + + % endfor +} + +% for style_struct in data.active_style_structs(): + impl style_structs::${style_struct.name} { + % for longhand in style_struct.longhands: + % if longhand.need_index: + /// Iterate over the values of ${longhand.name}. + #[allow(non_snake_case)] + #[inline] + pub fn ${longhand.ident}_iter(&self) -> ${longhand.camel_case}Iter { + ${longhand.camel_case}Iter { + style_struct: self, + current: 0, + max: self.${longhand.ident}_count(), + } + } + + /// Get a value mod `index` for the property ${longhand.name}. + #[allow(non_snake_case)] + #[inline] + pub fn ${longhand.ident}_mod(&self, index: usize) + -> longhands::${longhand.ident}::computed_value::SingleComputedValue { + self.${longhand.ident}_at(index % self.${longhand.ident}_count()) + } + + /// Clone the computed value for the property. + #[allow(non_snake_case)] + #[inline] + #[cfg(feature = "gecko")] + pub fn clone_${longhand.ident}( + &self, + ) -> longhands::${longhand.ident}::computed_value::T { + longhands::${longhand.ident}::computed_value::List( + self.${longhand.ident}_iter().collect() + ) + } + % endif + % endfor + + % if style_struct.name == "UI": + /// Returns whether there is any animation specified with + /// animation-name other than `none`. + pub fn specifies_animations(&self) -> bool { + self.animation_name_iter().any(|name| !name.is_none()) + } + + /// Returns whether there are any transitions specified. + #[cfg(feature = "servo")] + pub fn specifies_transitions(&self) -> bool { + (0..self.transition_property_count()).any(|index| { + let combined_duration = + self.transition_duration_mod(index).seconds().max(0.) + + self.transition_delay_mod(index).seconds(); + combined_duration > 0. + }) + } + + /// Returns whether there is any named progress timeline specified with + /// scroll-timeline-name other than `none`. + pub fn specifies_scroll_timelines(&self) -> bool { + self.scroll_timeline_name_iter().any(|name| !name.is_none()) + } + + /// Returns whether there is any named progress timeline specified with + /// view-timeline-name other than `none`. + pub fn specifies_view_timelines(&self) -> bool { + self.view_timeline_name_iter().any(|name| !name.is_none()) + } + + /// Returns true if animation properties are equal between styles, but without + /// considering keyframe data and animation-timeline. + #[cfg(feature = "servo")] + pub fn animations_equals(&self, other: &Self) -> bool { + self.animation_name_iter().eq(other.animation_name_iter()) && + self.animation_delay_iter().eq(other.animation_delay_iter()) && + self.animation_direction_iter().eq(other.animation_direction_iter()) && + self.animation_duration_iter().eq(other.animation_duration_iter()) && + self.animation_fill_mode_iter().eq(other.animation_fill_mode_iter()) && + self.animation_iteration_count_iter().eq(other.animation_iteration_count_iter()) && + self.animation_play_state_iter().eq(other.animation_play_state_iter()) && + self.animation_timing_function_iter().eq(other.animation_timing_function_iter()) + } + + % elif style_struct.name == "Column": + /// Whether this is a multicol style. + #[cfg(feature = "servo")] + pub fn is_multicol(&self) -> bool { + !self.column_width.is_auto() || !self.column_count.is_auto() + } + % endif + } + + % for longhand in style_struct.longhands: + % if longhand.need_index: + /// An iterator over the values of the ${longhand.name} properties. + pub struct ${longhand.camel_case}Iter<'a> { + style_struct: &'a style_structs::${style_struct.name}, + current: usize, + max: usize, + } + + impl<'a> Iterator for ${longhand.camel_case}Iter<'a> { + type Item = longhands::${longhand.ident}::computed_value::SingleComputedValue; + + fn next(&mut self) -> Option<Self::Item> { + self.current += 1; + if self.current <= self.max { + Some(self.style_struct.${longhand.ident}_at(self.current - 1)) + } else { + None + } + } + } + % endif + % endfor +% endfor + + +#[cfg(feature = "gecko")] +pub use super::gecko::{ComputedValues, ComputedValuesInner}; + +#[cfg(feature = "servo")] +#[cfg_attr(feature = "servo", derive(Clone, Debug))] +/// Actual data of ComputedValues, to match up with Gecko +pub struct ComputedValuesInner { + % for style_struct in data.active_style_structs(): + ${style_struct.ident}: Arc<style_structs::${style_struct.name}>, + % endfor + custom_properties: Option<Arc<crate::custom_properties::CustomPropertiesMap>>, + /// The writing mode of this computed values struct. + pub writing_mode: WritingMode, + + /// A set of flags we use to store misc information regarding this style. + pub flags: ComputedValueFlags, + + /// The rule node representing the ordered list of rules matched for this + /// node. Can be None for default values and text nodes. This is + /// essentially an optimization to avoid referencing the root rule node. + pub rules: Option<StrongRuleNode>, + + /// The element's computed values if visited, only computed if there's a + /// relevant link for this element. A element's "relevant link" is the + /// element being matched if it is a link or the nearest ancestor link. + visited_style: Option<Arc<ComputedValues>>, +} + +/// The struct that Servo uses to represent computed values. +/// +/// This struct contains an immutable atomically-reference-counted pointer to +/// every kind of style struct. +/// +/// When needed, the structs may be copied in order to get mutated. +#[cfg(feature = "servo")] +#[cfg_attr(feature = "servo", derive(Clone, Debug))] +pub struct ComputedValues { + /// The actual computed values + /// + /// In Gecko the outer ComputedValues is actually a ComputedStyle, whereas + /// ComputedValuesInner is the core set of computed values. + /// + /// We maintain this distinction in servo to reduce the amount of special + /// casing. + inner: ComputedValuesInner, + + /// The pseudo-element that we're using. + pseudo: Option<PseudoElement>, +} + +impl ComputedValues { + /// Returns the pseudo-element that this style represents. + #[cfg(feature = "servo")] + pub fn pseudo(&self) -> Option<<&PseudoElement> { + self.pseudo.as_ref() + } + + /// Returns true if this is the style for a pseudo-element. + #[cfg(feature = "servo")] + pub fn is_pseudo_style(&self) -> bool { + self.pseudo().is_some() + } + + /// Returns whether this style's display value is equal to contents. + pub fn is_display_contents(&self) -> bool { + self.clone_display().is_contents() + } + + /// Gets a reference to the rule node. Panic if no rule node exists. + pub fn rules(&self) -> &StrongRuleNode { + self.rules.as_ref().unwrap() + } + + /// Returns the visited rules, if applicable. + pub fn visited_rules(&self) -> Option<<&StrongRuleNode> { + self.visited_style().and_then(|s| s.rules.as_ref()) + } + + /// Gets a reference to the custom properties map (if one exists). + pub fn custom_properties(&self) -> Option<<&Arc<crate::custom_properties::CustomPropertiesMap>> { + self.custom_properties.as_ref() + } + + /// Returns whether we have the same custom properties as another style. + /// + /// This should effectively be just: + /// + /// self.custom_properties() == other.custom_properties() + /// + /// But that's not really the case because 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 + pub fn custom_properties_equal(&self, other: &Self) -> bool { + match (self.custom_properties(), other.custom_properties()) { + (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, + } + } + +% for prop in data.longhands: + /// Gets the computed value of a given property. + #[inline(always)] + #[allow(non_snake_case)] + pub fn clone_${prop.ident}( + &self, + ) -> longhands::${prop.ident}::computed_value::T { + self.get_${prop.style_struct.ident.strip("_")}() + % if prop.logical: + .clone_${prop.ident}(self.writing_mode) + % else: + .clone_${prop.ident}() + % endif + } +% endfor + + /// Writes the (resolved or computed) value of the given longhand as a string in `dest`. + /// + /// TODO(emilio): We should move all the special resolution from + /// nsComputedDOMStyle to ToResolvedValue instead. + pub fn computed_or_resolved_value( + &self, + property_id: LonghandId, + context: Option<<&resolved::Context>, + dest: &mut CssStringWriter, + ) -> fmt::Result { + use crate::values::resolved::ToResolvedValue; + let mut dest = CssWriter::new(dest); + match property_id { + % for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()): + <% props = list(props) %> + ${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => { + let value = match property_id { + % for prop in props: + LonghandId::${prop.camel_case} => self.clone_${prop.ident}(), + % endfor + _ => unsafe { debug_unreachable!() }, + }; + if let Some(c) = context { + value.to_resolved_value(c).to_css(&mut dest) + } else { + value.to_css(&mut dest) + } + } + % endfor + } + } + + /// Returns the given longhand's resolved value as a property declaration. + pub fn computed_or_resolved_declaration( + &self, + property_id: LonghandId, + context: Option<<&resolved::Context>, + ) -> PropertyDeclaration { + use crate::values::resolved::ToResolvedValue; + use crate::values::computed::ToComputedValue; + match property_id { + % for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()): + <% props = list(props) %> + ${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => { + let mut computed_value = match property_id { + % for prop in props: + LonghandId::${prop.camel_case} => self.clone_${prop.ident}(), + % endfor + _ => unsafe { debug_unreachable!() }, + }; + if let Some(c) = context { + let resolved = computed_value.to_resolved_value(c); + computed_value = ToResolvedValue::from_resolved_value(resolved); + } + let specified = ToComputedValue::from_computed_value(&computed_value); + % if props[0].boxed: + let specified = Box::new(specified); + % endif + % if len(props) == 1: + PropertyDeclaration::${props[0].camel_case}(specified) + % else: + unsafe { + let mut out = mem::MaybeUninit::uninit(); + ptr::write( + out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${specified_type}>, + PropertyDeclarationVariantRepr { + tag: property_id as u16, + value: specified, + }, + ); + out.assume_init() + } + % endif + } + % endfor + } + } + + /// Resolves the currentColor keyword. + /// + /// Any color value from computed values (except for the 'color' property + /// itself) should go through this method. + /// + /// Usage example: + /// let top_color = + /// style.resolve_color(style.get_border().clone_border_top_color()); + #[inline] + pub fn resolve_color(&self, color: computed::Color) -> crate::color::AbsoluteColor { + let current_color = self.get_inherited_text().clone_color(); + color.resolve_to_absolute(¤t_color) + } + + /// Returns which longhand properties have different values in the two + /// ComputedValues. + #[cfg(feature = "gecko_debug")] + pub fn differing_properties(&self, other: &ComputedValues) -> LonghandIdSet { + let mut set = LonghandIdSet::new(); + % for prop in data.longhands: + if self.clone_${prop.ident}() != other.clone_${prop.ident}() { + set.insert(LonghandId::${prop.camel_case}); + } + % endfor + set + } + + /// Create a `TransitionPropertyIterator` for this styles transition properties. + pub fn transition_properties<'a>( + &'a self + ) -> animated_properties::TransitionPropertyIterator<'a> { + animated_properties::TransitionPropertyIterator::from_style(self) + } +} + +#[cfg(feature = "servo")] +impl ComputedValues { + /// Create a new refcounted `ComputedValues` + pub fn new( + pseudo: Option<<&PseudoElement>, + custom_properties: Option<Arc<crate::custom_properties::CustomPropertiesMap>>, + writing_mode: WritingMode, + flags: ComputedValueFlags, + rules: Option<StrongRuleNode>, + visited_style: Option<Arc<ComputedValues>>, + % for style_struct in data.active_style_structs(): + ${style_struct.ident}: Arc<style_structs::${style_struct.name}>, + % endfor + ) -> Arc<Self> { + Arc::new(Self { + inner: ComputedValuesInner { + custom_properties, + writing_mode, + rules, + visited_style, + flags, + % for style_struct in data.active_style_structs(): + ${style_struct.ident}, + % endfor + }, + pseudo: pseudo.cloned(), + }) + } + + /// Get the initial computed values. + pub fn initial_values() -> &'static Self { &*INITIAL_SERVO_VALUES } + + /// Serializes the computed value of this property as a string. + pub fn computed_value_to_string(&self, property: PropertyDeclarationId) -> String { + match property { + PropertyDeclarationId::Longhand(id) => { + let mut s = String::new(); + self.get_longhand_property_value( + id, + &mut CssWriter::new(&mut s) + ).unwrap(); + s + } + PropertyDeclarationId::Custom(name) => { + self.custom_properties + .as_ref() + .and_then(|map| map.get(name)) + .map_or(String::new(), |value| value.to_css_string()) + } + } + } +} + +#[cfg(feature = "servo")] +impl ops::Deref for ComputedValues { + type Target = ComputedValuesInner; + fn deref(&self) -> &ComputedValuesInner { + &self.inner + } +} + +#[cfg(feature = "servo")] +impl ops::DerefMut for ComputedValues { + fn deref_mut(&mut self) -> &mut ComputedValuesInner { + &mut self.inner + } +} + +#[cfg(feature = "servo")] +impl ComputedValuesInner { + /// Returns the visited style, if any. + pub fn visited_style(&self) -> Option<<&ComputedValues> { + self.visited_style.as_deref() + } + + % for style_struct in data.active_style_structs(): + /// Clone the ${style_struct.name} struct. + #[inline] + pub fn clone_${style_struct.name_lower}(&self) -> Arc<style_structs::${style_struct.name}> { + self.${style_struct.ident}.clone() + } + + /// Get a immutable reference to the ${style_struct.name} struct. + #[inline] + pub fn get_${style_struct.name_lower}(&self) -> &style_structs::${style_struct.name} { + &self.${style_struct.ident} + } + + /// Get a mutable reference to the ${style_struct.name} struct. + #[inline] + pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} { + Arc::make_mut(&mut self.${style_struct.ident}) + } + % endfor + + /// Gets a reference to the rule node. Panic if no rule node exists. + pub fn rules(&self) -> &StrongRuleNode { + self.rules.as_ref().unwrap() + } + + #[inline] + /// Returns whether the "content" property for the given style is completely + /// ineffective, and would yield an empty `::before` or `::after` + /// pseudo-element. + pub fn ineffective_content_property(&self) -> bool { + use crate::values::generics::counters::Content; + match self.get_counters().content { + Content::Normal | Content::None => true, + Content::Items(ref items) => items.is_empty(), + } + } + + /// Whether the current style or any of its ancestors is multicolumn. + #[inline] + pub fn can_be_fragmented(&self) -> bool { + self.flags.contains(ComputedValueFlags::CAN_BE_FRAGMENTED) + } + + /// Whether the current style is multicolumn. + #[inline] + pub fn is_multicol(&self) -> bool { + self.get_column().is_multicol() + } + + /// Get the logical computed inline size. + #[inline] + pub fn content_inline_size(&self) -> &computed::Size { + let position_style = self.get_position(); + if self.writing_mode.is_vertical() { + &position_style.height + } else { + &position_style.width + } + } + + /// Get the logical computed block size. + #[inline] + pub fn content_block_size(&self) -> &computed::Size { + let position_style = self.get_position(); + if self.writing_mode.is_vertical() { &position_style.width } else { &position_style.height } + } + + /// Get the logical computed min inline size. + #[inline] + pub fn min_inline_size(&self) -> &computed::Size { + let position_style = self.get_position(); + if self.writing_mode.is_vertical() { &position_style.min_height } else { &position_style.min_width } + } + + /// Get the logical computed min block size. + #[inline] + pub fn min_block_size(&self) -> &computed::Size { + let position_style = self.get_position(); + if self.writing_mode.is_vertical() { &position_style.min_width } else { &position_style.min_height } + } + + /// Get the logical computed max inline size. + #[inline] + pub fn max_inline_size(&self) -> &computed::MaxSize { + let position_style = self.get_position(); + if self.writing_mode.is_vertical() { &position_style.max_height } else { &position_style.max_width } + } + + /// Get the logical computed max block size. + #[inline] + pub fn max_block_size(&self) -> &computed::MaxSize { + let position_style = self.get_position(); + if self.writing_mode.is_vertical() { &position_style.max_width } else { &position_style.max_height } + } + + /// Get the logical computed padding for this writing mode. + #[inline] + pub fn logical_padding(&self) -> LogicalMargin<<&computed::LengthPercentage> { + let padding_style = self.get_padding(); + LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( + &padding_style.padding_top.0, + &padding_style.padding_right.0, + &padding_style.padding_bottom.0, + &padding_style.padding_left.0, + )) + } + + /// Get the logical border width + #[inline] + pub fn border_width_for_writing_mode(&self, writing_mode: WritingMode) -> LogicalMargin<Au> { + let border_style = self.get_border(); + LogicalMargin::from_physical(writing_mode, SideOffsets2D::new( + Au::from(border_style.border_top_width), + Au::from(border_style.border_right_width), + Au::from(border_style.border_bottom_width), + Au::from(border_style.border_left_width), + )) + } + + /// Gets the logical computed border widths for this style. + #[inline] + pub fn logical_border_width(&self) -> LogicalMargin<Au> { + self.border_width_for_writing_mode(self.writing_mode) + } + + /// Gets the logical computed margin from this style. + #[inline] + pub fn logical_margin(&self) -> LogicalMargin<<&computed::LengthPercentageOrAuto> { + let margin_style = self.get_margin(); + LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( + &margin_style.margin_top, + &margin_style.margin_right, + &margin_style.margin_bottom, + &margin_style.margin_left, + )) + } + + /// Gets the logical position from this style. + #[inline] + pub fn logical_position(&self) -> LogicalMargin<<&computed::LengthPercentageOrAuto> { + // FIXME(SimonSapin): should be the writing mode of the containing block, maybe? + let position_style = self.get_position(); + LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( + &position_style.top, + &position_style.right, + &position_style.bottom, + &position_style.left, + )) + } + + /// Return true if the effects force the transform style to be Flat + pub fn overrides_transform_style(&self) -> bool { + use crate::computed_values::mix_blend_mode::T as MixBlendMode; + + let effects = self.get_effects(); + // TODO(gw): Add clip-path, isolation, mask-image, mask-border-source when supported. + effects.opacity < 1.0 || + !effects.filter.0.is_empty() || + !effects.clip.is_auto() || + effects.mix_blend_mode != MixBlendMode::Normal + } + + /// <https://drafts.csswg.org/css-transforms/#grouping-property-values> + pub fn get_used_transform_style(&self) -> computed_values::transform_style::T { + use crate::computed_values::transform_style::T as TransformStyle; + + let box_ = self.get_box(); + + if self.overrides_transform_style() { + TransformStyle::Flat + } else { + // Return the computed value if not overridden by the above exceptions + box_.transform_style + } + } + + /// Whether given this transform value, the compositor would require a + /// layer. + pub fn transform_requires_layer(&self) -> bool { + use crate::values::generics::transform::TransformOperation; + // Check if the transform matrix is 2D or 3D + for transform in &*self.get_box().transform.0 { + match *transform { + TransformOperation::Perspective(..) => { + return true; + } + TransformOperation::Matrix3D(m) => { + // See http://dev.w3.org/csswg/css-transforms/#2d-matrix + if m.m31 != 0.0 || m.m32 != 0.0 || + m.m13 != 0.0 || m.m23 != 0.0 || + m.m43 != 0.0 || m.m14 != 0.0 || + m.m24 != 0.0 || m.m34 != 0.0 || + m.m33 != 1.0 || m.m44 != 1.0 { + return true; + } + } + TransformOperation::Translate3D(_, _, z) | + TransformOperation::TranslateZ(z) => { + if z.px() != 0. { + return true; + } + } + _ => {} + } + } + + // Neither perspective nor transform present + false + } +} + +/// A reference to a style struct of the parent, or our own style struct. +pub enum StyleStructRef<'a, T: 'static> { + /// A borrowed struct from the parent, for example, for inheriting style. + Borrowed(&'a T), + /// An owned struct, that we've already mutated. + Owned(UniqueArc<T>), + /// Temporarily vacated, will panic if accessed + Vacated, +} + +impl<'a, T: 'a> StyleStructRef<'a, T> +where + T: Clone, +{ + /// Ensure a mutable reference of this value exists, either cloning the + /// borrowed value, or returning the owned one. + pub fn mutate(&mut self) -> &mut T { + if let StyleStructRef::Borrowed(v) = *self { + *self = StyleStructRef::Owned(UniqueArc::new(v.clone())); + } + + match *self { + StyleStructRef::Owned(ref mut v) => v, + StyleStructRef::Borrowed(..) => unreachable!(), + StyleStructRef::Vacated => panic!("Accessed vacated style struct") + } + } + + /// Whether this is pointer-equal to the struct we're going to copy the + /// value from. + /// + /// This is used to avoid allocations when people write stuff like `font: + /// inherit` or such `all: initial`. + #[inline] + pub fn ptr_eq(&self, struct_to_copy_from: &T) -> bool { + match *self { + StyleStructRef::Owned(..) => false, + StyleStructRef::Borrowed(s) => { + s as *const T == struct_to_copy_from as *const T + } + StyleStructRef::Vacated => panic!("Accessed vacated style struct") + } + } + + /// Extract a unique Arc from this struct, vacating it. + /// + /// The vacated state is a transient one, please put the Arc back + /// when done via `put()`. This function is to be used to separate + /// the struct being mutated from the computed context + pub fn take(&mut self) -> UniqueArc<T> { + use std::mem::replace; + let inner = replace(self, StyleStructRef::Vacated); + + match inner { + StyleStructRef::Owned(arc) => arc, + StyleStructRef::Borrowed(s) => UniqueArc::new(s.clone()), + StyleStructRef::Vacated => panic!("Accessed vacated style struct"), + } + } + + /// Replace vacated ref with an arc + pub fn put(&mut self, arc: UniqueArc<T>) { + debug_assert!(matches!(*self, StyleStructRef::Vacated)); + *self = StyleStructRef::Owned(arc); + } + + /// Get a mutable reference to the owned struct, or `None` if the struct + /// hasn't been mutated. + pub fn get_if_mutated(&mut self) -> Option<<&mut T> { + match *self { + StyleStructRef::Owned(ref mut v) => Some(v), + StyleStructRef::Borrowed(..) => None, + StyleStructRef::Vacated => panic!("Accessed vacated style struct") + } + } + + /// Returns an `Arc` to the internal struct, constructing one if + /// appropriate. + pub fn build(self) -> Arc<T> { + match self { + StyleStructRef::Owned(v) => v.shareable(), + // SAFETY: We know all style structs are arc-allocated. + StyleStructRef::Borrowed(v) => unsafe { Arc::from_raw_addrefed(v) }, + StyleStructRef::Vacated => panic!("Accessed vacated style struct") + } + } +} + +impl<'a, T: 'a> ops::Deref for StyleStructRef<'a, T> { + type Target = T; + + fn deref(&self) -> &T { + match *self { + StyleStructRef::Owned(ref v) => &**v, + StyleStructRef::Borrowed(v) => v, + StyleStructRef::Vacated => panic!("Accessed vacated style struct") + } + } +} + +/// A type used to compute a struct with minimal overhead. +/// +/// This allows holding references to the parent/default computed values without +/// actually cloning them, until we either build the style, or mutate the +/// inherited value. +pub struct StyleBuilder<'a> { + /// The device we're using to compute style. + /// + /// This provides access to viewport unit ratios, etc. + pub device: &'a Device, + + /// The style we're inheriting from. + /// + /// This is effectively + /// `parent_style.unwrap_or(device.default_computed_values())`. + inherited_style: &'a ComputedValues, + + /// The style we're inheriting from for properties that don't inherit from + /// ::first-line. This is the same as inherited_style, unless + /// inherited_style is a ::first-line style. + inherited_style_ignoring_first_line: &'a ComputedValues, + + /// The style we're getting reset structs from. + reset_style: &'a ComputedValues, + + /// The rule node representing the ordered list of rules matched for this + /// node. + pub rules: Option<StrongRuleNode>, + + custom_properties: Option<Arc<crate::custom_properties::CustomPropertiesMap>>, + + /// The pseudo-element this style will represent. + pub pseudo: Option<<&'a PseudoElement>, + + /// Whether we have mutated any reset structs since the the last time + /// `clear_modified_reset` was called. This is used to tell whether the + /// `StyleAdjuster` did any work. + modified_reset: bool, + + /// Whether this is the style for the root element. + pub is_root_element: bool, + + /// The writing mode flags. + /// + /// TODO(emilio): Make private. + pub writing_mode: WritingMode, + + /// Flags for the computed value. + pub flags: Cell<ComputedValueFlags>, + + /// The element's style if visited, only computed if there's a relevant link + /// for this element. A element's "relevant link" is the element being + /// matched if it is a link or the nearest ancestor link. + pub visited_style: Option<Arc<ComputedValues>>, + % for style_struct in data.active_style_structs(): + ${style_struct.ident}: StyleStructRef<'a, style_structs::${style_struct.name}>, + % endfor +} + +impl<'a> StyleBuilder<'a> { + /// Trivially construct a `StyleBuilder`. + pub(super) fn new( + device: &'a Device, + parent_style: Option<<&'a ComputedValues>, + parent_style_ignoring_first_line: Option<<&'a ComputedValues>, + pseudo: Option<<&'a PseudoElement>, + rules: Option<StrongRuleNode>, + custom_properties: Option<Arc<crate::custom_properties::CustomPropertiesMap>>, + is_root_element: bool, + ) -> Self { + debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some()); + #[cfg(feature = "gecko")] + debug_assert!(parent_style.is_none() || + std::ptr::eq(parent_style.unwrap(), + parent_style_ignoring_first_line.unwrap()) || + parent_style.unwrap().is_first_line_style()); + let reset_style = device.default_computed_values(); + let inherited_style = parent_style.unwrap_or(reset_style); + let inherited_style_ignoring_first_line = parent_style_ignoring_first_line.unwrap_or(reset_style); + + let flags = inherited_style.flags.inherited(); + + StyleBuilder { + device, + inherited_style, + inherited_style_ignoring_first_line, + reset_style, + pseudo, + rules, + modified_reset: false, + is_root_element, + custom_properties, + writing_mode: inherited_style.writing_mode, + flags: Cell::new(flags), + visited_style: None, + % for style_struct in data.active_style_structs(): + % if style_struct.inherited: + ${style_struct.ident}: StyleStructRef::Borrowed(inherited_style.get_${style_struct.name_lower}()), + % else: + ${style_struct.ident}: StyleStructRef::Borrowed(reset_style.get_${style_struct.name_lower}()), + % endif + % endfor + } + } + + /// NOTE(emilio): This is done so we can compute relative units with respect + /// to the parent style, but all the early properties / writing-mode / etc + /// are already set to the right ones on the kid. + /// + /// Do _not_ actually call this to construct a style, this should mostly be + /// used for animations. + pub fn for_animation( + device: &'a Device, + style_to_derive_from: &'a ComputedValues, + parent_style: Option<<&'a ComputedValues>, + ) -> Self { + let reset_style = device.default_computed_values(); + let inherited_style = parent_style.unwrap_or(reset_style); + #[cfg(feature = "gecko")] + debug_assert!(parent_style.is_none() || + !parent_style.unwrap().is_first_line_style()); + StyleBuilder { + device, + inherited_style, + // None of our callers pass in ::first-line parent styles. + inherited_style_ignoring_first_line: inherited_style, + reset_style, + pseudo: None, + modified_reset: false, + is_root_element: false, + rules: None, + custom_properties: style_to_derive_from.custom_properties().cloned(), + writing_mode: style_to_derive_from.writing_mode, + flags: Cell::new(style_to_derive_from.flags), + visited_style: None, + % for style_struct in data.active_style_structs(): + ${style_struct.ident}: StyleStructRef::Borrowed( + style_to_derive_from.get_${style_struct.name_lower}() + ), + % endfor + } + } + + /// Copy the reset properties from `style`. + pub fn copy_reset_from(&mut self, style: &'a ComputedValues) { + % for style_struct in data.active_style_structs(): + % if not style_struct.inherited: + self.${style_struct.ident} = + StyleStructRef::Borrowed(style.get_${style_struct.name_lower}()); + % endif + % endfor + } + + % for property in data.longhands: + % if not property.style_struct.inherited: + /// Inherit `${property.ident}` from our parent style. + #[allow(non_snake_case)] + pub fn inherit_${property.ident}(&mut self) { + let inherited_struct = + self.inherited_style_ignoring_first_line + .get_${property.style_struct.name_lower}(); + + self.modified_reset = true; + self.add_flags(ComputedValueFlags::INHERITS_RESET_STYLE); + + % if property.ident == "content": + self.add_flags(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE); + % endif + + % if property.ident == "display": + self.add_flags(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE); + % endif + + if self.${property.style_struct.ident}.ptr_eq(inherited_struct) { + return; + } + + self.${property.style_struct.ident}.mutate() + .copy_${property.ident}_from( + inherited_struct, + % if property.logical: + self.writing_mode, + % endif + ); + } + % else: + /// Reset `${property.ident}` to the initial value. + #[allow(non_snake_case)] + pub fn reset_${property.ident}(&mut self) { + let reset_struct = + self.reset_style.get_${property.style_struct.name_lower}(); + + if self.${property.style_struct.ident}.ptr_eq(reset_struct) { + return; + } + + self.${property.style_struct.ident}.mutate() + .reset_${property.ident}( + reset_struct, + % if property.logical: + self.writing_mode, + % endif + ); + } + % endif + + % if not property.is_vector or property.simple_vector_bindings or engine in ["servo-2013", "servo-2020"]: + /// Set the `${property.ident}` to the computed value `value`. + #[allow(non_snake_case)] + pub fn set_${property.ident}( + &mut self, + value: longhands::${property.ident}::computed_value::T + ) { + % if not property.style_struct.inherited: + self.modified_reset = true; + % endif + + self.${property.style_struct.ident}.mutate() + .set_${property.ident}( + value, + % if property.logical: + self.writing_mode, + % endif + ); + } + % endif + % endfor + <% del property %> + + /// Inherits style from the parent element, accounting for the default + /// computed values that need to be provided as well. + pub fn for_inheritance( + device: &'a Device, + parent: Option<<&'a ComputedValues>, + pseudo: Option<<&'a PseudoElement>, + ) -> Self { + // Rebuild the visited style from the parent, ensuring that it will also + // not have rules. This matches the unvisited style that will be + // produced by this builder. This assumes that the caller doesn't need + // to adjust or process visited style, so we can just build visited + // style here for simplicity. + let visited_style = parent.and_then(|parent| { + parent.visited_style().map(|style| { + Self::for_inheritance( + device, + Some(style), + pseudo, + ).build() + }) + }); + let mut ret = Self::new( + device, + parent, + parent, + pseudo, + /* rules = */ None, + parent.and_then(|p| p.custom_properties().cloned()), + /* is_root_element = */ false, + ); + ret.visited_style = visited_style; + ret + } + + /// Returns whether we have a visited style. + pub fn has_visited_style(&self) -> bool { + self.visited_style.is_some() + } + + /// Returns whether we're a pseudo-elements style. + pub fn is_pseudo_element(&self) -> bool { + self.pseudo.map_or(false, |p| !p.is_anon_box()) + } + + /// Returns the style we're getting reset properties from. + pub fn default_style(&self) -> &'a ComputedValues { + self.reset_style + } + + % for style_struct in data.active_style_structs(): + /// Gets an immutable view of the current `${style_struct.name}` style. + pub fn get_${style_struct.name_lower}(&self) -> &style_structs::${style_struct.name} { + &self.${style_struct.ident} + } + + /// Gets a mutable view of the current `${style_struct.name}` style. + pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} { + % if not style_struct.inherited: + self.modified_reset = true; + % endif + self.${style_struct.ident}.mutate() + } + + /// Gets a mutable view of the current `${style_struct.name}` style. + pub fn take_${style_struct.name_lower}(&mut self) -> UniqueArc<style_structs::${style_struct.name}> { + % if not style_struct.inherited: + self.modified_reset = true; + % endif + self.${style_struct.ident}.take() + } + + /// Gets a mutable view of the current `${style_struct.name}` style. + pub fn put_${style_struct.name_lower}(&mut self, s: UniqueArc<style_structs::${style_struct.name}>) { + self.${style_struct.ident}.put(s) + } + + /// Gets a mutable view of the current `${style_struct.name}` style, + /// only if it's been mutated before. + pub fn get_${style_struct.name_lower}_if_mutated(&mut self) + -> Option<<&mut style_structs::${style_struct.name}> { + self.${style_struct.ident}.get_if_mutated() + } + + /// Reset the current `${style_struct.name}` style to its default value. + pub fn reset_${style_struct.name_lower}_struct(&mut self) { + self.${style_struct.ident} = + StyleStructRef::Borrowed(self.reset_style.get_${style_struct.name_lower}()); + } + % endfor + <% del style_struct %> + + /// Returns whether this computed style represents a floated object. + pub fn is_floating(&self) -> bool { + self.get_box().clone_float().is_floating() + } + + /// Returns whether this computed style represents an absolutely-positioned + /// object. + pub fn is_absolutely_positioned(&self) -> bool { + self.get_box().clone_position().is_absolutely_positioned() + } + + /// Whether this style has a top-layer style. + #[cfg(feature = "servo")] + pub fn in_top_layer(&self) -> bool { + matches!(self.get_box().clone__servo_top_layer(), + longhands::_servo_top_layer::computed_value::T::Top) + } + + /// Whether this style has a top-layer style. + #[cfg(feature = "gecko")] + pub fn in_top_layer(&self) -> bool { + matches!(self.get_box().clone__moz_top_layer(), + longhands::_moz_top_layer::computed_value::T::Top) + } + + /// Clears the "have any reset structs been modified" flag. + pub fn clear_modified_reset(&mut self) { + self.modified_reset = false; + } + + /// Returns whether we have mutated any reset structs since the the last + /// time `clear_modified_reset` was called. + pub fn modified_reset(&self) -> bool { + self.modified_reset + } + + /// Return the current flags. + #[inline] + pub fn flags(&self) -> ComputedValueFlags { + self.flags.get() + } + + /// Add a flag to the current builder. + #[inline] + pub fn add_flags(&self, flag: ComputedValueFlags) { + let flags = self.flags() | flag; + self.flags.set(flags); + } + + /// Removes a flag to the current builder. + #[inline] + pub fn remove_flags(&self, flag: ComputedValueFlags) { + let flags = self.flags() & !flag; + self.flags.set(flags); + } + + /// Turns this `StyleBuilder` into a proper `ComputedValues` instance. + pub fn build(self) -> Arc<ComputedValues> { + ComputedValues::new( + self.pseudo, + self.custom_properties, + self.writing_mode, + self.flags.get(), + self.rules, + self.visited_style, + % for style_struct in data.active_style_structs(): + self.${style_struct.ident}.build(), + % endfor + ) + } + + /// Get the custom properties map if necessary. + pub fn custom_properties(&self) -> Option<<&Arc<crate::custom_properties::CustomPropertiesMap>> { + self.custom_properties.as_ref() + } + + /// Access to various information about our inherited styles. We don't + /// expose an inherited ComputedValues directly, because in the + /// ::first-line case some of the inherited information needs to come from + /// one ComputedValues instance and some from a different one. + + /// Inherited writing-mode. + pub fn inherited_writing_mode(&self) -> &WritingMode { + &self.inherited_style.writing_mode + } + + /// The computed value flags of our parent. + #[inline] + pub fn get_parent_flags(&self) -> ComputedValueFlags { + self.inherited_style.flags + } + + /// And access to inherited style structs. + % for style_struct in data.active_style_structs(): + /// Gets our inherited `${style_struct.name}`. We don't name these + /// accessors `inherited_${style_struct.name_lower}` because we already + /// have things like "box" vs "inherited_box" as struct names. Do the + /// next-best thing and call them `parent_${style_struct.name_lower}` + /// instead. + pub fn get_parent_${style_struct.name_lower}(&self) -> &style_structs::${style_struct.name} { + % if style_struct.inherited: + self.inherited_style.get_${style_struct.name_lower}() + % else: + self.inherited_style_ignoring_first_line.get_${style_struct.name_lower}() + % endif + } + % endfor +} + +#[cfg(feature = "servo")] +pub use self::lazy_static_module::INITIAL_SERVO_VALUES; + +// Use a module to work around #[cfg] on lazy_static! not being applied to every generated item. +#[cfg(feature = "servo")] +#[allow(missing_docs)] +mod lazy_static_module { + use crate::logical_geometry::WritingMode; + use crate::computed_value_flags::ComputedValueFlags; + use servo_arc::Arc; + use super::{ComputedValues, ComputedValuesInner, longhands, style_structs}; + + lazy_static! { + /// The initial values for all style structs as defined by the specification. + pub static ref INITIAL_SERVO_VALUES: ComputedValues = ComputedValues { + inner: ComputedValuesInner { + % for style_struct in data.active_style_structs(): + ${style_struct.ident}: Arc::new(style_structs::${style_struct.name} { + % for longhand in style_struct.longhands: + % if not longhand.logical: + ${longhand.ident}: longhands::${longhand.ident}::get_initial_value(), + % endif + % endfor + % if style_struct.name == "InheritedText": + text_decorations_in_effect: + crate::values::computed::text::TextDecorationsInEffect::default(), + % endif + % if style_struct.name == "Font": + hash: 0, + % endif + % if style_struct.name == "Box": + original_display: longhands::display::get_initial_value(), + % endif + }), + % endfor + custom_properties: None, + writing_mode: WritingMode::empty(), + rules: None, + visited_style: None, + flags: ComputedValueFlags::empty(), + }, + pseudo: None, + }; + } +} + +/// A per-longhand function that performs the CSS cascade for that longhand. +pub type CascadePropertyFn = + extern "Rust" fn( + declaration: &PropertyDeclaration, + context: &mut computed::Context, + ); + +/// A per-longhand array of functions to perform the CSS cascade on each of +/// them, effectively doing virtual dispatch. +pub static CASCADE_PROPERTY: [CascadePropertyFn; ${len(data.longhands)}] = [ + % for property in data.longhands: + longhands::${property.ident}::cascade_property, + % endfor +]; + + +/// See StyleAdjuster::adjust_for_border_width. +pub fn adjust_border_width(style: &mut StyleBuilder) { + % for side in ["top", "right", "bottom", "left"]: + // Like calling to_computed_value, which wouldn't type check. + if style.get_border().clone_border_${side}_style().none_or_hidden() && + style.get_border().border_${side}_has_nonzero_width() { + style.set_border_${side}_width(Au(0)); + } + % endfor +} + +/// An identifier for a given alias property. +#[derive(Clone, Copy, Eq, PartialEq, MallocSizeOf)] +#[repr(u16)] +pub enum AliasId { + % for i, property in enumerate(data.all_aliases()): + /// ${property.name} + ${property.camel_case} = ${i}, + % endfor +} + +#[derive(Clone, Copy, Eq, PartialEq)] +enum AliasedPropertyId { + #[allow(dead_code)] // Servo doesn't have aliased shorthands. + Shorthand(ShorthandId), + Longhand(LonghandId), +} + +impl fmt::Debug for AliasId { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let name = NonCustomPropertyId::from(*self).name(); + formatter.write_str(name) + } +} + +impl AliasId { + /// Returns the property we're aliasing, as a longhand or a shorthand. + #[inline] + fn aliased_property(self) -> AliasedPropertyId { + static MAP: [AliasedPropertyId; ${len(data.all_aliases())}] = [ + % for alias in data.all_aliases(): + % if alias.original.type() == "longhand": + AliasedPropertyId::Longhand(LonghandId::${alias.original.camel_case}), + % else: + <% assert alias.original.type() == "shorthand" %> + AliasedPropertyId::Shorthand(ShorthandId::${alias.original.camel_case}), + % endif + % endfor + ]; + MAP[self as usize] + } +} + +/// Call the given macro with tokens like this for each longhand and shorthand properties +/// that is enabled in content: +/// +/// ``` +/// [CamelCaseName, SetCamelCaseName, PropertyId::Longhand(LonghandId::CamelCaseName)], +/// ``` +/// +/// NOTE(emilio): Callers are responsible to deal with prefs. +#[macro_export] +macro_rules! css_properties_accessors { + ($macro_name: ident) => { + $macro_name! { + % for kind, props in [("Longhand", data.longhands), ("Shorthand", data.shorthands)]: + % for property in props: + % if property.enabled_in_content(): + % for prop in [property] + property.aliases: + % if '-' in prop.name: + [${prop.ident.capitalize()}, Set${prop.ident.capitalize()}, + PropertyId::${kind}(${kind}Id::${property.camel_case})], + % endif + [${prop.camel_case}, Set${prop.camel_case}, + PropertyId::${kind}(${kind}Id::${property.camel_case})], + % endfor + % endif + % endfor + % endfor + } + } +} + +/// Call the given macro with tokens like this for each longhand properties: +/// +/// ``` +/// { snake_case_ident } +/// ``` +#[macro_export] +macro_rules! longhand_properties_idents { + ($macro_name: ident) => { + $macro_name! { + % for property in data.longhands: + { ${property.ident} } + % endfor + } + } +} + +// Large pages generate tens of thousands of ComputedValues. +size_of_test!(ComputedValues, 232); +// FFI relies on this. +size_of_test!(Option<Arc<ComputedValues>>, 8); + +// There are two reasons for this test to fail: +// +// * Your changes made a specified value type for a given property go +// over the threshold. In that case, you should try to shrink it again +// or, if not possible, mark the property as boxed in the property +// definition. +// +// * Your changes made a specified value type smaller, so that it no +// longer needs to be boxed. In this case you just need to remove +// boxed=True from the property definition. Nice job! +#[cfg(target_pointer_width = "64")] +#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/96952 +const BOX_THRESHOLD: usize = 24; +% for longhand in data.longhands: +#[cfg(target_pointer_width = "64")] +% if longhand.boxed: +const_assert!(std::mem::size_of::<longhands::${longhand.ident}::SpecifiedValue>() > BOX_THRESHOLD); +% else: +const_assert!(std::mem::size_of::<longhands::${longhand.ident}::SpecifiedValue>() <= BOX_THRESHOLD); +% endif +% endfor + +% if engine in ["servo-2013", "servo-2020"]: +% for effect_name in ["repaint", "reflow_out_of_flow", "reflow", "rebuild_and_reflow_inline", "rebuild_and_reflow"]: + macro_rules! restyle_damage_${effect_name} { + ($old: ident, $new: ident, $damage: ident, [ $($effect:expr),* ]) => ({ + if + % for style_struct in data.active_style_structs(): + % for longhand in style_struct.longhands: + % if effect_name in longhand.servo_restyle_damage.split() and not longhand.logical: + $old.get_${style_struct.name_lower}().${longhand.ident} != + $new.get_${style_struct.name_lower}().${longhand.ident} || + % endif + % endfor + % endfor + + false { + $damage.insert($($effect)|*); + true + } else { + false + } + }) + } +% endfor +% endif |