From 73082d16268a7ea181359a0c9b017746ca480192 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 3 Mar 2015 10:42:52 -0700 Subject: [PATCH] =?UTF-8?q?servo:=20Merge=20#5067=20-=20layout:=20Implemen?= =?UTF-8?q?t=20ordered=20lists,=20CSS=20counters,=20and=20`quotes`=20per?= =?UTF-8?q?=20CSS=202.1=20=C2=A7=2012.3-12.5=20(from=20servo:counters);=20?= =?UTF-8?q?r=3DSimonSapin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only simple alphabetic and numeric counter styles are supported. (This is most of them though.) Although this PR adds a sequential pass to layout, I verified that on pages that contain a reasonable number of ordered lists (Reddit `/r/rust`), the time spent in generated content resolution is dwarfed by the time spent in the parallelizable parts of layout. So I don't expect this to negatively affect our parallelism expect perhaps in pathological cases. Moved from #4544, because Critic. Fixes #4544. Source-Repo: https://github.com/servo/servo Source-Revision: 5cd6316addc1acf145ed3220719387ef6ef08d2f --- servo/components/layout/block.rs | 39 +- servo/components/layout/construct.rs | 254 ++++---- .../components/layout/display_list_builder.rs | 1 + servo/components/layout/flow.rs | 118 ++-- servo/components/layout/fragment.rs | 162 +++-- servo/components/layout/generated_content.rs | 573 ++++++++++++++++++ servo/components/layout/incremental.rs | 48 +- servo/components/layout/inline.rs | 22 +- servo/components/layout/layout_task.rs | 8 + servo/components/layout/lib.rs | 3 +- servo/components/layout/list_item.rs | 97 ++- servo/components/layout/sequential.rs | 24 +- servo/components/layout/table.rs | 61 +- servo/components/layout/table_caption.rs | 14 +- servo/components/layout/table_cell.rs | 6 +- servo/components/layout/table_colgroup.rs | 4 +- servo/components/layout/table_row.rs | 23 +- servo/components/layout/table_rowgroup.rs | 16 +- servo/components/layout/table_wrapper.rs | 27 +- servo/components/layout/traversal.rs | 5 +- servo/components/layout/wrapper.rs | 47 +- .../dom/webidls/CSSStyleDeclaration.webidl | 5 + servo/components/style/properties.mako.rs | 357 ++++++++--- servo/components/util/time.rs | 3 + servo/ports/cef/Cargo.lock | 1 + 25 files changed, 1417 insertions(+), 501 deletions(-) create mode 100644 servo/components/layout/generated_content.rs diff --git a/servo/components/layout/block.rs b/servo/components/layout/block.rs index 822ec6bfb82b..a6a4a3c07a13 100644 --- a/servo/components/layout/block.rs +++ b/servo/components/layout/block.rs @@ -27,7 +27,6 @@ #![deny(unsafe_blocks)] -use construct::FlowConstructor; use context::LayoutContext; use css::node_style::StyledNode; use display_list_builder::{BlockFlowDisplayListBuilding, FragmentDisplayListBuilding}; @@ -40,7 +39,8 @@ use flow::{IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS}; use flow::{LAYERS_NEEDED_FOR_DESCENDANTS, NEEDS_LAYER}; use flow::{IS_ABSOLUTELY_POSITIONED}; use flow::{CLEARS_LEFT, CLEARS_RIGHT}; -use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo}; +use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, FragmentMutator}; +use fragment::{SpecificFragmentInfo}; use incremental::{REFLOW, REFLOW_OUT_OF_FLOW}; use layout_debug; use model::{IntrinsicISizes, MarginCollapseInfo}; @@ -569,20 +569,6 @@ impl Encodable for BlockFlowFlags { } impl BlockFlow { - pub fn from_node(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) -> BlockFlow { - let writing_mode = node.style().writing_mode; - BlockFlow { - base: BaseFlow::new(Some((*node).clone()), writing_mode, ForceNonfloatedFlag::ForceNonfloated), - fragment: Fragment::new(constructor, node), - static_b_offset: Au::new(0), - inline_size_of_preceding_left_floats: Au(0), - inline_size_of_preceding_right_floats: Au(0), - hypothetical_position: LogicalPoint::new(writing_mode, Au(0), Au(0)), - float: None, - flags: BlockFlowFlags::empty(), - } - } - pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> BlockFlow { let writing_mode = node.style().writing_mode; BlockFlow { @@ -597,23 +583,6 @@ impl BlockFlow { } } - pub fn float_from_node(constructor: &mut FlowConstructor, - node: &ThreadSafeLayoutNode, - float_kind: FloatKind) - -> BlockFlow { - let writing_mode = node.style().writing_mode; - BlockFlow { - base: BaseFlow::new(Some((*node).clone()), writing_mode, ForceNonfloatedFlag::FloatIfNecessary), - fragment: Fragment::new(constructor, node), - static_b_offset: Au::new(0), - inline_size_of_preceding_left_floats: Au(0), - inline_size_of_preceding_right_floats: Au(0), - hypothetical_position: LogicalPoint::new(writing_mode, Au(0), Au(0)), - float: Some(box FloatedBlockInfo::new(float_kind)), - flags: BlockFlowFlags::empty(), - } - } - pub fn float_from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment, float_kind: FloatKind) @@ -1919,6 +1888,10 @@ impl Flow for BlockFlow { CoordinateSystem::Parent) .translate(stacking_context_position)); } + + fn mutate_fragments(&mut self, mutator: &mut FragmentMutator) { + mutator.process(&mut self.fragment) + } } impl fmt::Debug for BlockFlow { diff --git a/servo/components/layout/construct.rs b/servo/components/layout/construct.rs index b2ea8090ea65..82c941b4d674 100644 --- a/servo/components/layout/construct.rs +++ b/servo/components/layout/construct.rs @@ -13,33 +13,27 @@ #![deny(unsafe_blocks)] -use css::node_style::StyledNode; use block::BlockFlow; use context::LayoutContext; +use css::node_style::StyledNode; use floats::FloatKind; -use flow::{Flow, ImmutableFlowUtils, MutableOwnedFlowUtils}; -use flow::{Descendants, AbsDescendants}; -use flow::{IS_ABSOLUTELY_POSITIONED}; -use flow; +use flow::{self, AbsDescendants, Descendants, Flow, IS_ABSOLUTELY_POSITIONED, ImmutableFlowUtils}; +use flow::{MutableOwnedFlowUtils}; use flow_ref::FlowRef; -use fragment::{Fragment, IframeFragmentInfo}; -use fragment::ImageFragmentInfo; -use fragment::CanvasFragmentInfo; -use fragment::InlineAbsoluteHypotheticalFragmentInfo; -use fragment::{InlineBlockFragmentInfo, SpecificFragmentInfo}; -use fragment::TableColumnFragmentInfo; -use fragment::UnscannedTextFragmentInfo; +use fragment::{CanvasFragmentInfo, Fragment, GeneratedContentInfo, IframeFragmentInfo}; +use fragment::{ImageFragmentInfo, InlineAbsoluteHypotheticalFragmentInfo, InlineBlockFragmentInfo}; +use fragment::{SpecificFragmentInfo, TableColumnFragmentInfo, UnscannedTextFragmentInfo}; use incremental::{RECONSTRUCT_FLOW, RestyleDamage}; use inline::InlineFlow; -use list_item::{self, ListItemFlow}; +use list_item::{ListItemFlow, ListStyleTypeContent}; use parallel; -use table_wrapper::TableWrapperFlow; use table::TableFlow; use table_caption::TableCaptionFlow; -use table_colgroup::TableColGroupFlow; -use table_rowgroup::TableRowGroupFlow; -use table_row::TableRowFlow; use table_cell::TableCellFlow; +use table_colgroup::TableColGroupFlow; +use table_row::TableRowFlow; +use table_rowgroup::TableRowGroupFlow; +use table_wrapper::TableWrapperFlow; use text::TextRunScanner; use util::{HAS_NEWLY_CONSTRUCTED_FLOW, LayoutDataAccess, OpaqueNodeMethods, LayoutDataWrapper}; use wrapper::{PostorderNodeMutTraversal, PseudoElementType, TLayoutNode, ThreadSafeLayoutNode}; @@ -54,9 +48,10 @@ use std::borrow::ToOwned; use std::collections::DList; use std::mem; use std::sync::atomic::Ordering; +use style::computed_values::content::ContentItem; use style::computed_values::{caption_side, display, empty_cells, float, list_style_position}; use style::computed_values::{position}; -use style::properties::{ComputedValues, make_inline}; +use style::properties::{self, ComputedValues}; use std::sync::Arc; use url::Url; @@ -250,41 +245,51 @@ impl<'a> FlowConstructor<'a> { } } - /// Builds specific `Fragment` info for the given node. - /// - /// This does *not* construct the text for generated content (but, for generated content with - /// `display: block`, it does construct the generic fragment corresponding to the block). - /// Construction of the text fragment is done specially by `build_flow_using_children()` and - /// `build_fragments_for_replaced_inline_content()`. - pub fn build_specific_fragment_info_for_node(&mut self, node: &ThreadSafeLayoutNode) - -> SpecificFragmentInfo { - match node.type_id() { - Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLIFrameElement))) => { + /// Builds the fragment for the given block or subclass thereof. + fn build_fragment_for_block(&mut self, node: &ThreadSafeLayoutNode) -> Fragment { + let specific_fragment_info = match node.type_id() { + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLIFrameElement))) => { SpecificFragmentInfo::Iframe(box IframeFragmentInfo::new(node)) } - Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLImageElement))) => { + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLImageElement))) => { self.build_fragment_info_for_image(node, node.image_url()) } - Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement))) => { + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLObjectElement))) => { let data = node.get_object_data(); self.build_fragment_info_for_image(node, data) } - Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableElement))) => SpecificFragmentInfo::TableWrapper, - Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableColElement))) => { + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTableElement))) => { + SpecificFragmentInfo::TableWrapper + } + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTableColElement))) => { SpecificFragmentInfo::TableColumn(TableColumnFragmentInfo::new(node)) } - Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableCellElement(_)))) => SpecificFragmentInfo::TableCell, - Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableRowElement))) | - Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableSectionElement))) => SpecificFragmentInfo::TableRow, - Some(NodeTypeId::Text) => SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::new(node)), - Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLCanvasElement))) => { + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTableCellElement(_)))) => { + SpecificFragmentInfo::TableCell + } + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTableRowElement))) | + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTableSectionElement))) => { + SpecificFragmentInfo::TableRow + } + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLCanvasElement))) => { SpecificFragmentInfo::Canvas(box CanvasFragmentInfo::new(node)) } _ => { // This includes pseudo-elements. SpecificFragmentInfo::Generic } - } + }; + + Fragment::new(node, specific_fragment_info) } /// Creates an inline flow from a set of inline fragments, then adds it as a child of the given @@ -324,7 +329,9 @@ impl<'a> FlowConstructor<'a> { let mut inline_block_flows = vec!(); for f in fragments.iter() { match f.specific { - SpecificFragmentInfo::InlineBlock(ref info) => inline_block_flows.push(info.flow_ref.clone()), + SpecificFragmentInfo::InlineBlock(ref info) => { + inline_block_flows.push(info.flow_ref.clone()) + } SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => { inline_block_flows.push(info.flow_ref.clone()) } @@ -458,8 +465,8 @@ impl<'a> FlowConstructor<'a> { whitespace_damage)) => { // Add whitespace results. They will be stripped out later on when // between block elements, and retained when between inline elements. - let fragment_info = - SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::from_text(" ".to_owned())); + let fragment_info = SpecificFragmentInfo::UnscannedText( + UnscannedTextFragmentInfo::from_text(" ".to_owned())); let fragment = Fragment::from_opaque_node_and_style(whitespace_node, whitespace_style, whitespace_damage, @@ -555,18 +562,22 @@ impl<'a> FlowConstructor<'a> { fn build_flow_for_block(&mut self, flow: FlowRef, node: &ThreadSafeLayoutNode) -> ConstructionResult { let initial_fragment = if node.get_pseudo_element_type() != PseudoElementType::Normal || - node.type_id() == Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement))) || - node.type_id() == Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement))) { + node.type_id() == Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLInputElement))) || + node.type_id() == Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTextAreaElement))) { // A TextArea's text contents are displayed through the input text // box, so don't construct them. - if node.type_id() == Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement))) { + if node.type_id() == Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTextAreaElement))) { for kid in node.children() { kid.set_flow_construction_result(ConstructionResult::None) } } - Some(Fragment::new_from_specific_info( - node, - SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::new(node)))) + + let mut fragments = DList::new(); + self.create_fragments_for_node_text_content(&mut fragments, node, node.style()); + fragments.into_iter().next() } else { None }; @@ -574,12 +585,39 @@ impl<'a> FlowConstructor<'a> { self.build_flow_for_block_starting_with_fragment(flow, node, initial_fragment) } + /// Pushes fragments appropriate for the content of the given node onto the given list. + fn create_fragments_for_node_text_content(&self, + fragments: &mut DList, + node: &ThreadSafeLayoutNode, + style: &Arc) { + for content_item in node.text_content().into_iter() { + let specific = match content_item { + ContentItem::String(string) => { + let info = UnscannedTextFragmentInfo::from_text(string); + SpecificFragmentInfo::UnscannedText(info) + } + content_item => { + let content_item = box GeneratedContentInfo::ContentItem(content_item); + SpecificFragmentInfo::GeneratedContent(content_item) + } + }; + + let opaque_node = OpaqueNodeMethods::from_thread_safe_layout_node(node); + fragments.push_back(Fragment::from_opaque_node_and_style(opaque_node, + style.clone(), + node.restyle_damage(), + specific)) + } + } + + /// Builds a flow for a node with `display: block`. This yields a `BlockFlow` with possibly /// other `BlockFlow`s or `InlineFlow`s underneath it, depending on whether {ib} splits needed /// to happen. fn build_flow_for_nonfloated_block(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { - let flow = box BlockFlow::from_node(self, node) as Box; + let flow = box BlockFlow::from_node_and_fragment(node, self.build_fragment_for_block(node)) + as Box; self.build_flow_for_block(FlowRef::new(flow), node) } @@ -587,7 +625,9 @@ impl<'a> FlowConstructor<'a> { /// a `BlockFlow` underneath it. fn build_flow_for_floated_block(&mut self, node: &ThreadSafeLayoutNode, float_kind: FloatKind) -> ConstructionResult { - let flow = box BlockFlow::float_from_node(self, node, float_kind) as Box; + let fragment = self.build_fragment_for_block(node); + let flow = box BlockFlow::float_from_node_and_fragment(node, fragment, float_kind) as + Box; self.build_flow_for_block(FlowRef::new(flow), node) } @@ -655,8 +695,8 @@ impl<'a> FlowConstructor<'a> { whitespace_style, whitespace_damage)) => { // Instantiate the whitespace fragment. - let fragment_info = SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::from_text( - " ".to_owned())); + let fragment_info = SpecificFragmentInfo::UnscannedText( + UnscannedTextFragmentInfo::from_text(" ".to_owned())); let fragment = Fragment::from_opaque_node_and_style(whitespace_node, whitespace_style, whitespace_damage, @@ -710,30 +750,22 @@ impl<'a> FlowConstructor<'a> { // `baz` had better not be absolutely positioned! let mut style = (*node.style()).clone(); if style.get_box().display != display::T::inline { - style = Arc::new(make_inline(&*style)) + style = Arc::new(properties::make_inline(&*style)) } // If this is generated content, then we need to initialize the accumulator with the // fragment corresponding to that content. Otherwise, just initialize with the ordinary // fragment that needs to be generated for this inline node. - let fragment = if node.get_pseudo_element_type() != PseudoElementType::Normal { - let fragment_info = - SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::new(node)); - Fragment::from_opaque_node_and_style( - OpaqueNodeMethods::from_thread_safe_layout_node(node), - style, - node.restyle_damage(), - fragment_info) - } else { - Fragment::from_opaque_node_and_style( - OpaqueNodeMethods::from_thread_safe_layout_node(node), - style, - node.restyle_damage(), - self.build_specific_fragment_info_for_node(node)) - }; - let mut fragments = DList::new(); - fragments.push_back(fragment); + match (node.get_pseudo_element_type(), node.type_id()) { + (_, Some(NodeTypeId::Text)) => { + self.create_fragments_for_node_text_content(&mut fragments, node, &style) + } + (PseudoElementType::Normal, _) => { + fragments.push_back(self.build_fragment_for_block(node)); + } + (_, _) => self.create_fragments_for_node_text_content(&mut fragments, node, &style), + } let construction_item = ConstructionItem::InlineFragments(InlineFragmentsConstructionResult { @@ -754,7 +786,7 @@ impl<'a> FlowConstructor<'a> { let fragment_info = SpecificFragmentInfo::InlineBlock(InlineBlockFragmentInfo::new( block_flow)); - let fragment = Fragment::new_from_specific_info(node, fragment_info); + let fragment = Fragment::new(node, fragment_info); let mut fragment_accumulator = InlineFragmentsAccumulator::from_inline_node(node); fragment_accumulator.fragments.push_back(fragment); @@ -780,7 +812,7 @@ impl<'a> FlowConstructor<'a> { let fragment_info = SpecificFragmentInfo::InlineAbsoluteHypothetical( InlineAbsoluteHypotheticalFragmentInfo::new(block_flow)); - let fragment = Fragment::new_from_specific_info(node, fragment_info); + let fragment = Fragment::new(node, fragment_info); let mut fragment_accumulator = InlineFragmentsAccumulator::from_inline_node(node); fragment_accumulator.fragments.push_back(fragment); @@ -865,7 +897,7 @@ impl<'a> FlowConstructor<'a> { /// possibly other `TableCaptionFlow`s or `TableFlow`s underneath it. fn build_flow_for_table_wrapper(&mut self, node: &ThreadSafeLayoutNode, float_value: float::T) -> ConstructionResult { - let fragment = Fragment::new_from_specific_info(node, SpecificFragmentInfo::TableWrapper); + let fragment = Fragment::new(node, SpecificFragmentInfo::TableWrapper); let wrapper_flow = match float_value { float::T::none => box TableWrapperFlow::from_node_and_fragment(node, fragment), _ => { @@ -875,7 +907,7 @@ impl<'a> FlowConstructor<'a> { }; let mut wrapper_flow = FlowRef::new(wrapper_flow as Box); - let table_fragment = Fragment::new_from_specific_info(node, SpecificFragmentInfo::Table); + let table_fragment = Fragment::new(node, SpecificFragmentInfo::Table); let table_flow = box TableFlow::from_node_and_fragment(node, table_fragment); let table_flow = FlowRef::new(table_flow as Box); @@ -909,7 +941,8 @@ impl<'a> FlowConstructor<'a> { wrapper_flow.finish(); let is_positioned = wrapper_flow.as_block().is_positioned(); let is_fixed_positioned = wrapper_flow.as_block().is_fixed(); - let is_absolutely_positioned = flow::base(&*wrapper_flow).flags.contains(IS_ABSOLUTELY_POSITIONED); + let is_absolutely_positioned = + flow::base(&*wrapper_flow).flags.contains(IS_ABSOLUTELY_POSITIONED); if is_positioned { // This is the containing block for all the absolute descendants. wrapper_flow.set_absolute_descendants(abs_descendants); @@ -932,7 +965,8 @@ impl<'a> FlowConstructor<'a> { /// Builds a flow for a node with `display: table-caption`. This yields a `TableCaptionFlow` /// with possibly other `BlockFlow`s or `InlineFlow`s underneath it. fn build_flow_for_table_caption(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { - let flow = box TableCaptionFlow::from_node(self, node) as Box; + let fragment = self.build_fragment_for_block(node); + let flow = box TableCaptionFlow::from_node_and_fragment(node, fragment) as Box; self.build_flow_for_block(FlowRef::new(flow), node) } @@ -940,7 +974,7 @@ impl<'a> FlowConstructor<'a> { /// with possibly other `TableRowFlow`s underneath it. fn build_flow_for_table_rowgroup(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { - let fragment = Fragment::new_from_specific_info(node, SpecificFragmentInfo::TableRow); + let fragment = Fragment::new(node, SpecificFragmentInfo::TableRow); let flow = box TableRowGroupFlow::from_node_and_fragment(node, fragment); let flow = flow as Box; self.build_flow_for_block(FlowRef::new(flow), node) @@ -949,7 +983,7 @@ impl<'a> FlowConstructor<'a> { /// Builds a flow for a node with `display: table-row`. This yields a `TableRowFlow` with /// possibly other `TableCellFlow`s underneath it. fn build_flow_for_table_row(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { - let fragment = Fragment::new_from_specific_info(node, SpecificFragmentInfo::TableRow); + let fragment = Fragment::new(node, SpecificFragmentInfo::TableRow); let flow = box TableRowFlow::from_node_and_fragment(node, fragment) as Box; self.build_flow_for_block(FlowRef::new(flow), node) } @@ -957,7 +991,7 @@ impl<'a> FlowConstructor<'a> { /// Builds a flow for a node with `display: table-cell`. This yields a `TableCellFlow` with /// possibly other `BlockFlow`s or `InlineFlow`s underneath it. fn build_flow_for_table_cell(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { - let fragment = Fragment::new_from_specific_info(node, SpecificFragmentInfo::TableCell); + let fragment = Fragment::new(node, SpecificFragmentInfo::TableCell); // Determine if the table cell should be hidden. Per CSS 2.1 § 17.6.1.1, this will be true // if the cell has any in-flow elements (even empty ones!) and has `empty-cells` set to @@ -985,19 +1019,18 @@ impl<'a> FlowConstructor<'a> { }; let marker_fragment = match node.style().get_list().list_style_image { Some(ref url) => { - Some(Fragment::new_from_specific_info( - node, - self.build_fragment_info_for_image(node, Some((*url).clone())))) + Some(Fragment::new(node, + self.build_fragment_info_for_image(node, Some((*url).clone())))) } None => { - match list_item::static_text_for_list_style_type(node.style() + match ListStyleTypeContent::from_list_style_type(node.style() .get_list() .list_style_type) { - None => None, - Some(text) => { + ListStyleTypeContent::None => None, + ListStyleTypeContent::StaticText(text) => { let text = text.to_owned(); let mut unscanned_marker_fragments = DList::new(); - unscanned_marker_fragments.push_back(Fragment::new_from_specific_info( + unscanned_marker_fragments.push_back(Fragment::new( node, SpecificFragmentInfo::UnscannedText( UnscannedTextFragmentInfo::from_text(text)))); @@ -1007,6 +1040,9 @@ impl<'a> FlowConstructor<'a> { debug_assert!(marker_fragments.len() == 1); marker_fragments.fragments.into_iter().next() } + ListStyleTypeContent::GeneratedContent(info) => { + Some(Fragment::new(node, SpecificFragmentInfo::GeneratedContent(info))) + } } } }; @@ -1018,19 +1054,20 @@ impl<'a> FlowConstructor<'a> { // there. let flow; let initial_fragment; + let main_fragment = self.build_fragment_for_block(node); match node.style().get_list().list_style_position { list_style_position::T::outside => { - flow = box ListItemFlow::from_node_marker_and_flotation(self, - node, - marker_fragment, - flotation); + flow = box ListItemFlow::from_node_fragments_and_flotation(node, + main_fragment, + marker_fragment, + flotation); initial_fragment = None; } list_style_position::T::inside => { - flow = box ListItemFlow::from_node_marker_and_flotation(self, - node, - None, - flotation); + flow = box ListItemFlow::from_node_fragments_and_flotation(node, + main_fragment, + None, + flotation); initial_fragment = marker_fragment; } } @@ -1049,9 +1086,8 @@ impl<'a> FlowConstructor<'a> { } let specific = SpecificFragmentInfo::TableColumn(TableColumnFragmentInfo::new(node)); - let construction_item = ConstructionItem::TableColumnFragment( - Fragment::new_from_specific_info(node, specific) - ); + let construction_item = ConstructionItem::TableColumnFragment(Fragment::new(node, + specific)); ConstructionResult::ConstructionItem(construction_item) } @@ -1059,9 +1095,9 @@ impl<'a> FlowConstructor<'a> { /// This yields a `TableColGroupFlow`. fn build_flow_for_table_colgroup(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { - let fragment = Fragment::new_from_specific_info( - node, - SpecificFragmentInfo::TableColumn(TableColumnFragmentInfo::new(node))); + let fragment = + Fragment::new(node, + SpecificFragmentInfo::TableColumn(TableColumnFragmentInfo::new(node))); let mut col_fragments = vec!(); for kid in node.children() { // CSS 2.1 § 17.2.1. Treat all non-column child fragments of `table-column-group` @@ -1077,7 +1113,7 @@ impl<'a> FlowConstructor<'a> { if col_fragments.is_empty() { debug!("add SpecificFragmentInfo::TableColumn for empty colgroup"); let specific = SpecificFragmentInfo::TableColumn(TableColumnFragmentInfo::new(node)); - col_fragments.push(Fragment::new_from_specific_info(node, specific)); + col_fragments.push(Fragment::new(node, specific)); } let flow = box TableColGroupFlow::from_node_and_fragments(node, fragment, col_fragments); let mut flow = FlowRef::new(flow as Box); @@ -1293,25 +1329,29 @@ trait NodeUtils { /// Sets the construction result of a flow. fn set_flow_construction_result(self, result: ConstructionResult); - /// Replaces the flow construction result in a node with `ConstructionResult::None` and returns the - /// old value. + /// Replaces the flow construction result in a node with `ConstructionResult::None` and returns + /// the old value. fn swap_out_construction_result(self) -> ConstructionResult; } impl<'ln> NodeUtils for ThreadSafeLayoutNode<'ln> { fn is_replaced_content(&self) -> bool { match self.type_id() { + None | Some(NodeTypeId::Text) | Some(NodeTypeId::ProcessingInstruction) | Some(NodeTypeId::Comment) | Some(NodeTypeId::DocumentType) | Some(NodeTypeId::DocumentFragment) | Some(NodeTypeId::Document) | - None | - Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLImageElement))) => true, - Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement))) => self.has_object_data(), - Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLIFrameElement))) => true, - Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLCanvasElement))) => true, + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLImageElement))) | + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLIFrameElement))) | + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLCanvasElement))) => true, + Some(NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLObjectElement))) => self.has_object_data(), Some(NodeTypeId::Element(_)) => false, } } diff --git a/servo/components/layout/display_list_builder.rs b/servo/components/layout/display_list_builder.rs index e20ade0ce191..50116303600d 100644 --- a/servo/components/layout/display_list_builder.rs +++ b/servo/components/layout/display_list_builder.rs @@ -885,6 +885,7 @@ impl FragmentDisplayListBuilding for Fragment { } } SpecificFragmentInfo::Generic | + SpecificFragmentInfo::GeneratedContent(..) | SpecificFragmentInfo::Iframe(..) | SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell | diff --git a/servo/components/layout/flow.rs b/servo/components/layout/flow.rs index 19f1df86c3ae..b9dacc872d10 100644 --- a/servo/components/layout/flow.rs +++ b/servo/components/layout/flow.rs @@ -32,10 +32,10 @@ use display_list_builder::DisplayListBuildingResult; use floats::Floats; use flow_list::{FlowList, FlowListIterator, MutFlowListIterator}; use flow_ref::FlowRef; -use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo}; -use incremental::{RECONSTRUCT_FLOW, REFLOW, REFLOW_OUT_OF_FLOW, RestyleDamage}; +use fragment::{Fragment, FragmentBorderBoxIterator, FragmentMutator, SpecificFragmentInfo}; +use incremental::{self, RECONSTRUCT_FLOW, REFLOW, REFLOW_OUT_OF_FLOW, RestyleDamage}; use inline::InlineFlow; -use model::{CollapsibleMargins, IntrinsicISizes}; +use model::{CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo}; use parallel::FlowParallelInfo; use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, TableFlow}; use table_caption::TableCaptionFlow; @@ -235,6 +235,15 @@ pub trait Flow: fmt::Debug + Sync { iterator: &mut FragmentBorderBoxIterator, stacking_context_position: &Point2D); + /// Mutably iterates through fragments in this flow. + fn mutate_fragments(&mut self, mutator: &mut FragmentMutator); + + fn compute_collapsible_block_start_margin(&mut self, + _layout_context: &mut LayoutContext, + _margin_collapse_info: &mut MarginCollapseInfo) { + // The default implementation is a no-op. + } + /// Marks this flow as the root flow. The default implementation is a no-op. fn mark_as_root(&mut self) {} @@ -474,51 +483,67 @@ pub trait PostorderFlowTraversal { } } +/// An in-order (sequential only) traversal. +pub trait InorderFlowTraversal { + /// The operation to perform. Returns the level of the tree we're at. + fn process(&mut self, flow: &mut Flow, level: u32); + + /// Returns true if this node should be processed and false if neither this node nor its + /// descendants should be processed. + fn should_process(&mut self, flow: &mut Flow) -> bool; +} + bitflags! { #[doc = "Flags used in flows."] - flags FlowFlags: u16 { + flags FlowFlags: u32 { // floated descendants flags #[doc = "Whether this flow has descendants that float left in the same block formatting"] #[doc = "context."] - const HAS_LEFT_FLOATED_DESCENDANTS = 0b0000_0000_0000_0001, + const HAS_LEFT_FLOATED_DESCENDANTS = 0b0000_0000_0000_0000_0001, #[doc = "Whether this flow has descendants that float right in the same block formatting"] #[doc = "context."] - const HAS_RIGHT_FLOATED_DESCENDANTS = 0b0000_0000_0000_0010, + const HAS_RIGHT_FLOATED_DESCENDANTS = 0b0000_0000_0000_0000_0010, #[doc = "Whether this flow is impacted by floats to the left in the same block formatting"] #[doc = "context (i.e. its height depends on some prior flows with `float: left`)."] - const IMPACTED_BY_LEFT_FLOATS = 0b0000_0000_0000_0100, + const IMPACTED_BY_LEFT_FLOATS = 0b0000_0000_0000_0000_0100, #[doc = "Whether this flow is impacted by floats to the right in the same block"] #[doc = "formatting context (i.e. its height depends on some prior flows with `float:"] #[doc = "right`)."] - const IMPACTED_BY_RIGHT_FLOATS = 0b0000_0000_0000_1000, + const IMPACTED_BY_RIGHT_FLOATS = 0b0000_0000_0000_0000_1000, // text align flags #[doc = "Whether this flow contains a flow that has its own layer within the same absolute"] #[doc = "containing block."] - const LAYERS_NEEDED_FOR_DESCENDANTS = 0b0000_0000_0001_0000, + const LAYERS_NEEDED_FOR_DESCENDANTS = 0b0000_0000_0000_0001_0000, #[doc = "Whether this flow must have its own layer. Even if this flag is not set, it might"] #[doc = "get its own layer if it's deemed to be likely to overlap flows with their own"] #[doc = "layer."] - const NEEDS_LAYER = 0b0000_0000_0010_0000, + const NEEDS_LAYER = 0b0000_0000_0000_0010_0000, #[doc = "Whether this flow is absolutely positioned. This is checked all over layout, so a"] #[doc = "virtual call is too expensive."] - const IS_ABSOLUTELY_POSITIONED = 0b0000_0000_0100_0000, + const IS_ABSOLUTELY_POSITIONED = 0b0000_0000_0000_0100_0000, #[doc = "Whether this flow clears to the left. This is checked all over layout, so a"] #[doc = "virtual call is too expensive."] - const CLEARS_LEFT = 0b0000_0000_1000_0000, + const CLEARS_LEFT = 0b0000_0000_0000_1000_0000, #[doc = "Whether this flow clears to the right. This is checked all over layout, so a"] #[doc = "virtual call is too expensive."] - const CLEARS_RIGHT = 0b0000_0001_0000_0000, + const CLEARS_RIGHT = 0b0000_0000_0001_0000_0000, #[doc = "Whether this flow is left-floated. This is checked all over layout, so a"] #[doc = "virtual call is too expensive."] - const FLOATS_LEFT = 0b0000_0010_0000_0000, + const FLOATS_LEFT = 0b0000_0000_0010_0000_0000, #[doc = "Whether this flow is right-floated. This is checked all over layout, so a"] #[doc = "virtual call is too expensive."] - const FLOATS_RIGHT = 0b0000_0100_0000_0000, + const FLOATS_RIGHT = 0b0000_0000_0100_0000_0000, #[doc = "Text alignment. \ NB: If you update this, update `TEXT_ALIGN_SHIFT` below."] - const TEXT_ALIGN = 0b0111_1000_0000_0000, + const TEXT_ALIGN = 0b0000_0111_1000_0000_0000, + #[doc = "Whether this flow has a fragment with `counter-reset` or `counter-increment` \ + styles."] + const AFFECTS_COUNTERS = 0b0000_1000_0000_0000_0000, + #[doc = "Whether this flow's descendants have fragments that affect `counter-reset` or \ + `counter-increment` styles."] + const HAS_COUNTER_AFFECTING_CHILDREN = 0b0001_0000_0000_0000_0000 } } @@ -544,13 +569,13 @@ impl FlowFlags { #[inline] pub fn text_align(self) -> text_align::T { - FromPrimitive::from_u16((self & TEXT_ALIGN).bits() >> TEXT_ALIGN_SHIFT).unwrap() + FromPrimitive::from_u32((self & TEXT_ALIGN).bits() >> TEXT_ALIGN_SHIFT).unwrap() } #[inline] pub fn set_text_align(&mut self, value: text_align::T) { *self = (*self & !TEXT_ALIGN) | - FlowFlags::from_bits((value as u16) << TEXT_ALIGN_SHIFT).unwrap(); + FlowFlags::from_bits((value as u32) << TEXT_ALIGN_SHIFT).unwrap(); } #[inline] @@ -870,39 +895,41 @@ impl BaseFlow { force_nonfloated: ForceNonfloatedFlag) -> BaseFlow { let mut flags = FlowFlags::empty(); - match node { - None => {} - Some(node) => { - let node_style = node.style(); - match node_style.get_box().position { - position::T::absolute | position::T::fixed => { - flags.insert(IS_ABSOLUTELY_POSITIONED) - } - _ => {} + if let Some(node) = node { + let node_style = node.style(); + match node_style.get_box().position { + position::T::absolute | position::T::fixed => { + flags.insert(IS_ABSOLUTELY_POSITIONED) } + _ => {} + } - if force_nonfloated == ForceNonfloatedFlag::FloatIfNecessary { - match node_style.get_box().float { - float::T::none => {} - float::T::left => flags.insert(FLOATS_LEFT), - float::T::right => flags.insert(FLOATS_RIGHT), - } + if force_nonfloated == ForceNonfloatedFlag::FloatIfNecessary { + match node_style.get_box().float { + float::T::none => {} + float::T::left => flags.insert(FLOATS_LEFT), + float::T::right => flags.insert(FLOATS_RIGHT), } + } - match node_style.get_box().clear { - clear::T::none => {} - clear::T::left => flags.insert(CLEARS_LEFT), - clear::T::right => flags.insert(CLEARS_RIGHT), - clear::T::both => { - flags.insert(CLEARS_LEFT); - flags.insert(CLEARS_RIGHT); - } + match node_style.get_box().clear { + clear::T::none => {} + clear::T::left => flags.insert(CLEARS_LEFT), + clear::T::right => flags.insert(CLEARS_RIGHT), + clear::T::both => { + flags.insert(CLEARS_LEFT); + flags.insert(CLEARS_RIGHT); } } + + if !node_style.get_counters().counter_reset.0.is_empty() || + !node_style.get_counters().counter_increment.0.is_empty() { + flags.insert(AFFECTS_COUNTERS) + } } // New flows start out as fully damaged. - let mut damage = RestyleDamage::all(); + let mut damage = incremental::all(); damage.remove(RECONSTRUCT_FLOW); BaseFlow { @@ -976,10 +1003,12 @@ impl BaseFlow { } impl<'a> ImmutableFlowUtils for &'a (Flow + 'a) { - /// Returns true if this flow is a block flow. + /// Returns true if this flow is a block flow or subclass thereof. fn is_block_like(self) -> bool { match self.class() { - FlowClass::Block => true, + FlowClass::Block | FlowClass::ListItem | FlowClass::Table | FlowClass::TableRowGroup | + FlowClass::TableRow | FlowClass::TableCaption | FlowClass::TableCell | + FlowClass::TableWrapper => true, _ => false, } } @@ -1314,3 +1343,4 @@ impl ContainingBlockLink { } } } + diff --git a/servo/components/layout/fragment.rs b/servo/components/layout/fragment.rs index 26a91139b31f..88204e5042c8 100644 --- a/servo/components/layout/fragment.rs +++ b/servo/components/layout/fragment.rs @@ -8,13 +8,12 @@ use canvas::canvas_paint_task::CanvasMsg; use css::node_style::StyledNode; -use construct::FlowConstructor; use context::LayoutContext; use floats::ClearType; use flow; use flow::Flow; use flow_ref::FlowRef; -use incremental::RestyleDamage; +use incremental::{self, RestyleDamage}; use inline::{InlineFragmentContext, InlineMetrics}; use layout_debug; use model::{IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto, specified}; @@ -28,11 +27,11 @@ use geom::{Point2D, Rect, Size2D}; use gfx::display_list::{BLUR_INFLATION_FACTOR, OpaqueNode}; use gfx::text::glyph::CharIndex; use gfx::text::text_run::{TextRun, TextRunSlice}; -use script_traits::UntrustedNodeAddress; -use rustc_serialize::{Encodable, Encoder}; use msg::constellation_msg::{PipelineId, SubpageId}; use net::image::holder::ImageHolder; use net::local_image_cache::LocalImageCache; +use rustc_serialize::{Encodable, Encoder}; +use script_traits::UntrustedNodeAddress; use servo_util::geometry::{self, Au, ZERO_POINT}; use servo_util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin}; use servo_util::range::*; @@ -44,15 +43,17 @@ use std::collections::DList; use std::fmt; use std::num::ToPrimitive; use std::str::FromStr; -use std::sync::{Arc, Mutex}; use std::sync::mpsc::Sender; +use std::sync::{Arc, Mutex}; use string_cache::Atom; -use style::properties::{ComputedValues, cascade_anonymous, make_border}; -use style::node::{TElement, TNode}; -use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; +use style::computed_values::content::ContentItem; use style::computed_values::{clear, mix_blend_mode, overflow_wrap}; use style::computed_values::{position, text_align, text_decoration, vertical_align, white_space}; use style::computed_values::{word_break}; +use style::node::{TElement, TNode}; +use style::properties::{ComputedValues, cascade_anonymous, make_border}; +use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto}; +use style::values::computed::{LengthOrPercentageOrNone}; use text::TextRunScanner; use url::Url; @@ -136,6 +137,11 @@ impl Encodable for Fragment { #[derive(Clone)] pub enum SpecificFragmentInfo { Generic, + + /// A piece of generated content that cannot be resolved into `ScannedText` until the generated + /// content resolution phase (e.g. an ordered list item marker). + GeneratedContent(Box), + Iframe(Box), Image(Box), Canvas(Box), @@ -158,17 +164,18 @@ impl SpecificFragmentInfo { fn restyle_damage(&self) -> RestyleDamage { let flow = match *self { - SpecificFragmentInfo::Iframe(_) - | SpecificFragmentInfo::Image(_) - | SpecificFragmentInfo::ScannedText(_) - | SpecificFragmentInfo::Table - | SpecificFragmentInfo::TableCell - | SpecificFragmentInfo::TableColumn(_) - | SpecificFragmentInfo::TableRow - | SpecificFragmentInfo::TableWrapper - | SpecificFragmentInfo::UnscannedText(_) - | SpecificFragmentInfo::Canvas(_) - | SpecificFragmentInfo::Generic => return RestyleDamage::empty(), + SpecificFragmentInfo::Canvas(_) | + SpecificFragmentInfo::GeneratedContent(_) | + SpecificFragmentInfo::Iframe(_) | + SpecificFragmentInfo::Image(_) | + SpecificFragmentInfo::ScannedText(_) | + SpecificFragmentInfo::Table | + SpecificFragmentInfo::TableCell | + SpecificFragmentInfo::TableColumn(_) | + SpecificFragmentInfo::TableRow | + SpecificFragmentInfo::TableWrapper | + SpecificFragmentInfo::UnscannedText(_) | + SpecificFragmentInfo::Generic => return RestyleDamage::empty(), SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => &info.flow_ref, SpecificFragmentInfo::InlineBlock(ref info) => &info.flow_ref, }; @@ -180,9 +187,12 @@ impl SpecificFragmentInfo { match *self { SpecificFragmentInfo::Canvas(_) => "SpecificFragmentInfo::Canvas", SpecificFragmentInfo::Generic => "SpecificFragmentInfo::Generic", + SpecificFragmentInfo::GeneratedContent(_) => "SpecificFragmentInfo::GeneratedContent", SpecificFragmentInfo::Iframe(_) => "SpecificFragmentInfo::Iframe", SpecificFragmentInfo::Image(_) => "SpecificFragmentInfo::Image", - SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => "SpecificFragmentInfo::InlineAbsoluteHypothetical", + SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => { + "SpecificFragmentInfo::InlineAbsoluteHypothetical" + } SpecificFragmentInfo::InlineBlock(_) => "SpecificFragmentInfo::InlineBlock", SpecificFragmentInfo::ScannedText(_) => "SpecificFragmentInfo::ScannedText", SpecificFragmentInfo::Table => "SpecificFragmentInfo::Table", @@ -196,8 +206,11 @@ impl SpecificFragmentInfo { } /// Clamp a value obtained from style_length, based on min / max lengths. -fn clamp_size(size: Au, min_size: LengthOrPercentage, max_size: LengthOrPercentageOrNone, - container_inline_size: Au) -> Au { +fn clamp_size(size: Au, + min_size: LengthOrPercentage, + max_size: LengthOrPercentageOrNone, + container_inline_size: Au) + -> Au { let min_size = model::specified(min_size, container_inline_size); let max_size = model::specified_or_none(max_size, container_inline_size); @@ -207,6 +220,13 @@ fn clamp_size(size: Au, min_size: LengthOrPercentage, max_size: LengthOrPercenta }) } +/// Information for generated content. +#[derive(Clone)] +pub enum GeneratedContentInfo { + ListItem, + ContentItem(ContentItem), +} + /// A hypothetical box (see CSS 2.1 § 10.3.7) for an absolutely-positioned block that was declared /// with `display: inline;`. /// @@ -637,14 +657,6 @@ pub struct UnscannedTextFragmentInfo { } impl UnscannedTextFragmentInfo { - /// Creates a new instance of `UnscannedTextFragmentInfo` from the given DOM node. - pub fn new(node: &ThreadSafeLayoutNode) -> UnscannedTextFragmentInfo { - // FIXME(pcwalton): Don't copy text; atomically reference count it instead. - UnscannedTextFragmentInfo { - text: box node.text(), - } - } - /// Creates a new instance of `UnscannedTextFragmentInfo` from the given text. #[inline] pub fn from_text(text: String) -> UnscannedTextFragmentInfo { @@ -678,34 +690,8 @@ impl TableColumnFragmentInfo { } impl Fragment { - /// Constructs a new `Fragment` instance for the given node. - /// - /// This does *not* construct the text for generated content. See comments in - /// `FlowConstructor::build_specific_fragment_info_for_node()` for more details. - /// - /// Arguments: - /// - /// * `constructor`: The flow constructor. - /// * `node`: The node to create a fragment for. - pub fn new(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) -> Fragment { - let style = node.style().clone(); - let writing_mode = style.writing_mode; - Fragment { - node: OpaqueNodeMethods::from_thread_safe_layout_node(node), - style: style, - restyle_damage: node.restyle_damage(), - border_box: LogicalRect::zero(writing_mode), - border_padding: LogicalMargin::zero(writing_mode), - margin: LogicalMargin::zero(writing_mode), - specific: constructor.build_specific_fragment_info_for_node(node), - inline_context: None, - debug_id: layout_debug::generate_unique_debug_id(), - } - } - - /// Constructs a new `Fragment` instance from a specific info. - pub fn new_from_specific_info(node: &ThreadSafeLayoutNode, specific: SpecificFragmentInfo) - -> Fragment { + /// Constructs a new `Fragment` instance. + pub fn new(node: &ThreadSafeLayoutNode, specific: SpecificFragmentInfo) -> Fragment { let style = node.style().clone(); let writing_mode = style.writing_mode; Fragment { @@ -721,24 +707,6 @@ impl Fragment { } } - /// Constructs a new `Fragment` instance for an anonymous object. - pub fn new_anonymous(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) - -> Fragment { - let node_style = cascade_anonymous(&**node.style()); - let writing_mode = node_style.writing_mode; - Fragment { - node: OpaqueNodeMethods::from_thread_safe_layout_node(node), - style: Arc::new(node_style), - restyle_damage: node.restyle_damage(), - border_box: LogicalRect::zero(writing_mode), - border_padding: LogicalMargin::zero(writing_mode), - margin: LogicalMargin::zero(writing_mode), - specific: constructor.build_specific_fragment_info_for_node(node), - inline_context: None, - debug_id: layout_debug::generate_unique_debug_id(), - } - } - /// Constructs a new `Fragment` instance for an anonymous table object. pub fn new_anonymous_from_specific_info(node: &ThreadSafeLayoutNode, specific: SpecificFragmentInfo) @@ -837,7 +805,7 @@ impl Fragment { Fragment { node: self.node, style: self.style.clone(), - restyle_damage: RestyleDamage::all(), + restyle_damage: incremental::all(), border_box: new_border_box, border_padding: self.border_padding, margin: self.margin, @@ -912,6 +880,7 @@ impl Fragment { match self.specific { SpecificFragmentInfo::Canvas(_) | SpecificFragmentInfo::Generic | + SpecificFragmentInfo::GeneratedContent(_) | SpecificFragmentInfo::Iframe(_) | SpecificFragmentInfo::Image(_) | SpecificFragmentInfo::InlineBlock(_) => { @@ -931,7 +900,9 @@ impl Fragment { INTRINSIC_INLINE_SIZE_INCLUDES_BORDER | INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED } - SpecificFragmentInfo::ScannedText(_) | SpecificFragmentInfo::TableColumn(_) | SpecificFragmentInfo::UnscannedText(_) | + SpecificFragmentInfo::ScannedText(_) | + SpecificFragmentInfo::TableColumn(_) | + SpecificFragmentInfo::UnscannedText(_) | SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => { QuantitiesIncludedInIntrinsicInlineSizes::empty() } @@ -1220,6 +1191,14 @@ impl Fragment { } } + /// Returns true if and only if this fragment is a generated content fragment. + pub fn is_generated_content(&self) -> bool { + match self.specific { + SpecificFragmentInfo::GeneratedContent(..) => true, + _ => false, + } + } + /// Returns true if and only if this is a scanned text fragment. pub fn is_scanned_text_fragment(&self) -> bool { match self.specific { @@ -1233,6 +1212,7 @@ impl Fragment { let mut result = self.style_specified_intrinsic_inline_size(); match self.specific { SpecificFragmentInfo::Generic | + SpecificFragmentInfo::GeneratedContent(_) | SpecificFragmentInfo::Iframe(_) | SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell | @@ -1302,6 +1282,7 @@ impl Fragment { pub fn content_inline_size(&self) -> Au { match self.specific { SpecificFragmentInfo::Generic | + SpecificFragmentInfo::GeneratedContent(_) | SpecificFragmentInfo::Iframe(_) | SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell | @@ -1333,6 +1314,7 @@ impl Fragment { pub fn content_block_size(&self, layout_context: &LayoutContext) -> Au { match self.specific { SpecificFragmentInfo::Generic | + SpecificFragmentInfo::GeneratedContent(_) | SpecificFragmentInfo::Iframe(_) | SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell | @@ -1351,7 +1333,7 @@ impl Fragment { self.calculate_line_height(layout_context) } SpecificFragmentInfo::TableColumn(_) => { - panic!("Table column fragments do not have block_size") + panic!("Table column fragments do not have block size") } SpecificFragmentInfo::UnscannedText(_) => { panic!("Unscanned text fragments should have been scanned by now!") @@ -1381,6 +1363,7 @@ impl Fragment { match self.specific { SpecificFragmentInfo::Canvas(_) | SpecificFragmentInfo::Generic | + SpecificFragmentInfo::GeneratedContent(_) | SpecificFragmentInfo::Iframe(_) | SpecificFragmentInfo::Image(_) | SpecificFragmentInfo::Table | @@ -1688,22 +1671,23 @@ impl Fragment { pub fn assign_replaced_inline_size_if_necessary<'a>(&'a mut self, container_inline_size: Au) { match self.specific { SpecificFragmentInfo::Generic | + SpecificFragmentInfo::GeneratedContent(_) | SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell | SpecificFragmentInfo::TableRow | SpecificFragmentInfo::TableWrapper => return, SpecificFragmentInfo::TableColumn(_) => { - panic!("Table column fragments do not have inline_size") + panic!("Table column fragments do not have inline size") } SpecificFragmentInfo::UnscannedText(_) => { panic!("Unscanned text fragments should have been scanned by now!") } SpecificFragmentInfo::Canvas(_) | SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::ScannedText(_) | + SpecificFragmentInfo::Iframe(_) | SpecificFragmentInfo::InlineBlock(_) | SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | - SpecificFragmentInfo::Iframe(_) => {} + SpecificFragmentInfo::ScannedText(_) => {} }; let style = self.style().clone(); @@ -1767,22 +1751,23 @@ impl Fragment { pub fn assign_replaced_block_size_if_necessary(&mut self, containing_block_block_size: Au) { match self.specific { SpecificFragmentInfo::Generic | + SpecificFragmentInfo::GeneratedContent(_) | SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell | SpecificFragmentInfo::TableRow | SpecificFragmentInfo::TableWrapper => return, SpecificFragmentInfo::TableColumn(_) => { - panic!("Table column fragments do not have block_size") + panic!("Table column fragments do not have block size") } SpecificFragmentInfo::UnscannedText(_) => { panic!("Unscanned text fragments should have been scanned by now!") } SpecificFragmentInfo::Canvas(_) | + SpecificFragmentInfo::Iframe(_) | SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::ScannedText(_) | SpecificFragmentInfo::InlineBlock(_) | SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | - SpecificFragmentInfo::Iframe(_) => {} + SpecificFragmentInfo::ScannedText(_) => {} } let style = self.style().clone(); @@ -1920,6 +1905,7 @@ impl Fragment { SpecificFragmentInfo::TableWrapper => false, SpecificFragmentInfo::Canvas(_) | SpecificFragmentInfo::Generic | + SpecificFragmentInfo::GeneratedContent(_) | SpecificFragmentInfo::Iframe(_) | SpecificFragmentInfo::Image(_) | SpecificFragmentInfo::ScannedText(_) | @@ -2133,3 +2119,9 @@ fn strip_trailing_whitespace(text_run: &TextRun, range: &mut Range) - return true } +/// A mutable iterator over fragments. +pub trait FragmentMutator { + /// The operation to perform. + fn process(&mut self, fragment: &mut Fragment); +} + diff --git a/servo/components/layout/generated_content.rs b/servo/components/layout/generated_content.rs new file mode 100644 index 000000000000..d4ec6e5a5348 --- /dev/null +++ b/servo/components/layout/generated_content.rs @@ -0,0 +1,573 @@ +/* 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/. */ + +//! The generated content assignment phase. +//! +//! This phase handles CSS counters, quotes, and ordered lists per CSS § 12.3-12.5. It cannot be +//! done in parallel and is therefore a sequential pass that runs on as little of the flow tree +//! as possible. + +use context::LayoutContext; +use flow::{self, AFFECTS_COUNTERS, Flow, HAS_COUNTER_AFFECTING_CHILDREN, ImmutableFlowUtils}; +use flow::{InorderFlowTraversal}; +use fragment::{Fragment, FragmentMutator, GeneratedContentInfo, SpecificFragmentInfo}; +use fragment::{UnscannedTextFragmentInfo}; +use incremental::{self, RESOLVE_GENERATED_CONTENT}; +use text::TextRunScanner; + +use gfx::display_list::OpaqueNode; +use servo_util::smallvec::{SmallVec, SmallVec8}; +use std::collections::{DList, HashMap}; +use std::sync::Arc; +use style::computed_values::content::ContentItem; +use style::computed_values::{display, list_style_type}; +use style::properties::ComputedValues; + +// Decimal styles per CSS-COUNTER-STYLES § 6.1: +static DECIMAL: [char; 10] = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ]; +// TODO(pcwalton): `decimal-leading-zero` +static ARABIC_INDIC: [char; 10] = [ '٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩' ]; +// TODO(pcwalton): `armenian`, `upper-armenian`, `lower-armenian` +static BENGALI: [char; 10] = [ '০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯' ]; +static CAMBODIAN: [char; 10] = [ '០', '១', '២', '៣', '៤', '៥', '៦', '៧', '៨', '៩' ]; +// TODO(pcwalton): Suffix for CJK decimal. +static CJK_DECIMAL: [char; 10] = [ '〇', '一', '二', '三', '四', '五', '六', '七', '八', '九' ]; +static DEVANAGARI: [char; 10] = [ '०', '१', '२', '३', '४', '५', '६', '७', '८', '९' ]; +// TODO(pcwalton): `georgian` +static GUJARATI: [char; 10] = ['૦', '૧', '૨', '૩', '૪', '૫', '૬', '૭', '૮', '૯']; +static GURMUKHI: [char; 10] = ['੦', '੧', '੨', '੩', '੪', '੫', '੬', '੭', '੮', '੯']; +// TODO(pcwalton): `hebrew` +static KANNADA: [char; 10] = ['೦', '೧', '೨', '೩', '೪', '೫', '೬', '೭', '೮', '೯']; +static LAO: [char; 10] = ['໐', '໑', '໒', '໓', '໔', '໕', '໖', '໗', '໘', '໙']; +static MALAYALAM: [char; 10] = ['൦', '൧', '൨', '൩', '൪', '൫', '൬', '൭', '൮', '൯']; +static MONGOLIAN: [char; 10] = ['᠐', '᠑', '᠒', '᠓', '᠔', '᠕', '᠖', '᠗', '᠘', '᠙']; +static MYANMAR: [char; 10] = ['၀', '၁', '၂', '၃', '၄', '၅', '၆', '၇', '၈', '၉']; +static ORIYA: [char; 10] = ['୦', '୧', '୨', '୩', '୪', '୫', '୬', '୭', '୮', '୯']; +static PERSIAN: [char; 10] = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; +// TODO(pcwalton): `lower-roman`, `upper-roman` +static TELUGU: [char; 10] = ['౦', '౧', '౨', '౩', '౪', '౫', '౬', '౭', '౮', '౯']; +static THAI: [char; 10] = ['๐', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙']; +static TIBETAN: [char; 10] = ['༠', '༡', '༢', '༣', '༤', '༥', '༦', '༧', '༨', '༩']; + +// Alphabetic styles per CSS-COUNTER-STYLES § 6.2: +static LOWER_ALPHA: [char; 26] = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z' +]; +static UPPER_ALPHA: [char; 26] = [ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' +]; +static CJK_EARTHLY_BRANCH: [char; 12] = [ + '子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥' +]; +static CJK_HEAVENLY_STEM: [char; 10] = [ + '甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸' +]; +static LOWER_GREEK: [char; 24] = [ + 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ', 'σ', 'τ', + 'υ', 'φ', 'χ', 'ψ', 'ω' +]; +static HIRAGANA: [char; 48] = [ + 'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し', 'す', 'せ', 'そ', + 'た', 'ち', 'つ', 'て', 'と', 'な', 'に', 'ぬ', 'ね', 'の', 'は', 'ひ', 'ふ', 'へ', 'ほ', + 'ま', 'み', 'む', 'め', 'も', 'や', 'ゆ', 'よ', 'ら', 'り', 'る', 'れ', 'ろ', + 'わ', 'ゐ', 'ゑ', 'を', 'ん' +]; +static HIRAGANA_IROHA: [char; 47] = [ + 'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を', 'わ', 'か', 'よ', + 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら', 'む', 'う', 'ゐ', 'の', 'お', 'く', 'や', 'ま', + 'け', 'ふ', 'こ', 'え', 'て', 'あ', 'さ', 'き', 'ゆ', 'め', 'み', 'し', 'ゑ', + 'ひ', 'も', 'せ', 'す' +]; +static KATAKANA: [char; 48] = [ + 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス', 'セ', 'ソ', + 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', 'ヌ', 'ネ', 'ノ', 'ハ', 'ヒ', 'フ', 'ヘ', 'ホ', + 'マ', 'ミ', 'ム', 'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', + 'ワ', 'ヰ', 'ヱ', 'ヲ', 'ン' +]; +static KATAKANA_IROHA: [char; 47] = [ + 'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル', 'ヲ', 'ワ', 'カ', 'ヨ', + 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ', 'ム', 'ウ', 'ヰ', 'ノ', 'オ', 'ク', 'ヤ', 'マ', + 'ケ', 'フ', 'コ', 'エ', 'テ', 'ア', 'サ', 'キ', 'ユ', 'メ', 'ミ', 'シ', 'ヱ', + 'ヒ', 'モ', 'セ', 'ス' +]; + +/// The generated content resolution traversal. +pub struct ResolveGeneratedContent<'a> { + /// The layout context. + layout_context: &'a LayoutContext<'a>, + /// The counter representing an ordered list item. + list_item: Counter, + /// Named CSS counters. + counters: HashMap, + /// The level of quote nesting. + quote: u32, +} + +impl<'a> ResolveGeneratedContent<'a> { + /// Creates a new generated content resolution traversal. + pub fn new(layout_context: &'a LayoutContext<'a>) -> ResolveGeneratedContent<'a> { + ResolveGeneratedContent { + layout_context: layout_context, + list_item: Counter::new(), + counters: HashMap::new(), + quote: 0, + } + } +} + +impl<'a> InorderFlowTraversal for ResolveGeneratedContent<'a> { + #[inline] + fn process(&mut self, flow: &mut Flow, level: u32) { + let mut mutator = ResolveGeneratedContentFragmentMutator { + traversal: self, + level: level, + is_block: flow.is_block_like(), + incremented: false, + }; + flow.mutate_fragments(&mut mutator); + } + + #[inline] + fn should_process(&mut self, flow: &mut Flow) -> bool { + flow::base(flow).restyle_damage.intersects(RESOLVE_GENERATED_CONTENT) || + flow::base(flow).flags.intersects(AFFECTS_COUNTERS | HAS_COUNTER_AFFECTING_CHILDREN) + } +} + +/// The object that mutates the generated content fragments. +struct ResolveGeneratedContentFragmentMutator<'a,'b:'a> { + /// The traversal. + traversal: &'a mut ResolveGeneratedContent<'b>, + /// The level we're at in the flow tree. + level: u32, + /// Whether this flow is a block flow. + is_block: bool, + /// Whether we've incremented the counter yet. + incremented: bool, +} + +impl<'a,'b> FragmentMutator for ResolveGeneratedContentFragmentMutator<'a,'b> { + fn process(&mut self, fragment: &mut Fragment) { + // We only reset and/or increment counters once per flow. This avoids double-incrementing + // counters on list items (once for the main fragment and once for the marker). + if !self.incremented { + self.reset_and_increment_counters_as_necessary(fragment); + } + + let mut list_style_type = fragment.style().get_list().list_style_type; + if fragment.style().get_box().display != display::T::list_item { + list_style_type = list_style_type::T::none + } + + let mut new_info = None; + { + let info = + if let SpecificFragmentInfo::GeneratedContent(ref mut info) = fragment.specific { + info + } else { + return + }; + + match **info { + GeneratedContentInfo::ListItem => { + new_info = self.traversal.list_item.render(self.traversal.layout_context, + fragment.node, + fragment.style.clone(), + list_style_type, + RenderingMode::Suffix(".\u{00a0}")) + } + GeneratedContentInfo::ContentItem(ContentItem::String(_)) => { + // Nothing to do here. + } + GeneratedContentInfo::ContentItem(ContentItem::Counter(ref counter_name, + list_style_type)) => { + let mut temporary_counter = Counter::new(); + let counter = self.traversal + .counters + .get(counter_name.as_slice()) + .unwrap_or(&mut temporary_counter); + new_info = counter.render(self.traversal.layout_context, + fragment.node, + fragment.style.clone(), + list_style_type, + RenderingMode::Plain) + } + GeneratedContentInfo::ContentItem(ContentItem::Counters(ref counter_name, + ref separator, + list_style_type)) => { + let mut temporary_counter = Counter::new(); + let counter = self.traversal + .counters + .get(counter_name.as_slice()) + .unwrap_or(&mut temporary_counter); + new_info = counter.render(self.traversal.layout_context, + fragment.node, + fragment.style.clone(), + list_style_type, + RenderingMode::All(separator.as_slice())) + } + GeneratedContentInfo::ContentItem(ContentItem::OpenQuote) => { + new_info = Some(render_text(self.traversal.layout_context, + fragment.node, + fragment.style.clone(), + self.quote(&*fragment.style, false))); + self.traversal.quote += 1 + } + GeneratedContentInfo::ContentItem(ContentItem::CloseQuote) => { + if self.traversal.quote >= 1 { + self.traversal.quote -= 1 + } + + new_info = Some(render_text(self.traversal.layout_context, + fragment.node, + fragment.style.clone(), + self.quote(&*fragment.style, true))); + } + GeneratedContentInfo::ContentItem(ContentItem::NoOpenQuote) => { + self.traversal.quote += 1 + } + GeneratedContentInfo::ContentItem(ContentItem::NoCloseQuote) => { + if self.traversal.quote >= 1 { + self.traversal.quote -= 1 + } + } + } + }; + + if let Some(new_info) = new_info { + fragment.specific = new_info + } + } +} + +impl<'a,'b> ResolveGeneratedContentFragmentMutator<'a,'b> { + fn reset_and_increment_counters_as_necessary(&mut self, fragment: &mut Fragment) { + let mut list_style_type = fragment.style().get_list().list_style_type; + if !self.is_block || fragment.style().get_box().display != display::T::list_item { + list_style_type = list_style_type::T::none + } + + match list_style_type { + list_style_type::T::disc | list_style_type::T::none | list_style_type::T::circle | + list_style_type::T::square | list_style_type::T::disclosure_open | + list_style_type::T::disclosure_closed => {} + _ => self.traversal.list_item.increment(self.level, 1), + } + + // Truncate down counters. + for (_, counter) in self.traversal.counters.iter_mut() { + counter.truncate_to_level(self.level); + } + self.traversal.list_item.truncate_to_level(self.level); + + for &(ref counter_name, value) in fragment.style().get_counters().counter_reset.0.iter() { + if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) { + counter.reset(self.level, value); + continue + } + + let mut counter = Counter::new(); + counter.reset(self.level, value); + self.traversal.counters.insert((*counter_name).clone(), counter); + } + + for &(ref counter_name, value) in fragment.style() + .get_counters() + .counter_increment + .0 + .iter() { + if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) { + counter.increment(self.level, value); + continue + } + + let mut counter = Counter::new(); + counter.increment(self.level, value); + self.traversal.counters.insert((*counter_name).clone(), counter); + } + + self.incremented = true + } + + fn quote(&self, style: &ComputedValues, close: bool) -> String { + let quotes = &style.get_list().quotes; + debug_assert!(!quotes.0.is_empty()); + let &(ref open_quote, ref close_quote) = + if self.traversal.quote as uint >= quotes.0.len() { + quotes.0.last().unwrap() + } else { + "es.0[self.traversal.quote as uint] + }; + if close { + close_quote.to_string() + } else { + open_quote.to_string() + } + } +} + +/// A counter per CSS 2.1 § 12.4. +struct Counter { + /// The values at each level. + values: Vec, +} + +impl Counter { + fn new() -> Counter { + Counter { + values: Vec::new(), + } + } + + fn reset(&mut self, level: u32, value: i32) { + // Do we have an instance of the counter at this level? If so, just mutate it. + match self.values.last_mut() { + Some(ref mut existing_value) if level == existing_value.level => { + existing_value.value = value; + return + } + _ => {} + } + + // Otherwise, push a new instance of the counter. + self.values.push(CounterValue { + level: level, + value: value, + }) + } + + fn truncate_to_level(&mut self, level: u32) { + let mut position = None; + for (i, value) in self.values.iter().enumerate() { + if value.level > level { + position = Some(i); + break + } + } + + if let Some(position) = position { + self.values.truncate(position) + } + } + + fn increment(&mut self, level: u32, amount: i32) { + if let Some(ref mut value) = self.values.last_mut() { + value.value += amount; + return + } + + self.values.push(CounterValue { + level: level, + value: amount, + }) + } + + fn render(&self, + layout_context: &LayoutContext, + node: OpaqueNode, + style: Arc, + list_style_type: list_style_type::T, + mode: RenderingMode) + -> Option { + let mut string = String::new(); + match mode { + RenderingMode::Plain => { + let value = match self.values.last() { + Some(ref value) => value.value, + None => 0, + }; + push_representation(value, list_style_type, &mut string) + } + RenderingMode::Suffix(suffix) => { + let value = match self.values.last() { + Some(ref value) => value.value, + None => 0, + }; + push_representation(value, list_style_type, &mut string); + string.push_str(suffix) + } + RenderingMode::All(separator) => { + let mut first = true; + for value in self.values.iter() { + if !first { + string.push_str(separator) + } + first = false; + push_representation(value.value, list_style_type, &mut string) + } + } + } + + if string.is_empty() { + None + } else { + Some(render_text(layout_context, node, style, string)) + } + } +} + +/// How a counter value is to be rendered. +enum RenderingMode<'a> { + /// The innermost counter value is rendered with no extra decoration. + Plain, + /// The innermost counter value is rendered with the given string suffix. + Suffix(&'a str), + /// All values of the counter are rendered with the given separator string between them. + All(&'a str), +} + +/// The value of a counter at a given level. +struct CounterValue { + /// The level of the flow tree that this corresponds to. + level: u32, + /// The value of the counter at this level. + value: i32, +} + +/// Creates fragment info for a literal string. +fn render_text(layout_context: &LayoutContext, + node: OpaqueNode, + style: Arc, + string: String) + -> SpecificFragmentInfo { + let mut fragments = DList::new(); + let info = SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::from_text(string)); + fragments.push_back(Fragment::from_opaque_node_and_style(node, + style, + incremental::all(), + info)); + let fragments = TextRunScanner::new().scan_for_runs(layout_context.font_context(), fragments); + debug_assert!(fragments.len() == 1); + fragments.fragments.into_iter().next().unwrap().specific +} + +/// Appends string that represents the value rendered using the system appropriate for the given +/// `list-style-type` onto the given string. +fn push_representation(value: i32, list_style_type: list_style_type::T, accumulator: &mut String) { + match list_style_type { + list_style_type::T::none => {} + list_style_type::T::disc | + list_style_type::T::circle | + list_style_type::T::square | + list_style_type::T::disclosure_open | + list_style_type::T::disclosure_closed => { + accumulator.push_str(static_representation(list_style_type).unwrap()) + } + list_style_type::T::decimal => push_numeric_representation(value, &DECIMAL, accumulator), + list_style_type::T::arabic_indic => { + push_numeric_representation(value, &ARABIC_INDIC, accumulator) + } + list_style_type::T::bengali => push_numeric_representation(value, &BENGALI, accumulator), + list_style_type::T::cambodian | list_style_type::T::khmer => { + push_numeric_representation(value, &CAMBODIAN, accumulator) + } + list_style_type::T::cjk_decimal => { + push_numeric_representation(value, &CJK_DECIMAL, accumulator) + } + list_style_type::T::devanagari => { + push_numeric_representation(value, &DEVANAGARI, accumulator) + } + list_style_type::T::gujarati => push_numeric_representation(value, &GUJARATI, accumulator), + list_style_type::T::gurmukhi => push_numeric_representation(value, &GURMUKHI, accumulator), + list_style_type::T::kannada => push_numeric_representation(value, &KANNADA, accumulator), + list_style_type::T::lao => push_numeric_representation(value, &LAO, accumulator), + list_style_type::T::malayalam => { + push_numeric_representation(value, &MALAYALAM, accumulator) + } + list_style_type::T::mongolian => { + push_numeric_representation(value, &MONGOLIAN, accumulator) + } + list_style_type::T::myanmar => push_numeric_representation(value, &MYANMAR, accumulator), + list_style_type::T::oriya => push_numeric_representation(value, &ORIYA, accumulator), + list_style_type::T::persian => push_numeric_representation(value, &PERSIAN, accumulator), + list_style_type::T::telugu => push_numeric_representation(value, &TELUGU, accumulator), + list_style_type::T::thai => push_numeric_representation(value, &THAI, accumulator), + list_style_type::T::tibetan => push_numeric_representation(value, &TIBETAN, accumulator), + list_style_type::T::lower_alpha => { + push_alphabetic_representation(value, &LOWER_ALPHA, accumulator) + } + list_style_type::T::upper_alpha => { + push_alphabetic_representation(value, &UPPER_ALPHA, accumulator) + } + list_style_type::T::cjk_earthly_branch => { + push_alphabetic_representation(value, &CJK_EARTHLY_BRANCH, accumulator) + } + list_style_type::T::cjk_heavenly_stem => { + push_alphabetic_representation(value, &CJK_HEAVENLY_STEM, accumulator) + } + list_style_type::T::lower_greek => { + push_alphabetic_representation(value, &LOWER_GREEK, accumulator) + } + list_style_type::T::hiragana => { + push_alphabetic_representation(value, &HIRAGANA, accumulator) + } + list_style_type::T::hiragana_iroha => { + push_alphabetic_representation(value, &HIRAGANA_IROHA, accumulator) + } + list_style_type::T::katakana => { + push_alphabetic_representation(value, &KATAKANA, accumulator) + } + list_style_type::T::katakana_iroha => { + push_alphabetic_representation(value, &KATAKANA_IROHA, accumulator) + } + } +} + +/// Returns the static string that represents the value rendered using the given list-style, if +/// possible. +pub fn static_representation(list_style_type: list_style_type::T) -> Option<&'static str> { + match list_style_type { + list_style_type::T::disc => Some("•\u{00a0}"), + list_style_type::T::circle => Some("◦\u{00a0}"), + list_style_type::T::square => Some("▪\u{00a0}"), + list_style_type::T::disclosure_open => Some("▾\u{00a0}"), + list_style_type::T::disclosure_closed => Some("‣\u{00a0}"), + _ => None, + } +} + +/// Pushes the string that represents the value rendered using the given *alphabetic system* onto +/// the accumulator per CSS-COUNTER-STYLES § 3.1.4. +fn push_alphabetic_representation(mut value: i32, system: &[char], accumulator: &mut String) { + let mut string = SmallVec8::new(); + while value != 0 { + // Step 1. + value = value - 1; + // Step 2. + string.push(system[(value as uint) % system.len()]); + // Step 3. + value = ((value as uint) / system.len()) as i32; + } + + for i in range(0, string.len()).rev() { + accumulator.push(*string.get(i)) + } +} + +/// Pushes the string that represents the value rendered using the given *numeric system* onto the +/// accumulator per CSS-COUNTER-STYLES § 3.1.4. +fn push_numeric_representation(mut value: i32, system: &[char], accumulator: &mut String) { + // Step 1. + if value == 0 { + accumulator.push(system[0]); + return + } + + // Step 2. + let mut string = SmallVec8::new(); + while value != 0 { + // Step 2.1. + string.push(system[(value as uint) % system.len()]); + // Step 2.2. + value = ((value as uint) / system.len()) as i32; + } + + // Step 3. + for i in range(0, string.len()).rev() { + accumulator.push(*string.get(i)) + } +} + diff --git a/servo/components/layout/incremental.rs b/servo/components/layout/incremental.rs index b163a8fbf29a..2bf7a67ee7a5 100644 --- a/servo/components/layout/incremental.rs +++ b/servo/components/layout/incremental.rs @@ -2,8 +2,7 @@ * 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 flow::{self, Flow}; -use flow::{IS_ABSOLUTELY_POSITIONED}; +use flow::{self, AFFECTS_COUNTERS, Flow, HAS_COUNTER_AFFECTING_CHILDREN, IS_ABSOLUTELY_POSITIONED}; use std::fmt; use std::sync::Arc; @@ -32,8 +31,12 @@ bitflags! { #[doc = "top-down."] const REFLOW = 0x08, + #[doc = "Re-resolve generated content. \ + Propagates up the flow tree because the computation is inorder."] + const RESOLVE_GENERATED_CONTENT = 0x10, + #[doc = "The entire flow needs to be reconstructed."] - const RECONSTRUCT_FLOW = 0x10 + const RECONSTRUCT_FLOW = 0x20 } } @@ -50,9 +53,9 @@ impl RestyleDamage { /// we should add to the *parent* of this flow. pub fn damage_for_parent(self, child_is_absolutely_positioned: bool) -> RestyleDamage { if child_is_absolutely_positioned { - self & (REPAINT | REFLOW_OUT_OF_FLOW) + self & (REPAINT | REFLOW_OUT_OF_FLOW | RESOLVE_GENERATED_CONTENT) } else { - self & (REPAINT | REFLOW | REFLOW_OUT_OF_FLOW) + self & (REPAINT | REFLOW | REFLOW_OUT_OF_FLOW | RESOLVE_GENERATED_CONTENT) } } @@ -91,10 +94,11 @@ impl fmt::Debug for RestyleDamage { let mut first_elem = true; let to_iter = - [ (REPAINT, "Repaint") - , (BUBBLE_ISIZES, "BubbleISizes") + [ (REPAINT, "Repaint") + , (BUBBLE_ISIZES, "BubbleISizes") , (REFLOW_OUT_OF_FLOW, "ReflowOutOfFlow") - , (REFLOW, "Reflow") + , (REFLOW, "Reflow") + , (RESOLVE_GENERATED_CONTENT, "ResolveGeneratedContent") , (RECONSTRUCT_FLOW, "ReconstructFlow") ]; @@ -126,10 +130,18 @@ macro_rules! add_if_not_equal( }) ); +/// Returns a bitmask that represents a fully damaged flow. +/// +/// Use this instead of `RestyleDamage::all()` because `RestyleDamage::all()` will result in +/// unnecessary sequential resolution of generated content. +pub fn all() -> RestyleDamage { + REPAINT | BUBBLE_ISIZES | REFLOW_OUT_OF_FLOW | REFLOW | RECONSTRUCT_FLOW +} + pub fn compute_damage(old: &Option>, new: &ComputedValues) -> RestyleDamage { let old: &ComputedValues = match old.as_ref() { - None => return RestyleDamage::all(), + None => return all(), Some(cv) => &**cv, }; @@ -186,6 +198,9 @@ impl<'a> LayoutDamageComputation for &'a mut (Flow + 'a) { let mut special_damage = SpecialRestyleDamage::empty(); let is_absolutely_positioned = flow::base(self).flags.contains(IS_ABSOLUTELY_POSITIONED); + // In addition to damage, we use this phase to compute whether nodes affect CSS counters. + let mut has_counter_affecting_children = false; + { let self_base = flow::mut_base(self); for kid in self_base.children.iter_mut() { @@ -199,21 +214,32 @@ impl<'a> LayoutDamageComputation for &'a mut (Flow + 'a) { self_base.restyle_damage .insert(flow::base(kid).restyle_damage.damage_for_parent( child_is_absolutely_positioned)); + + has_counter_affecting_children = has_counter_affecting_children || + flow::base(kid).flags.intersects(AFFECTS_COUNTERS | + HAS_COUNTER_AFFECTING_CHILDREN); + } } - let self_base = flow::base(self); + let self_base = flow::mut_base(self); if self_base.flags.float_kind() != float::T::none && self_base.restyle_damage.intersects(REFLOW) { special_damage.insert(REFLOW_ENTIRE_DOCUMENT); } + if has_counter_affecting_children { + self_base.flags.insert(HAS_COUNTER_AFFECTING_CHILDREN) + } else { + self_base.flags.remove(HAS_COUNTER_AFFECTING_CHILDREN) + } + special_damage } fn reflow_entire_document(self) { let self_base = flow::mut_base(self); - self_base.restyle_damage.insert(RestyleDamage::all()); + self_base.restyle_damage.insert(all()); self_base.restyle_damage.remove(RECONSTRUCT_FLOW); for kid in self_base.children.iter_mut() { kid.reflow_entire_document(); diff --git a/servo/components/layout/inline.rs b/servo/components/layout/inline.rs index 0a4b575c3c46..5c25d5ab00f5 100644 --- a/servo/components/layout/inline.rs +++ b/servo/components/layout/inline.rs @@ -11,10 +11,10 @@ use floats::{FloatKind, Floats, PlacementInfo}; use flow::{BaseFlow, FlowClass, Flow, MutableFlowUtils, ForceNonfloatedFlag}; use flow::{IS_ABSOLUTELY_POSITIONED}; use flow; -use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, ScannedTextFragmentInfo}; -use fragment::{SpecificFragmentInfo}; +use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, FragmentMutator}; +use fragment::{ScannedTextFragmentInfo, SpecificFragmentInfo}; use fragment::SplitInfo; -use incremental::{REFLOW, REFLOW_OUT_OF_FLOW}; +use incremental::{REFLOW, REFLOW_OUT_OF_FLOW, RESOLVE_GENERATED_CONTENT}; use layout_debug; use model::IntrinsicISizesContribution; use text; @@ -789,14 +789,22 @@ pub struct InlineFlow { impl InlineFlow { pub fn from_fragments(fragments: InlineFragments, writing_mode: WritingMode) -> InlineFlow { - InlineFlow { + let mut flow = InlineFlow { base: BaseFlow::new(None, writing_mode, ForceNonfloatedFlag::ForceNonfloated), fragments: fragments, lines: Vec::new(), minimum_block_size_above_baseline: Au(0), minimum_depth_below_baseline: Au(0), first_line_indentation: Au(0), + }; + + for fragment in flow.fragments.fragments.iter() { + if fragment.is_generated_content() { + flow.base.restyle_damage.insert(RESOLVE_GENERATED_CONTENT) + } } + + flow } /// Returns the distance from the baseline for the logical block-start inline-start corner of @@ -1391,6 +1399,12 @@ impl Flow for InlineFlow { .translate(stacking_context_position)) } } + + fn mutate_fragments(&mut self, mutator: &mut FragmentMutator) { + for fragment in self.fragments.fragments.iter_mut() { + mutator.process(fragment) + } + } } impl fmt::Debug for InlineFlow { diff --git a/servo/components/layout/layout_task.rs b/servo/components/layout/layout_task.rs index ca321f23526e..3a0959d6b484 100644 --- a/servo/components/layout/layout_task.rs +++ b/servo/components/layout/layout_task.rs @@ -834,6 +834,14 @@ impl LayoutTask { layout_debug::begin_trace(layout_root.clone()); } + // Resolve generated content. + profile(TimeProfilerCategory::LayoutGeneratedContent, + self.profiler_metadata(data), + self.time_profiler_chan.clone(), + || { + sequential::resolve_generated_content(&mut layout_root, &shared_layout_context) + }); + // Perform the primary layout passes over the flow tree to compute the locations of all // the boxes. profile(TimeProfilerCategory::LayoutMain, diff --git a/servo/components/layout/lib.rs b/servo/components/layout/lib.rs index 837945eff817..0985a51997e3 100644 --- a/servo/components/layout/lib.rs +++ b/servo/components/layout/lib.rs @@ -66,7 +66,9 @@ pub mod flow; pub mod flow_list; pub mod flow_ref; pub mod fragment; +pub mod generated_content; pub mod layout_task; +pub mod incremental; pub mod inline; pub mod list_item; pub mod model; @@ -82,7 +84,6 @@ pub mod table_cell; pub mod text; pub mod traversal; pub mod util; -pub mod incremental; pub mod wrapper; pub mod css { diff --git a/servo/components/layout/list_item.rs b/servo/components/layout/list_item.rs index eedb2d3b6b6e..23748480cf10 100644 --- a/servo/components/layout/list_item.rs +++ b/servo/components/layout/list_item.rs @@ -8,12 +8,14 @@ #![deny(unsafe_blocks)] use block::BlockFlow; -use construct::FlowConstructor; use context::LayoutContext; use display_list_builder::ListItemFlowDisplayListBuilding; use floats::FloatKind; use flow::{Flow, FlowClass}; -use fragment::{Fragment, FragmentBorderBoxIterator}; +use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, FragmentMutator}; +use fragment::{GeneratedContentInfo}; +use generated_content; +use incremental::RESOLVE_GENERATED_CONTENT; use wrapper::ThreadSafeLayoutNode; use geom::{Point2D, Rect}; @@ -36,19 +38,33 @@ pub struct ListItemFlow { } impl ListItemFlow { - pub fn from_node_marker_and_flotation(constructor: &mut FlowConstructor, - node: &ThreadSafeLayoutNode, - marker_fragment: Option, - flotation: Option) - -> ListItemFlow { - ListItemFlow { + pub fn from_node_fragments_and_flotation(node: &ThreadSafeLayoutNode, + main_fragment: Fragment, + marker_fragment: Option, + flotation: Option) + -> ListItemFlow { + let mut this = ListItemFlow { block_flow: if let Some(flotation) = flotation { - BlockFlow::float_from_node(constructor, node, flotation) + BlockFlow::float_from_node_and_fragment(node, main_fragment, flotation) } else { - BlockFlow::from_node(constructor, node) + BlockFlow::from_node_and_fragment(node, main_fragment) }, marker: marker_fragment, + }; + + if let Some(ref marker) = this.marker { + match marker.style().get_list().list_style_type { + list_style_type::T::disc | + list_style_type::T::none | + list_style_type::T::circle | + list_style_type::T::square | + list_style_type::T::disclosure_open | + list_style_type::T::disclosure_closed => {} + _ => this.block_flow.base.restyle_damage.insert(RESOLVE_GENERATED_CONTENT), + } } + + this } } @@ -134,24 +150,55 @@ impl Flow for ListItemFlow { fn iterate_through_fragment_border_boxes(&self, iterator: &mut FragmentBorderBoxIterator, stacking_context_position: &Point2D) { - self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position) + self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position); + + if let Some(ref marker) = self.marker { + if iterator.should_process(marker) { + iterator.process( + marker, + &marker.stacking_relative_border_box(&self.block_flow + .base + .stacking_relative_position, + &self.block_flow + .base + .absolute_position_info + .relative_containing_block_size, + CoordinateSystem::Parent) + .translate(stacking_context_position)); + } + } + } + + fn mutate_fragments(&mut self, mutator: &mut FragmentMutator) { + self.block_flow.mutate_fragments(mutator); + + if let Some(ref mut marker) = self.marker { + mutator.process(marker) + } } } -/// Returns the static text to be used for the given value of the `list-style-type` property. -/// -/// TODO(pcwalton): Return either a string or a counter descriptor, once we support counters. -pub fn static_text_for_list_style_type(list_style_type: list_style_type::T) - -> Option<&'static str> { - // Just to keep things simple, use a nonbreaking space (Unicode 0xa0) to provide the marker - // separation. - match list_style_type { - list_style_type::T::none => None, - list_style_type::T::disc => Some("•\u{a0}"), - list_style_type::T::circle => Some("◦\u{a0}"), - list_style_type::T::square => Some("▪\u{a0}"), - list_style_type::T::disclosure_open => Some("▾\u{a0}"), - list_style_type::T::disclosure_closed => Some("‣\u{a0}"), +/// The kind of content that `list-style-type` results in. +pub enum ListStyleTypeContent { + None, + StaticText(&'static str), + GeneratedContent(Box), +} + +impl ListStyleTypeContent { + /// Returns the content to be used for the given value of the `list-style-type` property. + pub fn from_list_style_type(list_style_type: list_style_type::T) -> ListStyleTypeContent { + // Just to keep things simple, use a nonbreaking space (Unicode 0xa0) to provide the marker + // separation. + match list_style_type { + list_style_type::T::none => ListStyleTypeContent::None, + list_style_type::T::disc | list_style_type::T::circle | list_style_type::T::square | + list_style_type::T::disclosure_open | list_style_type::T::disclosure_closed => { + let text = generated_content::static_representation(list_style_type).unwrap(); + ListStyleTypeContent::StaticText(text) + } + _ => ListStyleTypeContent::GeneratedContent(box GeneratedContentInfo::ListItem), + } } } diff --git a/servo/components/layout/sequential.rs b/servo/components/layout/sequential.rs index ff2bd948c516..c33441b6fafa 100644 --- a/servo/components/layout/sequential.rs +++ b/servo/components/layout/sequential.rs @@ -5,10 +5,11 @@ //! Implements sequential traversals over the DOM and flow trees. use context::{LayoutContext, SharedLayoutContext}; -use flow::{self, Flow, ImmutableFlowUtils, MutableFlowUtils, PostorderFlowTraversal}; -use flow::{PreorderFlowTraversal}; +use flow::{self, Flow, ImmutableFlowUtils, InorderFlowTraversal, MutableFlowUtils}; +use flow::{PostorderFlowTraversal, PreorderFlowTraversal}; use flow_ref::FlowRef; use fragment::FragmentBorderBoxIterator; +use generated_content::ResolveGeneratedContent; use traversal::{BubbleISizes, RecalcStyleForNode, ConstructFlows}; use traversal::{AssignBSizesAndStoreOverflow, AssignISizes}; use traversal::{ComputeAbsolutePositions, BuildDisplayList}; @@ -39,6 +40,24 @@ pub fn traverse_dom_preorder(root: LayoutNode, doit(root, recalc_style, construct_flows); } +pub fn resolve_generated_content(root: &mut FlowRef, shared_layout_context: &SharedLayoutContext) { + fn doit(flow: &mut Flow, level: u32, traversal: &mut ResolveGeneratedContent) { + if !traversal.should_process(flow) { + return + } + + traversal.process(flow, level); + + for kid in flow::mut_base(flow).children.iter_mut() { + doit(kid, level + 1, traversal) + } + } + + let layout_context = LayoutContext::new(shared_layout_context); + let mut traversal = ResolveGeneratedContent::new(&layout_context); + doit(&mut **root, 0, &mut traversal) +} + pub fn traverse_flow_tree_preorder(root: &mut FlowRef, shared_layout_context: &SharedLayoutContext) { fn doit(flow: &mut Flow, @@ -119,3 +138,4 @@ pub fn iterate_through_flow_tree_fragment_border_boxes(root: &mut FlowRef, doit(&mut **root, iterator, &ZERO_POINT); } + diff --git a/servo/components/layout/table.rs b/servo/components/layout/table.rs index 270143a21daa..4a23db3f532f 100644 --- a/servo/components/layout/table.rs +++ b/servo/components/layout/table.rs @@ -8,12 +8,11 @@ use block::{BlockFlow, ISizeAndMarginsComputer, MarginsMayCollapseFlag}; use block::{ISizeConstraintInput, ISizeConstraintSolution}; -use construct::FlowConstructor; use context::LayoutContext; use floats::FloatKind; use flow::{self, Flow, FlowClass, IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS}; -use flow::ImmutableFlowUtils; -use fragment::{Fragment, FragmentBorderBoxIterator}; +use flow::{ImmutableFlowUtils}; +use fragment::{Fragment, FragmentBorderBoxIterator, FragmentMutator}; use layout_debug; use model::{IntrinsicISizes, IntrinsicISizesContribution}; use table_row::CellIntrinsicInlineSize; @@ -55,12 +54,12 @@ impl TableFlow { fragment: Fragment) -> TableFlow { let mut block_flow = BlockFlow::from_node_and_fragment(node, fragment); - let table_layout = if block_flow.fragment().style().get_table().table_layout == - table_layout::T::fixed { - TableLayout::Fixed - } else { - TableLayout::Auto - }; + let table_layout = + if block_flow.fragment().style().get_table().table_layout == table_layout::T::fixed { + TableLayout::Fixed + } else { + TableLayout::Auto + }; TableFlow { block_flow: block_flow, column_intrinsic_inline_sizes: Vec::new(), @@ -69,35 +68,17 @@ impl TableFlow { } } - pub fn from_node(constructor: &mut FlowConstructor, - node: &ThreadSafeLayoutNode) - -> TableFlow { - let mut block_flow = BlockFlow::from_node(constructor, node); - let table_layout = if block_flow.fragment().style().get_table().table_layout == - table_layout::T::fixed { - TableLayout::Fixed - } else { - TableLayout::Auto - }; - TableFlow { - block_flow: block_flow, - column_intrinsic_inline_sizes: Vec::new(), - column_computed_inline_sizes: Vec::new(), - table_layout: table_layout - } - } - - pub fn float_from_node(constructor: &mut FlowConstructor, - node: &ThreadSafeLayoutNode, - float_kind: FloatKind) - -> TableFlow { - let mut block_flow = BlockFlow::float_from_node(constructor, node, float_kind); - let table_layout = if block_flow.fragment().style().get_table().table_layout == - table_layout::T::fixed { - TableLayout::Fixed - } else { - TableLayout::Auto - }; + pub fn float_from_node_and_fragment(node: &ThreadSafeLayoutNode, + fragment: Fragment, + float_kind: FloatKind) + -> TableFlow { + let mut block_flow = BlockFlow::float_from_node_and_fragment(node, fragment, float_kind); + let table_layout = + if block_flow.fragment().style().get_table().table_layout == table_layout::T::fixed { + TableLayout::Fixed + } else { + TableLayout::Auto + }; TableFlow { block_flow: block_flow, column_intrinsic_inline_sizes: Vec::new(), @@ -396,6 +377,10 @@ impl Flow for TableFlow { stacking_context_position: &Point2D) { self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position) } + + fn mutate_fragments(&mut self, mutator: &mut FragmentMutator) { + self.block_flow.mutate_fragments(mutator) + } } impl fmt::Debug for TableFlow { diff --git a/servo/components/layout/table_caption.rs b/servo/components/layout/table_caption.rs index 43eaacf9384e..f892929932b9 100644 --- a/servo/components/layout/table_caption.rs +++ b/servo/components/layout/table_caption.rs @@ -7,10 +7,9 @@ #![deny(unsafe_blocks)] use block::BlockFlow; -use construct::FlowConstructor; use context::LayoutContext; use flow::{FlowClass, Flow}; -use fragment::FragmentBorderBoxIterator; +use fragment::{Fragment, FragmentBorderBoxIterator, FragmentMutator}; use wrapper::ThreadSafeLayoutNode; use geom::{Point2D, Rect}; @@ -26,11 +25,10 @@ pub struct TableCaptionFlow { } impl TableCaptionFlow { - pub fn from_node(constructor: &mut FlowConstructor, - node: &ThreadSafeLayoutNode) - -> TableCaptionFlow { + pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) + -> TableCaptionFlow { TableCaptionFlow { - block_flow: BlockFlow::from_node(constructor, node) + block_flow: BlockFlow::from_node_and_fragment(node, fragment) } } } @@ -96,6 +94,10 @@ impl Flow for TableCaptionFlow { stacking_context_position: &Point2D) { self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position) } + + fn mutate_fragments(&mut self, mutator: &mut FragmentMutator) { + self.block_flow.mutate_fragments(mutator) + } } impl fmt::Debug for TableCaptionFlow { diff --git a/servo/components/layout/table_cell.rs b/servo/components/layout/table_cell.rs index 670dcf7b3444..2735f5294df7 100644 --- a/servo/components/layout/table_cell.rs +++ b/servo/components/layout/table_cell.rs @@ -9,7 +9,7 @@ use block::{BlockFlow, ISizeAndMarginsComputer, MarginsMayCollapseFlag}; use context::LayoutContext; use flow::{Flow, FlowClass}; -use fragment::{Fragment, FragmentBorderBoxIterator}; +use fragment::{Fragment, FragmentBorderBoxIterator, FragmentMutator}; use model::{MaybeAuto}; use layout_debug; use table::InternalTable; @@ -178,6 +178,10 @@ impl Flow for TableCellFlow { stacking_context_position: &Point2D) { self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position) } + + fn mutate_fragments(&mut self, mutator: &mut FragmentMutator) { + self.block_flow.mutate_fragments(mutator) + } } impl fmt::Debug for TableCellFlow { diff --git a/servo/components/layout/table_colgroup.rs b/servo/components/layout/table_colgroup.rs index 6d19a0c5778b..2bf02db4428f 100644 --- a/servo/components/layout/table_colgroup.rs +++ b/servo/components/layout/table_colgroup.rs @@ -9,7 +9,7 @@ use context::LayoutContext; use css::node_style::StyledNode; use flow::{BaseFlow, FlowClass, Flow, ForceNonfloatedFlag}; -use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo}; +use fragment::{Fragment, FragmentBorderBoxIterator, FragmentMutator, SpecificFragmentInfo}; use layout_debug; use wrapper::ThreadSafeLayoutNode; @@ -104,6 +104,8 @@ impl Flow for TableColGroupFlow { fn iterate_through_fragment_border_boxes(&self, _: &mut FragmentBorderBoxIterator, _: &Point2D) {} + + fn mutate_fragments(&mut self, _: &mut FragmentMutator) {} } impl fmt::Debug for TableColGroupFlow { diff --git a/servo/components/layout/table_row.rs b/servo/components/layout/table_row.rs index d712d7e4945d..6425b63d1ea4 100644 --- a/servo/components/layout/table_row.rs +++ b/servo/components/layout/table_row.rs @@ -8,11 +8,9 @@ use block::BlockFlow; use block::ISizeAndMarginsComputer; -use construct::FlowConstructor; use context::LayoutContext; -use flow::{FlowClass, Flow, ImmutableFlowUtils}; -use flow; -use fragment::{Fragment, FragmentBorderBoxIterator}; +use flow::{self, FlowClass, Flow, ImmutableFlowUtils}; +use fragment::{Fragment, FragmentBorderBoxIterator, FragmentMutator}; use layout_debug; use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable}; use model::MaybeAuto; @@ -49,8 +47,7 @@ pub struct CellIntrinsicInlineSize { } impl TableRowFlow { - pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, - fragment: Fragment) + pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> TableRowFlow { TableRowFlow { block_flow: BlockFlow::from_node_and_fragment(node, fragment), @@ -59,16 +56,6 @@ impl TableRowFlow { } } - pub fn from_node(constructor: &mut FlowConstructor, - node: &ThreadSafeLayoutNode) - -> TableRowFlow { - TableRowFlow { - block_flow: BlockFlow::from_node(constructor, node), - cell_intrinsic_inline_sizes: Vec::new(), - column_computed_inline_sizes: Vec::new() - } - } - pub fn fragment<'a>(&'a mut self) -> &'a Fragment { &self.block_flow.fragment } @@ -331,6 +318,10 @@ impl Flow for TableRowFlow { stacking_context_position: &Point2D) { self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position) } + + fn mutate_fragments(&mut self, mutator: &mut FragmentMutator) { + self.block_flow.mutate_fragments(mutator) + } } impl fmt::Debug for TableRowFlow { diff --git a/servo/components/layout/table_rowgroup.rs b/servo/components/layout/table_rowgroup.rs index 9728fde36a87..ac86cd8987e4 100644 --- a/servo/components/layout/table_rowgroup.rs +++ b/servo/components/layout/table_rowgroup.rs @@ -7,10 +7,9 @@ #![deny(unsafe_blocks)] use block::{BlockFlow, ISizeAndMarginsComputer, MarginsMayCollapseFlag}; -use construct::FlowConstructor; use context::LayoutContext; use flow::{FlowClass, Flow}; -use fragment::{Fragment, FragmentBorderBoxIterator}; +use fragment::{Fragment, FragmentBorderBoxIterator, FragmentMutator}; use layout_debug; use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable}; use wrapper::ThreadSafeLayoutNode; @@ -45,15 +44,6 @@ impl TableRowGroupFlow { } } - pub fn from_node(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) - -> TableRowGroupFlow { - TableRowGroupFlow { - block_flow: BlockFlow::from_node(constructor, node), - column_intrinsic_inline_sizes: Vec::new(), - column_computed_inline_sizes: Vec::new(), - } - } - pub fn fragment<'a>(&'a mut self) -> &'a Fragment { &self.block_flow.fragment } @@ -165,6 +155,10 @@ impl Flow for TableRowGroupFlow { stacking_context_position: &Point2D) { self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position) } + + fn mutate_fragments(&mut self, mutator: &mut FragmentMutator) { + self.block_flow.mutate_fragments(mutator) + } } impl fmt::Debug for TableRowGroupFlow { diff --git a/servo/components/layout/table_wrapper.rs b/servo/components/layout/table_wrapper.rs index e849150bede0..fbe124af1bf5 100644 --- a/servo/components/layout/table_wrapper.rs +++ b/servo/components/layout/table_wrapper.rs @@ -13,13 +13,13 @@ #![deny(unsafe_blocks)] -use block::{BlockFlow, BlockNonReplaced, FloatNonReplaced, ISizeAndMarginsComputer, MarginsMayCollapseFlag}; -use construct::FlowConstructor; +use block::{BlockFlow, BlockNonReplaced, FloatNonReplaced, ISizeAndMarginsComputer}; +use block::{MarginsMayCollapseFlag}; use context::LayoutContext; use floats::FloatKind; use flow::{FlowClass, Flow, ImmutableFlowUtils}; use flow::{IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS}; -use fragment::{Fragment, FragmentBorderBoxIterator}; +use fragment::{Fragment, FragmentBorderBoxIterator, FragmentMutator}; use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize}; use wrapper::ThreadSafeLayoutNode; @@ -70,23 +70,6 @@ impl TableWrapperFlow { } } - pub fn from_node(constructor: &mut FlowConstructor, - node: &ThreadSafeLayoutNode) - -> TableWrapperFlow { - let mut block_flow = BlockFlow::from_node(constructor, node); - let table_layout = if block_flow.fragment().style().get_table().table_layout == - table_layout::T::fixed { - TableLayout::Fixed - } else { - TableLayout::Auto - }; - TableWrapperFlow { - block_flow: block_flow, - column_intrinsic_inline_sizes: vec!(), - table_layout: table_layout - } - } - pub fn float_from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment, float_kind: FloatKind) @@ -383,6 +366,10 @@ impl Flow for TableWrapperFlow { stacking_context_position: &Point2D) { self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position) } + + fn mutate_fragments(&mut self, mutator: &mut FragmentMutator) { + self.block_flow.mutate_fragments(mutator) + } } impl fmt::Debug for TableWrapperFlow { diff --git a/servo/components/layout/traversal.rs b/servo/components/layout/traversal.rs index 2211cf2fcceb..04a99ee4b777 100644 --- a/servo/components/layout/traversal.rs +++ b/servo/components/layout/traversal.rs @@ -13,7 +13,7 @@ use context::LayoutContext; use flow::{Flow, MutableFlowUtils}; use flow::{PreorderFlowTraversal, PostorderFlowTraversal}; use flow; -use incremental::{RestyleDamage, BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW}; +use incremental::{self, BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW, RestyleDamage}; use wrapper::{layout_node_to_unsafe_layout_node, LayoutNode}; use wrapper::{PostorderNodeMutTraversal, ThreadSafeLayoutNode, UnsafeLayoutNode}; use wrapper::{PreorderDomTraversal, PostorderDomTraversal}; @@ -171,7 +171,7 @@ impl<'a> PreorderDomTraversal for RecalcStyleForNode<'a> { &mut applicable_declarations, &mut shareable); } else { - ThreadSafeLayoutNode::new(&node).set_restyle_damage(RestyleDamage::all()) + ThreadSafeLayoutNode::new(&node).set_restyle_damage(incremental::all()) } // Perform the CSS cascade. @@ -376,3 +376,4 @@ impl<'a> PostorderFlowTraversal for BuildDisplayList<'a> { flow.build_display_list(self.layout_context); } } + diff --git a/servo/components/layout/wrapper.rs b/servo/components/layout/wrapper.rs index 54f341d1d093..e43603d0dc11 100644 --- a/servo/components/layout/wrapper.rs +++ b/servo/components/layout/wrapper.rs @@ -67,9 +67,11 @@ use std::marker::ContravariantLifetime; use std::mem; use std::sync::mpsc::Sender; use string_cache::{Atom, Namespace}; -use style::computed_values::{content, display, white_space}; use selectors::parser::{NamespaceConstraint, AttrSelector}; -use style::legacy::{LengthAttribute, SimpleColorAttribute, UnsignedIntegerAttribute, IntegerAttribute}; +use style::computed_values::content::ContentItem; +use style::computed_values::{content, display, white_space}; +use style::legacy::{IntegerAttribute, LengthAttribute, SimpleColorAttribute}; +use style::legacy::{UnsignedIntegerAttribute}; use style::node::{TElement, TElementAttributes, TNode}; use style::properties::PropertyDeclarationBlock; use url::Url; @@ -154,10 +156,11 @@ pub trait TLayoutNode { } } - /// If this is a text node, copies out the text. If this is not a text node, fails. + /// If this is a text node or generated content, copies out its content. If this is not a text + /// node, fails. /// - /// FIXME(pcwalton): Don't copy text. Atomically reference count instead. - fn text(&self) -> String; + /// FIXME(pcwalton): This might have too much copying and/or allocation. Profile this. + fn text_content(&self) -> Vec; /// Returns the first child of this node. fn first_child(&self) -> Option; @@ -214,19 +217,25 @@ impl<'ln> TLayoutNode for LayoutNode<'ln> { } } - fn text(&self) -> String { + fn text_content(&self) -> Vec { unsafe { let text: Option> = TextCast::to_layout_js(self.get_jsmanaged()); if let Some(text) = text { - return (*text.unsafe_get()).characterdata().data_for_layout().to_owned(); + return vec![ + ContentItem::String((*text.unsafe_get()).characterdata() + .data_for_layout() + .to_owned()) + ]; } - let input: Option> = HTMLInputElementCast::to_layout_js(self.get_jsmanaged()); + let input: Option> = + HTMLInputElementCast::to_layout_js(self.get_jsmanaged()); if let Some(input) = input { - return input.get_value_for_layout(); + return vec![ContentItem::String(input.get_value_for_layout())]; } - let area: Option> = HTMLTextAreaElementCast::to_layout_js(self.get_jsmanaged()); + let area: Option> = + HTMLTextAreaElementCast::to_layout_js(self.get_jsmanaged()); if let Some(area) = area { - return area.get_value_for_layout(); + return vec![ContentItem::String(area.get_value_for_layout())]; } panic!("not text!") @@ -661,16 +670,10 @@ impl<'le> TElementAttributes for LayoutElement<'le> { } } -fn get_content(content_list: &content::T) -> String { +fn get_content(content_list: &content::T) -> Vec { match *content_list { - content::T::Content(ref value) => { - let iter = &mut value.clone().into_iter().peekable(); - match iter.next() { - Some(content::ContentItem::StringContent(content)) => content, - _ => "".to_owned(), - } - } - _ => "".to_owned(), + content::T::Content(ref value) if !value.is_empty() => (*value).clone(), + _ => vec![ContentItem::String("".to_owned())], } } @@ -762,7 +765,7 @@ impl<'ln> TLayoutNode for ThreadSafeLayoutNode<'ln> { } } - fn text(&self) -> String { + fn text_content(&self) -> Vec { if self.pseudo != PseudoElementType::Normal { let layout_data_ref = self.borrow_layout_data(); let node_layout_data_wrapper = layout_data_ref.as_ref().unwrap(); @@ -775,7 +778,7 @@ impl<'ln> TLayoutNode for ThreadSafeLayoutNode<'ln> { return get_content(&after_style.get_box().content) } } - self.node.text() + self.node.text_content() } } diff --git a/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl b/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl index d6a4420b2a9a..d89f1554354d 100644 --- a/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl +++ b/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl @@ -102,6 +102,11 @@ partial interface CSSStyleDeclaration { [TreatNullAs=EmptyString] attribute DOMString listStyleType; [TreatNullAs=EmptyString] attribute DOMString listStyleImage; + [TreatNullAs=EmptyString] attribute DOMString quotes; + + [TreatNullAs=EmptyString] attribute DOMString counterIncrement; + [TreatNullAs=EmptyString] attribute DOMString counterReset; + [TreatNullAs=EmptyString] attribute DOMString overflow; [TreatNullAs=EmptyString] attribute DOMString overflowX; [TreatNullAs=EmptyString] attribute DOMString overflowY; diff --git a/servo/components/style/properties.mako.rs b/servo/components/style/properties.mako.rs index 83d6570a800d..09aa328be0fe 100644 --- a/servo/components/style/properties.mako.rs +++ b/servo/components/style/properties.mako.rs @@ -758,86 +758,159 @@ pub mod longhands { ${switch_to_style_struct("Box")} <%self:longhand name="content"> - pub use self::computed_value::T as SpecifiedValue; - pub use self::computed_value::ContentItem; - use cssparser::Token; - use values::computed::ComputedValueAsSpecified; + use cssparser::{ToCss, Token}; + use std::ascii::AsciiExt; + use values::computed::ComputedValueAsSpecified; - impl ComputedValueAsSpecified for SpecifiedValue {} + use super::list_style_type; - pub mod computed_value { - use std::borrow::IntoCow; - use cssparser::{ToCss, Token}; - use text_writer::{self, TextWriter}; + pub use self::computed_value::T as SpecifiedValue; + pub use self::computed_value::ContentItem; - #[derive(PartialEq, Eq, Clone)] - pub enum ContentItem { - StringContent(String), + impl ComputedValueAsSpecified for SpecifiedValue {} + + pub mod computed_value { + use super::super::list_style_type; + + use cssparser::{ToCss, Token}; + use std::borrow::IntoCow; + use text_writer::{self, TextWriter}; + + #[derive(PartialEq, Eq, Clone)] + pub enum ContentItem { + /// Literal string content. + String(String), + /// `counter(name, style)`. + Counter(String, list_style_type::computed_value::T), + /// `counters(name, separator, style)`. + Counters(String, String, list_style_type::computed_value::T), + /// `open-quote`. + OpenQuote, + /// `close-quote`. + CloseQuote, + /// `no-open-quote`. + NoOpenQuote, + /// `no-close-quote`. + NoCloseQuote, + } + + impl ToCss for ContentItem { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + match self { + &ContentItem::String(ref s) => { + Token::QuotedString((&**s).into_cow()).to_css(dest) + } + &ContentItem::Counter(ref s, _) => { + // FIXME(pcwalton) + Token::QuotedString((&**s).into_cow()).to_css(dest) + } + &ContentItem::Counters(ref s, _, _) => { + // FIXME(pcwalton) + Token::QuotedString((&**s).into_cow()).to_css(dest) + } + &ContentItem::OpenQuote => dest.write_str("open-quote"), + &ContentItem::CloseQuote => dest.write_str("close-quote"), + &ContentItem::NoOpenQuote => dest.write_str("no-open-quote"), + &ContentItem::NoCloseQuote => dest.write_str("no-close-quote"), + } } + } - impl ToCss for ContentItem { - fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { - match self { - &ContentItem::StringContent(ref s) => { - Token::QuotedString((&**s).into_cow()).to_css(dest) - } - } - } - } - - #[allow(non_camel_case_types)] - #[derive(PartialEq, Eq, Clone)] - pub enum T { - normal, - none, - Content(Vec), - } - - impl ToCss for T { - fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { - match self { - &T::normal => dest.write_str("normal"), - &T::none => dest.write_str("none"), - &T::Content(ref content) => { - let mut iter = content.iter(); - try!(iter.next().unwrap().to_css(dest)); - for c in iter { - try!(c.to_css(dest)); - } - Ok(()) + #[allow(non_camel_case_types)] + #[derive(PartialEq, Eq, Clone)] + pub enum T { + normal, + none, + Content(Vec), + } + + impl ToCss for T { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + match self { + &T::normal => dest.write_str("normal"), + &T::none => dest.write_str("none"), + &T::Content(ref content) => { + let mut iter = content.iter(); + try!(iter.next().unwrap().to_css(dest)); + for c in iter { + try!(c.to_css(dest)); } + Ok(()) } } } } - #[inline] - pub fn get_initial_value() -> computed_value::T { - computed_value::T::normal - } + } + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T::normal + } - // normal | none | [ ]+ - // TODO: , , attr(), open-quote, close-quote, no-open-quote, no-close-quote - pub fn parse(_context: &ParserContext, input: &mut Parser) - -> Result { - if input.try(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(SpecifiedValue::normal) - } - if input.try(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(SpecifiedValue::none) - } - let mut content = vec![]; - loop { - match input.next() { - Ok(Token::QuotedString(value)) => { - content.push(ContentItem::StringContent(value.into_owned())) - } - Err(()) if !content.is_empty() => { - return Ok(SpecifiedValue::Content(content)) - } - _ => return Err(()) + pub fn counter_name_is_illegal(name: &str) -> bool { + name.eq_ignore_ascii_case("none") || name.eq_ignore_ascii_case("inherit") || + name.eq_ignore_ascii_case("initial") + } + + // normal | none | [ | | open-quote | close-quote | no-open-quote | + // no-close-quote ]+ + // TODO: , attr() + pub fn parse(context: &ParserContext, input: &mut Parser) + -> Result { + if input.try(|input| input.expect_ident_matching("normal")).is_ok() { + return Ok(SpecifiedValue::normal) + } + if input.try(|input| input.expect_ident_matching("none")).is_ok() { + return Ok(SpecifiedValue::none) + } + let mut content = vec![]; + loop { + match input.next() { + Ok(Token::QuotedString(value)) => { + content.push(ContentItem::String(value.into_owned())) } + Ok(Token::Function(name)) => { + content.push(try!(match_ignore_ascii_case! { name, + "counter" => input.parse_nested_block(|input| { + let name = try!(input.expect_ident()).into_owned(); + let style = input.try(|input| { + try!(input.expect_comma()); + list_style_type::parse(context, input) + }).unwrap_or(list_style_type::computed_value::T::decimal); + Ok(ContentItem::Counter(name, style)) + }), + "counters" => input.parse_nested_block(|input| { + let name = try!(input.expect_ident()).into_owned(); + try!(input.expect_comma()); + let separator = try!(input.expect_string()).into_owned(); + let style = input.try(|input| { + try!(input.expect_comma()); + list_style_type::parse(context, input) + }).unwrap_or(list_style_type::computed_value::T::decimal); + Ok(ContentItem::Counters(name, separator, style)) + }) + _ => return Err(()) + })); + } + Ok(Token::Ident(ident)) => { + match_ignore_ascii_case! { ident, + "open-quote" => content.push(ContentItem::OpenQuote), + "close-quote" => content.push(ContentItem::CloseQuote), + "no-open-quote" => content.push(ContentItem::NoOpenQuote), + "no-close-quote" => content.push(ContentItem::NoCloseQuote) + _ => return Err(()) + } + } + Err(()) if !content.is_empty() => { + let mut result = String::new(); + for content in content.iter() { + content.to_css(&mut result).unwrap() + } + return Ok(SpecifiedValue::Content(content)) + } + _ => return Err(()) } } + } ${new_style_struct("List", is_inherited=True)} @@ -846,14 +919,16 @@ pub mod longhands { // TODO(pcwalton): Implement the full set of counter styles per CSS-COUNTER-STYLES [1] 6.1: // - // decimal, decimal-leading-zero, arabic-indic, armenian, upper-armenian, lower-armenian, - // bengali, cambodian, khmer, cjk-decimal, devanagiri, georgian, gujarati, gurmukhi, - // hebrew, kannada, lao, malayalam, mongolian, myanmar, oriya, persian, lower-roman, - // upper-roman, telugu, thai, tibetan + // decimal-leading-zero, armenian, upper-armenian, lower-armenian, georgian, lower-roman, + // upper-roman // // [1]: http://dev.w3.org/csswg/css-counter-styles/ - ${single_keyword("list-style-type", - "disc none circle square disclosure-open disclosure-closed")} + ${single_keyword("list-style-type", """ + disc none circle square decimal arabic-indic bengali cambodian cjk-decimal devanagari + gujarati gurmukhi kannada khmer lao malayalam mongolian myanmar oriya persian telugu thai + tibetan lower-alpha upper-alpha cjk-earthly-branch cjk-heavenly-stem lower-greek hiragana + hiragana-iroha katakana katakana-iroha disclosure-open disclosure-closed + """)} <%self:longhand name="list-style-image"> use std::borrow::IntoCow; @@ -909,6 +984,144 @@ pub mod longhands { } + <%self:longhand name="quotes"> + use cssparser::{ToCss, Token}; + use text_writer::{self, TextWriter}; + use values::computed::{ToComputedValue, Context}; + + pub use self::computed_value::T as SpecifiedValue; + + pub mod computed_value { + #[derive(Clone, PartialEq)] + pub struct T(pub Vec<(String,String)>); + } + + impl ToComputedValue for SpecifiedValue { + type ComputedValue = computed_value::T; + + fn to_computed_value(&self, _: &Context) -> computed_value::T { + (*self).clone() + } + } + + impl ToCss for SpecifiedValue { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + // TODO(pcwalton) + dest.write_str("") + } + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T(vec![ + ("\u{201c}".to_string(), "\u{201d}".to_string()), + ("\u{2018}".to_string(), "\u{2019}".to_string()), + ]) + } + + pub fn parse(_: &ParserContext, input: &mut Parser) -> Result { + if input.try(|input| input.expect_ident_matching("none")).is_ok() { + return Ok(SpecifiedValue(Vec::new())) + } + + let mut quotes = Vec::new(); + loop { + let first = match input.next() { + Ok(Token::QuotedString(value)) => value.into_owned(), + Ok(_) => return Err(()), + Err(()) => break, + }; + let second = match input.next() { + Ok(Token::QuotedString(value)) => value.into_owned(), + _ => return Err(()), + }; + quotes.push((first, second)) + } + if !quotes.is_empty() { + Ok(SpecifiedValue(quotes)) + } else { + Err(()) + } + } + + + ${new_style_struct("Counters", is_inherited=False)} + + <%self:longhand name="counter-increment"> + use cssparser::{NumericValue, ToCss, Token}; + use super::content; + use text_writer::{self, TextWriter}; + use values::computed::{ToComputedValue, Context}; + + use std::borrow::ToOwned; + + pub use self::computed_value::T as SpecifiedValue; + + pub mod computed_value { + #[derive(Clone, PartialEq)] + pub struct T(pub Vec<(String,i32)>); + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T(Vec::new()) + } + + impl ToComputedValue for SpecifiedValue { + type ComputedValue = computed_value::T; + + fn to_computed_value(&self, _: &Context) -> computed_value::T { + (*self).clone() + } + } + + impl ToCss for SpecifiedValue { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + // TODO(pcwalton) + dest.write_str("") + } + } + + pub fn parse(_: &ParserContext, input: &mut Parser) -> Result { + if input.try(|input| input.expect_ident_matching("none")).is_ok() { + return Ok(SpecifiedValue(Vec::new())) + } + + let mut counters = Vec::new(); + loop { + let counter_name = match input.next() { + Ok(Token::Ident(ident)) => (*ident).to_owned(), + Ok(_) => return Err(()), + Err(_) => break, + }; + if content::counter_name_is_illegal(counter_name.as_slice()) { + return Err(()) + } + let counter_delta = input.try(|input| { + match input.next() { + Ok(Token::Number(NumericValue { + int_value: Some(int_value), + .. + })) => Ok(int_value as i32), + _ => Err(()), + } + }).unwrap_or(1); + counters.push((counter_name, counter_delta)) + } + + if !counters.is_empty() { + Ok(SpecifiedValue(counters)) + } else { + Err(()) + } + } + + + <%self:longhand name="counter-reset"> + pub use super::counter_increment::{SpecifiedValue, computed_value, get_initial_value}; + pub use super::counter_increment::{parse}; + + // CSS 2.1, Section 13 - Paged media // CSS 2.1, Section 14 - Colors and Backgrounds diff --git a/servo/components/util/time.rs b/servo/components/util/time.rs index 46de5abbceb8..c9cf32c06173 100644 --- a/servo/components/util/time.rs +++ b/servo/components/util/time.rs @@ -81,6 +81,7 @@ pub enum TimeProfilerCategory { LayoutSelectorMatch, LayoutTreeBuilder, LayoutDamagePropagate, + LayoutGeneratedContent, LayoutMain, LayoutParallelWarmup, LayoutShaping, @@ -99,6 +100,7 @@ impl Formatable for TimeProfilerCategory { TimeProfilerCategory::LayoutStyleRecalc | TimeProfilerCategory::LayoutRestyleDamagePropagation | TimeProfilerCategory::LayoutNonIncrementalReset | + TimeProfilerCategory::LayoutGeneratedContent | TimeProfilerCategory::LayoutMain | TimeProfilerCategory::LayoutDispListBuild | TimeProfilerCategory::LayoutShaping | @@ -119,6 +121,7 @@ impl Formatable for TimeProfilerCategory { TimeProfilerCategory::LayoutSelectorMatch => "Selector Matching", TimeProfilerCategory::LayoutTreeBuilder => "Tree Building", TimeProfilerCategory::LayoutDamagePropagate => "Damage Propagation", + TimeProfilerCategory::LayoutGeneratedContent => "Generated Content Resolution", TimeProfilerCategory::LayoutMain => "Primary Layout Pass", TimeProfilerCategory::LayoutParallelWarmup => "Parallel Warmup", TimeProfilerCategory::LayoutShaping => "Shaping", diff --git a/servo/ports/cef/Cargo.lock b/servo/ports/cef/Cargo.lock index c67a240d5355..15370ead80be 100644 --- a/servo/ports/cef/Cargo.lock +++ b/servo/ports/cef/Cargo.lock @@ -902,6 +902,7 @@ dependencies = [ "regex 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.1.0 (git+https://github.com/servo/rust-selectors)", + "plugins 0.0.1", "string_cache 0.0.0 (git+https://github.com/servo/string-cache)", "string_cache_macros 0.0.0 (git+https://github.com/servo/string-cache)", "task_info 0.0.1",