/* 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/. */ //! Types and traits used to access the DOM from style calculation. #![allow(unsafe_code)] use {Atom, Namespace, LocalName}; use atomic_refcell::{AtomicRef, AtomicRefCell}; use data::{ElementStyles, ElementData}; use element_state::ElementState; use parking_lot::RwLock; use properties::{ComputedValues, PropertyDeclarationBlock}; use properties::longhands::display::computed_value as display; use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint}; use selector_parser::{ElementExt, PseudoElement, RestyleDamage}; use sink::Push; use std::fmt::Debug; use std::ops::BitOr; use std::sync::Arc; use stylist::ApplicableDeclarationBlock; use traversal::DomTraversalContext; use util::opts; pub use style_traits::UnsafeNode; /// An opaque handle to a node, which, unlike UnsafeNode, cannot be transformed /// back into a non-opaque representation. The only safe operation that can be /// performed on this node is to compare it to another opaque handle or to another /// OpaqueNode. /// /// Layout and Graphics use this to safely represent nodes for comparison purposes. /// Because the script task's GC does not trace layout, node data cannot be safely stored in layout /// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for /// locality reasons. Using `OpaqueNode` enforces this invariant. #[derive(Clone, PartialEq, Copy, Debug, Hash, Eq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))] pub struct OpaqueNode(pub usize); impl OpaqueNode { /// Returns the address of this node, for debugging purposes. #[inline] pub fn id(&self) -> usize { self.0 } } #[derive(Clone, Copy, PartialEq)] pub enum StylingMode { /// The node has never been styled before, and needs a full style computation. Initial, /// The node has been styled before, but needs some amount of recomputation. Restyle, /// The node does not need any style processing, but one or more of its /// descendants do. Traverse, /// No nodes in this subtree require style processing. Stop, } pub trait TRestyleDamage : Debug + PartialEq + BitOr + Copy { /// The source for our current computed values in the cascade. This is a /// ComputedValues in Servo and a StyleContext in Gecko. /// /// This is needed because Gecko has a few optimisations for the calculation /// of the difference depending on which values have been used during /// layout. /// /// This should be obtained via TNode::existing_style_for_restyle_damage type PreExistingComputedValues; fn compute(old: &Self::PreExistingComputedValues, new: &Arc) -> Self; fn empty() -> Self; fn rebuild_and_reflow() -> Self; } /// Simple trait to provide basic information about the type of an element. /// /// We avoid exposing the full type id, since computing it in the general case /// would be difficult for Gecko nodes. pub trait NodeInfo { fn is_element(&self) -> bool; fn is_text_node(&self) -> bool; // Comments, doctypes, etc are ignored by layout algorithms. fn needs_layout(&self) -> bool { self.is_element() || self.is_text_node() } } pub struct LayoutIterator(pub T); impl Iterator for LayoutIterator where T: Iterator, I: NodeInfo { type Item = I; fn next(&mut self) -> Option { loop { // Filter out nodes that layout should ignore. let n = self.0.next(); if n.is_none() || n.as_ref().unwrap().needs_layout() { return n } } } } pub trait TNode : Sized + Copy + Clone + NodeInfo { type ConcreteElement: TElement; type ConcreteChildrenIterator: Iterator; fn to_unsafe(&self) -> UnsafeNode; unsafe fn from_unsafe(n: &UnsafeNode) -> Self; fn dump(self); fn dump_style(self); /// Returns an iterator over this node's children. fn children(self) -> LayoutIterator; /// Converts self into an `OpaqueNode`. fn opaque(&self) -> OpaqueNode; /// While doing a reflow, the node at the root has no parent, as far as we're /// concerned. This method returns `None` at the reflow root. fn layout_parent_element(self, reflow_root: OpaqueNode) -> Option; fn debug_id(self) -> usize; fn as_element(&self) -> Option; fn needs_dirty_on_viewport_size_changed(&self) -> bool; unsafe fn set_dirty_on_viewport_size_changed(&self); fn can_be_fragmented(&self) -> bool; unsafe fn set_can_be_fragmented(&self, value: bool); fn parent_node(&self) -> Option; fn first_child(&self) -> Option; fn last_child(&self) -> Option; fn prev_sibling(&self) -> Option; fn next_sibling(&self) -> Option; } pub trait PresentationalHintsSynthetizer { fn synthesize_presentational_hints_for_legacy_attributes(&self, hints: &mut V) where V: Push; } pub trait TElement : PartialEq + Debug + Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer { type ConcreteNode: TNode; fn as_node(&self) -> Self::ConcreteNode; fn style_attribute(&self) -> Option<&Arc>>; fn get_state(&self) -> ElementState; fn has_attr(&self, namespace: &Namespace, attr: &LocalName) -> bool; fn attr_equals(&self, namespace: &Namespace, attr: &LocalName, value: &Atom) -> bool; /// Set the restyle damage field. fn set_restyle_damage(self, damage: RestyleDamage); /// XXX: It's a bit unfortunate we need to pass the current computed values /// as an argument here, but otherwise Servo would crash due to double /// borrows to return it. fn existing_style_for_restyle_damage<'a>(&'a self, current_computed_values: Option<&'a Arc>, pseudo: Option<&PseudoElement>) -> Option<&'a ::PreExistingComputedValues>; /// The concept of a dirty bit doesn't exist in our new restyle algorithm. /// Instead, we associate restyle and change hints with nodes. However, we /// continue to allow the dirty bit to trigger unconditional restyles while /// we transition both Servo and Stylo to the new architecture. fn deprecated_dirty_bit_is_set(&self) -> bool; fn has_dirty_descendants(&self) -> bool; unsafe fn set_dirty_descendants(&self); /// Atomically stores the number of children of this node that we will /// need to process during bottom-up traversal. fn store_children_to_process(&self, n: isize); /// Atomically notes that a child has been processed during bottom-up /// traversal. Returns the number of children left to process. fn did_process_child(&self) -> isize; /// Returns true if this element's current style is display:none. Only valid /// to call after styling. fn is_display_none(&self) -> bool { self.borrow_data().unwrap() .current_styles().primary .get_box().clone_display() == display::T::none } /// Returns true if this node has a styled layout frame that owns the style. fn frame_has_style(&self) -> bool { false } /// Returns the styles from the layout frame that owns them, if any. /// /// FIXME(bholley): Once we start dropping ElementData from nodes when /// creating frames, we'll want to teach this method to actually get /// style data from the frame. fn get_styles_from_frame(&self) -> Option { None } /// Returns the styling mode for this node. This is only valid to call before /// and during restyling, before finish_styling is invoked. /// /// See the comments around StylingMode. fn styling_mode(&self) -> StylingMode { use self::StylingMode::*; // Non-incremental layout impersonates Initial. if opts::get().nonincremental_layout { return Initial; } // Compute the default result if this node doesn't require processing. let mode_for_descendants = if self.has_dirty_descendants() { Traverse } else { Stop }; let mut mode = match self.borrow_data() { // No element data, no style on the frame. None if !self.frame_has_style() => Initial, // No element data, style on the frame. None => mode_for_descendants, // We have element data. Decide below. Some(d) => { if d.has_current_styles() { // The element has up-to-date style. debug_assert!(!self.frame_has_style()); debug_assert!(d.restyle_data.is_none()); mode_for_descendants } else { // The element needs processing. if d.previous_styles().is_some() { Restyle } else { Initial } } }, }; // Handle the deprecated dirty bit. This should go away soon. if mode != Initial && self.deprecated_dirty_bit_is_set() { mode = Restyle; } mode } /// Immutable borrows the ElementData. fn borrow_data(&self) -> Option>; /// Gets a reference to the ElementData container. fn get_data(&self) -> Option<&AtomicRefCell>; /// Properly marks nodes as dirty in response to restyle hints. fn note_restyle_hint>(&self, hint: RestyleHint) { // Bail early if there's no restyling to do. if hint.is_empty() { return; } // If the restyle hint is non-empty, we need to restyle either this element // or one of its siblings. Mark our ancestor chain as having dirty descendants. let mut curr = *self; while let Some(parent) = curr.parent_element() { if parent.has_dirty_descendants() { break } unsafe { parent.set_dirty_descendants(); } curr = parent; } // Process hints. if hint.contains(RESTYLE_SELF) { unsafe { let _ = C::prepare_for_styling(self); } // XXX(emilio): For now, dirty implies dirty descendants if found. } else if hint.contains(RESTYLE_DESCENDANTS) { unsafe { self.set_dirty_descendants(); } let mut current = self.first_child_element(); while let Some(el) = current { unsafe { let _ = C::prepare_for_styling(&el); } current = el.next_sibling_element(); } } if hint.contains(RESTYLE_LATER_SIBLINGS) { let mut next = ::selectors::Element::next_sibling_element(self); while let Some(sib) = next { unsafe { let _ = C::prepare_for_styling(&sib); } next = ::selectors::Element::next_sibling_element(&sib); } } } }