servo: Merge #10252 - Implement ::selection pseudo-element (from emilio:selection); r=mbrubeck
authorEmilio Cobos Álvarez <ecoal95@gmail.com>
Thu, 31 Mar 2016 00:17:37 +0500
changeset 338363 f44874fd3f4624bbf39d878ed74f117f28d1a731
parent 338362 b160f37cb18c51440b9571ddc580c6e54e4df8e6
child 338364 0ddaf47f66a983366e11e5ac97045169fcbe31d2
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck
servo: Merge #10252 - Implement ::selection pseudo-element (from emilio:selection); r=mbrubeck It only supports `color` and `background`, for now, but it shouldn't be hard to add more properties (like text-shadow). r? @mbrubeck Source-Repo: https://github.com/servo/servo Source-Revision: 723989b9dddeb9bcdc28dc7d640fd6fd7247a27f
servo/components/layout/construct.rs
servo/components/layout/display_list_builder.rs
servo/components/layout/flow.rs
servo/components/layout/fragment.rs
servo/components/layout/generated_content.rs
servo/components/layout/inline.rs
servo/components/layout/query.rs
servo/components/layout/text.rs
servo/components/layout/wrapper.rs
servo/components/style/selector_impl.rs
servo/tests/unit/layout/size_of.rs
servo/tests/unit/util/lib.rs
--- a/servo/components/layout/construct.rs
+++ b/servo/components/layout/construct.rs
@@ -212,16 +212,17 @@ impl InlineFragmentsAccumulator {
     fn from_inline_node<N>(node: &N) -> InlineFragmentsAccumulator
             where N: ThreadSafeLayoutNode {
         InlineFragmentsAccumulator {
             fragments: IntermediateInlineFragments::new(),
             enclosing_node: Some(InlineFragmentNodeInfo {
                 address: node.opaque(),
                 pseudo: node.get_pseudo_element_type().strip(),
                 style: node.style().clone(),
+                selected_style: node.selected_style().clone(),
                 flags: InlineFragmentNodeFlags::empty(),
             }),
             bidi_control_chars: None,
             restyle_damage: node.restyle_damage(),
         }
     }
 
     fn push(&mut self, fragment: Fragment) {
@@ -355,43 +356,46 @@ impl<'a, ConcreteThreadSafeLayoutNode: T
         }
 
         if child.is_table_cell() {
             let mut style = child_node.style().clone();
             properties::modify_style_for_anonymous_table_object(&mut style, display::T::table_row);
             let fragment = Fragment::from_opaque_node_and_style(child_node.opaque(),
                                                                 PseudoElementType::Normal,
                                                                 style,
+                                                                child_node.selected_style().clone(),
                                                                 child_node.restyle_damage(),
                                                                 SpecificFragmentInfo::TableRow);
             let mut new_child: FlowRef = Arc::new(TableRowFlow::from_fragment(fragment));
             new_child.add_new_child(child.clone());
             child.finish();
             *child = new_child
         }
         if child.is_table_row() || child.is_table_rowgroup() {
             let mut style = child_node.style().clone();
             properties::modify_style_for_anonymous_table_object(&mut style, display::T::table);
             let fragment = Fragment::from_opaque_node_and_style(child_node.opaque(),
                                                                 PseudoElementType::Normal,
                                                                 style,
+                                                                child_node.selected_style().clone(),
                                                                 child_node.restyle_damage(),
                                                                 SpecificFragmentInfo::Table);
             let mut new_child: FlowRef = Arc::new(TableFlow::from_fragment(fragment));
             new_child.add_new_child(child.clone());
             child.finish();
             *child = new_child
         }
         if child.is_table() {
             let mut style = child_node.style().clone();
             properties::modify_style_for_anonymous_table_object(&mut style, display::T::table);
             let fragment =
                 Fragment::from_opaque_node_and_style(child_node.opaque(),
                                                      PseudoElementType::Normal,
                                                      style,
+                                                     child_node.selected_style().clone(),
                                                      child_node.restyle_damage(),
                                                      SpecificFragmentInfo::TableWrapper);
             let mut new_child: FlowRef = Arc::new(TableWrapperFlow::from_fragment(fragment, None));
             new_child.add_new_child(child.clone());
             child.finish();
             *child = new_child
         }
     }
