/* 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/. */ use HTMLCanvasData; use LayoutNodeType; use OpaqueStyleAndLayoutData; use gfx_traits::{ByteIndex, LayerId, LayerType}; use msg::constellation_msg::PipelineId; use range::Range; use restyle_damage::RestyleDamage; use std::fmt::Debug; use std::sync::Arc; use string_cache::{Atom, Namespace}; use style::atomic_refcell::AtomicRefCell; use style::computed_values::display; use style::context::SharedStyleContext; use style::data::PersistentStyleData; use style::dom::{LayoutIterator, NodeInfo, PresentationalHintsSynthetizer, TNode}; use style::dom::OpaqueNode; use style::properties::ServoComputedValues; use style::selector_impl::{PseudoElement, PseudoElementCascadeType, ServoSelectorImpl}; use url::Url; #[derive(Copy, PartialEq, Clone)] pub enum PseudoElementType { Normal, Before(T), After(T), DetailsSummary(T), DetailsContent(T), } impl PseudoElementType { pub fn is_before(&self) -> bool { match *self { PseudoElementType::Before(_) => true, _ => false, } } pub fn is_replaced_content(&self) -> bool { match *self { PseudoElementType::Before(_) | PseudoElementType::After(_) => true, _ => false, } } pub fn strip(&self) -> PseudoElementType<()> { match *self { PseudoElementType::Normal => PseudoElementType::Normal, PseudoElementType::Before(_) => PseudoElementType::Before(()), PseudoElementType::After(_) => PseudoElementType::After(()), PseudoElementType::DetailsSummary(_) => PseudoElementType::DetailsSummary(()), PseudoElementType::DetailsContent(_) => PseudoElementType::DetailsContent(()), } } pub fn style_pseudo_element(&self) -> PseudoElement { match *self { PseudoElementType::Normal => unreachable!("style_pseudo_element called with PseudoElementType::Normal"), PseudoElementType::Before(_) => PseudoElement::Before, PseudoElementType::After(_) => PseudoElement::After, PseudoElementType::DetailsSummary(_) => PseudoElement::DetailsSummary, PseudoElementType::DetailsContent(_) => PseudoElement::DetailsContent, } } } /// A wrapper so that layout can access only the methods that it should have access to. Layout must /// only ever see these and must never see instances of `LayoutJS`. pub trait LayoutNode: TNode { type ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode; fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode; /// Returns the type ID of this node. fn type_id(&self) -> LayoutNodeType; fn get_style_data(&self) -> Option<&AtomicRefCell>; fn init_style_and_layout_data(&self, data: OpaqueStyleAndLayoutData); fn get_style_and_layout_data(&self) -> Option; fn rev_children(self) -> LayoutIterator> { LayoutIterator(ReverseChildrenIterator { current: self.last_child(), }) } fn traverse_preorder(self) -> TreeIterator { TreeIterator::new(self) } } pub struct ReverseChildrenIterator where ConcreteNode: TNode { current: Option, } impl Iterator for ReverseChildrenIterator where ConcreteNode: TNode { type Item = ConcreteNode; fn next(&mut self) -> Option { let node = self.current; self.current = node.and_then(|node| node.prev_sibling()); node } } pub struct TreeIterator where ConcreteNode: TNode { stack: Vec, } impl TreeIterator where ConcreteNode: LayoutNode { fn new(root: ConcreteNode) -> TreeIterator { let mut stack = vec![]; stack.push(root); TreeIterator { stack: stack, } } pub fn next_skipping_children(&mut self) -> Option { self.stack.pop() } } impl Iterator for TreeIterator where ConcreteNode: LayoutNode { type Item = ConcreteNode; fn next(&mut self) -> Option { let ret = self.stack.pop(); ret.map(|node| self.stack.extend(node.rev_children())); ret } } /// A thread-safe version of `LayoutNode`, used during flow construction. This type of layout /// node does not allow any parents or siblings of nodes to be accessed, to avoid races. pub trait ThreadSafeLayoutNode: Clone + Copy + NodeInfo + PartialEq + Sized { type ConcreteThreadSafeLayoutElement: ThreadSafeLayoutElement + ::selectors::Element; type ChildrenIterator: Iterator + Sized; /// Creates a new `ThreadSafeLayoutNode` for the same `LayoutNode` /// with a different pseudo-element type. fn with_pseudo(&self, pseudo: PseudoElementType>) -> Self; /// Converts self into an `OpaqueNode`. fn opaque(&self) -> OpaqueNode; /// Returns the type ID of this node. /// Returns `None` if this is a pseudo-element; otherwise, returns `Some`. fn type_id(&self) -> Option; /// Returns the type ID of this node, without discarding pseudo-elements as /// `type_id` does. fn type_id_without_excluding_pseudo_elements(&self) -> LayoutNodeType; #[inline] fn is_element_or_elements_pseudo(&self) -> bool { match self.type_id_without_excluding_pseudo_elements() { LayoutNodeType::Element(..) => true, _ => false, } } fn debug_id(self) -> usize; /// Returns an iterator over this node's children. fn children(&self) -> LayoutIterator; /// If this is an element, accesses the element data. Fails if this is not an element node. #[inline] fn as_element(&self) -> Self::ConcreteThreadSafeLayoutElement; #[inline] fn get_pseudo_element_type(&self) -> PseudoElementType>; #[inline] fn get_before_pseudo(&self) -> Option { if self.get_style_data() .unwrap() .borrow() .per_pseudo .contains_key(&PseudoElement::Before) { Some(self.with_pseudo(PseudoElementType::Before(None))) } else { None } } #[inline] fn get_after_pseudo(&self) -> Option { if self.get_style_data() .unwrap() .borrow() .per_pseudo .contains_key(&PseudoElement::After) { Some(self.with_pseudo(PseudoElementType::After(None))) } else { None } } #[inline] fn get_details_summary_pseudo(&self) -> Option { if self.is_element() && self.as_element().get_local_name() == &atom!("details") && self.as_element().get_namespace() == &ns!(html) { Some(self.with_pseudo(PseudoElementType::DetailsSummary(None))) } else { None } } #[inline] fn get_details_content_pseudo(&self) -> Option { if self.is_element() && self.as_element().get_local_name() == &atom!("details") && self.as_element().get_namespace() == &ns!(html) { let display = if self.as_element().get_attr(&ns!(), &atom!("open")).is_some() { None // Specified by the stylesheet } else { Some(display::T::none) }; Some(self.with_pseudo(PseudoElementType::DetailsContent(display))) } else { None } } fn get_style_and_layout_data(&self) -> Option; /// Returns the style results for the given node. If CSS selector matching /// has not yet been performed, fails. /// /// Unlike the version on TNode, this handles pseudo-elements. #[inline] fn style(&self, context: &SharedStyleContext) -> Arc { match self.get_pseudo_element_type() { PseudoElementType::Normal => { self.get_style_data().unwrap().borrow() .style.as_ref().unwrap().clone() }, other => { // Precompute non-eagerly-cascaded pseudo-element styles if not // cached before. let style_pseudo = other.style_pseudo_element(); match style_pseudo.cascade_type() { // Already computed during the cascade. PseudoElementCascadeType::Eager => {}, PseudoElementCascadeType::Precomputed => { if !self.get_style_data() .unwrap() .borrow() .per_pseudo.contains_key(&style_pseudo) { let mut data = self.get_style_data().unwrap().borrow_mut(); let new_style = context.stylist .precomputed_values_for_pseudo(&style_pseudo, data.style.as_ref()); data.per_pseudo .insert(style_pseudo.clone(), new_style.unwrap()); } } PseudoElementCascadeType::Lazy => { debug_assert!(self.is_element_or_elements_pseudo()); if !self.get_style_data() .unwrap() .borrow() .per_pseudo.contains_key(&style_pseudo) { let mut data = self.get_style_data().unwrap().borrow_mut(); let new_style = context.stylist .lazily_compute_pseudo_element_style( &self.as_element(), &style_pseudo, data.style.as_ref().unwrap()); data.per_pseudo .insert(style_pseudo.clone(), new_style.unwrap()); } } } self.get_style_data().unwrap().borrow() .per_pseudo.get(&style_pseudo) .unwrap().clone() } } } /// Returns the already resolved style of the node. /// /// This differs from `style(ctx)` in that if the pseudo-element has not yet /// been computed it would panic. /// /// This should be used just for querying layout, or when we know the /// element style is precomputed, not from general layout itself. #[inline] fn resolved_style(&self) -> Arc { let data = self.get_style_data().unwrap().borrow(); match self.get_pseudo_element_type() { PseudoElementType::Normal => data.style.as_ref().unwrap().clone(), other => data.per_pseudo.get(&other.style_pseudo_element()).unwrap().clone(), } } #[inline] fn selected_style(&self, _context: &SharedStyleContext) -> Arc { let data = self.get_style_data().unwrap().borrow(); data.per_pseudo .get(&PseudoElement::Selection) .unwrap_or(data.style.as_ref().unwrap()).clone() } /// Removes the style from this node. /// /// Unlike the version on TNode, this handles pseudo-elements. fn unstyle(self) { let mut data = self.get_style_data().unwrap().borrow_mut(); match self.get_pseudo_element_type() { PseudoElementType::Normal => { data.style = None; } other => { data.per_pseudo.remove(&other.style_pseudo_element()); } }; } fn is_ignorable_whitespace(&self, context: &SharedStyleContext) -> bool; fn restyle_damage(self) -> RestyleDamage; fn set_restyle_damage(self, damage: RestyleDamage); /// Returns true if this node contributes content. This is used in the implementation of /// `empty_cells` per CSS 2.1 ยง 17.6.1.1. fn is_content(&self) -> bool { match self.type_id() { Some(LayoutNodeType::Element(..)) | Some(LayoutNodeType::Text) => true, _ => false } } fn can_be_fragmented(&self) -> bool; fn node_text_content(&self) -> String; /// If the insertion point is within this node, returns it. Otherwise, returns `None`. fn selection(&self) -> Option>; /// If this is an image element, returns its URL. If this is not an image element, fails. /// /// FIXME(pcwalton): Don't copy URLs. fn image_url(&self) -> Option; fn canvas_data(&self) -> Option; /// If this node is an iframe element, returns its pipeline ID. If this node is /// not an iframe element, fails. fn iframe_pipeline_id(&self) -> PipelineId; fn get_colspan(&self) -> u32; fn layer_id(&self) -> LayerId { let layer_type = match self.get_pseudo_element_type() { PseudoElementType::Normal => LayerType::FragmentBody, PseudoElementType::Before(_) => LayerType::BeforePseudoContent, PseudoElementType::After(_) => LayerType::AfterPseudoContent, PseudoElementType::DetailsSummary(_) => LayerType::FragmentBody, PseudoElementType::DetailsContent(_) => LayerType::FragmentBody, }; LayerId::new_of_type(layer_type, self.opaque().id() as usize) } fn layer_id_for_overflow_scroll(&self) -> LayerId { LayerId::new_of_type(LayerType::OverflowScroll, self.opaque().id() as usize) } fn get_style_data(&self) -> Option<&AtomicRefCell>; } // This trait is only public so that it can be implemented by the gecko wrapper. // It can be used to violate thread-safety, so don't use it elsewhere in layout! #[allow(unsafe_code)] pub trait DangerousThreadSafeLayoutNode: ThreadSafeLayoutNode { unsafe fn dangerous_first_child(&self) -> Option; unsafe fn dangerous_next_sibling(&self) -> Option; } pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + ::selectors::Element + PresentationalHintsSynthetizer { type ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode; #[inline] fn get_attr(&self, namespace: &Namespace, name: &Atom) -> Option<&str>; #[inline] fn get_local_name(&self) -> &Atom; #[inline] fn get_namespace(&self) -> &Namespace; }