diff options
Diffstat (limited to 'components/style/context.rs')
-rw-r--r-- | components/style/context.rs | 451 |
1 files changed, 447 insertions, 4 deletions
diff --git a/components/style/context.rs b/components/style/context.rs index 96b84ad2ed3..33ff5d61697 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -7,9 +7,10 @@ #[cfg(feature = "servo")] use animation::Animation; use animation::PropertyAnimation; use app_units::Au; +use arrayvec::ArrayVec; use bloom::StyleBloom; use cache::LRUCache; -use data::ElementData; +use data::{EagerPseudoStyles, ElementData}; use dom::{OpaqueNode, TNode, TElement, SendElement}; use error_reporting::ParseErrorReporter; use euclid::Size2D; @@ -17,9 +18,10 @@ use fnv::FnvHashMap; use font_metrics::FontMetricsProvider; #[cfg(feature = "gecko")] use gecko_bindings::structs; #[cfg(feature = "servo")] use parking_lot::RwLock; -#[cfg(feature = "gecko")] use properties::ComputedValues; -use selector_parser::SnapshotMap; -use selectors::matching::ElementSelectorFlags; +use properties::ComputedValues; +use rule_tree::StrongRuleNode; +use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, SnapshotMap}; +use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode}; use shared_lock::StylesheetGuards; use sharing::{ValidationData, StyleSharingCandidateCache}; use std::fmt; @@ -141,6 +143,423 @@ impl<'a> SharedStyleContext<'a> { } } +/// The structure holds various intermediate inputs that are eventually used by +/// by the cascade. +/// +/// The matching and cascading process stores them in this format temporarily +/// within the `CurrentElementInfo`. At the end of the cascade, they are folded +/// down into the main `ComputedValues` to reduce memory usage per element while +/// still remaining accessible. +#[derive(Clone)] +pub struct CascadeInputs { + /// The rule node representing the ordered list of rules matched for this + /// node. + rules: Option<StrongRuleNode>, + + /// The rule node representing the ordered list of rules matched for this + /// node 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_rules: Option<StrongRuleNode>, + + /// The element's computed values if visited, only computed if there's a + /// relevant link for this element. A element's "relevant link" is the + /// element being matched if it is a link or the nearest ancestor link. + /// + /// We also store a reference to this inside the regular ComputedValues to + /// avoid refactoring all APIs to become aware of multiple ComputedValues + /// objects. + visited_values: Option<Arc<ComputedValues>>, +} + +impl Default for CascadeInputs { + fn default() -> Self { + CascadeInputs { + rules: None, + visited_rules: None, + visited_values: None, + } + } +} + +impl CascadeInputs { + /// Construct inputs from previous cascade results, if any. + fn new_from_style(style: &Arc<ComputedValues>) -> Self { + CascadeInputs { + rules: style.rules.clone(), + visited_rules: style.get_visited_style().and_then(|v| v.rules.clone()), + // Values will be re-cascaded if necessary, so this can be None. + visited_values: None, + } + } + + /// Whether there are any rules. Rules will be present after unvisited + /// matching or pulled from a previous cascade if no matching is expected. + pub fn has_rules(&self) -> bool { + self.rules.is_some() + } + + /// Gets a mutable reference to the rule node, if any. + pub fn get_rules_mut(&mut self) -> Option<&mut StrongRuleNode> { + self.rules.as_mut() + } + + /// Gets a reference to the rule node. Panic if the element does not have + /// rule node. + pub fn rules(&self) -> &StrongRuleNode { + self.rules.as_ref().unwrap() + } + + /// Sets the rule node depending on visited mode. + /// Returns whether the rules changed. + pub fn set_rules(&mut self, + visited_handling: VisitedHandlingMode, + rules: StrongRuleNode) + -> bool { + match visited_handling { + VisitedHandlingMode::AllLinksVisitedAndUnvisited => { + unreachable!("We should never try to selector match with \ + AllLinksVisitedAndUnvisited"); + }, + VisitedHandlingMode::AllLinksUnvisited => self.set_unvisited_rules(rules), + VisitedHandlingMode::RelevantLinkVisited => self.set_visited_rules(rules), + } + } + + /// Sets the unvisited rule node, and returns whether it changed. + fn set_unvisited_rules(&mut self, rules: StrongRuleNode) -> bool { + if let Some(ref old_rules) = self.rules { + if *old_rules == rules { + return false + } + } + self.rules = Some(rules); + true + } + + /// Whether there are any visited rules. Visited rules will be present + /// after visited matching or pulled from a previous cascade (assuming there + /// was a relevant link at the time) if no matching is expected. + pub fn has_visited_rules(&self) -> bool { + self.visited_rules.is_some() + } + + /// Gets a reference to the visited rule node, if any. + pub fn get_visited_rules(&self) -> Option<&StrongRuleNode> { + self.visited_rules.as_ref() + } + + /// Gets a mutable reference to the visited rule node, if any. + pub fn get_visited_rules_mut(&mut self) -> Option<&mut StrongRuleNode> { + self.visited_rules.as_mut() + } + + /// Gets a reference to the visited rule node. Panic if the element does not + /// have visited rule node. + pub fn visited_rules(&self) -> &StrongRuleNode { + self.visited_rules.as_ref().unwrap() + } + + /// Sets the visited rule node, and returns whether it changed. + fn set_visited_rules(&mut self, rules: StrongRuleNode) -> bool { + if let Some(ref old_rules) = self.visited_rules { + if *old_rules == rules { + return false + } + } + self.visited_rules = Some(rules); + true + } + + /// Takes the visited rule node. + pub fn take_visited_rules(&mut self) -> Option<StrongRuleNode> { + self.visited_rules.take() + } + + /// Gets a reference to the visited computed values. Panic if the element + /// does not have visited computed values. + pub fn visited_values(&self) -> &Arc<ComputedValues> { + self.visited_values.as_ref().unwrap() + } + + /// Sets the visited computed values. + pub fn set_visited_values(&mut self, values: Arc<ComputedValues>) { + self.visited_values = Some(values); + } + + /// Take the visited computed values. + pub fn take_visited_values(&mut self) -> Option<Arc<ComputedValues>> { + self.visited_values.take() + } + + /// Clone the visited computed values Arc. Used to store a reference to the + /// visited values inside the regular values. + pub fn clone_visited_values(&self) -> Option<Arc<ComputedValues>> { + self.visited_values.clone() + } +} + +// We manually implement Debug for CascadeInputs so that we can avoid the +// verbose stringification of ComputedValues for normal logging. +impl fmt::Debug for CascadeInputs { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "CascadeInputs {{ rules: {:?}, visited_rules: {:?}, .. }}", + self.rules, self.visited_rules) + } +} + +/// A list of cascade inputs for eagerly-cascaded pseudo-elements. +/// The list is stored inline. +#[derive(Debug)] +pub struct EagerPseudoCascadeInputs(Option<[Option<CascadeInputs>; EAGER_PSEUDO_COUNT]>); + +// Manually implement `Clone` here because the derived impl of `Clone` for +// array types assumes the value inside is `Copy`. +impl Clone for EagerPseudoCascadeInputs { + fn clone(&self) -> Self { + if self.0.is_none() { + return EagerPseudoCascadeInputs(None) + } + let self_inputs = self.0.as_ref().unwrap(); + let mut inputs: [Option<CascadeInputs>; EAGER_PSEUDO_COUNT] = Default::default(); + for i in 0..EAGER_PSEUDO_COUNT { + inputs[i] = self_inputs[i].clone(); + } + EagerPseudoCascadeInputs(Some(inputs)) + } +} + +impl EagerPseudoCascadeInputs { + /// Construct inputs from previous cascade results, if any. + fn new_from_style(styles: &EagerPseudoStyles) -> Self { + EagerPseudoCascadeInputs(styles.0.as_ref().map(|styles| { + let mut inputs: [Option<CascadeInputs>; EAGER_PSEUDO_COUNT] = Default::default(); + for i in 0..EAGER_PSEUDO_COUNT { + inputs[i] = styles[i].as_ref().map(|s| CascadeInputs::new_from_style(s)); + } + inputs + })) + } + + /// Returns whether there are any pseudo inputs. + pub fn is_empty(&self) -> bool { + self.0.is_none() + } + + /// Returns a reference to the inputs for a given eager pseudo, if they exist. + pub fn get(&self, pseudo: &PseudoElement) -> Option<&CascadeInputs> { + debug_assert!(pseudo.is_eager()); + self.0.as_ref().and_then(|p| p[pseudo.eager_index()].as_ref()) + } + + /// Returns a mutable reference to the inputs for a given eager pseudo, if they exist. + pub fn get_mut(&mut self, pseudo: &PseudoElement) -> Option<&mut CascadeInputs> { + debug_assert!(pseudo.is_eager()); + self.0.as_mut().and_then(|p| p[pseudo.eager_index()].as_mut()) + } + + /// Returns true if the EagerPseudoCascadeInputs has a inputs for |pseudo|. + pub fn has(&self, pseudo: &PseudoElement) -> bool { + self.get(pseudo).is_some() + } + + /// Inserts a pseudo-element. The pseudo-element must not already exist. + pub fn insert(&mut self, pseudo: &PseudoElement, inputs: CascadeInputs) { + debug_assert!(!self.has(pseudo)); + if self.0.is_none() { + self.0 = Some(Default::default()); + } + self.0.as_mut().unwrap()[pseudo.eager_index()] = Some(inputs); + } + + /// Removes a pseudo-element inputs if they exist, and returns it. + pub fn take(&mut self, pseudo: &PseudoElement) -> Option<CascadeInputs> { + let result = match self.0.as_mut() { + None => return None, + Some(arr) => arr[pseudo.eager_index()].take(), + }; + let empty = self.0.as_ref().unwrap().iter().all(|x| x.is_none()); + if empty { + self.0 = None; + } + result + } + + /// Returns a list of the pseudo-elements. + pub fn keys(&self) -> ArrayVec<[PseudoElement; EAGER_PSEUDO_COUNT]> { + let mut v = ArrayVec::new(); + if let Some(ref arr) = self.0 { + for i in 0..EAGER_PSEUDO_COUNT { + if arr[i].is_some() { + v.push(PseudoElement::from_eager_index(i)); + } + } + } + v + } + + /// Adds the unvisited rule node for a given pseudo-element, which may or + /// may not exist. + /// + /// Returns true if the pseudo-element is new. + fn add_unvisited_rules(&mut self, + pseudo: &PseudoElement, + rules: StrongRuleNode) + -> bool { + if let Some(mut inputs) = self.get_mut(pseudo) { + inputs.set_unvisited_rules(rules); + return false + } + let mut inputs = CascadeInputs::default(); + inputs.set_unvisited_rules(rules); + self.insert(pseudo, inputs); + true + } + + /// Remove the unvisited rule node for a given pseudo-element, which may or + /// may not exist. Since removing the rule node implies we don't need any + /// other data for the pseudo, take the entire pseudo if found. + /// + /// Returns true if the pseudo-element was removed. + fn remove_unvisited_rules(&mut self, pseudo: &PseudoElement) -> bool { + self.take(pseudo).is_some() + } + + /// Adds the visited rule node for a given pseudo-element. It is assumed to + /// already exist because unvisited inputs should have been added first. + /// + /// Returns true if the pseudo-element is new. (Always false, but returns a + /// bool for parity with `add_unvisited_rules`.) + fn add_visited_rules(&mut self, + pseudo: &PseudoElement, + rules: StrongRuleNode) + -> bool { + debug_assert!(self.has(pseudo)); + let mut inputs = self.get_mut(pseudo).unwrap(); + inputs.set_visited_rules(rules); + false + } + + /// Remove the visited rule node for a given pseudo-element, which may or + /// may not exist. + /// + /// Returns true if the psuedo-element was removed. (Always false, but + /// returns a bool for parity with `remove_unvisited_rules`.) + fn remove_visited_rules(&mut self, pseudo: &PseudoElement) -> bool { + if let Some(mut inputs) = self.get_mut(pseudo) { + inputs.take_visited_rules(); + } + false + } + + /// Adds a rule node for a given pseudo-element, which may or may not exist. + /// The type of rule node depends on the visited mode. + /// + /// Returns true if the pseudo-element is new. + pub fn add_rules(&mut self, + pseudo: &PseudoElement, + visited_handling: VisitedHandlingMode, + rules: StrongRuleNode) + -> bool { + match visited_handling { + VisitedHandlingMode::AllLinksVisitedAndUnvisited => { + unreachable!("We should never try to selector match with \ + AllLinksVisitedAndUnvisited"); + }, + VisitedHandlingMode::AllLinksUnvisited => { + self.add_unvisited_rules(&pseudo, rules) + }, + VisitedHandlingMode::RelevantLinkVisited => { + self.add_visited_rules(&pseudo, rules) + }, + } + } + + /// Removes a rule node for a given pseudo-element, which may or may not + /// exist. The type of rule node depends on the visited mode. + /// + /// Returns true if the psuedo-element was removed. + pub fn remove_rules(&mut self, + pseudo: &PseudoElement, + visited_handling: VisitedHandlingMode) + -> bool { + match visited_handling { + VisitedHandlingMode::AllLinksVisitedAndUnvisited => { + unreachable!("We should never try to selector match with \ + AllLinksVisitedAndUnvisited"); + }, + VisitedHandlingMode::AllLinksUnvisited => { + self.remove_unvisited_rules(&pseudo) + }, + VisitedHandlingMode::RelevantLinkVisited => { + self.remove_visited_rules(&pseudo) + }, + } + } +} + +/// The cascade inputs associated with a node, including those for any +/// pseudo-elements. +/// +/// The matching and cascading process stores them in this format temporarily +/// within the `CurrentElementInfo`. At the end of the cascade, they are folded +/// down into the main `ComputedValues` to reduce memory usage per element while +/// still remaining accessible. +#[derive(Clone, Debug)] +pub struct ElementCascadeInputs { + /// The element's cascade inputs. + pub primary: Option<CascadeInputs>, + /// A list of the inputs for the element's eagerly-cascaded pseudo-elements. + pub pseudos: EagerPseudoCascadeInputs, +} + +impl Default for ElementCascadeInputs { + /// Construct an empty `ElementCascadeInputs`. + fn default() -> Self { + ElementCascadeInputs { + primary: None, + pseudos: EagerPseudoCascadeInputs(None), + } + } +} + +impl ElementCascadeInputs { + /// Construct inputs from previous cascade results, if any. + pub fn new_from_element_data(data: &ElementData) -> Self { + if !data.has_styles() { + return ElementCascadeInputs::default() + } + ElementCascadeInputs { + primary: Some(CascadeInputs::new_from_style(data.styles.primary())), + pseudos: EagerPseudoCascadeInputs::new_from_style(&data.styles.pseudos), + } + } + + /// Returns whether we have primary inputs. + pub fn has_primary(&self) -> bool { + self.primary.is_some() + } + + /// Gets the primary inputs. Panic if unavailable. + pub fn primary(&self) -> &CascadeInputs { + self.primary.as_ref().unwrap() + } + + /// Gets the mutable primary inputs. Panic if unavailable. + pub fn primary_mut(&mut self) -> &mut CascadeInputs { + self.primary.as_mut().unwrap() + } + + /// Ensure primary inputs exist and create them if they do not. + /// Returns a mutable reference to the primary inputs. + pub fn ensure_primary(&mut self) -> &mut CascadeInputs { + if self.primary.is_none() { + self.primary = Some(CascadeInputs::default()); + } + self.primary.as_mut().unwrap() + } +} + /// Information about the current element being processed. We group this /// together into a single struct within ThreadLocalStyleContext so that we can /// instantiate and destroy it easily at the beginning and end of element @@ -157,6 +576,11 @@ pub struct CurrentElementInfo { /// A Vec of possibly expired animations. Used only by Servo. #[allow(dead_code)] pub possibly_expired_animations: Vec<PropertyAnimation>, + /// Temporary storage for various intermediate inputs that are eventually + /// used by by the cascade. At the end of the cascade, they are folded down + /// into the main `ComputedValues` to reduce memory usage per element while + /// still remaining accessible. + pub cascade_inputs: ElementCascadeInputs, } /// Statistics gathered during the traversal. We gather statistics on each @@ -454,6 +878,7 @@ impl<E: TElement> ThreadLocalStyleContext<E> { is_initial_style: !data.has_styles(), validation_data: ValidationData::default(), possibly_expired_animations: Vec::new(), + cascade_inputs: ElementCascadeInputs::default(), }); } @@ -498,6 +923,24 @@ pub struct StyleContext<'a, E: TElement + 'a> { pub thread_local: &'a mut ThreadLocalStyleContext<E>, } +impl<'a, E: TElement + 'a> StyleContext<'a, E> { + /// Returns a reference to the cascade inputs. Panics if there is no + /// `CurrentElementInfo`. + pub fn cascade_inputs(&self) -> &ElementCascadeInputs { + &self.thread_local.current_element_info + .as_ref().unwrap() + .cascade_inputs + } + + /// Returns a mutable reference to the cascade inputs. Panics if there is + /// no `CurrentElementInfo`. + pub fn cascade_inputs_mut(&mut self) -> &mut ElementCascadeInputs { + &mut self.thread_local.current_element_info + .as_mut().unwrap() + .cascade_inputs + } +} + /// Why we're doing reflow. #[derive(PartialEq, Copy, Clone, Debug)] pub enum ReflowGoal { |