@@ -579,16 +583,17 @@ impl<'a, ConcreteThreadSafeLayoutNode: T
                 // between block elements, and retained when between inline elements.
                 let fragment_info = SpecificFragmentInfo::UnscannedText(
                     box UnscannedTextFragmentInfo::new(" ".to_owned(), None));
                 properties::modify_style_for_replaced_content(&mut whitespace_style);
                 properties::modify_style_for_text(&mut whitespace_style);
                 let fragment = Fragment::from_opaque_node_and_style(whitespace_node,
                                                                     whitespace_pseudo,
                                                                     whitespace_style,
+                                                                    node.selected_style().clone(),
                                                                     whitespace_damage,
                                                                     fragment_info);
                 inline_fragment_accumulator.fragments.fragments.push_back(fragment);
             }
             ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment(_)) => {
                 // TODO: Implement anonymous table objects for missing parents
                 // CSS 2.1 § 17.2.1, step 3-2
             }
@@ -708,24 +713,27 @@ impl<'a, ConcreteThreadSafeLayoutNode: T
         if text_content.is_empty() {
             return
         }
 
         let selection = node.selection();
         let mut style = (*style).clone();
         properties::modify_style_for_text(&mut style);
 
+        let selected_style = node.selected_style();
+
         match text_content {
             TextContent::Text(string) => {
                 let info = box UnscannedTextFragmentInfo::new(string, selection);
                 let specific_fragment_info = SpecificFragmentInfo::UnscannedText(info);
                 fragments.fragments.push_back(Fragment::from_opaque_node_and_style(
                         node.opaque(),
                         node.get_pseudo_element_type().strip(),
-                        style.clone(),
+                        style,
+                        selected_style.clone(),
                         node.restyle_damage(),
                         specific_fragment_info))
             }
             TextContent::GeneratedContent(content_items) => {
                 for content_item in content_items.into_iter() {
                     let specific_fragment_info = match content_item {
                         ContentItem::String(string) => {
                             let info = box UnscannedTextFragmentInfo::new(string, None);
@@ -735,16 +743,17 @@ impl<'a, ConcreteThreadSafeLayoutNode: T
                             let content_item = box GeneratedContentInfo::ContentItem(content_item);
                             SpecificFragmentInfo::GeneratedContent(content_item)
                         }
                     };
                     fragments.fragments.push_back(Fragment::from_opaque_node_and_style(
                             node.opaque(),
                             node.get_pseudo_element_type().strip(),
                             style.clone(),
+                            selected_style.clone(),
                             node.restyle_damage(),
                             specific_fragment_info))
                 }
             }
         }
     }
 
     /// Builds a flow for a node with `display: block`. This yields a `BlockFlow` with possibly
@@ -818,23 +827,25 @@ impl<'a, ConcreteThreadSafeLayoutNode: T
                         };
                         opt_inline_block_splits.push_back(split);
                         abs_descendants.push_descendants(kid_abs_descendants);
                     } else {
                         // Push the absolutely-positioned kid as an inline containing block.
                         let kid_node = flow.as_block().fragment.node;
                         let kid_pseudo = flow.as_block().fragment.pseudo.clone();
                         let kid_style = flow.as_block().fragment.style.clone();
+                        let kid_selected_style = flow.as_block().fragment.selected_style.clone();
                         let kid_restyle_damage = flow.as_block().fragment.restyle_damage;
                         let fragment_info = SpecificFragmentInfo::InlineAbsolute(
                             InlineAbsoluteFragmentInfo::new(flow));
                         fragment_accumulator.push(Fragment::from_opaque_node_and_style(
                                 kid_node,
                                 kid_pseudo,
                                 kid_style,
+                                kid_selected_style,
                                 kid_restyle_damage,
                                 fragment_info));
                         fragment_accumulator.fragments
                                             .absolute_descendants
                                             .push_descendants(kid_abs_descendants);
                     }
                 }
                 ConstructionResult::ConstructionItem(ConstructionItem::InlineFragments(
@@ -860,16 +871,17 @@ impl<'a, ConcreteThreadSafeLayoutNode: T
                     // Instantiate the whitespace fragment.
                     let fragment_info = SpecificFragmentInfo::UnscannedText(
                         box UnscannedTextFragmentInfo::new(" ".to_owned(), None));
                     properties::modify_style_for_replaced_content(&mut whitespace_style);
                     properties::modify_style_for_text(&mut whitespace_style);
                     let fragment = Fragment::from_opaque_node_and_style(whitespace_node,
                                                                         whitespace_pseudo,
                                                                         whitespace_style,
+                                                                        node.selected_style().clone(),
                                                                         whitespace_damage,
                                                                         fragment_info);
                     fragment_accumulator.fragments.fragments.push_back(fragment)
                 }
                 ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment(_)) => {
                     // TODO: Implement anonymous table objects for missing parents
                     // CSS 2.1 § 17.2.1, step 3-2
                 }
