/* 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 http://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" /> #[cfg(feature = "servo")] use app_units::Au; use dom::TElement; use custom_properties::CustomPropertiesBuilder; use servo_arc::{Arc, UniqueArc}; use smallbitvec::SmallBitVec; use std::borrow::Cow; use std::{ops, ptr}; use std::cell::RefCell; use std::fmt::{self, Write}; use std::mem::{self, ManuallyDrop}; #[cfg(feature = "servo")] use cssparser::RGBA; use cssparser::{CowRcStr, Parser, TokenSerializationType, serialize_identifier}; use cssparser::ParserInput; #[cfg(feature = "servo")] use euclid::SideOffsets2D; use context::QuirksMode; use font_metrics::FontMetricsProvider; #[cfg(feature = "gecko")] use gecko_bindings::bindings; #[cfg(feature = "gecko")] use gecko_bindings::structs::{self, nsCSSPropertyID}; #[cfg(feature = "servo")] use logical_geometry::LogicalMargin; #[cfg(feature = "servo")] use computed_values; use logical_geometry::WritingMode; #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use media_queries::Device; use parser::ParserContext; #[cfg(feature = "gecko")] use properties::longhands::system_font::SystemFont; use rule_cache::{RuleCache, RuleCacheConditions}; use selector_parser::PseudoElement; use selectors::parser::SelectorParseErrorKind; #[cfg(feature = "servo")] use servo_config::prefs::PREFS; use shared_lock::StylesheetGuards; use style_traits::{CssWriter, KeywordsCollectFn, ParseError, ParsingMode}; use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; use stylesheets::{CssRuleType, Origin, UrlExtraData}; #[cfg(feature = "servo")] use values::Either; use values::generics::text::LineHeight; use values::computed; use values::computed::NonNegativeLength; use rule_tree::{CascadeLevel, StrongRuleNode}; use self::computed_value_flags::*; use str::{CssString, CssStringBorrow, CssStringWriter}; use style_adjuster::StyleAdjuster; pub use self::declaration_block::*; <%! from data import Method, Keyword, to_rust_ident, to_camel_case, SYSTEM_FONT_LONGHANDS import os.path %> #[path="${repr(os.path.join(os.path.dirname(__file__), 'computed_value_flags.rs'))[1:-1]}"] pub mod computed_value_flags; #[path="${repr(os.path.join(os.path.dirname(__file__), 'declaration_block.rs'))[1:-1]}"] pub mod declaration_block; /// Conversion with fewer impls than From/Into pub trait MaybeBoxed { /// Convert fn maybe_boxed(self) -> Out; } impl MaybeBoxed for T { #[inline] fn maybe_boxed(self) -> T { self } } impl MaybeBoxed> for T { #[inline] fn maybe_boxed(self) -> Box { 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 { <%include file="/longhand/background.mako.rs" /> <%include file="/longhand/border.mako.rs" /> <%include file="/longhand/box.mako.rs" /> <%include file="/longhand/color.mako.rs" /> <%include file="/longhand/column.mako.rs" /> <%include file="/longhand/counters.mako.rs" /> <%include file="/longhand/effects.mako.rs" /> <%include file="/longhand/font.mako.rs" /> <%include file="/longhand/inherited_box.mako.rs" /> <%include file="/longhand/inherited_table.mako.rs" /> <%include file="/longhand/inherited_text.mako.rs" /> <%include file="/longhand/inherited_ui.mako.rs" /> <%include file="/longhand/list.mako.rs" /> <%include file="/longhand/margin.mako.rs" /> <%include file="/longhand/outline.mako.rs" /> <%include file="/longhand/padding.mako.rs" /> <%include file="/longhand/position.mako.rs" /> <%include file="/longhand/table.mako.rs" /> <%include file="/longhand/text.mako.rs" /> <%include file="/longhand/ui.mako.rs" /> <%include file="/longhand/inherited_svg.mako.rs" /> <%include file="/longhand/svg.mako.rs" /> <%include file="/longhand/xul.mako.rs" /> } 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 parser::{Parse, ParserContext}; use style_traits::{ParseError, StyleParseErrorKind}; use values::specified; <%include file="/shorthand/serialize.mako.rs" /> <%include file="/shorthand/background.mako.rs" /> <%include file="/shorthand/border.mako.rs" /> <%include file="/shorthand/box.mako.rs" /> <%include file="/shorthand/column.mako.rs" /> <%include file="/shorthand/font.mako.rs" /> <%include file="/shorthand/inherited_text.mako.rs" /> <%include file="/shorthand/list.mako.rs" /> <%include file="/shorthand/margin.mako.rs" /> <%include file="/shorthand/mask.mako.rs" /> <%include file="/shorthand/outline.mako.rs" /> <%include file="/shorthand/padding.mako.rs" /> <%include file="/shorthand/position.mako.rs" /> <%include file="/shorthand/inherited_svg.mako.rs" /> <%include file="/shorthand/text.mako.rs" /> // We don't defined 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(product): continue; if p.logical: logical_longhands.append(p.name) else: other_longhands.append(p.name) data.declare_shorthand( "all", logical_longhands + other_longhands, gecko_pref="layout.css.all-shorthand.enabled", 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 = [ { "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.append(v) groups[v["type"]] = [v] %> /// Servo's representation for a property declaration. #[repr(u16)] pub enum PropertyDeclaration { % for variant in variants: /// ${variant["doc"]} ${variant["name"]}(${variant["type"]}), % endfor } #[repr(C)] struct PropertyDeclarationVariantRepr { 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::uninitialized(); ptr::write( &mut out as *mut _ as *mut CopyVariants, *(self as *const _ as *const CopyVariants), ); return out; } } 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 = ManuallyDrop::new(mem::uninitialized()); ptr::write( &mut out as *mut _ as *mut PropertyDeclarationVariantRepr<${ty}>, PropertyDeclarationVariantRepr { tag: *(self as *const _ as *const u16), value: value.clone(), }, ); ManuallyDrop::into_inner(out) } } % 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 } } } } #[cfg(feature = "gecko")] 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 { /// 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 } } } /// 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); impl NonCustomPropertyId { #[cfg(feature = "gecko")] fn to_nscsspropertyid(self) -> nsCSSPropertyID { static MAP: [nsCSSPropertyID; ${len(data.longhands) + len(data.shorthands) + len(data.all_aliases())}] = [ % for property in data.longhands + data.shorthands + data.all_aliases(): ${property.nscsspropertyid()}, % endfor ]; MAP[self.0] } #[inline] fn enabled_for_all_content(self) -> bool { ${static_non_custom_property_id_set( "EXPERIMENTAL", lambda p: p.experimental(product) )} ${static_non_custom_property_id_set( "ALWAYS_ENABLED", lambda p: (not p.experimental(product)) and p.enabled_in_content() )} let passes_pref_check = || { % if product == "servo": static PREF_NAME: [Option< &str>; ${len(data.longhands) + len(data.shorthands)}] = [ % for property in data.longhands + data.shorthands: % if property.servo_pref: Some("${property.servo_pref}"), % else: None, % endif % endfor ]; let pref = match PREF_NAME[self.0] { None => return true, Some(pref) => pref, }; PREFS.get(pref).as_boolean().unwrap_or(false) % else: unsafe { structs::nsCSSProps_gPropertyEnabled[self.to_nscsspropertyid() as usize] } % endif }; if ALWAYS_ENABLED.contains(self) { return true } if EXPERIMENTAL.contains(self) && passes_pref_check() { return true } false } fn allowed_in(self, context: &ParserContext) -> bool { debug_assert!( matches!( context.rule_type(), CssRuleType::Keyframe | CssRuleType::Page | CssRuleType::Style ), "Declarations are only expected inside a keyframe, page, or style rule." ); ${static_non_custom_property_id_set( "DISALLOWED_IN_KEYFRAME_BLOCK", lambda p: not p.allowed_in_keyframe_block )} ${static_non_custom_property_id_set( "DISALLOWED_IN_PAGE_RULE", lambda p: not p.allowed_in_page_rule )} match context.rule_type() { CssRuleType::Keyframe if DISALLOWED_IN_KEYFRAME_BLOCK.contains(self) => { return false; } CssRuleType::Page if DISALLOWED_IN_PAGE_RULE.contains(self) => { return false; } _ => {} } // 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: ::SUPPORTED_TYPES, % endif % endfor ]; SUPPORTED_TYPES[self.0] } /// See PropertyId::collect_property_completion_keywords. fn collect_property_completion_keywords(&self, f: 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": &|_f| {}, // 'all' accepts no value other than CSS-wide keywords % else: &:: collect_completion_keywords, % endif % endfor ]; COLLECT_FUNCTIONS[self.0](f); } } impl From for NonCustomPropertyId { fn from(id: LonghandId) -> Self { NonCustomPropertyId(id as usize) } } impl From for NonCustomPropertyId { fn from(id: ShorthandId) -> Self { NonCustomPropertyId((id as usize) + ${len(data.longhands)}) } } impl From for NonCustomPropertyId { fn from(id: AliasId) -> Self { NonCustomPropertyId(id as usize + ${len(data.longhands) + len(data.shorthands)}) } } /// A set of all properties #[derive(Clone, PartialEq)] pub struct NonCustomPropertyIdSet { storage: [u32; (${len(data.longhands) + len(data.shorthands) + len(data.all_aliases())} - 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] * ((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[i / 32] |= 1 << (i % 32) %> storage: [${", ".join("0x%x" % word for word in storage)}] }; <%def name="static_longhand_id_set(name, is_member)"> static ${name}: LonghandIdSet = LonghandIdSet { <% storage = [0] * ((len(data.longhands) - 1 + 32) / 32) for i, property in enumerate(data.longhands): if is_member(property): storage[i / 32] |= 1 << (i % 32) %> storage: [${", ".join("0x%x" % word for word in storage)}] }; /// A set of longhand properties #[derive(Clone, Debug, MallocSizeOf, PartialEq)] pub struct LonghandIdSet { storage: [u32; (${len(data.longhands)} - 1 + 32) / 32] } /// 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 { 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); } } } } impl LonghandIdSet { /// 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 } /// 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 { ${static_longhand_id_set("RESET", lambda p: not p.style_struct.inherited)} self.contains_any(&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)] pub enum CSSWideKeyword { /// The `initial` keyword. Initial, /// The `inherit` keyword. Inherit, /// The `unset` keyword. Unset, } impl CSSWideKeyword { fn to_str(&self) -> &'static str { match *self { CSSWideKeyword::Initial => "initial", CSSWideKeyword::Inherit => "inherit", CSSWideKeyword::Unset => "unset", } } /// Takes the result of cssparser::Parser::expect_ident() and converts it /// to a CSSWideKeyword. pub fn from_ident<'i>(ident: &str) -> Option { match_ignore_ascii_case! { ident, // If modifying this set of keyword, also update values::CustomIdent::from_ident "initial" => Some(CSSWideKeyword::Initial), "inherit" => Some(CSSWideKeyword::Inherit), "unset" => Some(CSSWideKeyword::Unset), _ => None } } } impl CSSWideKeyword { fn parse(input: &mut Parser) -> Result { let ident = input.expect_ident().map_err(|_| ())?.clone(); input.expect_exhausted().map_err(|_| ())?; CSSWideKeyword::from_ident(&ident).ok_or(()) } } bitflags! { /// A set of flags for properties. pub struct PropertyFlags: u8 { /// This property requires a stacking context. const CREATES_STACKING_CONTEXT = 1 << 0; /// This property has values that can establish a containing block for /// fixed positioned and absolutely positioned elements. const FIXPOS_CB = 1 << 1; /// This property has values that can establish a containing block for /// absolutely positioned elements. const ABSPOS_CB = 1 << 2; /// This longhand property applies to ::first-letter. const APPLIES_TO_FIRST_LETTER = 1 << 3; /// This longhand property applies to ::first-line. const APPLIES_TO_FIRST_LINE = 1 << 4; /// This longhand property applies to ::placeholder. const APPLIES_TO_PLACEHOLDER = 1 << 5; /* 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's getComputedStyle implementation requires layout /// to be flushed. const GETCS_NEEDS_LAYOUT_FLUSH = 0; /// This property can be animated on the compositor. const CAN_ANIMATE_ON_COMPOSITOR = 0; } } /// An identifier for a given longhand property. #[derive(Clone, Copy, Eq, Hash, MallocSizeOf, PartialEq)] #[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(&self, dest: &mut CssWriter) -> 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. pub fn name(&self) -> &'static str { match *self { % for property in data.longhands: LonghandId::${property.camel_case} => "${property.name}", % endfor } } /// Returns whether the longhand property is inherited by default. pub fn inherited(&self) -> bool { ${static_longhand_id_set("INHERITED", lambda p: p.style_struct.inherited)} INHERITED.contains(*self) } fn shorthands(&self) -> NonCustomPropertyIterator { // 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. <% 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 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.itervalues(): shorthand_list.sort(cmp=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> { match *self { % for property in data.longhands: LonghandId::${property.camel_case} => { longhands::${property.ident}::parse_declared(context, input) } % endfor } } /// Returns whether this property is animatable. pub fn is_animatable(self) -> bool { match self { % for property in data.longhands: LonghandId::${property.camel_case} => { ${str(property.animatable).lower()} } % endfor } } /// Returns whether this property is animatable in a discrete way. pub fn is_discrete_animatable(self) -> bool { match self { % for property in data.longhands: LonghandId::${property.camel_case} => { ${str(property.animation_value_type == "discrete").lower()} } % endfor } } /// 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 { match PropertyId::from_nscsspropertyid(id) { Ok(PropertyId::Longhand(id)) | Ok(PropertyId::LonghandAlias(id, _)) => Ok(id), _ => Err(()), } } /// If this is a logical property, return the corresponding physical one in the given writing mode. /// Otherwise, return unchanged. pub fn to_physical(&self, wm: WritingMode) -> Self { match *self { % for property in data.longhands: % if property.logical: LonghandId::${property.camel_case} => { <%helpers:logical_setter_helper name="${property.name}"> <%def name="inner(physical_ident)"> LonghandId::${to_camel_case(physical_ident)} } % endif % endfor _ => *self } } /// Returns PropertyFlags for given longhand property. pub fn flags(&self) -> PropertyFlags { match *self { % for property in data.longhands: LonghandId::${property.camel_case} => % for flag in property.flags: PropertyFlags::${flag} | % endfor PropertyFlags::empty(), % endfor } } /// 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. fn is_visited_dependent(&self) -> bool { matches!(*self, % if product == "gecko": LonghandId::ColumnRuleColor | LonghandId::TextEmphasisColor | LonghandId::WebkitTextFillColor | LonghandId::WebkitTextStrokeColor | LonghandId::TextDecorationColor | LonghandId::Fill | LonghandId::Stroke | LonghandId::CaretColor | % endif LonghandId::Color | LonghandId::BackgroundColor | LonghandId::BorderTopColor | LonghandId::BorderRightColor | LonghandId::BorderBottomColor | LonghandId::BorderLeftColor | LonghandId::OutlineColor ) } /// Returns true if the property is one that is ignored when document /// colors are disabled. fn is_ignored_when_document_colors_disabled( &self, cascade_level: CascadeLevel, pseudo: Option<<&PseudoElement>, ) -> bool { let is_ua_or_user_rule = matches!( cascade_level, CascadeLevel::UANormal | CascadeLevel::UserNormal | CascadeLevel::UserImportant | CascadeLevel::UAImportant ); if is_ua_or_user_rule { return false; } let is_style_attribute = matches!( cascade_level, CascadeLevel::StyleAttributeNormal | CascadeLevel::StyleAttributeImportant ); // Don't override colors on pseudo-element's style attributes. The // background-color on ::-moz-color-swatch is an example. Those are set // as an author style (via the style attribute), but it's pretty // important for it to show up for obvious reasons :) if pseudo.is_some() && is_style_attribute { return false; } matches!(*self, ${" | ".join([("LonghandId::" + p.camel_case) for p in data.longhands if p.ignored_when_colors_disabled])} ) } /// The computed value of some properties depends on the (sometimes /// computed) value of *other* properties. /// /// So we classify properties into "early" and "other", such that the only /// dependencies can be from "other" to "early". /// /// Unfortunately, it’s not easy to check that this classification is /// correct. fn is_early_property(&self) -> bool { matches!(*self, % if product == 'gecko': // Needed to properly compute the writing mode, to resolve logical // properties, and similar stuff. In this block instead of along // `WritingMode` and `Direction` just for convenience, since it's // Gecko-only (for now at least). // // see WritingMode::new. LonghandId::TextOrientation | // Needed to properly compute the zoomed font-size. // // FIXME(emilio): This could probably just be a cascade flag like // IN_SVG_SUBTREE or such, and we could nuke this property. LonghandId::XTextZoom | // Needed to do font-size computation in a language-dependent way. LonghandId::XLang | // Needed for ruby to respect language-dependent min-font-size // preferences properly, see bug 1165538. LonghandId::MozMinFontSizeRatio | // Needed to do font-size for MathML. :( LonghandId::MozScriptLevel | % endif // Needed to compute font-relative lengths correctly. LonghandId::FontSize | LonghandId::FontFamily | // Needed to resolve currentcolor at computed value time properly. // // FIXME(emilio): All the properties should be moved to currentcolor // as a computed-value (and thus resolving it at used-value time). // // This would allow this property to go away from this list. LonghandId::Color | // FIXME(emilio): There's no reason for this afaict, nuke it. LonghandId::TextDecorationLine | // Needed to properly compute the writing mode, to resolve logical // properties, and similar stuff. LonghandId::WritingMode | LonghandId::Direction ) } /// Whether computed values of this property lossily convert any complex /// colors into RGBA colors. /// /// In Gecko, there are some properties still that compute currentcolor /// down to an RGBA color at computed value time, instead of as /// `StyleComplexColor`s. For these properties, we must return `false`, /// so that we correctly avoid caching style data in the rule tree. pub fn stores_complex_colors_lossily(&self) -> bool { % if product == "gecko": matches!(*self, % for property in data.longhands: % if property.predefined_type == "RGBAColor": LonghandId::${property.camel_case} | % endif % endfor LonghandId::BackgroundImage | LonghandId::BorderImageSource | LonghandId::BoxShadow | LonghandId::MaskImage | LonghandId::TextShadow ) % else: false % endif } } /// 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 { filter: bool, iter: ::std::slice::Iter<'static, Item>, } impl Iterator for NonCustomPropertyIterator where Item: 'static + Copy + Into, { type Item = Item; fn next(&mut self) -> Option { 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)] pub enum ShorthandId { % for property in data.shorthands: /// ${property.name} ${property.camel_case}, % endfor } impl ToCss for ShorthandId { #[inline] fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { dest.write_str(self.name()) } } impl ShorthandId { /// Get the name for this shorthand property. pub fn name(&self) -> &'static str { match *self { % for property in data.shorthands: ShorthandId::${property.camel_case} => "${property.name}", % endfor } } /// Converts from a ShorthandId to an adequate nsCSSPropertyID. #[cfg(feature = "gecko")] #[inline] pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { NonCustomPropertyId::from(self).to_nscsspropertyid() } /// Get the longhand ids that form this shorthand. pub fn longhands(&self) -> NonCustomPropertyIterator { % 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<'a, W, I>( &self, declarations: I, dest: &mut CssWriter, ) -> fmt::Result where W: Write, I: Iterator, { match *self { ShorthandId::All => { // 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. Err(fmt::Error) } % for property in data.shorthands_except_all(): ShorthandId::${property.camel_case} => { match shorthands::${property.ident}::LonghandsToSerialize::from_iter(declarations) { Ok(longhands) => longhands.to_css(dest), Err(_) => Err(fmt::Error) } }, % endfor } } /// Finds and returns an appendable value for the given declarations. /// /// Returns the optional appendable value. pub fn get_shorthand_appendable_value<'a, I>( self, declarations: I, ) -> Option> where I: IntoIterator, I::IntoIter: Clone, { let declarations = declarations.into_iter(); // Only cloning iterators (a few pointers each) not declarations. let mut declarations2 = declarations.clone(); let mut declarations3 = declarations.clone(); let first_declaration = declarations2.next()?; // https://drafts.csswg.org/css-variables/#variables-in-shorthands if let Some(css) = first_declaration.with_variables_from_shorthand(self) { if declarations2.all(|d| d.with_variables_from_shorthand(self) == Some(css)) { return Some(AppendableValue::Css { css: CssStringBorrow::from(css), with_variables: true, }); } return None; } // Check whether they are all the same CSS-wide keyword. if let Some(keyword) = first_declaration.get_css_wide_keyword() { if declarations2.all(|d| d.get_css_wide_keyword() == Some(keyword)) { return Some(AppendableValue::Css { css: CssStringBorrow::from(keyword.to_str()), with_variables: false, }); } return None; } // Check whether all declarations can be serialized as part of shorthand. if declarations3.all(|d| d.may_serialize_as_part_of_shorthand()) { return Some(AppendableValue::DeclarationsForShorthand(self, declarations)); } None } /// Returns PropertyFlags for given shorthand property. pub fn flags(&self) -> PropertyFlags { match *self { % for property in data.shorthands: ShorthandId::${property.camel_case} => % for flag in property.flags: PropertyFlags::${flag} | % endfor PropertyFlags::empty(), % endfor } } fn parse_into<'i, 't>( &self, declarations: &mut SourcePropertyDeclaration, context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<(), ParseError<'i>> { match *self { % for shorthand in data.shorthands_except_all(): ShorthandId::${shorthand.camel_case} => { shorthands::${shorthand.ident}::parse_into(declarations, context, input) } % endfor // 'all' accepts no value other than CSS-wide keywords ShorthandId::All => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } } /// Servo's representation of a declared value for a given `T`, which is the /// declared value for that property. #[derive(Clone, Debug, Eq, PartialEq)] pub enum DeclaredValue<'a, T: 'a> { /// A known specified value from the stylesheet. Value(&'a T), /// An unparsed value that contains `var()` functions. WithVariables(&'a Arc), /// An CSS-wide keyword. CSSWideKeyword(CSSWideKeyword), } /// A variant of DeclaredValue that owns its data. This separation exists so /// that PropertyDeclaration can avoid embedding a DeclaredValue (and its /// extra discriminant word) and synthesize dependent DeclaredValues for /// PropertyDeclaration instances as needed. #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Debug, Eq, PartialEq, ToCss)] pub enum DeclaredValueOwned { /// A known specified value from the stylesheet. Value(T), /// An unparsed value that contains `var()` functions. WithVariables( #[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")] Arc ), /// An CSS-wide keyword. CSSWideKeyword(CSSWideKeyword), } impl DeclaredValueOwned { /// Creates a dependent DeclaredValue from this DeclaredValueOwned. fn borrow(&self) -> DeclaredValue { match *self { DeclaredValueOwned::Value(ref v) => DeclaredValue::Value(v), DeclaredValueOwned::WithVariables(ref v) => DeclaredValue::WithVariables(v), DeclaredValueOwned::CSSWideKeyword(v) => DeclaredValue::CSSWideKeyword(v), } } } /// An unparsed property value that contains `var()` functions. #[derive(Debug, Eq, PartialEq)] 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, } impl ToCss for UnparsedValue { fn to_css(&self, dest: &mut CssWriter) -> 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(()) } } impl UnparsedValue { fn substitute_variables( &self, longhand_id: LonghandId, custom_properties: Option<<&Arc<::custom_properties::CustomPropertiesMap>>, quirks_mode: QuirksMode, ) -> PropertyDeclaration { ::custom_properties::substitute(&self.css, self.first_token_type, custom_properties) .ok() .and_then(|css| { // As of this writing, only the base URL is used for property // values. // // FIXME(emilio): These bits are slightly fishy. let context = ParserContext::new( Origin::Author, &self.url_data, Some(CssRuleType::Style), ParsingMode::DEFAULT, quirks_mode, ); let mut input = ParserInput::new(&css); Parser::new(&mut input).parse_entirely(|input| { match self.from_shorthand { None => longhand_id.parse_value(&context, input), Some(ShorthandId::All) => { // No need to parse the 'all' shorthand as anything other than a CSS-wide // keyword, after variable substitution. Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent("all".into()))) } % for shorthand in data.shorthands_except_all(): Some(ShorthandId::${shorthand.camel_case}) => { shorthands::${shorthand.ident}::parse_value(&context, input) .map(|longhands| { match longhand_id { % for property in shorthand.sub_properties: LonghandId::${property.camel_case} => { PropertyDeclaration::${property.camel_case}( longhands.${property.ident} ) } % endfor _ => unreachable!() } }) } % endfor } }) .ok() }) .unwrap_or_else(|| { // Invalid at computed-value time. let keyword = if longhand_id.inherited() { CSSWideKeyword::Inherit } else { CSSWideKeyword::Initial }; PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration { id: longhand_id, keyword, }) }) } } /// An identifier for a given property declaration, which can be either a /// longhand or a custom property. #[derive(Clone, Copy, PartialEq)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub enum PropertyDeclarationId<'a> { /// A longhand. Longhand(LonghandId), /// A custom property declaration. Custom(&'a ::custom_properties::Name), } impl<'a> ToCss for PropertyDeclarationId<'a> { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { match *self { PropertyDeclarationId::Longhand(id) => dest.write_str(id.name()), PropertyDeclarationId::Custom(_) => { serialize_identifier(&self.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() } } } } /// 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(::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(&self, dest: &mut CssWriter) -> 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(_) => { serialize_identifier(&self.name(), dest) } } } } impl PropertyId { /// Return the longhand id that this property id represents. #[inline] pub fn longhand_id(&self) -> Option { Some(match *self { PropertyId::Longhand(id) => id, PropertyId::LonghandAlias(id, _) => id, _ => return None, }) } /// Returns a given property from the string `s`. /// /// Returns Err(()) for unknown non-custom properties. fn parse_unchecked(property_name: &str) -> Result { // FIXME(https://github.com/rust-lang/rust/issues/33156): remove this // enum and use PropertyId when stable Rust allows destructors in // statics. // // 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), } 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.alias: "${alias.name}" => { StaticId::${kind}Alias(${kind}Id::${property.camel_case}, AliasId::${alias.camel_case}) }, % endfor % endfor % endfor } } Ok(match static_id(property_name) { Some(&StaticId::Longhand(id)) => { PropertyId::Longhand(id) }, Some(&StaticId::Shorthand(id)) => { PropertyId::Shorthand(id) }, Some(&StaticId::LonghandAlias(id, alias)) => { PropertyId::LonghandAlias(id, alias) }, Some(&StaticId::ShorthandAlias(id, alias)) => { PropertyId::ShorthandAlias(id, alias) }, None => { let name = ::custom_properties::parse_name(property_name)?; PropertyId::Custom(::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 { let id = Self::parse_unchecked(name)?; 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 { let id = Self::parse_unchecked(name)?; if !id.allowed_in(context) { return Err(()); } Ok(id) } /// Returns a property id from Gecko's nsCSSPropertyID. #[cfg(feature = "gecko")] #[allow(non_upper_case_globals)] pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Result { use gecko_bindings::structs::*; match id { % for property in data.longhands: ${property.nscsspropertyid()} => { Ok(PropertyId::Longhand(LonghandId::${property.camel_case})) } % for alias in property.alias: ${alias.nscsspropertyid()} => { Ok(PropertyId::LonghandAlias( LonghandId::${property.camel_case}, AliasId::${alias.camel_case} )) } % endfor % endfor % for property in data.shorthands: ${property.nscsspropertyid()} => { Ok(PropertyId::Shorthand(ShorthandId::${property.camel_case})) } % for alias in property.alias: ${alias.nscsspropertyid()} => { Ok(PropertyId::ShorthandAlias( ShorthandId::${property.camel_case}, AliasId::${alias.camel_case} )) } % endfor % endfor _ => Err(()) } } /// 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 { 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 name of the property without CSS escaping. pub fn name(&self) -> Cow<'static, str> { match *self { PropertyId::ShorthandAlias(id, _) | PropertyId::Shorthand(id) => id.name().into(), PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id.name().into(), PropertyId::Custom(ref name) => { let mut s = String::new(); write!(&mut s, "--{}", name).unwrap(); s.into() } } } fn non_custom_id(&self) -> Option { 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 { 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() } 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) } /// 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)] pub struct WideKeywordDeclaration { #[css(skip)] id: LonghandId, keyword: CSSWideKeyword, } /// An unparsed declaration that contains `var()` functions. #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, PartialEq, ToCss)] pub struct VariableDeclaration { #[css(skip)] id: LonghandId, #[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")] value: Arc, } /// A custom property declaration with the property name and the declared value. #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, PartialEq, ToCss)] pub struct CustomDeclaration { /// The name of the custom property. #[css(skip)] pub name: ::custom_properties::Name, /// The value of the custom property. #[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")] pub value: DeclaredValueOwned>, } 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 PropertyDeclarationId 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) } 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 if the declaration's value is one. pub fn get_css_wide_keyword(&self) -> Option { match *self { PropertyDeclaration::CSSWideKeyword(ref declaration) => { Some(declaration.keyword) }, _ => None, } } /// Returns whether or not the property is set by a system font #[cfg(feature = "gecko")] pub fn get_system(&self) -> Option { match *self { % for prop in SYSTEM_FONT_LONGHANDS: PropertyDeclaration::${to_camel_case(prop)}(ref prop) => { prop.get_system() } % endfor _ => 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 } } #[cfg(feature = "servo")] /// Dummy method to avoid cfg()s pub fn get_system(&self) -> Option<()> { None } /// 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.borrow(), DeclaredValue::CSSWideKeyword(..)) } _ => 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: /// /// /// > The inside of 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, name: CowRcStr<'i>, context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<(), ParseError<'i>> { assert!(declarations.is_empty()); debug_assert!(id.allowed_in(context), "{:?}", id); let start = input.state(); match id { PropertyId::Custom(property_name) => { // FIXME: fully implement https://github.com/w3c/csswg-drafts/issues/774 // before adding skip_whitespace here. // This probably affects some test results. let value = match input.try(|i| CSSWideKeyword::parse(i)) { Ok(keyword) => DeclaredValueOwned::CSSWideKeyword(keyword), Err(()) => match ::custom_properties::SpecifiedValue::parse(input) { Ok(value) => DeclaredValueOwned::Value(value), Err(e) => return Err(StyleParseErrorKind::new_invalid(name, e)), } }; declarations.push(PropertyDeclaration::Custom(CustomDeclaration { name: property_name, value, })); Ok(()) } PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => { input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less. input.try(|i| CSSWideKeyword::parse(i)).map(|keyword| { PropertyDeclaration::CSSWideKeyword( WideKeywordDeclaration { id, keyword }, ) }).or_else(|()| { input.look_for_var_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_functions() { input.reset(&start); let (first_token_type, css) = ::custom_properties::parse_non_custom_with_var(input).map_err(|e| { StyleParseErrorKind::new_invalid(name, e) })?; Ok(PropertyDeclaration::WithVariables(VariableDeclaration { id, value: Arc::new(UnparsedValue { css: css.into_owned(), first_token_type: first_token_type, url_data: context.url_data.clone(), from_shorthand: None, }), })) } else { Err(StyleParseErrorKind::new_invalid(name, err)) } }) }).map(|declaration| { declarations.push(declaration) }) } PropertyId::ShorthandAlias(id, _) | PropertyId::Shorthand(id) => { input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less. if let Ok(keyword) = input.try(|i| CSSWideKeyword::parse(i)) { if id == ShorthandId::All { declarations.all_shorthand = AllShorthand::CSSWideKeyword(keyword) } else { for longhand in id.longhands() { declarations.push(PropertyDeclaration::CSSWideKeyword( WideKeywordDeclaration { id: longhand, keyword, }, )) } } Ok(()) } else { input.look_for_var_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_functions() { input.reset(&start); let (first_token_type, css) = ::custom_properties::parse_non_custom_with_var(input).map_err(|e| { StyleParseErrorKind::new_invalid(name, e) })?; let unparsed = Arc::new(UnparsedValue { css: css.into_owned(), first_token_type: 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(()) } else { Err(StyleParseErrorKind::new_invalid(name, err)) } }) } } } } } const MAX_SUB_PROPERTIES_PER_SHORTHAND_EXCEPT_ALL: usize = ${max(len(s.sub_properties) for s in data.shorthands_except_all())}; type SourcePropertyDeclarationArray = [PropertyDeclaration; MAX_SUB_PROPERTIES_PER_SHORTHAND_EXCEPT_ALL]; /// A stack-allocated vector of `PropertyDeclaration` /// large enough to parse one CSS `key: value` declaration. /// (Shorthands expand to multiple `PropertyDeclaration`s.) pub struct SourcePropertyDeclaration { declarations: ::arrayvec::ArrayVec, /// Stored separately to keep MAX_SUB_PROPERTIES_PER_SHORTHAND_EXCEPT_ALL smaller. all_shorthand: AllShorthand, } impl SourcePropertyDeclaration { /// Create one. It’s big, try not to move it around. #[inline] pub fn new() -> Self { SourcePropertyDeclaration { declarations: ::arrayvec::ArrayVec::new(), all_shorthand: AllShorthand::NotSet, } } /// 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> { declarations: ::arrayvec::Drain<'a, SourcePropertyDeclarationArray>, all_shorthand: AllShorthand, } enum AllShorthand { NotSet, CSSWideKeyword(CSSWideKeyword), WithVariables(Arc) } #[cfg(feature = "gecko")] pub use gecko_properties::style_structs; /// The module where all the style structs are defined. #[cfg(feature = "servo")] pub mod style_structs { use fnv::FnvHasher; use super::longhands; use std::hash::{Hash, Hasher}; use logical_geometry::WritingMode; use media_queries::Device; use values::computed::NonNegativeLength; % for style_struct in data.active_style_structs(): % if style_struct.name == "Font": #[derive(Clone, Debug, MallocSizeOf)] % else: #[derive(Clone, Debug, MallocSizeOf, PartialEq)] % endif /// The ${style_struct.name} style struct. pub struct ${style_struct.name} { % for longhand in style_struct.longhands: /// The ${longhand.name} computed value. pub ${longhand.ident}: longhands::${longhand.ident}::computed_value::T, % 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: ::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.is_vector: /// Set ${longhand.name}. #[allow(non_snake_case)] #[inline] pub fn set_${longhand.ident}(&mut self, v: I) where I: IntoIterator, I::IntoIter: ExactSizeIterator { self.${longhand.ident} = longhands::${longhand.ident}::computed_value ::List(v.into_iter().collect()); } % elif 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 { self.border_${side}_width != NonNegativeLength::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: FnvHasher = 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, _: &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 { 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 { self.outline_width != NonNegativeLength::zero() } % elif style_struct.name == "Text": /// Whether the text decoration has an underline. #[inline] pub fn has_underline(&self) -> bool { self.text_decoration_line.contains(longhands::text_decoration_line::SpecifiedValue::UNDERLINE) } /// Whether the text decoration has an overline. #[inline] pub fn has_overline(&self) -> bool { self.text_decoration_line.contains(longhands::text_decoration_line::SpecifiedValue::OVERLINE) } /// Whether the text decoration has a line through. #[inline] pub fn has_line_through(&self) -> bool { self.text_decoration_line.contains(longhands::text_decoration_line::SpecifiedValue::LINE_THROUGH) } % 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 == "Box": /// 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.0.is_some()) } /// Returns whether there are any transitions specified. #[cfg(feature = "servo")] pub fn specifies_transitions(&self) -> bool { self.transition_duration_iter() .take(self.transition_property_count()) .any(|t| t.seconds() > 0.) } % elif style_struct.name == "Column": /// Whether this is a multicol style. #[cfg(feature = "servo")] pub fn is_multicol(&self) -> bool { match self.column_width { Either::First(_width) => true, Either::Second(_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.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 gecko_properties::{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, % endfor custom_properties: Option>, /// 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, /// 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>, } /// 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, } impl ComputedValues { /// Returns whether this style's display value is equal to contents. pub fn is_display_contents(&self) -> bool { self.get_box().clone_display().is_contents() } /// Whether we're a visited style. pub fn is_style_if_visited(&self) -> bool { self.flags.contains(ComputedValueFlags::IS_STYLE_IF_VISITED) } /// 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 style, if any. pub fn visited_style(&self) -> Option<<&ComputedValues> { self.visited_style.as_ref().map(|s| &**s) } /// Returns the visited rules, if applicable. pub fn visited_rules(&self) -> Option<<&StrongRuleNode> { self.visited_style.as_ref().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<::custom_properties::CustomPropertiesMap>> { self.custom_properties.as_ref() } } #[cfg(feature = "servo")] impl ComputedValues { /// Create a new refcounted `ComputedValues` pub fn new( _: &Device, _: Option<<&ComputedValues>, _: Option<<&PseudoElement>, custom_properties: Option>, writing_mode: WritingMode, flags: ComputedValueFlags, rules: Option, visited_style: Option>, % for style_struct in data.active_style_structs(): ${style_struct.ident}: Arc, % endfor ) -> Arc { 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 } }) } /// Get the initial computed values. pub fn initial_values() -> &'static Self { &*INITIAL_SERVO_VALUES } } #[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 { % for style_struct in data.active_style_structs(): /// Clone the ${style_struct.name} struct. #[inline] pub fn clone_${style_struct.name_lower}(&self) -> Arc { 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} } /// Gets an immutable reference to the refcounted value that wraps /// `${style_struct.name}`. pub fn ${style_struct.name_lower}_arc(&self) -> &Arc { &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() } /// Whether this style has a -moz-binding value. This is always false for /// Servo for obvious reasons. pub fn has_moz_binding(&self) -> bool { false } #[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 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() } /// 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.Border.border_top_color); #[inline] pub fn resolve_color(&self, color: computed::Color) -> RGBA { color.to_rgba(self.get_color().color) } /// Get the logical computed inline size. #[inline] pub fn content_inline_size(&self) -> computed::LengthOrPercentageOrAuto { 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::LengthOrPercentageOrAuto { 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::LengthOrPercentage { 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::LengthOrPercentage { 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::LengthOrPercentageOrNone { 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::LengthOrPercentageOrNone { 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 { 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 { 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 { self.border_width_for_writing_mode(self.writing_mode) } /// Gets the logical computed margin from this style. #[inline] pub fn logical_margin(&self) -> LogicalMargin { 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 { // 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 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 } /// pub fn get_used_transform_style(&self) -> computed_values::transform_style::T { use 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 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 } /// Serializes the computed value of this property as a string. pub fn computed_value_to_string(&self, property: PropertyDeclarationId) -> String { match property { % for style_struct in data.active_style_structs(): % for longhand in style_struct.longhands: PropertyDeclarationId::Longhand(LonghandId::${longhand.camel_case}) => { self.${style_struct.ident}.${longhand.ident}.to_css_string() } % endfor % endfor PropertyDeclarationId::Custom(name) => { self.custom_properties .as_ref() .and_then(|map| map.get(name)) .map(|value| value.to_css_string()) .unwrap_or(String::new()) } } } } % if product == "gecko": pub use ::servo_arc::RawOffsetArc as BuilderArc; /// Clone an arc, returning a regular arc fn clone_arc(x: &BuilderArc) -> Arc { Arc::from_raw_offset(x.clone()) } % else: pub use ::servo_arc::Arc as BuilderArc; /// Clone an arc, returning a regular arc fn clone_arc(x: &BuilderArc) -> Arc { x.clone() } % endif /// 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 BuilderArc), /// An owned struct, that we've already mutated. Owned(UniqueArc), /// 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") } } /// 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 { use std::mem::replace; let inner = replace(self, StyleStructRef::Vacated); match inner { StyleStructRef::Owned(arc) => arc, StyleStructRef::Borrowed(arc) => UniqueArc::new((**arc).clone()), StyleStructRef::Vacated => panic!("Accessed vacated style struct"), } } /// Replace vacated ref with an arc pub fn put(&mut self, arc: UniqueArc) { 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 { match self { StyleStructRef::Owned(v) => v.shareable(), StyleStructRef::Borrowed(v) => clone_arc(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 style we're inheriting from explicitly, or none if we're the root of /// a subtree. parent_style: Option<<&'a ComputedValues>, /// The rule node representing the ordered list of rules matched for this /// node. pub rules: Option, custom_properties: Option>, /// 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, /// The writing mode flags. /// /// TODO(emilio): Make private. pub writing_mode: WritingMode, /// Flags for the computed value. pub flags: 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. visited_style: Option>, % 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`. fn new( device: &'a Device, parent_style: Option<<&'a ComputedValues>, parent_style_ignoring_first_line: Option<<&'a ComputedValues>, pseudo: Option<<&'a PseudoElement>, cascade_flags: CascadeFlags, rules: Option, custom_properties: Option>, visited_style: Option>, ) -> 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().pseudo() == Some(PseudoElement::FirstLine)); 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); // FIXME(bz): INHERIT_ALL seems like a fundamentally broken idea. I'm // 99% sure it should give incorrect behavior for table anonymous box // backgrounds, for example. This code doesn't attempt to make it play // nice with inherited_style_ignoring_first_line. let reset_style = if cascade_flags.contains(CascadeFlags::INHERIT_ALL) { inherited_style } else { reset_style }; let mut flags = inherited_style.flags.inherited(); if cascade_flags.contains(CascadeFlags::VISITED_DEPENDENT_ONLY) { flags.insert(ComputedValueFlags::IS_STYLE_IF_VISITED); } StyleBuilder { device, parent_style, inherited_style, inherited_style_ignoring_first_line, reset_style, pseudo, rules, modified_reset: false, custom_properties, writing_mode: inherited_style.writing_mode, flags, visited_style, % for style_struct in data.active_style_structs(): % if style_struct.inherited: ${style_struct.ident}: StyleStructRef::Borrowed(inherited_style.${style_struct.name_lower}_arc()), % else: ${style_struct.ident}: StyleStructRef::Borrowed(reset_style.${style_struct.name_lower}_arc()), % endif % endfor } } /// Whether we're a visited style. pub fn is_style_if_visited(&self) -> bool { self.flags.contains(ComputedValueFlags::IS_STYLE_IF_VISITED) } /// 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().pseudo() != Some(PseudoElement::FirstLine)); StyleBuilder { device, parent_style, 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, rules: None, custom_properties: style_to_derive_from.custom_properties().cloned(), writing_mode: style_to_derive_from.writing_mode, flags: 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.${style_struct.name_lower}_arc() ), % 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.${style_struct.name_lower}_arc()); % endif % endfor } % for property in data.longhands: % if property.ident != "font_size": /// Inherit `${property.ident}` from our parent style. #[allow(non_snake_case)] pub fn inherit_${property.ident}(&mut self) { let inherited_struct = % if property.style_struct.inherited: self.inherited_style.get_${property.style_struct.name_lower}(); % else: self.inherited_style_ignoring_first_line .get_${property.style_struct.name_lower}(); % endif % if not property.style_struct.inherited: self.flags.insert(ComputedValueFlags::INHERITS_RESET_STYLE); self.modified_reset = true; % endif % if property.ident == "content": self.flags.insert(ComputedValueFlags::INHERITS_CONTENT); % endif % if property.ident == "display": self.flags.insert(ComputedValueFlags::INHERITS_DISPLAY); % endif self.${property.style_struct.ident}.mutate() .copy_${property.ident}_from( inherited_struct, % if property.logical: self.writing_mode, % endif ); } /// 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 not property.style_struct.inherited: self.modified_reset = true; % endif self.${property.style_struct.ident}.mutate() .reset_${property.ident}( reset_struct, % if property.logical: self.writing_mode, % endif ); } % if not property.is_vector: /// 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 <% props_need_device = ["content", "list_style_type", "font_variant_alternates"] %> self.${property.style_struct.ident}.mutate() .set_${property.ident}( value, % if property.logical: self.writing_mode, % elif product == "gecko" and property.ident in props_need_device: self.device, % endif ); } % 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() }) }); Self::new( device, parent, parent, pseudo, CascadeFlags::empty(), /* rules = */ None, parent.and_then(|p| p.custom_properties().cloned()), visited_style, ) } /// 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 { % 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) { 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.${style_struct.name_lower}_arc()); } % endfor <% del style_struct %> /// Returns whether this computed style represents a floated object. pub fn floated(&self) -> bool { self.get_box().clone_float() != longhands::float::computed_value::T::None } /// Returns whether this computed style represents an out of flow-positioned /// object. pub fn out_of_flow_positioned(&self) -> bool { use properties::longhands::position::computed_value::T as Position; matches!(self.get_box().clone_position(), Position::Absolute | Position::Fixed) } /// Whether this style has a top-layer style. That's implemented in Gecko /// via the -moz-top-layer property, but servo doesn't have any concept of a /// top layer (yet, it's needed for fullscreen). #[cfg(feature = "servo")] pub fn in_top_layer(&self) -> bool { false } /// 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. 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. fn modified_reset(&self) -> bool { self.modified_reset } /// Turns this `StyleBuilder` into a proper `ComputedValues` instance. pub fn build(self) -> Arc { ComputedValues::new( self.device, self.parent_style, self.pseudo, self.custom_properties, self.writing_mode, self.flags, 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. /// /// Cloning the Arc here is fine because it only happens in the case where /// we have custom properties, and those are both rare and expensive. fn custom_properties(&self) -> Option<<&Arc<::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 logical_geometry::WritingMode; use servo_arc::Arc; use super::{ComputedValues, ComputedValuesInner, longhands, style_structs}; use super::computed_value_flags::ComputedValueFlags; /// The initial values for all style structs as defined by the specification. lazy_static! { 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: ${longhand.ident}: longhands::${longhand.ident}::get_initial_value(), % endfor % if style_struct.name == "InheritedText": text_decorations_in_effect: ::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(), } }; } } /// 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. static CASCADE_PROPERTY: [CascadePropertyFn; ${len(data.longhands)}] = [ % for property in data.longhands: longhands::${property.ident}::cascade_property, % endfor ]; bitflags! { /// A set of flags to tweak the behavior of the `cascade` function. pub struct CascadeFlags: u8 { /// Whether to inherit all styles from the parent. If this flag is not /// present, non-inherited styles are reset to their initial values. const INHERIT_ALL = 1; /// Whether to only cascade properties that are visited dependent. const VISITED_DEPENDENT_ONLY = 1 << 1; } } /// Performs the CSS cascade, computing new styles for an element from its parent style. /// /// The arguments are: /// /// * `device`: Used to get the initial viewport and other external state. /// /// * `rule_node`: The rule node in the tree that represent the CSS rules that /// matched. /// /// * `parent_style`: The parent style, if applicable; if `None`, this is the root node. /// /// Returns the computed values. /// * `flags`: Various flags. /// pub fn cascade( device: &Device, pseudo: Option<<&PseudoElement>, rule_node: &StrongRuleNode, guards: &StylesheetGuards, parent_style: Option<<&ComputedValues>, parent_style_ignoring_first_line: Option<<&ComputedValues>, layout_parent_style: Option<<&ComputedValues>, visited_style: Option>, font_metrics_provider: &FontMetricsProvider, flags: CascadeFlags, quirks_mode: QuirksMode, rule_cache: Option<<&RuleCache>, rule_cache_conditions: &mut RuleCacheConditions, element: Option, ) -> Arc where E: TElement, { debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some()); let empty = SmallBitVec::new(); let property_restriction = pseudo.and_then(|p| p.property_restriction()); let iter_declarations = || { rule_node.self_and_ancestors().flat_map(|node| { let cascade_level = node.cascade_level(); let source = node.style_source(); let declarations = if source.is_some() { source.as_ref().unwrap().read(cascade_level.guard(guards)).declaration_importance_iter() } else { // The root node has no style source. DeclarationImportanceIterator::new(&[], &empty) }; let node_importance = node.importance(); declarations // Yield declarations later in source order (with more precedence) first. .rev() .filter_map(move |(declaration, declaration_importance)| { if let Some(property_restriction) = property_restriction { // declaration.id() is either a longhand or a custom // property. Custom properties are always allowed, but // longhands are only allowed if they have our // property_restriction flag set. if let PropertyDeclarationId::Longhand(id) = declaration.id() { if !id.flags().contains(property_restriction) { return None } } } if declaration_importance == node_importance { Some((declaration, cascade_level)) } else { None } }) }) }; apply_declarations( device, pseudo, rule_node, guards, iter_declarations, parent_style, parent_style_ignoring_first_line, layout_parent_style, visited_style, font_metrics_provider, flags, quirks_mode, rule_cache, rule_cache_conditions, element, ) } /// NOTE: This function expects the declaration with more priority to appear /// first. pub fn apply_declarations<'a, E, F, I>( device: &Device, pseudo: Option<<&PseudoElement>, rules: &StrongRuleNode, guards: &StylesheetGuards, iter_declarations: F, parent_style: Option<<&ComputedValues>, parent_style_ignoring_first_line: Option<<&ComputedValues>, layout_parent_style: Option<<&ComputedValues>, visited_style: Option>, font_metrics_provider: &FontMetricsProvider, flags: CascadeFlags, quirks_mode: QuirksMode, rule_cache: Option<<&RuleCache>, rule_cache_conditions: &mut RuleCacheConditions, element: Option, ) -> Arc where E: TElement, F: Fn() -> I, I: Iterator, { debug_assert!(layout_parent_style.is_none() || parent_style.is_some()); 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().pseudo() == Some(PseudoElement::FirstLine)); let (inherited_style, layout_parent_style) = match parent_style { Some(parent_style) => { (parent_style, layout_parent_style.unwrap_or(parent_style)) }, None => { (device.default_computed_values(), device.default_computed_values()) } }; let custom_properties = { let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties()); for (declaration, _cascade_level) in iter_declarations() { if let PropertyDeclaration::Custom(ref declaration) = *declaration { builder.cascade(&declaration.name, declaration.value.borrow()); } } builder.build() }; let mut context = computed::Context { is_root_element: pseudo.is_none() && element.map_or(false, |e| e.is_root()), // We'd really like to own the rules here to avoid refcount traffic, but // animation's usage of `apply_declarations` make this tricky. See bug // 1375525. builder: StyleBuilder::new( device, parent_style, parent_style_ignoring_first_line, pseudo, flags, Some(rules.clone()), custom_properties, visited_style, ), cached_system_font: None, in_media_query: false, for_smil_animation: false, for_non_inherited_property: None, font_metrics_provider, quirks_mode, rule_cache_conditions: RefCell::new(rule_cache_conditions), }; let ignore_colors = !device.use_document_colors(); // Set computed values, overwriting earlier declarations for the same // property. let mut seen = LonghandIdSet::new(); // Declaration blocks are stored in increasing precedence order, we want // them in decreasing order here. // // We could (and used to) use a pattern match here, but that bloats this // function to over 100K of compiled code! // // To improve i-cache behavior, we outline the individual functions and use // virtual dispatch instead. let mut apply_reset = true; % for category_to_cascade_now in ["early", "other"]: % if category_to_cascade_now == "early": // Pull these out so that we can compute them in a specific order // without introducing more iterations. let mut font_size = None; let mut font_family = None; % endif for (declaration, cascade_level) in iter_declarations() { let declaration_id = declaration.id(); let longhand_id = match declaration_id { PropertyDeclarationId::Longhand(id) => id, PropertyDeclarationId::Custom(..) => continue, }; // 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. if flags.contains(CascadeFlags::VISITED_DEPENDENT_ONLY) && !longhand_id.is_visited_dependent() { continue } if !apply_reset && !longhand_id.inherited() { continue; } if % if category_to_cascade_now == "early": ! % endif longhand_id.is_early_property() { continue } <% maybe_to_physical = ".to_physical(writing_mode)" if category_to_cascade_now != "early" else "" %> let physical_longhand_id = longhand_id ${maybe_to_physical}; if seen.contains(physical_longhand_id) { continue } let mut declaration = match *declaration { PropertyDeclaration::WithVariables(ref declaration) => { if !declaration.id.inherited() { context.rule_cache_conditions.borrow_mut() .set_uncacheable(); } Cow::Owned(declaration.value.substitute_variables( declaration.id, context.builder.custom_properties.as_ref(), context.quirks_mode )) } ref d => Cow::Borrowed(d) }; // When document colors are disabled, skip properties that are // marked as ignored in that mode, unless they come from a UA or // user style sheet. if ignore_colors && longhand_id.is_ignored_when_document_colors_disabled( cascade_level, context.builder.pseudo ) { let non_transparent_background = match *declaration { PropertyDeclaration::BackgroundColor(ref color) => { // Treat background-color a bit differently. If the specified // color is anything other than a fully transparent color, convert // it into the Device's default background color. color.is_non_transparent() } _ => continue }; // FIXME: moving this out of `match` is a work around for // borrows being lexical. if non_transparent_background { let color = device.default_background_color(); declaration = Cow::Owned(PropertyDeclaration::BackgroundColor(color.into())); } } seen.insert(physical_longhand_id); % if category_to_cascade_now == "early": if LonghandId::FontSize == longhand_id { font_size = Some(declaration.clone()); continue; } if LonghandId::FontFamily == longhand_id { font_family = Some(declaration.clone()); continue; } % endif let discriminant = longhand_id as usize; (CASCADE_PROPERTY[discriminant])(&*declaration, &mut context); } % if category_to_cascade_now == "early": let writing_mode = WritingMode::new(context.builder.get_inheritedbox()); context.builder.writing_mode = writing_mode; let mut _skip_font_family = false; % if product == "gecko": // is not affected by text zoom, and it uses a preshint to // disable it. We fix up the struct when this happens by unzooming // its contained font values, which will have been zoomed in the parent if seen.contains(LonghandId::XTextZoom) { let zoom = context.builder.get_font().gecko().mAllowZoom; let parent_zoom = context.style().get_parent_font().gecko().mAllowZoom; if zoom != parent_zoom { debug_assert!(!zoom, "We only ever disable text zoom (in svg:text), never enable it"); // can't borrow both device and font, use the take/put machinery let mut font = context.builder.take_font(); font.unzoom_fonts(context.device()); context.builder.put_font(font); } } // Whenever a single generic value is specified, gecko will do a bunch of // recalculation walking up the rule tree, including handling the font-size stuff. // It basically repopulates the font struct with the default font for a given // generic and language. We handle the font-size stuff separately, so this boils // down to just copying over the font-family lists (no other aspect of the default // font can be configured). if seen.contains(LonghandId::XLang) || font_family.is_some() { // if just the language changed, the inherited generic is all we need let mut generic = inherited_style.get_font().gecko().mGenericID; if let Some(ref declaration) = font_family { if let PropertyDeclaration::FontFamily(ref fam) = **declaration { if let Some(id) = fam.single_generic() { generic = id; // In case of a specified font family with a single generic, we will // end up setting font family below, but its value would get // overwritten later in the pipeline when cascading. // // We instead skip cascading font-family in that case. // // In case of the language changing, we wish for a specified font- // family to override this, so we do not skip cascading then. _skip_font_family = true; } } } let pres_context = context.builder.device.pres_context(); let gecko_font = context.builder.mutate_font().gecko_mut(); gecko_font.mGenericID = generic; unsafe { bindings::Gecko_nsStyleFont_PrefillDefaultForGeneric( gecko_font, pres_context, generic, ); } } % endif // It is important that font_size is computed before // the late properties (for em units), but after font-family // (for the base-font-size dependence for default and keyword font-sizes) // Additionally, when we support system fonts they will have to be // computed early, and *before* font_family, so I'm including // font_family here preemptively instead of keeping it within // the early properties. // // To avoid an extra iteration, we just pull out the property // during the early iteration and cascade them in order // after it. if !_skip_font_family { if let Some(ref declaration) = font_family { let discriminant = LonghandId::FontFamily as usize; (CASCADE_PROPERTY[discriminant])(declaration, &mut context); % if product == "gecko": let device = context.builder.device; if let PropertyDeclaration::FontFamily(ref val) = **declaration { if val.get_system().is_some() { let default = context.cached_system_font .as_ref().unwrap().default_font_type; context.builder.mutate_font().fixup_system(default); } else { context.builder.mutate_font().fixup_none_generic(device); } } % endif } } if let Some(ref declaration) = font_size { let discriminant = LonghandId::FontSize as usize; (CASCADE_PROPERTY[discriminant])(declaration, &mut context); % if product == "gecko": // Font size must be explicitly inherited to handle lang changes and // scriptlevel changes. } else if seen.contains(LonghandId::XLang) || seen.contains(LonghandId::MozScriptLevel) || seen.contains(LonghandId::MozMinFontSizeRatio) || font_family.is_some() { let discriminant = LonghandId::FontSize as usize; let size = PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration { id: LonghandId::FontSize, keyword: CSSWideKeyword::Inherit, }); (CASCADE_PROPERTY[discriminant])(&size, &mut context); % endif } if let Some(style) = rule_cache.and_then(|c| c.find(guards, &context.builder)) { context.builder.copy_reset_from(style); apply_reset = false; } % endif // category == "early" % endfor let mut builder = context.builder; % if product == "gecko": if let Some(ref mut bg) = builder.get_background_if_mutated() { bg.fill_arrays(); } if let Some(ref mut svg) = builder.get_svg_if_mutated() { svg.fill_arrays(); } % endif % if product == "servo": if seen.contains(LonghandId::FontStyle) || seen.contains(LonghandId::FontWeight) || seen.contains(LonghandId::FontStretch) || seen.contains(LonghandId::FontFamily) { builder.mutate_font().compute_font_hash(); } % endif builder.clear_modified_reset(); StyleAdjuster::new(&mut builder).adjust( layout_parent_style, element, flags, ); if builder.modified_reset() || !apply_reset { // If we adjusted any reset structs, we can't cache this ComputedValues. // // Also, if we re-used existing reset structs, don't bother caching it // back again. (Aside from being wasted effort, it will be wrong, since // context.rule_cache_conditions won't be set appropriately if we // didn't compute those reset properties.) context.rule_cache_conditions.borrow_mut() .set_uncacheable(); } builder.build() } /// 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(NonNegativeLength::zero()); } % endfor } /// An identifier for a given alias property. #[derive(Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub enum AliasId { % for i, property in enumerate(data.all_aliases()): /// ${property.name} ${property.camel_case} = ${i}, % endfor } impl fmt::Debug for AliasId { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { let name = match *self { % for property in data.all_aliases(): AliasId::${property.camel_case} => "${property.camel_case}", % endfor }; formatter.write_str(name) } } // 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.alias: % 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 } } } #[macro_export] macro_rules! longhand_properties_idents { ($macro_name: ident) => { $macro_name! { % for property in data.longhands: { ${property.ident}, ${"true" if property.boxed else "false"} } % endfor } } } % if product == "servo": % 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