diff --git a/components/servo_arc/lib.rs b/components/servo_arc/lib.rs index 5cac8c9b9bb..9ed2801ac2c 100644 --- a/components/servo_arc/lib.rs +++ b/components/servo_arc/lib.rs @@ -122,6 +122,12 @@ impl PartialEq for NonZeroPtrMut { impl Eq for NonZeroPtrMut {} +impl Hash for NonZeroPtrMut { + fn hash(&self, state: &mut H) { + self.ptr().hash(state) + } +} + pub struct Arc { p: NonZeroPtrMut>, } diff --git a/components/style/animation.rs b/components/style/animation.rs index 4dbb7b6474e..e29236186c7 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -502,7 +502,9 @@ fn compute_style_for_animation_step(context: &SharedStyleContext, /* visited_style = */ None, font_metrics_provider, CascadeFlags::empty(), - context.quirks_mode()); + context.quirks_mode(), + /* rule_cache = */ None, + &mut Default::default()); computed } } diff --git a/components/style/context.rs b/components/style/context.rs index b94f7049129..88cf90c33e6 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -20,6 +20,7 @@ use parallel::{STACK_SAFETY_MARGIN_KB, STYLE_THREAD_STACK_SIZE_KB}; #[cfg(feature = "servo")] use parking_lot::RwLock; use properties::ComputedValues; #[cfg(feature = "servo")] use properties::PropertyId; +use rule_cache::RuleCache; use rule_tree::StrongRuleNode; use selector_parser::{EAGER_PSEUDO_COUNT, SnapshotMap}; use selectors::matching::ElementSelectorFlags; @@ -685,6 +686,8 @@ impl StackLimitChecker { pub struct ThreadLocalStyleContext { /// A cache to share style among siblings. pub sharing_cache: StyleSharingCache, + /// A cache from matched properties to elements that match those. + pub rule_cache: RuleCache, /// The bloom filter used to fast-reject selector-matching. pub bloom_filter: StyleBloom, /// A channel on which new animations that have been triggered by style @@ -722,6 +725,7 @@ impl ThreadLocalStyleContext { pub fn new(shared: &SharedStyleContext) -> Self { ThreadLocalStyleContext { sharing_cache: StyleSharingCache::new(), + rule_cache: RuleCache::new(), bloom_filter: StyleBloom::new(), new_animations_sender: shared.local_context_creation_data.lock().unwrap().new_animations_sender.clone(), tasks: SequentialTaskList(Vec::new()), @@ -739,6 +743,7 @@ impl ThreadLocalStyleContext { pub fn new(shared: &SharedStyleContext) -> Self { ThreadLocalStyleContext { sharing_cache: StyleSharingCache::new(), + rule_cache: RuleCache::new(), bloom_filter: StyleBloom::new(), tasks: SequentialTaskList(Vec::new()), selector_flags: SelectorFlagsMap::new(), diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index 1f9a5585fcb..7ff3b816cff 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -23,7 +23,9 @@ use media_queries::MediaType; use parser::ParserContext; use properties::{ComputedValues, StyleBuilder}; use properties::longhands::font_size; +use rule_cache::RuleCacheConditions; use servo_arc::Arc; +use std::cell::RefCell; use std::fmt::{self, Write}; use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering}; use str::starts_with_ignore_ascii_case; @@ -694,6 +696,7 @@ impl Expression { // http://dev.w3.org/csswg/mediaqueries3/#units // em units are relative to the initial font-size. + let mut conditions = RuleCacheConditions::default(); let context = computed::Context { is_root_element: false, builder: StyleBuilder::for_derived_style(device, default_values, None, None), @@ -703,6 +706,8 @@ impl Expression { // TODO: pass the correct value here. quirks_mode: quirks_mode, for_smil_animation: false, + for_non_inherited_property: None, + rule_cache_conditions: RefCell::new(&mut conditions), }; let required_value = match self.value { diff --git a/components/style/lib.rs b/components/style/lib.rs index 8aa3ce9507a..5ae4f1633a6 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -124,6 +124,7 @@ pub mod matching; pub mod media_queries; pub mod parallel; pub mod parser; +pub mod rule_cache; pub mod rule_tree; pub mod scoped_tls; pub mod selector_map; diff --git a/components/style/properties/computed_value_flags.rs b/components/style/properties/computed_value_flags.rs index 3ee32cd89de..0e5e5ecd266 100644 --- a/components/style/properties/computed_value_flags.rs +++ b/components/style/properties/computed_value_flags.rs @@ -59,5 +59,8 @@ bitflags! { /// Whether the child explicitly inherits any reset property. const INHERITS_RESET_STYLE = 1 << 8, + + /// A flag to mark a style which is a visited style. + const IS_STYLE_IF_VISITED = 1 << 9, } } diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 965bf955127..3a046010f15 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -52,7 +52,7 @@ use gecko::values::round_border_to_device_pixels; use logical_geometry::WritingMode; use media_queries::Device; use properties::animated_properties::TransitionProperty; -use properties::computed_value_flags::ComputedValueFlags; +use properties::computed_value_flags::*; use properties::{default_font_size_keyword, longhands, FontComputationData, Importance, LonghandId}; use properties::{PropertyDeclaration, PropertyDeclarationBlock, PropertyDeclarationId}; use rule_tree::StrongRuleNode; @@ -259,6 +259,11 @@ impl ops::DerefMut for ComputedValues { } impl ComputedValuesInner { + /// Whether we're a visited style. + pub fn is_style_if_visited(&self) -> bool { + self.flags.contains(IS_STYLE_IF_VISITED) + } + #[inline] pub fn is_display_contents(&self) -> bool { self.get_box().clone_display() == longhands::display::computed_value::T::contents diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index be7bb7ea9c5..e764fcd3d74 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -316,6 +316,13 @@ _ => panic!("entered the wrong cascade_property() implementation"), }; + context.for_non_inherited_property = + % if property.style_struct.inherited: + None; + % else: + Some(LonghandId::${property.camel_case}); + % endif + % if not property.derived_from: match value { DeclaredValue::Value(specified_value) => { @@ -324,6 +331,10 @@ longhands::system_font::resolve_system_font(sf, context); } % endif + % if not property.style_struct.inherited and property.logical: + context.rule_cache_conditions.borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + % endif % if property.is_vector: // In the case of a vector property we want to pass // down an iterator so that this can be computed @@ -375,6 +386,9 @@ CSSWideKeyword::Unset | % endif CSSWideKeyword::Inherit => { + % if not property.style_struct.inherited: + context.rule_cache_conditions.borrow_mut().set_uncacheable(); + % endif % if property.ident == "font_size": longhands::font_size::cascade_inherit_font_size(context); % else: diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index b63a9329288..da1bf83bd8e 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -226,10 +226,24 @@ ${helpers.single_keyword("position", "static absolute relative fixed sticky", let ltr = context.style().writing_mode.is_bidi_ltr(); // https://drafts.csswg.org/css-logical-props/#float-clear match *self { - SpecifiedValue::inline_start if ltr => computed_value::T::left, - SpecifiedValue::inline_start => computed_value::T::right, - SpecifiedValue::inline_end if ltr => computed_value::T::right, - SpecifiedValue::inline_end => computed_value::T::left, + SpecifiedValue::inline_start => { + context.rule_cache_conditions.borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if ltr { + computed_value::T::left + } else { + computed_value::T::right + } + } + SpecifiedValue::inline_end => { + context.rule_cache_conditions.borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if ltr { + computed_value::T::right + } else { + computed_value::T::left + } + } % for value in "none left right".split(): SpecifiedValue::${value} => computed_value::T::${value}, % endfor @@ -264,10 +278,24 @@ ${helpers.single_keyword("position", "static absolute relative fixed sticky", let ltr = context.style().writing_mode.is_bidi_ltr(); // https://drafts.csswg.org/css-logical-props/#float-clear match *self { - SpecifiedValue::inline_start if ltr => computed_value::T::left, - SpecifiedValue::inline_start => computed_value::T::right, - SpecifiedValue::inline_end if ltr => computed_value::T::right, - SpecifiedValue::inline_end => computed_value::T::left, + SpecifiedValue::inline_start => { + context.rule_cache_conditions.borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if ltr { + computed_value::T::left + } else { + computed_value::T::right + } + } + SpecifiedValue::inline_end => { + context.rule_cache_conditions.borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if ltr { + computed_value::T::right + } else { + computed_value::T::left + } + } % for value in "none left right both".split(): SpecifiedValue::${value} => computed_value::T::${value}, % endfor diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 3c3f06e14be..4784d4c6482 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -16,6 +16,7 @@ use smallbitvec::SmallBitVec; use std::borrow::Cow; use hash::HashSet; use std::{fmt, mem, ops}; +use std::cell::RefCell; #[cfg(feature = "gecko")] use std::ptr; #[cfg(feature = "servo")] use cssparser::RGBA; @@ -33,6 +34,7 @@ use media_queries::Device; use parser::ParserContext; use properties::animated_properties::AnimatableLonghand; #[cfg(feature = "gecko")] use properties::longhands::system_font::SystemFont; +use rule_cache::{RuleCache, RuleCacheConditions}; use selector_parser::PseudoElement; use selectors::parser::SelectorParseError; #[cfg(feature = "servo")] use servo_config::prefs::PREFS; @@ -45,7 +47,7 @@ use values::generics::text::LineHeight; use values::computed; use values::computed::NonNegativeLength; use rule_tree::{CascadeLevel, StrongRuleNode}; -use self::computed_value_flags::ComputedValueFlags; +use self::computed_value_flags::*; use style_adjuster::StyleAdjuster; #[cfg(feature = "servo")] use values::specified::BorderStyle; @@ -625,6 +627,36 @@ impl LonghandId { 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::MozBorderBottomColors | + LonghandId::MozBorderLeftColors | + LonghandId::MozBorderRightColors | + LonghandId::MozBorderTopColors | + LonghandId::TextShadow + ) + % else: + false + % endif + } } /// An identifier for a given shorthand property. @@ -2142,6 +2174,11 @@ impl ComputedValuesInner { /// Servo for obvious reasons. pub fn has_moz_binding(&self) -> bool { false } + /// Whether we're a visited style. + pub fn is_style_if_visited(&self) -> bool { + self.flags.contains(IS_STYLE_IF_VISITED) + } + /// Returns whether this style's display value is equal to contents. /// /// Since this isn't supported in Servo, this is always false for Servo. @@ -2544,12 +2581,17 @@ pub struct StyleBuilder<'a> { /// The rule node representing the ordered list of rules matched for this /// node. - rules: Option, + pub rules: Option, custom_properties: Option>, /// The pseudo-element this style will represent. - pseudo: Option<<&'a PseudoElement>, + 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. /// @@ -2580,7 +2622,7 @@ impl<'a> StyleBuilder<'a> { custom_properties: Option>, writing_mode: WritingMode, font_size_keyword: FontComputationData, - flags: ComputedValueFlags, + mut flags: ComputedValueFlags, visited_style: Option>, ) -> Self { debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some()); @@ -2602,6 +2644,10 @@ impl<'a> StyleBuilder<'a> { reset_style }; + if cascade_flags.contains(VISITED_DEPENDENT_ONLY) { + flags.insert(IS_STYLE_IF_VISITED); + } + StyleBuilder { device, parent_style, @@ -2610,6 +2656,7 @@ impl<'a> StyleBuilder<'a> { reset_style, pseudo, rules, + modified_reset: false, custom_properties, writing_mode, font_size_keyword, @@ -2625,6 +2672,11 @@ impl<'a> StyleBuilder<'a> { } } + /// Whether we're a visited style. + pub fn is_style_if_visited(&self) -> bool { + self.flags.contains(IS_STYLE_IF_VISITED) + } + /// Creates a StyleBuilder holding only references to the structs of `s`, in /// order to create a derived style. pub fn for_derived_style( @@ -2646,6 +2698,7 @@ impl<'a> StyleBuilder<'a> { inherited_style_ignoring_first_line: inherited_style, reset_style, pseudo, + modified_reset: false, rules: None, // FIXME(emilio): Dubious... custom_properties: style_to_derive_from.custom_properties(), writing_mode: style_to_derive_from.writing_mode, @@ -2660,6 +2713,16 @@ impl<'a> StyleBuilder<'a> { } } + /// 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. @@ -2669,11 +2732,13 @@ impl<'a> StyleBuilder<'a> { % 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}(); + self.inherited_style_ignoring_first_line + .get_${property.style_struct.name_lower}(); % endif % if not property.style_struct.inherited: self.flags.insert(::properties::computed_value_flags::INHERITS_RESET_STYLE); + self.modified_reset = true; % endif % if property.ident == "content": @@ -2699,6 +2764,10 @@ impl<'a> StyleBuilder<'a> { 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, @@ -2715,6 +2784,10 @@ impl<'a> StyleBuilder<'a> { &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}( @@ -2778,11 +2851,17 @@ impl<'a> StyleBuilder<'a> { /// 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 property.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 property.style_struct.inherited: + self.modified_reset = true; + % endif self.${style_struct.ident}.take() } @@ -2831,6 +2910,16 @@ impl<'a> StyleBuilder<'a> { 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 { @@ -3015,7 +3104,9 @@ pub fn cascade( visited_style: Option>, font_metrics_provider: &FontMetricsProvider, flags: CascadeFlags, - quirks_mode: QuirksMode + quirks_mode: QuirksMode, + rule_cache: Option<<&RuleCache>, + rule_cache_conditions: &mut RuleCacheConditions, ) -> Arc { debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some()); #[cfg(feature = "gecko")] @@ -3075,6 +3166,8 @@ pub fn cascade( font_metrics_provider, flags, quirks_mode, + rule_cache, + rule_cache_conditions, ) } @@ -3093,6 +3186,8 @@ pub fn apply_declarations<'a, F, I>( font_metrics_provider: &FontMetricsProvider, flags: CascadeFlags, quirks_mode: QuirksMode, + rule_cache: Option<<&RuleCache>, + rule_cache_conditions: &mut RuleCacheConditions, ) -> Arc where F: Fn() -> I, @@ -3149,11 +3244,13 @@ where ComputedValueFlags::empty(), visited_style, ), - font_metrics_provider: font_metrics_provider, cached_system_font: None, in_media_query: false, - quirks_mode: quirks_mode, 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(); @@ -3176,6 +3273,7 @@ where // // 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 @@ -3186,6 +3284,10 @@ where for (declaration, cascade_level) in iter_declarations() { let mut declaration = match *declaration { PropertyDeclaration::WithVariables(id, ref unparsed) => { + if !id.inherited() { + context.rule_cache_conditions.borrow_mut() + .set_uncacheable(); + } Cow::Owned(unparsed.substitute_variables( id, &context.builder.custom_properties, @@ -3208,6 +3310,10 @@ where continue } + if !apply_reset && !longhand_id.inherited() { + continue; + } + // When document colors are disabled, skip properties that are // marked as ignored in that mode, if they come from a UA or // user style sheet. @@ -3375,16 +3481,16 @@ where (CASCADE_PROPERTY[discriminant])(&size, &mut context); % endif } - % endif + + if let Some(style) = rule_cache.and_then(|c| c.find(&context.builder)) { + context.builder.copy_reset_from(style); + apply_reset = false; + } + % endif // category == "early" % endfor let mut builder = context.builder; - { - StyleAdjuster::new(&mut builder) - .adjust(layout_parent_style, flags); - } - % if product == "gecko": if let Some(ref mut bg) = builder.get_background_if_mutated() { bg.fill_arrays(); @@ -3404,6 +3510,22 @@ where } % endif + builder.clear_modified_reset(); + + StyleAdjuster::new(&mut builder) + .adjust(layout_parent_style, 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() } diff --git a/components/style/rule_cache.rs b/components/style/rule_cache.rs new file mode 100644 index 00000000000..7862414f530 --- /dev/null +++ b/components/style/rule_cache.rs @@ -0,0 +1,159 @@ +/* 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/. */ + +//! A cache from rule node to computed values, in order to cache reset +//! properties. + +use fnv::FnvHashMap; +use logical_geometry::WritingMode; +use properties::{ComputedValues, StyleBuilder}; +use rule_tree::StrongRuleNode; +use selector_parser::PseudoElement; +use servo_arc::Arc; +use smallvec::SmallVec; +use values::computed::NonNegativeLength; + +/// The conditions for caching and matching a style in the rule cache. +#[derive(Clone, Debug, Default)] +pub struct RuleCacheConditions { + uncacheable: bool, + font_size: Option, + writing_mode: Option, +} + +impl RuleCacheConditions { + /// Sets the style as depending in the font-size value. + pub fn set_font_size_dependency(&mut self, font_size: NonNegativeLength) { + debug_assert!(self.font_size.map_or(true, |f| f == font_size)); + self.font_size = Some(font_size); + } + + /// Sets the style as uncacheable. + pub fn set_uncacheable(&mut self) { + self.uncacheable = true; + } + + /// Sets the style as depending in the writing-mode value `writing_mode`. + pub fn set_writing_mode_dependency(&mut self, writing_mode: WritingMode) { + debug_assert!(self.writing_mode.map_or(true, |wm| wm == writing_mode)); + self.writing_mode = Some(writing_mode); + } + + /// Returns whether the current style's reset properties are cacheable. + fn cacheable(&self) -> bool { + !self.uncacheable + } + + /// Returns whether `style` matches the conditions. + fn matches(&self, style: &StyleBuilder) -> bool { + if self.uncacheable { + return false; + } + + if let Some(fs) = self.font_size { + if style.get_font().clone_font_size() != fs { + return false; + } + } + + if let Some(wm) = self.writing_mode { + if style.writing_mode != wm { + return false; + } + } + + true + } +} + +/// A TLS cache from rules matched to computed values. +pub struct RuleCache { + // FIXME(emilio): Consider using LRUCache or something like that? + map: FnvHashMap); 1]>>, +} + +impl RuleCache { + /// Creates an empty `RuleCache`. + pub fn new() -> Self { + Self { + map: FnvHashMap::default(), + } + } + + /// Finds a node in the properties matched cache. + /// + /// This needs to receive a `StyleBuilder` with the `early` properties + /// already applied. + pub fn find( + &self, + builder_with_early_props: &StyleBuilder, + ) -> Option<&ComputedValues> { + if builder_with_early_props.is_style_if_visited() { + // FIXME(emilio): We can probably do better, does it matter much? + return None; + } + + // A pseudo-element with property restrictions can result in different + // computed values if it's also used for a non-pseudo. + if builder_with_early_props.pseudo + .and_then(|p| p.property_restriction()) + .is_some() { + return None; + } + + let rules = match builder_with_early_props.rules { + Some(ref rules) => rules, + None => return None, + }; + + self.map.get(rules).and_then(|cached_values| { + for &(ref conditions, ref values) in cached_values.iter() { + if conditions.matches(builder_with_early_props) { + debug!("Using cached reset style with conditions {:?}", conditions); + return Some(&**values) + } + } + None + }) + } + + /// Inserts a node into the rules cache if possible. + /// + /// Returns whether the style was inserted into the cache. + pub fn insert_if_possible( + &mut self, + style: &Arc, + pseudo: Option<&PseudoElement>, + conditions: &RuleCacheConditions, + ) -> bool { + if !conditions.cacheable() { + return false; + } + + if style.is_style_if_visited() { + // FIXME(emilio): We can probably do better, does it matter much? + return false; + } + + // A pseudo-element with property restrictions can result in different + // computed values if it's also used for a non-pseudo. + if pseudo.and_then(|p| p.property_restriction()).is_some() { + return false; + } + + let rules = match style.rules { + Some(ref r) => r.clone(), + None => return false, + }; + + debug!("Inserting cached reset style with conditions {:?}", conditions); + self.map + .entry(rules) + .or_insert_with(SmallVec::new) + .push((conditions.clone(), style.clone())); + + true + } + +} diff --git a/components/style/rule_tree/mod.rs b/components/style/rule_tree/mod.rs index 81e51ecc5e1..51d70913e66 100644 --- a/components/style/rule_tree/mod.rs +++ b/components/style/rule_tree/mod.rs @@ -819,7 +819,7 @@ struct WeakRuleNode { } /// A strong reference to a rule node. -#[derive(Debug, PartialEq)] +#[derive(Debug, Eq, Hash, PartialEq)] pub struct StrongRuleNode { p: NonZeroPtrMut, } diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index dff545e1746..244a67871fa 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -13,7 +13,9 @@ use media_queries::MediaType; use parser::ParserContext; use properties::{ComputedValues, StyleBuilder}; use properties::longhands::font_size; +use rule_cache::RuleCacheConditions; use selectors::parser::SelectorParseError; +use std::cell::RefCell; use std::fmt; use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering}; use style_traits::{CSSPixel, DevicePixel, ToCss, ParseError}; @@ -244,6 +246,7 @@ pub enum Range { impl Range { fn to_computed_range(&self, device: &Device, quirks_mode: QuirksMode) -> Range { let default_values = device.default_computed_values(); + let mut conditions = RuleCacheConditions::default(); // http://dev.w3.org/csswg/mediaqueries3/#units // em units are relative to the initial font-size. let context = computed::Context { @@ -257,6 +260,8 @@ impl Range { cached_system_font: None, quirks_mode: quirks_mode, for_smil_animation: false, + for_non_inherited_property: None, + rule_cache_conditions: RefCell::new(&mut conditions), }; match *self { diff --git a/components/style/style_resolver.rs b/components/style/style_resolver.rs index f236d9dfb1f..1a993e15662 100644 --- a/components/style/style_resolver.rs +++ b/components/style/style_resolver.rs @@ -574,10 +574,13 @@ where } let implemented_pseudo = self.element.implemented_pseudo_element(); + let pseudo = pseudo.or(implemented_pseudo.as_ref()); + + let mut conditions = Default::default(); let values = cascade( self.context.shared.stylist.device(), - pseudo.or(implemented_pseudo.as_ref()), + pseudo, rules.unwrap_or(self.context.shared.stylist.rule_tree().root()), &self.context.shared.guards, parent_style, @@ -587,8 +590,15 @@ where &self.context.thread_local.font_metrics_provider, cascade_flags, self.context.shared.quirks_mode(), + Some(&self.context.thread_local.rule_cache), + &mut conditions, ); + self.context + .thread_local + .rule_cache + .insert_if_possible(&values, pseudo, &conditions); + values } } diff --git a/components/style/stylesheets/viewport_rule.rs b/components/style/stylesheets/viewport_rule.rs index a3dd8183e0a..bfa41138dc9 100644 --- a/components/style/stylesheets/viewport_rule.rs +++ b/components/style/stylesheets/viewport_rule.rs @@ -17,10 +17,12 @@ use font_metrics::get_metrics_provider_for_product; use media_queries::Device; use parser::{ParserContext, ParserErrorContext}; use properties::StyleBuilder; +use rule_cache::RuleCacheConditions; use selectors::parser::SelectorParseError; use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; use std::ascii::AsciiExt; use std::borrow::Cow; +use std::cell::RefCell; use std::fmt; use std::iter::Enumerate; use std::str::Chars; @@ -707,6 +709,7 @@ impl MaybeNew for ViewportConstraints { let default_values = device.default_computed_values(); + let mut conditions = RuleCacheConditions::default(); let context = Context { is_root_element: false, builder: StyleBuilder::for_derived_style(device, default_values, None, None), @@ -715,6 +718,8 @@ impl MaybeNew for ViewportConstraints { in_media_query: false, quirks_mode: quirks_mode, for_smil_animation: false, + for_non_inherited_property: None, + rule_cache_conditions: RefCell::new(&mut conditions), }; // DEVICE-ADAPT ยง 9.3 Resolving 'extend-to-zoom' diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 5027889a453..d5ae300bb88 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -771,6 +771,8 @@ impl Stylist { font_metrics, cascade_flags, self.quirks_mode, + /* rule_cache = */ None, + &mut Default::default(), ) } @@ -987,6 +989,8 @@ impl Stylist { font_metrics, cascade_flags, self.quirks_mode, + /* rule_cache = */ None, + &mut Default::default(), )) } else { None @@ -1012,6 +1016,8 @@ impl Stylist { font_metrics, cascade_flags, self.quirks_mode, + /* rule_cache = */ None, + &mut Default::default(), ) } @@ -1625,6 +1631,8 @@ impl Stylist { &metrics, CascadeFlags::empty(), self.quirks_mode, + /* rule_cache = */ None, + &mut Default::default(), ) } diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index 344bf4ca68e..3e85339f875 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -866,6 +866,10 @@ impl ToComputedValue for specified::MozLength { MozLength::LengthOrPercentageOrAuto(lopoa.to_computed_value(context)) } specified::MozLength::ExtremumLength(ref ext) => { + debug_assert!(context.for_non_inherited_property.is_some(), + "should check whether we're a non-inherited property"); + context.rule_cache_conditions.borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); MozLength::ExtremumLength(ext.clone()) } } diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index cd767a3fc3f..a8599991943 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -11,10 +11,12 @@ use font_metrics::FontMetricsProvider; use media_queries::Device; #[cfg(feature = "gecko")] use properties; -use properties::{ComputedValues, StyleBuilder}; +use properties::{ComputedValues, LonghandId, StyleBuilder}; +use rule_cache::RuleCacheConditions; #[cfg(feature = "servo")] use servo_url::ServoUrl; use std::{f32, fmt}; +use std::cell::RefCell; #[cfg(feature = "servo")] use std::sync::Arc; use style_traits::ToCss; @@ -116,6 +118,17 @@ pub struct Context<'a> { /// This is used to allow certain properties to generate out-of-range /// values, which SMIL allows. pub for_smil_animation: bool, + + /// The property we are computing a value for, if it is a non-inherited + /// property. None if we are computed a value for an inherited property + /// or not computing for a property at all (e.g. in a media query + /// evaluation). + pub for_non_inherited_property: Option, + + /// The conditions to cache a rule node on the rule cache. + /// + /// FIXME(emilio): Drop the refcell. + pub rule_cache_conditions: RefCell<&'a mut RuleCacheConditions>, } impl<'a> Context<'a> { diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 2f2ace7f6e2..df4b0565182 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -249,18 +249,36 @@ fn convert_nscolor_to_computedcolor(color: nscolor) -> ComputedColor { impl ToComputedValue for Color { type ComputedValue = ComputedColor; - fn to_computed_value(&self, _context: &Context) -> ComputedColor { + fn to_computed_value(&self, context: &Context) -> ComputedColor { match *self { - Color::CurrentColor => ComputedColor::currentcolor(), + Color::CurrentColor => { + if let Some(longhand) = context.for_non_inherited_property { + if longhand.stores_complex_colors_lossily() { + context.rule_cache_conditions.borrow_mut() + .set_uncacheable(); + } + } + ComputedColor::currentcolor() + } Color::Numeric { ref parsed, .. } => ComputedColor::rgba(*parsed), - Color::Complex(ref complex) => *complex, + Color::Complex(ref complex) => { + if complex.foreground_ratio != 0 { + if let Some(longhand) = context.for_non_inherited_property { + if longhand.stores_complex_colors_lossily() { + context.rule_cache_conditions.borrow_mut() + .set_uncacheable(); + } + } + } + *complex + } #[cfg(feature = "gecko")] Color::System(system) => - convert_nscolor_to_computedcolor(system.to_computed_value(_context)), + convert_nscolor_to_computedcolor(system.to_computed_value(context)), #[cfg(feature = "gecko")] Color::Special(special) => { use self::gecko::SpecialColorKeyword as Keyword; - let pres_context = _context.device().pres_context(); + let pres_context = context.device().pres_context(); convert_nscolor_to_computedcolor(match special { Keyword::MozDefaultColor => pres_context.mDefaultColor, Keyword::MozDefaultBackgroundColor => pres_context.mBackgroundColor, @@ -274,7 +292,7 @@ impl ToComputedValue for Color { use dom::TElement; use gecko::wrapper::GeckoElement; use gecko_bindings::bindings::Gecko_GetBody; - let pres_context = _context.device().pres_context(); + let pres_context = context.device().pres_context(); let body = unsafe { Gecko_GetBody(pres_context) }.map(GeckoElement); let data = body.as_ref().and_then(|wrap| wrap.borrow_data()); if let Some(data) = data { diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 1ca4839904e..c4da56cf600 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -132,9 +132,18 @@ impl FontRelativeLength { match *self { FontRelativeLength::Em(length) => { + if !matches!(base_size, FontBaseSize::InheritedStyle) { + context.rule_cache_conditions.borrow_mut() + .set_font_size_dependency( + reference_font_size.into() + ); + } (reference_font_size, length) }, FontRelativeLength::Ex(length) => { + if context.for_non_inherited_property.is_some() { + context.rule_cache_conditions.borrow_mut().set_uncacheable(); + } let reference_size = match query_font_metrics(context, reference_font_size) { FontMetricsQueryResult::Available(metrics) => { metrics.x_height @@ -152,6 +161,9 @@ impl FontRelativeLength { (reference_size, length) }, FontRelativeLength::Ch(length) => { + if context.for_non_inherited_property.is_some() { + context.rule_cache_conditions.borrow_mut().set_uncacheable(); + } let reference_size = match query_font_metrics(context, reference_font_size) { FontMetricsQueryResult::Available(metrics) => { metrics.zero_advance_measure diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index ad1209dd287..2a0095d3d6e 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -9,6 +9,7 @@ use malloc_size_of::MallocSizeOfOps; use selectors::Element; use selectors::matching::{MatchingContext, MatchingMode, matches_selector}; use servo_arc::{Arc, ArcBorrow, RawOffsetArc}; +use std::cell::RefCell; use std::env; use std::fmt::Write; use std::iter; @@ -116,6 +117,7 @@ use style::properties::PROHIBIT_DISPLAY_CONTENTS; use style::properties::animated_properties::{AnimatableLonghand, AnimationValue}; use style::properties::animated_properties::compare_property_priority; use style::properties::parse_one_declaration_into; +use style::rule_cache::RuleCacheConditions; use style::rule_tree::{CascadeLevel, StyleSource}; use style::selector_parser::PseudoElementCascadeType; use style::shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard, Locked}; @@ -3186,6 +3188,7 @@ fn create_context<'a>( parent_style: Option<&'a ComputedValues>, pseudo: Option<&'a PseudoElement>, for_smil_animation: bool, + rule_cache_conditions: &'a mut RuleCacheConditions, ) -> Context<'a> { Context { is_root_element: false, @@ -3200,6 +3203,8 @@ fn create_context<'a>( in_media_query: false, quirks_mode: per_doc_data.stylist.quirks_mode(), for_smil_animation, + for_non_inherited_property: None, + rule_cache_conditions: RefCell::new(rule_cache_conditions), } } @@ -3269,6 +3274,7 @@ pub extern "C" fn Servo_GetComputedKeyframeValues(keyframes: RawGeckoKeyframeLis let parent_style = parent_data.as_ref().map(|d| d.styles.primary()).map(|x| &**x); let pseudo = style.pseudo(); + let mut conditions = Default::default(); let mut context = create_context( &data, &metrics, @@ -3276,6 +3282,7 @@ pub extern "C" fn Servo_GetComputedKeyframeValues(keyframes: RawGeckoKeyframeLis parent_style, pseudo.as_ref(), /* for_smil_animation = */ false, + &mut conditions, ); let global_style_data = &*GLOBAL_STYLE_DATA; @@ -3356,13 +3363,15 @@ pub extern "C" fn Servo_GetAnimationValues(declarations: RawServoDeclarationBloc let parent_style = parent_data.as_ref().map(|d| d.styles.primary()).map(|x| &**x); let pseudo = style.pseudo(); + let mut conditions = Default::default(); let mut context = create_context( &data, &metrics, &style, parent_style, pseudo.as_ref(), - /* for_smil_animation = */ true + /* for_smil_animation = */ true, + &mut conditions, ); let default_values = data.default_computed_values(); @@ -3392,13 +3401,15 @@ pub extern "C" fn Servo_AnimationValue_Compute(element: RawGeckoElementBorrowed, let parent_style = parent_data.as_ref().map(|d| d.styles.primary()).map(|x| &**x); let pseudo = style.pseudo(); + let mut conditions = Default::default(); let mut context = create_context( &data, &metrics, style, parent_style, pseudo.as_ref(), - /* for_smil_animation = */ false + /* for_smil_animation = */ false, + &mut conditions, ); let default_values = data.default_computed_values();