@@ -957,16 +969,17 @@ impl<'a, ConcreteThreadSafeLayoutNode: T
 
         let mut modified_style = (*node.style()).clone();
         properties::modify_style_for_outer_inline_block_fragment(&mut modified_style);
         let fragment_info = SpecificFragmentInfo::InlineBlock(InlineBlockFragmentInfo::new(
                 block_flow));
         let fragment = Fragment::from_opaque_node_and_style(node.opaque(),
                                                             node.get_pseudo_element_type().strip(),
                                                             modified_style.clone(),
+                                                            node.selected_style().clone(),
                                                             node.restyle_damage(),
                                                             fragment_info);
 
         let mut fragment_accumulator = InlineFragmentsAccumulator::new();
         fragment_accumulator.fragments.fragments.push_back(fragment);
         fragment_accumulator.fragments.absolute_descendants.push_descendants(abs_descendants);
 
         let construction_item =
@@ -989,16 +1002,17 @@ impl<'a, ConcreteThreadSafeLayoutNode: T
 
         let fragment_info = SpecificFragmentInfo::InlineAbsoluteHypothetical(
             InlineAbsoluteHypotheticalFragmentInfo::new(block_flow));
         let mut style = node.style().clone();
         properties::modify_style_for_inline_absolute_hypothetical_fragment(&mut style);
         let fragment = Fragment::from_opaque_node_and_style(node.opaque(),
                                                             PseudoElementType::Normal,
                                                             style,
+                                                            node.selected_style().clone(),
                                                             node.restyle_damage(),
                                                             fragment_info);
 
         let mut fragment_accumulator = InlineFragmentsAccumulator::from_inline_node(node);
         fragment_accumulator.fragments.fragments.push_back(fragment);
         fragment_accumulator.fragments.absolute_descendants.push_descendants(abs_descendants);
 
         let construction_item =
@@ -1848,12 +1862,13 @@ fn control_chars_to_fragment(node: &Inli
                              restyle_damage: RestyleDamage)
                              -> Fragment {
     let info = SpecificFragmentInfo::UnscannedText(
         box UnscannedTextFragmentInfo::new(String::from(text), None));
     let mut style = node.style.clone();
     properties::modify_style_for_text(&mut style);
     Fragment::from_opaque_node_and_style(node.address,
                                          node.pseudo,
-                                         style,
+                                         style.clone(),
+                                         node.selected_style.clone(),
                                          restyle_damage,
                                          info)
 }
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -99,20 +99,16 @@ impl<'a> DisplayListBuildState<'a> {
         self.stacking_context_id_stack.pop();
         assert!(!self.stacking_context_id_stack.is_empty());
     }
 }
 
 /// The logical width of an insertion point: at the moment, a one-pixel-wide line.
 const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(1 * AU_PER_PX);
 
-// Colors for selected text.  TODO (#8077): Use the ::selection pseudo-element to set these.
-const SELECTION_FOREGROUND_COLOR: RGBA = RGBA { red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0 };
-const SELECTION_BACKGROUND_COLOR: RGBA = RGBA { red: 0.69, green: 0.84, blue: 1.0, alpha: 1.0 };
-
 // TODO(gw): The transforms spec says that perspective length must
 // be positive. However, there is some confusion between the spec
 // and browser implementations as to handling the case of 0 for the
 // perspective value. Until the spec bug is resolved, at least ensure
 // that a provided perspective value of <= 0.0 doesn't cause panics
 // and behaves as it does in other browsers.
 // See https://lists.w3.org/Archives/Public/www-style/2016Jan/0020.html for more details.
 #[inline]
