diff options
author | bors-servo <metajack+bors@gmail.com> | 2014-12-15 19:33:46 -0700 |
---|---|---|
committer | bors-servo <metajack+bors@gmail.com> | 2014-12-15 19:33:46 -0700 |
commit | 8e31e5f98747e4b42dafcc4b076fac46aeb09310 (patch) | |
tree | e7797b853ac10f987b3623fd184bb515cc6bdadd /components | |
parent | 88ec52dd617bafe384553ef38325c552fb4f1e23 (diff) | |
parent | a1ea44b294e73f4397887fe806fc5bc95499efda (diff) | |
download | servo-8e31e5f98747e4b42dafcc4b076fac46aeb09310.tar.gz servo-8e31e5f98747e4b42dafcc4b076fac46aeb09310.zip |
auto merge of #4289 : pcwalton/servo/hacker-news, r=SimonSapin
This patch provides some of the groundwork for column spans greater than
1. It implements the column-span CSS property as well as the
corresponding colspan attribute; although the former is not
well-specified outside of CSS multi-column layout, INTRINSIC refers to
it. Although width is distributed to spanning columns, they do not yet
contribute minimum and preferred widths; this will be implemented in a
follow-up.
The parsing for the legacy bgcolor and border attributes is
implemented according to the WHATWG HTML specification.
Additionally, this patch cleans up some miscellaneous formatting issues,
refactors layout/css somewhat to eliminate needless levels of
indirection, and cleans up the handling of table rowgroups.
New Hacker News screenshot: http://i.imgur.com/hnl2a7E.png
Diffstat (limited to 'components')
34 files changed, 1429 insertions, 559 deletions
diff --git a/components/layout/Cargo.toml b/components/layout/Cargo.toml index edd6ed0ad1b..ca77107965b 100644 --- a/components/layout/Cargo.toml +++ b/components/layout/Cargo.toml @@ -31,6 +31,9 @@ path = "../net" [dependencies.util] path = "../util" +[dependencies.cssparser] +git = "https://github.com/servo/rust-cssparser" + [dependencies.encoding] git = "https://github.com/lifthrasiir/rust-encoding" diff --git a/components/layout/css/matching.rs b/components/layout/css/matching.rs index f1615aa005d..86968a6d9c2 100644 --- a/components/layout/css/matching.rs +++ b/components/layout/css/matching.rs @@ -268,6 +268,9 @@ impl StyleSharingCandidate { return false } + // FIXME(pcwalton): It's probably faster to iterate over all the element's attributes and + // use the {common, rare}-style-affecting-attributes tables as lookup tables. + for attribute_info in style::common_style_affecting_attributes().iter() { match attribute_info.mode { AttrIsPresentMode(flag) => { @@ -295,6 +298,12 @@ impl StyleSharingCandidate { } } + for attribute_name in style::rare_style_affecting_attributes().iter() { + if element.get_attr(&ns!(""), attribute_name).is_some() { + return false + } + } + if element.get_link().is_some() != self.link { return false } diff --git a/components/layout/css/node_style.rs b/components/layout/css/node_style.rs index ecfb3383453..c3eaa04ddd2 100644 --- a/components/layout/css/node_style.rs +++ b/components/layout/css/node_style.rs @@ -2,27 +2,75 @@ * 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/. */ -// Style retrieval from DOM elements. +//! Style retrieval from DOM elements. -use css::node_util::NodeUtil; -use wrapper::ThreadSafeLayoutNode; +use wrapper::{After, Before, Normal, ThreadSafeLayoutNode}; +use std::mem; use style::ComputedValues; use sync::Arc; /// Node mixin providing `style` method that returns a `NodeStyle` pub trait StyledNode { + /// Returns the style results for the given node. If CSS selector matching has not yet been + /// performed, fails. fn style<'a>(&'a self) -> &'a Arc<ComputedValues>; + /// Does this node have a computed style yet? + fn has_style(&self) -> bool; + /// Removes the style from this node. fn unstyle(self); } impl<'ln> StyledNode for ThreadSafeLayoutNode<'ln> { #[inline] fn style<'a>(&'a self) -> &'a Arc<ComputedValues> { - self.get_css_select_results() + unsafe { + let layout_data_ref = self.borrow_layout_data(); + match self.get_pseudo_element_type() { + Before(_) => { + mem::transmute(layout_data_ref.as_ref() + .unwrap() + .data + .before_style + .as_ref() + .unwrap()) + } + After(_) => { + mem::transmute(layout_data_ref.as_ref() + .unwrap() + .data + .after_style + .as_ref() + .unwrap()) + } + Normal => { + mem::transmute(layout_data_ref.as_ref() + .unwrap() + .shared_data + .style + .as_ref() + .unwrap()) + } + } + } + } + + fn has_style(&self) -> bool { + let layout_data_ref = self.borrow_layout_data(); + layout_data_ref.as_ref().unwrap().shared_data.style.is_some() } fn unstyle(self) { - self.remove_css_select_results() + let mut layout_data_ref = self.mutate_layout_data(); + let layout_data = layout_data_ref.as_mut().expect("no layout data"); + + let style = + match self.get_pseudo_element_type() { + Before(_) => &mut layout_data.data.before_style, + After (_) => &mut layout_data.data.after_style, + Normal => &mut layout_data.shared_data.style, + }; + + *style = None; } } diff --git a/components/layout/css/node_util.rs b/components/layout/css/node_util.rs deleted file mode 100644 index eb447b0cf6b..00000000000 --- a/components/layout/css/node_util.rs +++ /dev/null @@ -1,75 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -use util::LayoutDataAccess; -use wrapper::ThreadSafeLayoutNode; -use wrapper::{After, Before, Normal}; - -use std::mem; -use style::ComputedValues; -use sync::Arc; - -pub trait NodeUtil { - fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues>; - fn have_css_select_results(&self) -> bool; - fn remove_css_select_results(self); -} - -impl<'ln> NodeUtil for ThreadSafeLayoutNode<'ln> { - /// Returns the style results for the given node. If CSS selector - /// matching has not yet been performed, fails. - #[inline] - fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues> { - unsafe { - let layout_data_ref = self.borrow_layout_data(); - match self.get_pseudo_element_type() { - Before(_) => { - mem::transmute(layout_data_ref.as_ref() - .unwrap() - .data - .before_style - .as_ref() - .unwrap()) - } - After(_) => { - mem::transmute(layout_data_ref.as_ref() - .unwrap() - .data - .after_style - .as_ref() - .unwrap()) - } - Normal => { - mem::transmute(layout_data_ref.as_ref() - .unwrap() - .shared_data - .style - .as_ref() - .unwrap()) - } - } - } - } - - /// Does this node have a computed style yet? - fn have_css_select_results(&self) -> bool { - let layout_data_ref = self.borrow_layout_data(); - layout_data_ref.as_ref().unwrap().shared_data.style.is_some() - } - - fn remove_css_select_results(self) { - let mut layout_data_ref = self.mutate_layout_data(); - let layout_data = layout_data_ref.as_mut().expect("no layout data"); - - let style = - match self.get_pseudo_element_type() { - Before(_) => &mut layout_data.data.before_style, - After (_) => &mut layout_data.data.after_style, - Normal => &mut layout_data.shared_data.style, - }; - - *style = None; - } -} - diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index deda7d481c7..d187aa1a37f 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -40,7 +40,7 @@ use script::layout_interface::{ContentBoxesQuery, ContentBoxQuery, ExitNowMsg, G use script::layout_interface::{HitTestResponse, LayoutChan, LayoutRPC, LoadStylesheetMsg}; use script::layout_interface::{MouseOverResponse, Msg, NoQuery, PrepareToExitMsg}; use script::layout_interface::{ReapLayoutDataMsg, Reflow, ReflowForDisplay, ReflowMsg}; -use script::layout_interface::{ScriptLayoutChan, TrustedNodeAddress}; +use script::layout_interface::{ScriptLayoutChan, SetQuirksModeMsg, TrustedNodeAddress}; use script_traits::{SendEventMsg, ReflowEvent, ReflowCompleteMsg, OpaqueScriptLayoutChannel}; use script_traits::{ScriptControlChan, UntrustedNodeAddress}; use servo_msg::compositor_msg::Scrollable; @@ -390,6 +390,7 @@ impl LayoutTask { match request { AddStylesheetMsg(sheet) => self.handle_add_stylesheet(sheet, possibly_locked_rw_data), LoadStylesheetMsg(url) => self.handle_load_stylesheet(url, possibly_locked_rw_data), + SetQuirksModeMsg => self.handle_set_quirks_mode(possibly_locked_rw_data), GetRPCMsg(response_chan) => { response_chan.send(box LayoutRPCImpl(self.rw_data.clone()) as Box<LayoutRPC + Send>); @@ -500,6 +501,15 @@ impl LayoutTask { LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data); } + /// Sets quirks mode for the document, causing the quirks mode stylesheet to be loaded. + fn handle_set_quirks_mode<'a>(&'a self, + possibly_locked_rw_data: + &mut Option<MutexGuard<'a, LayoutTaskData>>) { + let mut rw_data = self.lock_rw_data(possibly_locked_rw_data); + rw_data.stylist.add_quirks_mode_stylesheet(); + LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data); + } + /// Retrieves the flow tree root from the root node. fn try_get_layout_root(&self, node: LayoutNode) -> Option<FlowRef> { let mut layout_data_ref = node.mutate_layout_data(); diff --git a/components/layout/lib.rs b/components/layout/lib.rs index 1fa21311373..26054f0dae0 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -14,6 +14,7 @@ #[phase(plugin, link)] extern crate log; +extern crate cssparser; extern crate geom; extern crate gfx; extern crate layout_traits; @@ -70,8 +71,6 @@ pub mod incremental; pub mod wrapper; pub mod css { - mod node_util; - pub mod matching; pub mod node_style; } diff --git a/components/layout/table.rs b/components/layout/table.rs index 37f589917a2..0e2af0b2f68 100644 --- a/components/layout/table.rs +++ b/components/layout/table.rs @@ -11,11 +11,12 @@ use block::{ISizeConstraintInput, ISizeConstraintSolution}; use construct::FlowConstructor; use context::LayoutContext; use floats::FloatKind; -use flow::{Flow, FlowClass, IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS, ImmutableFlowUtils}; -use flow::{TableFlowClass}; +use flow::{mod, Flow, FlowClass, IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS}; +use flow::{ImmutableFlowUtils, TableFlowClass}; use fragment::{Fragment, FragmentBoundsIterator}; use layout_debug; use model::{IntrinsicISizes, IntrinsicISizesContribution}; +use table_row::CellIntrinsicInlineSize; use table_wrapper::{TableLayout, FixedLayout, AutoLayout}; use wrapper::ThreadSafeLayoutNode; @@ -105,24 +106,53 @@ impl TableFlow { /// Update the corresponding value of `self_inline_sizes` if a value of `kid_inline_sizes` has /// a larger value than one of `self_inline_sizes`. Returns the minimum and preferred inline /// sizes. - pub fn update_column_inline_sizes(parent_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>, - child_inline_sizes: &Vec<ColumnIntrinsicInlineSize>) - -> IntrinsicISizes { + fn update_automatic_column_inline_sizes( + parent_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>, + child_cell_inline_sizes: &[CellIntrinsicInlineSize]) + -> IntrinsicISizes { let mut total_inline_sizes = IntrinsicISizes::new(); - for (parent_sizes, child_sizes) in parent_inline_sizes.iter_mut() - .zip(child_inline_sizes.iter()) { - *parent_sizes = ColumnIntrinsicInlineSize { - minimum_length: max(parent_sizes.minimum_length, child_sizes.minimum_length), - percentage: parent_sizes.greatest_percentage(child_sizes), - preferred: max(parent_sizes.preferred, child_sizes.preferred), - constrained: parent_sizes.constrained || child_sizes.constrained - }; - - total_inline_sizes.minimum_inline_size = total_inline_sizes.minimum_inline_size + - parent_sizes.minimum_length; - total_inline_sizes.preferred_inline_size = total_inline_sizes.preferred_inline_size + - parent_sizes.preferred; + let mut column_index = 0; + for child_cell_inline_size in child_cell_inline_sizes.iter() { + for _ in range(0, child_cell_inline_size.column_span) { + if column_index < parent_inline_sizes.len() { + // We already have some intrinsic size information for this column. Merge it in + // according to the rules specified in INTRINSIC § 4. + let parent_sizes = &mut parent_inline_sizes[column_index]; + if child_cell_inline_size.column_span > 1 { + // TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC § + // 4. For now we make this column contribute no width. + } else { + let column_size = &child_cell_inline_size.column_size; + *parent_sizes = ColumnIntrinsicInlineSize { + minimum_length: max(parent_sizes.minimum_length, + column_size.minimum_length), + percentage: parent_sizes.greatest_percentage(column_size), + preferred: max(parent_sizes.preferred, column_size.preferred), + constrained: parent_sizes.constrained || column_size.constrained, + } + } + } else { + // We discovered a new column. Initialize its data. + debug_assert!(column_index == parent_inline_sizes.len()); + if child_cell_inline_size.column_span > 1 { + // TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC § + // 4. For now we make this column contribute no width. + parent_inline_sizes.push(ColumnIntrinsicInlineSize::new()) + } else { + parent_inline_sizes.push(child_cell_inline_size.column_size) + } + } + + total_inline_sizes.minimum_inline_size = total_inline_sizes.minimum_inline_size + + parent_inline_sizes[column_index].minimum_length; + total_inline_sizes.preferred_inline_size = + total_inline_sizes.preferred_inline_size + + parent_inline_sizes[column_index].preferred; + + column_index += 1 + } } + total_inline_sizes } @@ -136,6 +166,39 @@ impl TableFlow { fn assign_block_size_table_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse); } + + /// Updates the minimum and preferred inline-size calculation for a single row. This is + /// factored out into a separate function because we process children of rowgroups too. + fn update_column_inline_sizes_for_row(child: &mut Flow, + column_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>, + computation: &mut IntrinsicISizesContribution, + did_first_row: &mut bool, + table_layout: TableLayout) { + // Read column inline-sizes from the table-row, and assign inline-size=0 for the columns + // not defined in the column group. + // + // FIXME: Need to read inline-sizes from either table-header-group OR the first table-row. + debug_assert!(child.is_table_row()); + let row = child.as_table_row(); + match table_layout { + FixedLayout => { + // Fixed table layout only looks at the first row. + // + // FIXME(pcwalton): This is really inefficient. We should stop after the first row! + if !*did_first_row { + *did_first_row = true; + for cell_inline_size in row.cell_intrinsic_inline_sizes.iter() { + column_inline_sizes.push(cell_inline_size.column_size); + } + } + } + AutoLayout => { + computation.union_block(&TableFlow::update_automatic_column_inline_sizes( + column_inline_sizes, + row.cell_intrinsic_inline_sizes.as_slice())) + } + } + } } impl Flow for TableFlow { @@ -190,50 +253,22 @@ impl Flow for TableFlow { constrained: false, }) } - } else if kid.is_table_rowgroup() || kid.is_table_row() { - // Read column inline-sizes from the table-row-group/table-row, and assign - // inline-size=0 for the columns not defined in the column group. - // FIXME: Need to read inline-sizes from either table-header-group OR the first - // table-row. - match self.table_layout { - FixedLayout => { - // Fixed table layout only looks at the first row. - if !did_first_row { - did_first_row = true; - for child_column_inline_size in kid.column_intrinsic_inline_sizes() - .iter() { - self.column_intrinsic_inline_sizes.push(*child_column_inline_size); - } - } - } - AutoLayout => { - let child_column_inline_sizes = kid.column_intrinsic_inline_sizes(); - let mut child_intrinsic_sizes = TableFlow::update_column_inline_sizes( - &mut self.column_intrinsic_inline_sizes, - child_column_inline_sizes); - - // Add new columns if processing this row caused us to discover them. - let child_column_count = child_column_inline_sizes.len(); - let parent_column_count = self.column_intrinsic_inline_sizes.len(); - debug!("table until the previous row has {} column(s) and this row has {} \ - column(s)", - parent_column_count, - child_column_count); - self.column_intrinsic_inline_sizes.reserve(child_column_count); - for i in range(parent_column_count, child_column_count) { - let inline_size_for_new_column = (*child_column_inline_sizes)[i]; - child_intrinsic_sizes.minimum_inline_size = - child_intrinsic_sizes.minimum_inline_size + - inline_size_for_new_column.minimum_length; - child_intrinsic_sizes.preferred_inline_size = - child_intrinsic_sizes.preferred_inline_size + - inline_size_for_new_column.preferred; - self.column_intrinsic_inline_sizes.push(inline_size_for_new_column); - } - - computation.union_block(&child_intrinsic_sizes) - } + } else if kid.is_table_rowgroup() { + for grandkid in flow::mut_base(kid).child_iter() { + TableFlow::update_column_inline_sizes_for_row( + grandkid, + &mut self.column_intrinsic_inline_sizes, + &mut computation, + &mut did_first_row, + self.table_layout) } + } else if kid.is_table_row() { + TableFlow::update_column_inline_sizes_for_row( + kid, + &mut self.column_intrinsic_inline_sizes, + &mut computation, + &mut did_first_row, + self.table_layout) } } @@ -277,8 +312,7 @@ impl Flow for TableFlow { // In fixed table layout, we distribute extra space among the unspecified columns // if there are any, or among all the columns if all are specified. self.column_computed_inline_sizes.clear(); - if total_column_inline_size < content_inline_size && - num_unspecified_inline_sizes == 0 { + if num_unspecified_inline_sizes == 0 { let ratio = content_inline_size.to_subpx() / total_column_inline_size.to_subpx(); for column_inline_size in self.column_intrinsic_inline_sizes.iter() { @@ -413,6 +447,16 @@ pub struct ColumnIntrinsicInlineSize { } impl ColumnIntrinsicInlineSize { + /// Returns a newly-initialized `ColumnIntrinsicInlineSize` with all fields blank. + pub fn new() -> ColumnIntrinsicInlineSize { + ColumnIntrinsicInlineSize { + preferred: Au(0), + minimum_length: Au(0), + percentage: 0.0, + constrained: false, + } + } + /// Returns the true minimum size of this column, given the containing block's inline size. /// Beware that this is generally only correct for fixed table layout. (Compare CSS 2.1 § /// 17.5.2.1 with the algorithm in INTRINSIC § 4.) diff --git a/components/layout/table_cell.rs b/components/layout/table_cell.rs index 280e86408cb..0a4e7d05e24 100644 --- a/components/layout/table_cell.rs +++ b/components/layout/table_cell.rs @@ -17,7 +17,7 @@ use wrapper::ThreadSafeLayoutNode; use servo_util::geometry::Au; use std::fmt; -use style::ComputedValues; +use style::{ColSpanUnsignedIntegerAttribute, ComputedValues}; use sync::Arc; /// A table formatting context. @@ -25,13 +25,17 @@ use sync::Arc; pub struct TableCellFlow { /// Data common to all block flows. pub block_flow: BlockFlow, + /// The column span of this cell. + pub column_span: u32, } impl TableCellFlow { pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> TableCellFlow { TableCellFlow { - block_flow: BlockFlow::from_node_and_fragment(node, fragment) + block_flow: BlockFlow::from_node_and_fragment(node, fragment), + column_span: node.get_unsigned_integer_attribute(ColSpanUnsignedIntegerAttribute) + .unwrap_or(1), } } diff --git a/components/layout/table_row.rs b/components/layout/table_row.rs index 307892635a0..e50f5b2f33e 100644 --- a/components/layout/table_row.rs +++ b/components/layout/table_row.rs @@ -30,20 +30,29 @@ use sync::Arc; pub struct TableRowFlow { pub block_flow: BlockFlow, - /// Information about the intrinsic inline-sizes of each column. - pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>, + /// Information about the intrinsic inline-sizes of each cell. + pub cell_intrinsic_inline_sizes: Vec<CellIntrinsicInlineSize>, /// Information about the computed inline-sizes of each column. pub column_computed_inline_sizes: Vec<ColumnComputedInlineSize>, } +/// Information about the column inline size and span for each cell. +#[deriving(Encodable)] +pub struct CellIntrinsicInlineSize { + /// Inline sizes that this cell contributes to the column. + pub column_size: ColumnIntrinsicInlineSize, + /// The column span of this cell. + pub column_span: u32, +} + impl TableRowFlow { pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> TableRowFlow { TableRowFlow { block_flow: BlockFlow::from_node_and_fragment(node, fragment), - column_intrinsic_inline_sizes: Vec::new(), + cell_intrinsic_inline_sizes: Vec::new(), column_computed_inline_sizes: Vec::new(), } } @@ -53,8 +62,8 @@ impl TableRowFlow { -> TableRowFlow { TableRowFlow { block_flow: BlockFlow::from_node(constructor, node), - column_intrinsic_inline_sizes: Vec::new(), - column_computed_inline_sizes: Vec::new(), + cell_intrinsic_inline_sizes: Vec::new(), + column_computed_inline_sizes: Vec::new() } } @@ -156,7 +165,7 @@ impl Flow for TableRowFlow { } fn column_intrinsic_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<ColumnIntrinsicInlineSize> { - &mut self.column_intrinsic_inline_sizes + panic!("can't call column_intrinsic_inline_sizes() on table row") } fn column_computed_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<ColumnComputedInlineSize> { @@ -181,10 +190,15 @@ impl Flow for TableRowFlow { // Collect the specified column inline-size of the cell. This is used in both fixed and // automatic table layout calculation. - let child_specified_inline_size = kid.as_table_cell() - .fragment() - .style() - .content_inline_size(); + let child_specified_inline_size; + let child_column_span; + { + let child_table_cell = kid.as_table_cell(); + child_specified_inline_size = child_table_cell.fragment() + .style() + .content_inline_size(); + child_column_span = child_table_cell.column_span + } // Collect minimum and preferred inline-sizes of the cell for automatic table layout // calculation. @@ -208,15 +222,16 @@ impl Flow for TableRowFlow { }; min_inline_size = min_inline_size + child_column_inline_size.minimum_length; pref_inline_size = pref_inline_size + child_column_inline_size.preferred; - self.column_intrinsic_inline_sizes.push(child_column_inline_size); + self.cell_intrinsic_inline_sizes.push(CellIntrinsicInlineSize { + column_size: child_column_inline_size, + column_span: child_column_span, + }); } self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size; self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = max(min_inline_size, pref_inline_size); } - /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. - /// When called on this context, the context has had its inline-size set by the parent context. fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { let _scope = layout_debug_scope!("table_row::assign_inline_sizes {:x}", self.block_flow.base.debug_id()); @@ -233,11 +248,46 @@ impl Flow for TableRowFlow { layout_context, containing_block_inline_size); + // Spread out the completed inline sizes among columns with spans > 1. + let mut computed_inline_size_for_cells = Vec::new(); + let mut column_computed_inline_size_iterator = self.column_computed_inline_sizes.iter(); + for cell_intrinsic_inline_size in self.cell_intrinsic_inline_sizes.iter() { + // Start with the computed inline size for the first column in the span. + let mut column_computed_inline_size = + match column_computed_inline_size_iterator.next() { + Some(column_computed_inline_size) => *column_computed_inline_size, + None => { + // We're in fixed layout mode and there are more cells in this row than + // columns we know about. According to CSS 2.1 § 17.5.2.1, the behavior is + // now undefined. So just use zero. + // + // FIXME(pcwalton): $10 says this isn't Web compatible. + ColumnComputedInlineSize { + size: Au(0), + } + } + }; + + // Add in computed inline sizes for any extra columns in the span. + for _ in range(1, cell_intrinsic_inline_size.column_span) { + let extra_column_computed_inline_size = + match column_computed_inline_size_iterator.next() { + Some(column_computed_inline_size) => column_computed_inline_size, + None => break, + }; + column_computed_inline_size.size = column_computed_inline_size.size + + extra_column_computed_inline_size.size; + } + + computed_inline_size_for_cells.push(column_computed_inline_size) + } + + // Push those inline sizes down to the cells. self.block_flow.propagate_assigned_inline_size_to_children( layout_context, inline_start_content_edge, containing_block_inline_size, - Some(self.column_computed_inline_sizes.as_slice())); + Some(computed_inline_size_for_cells.as_slice())); } fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { diff --git a/components/layout/table_rowgroup.rs b/components/layout/table_rowgroup.rs index c3837185a47..4f62cd10a75 100644 --- a/components/layout/table_rowgroup.rs +++ b/components/layout/table_rowgroup.rs @@ -9,11 +9,10 @@ use block::{BlockFlow, ISizeAndMarginsComputer, MarginsMayNotCollapse}; use construct::FlowConstructor; use context::LayoutContext; -use flow::{TableRowGroupFlowClass, FlowClass, Flow, ImmutableFlowUtils}; +use flow::{Flow, FlowClass, TableRowGroupFlowClass}; use fragment::{Fragment, FragmentBoundsIterator}; use layout_debug; -use model::IntrinsicISizesContribution; -use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable, TableFlow}; +use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable}; use wrapper::ThreadSafeLayoutNode; use servo_util::geometry::Au; @@ -24,6 +23,7 @@ use sync::Arc; /// A table formatting context. #[deriving(Encodable)] pub struct TableRowGroupFlow { + /// Fields common to all block flows. pub block_flow: BlockFlow, /// Information about the intrinsic inline-sizes of each column. @@ -91,54 +91,11 @@ impl Flow for TableRowGroupFlow { &mut self.column_computed_inline_sizes } - - /// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When - /// called on this context, all child contexts have had their min/pref inline-sizes set. This - /// function must decide min/pref inline-sizes based on child context inline-sizes and - /// dimensions of any fragments it is responsible for flowing. - /// - /// Min/pref inline-sizes set by this function are used in automatic table layout calculation. - /// - /// Also, this function finds the specified column inline-sizes from the first row. These are - /// used in fixed table layout calculation. fn bubble_inline_sizes(&mut self) { let _scope = layout_debug_scope!("table_rowgroup::bubble_inline_sizes {:x}", self.block_flow.base.debug_id()); - - let mut computation = IntrinsicISizesContribution::new(); - for kid in self.block_flow.base.child_iter() { - assert!(kid.is_table_row()); - - // Calculate minimum and preferred inline sizes for automatic table layout. - if self.column_intrinsic_inline_sizes.is_empty() { - // We're the first row. - self.column_intrinsic_inline_sizes = kid.column_intrinsic_inline_sizes().clone(); - } else { - let mut child_intrinsic_sizes = - TableFlow::update_column_inline_sizes(&mut self.column_intrinsic_inline_sizes, - kid.column_intrinsic_inline_sizes()); - - // update the number of column inline-sizes from table-rows. - let column_count = self.column_intrinsic_inline_sizes.len(); - let child_column_count = kid.column_intrinsic_inline_sizes().len(); - for i in range(column_count, child_column_count) { - let this_column_inline_size = (*kid.column_intrinsic_inline_sizes())[i]; - - // FIXME(pcwalton): Ignoring the percentage here seems dubious. - child_intrinsic_sizes.minimum_inline_size = - child_intrinsic_sizes.minimum_inline_size + - this_column_inline_size.minimum_length; - child_intrinsic_sizes.preferred_inline_size = - child_intrinsic_sizes.preferred_inline_size + - this_column_inline_size.preferred; - self.column_intrinsic_inline_sizes.push(this_column_inline_size); - } - - computation.union_block(&child_intrinsic_sizes) - } - } - - self.block_flow.base.intrinsic_inline_sizes = computation.finish() + // Proper calculation of intrinsic sizes in table layout requires access to the entire + // table, which we don't have yet. Defer to our parent. } /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. diff --git a/components/layout/table_wrapper.rs b/components/layout/table_wrapper.rs index 271a6545d1d..0d785ce1ca3 100644 --- a/components/layout/table_wrapper.rs +++ b/components/layout/table_wrapper.rs @@ -31,7 +31,7 @@ use style::{ComputedValues, CSSFloat}; use style::computed_values::table_layout; use sync::Arc; -#[deriving(Encodable)] +#[deriving(Encodable, Show)] pub enum TableLayout { FixedLayout, AutoLayout diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index 9a70539c78b..97aa8dd189a 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -36,6 +36,7 @@ use incremental::RestyleDamage; use util::{LayoutDataAccess, LayoutDataFlags, LayoutDataWrapper, OpaqueNodeMethods}; use util::{PrivateLayoutData}; +use cssparser::RGBA; use gfx::display_list::OpaqueNode; use script::dom::bindings::codegen::InheritTypes::{ElementCast, HTMLIFrameElementCast}; use script::dom::bindings::codegen::InheritTypes::{HTMLImageElementCast, HTMLInputElementCast}; @@ -56,11 +57,12 @@ use servo_msg::constellation_msg::{PipelineId, SubpageId}; use servo_util::str::{LengthOrPercentageOrAuto, is_whitespace}; use std::kinds::marker::ContravariantLifetime; use std::mem; +use string_cache::{Atom, Namespace}; use style::computed_values::{content, display, white_space}; -use style::{AnyNamespace, AttrSelector, IntegerAttribute, LengthAttribute}; -use style::{PropertyDeclarationBlock, SpecificNamespace, TElement, TElementAttributes, TNode}; +use style::{AnyNamespace, AttrSelector, BorderUnsignedIntegerAttribute, IntegerAttribute}; +use style::{LengthAttribute, PropertyDeclarationBlock, SimpleColorAttribute, SpecificNamespace}; +use style::{TElement, TElementAttributes, TNode, UnsignedIntegerAttribute}; use url::Url; -use string_cache::{Atom, Namespace}; use std::cell::{Ref, RefMut}; @@ -580,6 +582,17 @@ impl<'le> TElement<'le> for LayoutElement<'le> { } } } + + #[inline] + fn has_nonzero_border(self) -> bool { + unsafe { + match self.element + .get_unsigned_integer_attribute_for_layout(BorderUnsignedIntegerAttribute) { + None | Some(0) => false, + _ => true, + } + } + } } impl<'le> TElementAttributes for LayoutElement<'le> { @@ -594,6 +607,18 @@ impl<'le> TElementAttributes for LayoutElement<'le> { self.element.get_integer_attribute_for_layout(integer_attribute) } } + + fn get_unsigned_integer_attribute(self, attribute: UnsignedIntegerAttribute) -> Option<u32> { + unsafe { + self.element.get_unsigned_integer_attribute_for_layout(attribute) + } + } + + fn get_simple_color_attribute(self, attribute: SimpleColorAttribute) -> Option<RGBA> { + unsafe { + self.element.get_simple_color_attribute_for_layout(attribute) + } + } } fn get_content(content_list: &content::T) -> String { @@ -913,6 +938,18 @@ impl<'ln> ThreadSafeLayoutNode<'ln> { } } + pub fn get_unsigned_integer_attribute(self, attribute: UnsignedIntegerAttribute) + -> Option<u32> { + unsafe { + match ElementCast::to_js(self.get_jsmanaged()) { + Some(element) => { + (*element.unsafe_get()).get_unsigned_integer_attribute_for_layout(attribute) + } + None => panic!("not an element!") + } + } + } + /// Get the description of how to account for recent style changes. /// This is a simple bitfield and fine to copy by value. pub fn restyle_damage(self) -> RestyleDamage { diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index afd329137c0..b877d022c2e 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -32,6 +32,7 @@ use dom::bindings::utils::{Reflectable, Reflector, WindowProxyHandler}; use dom::node::{Node, TrustedNodeAddress}; use collections::hash::{Hash, Hasher}; +use cssparser::RGBA; use geom::rect::Rect; use html5ever::tree_builder::QuirksMode; use hyper::header::Headers; @@ -48,7 +49,7 @@ use script_traits::UntrustedNodeAddress; use servo_msg::compositor_msg::ScriptListener; use servo_msg::constellation_msg::ConstellationChan; use servo_util::smallvec::{SmallVec1, SmallVec}; -use servo_util::str::LengthOrPercentageOrAuto; +use servo_util::str::{LengthOrPercentageOrAuto}; use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::comm::{Receiver, Sender}; @@ -214,6 +215,7 @@ no_jsmanaged_fields!(LayoutChan) no_jsmanaged_fields!(WindowProxyHandler) no_jsmanaged_fields!(UntrustedNodeAddress) no_jsmanaged_fields!(LengthOrPercentageOrAuto) +no_jsmanaged_fields!(RGBA) impl<'a> JSTraceable for &'a str { #[inline] diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index c4dc787ac26..4fdb8a9befd 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -61,6 +61,7 @@ use servo_util::namespace; use servo_util::str::{DOMString, split_html_space_chars}; use html5ever::tree_builder::{QuirksMode, NoQuirks, LimitedQuirks, Quirks}; +use layout_interface::{LayoutChan, SetQuirksModeMsg}; use string_cache::{Atom, QualName}; use url::Url; @@ -217,6 +218,15 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { fn set_quirks_mode(self, mode: QuirksMode) { self.quirks_mode.set(mode); + + match mode { + Quirks => { + let window = self.window.root(); + let LayoutChan(ref layout_chan) = window.page().layout_chan; + layout_chan.send(SetQuirksModeMsg); + } + NoQuirks | LimitedQuirks => {} + } } fn set_last_modified(self, value: DOMString) { diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 1e55f9f73c2..69846d40543 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -15,8 +15,12 @@ use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; use dom::bindings::codegen::Bindings::EventBinding::EventMethods; use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods; -use dom::bindings::codegen::InheritTypes::{ElementDerived, HTMLInputElementDerived, HTMLTableCellElementDerived}; -use dom::bindings::codegen::InheritTypes::{HTMLInputElementCast, NodeCast, EventTargetCast, ElementCast}; +use dom::bindings::codegen::InheritTypes::{ElementCast, ElementDerived, EventTargetCast}; +use dom::bindings::codegen::InheritTypes::{HTMLBodyElementDerived, HTMLInputElementCast}; +use dom::bindings::codegen::InheritTypes::{HTMLInputElementDerived, HTMLTableElementCast}; +use dom::bindings::codegen::InheritTypes::{HTMLTableElementDerived, HTMLTableCellElementDerived}; +use dom::bindings::codegen::InheritTypes::{HTMLTableRowElementDerived}; +use dom::bindings::codegen::InheritTypes::{HTMLTableSectionElementDerived, NodeCast}; use dom::bindings::js::{MutNullableJS, JS, JSRef, Temporary, TemporaryPushable}; use dom::bindings::js::{OptionalRootable, Root}; use dom::bindings::utils::{Reflectable, Reflector}; @@ -29,22 +33,28 @@ use dom::document::{Document, DocumentHelpers, LayoutDocumentHelpers}; use dom::domtokenlist::DOMTokenList; use dom::event::Event; use dom::eventtarget::{EventTarget, NodeTargetTypeId, EventTargetHelpers}; +use dom::htmlbodyelement::{HTMLBodyElement, HTMLBodyElementHelpers}; use dom::htmlcollection::HTMLCollection; use dom::htmlinputelement::{HTMLInputElement, RawLayoutHTMLInputElementHelpers}; use dom::htmlserializer::serialize; +use dom::htmltableelement::{HTMLTableElement, HTMLTableElementHelpers}; use dom::htmltablecellelement::{HTMLTableCellElement, HTMLTableCellElementHelpers}; -use dom::node::{CLICK_IN_PROGRESS, ElementNodeTypeId, Node, NodeHelpers, NodeIterator}; -use dom::node::{document_from_node, window_from_node, LayoutNodeHelpers, NodeStyleDamaged}; -use dom::node::{OtherNodeDamage}; +use dom::htmltablerowelement::{HTMLTableRowElement, HTMLTableRowElementHelpers}; +use dom::htmltablesectionelement::{HTMLTableSectionElement, HTMLTableSectionElementHelpers}; +use dom::node::{CLICK_IN_PROGRESS, ElementNodeTypeId, LayoutNodeHelpers, Node, NodeHelpers}; +use dom::node::{NodeIterator, NodeStyleDamaged, OtherNodeDamage, document_from_node}; +use dom::node::{window_from_node}; use dom::nodelist::NodeList; use dom::virtualmethods::{VirtualMethods, vtable_for}; use devtools_traits::AttrInfo; -use style::{IntegerAttribute, LengthAttribute, SizeIntegerAttribute, WidthLengthAttribute}; -use style::{matches, parse_selector_list_from_str}; -use style; +use style::{mod, AuthorOrigin, BgColorSimpleColorAttribute, BorderUnsignedIntegerAttribute}; +use style::{ColSpanUnsignedIntegerAttribute, IntegerAttribute, LengthAttribute, ParserContext}; +use style::{SimpleColorAttribute, SizeIntegerAttribute, UnsignedIntegerAttribute}; +use style::{WidthLengthAttribute, matches}; use servo_util::namespace; use servo_util::str::{DOMString, LengthOrPercentageOrAuto}; +use cssparser::RGBA; use std::ascii::AsciiExt; use std::cell::{Ref, RefMut}; use std::default::Default; @@ -201,6 +211,10 @@ pub trait RawLayoutElementHelpers { unsafe fn get_integer_attribute_for_layout(&self, integer_attribute: IntegerAttribute) -> Option<i32>; unsafe fn get_checked_state_for_layout(&self) -> bool; + unsafe fn get_unsigned_integer_attribute_for_layout(&self, attribute: UnsignedIntegerAttribute) + -> Option<u32>; + unsafe fn get_simple_color_attribute_for_layout(&self, attribute: SimpleColorAttribute) + -> Option<RGBA>; fn local_name<'a>(&'a self) -> &'a Atom; fn namespace<'a>(&'a self) -> &'a Namespace; fn style_attribute<'a>(&'a self) -> &'a DOMRefCell<Option<style::PropertyDeclarationBlock>>; @@ -285,11 +299,15 @@ impl RawLayoutElementHelpers for Element { -> LengthOrPercentageOrAuto { match length_attribute { WidthLengthAttribute => { - if !self.is_htmltablecellelement() { - panic!("I'm not a table cell!") + if self.is_htmltableelement() { + let this: &HTMLTableElement = mem::transmute(self); + this.get_width() + } else if self.is_htmltablecellelement() { + let this: &HTMLTableCellElement = mem::transmute(self); + this.get_width() + } else { + panic!("I'm not a table or table cell!") } - let this: &HTMLTableCellElement = mem::transmute(self); - this.get_width() } } } @@ -319,6 +337,61 @@ impl RawLayoutElementHelpers for Element { this.get_checked_state_for_layout() } + unsafe fn get_unsigned_integer_attribute_for_layout(&self, + attribute: UnsignedIntegerAttribute) + -> Option<u32> { + match attribute { + BorderUnsignedIntegerAttribute => { + if self.is_htmltableelement() { + let this: &HTMLTableElement = mem::transmute(self); + this.get_border() + } else { + // Don't panic since `:-servo-nonzero-border` can cause this to be called on + // arbitrary elements. + None + } + } + ColSpanUnsignedIntegerAttribute => { + if self.is_htmltablecellelement() { + let this: &HTMLTableCellElement = mem::transmute(self); + this.get_colspan() + } else { + // Don't panic since `display` can cause this to be called on arbitrary + // elements. + None + } + } + } + } + + #[inline] + #[allow(unrooted_must_root)] + unsafe fn get_simple_color_attribute_for_layout(&self, attribute: SimpleColorAttribute) + -> Option<RGBA> { + match attribute { + BgColorSimpleColorAttribute => { + if self.is_htmlbodyelement() { + let this: &HTMLBodyElement = mem::transmute(self); + this.get_background_color() + } else if self.is_htmltableelement() { + let this: &HTMLTableElement = mem::transmute(self); + this.get_background_color() + } else if self.is_htmltablecellelement() { + let this: &HTMLTableCellElement = mem::transmute(self); + this.get_background_color() + } else if self.is_htmltablerowelement() { + let this: &HTMLTableRowElement = mem::transmute(self); + this.get_background_color() + } else if self.is_htmltablesectionelement() { + let this: &HTMLTableSectionElement = mem::transmute(self); + this.get_background_color() + } else { + None + } + } + } + } + // Getters used in components/layout/wrapper.rs fn local_name<'a>(&'a self) -> &'a Atom { @@ -947,7 +1020,10 @@ impl<'a> ElementMethods for JSRef<'a, Element> { // http://dom.spec.whatwg.org/#dom-element-matches fn Matches(self, selectors: DOMString) -> Fallible<bool> { - match parse_selector_list_from_str(selectors.as_slice()) { + let parser_context = ParserContext { + origin: AuthorOrigin, + }; + match style::parse_selector_list_from_str(&parser_context, selectors.as_slice()) { Err(()) => Err(Syntax), Ok(ref selectors) => { let root: JSRef<Node> = NodeCast::from_ref(self); @@ -1222,6 +1298,17 @@ impl<'a> style::TElement<'a> for JSRef<'a, Element> { } } } + fn has_nonzero_border(self) -> bool { + match HTMLTableElementCast::to_ref(self) { + None => false, + Some(this) => { + match this.get_border() { + None | Some(0) => false, + Some(_) => true, + } + } + } + } } pub trait ActivationElementHelpers<'a> { diff --git a/components/script/dom/htmlbodyelement.rs b/components/script/dom/htmlbodyelement.rs index d088a6b8626..917b02913a7 100644 --- a/components/script/dom/htmlbodyelement.rs +++ b/components/script/dom/htmlbodyelement.rs @@ -2,11 +2,9 @@ * 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 dom::attr::Attr; -use dom::attr::AttrHelpers; +use dom::attr::{Attr, AttrHelpers}; use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::HTMLBodyElementBinding; -use dom::bindings::codegen::Bindings::HTMLBodyElementBinding::HTMLBodyElementMethods; +use dom::bindings::codegen::Bindings::HTMLBodyElementBinding::{mod, HTMLBodyElementMethods}; use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use dom::bindings::codegen::InheritTypes::EventTargetCast; use dom::bindings::codegen::InheritTypes::{HTMLBodyElementDerived, HTMLElementCast}; @@ -19,11 +17,14 @@ use dom::htmlelement::HTMLElement; use dom::node::{Node, ElementNodeTypeId, window_from_node}; use dom::virtualmethods::VirtualMethods; -use servo_util::str::DOMString; +use cssparser::RGBA; +use servo_util::str::{mod, DOMString}; +use std::cell::Cell; #[dom_struct] pub struct HTMLBodyElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, + background_color: Cell<Option<RGBA>>, } impl HTMLBodyElementDerived for EventTarget { @@ -33,14 +34,20 @@ impl HTMLBodyElementDerived for EventTarget { } impl HTMLBodyElement { - fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> HTMLBodyElement { + fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) + -> HTMLBodyElement { HTMLBodyElement { - htmlelement: HTMLElement::new_inherited(HTMLBodyElementTypeId, localName, prefix, document) + htmlelement: HTMLElement::new_inherited(HTMLBodyElementTypeId, + localName, + prefix, + document), + background_color: Cell::new(None), } } #[allow(unrooted_must_root)] - pub fn new(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> Temporary<HTMLBodyElement> { + pub fn new(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) + -> Temporary<HTMLBodyElement> { let element = HTMLBodyElement::new_inherited(localName, prefix, document); Node::reflect_node(box element, document, HTMLBodyElementBinding::Wrap) } @@ -58,6 +65,16 @@ impl<'a> HTMLBodyElementMethods for JSRef<'a, HTMLBodyElement> { } } +pub trait HTMLBodyElementHelpers { + fn get_background_color(&self) -> Option<RGBA>; +} + +impl HTMLBodyElementHelpers for HTMLBodyElement { + fn get_background_color(&self) -> Option<RGBA> { + self.background_color.get() + } +} + impl<'a> VirtualMethods for JSRef<'a, HTMLBodyElement> { fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { let element: &JSRef<HTMLElement> = HTMLElementCast::from_borrowed_ref(self); @@ -91,6 +108,25 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLBodyElement> { name.slice_from(2), attr.value().as_slice().to_string()); } + + match attr.local_name() { + &atom!("bgcolor") => { + self.background_color.set(str::parse_legacy_color(attr.value().as_slice()).ok()) + } + _ => {} + } + } + + fn before_remove_attr(&self, attr: JSRef<Attr>) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(attr), + _ => {} + } + + match attr.local_name() { + &atom!("bgcolor") => self.background_color.set(None), + _ => {} + } } } diff --git a/components/script/dom/htmltablecellelement.rs b/components/script/dom/htmltablecellelement.rs index cee07540301..9ca84b0e70f 100644 --- a/components/script/dom/htmltablecellelement.rs +++ b/components/script/dom/htmltablecellelement.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 dom::attr::Attr; -use dom::attr::AttrHelpers; +use dom::attr::{Attr, AttrHelpers}; use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLTableCellElementDerived}; use dom::bindings::js::JSRef; use dom::bindings::utils::{Reflectable, Reflector}; @@ -15,13 +14,15 @@ use dom::htmlelement::HTMLElement; use dom::node::ElementNodeTypeId; use dom::virtualmethods::VirtualMethods; -use servo_util::str::{AutoLpa, DOMString, LengthOrPercentageOrAuto}; -use servo_util::str; +use cssparser::RGBA; +use servo_util::str::{mod, AutoLpa, DOMString, LengthOrPercentageOrAuto}; use std::cell::Cell; #[dom_struct] pub struct HTMLTableCellElement { htmlelement: HTMLElement, + background_color: Cell<Option<RGBA>>, + colspan: Cell<Option<u32>>, width: Cell<LengthOrPercentageOrAuto>, } @@ -36,10 +37,16 @@ impl HTMLTableCellElementDerived for EventTarget { } impl HTMLTableCellElement { - pub fn new_inherited(type_id: ElementTypeId, tag_name: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> HTMLTableCellElement { + pub fn new_inherited(type_id: ElementTypeId, + tag_name: DOMString, + prefix: Option<DOMString>, + document: JSRef<Document>) + -> HTMLTableCellElement { HTMLTableCellElement { htmlelement: HTMLElement::new_inherited(type_id, tag_name, prefix, document), - width: Cell::new(AutoLpa) + background_color: Cell::new(None), + colspan: Cell::new(None), + width: Cell::new(AutoLpa), } } @@ -50,10 +57,20 @@ impl HTMLTableCellElement { } pub trait HTMLTableCellElementHelpers { + fn get_background_color(&self) -> Option<RGBA>; + fn get_colspan(&self) -> Option<u32>; fn get_width(&self) -> LengthOrPercentageOrAuto; } impl HTMLTableCellElementHelpers for HTMLTableCellElement { + fn get_background_color(&self) -> Option<RGBA> { + self.background_color.get() + } + + fn get_colspan(&self) -> Option<u32> { + self.colspan.get() + } + fn get_width(&self) -> LengthOrPercentageOrAuto { self.width.get() } @@ -72,6 +89,12 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLTableCellElement> { } match attr.local_name() { + &atom!("bgcolor") => { + self.background_color.set(str::parse_legacy_color(attr.value().as_slice()).ok()) + } + &atom!("colspan") => { + self.colspan.set(str::parse_unsigned_integer(attr.value().as_slice().chars())); + } &atom!("width") => self.width.set(str::parse_length(attr.value().as_slice())), _ => () } @@ -84,6 +107,8 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLTableCellElement> { } match attr.local_name() { + &atom!("bgcolor") => self.background_color.set(None), + &atom!("colspan") => self.colspan.set(None), &atom!("width") => self.width.set(AutoLpa), _ => () } diff --git a/components/script/dom/htmltableelement.rs b/components/script/dom/htmltableelement.rs index 7dddf98bdc4..d2c9e723b78 100644 --- a/components/script/dom/htmltableelement.rs +++ b/components/script/dom/htmltableelement.rs @@ -2,10 +2,12 @@ * 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 dom::bindings::codegen::Bindings::HTMLTableElementBinding; +use dom::attr::{Attr, AttrHelpers}; use dom::bindings::codegen::Bindings::HTMLTableElementBinding::HTMLTableElementMethods; -use dom::bindings::codegen::InheritTypes::{HTMLTableElementDerived, NodeCast, HTMLTableCaptionElementCast}; +use dom::bindings::codegen::Bindings::HTMLTableElementBinding; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLTableCaptionElementCast}; +use dom::bindings::codegen::InheritTypes::{HTMLTableElementDerived, NodeCast}; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::Document; @@ -14,11 +16,18 @@ use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; use dom::htmltablecaptionelement::HTMLTableCaptionElement; use dom::node::{Node, NodeHelpers, ElementNodeTypeId}; -use servo_util::str::DOMString; +use dom::virtualmethods::VirtualMethods; + +use cssparser::RGBA; +use servo_util::str::{mod, AutoLpa, DOMString, LengthOrPercentageOrAuto}; +use std::cell::Cell; #[dom_struct] pub struct HTMLTableElement { htmlelement: HTMLElement, + background_color: Cell<Option<RGBA>>, + border: Cell<Option<u32>>, + width: Cell<LengthOrPercentageOrAuto>, } impl HTMLTableElementDerived for EventTarget { @@ -28,14 +37,22 @@ impl HTMLTableElementDerived for EventTarget { } impl HTMLTableElement { - fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> HTMLTableElement { + fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) + -> HTMLTableElement { HTMLTableElement { - htmlelement: HTMLElement::new_inherited(HTMLTableElementTypeId, localName, prefix, document) + htmlelement: HTMLElement::new_inherited(HTMLTableElementTypeId, + localName, + prefix, + document), + background_color: Cell::new(None), + border: Cell::new(None), + width: Cell::new(AutoLpa), } } #[allow(unrooted_must_root)] - pub fn new(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> Temporary<HTMLTableElement> { + pub fn new(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) + -> Temporary<HTMLTableElement> { let element = HTMLTableElement::new_inherited(localName, prefix, document); Node::reflect_node(box element, document, HTMLTableElementBinding::Wrap) } @@ -77,3 +94,66 @@ impl<'a> HTMLTableElementMethods for JSRef<'a, HTMLTableElement> { }); } } + +pub trait HTMLTableElementHelpers { + fn get_background_color(&self) -> Option<RGBA>; + fn get_border(&self) -> Option<u32>; + fn get_width(&self) -> LengthOrPercentageOrAuto; +} + +impl HTMLTableElementHelpers for HTMLTableElement { + fn get_background_color(&self) -> Option<RGBA> { + self.background_color.get() + } + + fn get_border(&self) -> Option<u32> { + self.border.get() + } + + fn get_width(&self) -> LengthOrPercentageOrAuto { + self.width.get() + } +} + +impl<'a> VirtualMethods for JSRef<'a, HTMLTableElement> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_borrowed_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, attr: JSRef<Attr>) { + match self.super_type() { + Some(ref s) => s.after_set_attr(attr), + _ => () + } + + match attr.local_name() { + &atom!("bgcolor") => { + self.background_color.set(str::parse_legacy_color(attr.value().as_slice()).ok()) + } + &atom!("border") => { + // According to HTML5 § 14.3.9, invalid values map to 1px. + self.border.set(Some(str::parse_unsigned_integer(attr.value() + .as_slice() + .chars()).unwrap_or(1))) + } + &atom!("width") => self.width.set(str::parse_length(attr.value().as_slice())), + _ => () + } + } + + fn before_remove_attr(&self, attr: JSRef<Attr>) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(attr), + _ => () + } + + match attr.local_name() { + &atom!("bgcolor") => self.background_color.set(None), + &atom!("border") => self.border.set(None), + &atom!("width") => self.width.set(AutoLpa), + _ => () + } + } +} + diff --git a/components/script/dom/htmltablerowelement.rs b/components/script/dom/htmltablerowelement.rs index 3edf4a0e64f..500c7d74472 100644 --- a/components/script/dom/htmltablerowelement.rs +++ b/components/script/dom/htmltablerowelement.rs @@ -2,8 +2,9 @@ * 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 dom::attr::{Attr, AttrHelpers}; use dom::bindings::codegen::Bindings::HTMLTableRowElementBinding; -use dom::bindings::codegen::InheritTypes::HTMLTableRowElementDerived; +use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLTableRowElementDerived}; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::Document; @@ -11,11 +12,16 @@ use dom::element::HTMLTableRowElementTypeId; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; use dom::node::{Node, ElementNodeTypeId}; -use servo_util::str::DOMString; +use dom::virtualmethods::VirtualMethods; + +use cssparser::RGBA; +use servo_util::str::{mod, DOMString}; +use std::cell::Cell; #[dom_struct] pub struct HTMLTableRowElement { htmlelement: HTMLElement, + background_color: Cell<Option<RGBA>>, } impl HTMLTableRowElementDerived for EventTarget { @@ -25,9 +31,14 @@ impl HTMLTableRowElementDerived for EventTarget { } impl HTMLTableRowElement { - fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> HTMLTableRowElement { + fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) + -> HTMLTableRowElement { HTMLTableRowElement { - htmlelement: HTMLElement::new_inherited(HTMLTableRowElementTypeId, localName, prefix, document) + htmlelement: HTMLElement::new_inherited(HTMLTableRowElementTypeId, + localName, + prefix, + document), + background_color: Cell::new(None), } } @@ -45,3 +56,47 @@ impl Reflectable for HTMLTableRowElement { self.htmlelement.reflector() } } + +pub trait HTMLTableRowElementHelpers { + fn get_background_color(&self) -> Option<RGBA>; +} + +impl HTMLTableRowElementHelpers for HTMLTableRowElement { + fn get_background_color(&self) -> Option<RGBA> { + self.background_color.get() + } +} + +impl<'a> VirtualMethods for JSRef<'a, HTMLTableRowElement> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_borrowed_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, attr: JSRef<Attr>) { + match self.super_type() { + Some(ref s) => s.after_set_attr(attr), + _ => () + } + + match attr.local_name() { + &atom!("bgcolor") => { + self.background_color.set(str::parse_legacy_color(attr.value().as_slice()).ok()) + } + _ => {} + } + } + + fn before_remove_attr(&self, attr: JSRef<Attr>) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(attr), + _ => () + } + + match attr.local_name() { + &atom!("bgcolor") => self.background_color.set(None), + _ => {} + } + } +} + diff --git a/components/script/dom/htmltablesectionelement.rs b/components/script/dom/htmltablesectionelement.rs index d10e4b82b44..b191e21bde0 100644 --- a/components/script/dom/htmltablesectionelement.rs +++ b/components/script/dom/htmltablesectionelement.rs @@ -2,8 +2,9 @@ * 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 dom::attr::{Attr, AttrHelpers}; use dom::bindings::codegen::Bindings::HTMLTableSectionElementBinding; -use dom::bindings::codegen::InheritTypes::HTMLTableSectionElementDerived; +use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLTableSectionElementDerived}; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::Document; @@ -11,11 +12,16 @@ use dom::element::HTMLTableSectionElementTypeId; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; use dom::node::{Node, ElementNodeTypeId}; -use servo_util::str::DOMString; +use dom::virtualmethods::VirtualMethods; + +use cssparser::RGBA; +use servo_util::str::{mod, DOMString}; +use std::cell::Cell; #[dom_struct] pub struct HTMLTableSectionElement { htmlelement: HTMLElement, + background_color: Cell<Option<RGBA>>, } impl HTMLTableSectionElementDerived for EventTarget { @@ -25,14 +31,20 @@ impl HTMLTableSectionElementDerived for EventTarget { } impl HTMLTableSectionElement { - fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> HTMLTableSectionElement { + fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) + -> HTMLTableSectionElement { HTMLTableSectionElement { - htmlelement: HTMLElement::new_inherited(HTMLTableSectionElementTypeId, localName, prefix, document) + htmlelement: HTMLElement::new_inherited(HTMLTableSectionElementTypeId, + localName, + prefix, + document), + background_color: Cell::new(None), } } #[allow(unrooted_must_root)] - pub fn new(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> Temporary<HTMLTableSectionElement> { + pub fn new(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) + -> Temporary<HTMLTableSectionElement> { let element = HTMLTableSectionElement::new_inherited(localName, prefix, document); Node::reflect_node(box element, document, HTMLTableSectionElementBinding::Wrap) } @@ -43,3 +55,47 @@ impl Reflectable for HTMLTableSectionElement { self.htmlelement.reflector() } } + +pub trait HTMLTableSectionElementHelpers { + fn get_background_color(&self) -> Option<RGBA>; +} + +impl HTMLTableSectionElementHelpers for HTMLTableSectionElement { + fn get_background_color(&self) -> Option<RGBA> { + self.background_color.get() + } +} + +impl<'a> VirtualMethods for JSRef<'a, HTMLTableSectionElement> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_borrowed_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, attr: JSRef<Attr>) { + match self.super_type() { + Some(ref s) => s.after_set_attr(attr), + _ => () + } + + match attr.local_name() { + &atom!("bgcolor") => { + self.background_color.set(str::parse_legacy_color(attr.value().as_slice()).ok()) + } + _ => {} + } + } + + fn before_remove_attr(&self, attr: JSRef<Attr>) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(attr), + _ => () + } + + match attr.local_name() { + &atom!("bgcolor") => self.background_color.set(None), + _ => {} + } + } +} + diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index ecfccf8aefc..0871a266df3 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -50,7 +50,7 @@ use devtools_traits::NodeInfo; use script_traits::UntrustedNodeAddress; use servo_util::geometry::Au; use servo_util::str::{DOMString, null_str_as_empty}; -use style::{parse_selector_list_from_str, matches, SelectorList}; +use style::{matches, AuthorOrigin, ParserContext, SelectorList}; use js::jsapi::{JSContext, JSObject, JSTracer, JSRuntime}; use js::jsfriendapi; @@ -60,8 +60,7 @@ use std::cell::{Cell, RefCell, Ref, RefMut}; use std::default::Default; use std::iter::{FilterMap, Peekable}; use std::mem; -use style; -use style::ComputedValues; +use style::{mod, ComputedValues}; use sync::Arc; use uuid; use string_cache::QualName; @@ -741,7 +740,10 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { // http://dom.spec.whatwg.org/#dom-parentnode-queryselector fn query_selector(self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> { // Step 1. - match parse_selector_list_from_str(selectors.as_slice()) { + let parser_context = ParserContext { + origin: AuthorOrigin, + }; + match style::parse_selector_list_from_str(&parser_context, selectors.as_slice()) { // Step 2. Err(()) => return Err(Syntax), // Step 3. @@ -758,11 +760,15 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { /// Get an iterator over all nodes which match a set of selectors /// Be careful not to do anything which may manipulate the DOM tree whilst iterating, otherwise /// the iterator may be invalidated - unsafe fn query_selector_iter(self, selectors: DOMString) -> Fallible<QuerySelectorIterator<'a>> { + unsafe fn query_selector_iter(self, selectors: DOMString) + -> Fallible<QuerySelectorIterator<'a>> { // Step 1. let nodes; let root = self.ancestors().last().unwrap_or(self.clone()); - match parse_selector_list_from_str(selectors.as_slice()) { + let parser_context = ParserContext { + origin: AuthorOrigin, + }; + match style::parse_selector_list_from_str(&parser_context, selectors.as_slice()) { // Step 2. Err(()) => return Err(Syntax), // Step 3. diff --git a/components/script/dom/virtualmethods.rs b/components/script/dom/virtualmethods.rs index a90055bc50d..94a74124eca 100644 --- a/components/script/dom/virtualmethods.rs +++ b/components/script/dom/virtualmethods.rs @@ -22,7 +22,10 @@ use dom::bindings::codegen::InheritTypes::HTMLOptionElementCast; use dom::bindings::codegen::InheritTypes::HTMLScriptElementCast; use dom::bindings::codegen::InheritTypes::HTMLSelectElementCast; use dom::bindings::codegen::InheritTypes::HTMLStyleElementCast; +use dom::bindings::codegen::InheritTypes::HTMLTableElementCast; use dom::bindings::codegen::InheritTypes::HTMLTableCellElementCast; +use dom::bindings::codegen::InheritTypes::HTMLTableRowElementCast; +use dom::bindings::codegen::InheritTypes::HTMLTableSectionElementCast; use dom::bindings::codegen::InheritTypes::HTMLTextAreaElementCast; use dom::bindings::codegen::InheritTypes::HTMLTitleElementCast; use dom::bindings::js::JSRef; @@ -46,7 +49,10 @@ use dom::element::HTMLScriptElementTypeId; use dom::element::HTMLSelectElementTypeId; use dom::element::HTMLStyleElementTypeId; use dom::element::HTMLTableDataCellElementTypeId; +use dom::element::HTMLTableElementTypeId; use dom::element::HTMLTableHeaderCellElementTypeId; +use dom::element::HTMLTableRowElementTypeId; +use dom::element::HTMLTableSectionElementTypeId; use dom::element::HTMLTextAreaElementTypeId; use dom::element::HTMLTitleElementTypeId; use dom::event::Event; @@ -67,7 +73,10 @@ use dom::htmloptionelement::HTMLOptionElement; use dom::htmlscriptelement::HTMLScriptElement; use dom::htmlselectelement::HTMLSelectElement; use dom::htmlstyleelement::HTMLStyleElement; +use dom::htmltableelement::HTMLTableElement; use dom::htmltablecellelement::HTMLTableCellElement; +use dom::htmltablerowelement::HTMLTableRowElement; +use dom::htmltablesectionelement::HTMLTableSectionElement; use dom::htmltextareaelement::HTMLTextAreaElement; use dom::htmltitleelement::HTMLTitleElement; use dom::node::{Node, NodeHelpers, ElementNodeTypeId, CloneChildrenFlag}; @@ -226,9 +235,25 @@ pub fn vtable_for<'a>(node: &'a JSRef<'a, Node>) -> &'a VirtualMethods + 'a { let element: &'a JSRef<'a, HTMLStyleElement> = HTMLStyleElementCast::to_borrowed_ref(node).unwrap(); element as &'a VirtualMethods + 'a } + ElementNodeTypeId(HTMLTableElementTypeId) => { + let element: &'a JSRef<'a, HTMLTableElement> = + HTMLTableElementCast::to_borrowed_ref(node).unwrap(); + element as &'a VirtualMethods + 'a + } ElementNodeTypeId(HTMLTableDataCellElementTypeId) | ElementNodeTypeId(HTMLTableHeaderCellElementTypeId) => { - let element: &'a JSRef<'a, HTMLTableCellElement> = HTMLTableCellElementCast::to_borrowed_ref(node).unwrap(); + let element: &'a JSRef<'a, HTMLTableCellElement> = + HTMLTableCellElementCast::to_borrowed_ref(node).unwrap(); + element as &'a VirtualMethods + 'a + } + ElementNodeTypeId(HTMLTableRowElementTypeId) => { + let element: &'a JSRef<'a, HTMLTableRowElement> = + HTMLTableRowElementCast::to_borrowed_ref(node).unwrap(); + element as &'a VirtualMethods + 'a + } + ElementNodeTypeId(HTMLTableSectionElementTypeId) => { + let element: &'a JSRef<'a, HTMLTableSectionElement> = + HTMLTableSectionElementCast::to_borrowed_ref(node).unwrap(); element as &'a VirtualMethods + 'a } ElementNodeTypeId(HTMLTextAreaElementTypeId) => { diff --git a/components/script/layout_interface.rs b/components/script/layout_interface.rs index c3afb15c057..0f3f4fbacbf 100644 --- a/components/script/layout_interface.rs +++ b/components/script/layout_interface.rs @@ -29,6 +29,9 @@ pub enum Msg { /// Adds the given stylesheet to the document. LoadStylesheetMsg(Url), + /// Puts a document into quirks mode, causing the quirks mode stylesheet to be loaded. + SetQuirksModeMsg, + /// Requests a reflow. ReflowMsg(Box<Reflow>), diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index bb04bd528af..edede8178fc 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -121,7 +121,7 @@ dependencies = [ [[package]] name = "cssparser" version = "0.1.0" -source = "git+https://github.com/servo/rust-cssparser#cbbfd66f794bd019bbdeaefc88b29eff455b62e5" +source = "git+https://github.com/servo/rust-cssparser#3f98f1308b769b5d61efc6c133ac520df2b074ac" dependencies = [ "encoding 0.2.0 (git+https://github.com/lifthrasiir/rust-encoding)", ] @@ -410,6 +410,7 @@ dependencies = [ name = "layout" version = "0.0.1" dependencies = [ + "cssparser 0.1.0 (git+https://github.com/servo/rust-cssparser)", "encoding 0.2.0 (git+https://github.com/lifthrasiir/rust-encoding)", "geom 0.1.0 (git+https://github.com/servo/rust-geom)", "gfx 0.0.1", @@ -672,6 +673,7 @@ dependencies = [ name = "util" version = "0.0.1" dependencies = [ + "cssparser 0.1.0 (git+https://github.com/servo/rust-cssparser)", "geom 0.1.0 (git+https://github.com/servo/rust-geom)", "layers 0.1.0 (git+https://github.com/servo/rust-layers)", "string_cache 0.0.0 (git+https://github.com/servo/string-cache)", diff --git a/components/style/legacy.rs b/components/style/legacy.rs index f99314667b8..6b22d70ca76 100644 --- a/components/style/legacy.rs +++ b/components/style/legacy.rs @@ -5,6 +5,17 @@ //! Legacy presentational attributes defined in the HTML5 specification: `<td width>`, //! `<input size>`, and so forth. +use node::{TElement, TElementAttributes, TNode}; +use properties::{BackgroundColorDeclaration, BorderBottomWidthDeclaration}; +use properties::{BorderLeftWidthDeclaration, BorderRightWidthDeclaration}; +use properties::{BorderTopWidthDeclaration, SpecifiedValue, WidthDeclaration, specified}; +use selector_matching::{DeclarationBlock, Stylist}; + +use cssparser::RGBAColor; +use servo_util::geometry::Au; +use servo_util::smallvec::VecLike; +use servo_util::str::{AutoLpa, LengthLpa, PercentageLpa}; + /// Legacy presentational attributes that take a length as defined in HTML5 § 2.4.4.4. pub enum LengthAttribute { /// `<td width>` @@ -17,3 +28,187 @@ pub enum IntegerAttribute { SizeIntegerAttribute, } +/// Legacy presentational attributes that take a nonnegative integer as defined in HTML5 § 2.4.4.2. +pub enum UnsignedIntegerAttribute { + /// `<td border>` + BorderUnsignedIntegerAttribute, + /// `<td colspan>` + ColSpanUnsignedIntegerAttribute, +} + +/// Legacy presentational attributes that take a simple color as defined in HTML5 § 2.4.6. +pub enum SimpleColorAttribute { + /// `<body bgcolor>` + BgColorSimpleColorAttribute, +} + +/// Extension methods for `Stylist` that cause rules to be synthesized for legacy attributes. +pub trait PresentationalHintSynthesis { + /// Synthesizes rules from various HTML attributes (mostly legacy junk from HTML4) that confer + /// *presentational hints* as defined in the HTML5 specification. This handles stuff like + /// `<body bgcolor>`, `<input size>`, `<td width>`, and so forth. + /// + /// NB: Beware! If you add an attribute to this list, be sure to add it to + /// `common_style_affecting_attributes` or `rare_style_affecting_attributes` as appropriate. If + /// you don't, you risk strange random nondeterministic failures due to false positives in + /// style sharing. + fn synthesize_presentational_hints_for_legacy_attributes<'a,E,N,V>( + &self, + node: &N, + matching_rules_list: &mut V, + shareable: &mut bool) + where E: TElement<'a> + + TElementAttributes, + N: TNode<'a,E>, + V: VecLike<DeclarationBlock>; + /// Synthesizes rules for the legacy `bgcolor` attribute. + fn synthesize_presentational_hint_for_legacy_background_color_attribute<'a,E,V>( + &self, + element: E, + matching_rules_list: + &mut V, + shareable: &mut bool) + where + E: TElement<'a> + + TElementAttributes, + V: VecLike< + DeclarationBlock>; + /// Synthesizes rules for the legacy `border` attribute. + fn synthesize_presentational_hint_for_legacy_border_attribute<'a,E,V>( + &self, + element: E, + matching_rules_list: &mut V, + shareable: &mut bool) + where + E: TElement<'a> + + TElementAttributes, + V: VecLike<DeclarationBlock>; +} + +impl PresentationalHintSynthesis for Stylist { + fn synthesize_presentational_hints_for_legacy_attributes<'a,E,N,V>( + &self, + node: &N, + matching_rules_list: &mut V, + shareable: &mut bool) + where E: TElement<'a> + + TElementAttributes, + N: TNode<'a,E>, + V: VecLike<DeclarationBlock> { + let element = node.as_element(); + match element.get_local_name() { + name if *name == atom!("td") => { + match element.get_length_attribute(WidthLengthAttribute) { + AutoLpa => {} + PercentageLpa(percentage) => { + let width_value = specified::LPA_Percentage(percentage); + matching_rules_list.vec_push(DeclarationBlock::from_declaration( + WidthDeclaration(SpecifiedValue(width_value)))); + *shareable = false + } + LengthLpa(length) => { + let width_value = specified::LPA_Length(specified::Au_(length)); + matching_rules_list.vec_push(DeclarationBlock::from_declaration( + WidthDeclaration(SpecifiedValue(width_value)))); + *shareable = false + } + } + self.synthesize_presentational_hint_for_legacy_background_color_attribute( + element, + matching_rules_list, + shareable); + self.synthesize_presentational_hint_for_legacy_border_attribute( + element, + matching_rules_list, + shareable); + } + name if *name == atom!("table") => { + self.synthesize_presentational_hint_for_legacy_background_color_attribute( + element, + matching_rules_list, + shareable); + self.synthesize_presentational_hint_for_legacy_border_attribute( + element, + matching_rules_list, + shareable); + } + name if *name == atom!("body") || *name == atom!("tr") || *name == atom!("thead") || + *name == atom!("tbody") || *name == atom!("tfoot") => { + self.synthesize_presentational_hint_for_legacy_background_color_attribute( + element, + matching_rules_list, + shareable); + } + name if *name == atom!("input") => { + match element.get_integer_attribute(SizeIntegerAttribute) { + Some(value) if value != 0 => { + // Per HTML 4.01 § 17.4, this value is in characters if `type` is `text` or + // `password` and in pixels otherwise. + // + // FIXME(pcwalton): More use of atoms, please! + let value = match element.get_attr(&ns!(""), &atom!("type")) { + Some("text") | Some("password") => { + specified::ServoCharacterWidth(value) + } + _ => specified::Au_(Au::from_px(value as int)), + }; + matching_rules_list.vec_push(DeclarationBlock::from_declaration( + WidthDeclaration(SpecifiedValue(specified::LPA_Length( + value))))); + *shareable = false + } + Some(_) | None => {} + } + } + _ => {} + } + } + + fn synthesize_presentational_hint_for_legacy_background_color_attribute<'a,E,V>( + &self, + element: E, + matching_rules_list: + &mut V, + shareable: &mut bool) + where + E: TElement<'a> + + TElementAttributes, + V: VecLike< + DeclarationBlock> { + match element.get_simple_color_attribute(BgColorSimpleColorAttribute) { + None => {} + Some(color) => { + matching_rules_list.vec_push(DeclarationBlock::from_declaration( + BackgroundColorDeclaration(SpecifiedValue(RGBAColor(color))))); + *shareable = false + } + } + } + + fn synthesize_presentational_hint_for_legacy_border_attribute<'a,E,V>( + &self, + element: E, + matching_rules_list: &mut V, + shareable: &mut bool) + where + E: TElement<'a> + + TElementAttributes, + V: VecLike<DeclarationBlock> { + match element.get_unsigned_integer_attribute(BorderUnsignedIntegerAttribute) { + None => {} + Some(length) => { + let width_value = specified::Au_(Au::from_px(length as int)); + matching_rules_list.vec_push(DeclarationBlock::from_declaration( + BorderTopWidthDeclaration(SpecifiedValue(width_value)))); + matching_rules_list.vec_push(DeclarationBlock::from_declaration( + BorderLeftWidthDeclaration(SpecifiedValue(width_value)))); + matching_rules_list.vec_push(DeclarationBlock::from_declaration( + BorderBottomWidthDeclaration(SpecifiedValue(width_value)))); + matching_rules_list.vec_push(DeclarationBlock::from_declaration( + BorderRightWidthDeclaration(SpecifiedValue(width_value)))); + *shareable = false + } + } + } +} + diff --git a/components/style/lib.rs b/components/style/lib.rs index e6e0e77f7a5..d28ba62b112 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -41,7 +41,8 @@ pub use selector_matching::{DeclarationBlock, CommonStyleAffectingAttributes}; pub use selector_matching::{CommonStyleAffectingAttributeInfo, CommonStyleAffectingAttributeMode}; pub use selector_matching::{AttrIsPresentMode, AttrIsEqualMode}; pub use selector_matching::{matches, matches_simple_selector, common_style_affecting_attributes}; -pub use selector_matching::{RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE,SELECTOR_WHITESPACE}; +pub use selector_matching::{rare_style_affecting_attributes}; +pub use selector_matching::{RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE, SELECTOR_WHITESPACE}; pub use properties::{cascade, cascade_anonymous, computed}; pub use properties::{PropertyDeclaration, ComputedValues, computed_values, style_structs}; pub use properties::{PropertyDeclarationBlock, parse_style_attribute}; // Style attributes @@ -49,11 +50,14 @@ pub use properties::{CSSFloat, DeclaredValue, PropertyDeclarationParseResult}; pub use properties::{Angle, AngleOrCorner, AngleAoc, CornerAoc}; pub use properties::{Left, Right, Bottom, Top}; pub use node::{TElement, TElementAttributes, TNode}; -pub use selectors::{PseudoElement, Before, After, SelectorList, parse_selector_list_from_str}; +pub use selectors::{PseudoElement, Before, After, ParserContext, SelectorList}; pub use selectors::{AttrSelector, NamespaceConstraint, SpecificNamespace, AnyNamespace}; -pub use selectors::{SimpleSelector,LocalNameSelector}; +pub use selectors::{SimpleSelector, LocalNameSelector, parse_selector_list_from_str}; pub use cssparser::{Color, RGBA}; -pub use legacy::{IntegerAttribute, LengthAttribute, SizeIntegerAttribute, WidthLengthAttribute}; +pub use legacy::{BgColorSimpleColorAttribute, BorderUnsignedIntegerAttribute}; +pub use legacy::{ColSpanUnsignedIntegerAttribute, IntegerAttribute, LengthAttribute}; +pub use legacy::{SimpleColorAttribute, SizeIntegerAttribute, UnsignedIntegerAttribute}; +pub use legacy::{WidthLengthAttribute}; pub use font_face::{Source, LocalSource, UrlSource_}; mod stylesheets; diff --git a/components/style/media_queries.rs b/components/style/media_queries.rs index 33d5203064e..f014be0114d 100644 --- a/components/style/media_queries.rs +++ b/components/style/media_queries.rs @@ -8,12 +8,14 @@ use cssparser::ast::*; use errors::{ErrorLoggerIterator, log_css_error}; use geom::size::TypedSize2D; -use stylesheets::{CSSRule, CSSMediaRule, parse_style_rule, parse_nested_at_rule}; +use selectors::ParserContext; +use stylesheets::{CSSRule, CSSMediaRule}; use namespaces::NamespaceMap; use parsing_utils::{BufferedIter, ParserIter}; use properties::common_types::*; use properties::longhands; use servo_util::geometry::ViewportPx; +use stylesheets; use url::Url; pub struct MediaRule { @@ -95,8 +97,11 @@ impl Device { } } -pub fn parse_media_rule(rule: AtRule, parent_rules: &mut Vec<CSSRule>, - namespaces: &NamespaceMap, base_url: &Url) { +pub fn parse_media_rule(context: &ParserContext, + rule: AtRule, + parent_rules: &mut Vec<CSSRule>, + namespaces: &NamespaceMap, + base_url: &Url) { let media_queries = parse_media_query_list(rule.prelude.as_slice()); let block = match rule.block { Some(block) => block, @@ -108,9 +113,17 @@ pub fn parse_media_rule(rule: AtRule, parent_rules: &mut Vec<CSSRule>, let mut rules = vec!(); for rule in ErrorLoggerIterator(parse_rule_list(block.into_iter())) { match rule { - QualifiedRule_(rule) => parse_style_rule(rule, &mut rules, namespaces, base_url), - AtRule_(rule) => parse_nested_at_rule( - rule.name.as_slice().to_ascii_lower().as_slice(), rule, &mut rules, namespaces, base_url), + QualifiedRule_(rule) => { + stylesheets::parse_style_rule(context, rule, &mut rules, namespaces, base_url) + } + AtRule_(rule) => { + stylesheets::parse_nested_at_rule(context, + rule.name.as_slice().to_ascii_lower().as_slice(), + rule, + &mut rules, + namespaces, + base_url) + } } } parent_rules.push(CSSMediaRule(MediaRule { diff --git a/components/style/node.rs b/components/style/node.rs index 5a765f2798d..a1c9a8071c4 100644 --- a/components/style/node.rs +++ b/components/style/node.rs @@ -5,7 +5,8 @@ //! Traits that nodes must implement. Breaks the otherwise-cyclic dependency between layout and //! style. -use legacy::{IntegerAttribute, LengthAttribute}; +use cssparser::RGBA; +use legacy::{IntegerAttribute, LengthAttribute, SimpleColorAttribute, UnsignedIntegerAttribute}; use selectors::AttrSelector; use servo_util::str::LengthOrPercentageOrAuto; use string_cache::{Atom, Namespace}; @@ -47,6 +48,7 @@ pub trait TElement<'a> : Copy { fn get_enabled_state(self) -> bool; fn get_checked_state(self) -> bool; fn has_class(self, name: &Atom) -> bool; + fn has_nonzero_border(self) -> bool; // Ordinarily I wouldn't use callbacks like this, but the alternative is // really messy, since there is a `JSRef` and a `RefCell` involved. Maybe @@ -58,4 +60,6 @@ pub trait TElement<'a> : Copy { pub trait TElementAttributes : Copy { fn get_length_attribute(self, attribute: LengthAttribute) -> LengthOrPercentageOrAuto; fn get_integer_attribute(self, attribute: IntegerAttribute) -> Option<i32>; + fn get_unsigned_integer_attribute(self, attribute: UnsignedIntegerAttribute) -> Option<u32>; + fn get_simple_color_attribute(self, attribute: SimpleColorAttribute) -> Option<RGBA>; } diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index 7cd1fd1d232..5482745721f 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -11,21 +11,31 @@ use sync::Arc; use url::Url; use servo_util::bloom::BloomFilter; -use servo_util::geometry::Au; use servo_util::resource_files::read_resource_file; use servo_util::smallvec::VecLike; use servo_util::sort; -use servo_util::str::{AutoLpa, LengthLpa, PercentageLpa}; use string_cache::Atom; -use legacy::{SizeIntegerAttribute, WidthLengthAttribute}; +use legacy::PresentationalHintSynthesis; use media_queries::Device; use node::{TElement, TElementAttributes, TNode}; -use properties::{PropertyDeclaration, PropertyDeclarationBlock, SpecifiedValue, WidthDeclaration}; -use properties::{specified}; -use selectors::*; +use properties::{PropertyDeclaration, PropertyDeclarationBlock}; +use selectors::{After, AnyLink, AttrDashMatch, AttrEqual}; +use selectors::{AttrExists, AttrIncludes, AttrPrefixMatch}; +use selectors::{AttrSubstringMatch, AttrSuffixMatch, Before, CaseInsensitive, CaseSensitive}; +use selectors::{Checked, Child, ClassSelector}; +use selectors::{CompoundSelector, Descendant, Disabled, Enabled, FirstChild, FirstOfType}; +use selectors::{Hover, IDSelector, LastChild, LastOfType}; +use selectors::{LaterSibling, LocalName, LocalNameSelector}; +use selectors::{NamespaceSelector, Link, Negation}; +use selectors::{NextSibling, NthChild}; +use selectors::{NthLastChild, NthLastOfType}; +use selectors::{NthOfType, OnlyChild, OnlyOfType, PseudoElement, Root}; +use selectors::{SelectorList, ServoNonzeroBorder, SimpleSelector, Visited}; +use selectors::{get_selector_list_selectors}; use stylesheets::{Stylesheet, iter_stylesheet_media_rules, iter_stylesheet_style_rules}; +#[deriving(Clone, PartialEq)] pub enum StylesheetOrigin { UserAgentOrigin, AuthorOrigin, @@ -295,7 +305,6 @@ impl Stylist { after_map: PerPseudoElementSelectorMap::new(), rules_source_order: 0u, }; - // FIXME: Add quirks-mode.css in quirks mode. // FIXME: Add iso-8859-9.css when the document’s encoding is ISO-8859-8. // FIXME: presentational-hints.css should be at author origin with zero specificity. // (Does it make a difference?) @@ -391,6 +400,15 @@ impl Stylist { self.is_dirty |= is_dirty; } + pub fn add_quirks_mode_stylesheet(&mut self) { + self.add_stylesheet(Stylesheet::from_bytes( + read_resource_file(["quirks-mode.css"]).unwrap().as_slice(), + Url::parse("chrome:///quirks-mode.css").unwrap(), + None, + None, + UserAgentOrigin)) + } + pub fn add_stylesheet(&mut self, stylesheet: Stylesheet) { self.stylesheets.push(stylesheet); self.is_dirty = true; @@ -477,62 +495,6 @@ impl Stylist { shareable } - - /// Synthesizes rules from various HTML attributes (mostly legacy junk from HTML4) that confer - /// *presentational hints* as defined in the HTML5 specification. This handles stuff like - /// `<body bgcolor>`, `<input size>`, `<td width>`, and so forth. - fn synthesize_presentational_hints_for_legacy_attributes<'a,E,N,V>( - &self, - node: &N, - matching_rules_list: &mut V, - shareable: &mut bool) - where E: TElement<'a> + - TElementAttributes, - N: TNode<'a,E>, - V: VecLike<DeclarationBlock> { - let element = node.as_element(); - match element.get_local_name() { - name if *name == atom!("td") => { - match element.get_length_attribute(WidthLengthAttribute) { - AutoLpa => {} - PercentageLpa(percentage) => { - let width_value = specified::LPA_Percentage(percentage); - matching_rules_list.vec_push(DeclarationBlock::from_declaration( - WidthDeclaration(SpecifiedValue(width_value)))); - *shareable = false - } - LengthLpa(length) => { - let width_value = specified::LPA_Length(specified::Au_(length)); - matching_rules_list.vec_push(DeclarationBlock::from_declaration( - WidthDeclaration(SpecifiedValue(width_value)))); - *shareable = false - } - }; - } - name if *name == atom!("input") => { - match element.get_integer_attribute(SizeIntegerAttribute) { - Some(value) if value != 0 => { - // Per HTML 4.01 § 17.4, this value is in characters if `type` is `text` or - // `password` and in pixels otherwise. - // - // FIXME(pcwalton): More use of atoms, please! - let value = match element.get_attr(&ns!(""), &atom!("type")) { - Some("text") | Some("password") => { - specified::ServoCharacterWidth(value) - } - _ => specified::Au_(Au::from_px(value as int)), - }; - matching_rules_list.vec_push(DeclarationBlock::from_declaration( - WidthDeclaration(SpecifiedValue(specified::LPA_Length( - value))))); - *shareable = false - } - Some(_) | None => {} - } - } - _ => {} - } - } } struct PerOriginSelectorMap { @@ -858,6 +820,13 @@ pub fn common_style_affecting_attributes() -> [CommonStyleAffectingAttributeInfo ] } +/// Attributes that, if present, disable style sharing. All legacy HTML attributes must be in +/// either this list or `common_style_affecting_attributes`. See the comment in +/// `synthesize_presentational_hints_for_legacy_attributes`. +pub fn rare_style_affecting_attributes() -> [Atom, ..3] { + [ atom!("bgcolor"), atom!("border"), atom!("colspan") ] +} + /// Determines whether the given element matches the given single selector. /// /// NB: If you add support for any new kinds of selectors to this routine, be sure to set @@ -1052,6 +1021,12 @@ pub fn matches_simple_selector<'a,E,N>(selector: &SimpleSelector, matches_generic_nth_child(element, 0, 1, true, true) } + ServoNonzeroBorder => { + *shareable = false; + let elem = element.as_element(); + elem.has_nonzero_border() + } + Negation(ref negated) => { *shareable = false; !negated.iter().all(|s| matches_simple_selector(s, element, shareable)) @@ -1204,12 +1179,16 @@ mod tests { /// Each sublist of the result contains the Rules for one StyleRule. fn get_mock_rules(css_selectors: &[&str]) -> Vec<Vec<Rule>> { use namespaces::NamespaceMap; - use selectors::parse_selector_list; + use selectors::{ParserContext, parse_selector_list}; + use selector_matching::AuthorOrigin; use cssparser::tokenize; let namespaces = NamespaceMap::new(); css_selectors.iter().enumerate().map(|(i, selectors)| { - parse_selector_list(tokenize(*selectors).map(|(c, _)| c), &namespaces) + let context = ParserContext { + origin: AuthorOrigin, + }; + parse_selector_list(&context, tokenize(*selectors).map(|(c, _)| c), &namespaces) .unwrap().into_iter().map(|s| { Rule { selector: s.compound_selectors.clone(), diff --git a/components/style/selectors.rs b/components/style/selectors.rs index da0e2b7b638..1d72a530383 100644 --- a/components/style/selectors.rs +++ b/components/style/selectors.rs @@ -9,10 +9,16 @@ use sync::Arc; use cssparser::ast::*; use cssparser::{tokenize, parse_nth}; +use selector_matching::{StylesheetOrigin, UserAgentOrigin}; use string_cache::{Atom, Namespace}; use namespaces::NamespaceMap; +/// Ambient data used by the parser. +pub struct ParserContext { + /// The origin of this stylesheet. + pub origin: StylesheetOrigin, +} #[deriving(PartialEq, Clone)] pub struct Selector { @@ -79,7 +85,8 @@ pub enum SimpleSelector { NthLastOfType(i32, i32), FirstOfType, LastOfType, - OnlyOfType + OnlyOfType, + ServoNonzeroBorder, // ... } @@ -111,12 +118,6 @@ pub enum NamespaceConstraint { } -pub fn parse_selector_list_from_str(input: &str) -> Result<SelectorList, ()> { - let namespaces = NamespaceMap::new(); - let iter = tokenize(input).map(|(token, _)| token); - parse_selector_list(iter, &namespaces).map(|s| SelectorList { selectors: s }) -} - /// Re-exported to script, but opaque. pub struct SelectorList { selectors: Vec<Selector> @@ -127,70 +128,9 @@ pub fn get_selector_list_selectors<'a>(selector_list: &'a SelectorList) -> &'a [ selector_list.selectors.as_slice() } -/// Parse a comma-separated list of Selectors. -/// aka Selector Group in http://www.w3.org/TR/css3-selectors/#grouping -/// -/// Return the Selectors or None if there is an invalid selector. -pub fn parse_selector_list<I: Iterator<ComponentValue>>( - iter: I, namespaces: &NamespaceMap) - -> Result<Vec<Selector>, ()> { - let iter = &mut iter.peekable(); - let mut results = vec![try!(parse_selector(iter, namespaces))]; - - loop { - skip_whitespace(iter); - match iter.peek() { - None => break, // EOF - Some(&Comma) => { - iter.next(); - } - _ => return Err(()), - } - results.push(try!(parse_selector(iter, namespaces))); - } - Ok(results) -} - type Iter<I> = iter::Peekable<ComponentValue, I>; -/// Build up a Selector. -/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ; -/// -/// `Err` means invalid selector. -fn parse_selector<I: Iterator<ComponentValue>>( - iter: &mut Iter<I>, namespaces: &NamespaceMap) - -> Result<Selector, ()> { - let (first, mut pseudo_element) = try!(parse_simple_selectors(iter, namespaces)); - let mut compound = CompoundSelector{ simple_selectors: first, next: None }; - - while pseudo_element.is_none() { - let any_whitespace = skip_whitespace(iter); - let combinator = match iter.peek() { - None => break, // EOF - Some(&Comma) => break, - Some(&Delim('>')) => { iter.next(); Child }, - Some(&Delim('+')) => { iter.next(); NextSibling }, - Some(&Delim('~')) => { iter.next(); LaterSibling }, - Some(_) => { - if any_whitespace { Descendant } - else { return Err(()) } - } - }; - let (simple_selectors, pseudo) = try!(parse_simple_selectors(iter, namespaces)); - compound = CompoundSelector { - simple_selectors: simple_selectors, - next: Some((box compound, combinator)) - }; - pseudo_element = pseudo; - } - Ok(Selector { - specificity: compute_specificity(&compound, &pseudo_element), - compound_selectors: Arc::new(compound), - pseudo_element: pseudo_element, - }) -} - fn compute_specificity(mut selector: &CompoundSelector, pseudo_element: &Option<PseudoElement>) -> u32 { @@ -231,7 +171,7 @@ fn compute_specificity(mut selector: &CompoundSelector, // | &Empty | &Lang(*) | &NthChild(..) | &NthLastChild(..) | &NthOfType(..) | &NthLastOfType(..) - | &FirstOfType | &LastOfType | &OnlyOfType + | &FirstOfType | &LastOfType | &OnlyOfType | &ServoNonzeroBorder => specificity.class_like_selectors += 1, &NamespaceSelector(..) => (), &Negation(ref negated) @@ -247,32 +187,6 @@ fn compute_specificity(mut selector: &CompoundSelector, } -/// simple_selector_sequence -/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]* -/// | [ HASH | class | attrib | pseudo | negation ]+ -/// -/// `Err(())` means invalid selector -fn parse_simple_selectors<I: Iterator<ComponentValue>>( - iter: &mut Iter<I>, namespaces: &NamespaceMap) - -> Result<(Vec<SimpleSelector>, Option<PseudoElement>), ()> { - let mut empty = true; - let mut simple_selectors = match try!(parse_type_selector(iter, namespaces)) { - None => vec![], - Some(s) => { empty = false; s } - }; - - let mut pseudo_element = None; - loop { - match try!(parse_one_simple_selector(iter, namespaces, /* inside_negation = */ false)) { - None => break, - Some(SimpleSelectorResult(s)) => { simple_selectors.push(s); empty = false }, - Some(PseudoElementResult(p)) => { pseudo_element = Some(p); empty = false; break }, - } - } - if empty { Err(()) } // An empty selector is invalid - else { Ok((simple_selectors, pseudo_element)) } -} - /// * `Err(())`: Invalid selector, abort /// * `Ok(None)`: Not a type selector, could be something else. `iter` was not consumed. @@ -309,67 +223,6 @@ enum SimpleSelectorParseResult { PseudoElementResult(PseudoElement), } -/// Parse a simple selector other than a type selector. -/// -/// * `Err(())`: Invalid selector, abort -/// * `Ok(None)`: Not a simple selector, could be something else. `iter` was not consumed. -/// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element -fn parse_one_simple_selector<I: Iterator<ComponentValue>>( - iter: &mut Iter<I>, namespaces: &NamespaceMap, inside_negation: bool) - -> Result<Option<SimpleSelectorParseResult>, ()> { - match iter.peek() { - Some(&IDHash(_)) => match iter.next() { - Some(IDHash(id)) => Ok(Some(SimpleSelectorResult( - IDSelector(Atom::from_slice(id.as_slice()))))), - _ => panic!("Implementation error, this should not happen."), - }, - Some(&Delim('.')) => { - iter.next(); - match iter.next() { - Some(Ident(class)) => Ok(Some(SimpleSelectorResult( - ClassSelector(Atom::from_slice(class.as_slice()))))), - _ => Err(()), - } - } - Some(&SquareBracketBlock(_)) => match iter.next() { - Some(SquareBracketBlock(content)) - => Ok(Some(SimpleSelectorResult(try!(parse_attribute_selector(content, namespaces))))), - _ => panic!("Implementation error, this should not happen."), - }, - Some(&Colon) => { - iter.next(); - match iter.next() { - Some(Ident(name)) => match parse_simple_pseudo_class(name.as_slice()) { - Err(()) => { - match name.as_slice().to_ascii_lower().as_slice() { - // Supported CSS 2.1 pseudo-elements only. - // ** Do not add to this list! ** - "before" => Ok(Some(PseudoElementResult(Before))), - "after" => Ok(Some(PseudoElementResult(After))), -// "first-line" => PseudoElementResult(FirstLine), -// "first-letter" => PseudoElementResult(FirstLetter), - _ => Err(()) - } - }, - Ok(result) => Ok(Some(SimpleSelectorResult(result))), - }, - Some(Function(name, arguments)) - => Ok(Some(SimpleSelectorResult(try!(parse_functional_pseudo_class( - name, arguments, namespaces, inside_negation))))), - Some(Colon) => { - match iter.next() { - Some(Ident(name)) - => Ok(Some(PseudoElementResult(try!(parse_pseudo_element(name))))), - _ => Err(()), - } - } - _ => Err(()), - } - } - _ => Ok(None), - } -} - /// * `Err(())`: Invalid selector, abort /// * `Ok(None)`: Not a simple selector, could be something else. `iter` was not consumed. @@ -489,8 +342,224 @@ fn parse_attribute_flags<I: Iterator<ComponentValue>>(iter: &mut Iter<I>) } } +pub fn parse_selector_list_from_str(context: &ParserContext, input: &str) + -> Result<SelectorList,()> { + let namespaces = NamespaceMap::new(); + let iter = tokenize(input).map(|(token, _)| token); + parse_selector_list(context, iter, &namespaces).map(|s| SelectorList { selectors: s }) +} + +/// Parse a comma-separated list of Selectors. +/// aka Selector Group in http://www.w3.org/TR/css3-selectors/#grouping +/// +/// Return the Selectors or None if there is an invalid selector. +pub fn parse_selector_list<I>(context: &ParserContext, iter: I, namespaces: &NamespaceMap) + -> Result<Vec<Selector>,()> + where I: Iterator<ComponentValue> { + let iter = &mut iter.peekable(); + let mut results = vec![try!(parse_selector(context, iter, namespaces))]; -fn parse_simple_pseudo_class(name: &str) -> Result<SimpleSelector, ()> { + loop { + skip_whitespace(iter); + match iter.peek() { + None => break, // EOF + Some(&Comma) => { + iter.next(); + } + _ => return Err(()), + } + results.push(try!(parse_selector(context, iter, namespaces))); + } + Ok(results) +} +/// Build up a Selector. +/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ; +/// +/// `Err` means invalid selector. +fn parse_selector<I>(context: &ParserContext, iter: &mut Iter<I>, namespaces: &NamespaceMap) + -> Result<Selector,()> + where I: Iterator<ComponentValue> { + let (first, mut pseudo_element) = try!(parse_simple_selectors(context, iter, namespaces)); + let mut compound = CompoundSelector{ simple_selectors: first, next: None }; + + while pseudo_element.is_none() { + let any_whitespace = skip_whitespace(iter); + let combinator = match iter.peek() { + None => break, // EOF + Some(&Comma) => break, + Some(&Delim('>')) => { iter.next(); Child }, + Some(&Delim('+')) => { iter.next(); NextSibling }, + Some(&Delim('~')) => { iter.next(); LaterSibling }, + Some(_) => { + if any_whitespace { Descendant } + else { return Err(()) } + } + }; + let (simple_selectors, pseudo) = try!(parse_simple_selectors(context, iter, namespaces)); + compound = CompoundSelector { + simple_selectors: simple_selectors, + next: Some((box compound, combinator)) + }; + pseudo_element = pseudo; + } + Ok(Selector { + specificity: compute_specificity(&compound, &pseudo_element), + compound_selectors: Arc::new(compound), + pseudo_element: pseudo_element, + }) +} + +/// Level 3: Parse **one** simple_selector +fn parse_negation(context: &ParserContext, + arguments: Vec<ComponentValue>, + namespaces: &NamespaceMap) + -> Result<SimpleSelector,()> { + let iter = &mut arguments.into_iter().peekable(); + match try!(parse_type_selector(iter, namespaces)) { + Some(type_selector) => Ok(Negation(type_selector)), + None => { + match try!(parse_one_simple_selector(context, + iter, + namespaces, + /* inside_negation = */ true)) { + Some(SimpleSelectorResult(simple_selector)) => { + Ok(Negation(vec![simple_selector])) + } + _ => Err(()) + } + }, + } +} + +/// simple_selector_sequence +/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]* +/// | [ HASH | class | attrib | pseudo | negation ]+ +/// +/// `Err(())` means invalid selector +fn parse_simple_selectors<I>(context: &ParserContext, + iter: &mut Iter<I>, + namespaces: &NamespaceMap) + -> Result<(Vec<SimpleSelector>, Option<PseudoElement>),()> + where I: Iterator<ComponentValue> { + let mut empty = true; + let mut simple_selectors = match try!(parse_type_selector(iter, namespaces)) { + None => vec![], + Some(s) => { empty = false; s } + }; + + let mut pseudo_element = None; + loop { + match try!(parse_one_simple_selector(context, + iter, + namespaces, + /* inside_negation = */ false)) { + None => break, + Some(SimpleSelectorResult(s)) => { simple_selectors.push(s); empty = false }, + Some(PseudoElementResult(p)) => { pseudo_element = Some(p); empty = false; break }, + } + } + if empty { + // An empty selector is invalid. + Err(()) + } else { + Ok((simple_selectors, pseudo_element)) + } +} + +fn parse_functional_pseudo_class(context: &ParserContext, + name: String, + arguments: Vec<ComponentValue>, + namespaces: &NamespaceMap, + inside_negation: bool) + -> Result<SimpleSelector,()> { + match name.as_slice().to_ascii_lower().as_slice() { +// "lang" => parse_lang(arguments), + "nth-child" => parse_nth(arguments.as_slice()).map(|(a, b)| NthChild(a, b)), + "nth-last-child" => parse_nth(arguments.as_slice()).map(|(a, b)| NthLastChild(a, b)), + "nth-of-type" => parse_nth(arguments.as_slice()).map(|(a, b)| NthOfType(a, b)), + "nth-last-of-type" => parse_nth(arguments.as_slice()).map(|(a, b)| NthLastOfType(a, b)), + "not" => { + if inside_negation { + Err(()) + } else { + parse_negation(context, arguments, namespaces) + } + } + _ => Err(()) + } +} + +/// Parse a simple selector other than a type selector. +/// +/// * `Err(())`: Invalid selector, abort +/// * `Ok(None)`: Not a simple selector, could be something else. `iter` was not consumed. +/// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element +fn parse_one_simple_selector<I>(context: &ParserContext, + iter: &mut Iter<I>, + namespaces: &NamespaceMap, + inside_negation: bool) + -> Result<Option<SimpleSelectorParseResult>,()> + where I: Iterator<ComponentValue> { + match iter.peek() { + Some(&IDHash(_)) => match iter.next() { + Some(IDHash(id)) => Ok(Some(SimpleSelectorResult( + IDSelector(Atom::from_slice(id.as_slice()))))), + _ => panic!("Implementation error, this should not happen."), + }, + Some(&Delim('.')) => { + iter.next(); + match iter.next() { + Some(Ident(class)) => Ok(Some(SimpleSelectorResult( + ClassSelector(Atom::from_slice(class.as_slice()))))), + _ => Err(()), + } + } + Some(&SquareBracketBlock(_)) => match iter.next() { + Some(SquareBracketBlock(content)) + => Ok(Some(SimpleSelectorResult(try!(parse_attribute_selector(content, namespaces))))), + _ => panic!("Implementation error, this should not happen."), + }, + Some(&Colon) => { + iter.next(); + match iter.next() { + Some(Ident(name)) => match parse_simple_pseudo_class(context, name.as_slice()) { + Err(()) => { + match name.as_slice().to_ascii_lower().as_slice() { + // Supported CSS 2.1 pseudo-elements only. + // ** Do not add to this list! ** + "before" => Ok(Some(PseudoElementResult(Before))), + "after" => Ok(Some(PseudoElementResult(After))), +// "first-line" => PseudoElementResult(FirstLine), +// "first-letter" => PseudoElementResult(FirstLetter), + _ => Err(()) + } + }, + Ok(result) => Ok(Some(SimpleSelectorResult(result))), + }, + Some(Function(name, arguments)) + => { + Ok(Some(SimpleSelectorResult(try!(parse_functional_pseudo_class( + context, + name, + arguments, + namespaces, + inside_negation))))) + } + Some(Colon) => { + match iter.next() { + Some(Ident(name)) + => Ok(Some(PseudoElementResult(try!(parse_pseudo_element(name))))), + _ => Err(()), + } + } + _ => Err(()), + } + } + _ => Ok(None), + } +} + +fn parse_simple_pseudo_class(context: &ParserContext, name: &str) -> Result<SimpleSelector,()> { match name.to_ascii_lower().as_slice() { "any-link" => Ok(AnyLink), "link" => Ok(Link), @@ -506,27 +575,12 @@ fn parse_simple_pseudo_class(name: &str) -> Result<SimpleSelector, ()> { "first-of-type" => Ok(FirstOfType), "last-of-type" => Ok(LastOfType), "only-of-type" => Ok(OnlyOfType), -// "empty" => Ok(Empty), - _ => Err(()) - } -} - - -fn parse_functional_pseudo_class(name: String, arguments: Vec<ComponentValue>, - namespaces: &NamespaceMap, inside_negation: bool) - -> Result<SimpleSelector, ()> { - match name.as_slice().to_ascii_lower().as_slice() { -// "lang" => parse_lang(arguments), - "nth-child" => parse_nth(arguments.as_slice()).map(|(a, b)| NthChild(a, b)), - "nth-last-child" => parse_nth(arguments.as_slice()).map(|(a, b)| NthLastChild(a, b)), - "nth-of-type" => parse_nth(arguments.as_slice()).map(|(a, b)| NthOfType(a, b)), - "nth-last-of-type" => parse_nth(arguments.as_slice()).map(|(a, b)| NthLastOfType(a, b)), - "not" => if inside_negation { Err(()) } else { parse_negation(arguments, namespaces) }, + "-servo-nonzero-border" if context.origin == UserAgentOrigin => Ok(ServoNonzeroBorder), +// "empty" => Ok(Empty), _ => Err(()) } } - fn parse_pseudo_element(name: String) -> Result<PseudoElement, ()> { match name.as_slice().to_ascii_lower().as_slice() { // All supported pseudo-elements @@ -551,21 +605,6 @@ fn parse_pseudo_element(name: String) -> Result<PseudoElement, ()> { //} -/// Level 3: Parse **one** simple_selector -fn parse_negation(arguments: Vec<ComponentValue>, namespaces: &NamespaceMap) - -> Result<SimpleSelector, ()> { - let iter = &mut arguments.into_iter().peekable(); - match try!(parse_type_selector(iter, namespaces)) { - Some(type_selector) => Ok(Negation(type_selector)), - None => { - match try!(parse_one_simple_selector(iter, namespaces, /* inside_negation = */ true)) { - Some(SimpleSelectorResult(simple_selector)) => Ok(Negation(vec![simple_selector])), - _ => Err(()) - } - }, - } -} - /// Assuming the next token is an ident, consume it and return its value #[inline] @@ -593,6 +632,7 @@ mod tests { use sync::Arc; use cssparser; use namespaces::NamespaceMap; + use selector_matching::AuthorOrigin; use string_cache::Atom; use super::*; @@ -601,7 +641,10 @@ mod tests { } fn parse_ns(input: &str, namespaces: &NamespaceMap) -> Result<Vec<Selector>, ()> { - parse_selector_list(cssparser::tokenize(input).map(|(v, _)| v), namespaces) + let context = ParserContext { + origin: AuthorOrigin, + }; + parse_selector_list(&context, cssparser::tokenize(input).map(|(v, _)| v), namespaces) } fn specificity(a: u32, b: u32, c: u32) -> u32 { diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs index efec013ee2d..824172af037 100644 --- a/components/style/stylesheets.rs +++ b/components/style/stylesheets.rs @@ -10,12 +10,11 @@ use encoding::EncodingRef; use cssparser::{decode_stylesheet_bytes, tokenize, parse_stylesheet_rules, ToCss}; use cssparser::ast::*; -use selectors; +use selectors::{mod, ParserContext}; use properties; use errors::{ErrorLoggerIterator, log_css_error}; use namespaces::{NamespaceMap, parse_namespace_rule}; -use media_queries::{Device, MediaRule, parse_media_rule}; -use media_queries; +use media_queries::{mod, Device, MediaRule}; use font_face::{FontFaceRule, Source, parse_font_face_rule, iter_font_face_rules_inner}; use selector_matching::StylesheetOrigin; @@ -53,9 +52,12 @@ impl Stylesheet { Stylesheet::from_bytes(bytes.as_slice(), base_url, protocol_encoding_label, environment_encoding, origin) } - pub fn from_bytes( - bytes: &[u8], base_url: Url, protocol_encoding_label: Option<&str>, - environment_encoding: Option<EncodingRef>, origin: StylesheetOrigin) -> Stylesheet { + pub fn from_bytes(bytes: &[u8], + base_url: Url, + protocol_encoding_label: Option<&str>, + environment_encoding: Option<EncodingRef>, + origin: StylesheetOrigin) + -> Stylesheet { // TODO: bytes.as_slice could be bytes.container_as_bytes() let (string, _) = decode_stylesheet_bytes( bytes.as_slice(), protocol_encoding_label, environment_encoding); @@ -67,6 +69,11 @@ impl Stylesheet { static STATE_IMPORTS: uint = 2; static STATE_NAMESPACES: uint = 3; static STATE_BODY: uint = 4; + + let parser_context = ParserContext { + origin: origin, + }; + let mut state: uint = STATE_CHARSET; let mut rules = vec!(); @@ -77,7 +84,7 @@ impl Stylesheet { match rule { QualifiedRule_(rule) => { next_state = STATE_BODY; - parse_style_rule(rule, &mut rules, &namespaces, &base_url) + parse_style_rule(&parser_context, rule, &mut rules, &namespaces, &base_url) }, AtRule_(rule) => { let lower_name = rule.name.as_slice().to_ascii_lower(); @@ -114,7 +121,12 @@ impl Stylesheet { }, _ => { next_state = STATE_BODY; - parse_nested_at_rule(lower_name.as_slice(), rule, &mut rules, &namespaces, &base_url) + parse_nested_at_rule(&parser_context, + lower_name.as_slice(), + rule, + &mut rules, + &namespaces, + &base_url) }, } }, @@ -128,13 +140,36 @@ impl Stylesheet { } } +// lower_name is passed explicitly to avoid computing it twice. +pub fn parse_nested_at_rule(context: &ParserContext, + lower_name: &str, + rule: AtRule, + parent_rules: &mut Vec<CSSRule>, + namespaces: &NamespaceMap, + base_url: &Url) { + match lower_name { + "media" => { + media_queries::parse_media_rule(context, rule, parent_rules, namespaces, base_url) + } + "font-face" => parse_font_face_rule(rule, parent_rules, base_url), + _ => log_css_error(rule.location, + format!("Unsupported at-rule: @{:s}", lower_name).as_slice()) + } +} -pub fn parse_style_rule(rule: QualifiedRule, parent_rules: &mut Vec<CSSRule>, - namespaces: &NamespaceMap, base_url: &Url) { - let QualifiedRule { location, prelude, block} = rule; +pub fn parse_style_rule(context: &ParserContext, + rule: QualifiedRule, + parent_rules: &mut Vec<CSSRule>, + namespaces: &NamespaceMap, + base_url: &Url) { + let QualifiedRule { + location, + prelude, + block + } = rule; // FIXME: avoid doing this for valid selectors let serialized = prelude.iter().to_css(); - match selectors::parse_selector_list(prelude.into_iter(), namespaces) { + match selectors::parse_selector_list(context, prelude.into_iter(), namespaces) { Ok(selectors) => parent_rules.push(CSSStyleRule(StyleRule{ selectors: selectors, declarations: properties::parse_property_declaration_list(block.into_iter(), base_url) @@ -144,19 +179,6 @@ pub fn parse_style_rule(rule: QualifiedRule, parent_rules: &mut Vec<CSSRule>, } } - -// lower_name is passed explicitly to avoid computing it twice. -pub fn parse_nested_at_rule(lower_name: &str, rule: AtRule, - parent_rules: &mut Vec<CSSRule>, namespaces: &NamespaceMap, base_url: &Url) { - match lower_name { - "media" => parse_media_rule(rule, parent_rules, namespaces, base_url), - "font-face" => parse_font_face_rule(rule, parent_rules, base_url), - _ => log_css_error(rule.location, - format!("Unsupported at-rule: @{:s}", lower_name).as_slice()) - } -} - - pub fn iter_style_rules<'a>(rules: &[CSSRule], device: &media_queries::Device, callback: |&StyleRule|) { for rule in rules.iter() { diff --git a/components/util/Cargo.toml b/components/util/Cargo.toml index 29fc71d25a5..331fd2fb840 100644 --- a/components/util/Cargo.toml +++ b/components/util/Cargo.toml @@ -7,6 +7,9 @@ authors = ["The Servo Project Developers"] name = "util" path = "lib.rs" +[dependencies.cssparser] +git = "https://github.com/servo/rust-cssparser" + [dependencies.geom] git = "https://github.com/servo/rust-geom" diff --git a/components/util/lib.rs b/components/util/lib.rs index f556dda6eb9..b0e4cff8c62 100644 --- a/components/util/lib.rs +++ b/components/util/lib.rs @@ -13,6 +13,7 @@ extern crate log; extern crate alloc; extern crate collections; +extern crate cssparser; extern crate geom; extern crate getopts; extern crate layers; diff --git a/components/util/str.rs b/components/util/str.rs index 28aedee4f30..7b2cfdf5107 100644 --- a/components/util/str.rs +++ b/components/util/str.rs @@ -4,6 +4,8 @@ use geometry::Au; +use cssparser::{mod, RGBA, RGBAColor}; +use std::ascii::AsciiExt; use std::from_str::FromStr; use std::iter::Filter; use std::str::{CharEq, CharSplits}; @@ -61,7 +63,8 @@ pub static HTML_SPACE_CHARACTERS: StaticCharVec = &[ '\u000d', ]; -pub fn split_html_space_chars<'a>(s: &'a str) -> Filter<'a, &'a str, CharSplits<'a, StaticCharVec>> { +pub fn split_html_space_chars<'a>(s: &'a str) + -> Filter<'a, &'a str, CharSplits<'a, StaticCharVec>> { s.split(HTML_SPACE_CHARACTERS).filter(|&split| !split.is_empty()) } @@ -76,7 +79,6 @@ fn do_parse_integer<T: Iterator<char>>(input: T) -> Option<i64> { } } - let mut input = input.skip_while(|c| { HTML_SPACE_CHARACTERS.iter().any(|s| s == c) }).peekable(); @@ -184,6 +186,137 @@ pub fn parse_length(mut value: &str) -> LengthOrPercentageOrAuto { } } +/// Parses a legacy color per HTML5 § 2.4.6. If unparseable, `Err` is returned. +pub fn parse_legacy_color(mut input: &str) -> Result<RGBA,()> { + // Steps 1 and 2. + if input.len() == 0 { + return Err(()) + } + + // Step 3. + input = input.trim_left_chars(Whitespace).trim_right_chars(Whitespace); + + // Step 4. + if input.eq_ignore_ascii_case("transparent") { + return Err(()) + } + + // Step 5. + match cssparser::parse_color_keyword(input) { + Ok(RGBAColor(rgba)) => return Ok(rgba), + _ => {} + } + + // Step 6. + if input.len() == 4 { + match (input.as_bytes()[0], + hex(input.as_bytes()[1] as char), + hex(input.as_bytes()[2] as char), + hex(input.as_bytes()[3] as char)) { + (b'#', Ok(r), Ok(g), Ok(b)) => { + return Ok(RGBA { + red: (r as f32) * 17.0 / 255.0, + green: (g as f32) * 17.0 / 255.0, + blue: (b as f32) * 17.0 / 255.0, + alpha: 1.0, + }) + } + _ => {} + } + } + + // Step 7. + let mut new_input = String::new(); + for ch in input.chars() { + if ch as u32 > 0xffff { + new_input.push_str("00") + } else { + new_input.push(ch) + } + } + let mut input = new_input.as_slice(); + + // Step 8. + for (char_count, (index, _)) in input.char_indices().enumerate() { + if char_count == 128 { + input = input.slice_to(index); + break + } + } + + // Step 9. + if input.char_at(0) == '#' { + input = input.slice_from(1) + } + + // Step 10. + let mut new_input = Vec::new(); + for ch in input.chars() { + if hex(ch).is_ok() { + new_input.push(ch as u8) + } else { + new_input.push(b'0') + } + } + let mut input = new_input; + + // Step 11. + while input.len() == 0 || (input.len() % 3) != 0 { + input.push(b'0') + } + + // Step 12. + let mut length = input.len() / 3; + let (mut red, mut green, mut blue) = (input.slice_to(length), + input.slice(length, length * 2), + input.slice_from(length * 2)); + + // Step 13. + if length > 8 { + red = red.slice_from(length - 8); + green = green.slice_from(length - 8); + blue = blue.slice_from(length - 8); + length = 8 + } + + // Step 14. + while length > 2 && red[0] == b'0' && green[0] == b'0' && blue[0] == b'0' { + red = red.slice_from(1); + green = green.slice_from(1); + blue = blue.slice_from(1); + length -= 1 + } + + // Steps 15-20. + return Ok(RGBA { + red: hex_string(red).unwrap() as f32 / 255.0, + green: hex_string(green).unwrap() as f32 / 255.0, + blue: hex_string(blue).unwrap() as f32 / 255.0, + alpha: 1.0, + }); + + fn hex(ch: char) -> Result<u8,()> { + match ch { + '0'...'9' => Ok((ch as u8) - b'0'), + 'a'...'f' => Ok((ch as u8) - b'a' + 10), + 'A'...'F' => Ok((ch as u8) - b'A' + 10), + _ => Err(()), + } + } + + fn hex_string(string: &[u8]) -> Result<u8,()> { + match string.len() { + 0 => Err(()), + 1 => hex(string[0] as char), + _ => { + let upper = try!(hex(string[0] as char)); + let lower = try!(hex(string[1] as char)); + Ok((upper << 4) | lower) + } + } + } +} + #[deriving(Clone, Eq, PartialEq, Hash, Show)] pub struct LowercaseString { |