/* 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/. */ #![allow(unsafe_code)] use HTMLCanvasData; use LayoutNodeType; use OpaqueStyleAndLayoutData; use SVGSVGData; use gfx_traits::ByteIndex; use html5ever_atoms::{Namespace, LocalName}; use msg::constellation_msg::PipelineId; use range::Range; use restyle_damage::RestyleDamage; use std::fmt::Debug; use std::sync::Arc; use style::atomic_refcell::AtomicRefCell; use style::computed_values::display; use style::context::SharedStyleContext; use style::data::ElementData; use style::dom::{LayoutIterator, NodeInfo, PresentationalHintsSynthetizer, TElement, TNode}; use style::dom::OpaqueNode; use style::properties::ServoComputedValues; use style::selector_impl::{PseudoElement, PseudoElementCascadeType, ServoSelectorImpl}; use url::Url; #[derive(Copy, PartialEq, Clone, Debug)] 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, } } } /// Trait to abstract access to layout data across various data structures. pub trait GetLayoutData { fn get_style_and_layout_data(&self) -> Option; } /// 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: GetLayoutData + TNode { type ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode; fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode; /// Returns the type ID of this node. fn type_id(&self) -> LayoutNodeType; unsafe fn init_style_and_layout_data(&self, data: OpaqueStyleAndLayoutData); unsafe fn take_style_and_layout_data(&self) -> OpaqueStyleAndLayoutData; fn has_changed(&self) -> bool; unsafe fn clear_dirty_bits(&self); 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 + GetLayoutData + NodeInfo + PartialEq + Sized { type ConcreteThreadSafeLayoutElement: ThreadSafeLayoutElement + ::selectors::Element; type ChildrenIterator: Iterator + Sized; /// 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; /// Returns the style for a text node. This is computed on the fly from the /// parent style to avoid traversing text nodes in the style system. /// /// Note that this does require accessing the parent, which this interface /// technically forbids. But accessing the parent is only unsafe insofar as /// it can be used to reach siblings and cousins. A simple immutable borrow /// of the parent data is fine, since the bottom-up traversal will not process /// the parent until all the children have been processed. fn style_for_text_node(&self) -> Arc; #[inline] fn is_element_or_elements_pseudo(&self) -> bool { match self.type_id_without_excluding_pseudo_elements() { LayoutNodeType::Element(..) => true, _ => false, } } fn get_before_pseudo(&self) -> Option { self.as_element().and_then(|el| el.get_before_pseudo()).map(|el| el.as_node()) } fn get_after_pseudo(&self) -> Option { self.as_element().and_then(|el| el.get_after_pseudo()).map(|el| el.as_node()) } fn get_details_summary_pseudo(&self) -> Option { self.as_element().and_then(|el| el.get_details_summary_pseudo()).map(|el| el.as_node()) } fn get_details_content_pseudo(&self) -> Option { self.as_element().and_then(|el| el.get_details_content_pseudo()).map(|el| el.as_node()) } fn debug_id(self) -> usize; /// Returns an iterator over this node's children. fn children(&self) -> LayoutIterator; /// Returns a ThreadSafeLayoutElement if this is an element, None otherwise. #[inline] fn as_element(&self) -> Option; #[inline] fn get_pseudo_element_type(&self) -> PseudoElementType> { self.as_element().map_or(PseudoElementType::Normal, |el| el.get_pseudo_element_type()) } fn get_style_and_layout_data(&self) -> Option; fn style(&self, context: &SharedStyleContext) -> Arc { if let Some(el) = self.as_element() { el.style(context) } else { debug_assert!(self.is_text_node()); self.style_for_text_node() } } fn selected_style(&self) -> Arc { if let Some(el) = self.as_element() { el.selected_style() } else { debug_assert!(self.is_text_node()); self.style_for_text_node() } } fn is_ignorable_whitespace(&self, context: &SharedStyleContext) -> bool; fn restyle_damage(self) -> RestyleDamage; fn clear_restyle_damage(self); /// 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 { self.type_id().is_some() } 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; fn svg_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; } // 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 LayoutElement: Clone + Copy + Sized + Debug + GetLayoutData + TElement { } pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + ::selectors::Element + GetLayoutData + PresentationalHintsSynthetizer { type ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode; fn as_node(&self) -> Self::ConcreteThreadSafeLayoutNode; /// Creates a new `ThreadSafeLayoutElement` for the same `LayoutElement` /// with a different pseudo-element type. fn with_pseudo(&self, pseudo: PseudoElementType>) -> Self; /// Returns the type ID of this node. /// Returns `None` if this is a pseudo-element; otherwise, returns `Some`. fn type_id(&self) -> Option; #[inline] fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str>; fn get_style_data(&self) -> Option<&AtomicRefCell>; #[inline] fn get_pseudo_element_type(&self) -> PseudoElementType>; #[inline] fn get_before_pseudo(&self) -> Option { if self.get_style_data() .unwrap() .borrow() .current_styles().pseudos .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() .current_styles().pseudos .contains_key(&PseudoElement::After) { Some(self.with_pseudo(PseudoElementType::After(None))) } else { None } } #[inline] fn get_details_summary_pseudo(&self) -> Option { if self.get_local_name() == &local_name!("details") && self.get_namespace() == &ns!(html) { Some(self.with_pseudo(PseudoElementType::DetailsSummary(None))) } else { None } } #[inline] fn get_details_content_pseudo(&self) -> Option { if self.get_local_name() == &local_name!("details") && self.get_namespace() == &ns!(html) { let display = if self.get_attr(&ns!(), &local_name!("open")).is_some() { None // Specified by the stylesheet } else { Some(display::T::none) }; Some(self.with_pseudo(PseudoElementType::DetailsContent(display))) } else { None } } /// 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() .current_styles().primary.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() .current_styles().pseudos.contains_key(&style_pseudo) { let mut data = self.get_style_data().unwrap().borrow_mut(); let new_style_and_rule_node = context.stylist.precomputed_values_for_pseudo( &style_pseudo, Some(&data.current_styles().primary), false); data.current_pseudos_mut() .insert(style_pseudo.clone(), new_style_and_rule_node.unwrap()); } } PseudoElementCascadeType::Lazy => { if !self.get_style_data() .unwrap() .borrow() .current_styles().pseudos.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, &style_pseudo, &data.current_styles().primary); data.current_pseudos_mut() .insert(style_pseudo.clone(), new_style.unwrap()); } } } self.get_style_data().unwrap().borrow() .current_styles().pseudos.get(&style_pseudo) .unwrap().0.clone() } } } #[inline] fn selected_style(&self) -> Arc { let data = self.get_style_data().unwrap().borrow(); data.current_styles().pseudos .get(&PseudoElement::Selection).map(|s| &s.0) .unwrap_or(&data.current_styles().primary) .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.current_styles().primary.clone(), other => data.current_styles().pseudos .get(&other.style_pseudo_element()).unwrap().0.clone(), } } }