@@ -926,24 +922,26 @@ impl FragmentDisplayListBuilding for Fra
             }
             _ => return,
         };
 
         // Draw a highlighted background if the text is selected.
         //
         // TODO: Allow non-text fragments to be selected too.
         if scanned_text_fragment_info.selected() {
+            let style = self.selected_style();
+            let background_color = style.resolve_color(style.get_background().background_color);
             state.add_display_item(
                 DisplayItem::SolidColorClass(box SolidColorDisplayItem {
                     base: BaseDisplayItem::new(stacking_relative_border_box,
                                                DisplayItemMetadata::new(self.node,
                                                                         &*self.style,
                                                                         Cursor::DefaultCursor),
                                                &clip),
-                    color: SELECTION_BACKGROUND_COLOR.to_gfx_color()
+                    color: background_color.to_gfx_color(),
             }), display_list_section);
         }
 
         // Draw a caret at the insertion point.
         let insertion_point_index = match scanned_text_fragment_info.insertion_point {
             Some(insertion_point_index) => insertion_point_index,
             None => return,
         };
@@ -1111,21 +1109,24 @@ impl FragmentDisplayListBuilding for Fra
             self.stacking_relative_content_box(stacking_relative_border_box);
 
         match self.specific {
             SpecificFragmentInfo::ScannedText(ref text_fragment) => {
                 // Create items for shadows.
                 //
                 // NB: According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front
                 // to back).
+
+                // TODO(emilio): Allow changing more properties by ::selection
                 let text_color = if text_fragment.selected() {
-                    SELECTION_FOREGROUND_COLOR
+                    self.selected_style().get_color().color
                 } else {
                     self.style().get_color().color
                 };
+
                 for text_shadow in self.style.get_effects().text_shadow.0.iter().rev() {
                     let offset = &Point2D::new(text_shadow.offset_x, text_shadow.offset_y);
                     let color = self.style().resolve_color(text_shadow.color);
                     self.build_display_list_for_text_fragment(state,
                                                               &**text_fragment,
                                                               color,
                                                               &stacking_relative_content_box,
                                                               Some(text_shadow.blur_radius),
--- a/servo/components/layout/flow.rs
+++ b/servo/components/layout/flow.rs
@@ -1281,38 +1281,41 @@ impl<'a> ImmutableFlowUtils for &'a Flow
             FlowClass::Table | FlowClass::TableRowGroup => {
                 properties::modify_style_for_anonymous_table_object(
                     &mut style,
                     display::T::table_row);
                 let fragment = Fragment::from_opaque_node_and_style(
                     node.opaque(),
                     PseudoElementType::Normal,
                     style,
+                    node.selected_style().clone(),
                     node.restyle_damage(),
                     SpecificFragmentInfo::TableRow);
                 Arc::new(TableRowFlow::from_fragment(fragment))
             },
             FlowClass::TableRow => {
                 properties::modify_style_for_anonymous_table_object(
                     &mut style,
                     display::T::table_cell);
                 let fragment = Fragment::from_opaque_node_and_style(
                     node.opaque(),
                     PseudoElementType::Normal,
                     style,
+                    node.selected_style().clone(),
                     node.restyle_damage(),
                     SpecificFragmentInfo::TableCell);
                 let hide = node.style().get_inheritedtable().empty_cells == empty_cells::T::hide;
                 Arc::new(TableCellFlow::from_node_fragment_and_visibility_flag(node, fragment, !hide))
             },
             FlowClass::Flex => {
                 let fragment =
                     Fragment::from_opaque_node_and_style(node.opaque(),
                                                          PseudoElementType::Normal,
                                                          style,
+                                                         node.selected_style().clone(),
                                                          node.restyle_damage(),
                                                          SpecificFragmentInfo::Generic);
                 Arc::new(BlockFlow::from_fragment(fragment, None))
             },
             _ => {
                 panic!("no need to generate a missing child")
             }
         }
--- a/servo/components/layout/fragment.rs
+++ b/servo/components/layout/fragment.rs
@@ -80,16 +80,19 @@ use wrapper::{PseudoElementType, ThreadS
 #[derive(Clone)]
 pub struct Fragment {
     /// An opaque reference to the DOM node that this `Fragment` originates from.
     pub node: OpaqueNode,
 
     /// The CSS style of this fragment.
     pub style: Arc<ServoComputedValues>,
 
+    /// The CSS style of this fragment when it's selected
+    pub selected_style: Arc<ServoComputedValues>,
+
     /// The position of this fragment relative to its owning flow. The size includes padding and
     /// border, but not margin.
     ///
     /// NB: This does not account for relative positioning.
     /// NB: Collapsed borders are not included in this.
     pub border_box: LogicalRect<Au>,
 
     /// The sum of border and padding; i.e. the distance from the edge of the border box to the
@@ -793,16 +796,17 @@ impl Fragment {
         let writing_mode = style.writing_mode;
 
         let mut restyle_damage = node.restyle_damage();
         restyle_damage.remove(RECONSTRUCT_FLOW);
 
         Fragment {
             node: node.opaque(),
             style: style,
+            selected_style: node.selected_style().clone(),
             restyle_damage: restyle_damage,
             border_box: LogicalRect::zero(writing_mode),
             border_padding: LogicalMargin::zero(writing_mode),
             margin: LogicalMargin::zero(writing_mode),
             specific: specific,
             inline_context: None,
             pseudo: node.get_pseudo_element_type().strip(),
             flags: FragmentFlags::empty(),
@@ -810,26 +814,28 @@ impl Fragment {
             stacking_context_id: StackingContextId::new(0),
         }
     }
 
     /// Constructs a new `Fragment` instance from an opaque node.
     pub fn from_opaque_node_and_style(node: OpaqueNode,
                                       pseudo: PseudoElementType<()>,
                                       style: Arc<ServoComputedValues>,
+                                      selected_style: Arc<ServoComputedValues>,
                                       mut restyle_damage: RestyleDamage,
                                       specific: SpecificFragmentInfo)
                                       -> Fragment {
         let writing_mode = style.writing_mode;
 
         restyle_damage.remove(RECONSTRUCT_FLOW);
 
         Fragment {
             node: node,
             style: style,
+            selected_style: selected_style,
             restyle_damage: restyle_damage,
             border_box: LogicalRect::zero(writing_mode),
             border_padding: LogicalMargin::zero(writing_mode),
             margin: LogicalMargin::zero(writing_mode),
             specific: specific,
             inline_context: None,
             pseudo: pseudo,
             flags: FragmentFlags::empty(),
@@ -853,16 +859,17 @@ impl Fragment {
                                                           size);
 
         let mut restyle_damage = RestyleDamage::rebuild_and_reflow();
         restyle_damage.remove(RECONSTRUCT_FLOW);
 
         Fragment {
             node: self.node,
             style: self.style.clone(),
+            selected_style: self.selected_style.clone(),
             restyle_damage: restyle_damage,
             border_box: new_border_box,
             border_padding: self.border_padding,
             margin: self.margin,
             specific: info,
             inline_context: self.inline_context.clone(),
             pseudo: self.pseudo.clone(),
             flags: FragmentFlags::empty(),
@@ -1281,16 +1288,21 @@ impl Fragment {
         }
     }
 
     #[inline(always)]
     pub fn style(&self) -> &ServoComputedValues {
         &*self.style
     }
 
+    #[inline(always)]
+    pub fn selected_style(&self) -> &ServoComputedValues {
+        &*self.selected_style
+    }
+
     pub fn white_space(&self) -> white_space::T {
         self.style().get_inheritedtext().white_space
     }
 
     /// Returns the text decoration of this fragment, according to the style of the nearest ancestor
     /// element.
     ///
     /// NB: This may not be the actual text decoration, because of the override rules specified in
--- a/servo/components/layout/generated_content.rs
+++ b/servo/components/layout/generated_content.rs
@@ -433,16 +433,17 @@ fn render_text(layout_context: &LayoutCo
                style: Arc<ServoComputedValues>,
                string: String)
                -> Option<SpecificFragmentInfo> {
     let mut fragments = LinkedList::new();
     let info = SpecificFragmentInfo::UnscannedText(
         box UnscannedTextFragmentInfo::new(string, None));
     fragments.push_back(Fragment::from_opaque_node_and_style(node,
                                                              pseudo,
+                                                             style.clone(),
                                                              style,
                                                              RestyleDamage::rebuild_and_reflow(),
                                                              info));
     // FIXME(pcwalton): This should properly handle multiple marker fragments. This could happen
     // due to text run splitting.
     let fragments = TextRunScanner::new().scan_for_runs(&mut layout_context.font_context(),
                                                         fragments);
     if fragments.is_empty() {
--- a/servo/components/layout/inline.rs
+++ b/servo/components/layout/inline.rs
@@ -1849,16 +1849,17 @@ impl fmt::Debug for InlineFlow {
                flow::base(self))
     }
 }
 
 #[derive(Clone)]
 pub struct InlineFragmentNodeInfo {
     pub address: OpaqueNode,
     pub style: Arc<ServoComputedValues>,
+    pub selected_style: Arc<ServoComputedValues>,
     pub pseudo: PseudoElementType<()>,
     pub flags: InlineFragmentNodeFlags,
 }
 
 bitflags! {
     flags InlineFragmentNodeFlags: u8 {
         const FIRST_FRAGMENT_OF_ELEMENT = 0x01,
         const LAST_FRAGMENT_OF_ELEMENT = 0x02,
--- a/servo/components/layout/query.rs
+++ b/servo/components/layout/query.rs
@@ -529,21 +529,22 @@ pub fn process_node_scroll_area_request<
 }
 
 /// Return the resolved value of property for a given (pseudo)element.
 /// https://drafts.csswg.org/cssom/#resolved-value
 pub fn process_resolved_style_request<N: LayoutNode>(
             requested_node: N, pseudo: &Option<PseudoElement>,
             property: &Atom, layout_root: &mut FlowRef) -> Option<String> {
     let layout_node = requested_node.to_threadsafe();
-    let layout_node = match pseudo {
-        &Some(PseudoElement::Before) => layout_node.get_before_pseudo(),
-        &Some(PseudoElement::After) => layout_node.get_after_pseudo(),
-        &Some(PseudoElement::DetailsSummary) => layout_node.get_details_summary_pseudo(),
-        &Some(PseudoElement::DetailsContent) => layout_node.get_details_content_pseudo(),
+    let layout_node = match *pseudo {
+        Some(PseudoElement::Before) => layout_node.get_before_pseudo(),
+        Some(PseudoElement::After) => layout_node.get_after_pseudo(),
+        Some(PseudoElement::DetailsSummary) => layout_node.get_details_summary_pseudo(),
+        Some(PseudoElement::DetailsContent) => layout_node.get_details_content_pseudo(),
+        Some(PseudoElement::Selection) => None,
         _ => Some(layout_node)
     };
 
     let layout_node = match layout_node {
         None => {
             // The pseudo doesn't exist, return nothing.  Chrome seems to query
             // the element itself in this case, Firefox uses the resolved value.
             // https://www.w3.org/Bugs/Public/show_bug.cgi?id=29006
--- a/servo/components/layout/text.rs
+++ b/servo/components/layout/text.rs
@@ -363,16 +363,17 @@ impl TextRunScanner {
                 let new_metrics = new_text_fragment_info.run.metrics_for_range(&mapping.char_range);
                 let writing_mode = old_fragment.style.writing_mode;
                 let bounding_box_size = bounding_box_for_run_metrics(&new_metrics, writing_mode);
                 new_text_fragment_info.content_size = bounding_box_size;
 
                 let new_fragment = old_fragment.transform(
                     bounding_box_size,
                     SpecificFragmentInfo::ScannedText(new_text_fragment_info));
+
                 out_fragments.push(new_fragment)
             }
         }
 
         last_whitespace
     }
 }
 
--- a/servo/components/layout/wrapper.rs
+++ b/servo/components/layout/wrapper.rs
@@ -697,37 +697,40 @@ pub trait ThreadSafeLayoutNode : Clone +
         self.borrow_layout_data().unwrap()
             .style_data.per_pseudo
             .get(&PseudoElement::After)
             .map(|style| {
                 self.with_pseudo(PseudoElementType::After(style.get_box().display))
             })
     }
 
+    // TODO(emilio): Since the ::-details-* pseudos are internal, just affecting one element, and
+    // only changing `display` property when the element `open` attribute changes, this should be
+    // eligible for not being cascaded eagerly, reading the display property from layout instead.
     #[inline]
     fn get_details_summary_pseudo(&self) -> Option<Self> {
         if self.is_element() &&
-                self.as_element().get_local_name() == &atom!("details") &&
-                self.as_element().get_namespace() == &ns!(html) {
+           self.as_element().get_local_name() == &atom!("details") &&
+           self.as_element().get_namespace() == &ns!(html) {
             self.borrow_layout_data().unwrap()
                 .style_data.per_pseudo
                 .get(&PseudoElement::DetailsSummary)
                 .map(|style| {
                     self.with_pseudo(PseudoElementType::DetailsSummary(style.get_box().display))
                 })
         } else {
             None
         }
     }
 
     #[inline]
     fn get_details_content_pseudo(&self) -> Option<Self> {
         if self.is_element() &&
-                self.as_element().get_local_name() == &atom!("details") &&
-                self.as_element().get_namespace() == &ns!(html) {
+           self.as_element().get_local_name() == &atom!("details") &&
+           self.as_element().get_namespace() == &ns!(html) {
             self.borrow_layout_data().unwrap()
                 .style_data.per_pseudo
                 .get(&PseudoElement::DetailsContent)
                 .map(|style| {
                     self.with_pseudo(PseudoElementType::DetailsContent(style.get_box().display))
                 })
         } else {
             None
@@ -759,16 +762,23 @@ pub trait ThreadSafeLayoutNode : Clone +
                 PseudoElementType::DetailsSummary(_) => data.style_data.per_pseudo.get(&PseudoElement::DetailsSummary),
                 PseudoElementType::DetailsContent(_) => data.style_data.per_pseudo.get(&PseudoElement::DetailsContent),
                 PseudoElementType::Normal => data.style_data.style.as_ref(),
             };
             style.unwrap()
         })
     }
 
+    #[inline]
+    fn selected_style(&self) -> Ref<Arc<ServoComputedValues>> {
+        Ref::map(self.borrow_layout_data().unwrap(), |data| {
+            data.style_data.per_pseudo.get(&PseudoElement::Selection).unwrap_or(data.style_data.style.as_ref().unwrap())
+        })
+    }
+
     /// Removes the style from this node.
     ///
     /// Unlike the version on TNode, this handles pseudo-elements.
     fn unstyle(self) {
         let mut data = self.mutate_layout_data().unwrap();
 
         match self.get_pseudo_element_type() {
             PseudoElementType::Before(_) => {
@@ -1010,17 +1020,17 @@ impl<'ln> ThreadSafeLayoutNode for Servo
         }
     }
 
     fn can_be_fragmented(&self) -> bool {
         self.node.can_be_fragmented()
     }
 
     fn text_content(&self) -> TextContent {
-        if self.pseudo != PseudoElementType::Normal {
+        if self.pseudo.is_before_or_after() {
             let data = &self.borrow_layout_data().unwrap().style_data;
 
             let style = if self.pseudo.is_before() {
                 data.per_pseudo.get(&PseudoElement::Before).unwrap()
             } else {
                 data.per_pseudo.get(&PseudoElement::After).unwrap()
             };
 
@@ -1131,17 +1141,16 @@ impl<ConcreteNode> ThreadSafeLayoutNodeC
     }
 }
 
 impl<ConcreteNode> Iterator for ThreadSafeLayoutNodeChildrenIterator<ConcreteNode>
                             where ConcreteNode: DangerousThreadSafeLayoutNode {
     type Item = ConcreteNode;
     fn next(&mut self) -> Option<ConcreteNode> {
         match self.parent_node.get_pseudo_element_type() {
-
             PseudoElementType::Before(_) | PseudoElementType::After(_) => None,
 
             PseudoElementType::DetailsSummary(_) => {
                 let mut current_node = self.current_node.clone();
                 loop {
                     let next_node = if let Some(ref node) = current_node {
                         if node.is_element() &&
                                 node.as_element().get_local_name() == &atom!("summary") &&
@@ -1157,18 +1166,18 @@ impl<ConcreteNode> Iterator for ThreadSa
                     current_node = next_node;
                 }
             }
 
             PseudoElementType::DetailsContent(_) => {
                 let node = self.current_node.clone();
                 let node = node.and_then(|node| {
                     if node.is_element() &&
-                            node.as_element().get_local_name() == &atom!("summary") &&
-                            node.as_element().get_namespace() == &ns!(html) {
+                       node.as_element().get_local_name() == &atom!("summary") &&
+                       node.as_element().get_namespace() == &ns!(html) {
                         unsafe { node.dangerous_next_sibling() }
                     } else {
                         Some(node)
                     }
                 });
                 self.current_node = node.and_then(|node| unsafe { node.dangerous_next_sibling() });
                 node
             }
--- a/servo/components/style/selector_impl.rs
+++ b/servo/components/style/selector_impl.rs
@@ -21,16 +21,17 @@ pub trait SelectorImplExt : SelectorImpl
 
     fn get_quirks_mode_stylesheet() -> Option<&'static Stylesheet<Self>>;
 }
 
 #[derive(Clone, Debug, PartialEq, Eq, HeapSizeOf, Hash)]
 pub enum PseudoElement {
     Before,
     After,
+    Selection,
     DetailsSummary,
     DetailsContent,
 }
 
 #[derive(Clone, Debug, PartialEq, Eq, HeapSizeOf, Hash)]
 pub enum NonTSPseudoClass {
     AnyLink,
     Link,
@@ -100,16 +101,17 @@ impl SelectorImpl for ServoSelectorImpl 
     }
 
     fn parse_pseudo_element(context: &ParserContext,
                             name: &str) -> Result<PseudoElement, ()> {
         use self::PseudoElement::*;
         let pseudo_element = match_ignore_ascii_case! { name,
             "before" => Before,
             "after" => After,
+            "selection" => Selection,
             "-servo-details-summary" => if context.in_user_agent_stylesheet {
                 DetailsSummary
             } else {
                 return Err(())
             },
             "-servo-details-content" => if context.in_user_agent_stylesheet {
                 DetailsContent
             } else {
@@ -131,16 +133,17 @@ impl<E: Element<Impl=ServoSelectorImpl>>
 impl SelectorImplExt for ServoSelectorImpl {
     #[inline]
     fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
         where F: FnMut(PseudoElement) {
         fun(PseudoElement::Before);
         fun(PseudoElement::After);
         fun(PseudoElement::DetailsContent);
         fun(PseudoElement::DetailsSummary);
+        fun(PseudoElement::Selection);
     }
 
     #[inline]
     fn pseudo_class_state_flag(pc: &NonTSPseudoClass) -> ElementState {
         pc.state_flag()
     }
 
     #[inline]
--- a/servo/tests/unit/layout/size_of.rs
+++ b/servo/tests/unit/layout/size_of.rs
@@ -2,17 +2,17 @@
  * 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 layout::Fragment;
 use std::mem::size_of;
 
 #[test]
 fn test_size_of_fragment() {
-    let expected = 160;
+    let expected = 168;
     let actual = size_of::<Fragment>();
 
     if actual < expected {
         panic!("Your changes have decreased the stack size of layout::fragment::Fragment \
                 from {} to {}. Good work! Please update the size in tests/layout/size_of.rs",
                 expected, actual);
     }
 
--- a/servo/tests/unit/util/lib.rs
+++ b/servo/tests/unit/util/lib.rs
@@ -1,13 +1,13 @@
 /* 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/. */
 
-#![cfg_attr(test, feature(plugin, custom_derive, heap_api))]
+#![cfg_attr(test, feature(plugin, custom_derive))]
 #![cfg_attr(test, plugin(plugins))]
 #![feature(alloc)]
 
 extern crate alloc;
 extern crate app_units;
 extern crate euclid;
 extern crate libc;
 extern crate util;