diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs new file mode 100644 index 00000000000..41e308e3a83 --- /dev/null +++ b/components/style/properties/cascade.rs @@ -0,0 +1,807 @@ +/* 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/. */ + +//! The main cascading algorithm of the style system. + +use context::QuirksMode; +use custom_properties::CustomPropertiesBuilder; +use dom::TElement; +use font_metrics::FontMetricsProvider; +use logical_geometry::WritingMode; +use media_queries::Device; +use properties::{ComputedValues, StyleBuilder}; +use properties::{LonghandId, LonghandIdSet}; +use properties::{PropertyDeclaration, PropertyDeclarationId, DeclarationImportanceIterator}; +use properties::{CSSWideKeyword, WideKeywordDeclaration}; +use properties::CASCADE_PROPERTY; +use rule_cache::{RuleCache, RuleCacheConditions}; +use rule_tree::{CascadeLevel, StrongRuleNode}; +use selector_parser::PseudoElement; +use servo_arc::Arc; +use shared_lock::StylesheetGuards; +use smallbitvec::SmallBitVec; +use std::borrow::Cow; +use std::cell::RefCell; +use style_adjuster::StyleAdjuster; +use values::computed; + +/// We split the cascade in two phases: 'early' properties, and 'late' +/// properties. +/// +/// Early properties are the ones that don't have dependencies _and_ other +/// properties depend on, for example, writing-mode related properties, color +/// (for currentColor), or font-size (for em, etc). +/// +/// Late properties are all the others. +trait CascadePhase { + fn is_early() -> bool; +} + +struct EarlyProperties; +impl CascadePhase for EarlyProperties { + fn is_early() -> bool { + true + } +} + +struct LateProperties; +impl CascadePhase for LateProperties { + fn is_early() -> bool { + false + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum ApplyResetProperties { + No, + Yes, +} + +/// 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_rules: Option<&StrongRuleNode>, + font_metrics_provider: &FontMetricsProvider, + quirks_mode: QuirksMode, + rule_cache: Option<&RuleCache>, + rule_cache_conditions: &mut RuleCacheConditions, + element: Option, +) -> Arc +where + E: TElement, +{ + cascade_rules( + device, + pseudo, + rule_node, + guards, + parent_style, + parent_style_ignoring_first_line, + layout_parent_style, + font_metrics_provider, + CascadeMode::Unvisited { visited_rules }, + quirks_mode, + rule_cache, + rule_cache_conditions, + element, + ) +} + +fn cascade_rules( + 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>, + font_metrics_provider: &FontMetricsProvider, + cascade_mode: CascadeMode, + 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 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 node_importance = node.importance(); + let declarations = match node.style_source() { + Some(source) => source + .read(cascade_level.guard(guards)) + .declaration_importance_iter(), + None => DeclarationImportanceIterator::new(&[], &empty), + }; + + declarations + // Yield declarations later in source order (with more precedence) first. + .rev() + .filter_map(move |(declaration, declaration_importance)| { + if let Some(restriction) = 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 + // restriction flag set. + if let PropertyDeclarationId::Longhand(id) = declaration.id() { + if !id.flags().contains(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, + font_metrics_provider, + cascade_mode, + quirks_mode, + rule_cache, + rule_cache_conditions, + element, + ) +} + +/// Whether we're cascading for visited or unvisited styles. +#[derive(Clone, Copy)] +pub enum CascadeMode<'a> { + /// We're cascading for unvisited styles. + Unvisited { + /// The visited rules that should match the visited style. + visited_rules: Option<&'a StrongRuleNode>, + }, + /// We're cascading for visited styles. + Visited { + /// The writing mode of our unvisited style, needed to correctly resolve + /// logical properties.. + writing_mode: WritingMode, + }, +} + +/// 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>, + font_metrics_provider: &FontMetricsProvider, + cascade_mode: CascadeMode, + 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 = parent_style.unwrap_or(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, + Some(rules.clone()), + custom_properties, + ), + 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 using_cached_reset_properties = { + let mut cascade = Cascade::new(&mut context, cascade_mode); + + cascade + .apply_properties::(ApplyResetProperties::Yes, iter_declarations()); + + cascade.compute_visited_style_if_needed( + element, + parent_style, + parent_style_ignoring_first_line, + layout_parent_style, + guards, + ); + + let using_cached_reset_properties = + cascade.try_to_use_cached_reset_properties(rule_cache, guards); + + let apply_reset = if using_cached_reset_properties { + ApplyResetProperties::No + } else { + ApplyResetProperties::Yes + }; + + cascade.apply_properties::(apply_reset, iter_declarations()); + + using_cached_reset_properties + }; + + context.builder.clear_modified_reset(); + + if matches!(cascade_mode, CascadeMode::Unvisited { .. }) { + StyleAdjuster::new(&mut context.builder) + .adjust(layout_parent_style.unwrap_or(inherited_style), element); + } + + if context.builder.modified_reset() || using_cached_reset_properties { + // 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(); + } + + context.builder.build() +} + +fn should_ignore_declaration_when_ignoring_document_colors( + device: &Device, + longhand_id: LonghandId, + cascade_level: CascadeLevel, + pseudo: Option<&PseudoElement>, + declaration: &mut Cow, +) -> bool { + if !longhand_id.ignored_when_document_colors_disabled() { + return false; + } + + 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; + } + + // 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. + { + let background_color = match **declaration { + PropertyDeclaration::BackgroundColor(ref color) => color, + _ => return true, + }; + + if background_color.is_transparent() { + return false; + } + } + + let color = device.default_background_color(); + *declaration.to_mut() = PropertyDeclaration::BackgroundColor(color.into()); + + false +} + +struct Cascade<'a, 'b: 'a> { + context: &'a mut computed::Context<'b>, + cascade_mode: CascadeMode<'a>, + seen: LonghandIdSet, + saved_font_size: Option, + saved_font_family: Option, +} + +impl<'a, 'b: 'a> Cascade<'a, 'b> { + fn new(context: &'a mut computed::Context<'b>, cascade_mode: CascadeMode<'a>) -> Self { + Self { + context, + cascade_mode, + seen: LonghandIdSet::default(), + saved_font_size: None, + saved_font_family: None, + } + } + + fn substitute_variables_if_needed<'decl>( + &mut self, + declaration: &'decl PropertyDeclaration, + ) -> Cow<'decl, PropertyDeclaration> { + let declaration = match *declaration { + PropertyDeclaration::WithVariables(ref declaration) => declaration, + ref d => return Cow::Borrowed(d), + }; + + if !declaration.id.inherited() { + self.context + .rule_cache_conditions + .borrow_mut() + .set_uncacheable(); + } + + Cow::Owned(declaration.value.substitute_variables( + declaration.id, + self.context.builder.custom_properties.as_ref(), + self.context.quirks_mode, + )) + } + + fn apply_declaration( + &mut self, + longhand_id: LonghandId, + declaration: &PropertyDeclaration, + ) { + // FIXME(emilio): Find a cleaner abstraction for this. + // + // font-size and font-family are special because in Gecko they're + // they're dependent on other early props, like lang and + // -moz-min-font-size-ratio. This sucks a bit, we should ideally + // move the font-size computation code somewhere else... + if Phase::is_early() { + if longhand_id == LonghandId::FontSize { + self.saved_font_size = Some(declaration.clone()); + return; + } + if longhand_id == LonghandId::FontFamily { + self.saved_font_family = Some(declaration.clone()); + return; + } + } + + self.apply_declaration_ignoring_phase(longhand_id, declaration); + } + + #[inline(always)] + fn apply_declaration_ignoring_phase( + &mut self, + longhand_id: LonghandId, + declaration: &PropertyDeclaration, + ) { + // 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 discriminant = longhand_id as usize; + (CASCADE_PROPERTY[discriminant])(declaration, &mut self.context); + } + + fn apply_properties<'decls, Phase, I>( + &mut self, + apply_reset: ApplyResetProperties, + declarations: I, + ) where + Phase: CascadePhase, + I: Iterator, + { + let apply_reset = apply_reset == ApplyResetProperties::Yes; + + debug_assert!( + !Phase::is_early() || apply_reset, + "Should always apply reset properties in the early phase, since we \ + need to know font-size / writing-mode to decide whether to use the \ + cached reset properties" + ); + + let ignore_colors = !self.context.builder.device.use_document_colors(); + + for (declaration, cascade_level) in declarations { + let declaration_id = declaration.id(); + let longhand_id = match declaration_id { + PropertyDeclarationId::Longhand(id) => id, + PropertyDeclarationId::Custom(..) => continue, + }; + + let inherited = longhand_id.inherited(); + if !apply_reset && !inherited { + continue; + } + + if Phase::is_early() != longhand_id.is_early_property() { + continue; + } + + debug_assert!(!Phase::is_early() || !longhand_id.is_logical()); + let physical_longhand_id = if Phase::is_early() { + longhand_id + } else { + longhand_id.to_physical(self.context.builder.writing_mode) + }; + + if self.seen.contains(physical_longhand_id) { + 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 matches!(self.cascade_mode, CascadeMode::Visited { .. }) && + !physical_longhand_id.is_visited_dependent() + { + continue; + } + + let mut declaration = self.substitute_variables_if_needed(declaration); + + // 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 { + let should_ignore = should_ignore_declaration_when_ignoring_document_colors( + self.context.builder.device, + longhand_id, + cascade_level, + self.context.builder.pseudo, + &mut declaration, + ); + if should_ignore { + continue; + } + } + + self.seen.insert(physical_longhand_id); + + // FIXME(emilio): We should avoid generating code for logical + // longhands and just use the physical ones, then rename + // physical_longhand_id to just longhand_id. + self.apply_declaration::(longhand_id, &*declaration); + } + + if Phase::is_early() { + self.fixup_font_and_apply_saved_font_properties(); + self.compute_writing_mode(); + } else { + self.finished_applying_properties(); + } + } + + fn compute_writing_mode(&mut self) { + let writing_mode = match self.cascade_mode { + CascadeMode::Unvisited { .. } => { + WritingMode::new(self.context.builder.get_inherited_box()) + }, + CascadeMode::Visited { writing_mode } => writing_mode, + }; + self.context.builder.writing_mode = writing_mode; + } + + fn compute_visited_style_if_needed( + &mut self, + element: Option, + parent_style: Option<&ComputedValues>, + parent_style_ignoring_first_line: Option<&ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + guards: &StylesheetGuards, + ) where + E: TElement, + { + let visited_rules = match self.cascade_mode { + CascadeMode::Unvisited { visited_rules } => visited_rules, + CascadeMode::Visited { .. } => return, + }; + + let visited_rules = match visited_rules { + Some(rules) => rules, + None => return, + }; + + let is_link = self.context.builder.pseudo.is_none() && element.unwrap().is_link(); + + macro_rules! visited_parent { + ($parent:expr) => { + if is_link { + $parent + } else { + $parent.map(|p| p.visited_style().unwrap_or(p)) + } + }; + } + + let writing_mode = self.context.builder.writing_mode; + + // We could call apply_declarations directly, but that'd cause + // another instantiation of this function which is not great. + let style = cascade_rules( + self.context.builder.device, + self.context.builder.pseudo, + visited_rules, + guards, + visited_parent!(parent_style), + visited_parent!(parent_style_ignoring_first_line), + visited_parent!(layout_parent_style), + self.context.font_metrics_provider, + CascadeMode::Visited { writing_mode }, + self.context.quirks_mode, + // The rule cache doesn't care about caching :visited + // styles, we cache the unvisited style instead. We still do + // need to set the caching dependencies properly if present + // though, so the cache conditions need to match. + /* rule_cache = */ None, + &mut *self.context.rule_cache_conditions.borrow_mut(), + element, + ); + self.context.builder.visited_style = Some(style); + } + + fn finished_applying_properties(&mut self) { + let builder = &mut self.context.builder; + + #[cfg(feature = "gecko")] + { + if let Some(bg) = builder.get_background_if_mutated() { + bg.fill_arrays(); + } + + if let Some(svg) = builder.get_svg_if_mutated() { + svg.fill_arrays(); + } + } + + #[cfg(feature = "servo")] + { + // TODO(emilio): Use get_font_if_mutated instead. + if self.seen.contains(LonghandId::FontStyle) || + self.seen.contains(LonghandId::FontWeight) || + self.seen.contains(LonghandId::FontStretch) || + self.seen.contains(LonghandId::FontFamily) + { + builder.mutate_font().compute_font_hash(); + } + } + } + + fn try_to_use_cached_reset_properties( + &mut self, + cache: Option<&'b RuleCache>, + guards: &StylesheetGuards, + ) -> bool { + let cache = match cache { + Some(cache) => cache, + None => return false, + }; + + let cached_style = match cache.find(guards, &self.context.builder) { + Some(style) => style, + None => return false, + }; + + self.context.builder.copy_reset_from(cached_style); + true + } + + // FIXME(emilio): It'd be really nice to simplify all this, somehow. This is + // very annoying code in lots of ways, and there are various bits about it + // which I think are broken or could be improved, see the various FIXMEs + // below. + fn fixup_font_and_apply_saved_font_properties(&mut self) { + let font_family = self.saved_font_family.take(); + let font_size = self.saved_font_size.take(); + let mut _skip_font_family = false; + + #[cfg(feature = "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. + // + // FIXME(emilio): Could be cleaner if we just removed this property + // and made a style adjustment o something like that. + if self.seen.contains(LonghandId::XTextZoom) { + let builder = &mut self.context.builder; + + let parent_zoom = builder.get_parent_font().gecko().mAllowZoom; + let zoom = builder.get_font().gecko().mAllowZoom; + if zoom != parent_zoom { + debug_assert!( + !zoom, + "We only ever disable text zoom (in svg:text), never enable it" + ); + let device = builder.device; + builder.mutate_font().unzoom_fonts(device); + } + } + + // Whenever a single generic value is specified, Gecko used to do a + // bunch of recalculation walking up the rule tree, including + // handling the font-size stuff. + // + // It basically repopulated 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 self.seen.contains(LonghandId::XLang) || self.seen.contains(LonghandId::FontFamily) { + // If just the language changed, the inherited generic is all we + // need. + let mut generic = self.context.builder.get_parent_font().gecko().mGenericID; + + // FIXME(emilio): Isn't this bogus for CSS wide keywords like + // reset or such? + 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; + } + } + } + + // FIXME(emilio): Why both setting the generic and passing it + // down? + let pres_context = self.context.builder.device.pres_context(); + let gecko_font = self.context.builder.mutate_font().gecko_mut(); + gecko_font.mGenericID = generic; + unsafe { + ::gecko_bindings::bindings::Gecko_nsStyleFont_PrefillDefaultForGeneric( + gecko_font, + pres_context, + generic, + ); + } + } + } + + // 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). + // + // It's important that font-family comes after the other font properties + // to support system fonts. + // + // NOTE(emilio): I haven't verified that comment, but it was there. + // Verify, and if it's false make font-size the only weird property? + if !_skip_font_family { + if let Some(ref declaration) = font_family { + self.apply_declaration_ignoring_phase(LonghandId::FontFamily, declaration); + #[cfg(feature = "gecko")] + { + let context = &mut self.context; + 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); + } + } + } + } + } + + if let Some(declaration) = font_size { + self.apply_declaration_ignoring_phase(LonghandId::FontSize, &declaration); + } else { + #[cfg(feature = "gecko")] + { + if self.seen.contains(LonghandId::XLang) || + self.seen.contains(LonghandId::MozScriptLevel) || + self.seen.contains(LonghandId::MozMinFontSizeRatio) || + self.seen.contains(LonghandId::FontFamily) + { + // font-size must be explicitly inherited to handle lang + // changes and scriptlevel changes. + // + // FIXME(emilio): That looks a bit bogus... + let inherit = PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration { + id: LonghandId::FontSize, + keyword: CSSWideKeyword::Inherit, + }); + + self.apply_declaration_ignoring_phase(LonghandId::FontSize, &inherit); + } + } + } + } +} diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 681e9556653..39fc90b2f3d 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -13,13 +13,9 @@ #[cfg(feature = "servo")] use app_units::Au; use arrayvec::{ArrayVec, Drain as ArrayVecDrain}; -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}; @@ -27,8 +23,6 @@ use cssparser::{Parser, RGBA, TokenSerializationType}; 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; @@ -37,11 +31,9 @@ use logical_geometry::WritingMode; use media_queries::Device; use parser::ParserContext; 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}; @@ -49,12 +41,12 @@ use values::generics::text::LineHeight; use values::computed; use values::computed::NonNegativeLength; use values::serialize_atom_name; -use rule_tree::{CascadeLevel, StrongRuleNode}; +use rule_tree::StrongRuleNode; use self::computed_value_flags::*; use str::{CssString, CssStringBorrow, CssStringWriter}; -use style_adjuster::StyleAdjuster; pub use self::declaration_block::*; +pub use self::cascade::*; <%! from collections import defaultdict @@ -66,6 +58,8 @@ pub use self::declaration_block::*; pub mod computed_value_flags; #[path="${repr(os.path.join(os.path.dirname(__file__), 'declaration_block.rs'))[1:-1]}"] pub mod declaration_block; +#[path="${repr(os.path.join(os.path.dirname(__file__), 'cascade.rs'))[1:-1]}"] +pub mod cascade; /// Conversion with fewer impls than From/Into pub trait MaybeBoxed { @@ -731,7 +725,7 @@ static ${name}: LonghandIdSet = LonghandIdSet { /// A set of longhand properties -#[derive(Clone, Debug, MallocSizeOf, PartialEq)] +#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)] pub struct LonghandIdSet { storage: [u32; (${len(data.longhands)} - 1 + 32) / 32] } @@ -969,6 +963,7 @@ impl LonghandId { } /// Returns whether the longhand property is inherited by default. + #[inline] pub fn inherited(&self) -> bool { ${static_longhand_id_set("INHERITED", lambda p: p.style_struct.inherited)} INHERITED.contains(*self) @@ -1160,40 +1155,14 @@ impl LonghandId { /// 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 - ); + #[inline] + fn ignored_when_document_colors_disabled(self) -> bool { + ${static_longhand_id_set( + "IGNORED_WHEN_COLORS_DISABLED", + lambda p: p.ignored_when_colors_disabled + )} - 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])} - ) + IGNORED_WHEN_COLORS_DISABLED.contains(self) } /// The computed value of some properties depends on the (sometimes @@ -3469,6 +3438,10 @@ impl<'a> StyleBuilder<'a> { self.modified_reset = true; % endif + // TODO(emilio): There's a maybe-worth it optimization here: We should + // avoid allocating a new reset struct if `reset_struct` and our struct + // is the same pointer. Would remove a bunch of stupid allocations if + // you did something like `* { all: initial }` or what not. self.${property.style_struct.ident}.mutate() .reset_${property.ident}( reset_struct, @@ -3740,538 +3713,12 @@ pub type CascadePropertyFn = /// 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)}] = [ +pub static CASCADE_PROPERTY: [CascadePropertyFn; ${len(data.longhands)}] = [ % for property in data.longhands: longhands::${property.ident}::cascade_property, % endfor ]; -/// 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_rules: Option<<&StrongRuleNode>, - font_metrics_provider: &FontMetricsProvider, - quirks_mode: QuirksMode, - rule_cache: Option<<&RuleCache>, - rule_cache_conditions: &mut RuleCacheConditions, - element: Option, -) -> Arc -where - E: TElement, -{ - cascade_rules( - device, - pseudo, - rule_node, - guards, - parent_style, - parent_style_ignoring_first_line, - layout_parent_style, - font_metrics_provider, - CascadeMode::Unvisited { visited_rules }, - quirks_mode, - rule_cache, - rule_cache_conditions, - element, - ) -} - -fn cascade_rules( - 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>, - font_metrics_provider: &FontMetricsProvider, - cascade_mode: CascadeMode, - 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 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 node_importance = node.importance(); - let declarations = match node.style_source() { - Some(source) => { - source.read(cascade_level.guard(guards)).declaration_importance_iter() - } - None => DeclarationImportanceIterator::new(&[], &empty), - }; - - declarations - // Yield declarations later in source order (with more precedence) first. - .rev() - .filter_map(move |(declaration, declaration_importance)| { - if let Some(restriction) = 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 - // restriction flag set. - if let PropertyDeclarationId::Longhand(id) = declaration.id() { - if !id.flags().contains(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, - font_metrics_provider, - cascade_mode, - quirks_mode, - rule_cache, - rule_cache_conditions, - element, - ) -} - -/// Whether we're cascading for visited or unvisited styles. -#[derive(Clone, Copy)] -pub enum CascadeMode<'a> { - /// We're cascading for unvisited styles. - Unvisited { - /// The visited rules that should match the visited style. - visited_rules: Option<<&'a StrongRuleNode>, - }, - /// We're cascading for visited styles. - Visited { - /// The writing mode of our unvisited style, needed to correctly resolve - /// logical properties.. - writing_mode: WritingMode, - }, -} - -/// 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>, - font_metrics_provider: &FontMetricsProvider, - cascade_mode: CascadeMode, - 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 = - parent_style.unwrap_or(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, - Some(rules.clone()), - custom_properties, - ), - 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, - }; - - 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 - } - - // 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 matches!(cascade_mode, CascadeMode::Visited { .. }) && - !physical_longhand_id.is_visited_dependent() { - 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 = match cascade_mode { - CascadeMode::Unvisited { .. } => { - WritingMode::new(context.builder.get_inherited_box()) - } - CascadeMode::Visited { writing_mode } => writing_mode, - }; - - context.builder.writing_mode = writing_mode; - if let CascadeMode::Unvisited { visited_rules: Some(visited_rules) } = cascade_mode { - let is_link = pseudo.is_none() && element.unwrap().is_link(); - macro_rules! visited_parent { - ($parent:expr) => { - if is_link { - $parent - } else { - $parent.map(|p| p.visited_style().unwrap_or(p)) - } - } - } - // We could call apply_declarations directly, but that'd cause - // another instantiation of this function which is not great. - context.builder.visited_style = Some(cascade_rules( - device, - pseudo, - visited_rules, - guards, - visited_parent!(parent_style), - visited_parent!(parent_style_ignoring_first_line), - visited_parent!(layout_parent_style), - font_metrics_provider, - CascadeMode::Visited { writing_mode }, - quirks_mode, - // The rule cache doesn't care about caching :visited - // styles, we cache the unvisited style instead. We still do - // need to set the caching dependencies properly if present - // though, so the cache conditions need to match. - /* rule_cache = */ None, - &mut *context.rule_cache_conditions.borrow_mut(), - element, - )); - } - - 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(); - - if matches!(cascade_mode, CascadeMode::Unvisited { .. }) { - StyleAdjuster::new(&mut builder).adjust( - layout_parent_style.unwrap_or(inherited_style), - element, - ); - } - - 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) { diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index cce8f381806..85d29f3e7e7 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -317,12 +317,12 @@ impl Color { .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } - /// Returns false if the color is completely transparent, and - /// true otherwise. - pub fn is_non_transparent(&self) -> bool { + /// Returns true if the color is completely transparent, and false + /// otherwise. + pub fn is_transparent(&self) -> bool { match *self { - Color::Numeric { ref parsed, .. } => parsed.alpha != 0, - _ => true, + Color::Numeric { ref parsed, .. } => parsed.alpha == 0, + _ => false, } } }