servo: Merge #12757 - stylo: Stop restyling display: none elements, remove the has_changed hack that made us use ReconstructFrame unconditionally (from emilio:stylo); r=bholley,pcwalton
authorEmilio Cobos Álvarez <ecoal95@gmail.com>
Wed, 10 Aug 2016 21:02:30 -0500
changeset 368499 48d70a3b372b6eefc472d3ac777bc9fd47b82343
parent 368498 a598e41b2e1bb6899342de7f1916feb4e7391d9e
child 368500 3c16740a69d9414fa2b6d16e59a994e1e0577d0e
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley, pcwalton
servo: Merge #12757 - stylo: Stop restyling display: none elements, remove the has_changed hack that made us use ReconstructFrame unconditionally (from emilio:stylo); r=bholley,pcwalton <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> r? @bholley Source-Repo: https://github.com/servo/servo Source-Revision: 1b2450339c40dbcb65e94a346ea434d45f0edf90
servo/components/layout/animation.rs
servo/components/layout/construct.rs
servo/components/layout/query.rs
servo/components/layout/traversal.rs
servo/components/layout_thread/lib.rs
servo/components/script/layout_wrapper.rs
servo/components/script_layout_interface/restyle_damage.rs
servo/components/style/dom.rs
servo/components/style/matching.rs
servo/components/style/parallel.rs
servo/components/style/selector_impl.rs
servo/components/style/sequential.rs
servo/components/style/traversal.rs
servo/ports/geckolib/traversal.rs
servo/ports/geckolib/wrapper.rs
--- a/servo/components/layout/animation.rs
+++ b/servo/components/layout/animation.rs
@@ -130,17 +130,17 @@ pub fn recalc_style_for_animations(conte
     let mut damage = RestyleDamage::empty();
     flow.mutate_fragments(&mut |fragment| {
         if let Some(ref animations) = animations.get(&fragment.node) {
             for animation in animations.iter() {
                 let old_style = fragment.style.clone();
                 update_style_for_animation(&context.style_context,
                                            animation,
                                            &mut fragment.style);
-                damage |= RestyleDamage::compute(Some(&old_style), &fragment.style);
+                damage |= RestyleDamage::compute(&old_style, &fragment.style);
             }
         }
     });
 
     let base = flow::mut_base(flow);
     base.restyle_damage.insert(damage);
     for kid in base.children.iter_mut() {
         recalc_style_for_animations(context, kid, animations)
--- a/servo/components/layout/construct.rs
+++ b/servo/components/layout/construct.rs
@@ -1369,22 +1369,30 @@ impl<'a, ConcreteThreadSafeLayoutNode: T
     /// to block parents with no {ib} splits, adding out-of-flow kids, etc.
     pub fn repair_if_possible(&mut self, node: &ConcreteThreadSafeLayoutNode) -> bool {
         // We can skip reconstructing the flow if we don't have to reconstruct and none of our kids
         // did either.
         //
         // We visit the kids first and reset their HAS_NEWLY_CONSTRUCTED_FLOW flags after checking
         // them.  NOTE: Make sure not to bail out early before resetting all the flags!
         let mut need_to_reconstruct = false;
+
+        // If the node has display: none, it's possible that we haven't even
+        // styled the children once, so we need to bailout early here.
+        if node.style(self.style_context()).get_box().clone_display() == display::T::none {
+            return false;
+        }
+
         for kid in node.children() {
             if kid.flags().contains(HAS_NEWLY_CONSTRUCTED_FLOW) {
                 kid.remove_flags(HAS_NEWLY_CONSTRUCTED_FLOW);
                 need_to_reconstruct = true
             }
         }
+
         if need_to_reconstruct {
             return false
         }
 
         if node.restyle_damage().contains(RECONSTRUCT_FLOW) {
             return false
         }
 
--- a/servo/components/layout/query.rs
+++ b/servo/components/layout/query.rs
@@ -25,24 +25,25 @@ use script_layout_interface::wrapper_tra
 use script_traits::LayoutMsg as ConstellationMsg;
 use script_traits::UntrustedNodeAddress;
 use sequential;
 use std::cmp::{min, max};
 use std::ops::Deref;
 use std::sync::{Arc, Mutex};
 use string_cache::Atom;
 use style::computed_values;
+use style::context::StyleContext;
 use style::logical_geometry::{WritingMode, BlockFlowDirection, InlineBaseDirection};
 use style::properties::longhands::{display, position};
 use style::properties::style_structs;
 use style::selector_impl::PseudoElement;
 use style::selector_matching::Stylist;
 use style::values::LocalToCss;
 use style_traits::cursor::Cursor;
-use wrapper::ThreadSafeLayoutNodeHelpers;
+use wrapper::{LayoutNodeLayoutData, ThreadSafeLayoutNodeHelpers};
 
 /// Mutable data belonging to the LayoutThread.
 ///
 /// This needs to be protected by a mutex so we can do fast RPCs.
 pub struct LayoutThreadData {
     /// The channel on which messages can be sent to the constellation.
     pub constellation_chan: IpcSender<ConstellationMsg>,
 
@@ -615,21 +616,49 @@ pub fn process_node_scroll_area_request<
             let top = min(iterator.union_rect.origin.y, iterator.origin_rect.origin.y);
             let right = max(iterator.union_rect.size.width, iterator.origin_rect.size.width);
             Rect::new(Point2D::new(iterator.origin_rect.origin.x, top),
                       Size2D::new(right, iterator.origin_rect.size.height))
         }
     }
 }
 
+/// Ensures that a node's data, and all its parents' is initialized. This is
+/// needed to resolve style lazily.
+fn ensure_node_data_initialized<N: LayoutNode>(node: &N) {
+    let mut cur = Some(node.clone());
+    while let Some(current) = cur {
+        if current.borrow_data().is_some() {
+            break;
+        }
+
+        current.initialize_data();
+        cur = current.parent_node();
+    }
+}
+
 /// 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> {
+pub fn process_resolved_style_request<'a, N, C>(requested_node: N,
+                                                style_context: &'a C,
+                                                pseudo: &Option<PseudoElement>,
+                                                property: &Atom,
+                                                layout_root: &mut FlowRef) -> Option<String>
+    where N: LayoutNode,
+          C: StyleContext<'a>
+{
+    use style::traversal::ensure_node_styled;
+
+    // This node might have display: none, or it's style might be not up to
+    // date, so we might need to do style recalc.
+    //
+    // FIXME(emilio): Is a bit shame we have to do this instead of in style.
+    ensure_node_data_initialized(&requested_node);
+    ensure_node_styled(requested_node, style_context);
+
     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) |
         Some(PseudoElement::DetailsContent) |
         Some(PseudoElement::Selection) => None,
         _ => Some(layout_node)
--- a/servo/components/layout/traversal.rs
+++ b/servo/components/layout/traversal.rs
@@ -11,16 +11,17 @@ use flow::{CAN_BE_FRAGMENTED, Flow, Immu
 use flow::{PreorderFlowTraversal, self};
 use gfx::display_list::OpaqueNode;
 use script_layout_interface::restyle_damage::{BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, RestyleDamage};
 use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
 use std::mem;
 use style::context::SharedStyleContext;
 use style::dom::TNode;
 use style::selector_impl::ServoSelectorImpl;
+use style::traversal::RestyleResult;
 use style::traversal::{DomTraversalContext, remove_from_bloom_filter, recalc_style_at};
 use util::opts;
 use wrapper::{LayoutNodeLayoutData, ThreadSafeLayoutNodeHelpers};
 
 pub struct RecalcStyleAndConstructFlows<'lc> {
     context: LayoutContext<'lc>,
     root: OpaqueNode,
 }
@@ -64,22 +65,22 @@ impl<'lc, N> DomTraversalContext<N> for 
         // LayoutThread, generally much longer than that of a given SharedLayoutContext borrow.
         let shared_lc: &'lc SharedLayoutContext = unsafe { mem::transmute(shared) };
         RecalcStyleAndConstructFlows {
             context: LayoutContext::new(shared_lc),
             root: root,
         }
     }
 
-    fn process_preorder(&self, node: N) {
-        // FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML
-        // parser.
+    fn process_preorder(&self, node: N) -> RestyleResult {
+        // FIXME(pcwalton): Stop allocating here. Ideally this should just be
+        // done by the HTML parser.
         node.initialize_data();
 
-        recalc_style_at(&self.context, self.root, node);
+        recalc_style_at(&self.context, self.root, node)
     }
 
     fn process_postorder(&self, node: N) {
         construct_flows_at(&self.context, self.root, node);
     }
 }
 
 /// A bottom-up, parallelizable traversal.
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -1232,18 +1232,23 @@ impl LayoutThread {
                     rw_data.overflow_response = process_node_overflow_request(node);
                 },
                 ReflowQueryType::NodeLayerIdQuery(node) => {
                     let node = unsafe { ServoLayoutNode::new(&node) };
                     rw_data.layer_id_response = Some(process_node_layer_id_request(node));
                 },
                 ReflowQueryType::ResolvedStyleQuery(node, ref pseudo, ref property) => {
                     let node = unsafe { ServoLayoutNode::new(&node) };
+                    let layout_context = LayoutContext::new(&shared_layout_context);
                     rw_data.resolved_style_response =
-                        process_resolved_style_request(node, pseudo, property, &mut root_flow);
+                        process_resolved_style_request(node,
+                                                       &layout_context,
+                                                       pseudo,
+                                                       property,
+                                                       &mut root_flow);
                 },
                 ReflowQueryType::OffsetParentQuery(node) => {
                     let node = unsafe { ServoLayoutNode::new(&node) };
                     rw_data.offset_parent_response = process_offset_parent_query(node, &mut root_flow);
                 },
                 ReflowQueryType::MarginStyleQuery(node) => {
                     let node = unsafe { ServoLayoutNode::new(&node) };
                     rw_data.margin_style_response = process_margin_style_query(node);
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -56,17 +56,17 @@ use string_cache::{Atom, Namespace};
 use style::attr::AttrValue;
 use style::computed_values::display;
 use style::context::SharedStyleContext;
 use style::data::PrivateStyleData;
 use style::dom::{PresentationalHintsSynthetizer, OpaqueNode, TDocument, TElement, TNode, UnsafeNode};
 use style::element_state::*;
 use style::properties::{ComputedValues, PropertyDeclaration, PropertyDeclarationBlock};
 use style::refcell::{Ref, RefCell, RefMut};
-use style::selector_impl::{ElementSnapshot, NonTSPseudoClass, ServoSelectorImpl};
+use style::selector_impl::{ElementSnapshot, NonTSPseudoClass, PseudoElement, ServoSelectorImpl};
 use style::sink::Push;
 use style::str::is_whitespace;
 use url::Url;
 
 #[derive(Copy, Clone)]
 pub struct ServoLayoutNode<'a> {
     /// The wrapped node.
     node: LayoutJS<Node>,
@@ -261,17 +261,18 @@ impl<'ln> TNode for ServoLayoutNode<'ln>
     fn next_sibling(&self) -> Option<ServoLayoutNode<'ln>> {
         unsafe {
             self.node.next_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
         }
     }
 
     #[inline]
     fn existing_style_for_restyle_damage<'a>(&'a self,
-                                             current_cv: Option<&'a Arc<ComputedValues>>)
+                                             current_cv: Option<&'a Arc<ComputedValues>>,
+                                             _pseudo_element: Option<&PseudoElement>)
                                              -> Option<&'a Arc<ComputedValues>> {
         current_cv
     }
 }
 
 impl<'ln> LayoutNode for ServoLayoutNode<'ln> {
     type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'ln>;
 
--- a/servo/components/script_layout_interface/restyle_damage.rs
+++ b/servo/components/script_layout_interface/restyle_damage.rs
@@ -42,17 +42,21 @@ bitflags! {
         const RECONSTRUCT_FLOW = 0x40
     }
 }
 
 impl TRestyleDamage for RestyleDamage {
     /// For Servo the style source is always the computed values.
     type PreExistingComputedValues = Arc<ServoComputedValues>;
 
-    fn compute(old: Option<&Arc<ServoComputedValues>>,
+    fn empty() -> Self {
+        RestyleDamage::empty()
+    }
+
+    fn compute(old: &Arc<ServoComputedValues>,
                new: &Arc<ServoComputedValues>) -> RestyleDamage {
         compute_damage(old, new)
     }
 
     /// Returns a bitmask that represents a flow that needs to be rebuilt and
     /// reflowed.
     ///
     /// Use this instead of `RestyleDamage::all()` because
@@ -145,23 +149,17 @@ macro_rules! add_if_not_equal(
             $damage.insert($($effect)|*);
             true
         } else {
             false
         }
     })
 );
 
-fn compute_damage(old: Option<&Arc<ServoComputedValues>>, new: &Arc<ServoComputedValues>) -> RestyleDamage {
-    let new = &**new;
-    let old: &ServoComputedValues = match old {
-        None => return RestyleDamage::rebuild_and_reflow(),
-        Some(cv) => &**cv,
-    };
-
+fn compute_damage(old: &ServoComputedValues, new: &ServoComputedValues) -> RestyleDamage {
     let mut damage = RestyleDamage::empty();
 
     // This should check every CSS property, as enumerated in the fields of
     // http://doc.servo.org/style/properties/struct.ServoComputedValues.html
 
     // FIXME: Test somehow that every property is included.
 
     add_if_not_equal!(old, new, damage,
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -7,19 +7,20 @@
 #![allow(unsafe_code)]
 
 use context::SharedStyleContext;
 use data::PrivateStyleData;
 use element_state::ElementState;
 use properties::{ComputedValues, PropertyDeclaration, PropertyDeclarationBlock};
 use refcell::{Ref, RefMut};
 use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
-use selector_impl::ElementExt;
+use selector_impl::{ElementExt, PseudoElement};
 use selectors::matching::DeclarationBlock;
 use sink::Push;
+use std::fmt::Debug;
 use std::ops::BitOr;
 use std::sync::Arc;
 use string_cache::{Atom, Namespace};
 
 /// Opaque type stored in type-unsafe work queues for parallel layout.
 /// Must be transmutable to and from TNode.
 pub type UnsafeNode = (usize, usize);
 
@@ -39,30 +40,32 @@ pub struct OpaqueNode(pub usize);
 impl OpaqueNode {
     /// Returns the address of this node, for debugging purposes.
     #[inline]
     pub fn id(&self) -> usize {
         self.0
     }
 }
 
-pub trait TRestyleDamage : BitOr<Output=Self> + Copy {
+pub trait TRestyleDamage : Debug + PartialEq + BitOr<Output=Self> + Copy {
     /// The source for our current computed values in the cascade. This is a
     /// ComputedValues in Servo and a StyleContext in Gecko.
     ///
     /// This is needed because Gecko has a few optimisations for the calculation
     /// of the difference depending on which values have been used during
     /// layout.
     ///
     /// This should be obtained via TNode::existing_style_for_restyle_damage
     type PreExistingComputedValues;
 
-    fn compute(old: Option<&Self::PreExistingComputedValues>,
+    fn compute(old: &Self::PreExistingComputedValues,
                new: &Arc<ComputedValues>) -> Self;
 
+    fn empty() -> Self;
+
     fn rebuild_and_reflow() -> Self;
 }
 
 pub trait TNode : Sized + Copy + Clone {
     type ConcreteElement: TElement<ConcreteNode = Self, ConcreteDocument = Self::ConcreteDocument>;
     type ConcreteDocument: TDocument<ConcreteNode = Self, ConcreteElement = Self::ConcreteElement>;
     type ConcreteRestyleDamage: TRestyleDamage;
 
@@ -169,17 +172,18 @@ pub trait TNode : Sized + Copy + Clone {
     fn unstyle(self) {
         self.mutate_data().unwrap().style = None;
     }
 
     /// XXX: It's a bit unfortunate we need to pass the current computed values
     /// as an argument here, but otherwise Servo would crash due to double
     /// borrows to return it.
     fn existing_style_for_restyle_damage<'a>(&'a self,
-                                             current_computed_values: Option<&'a Arc<ComputedValues>>)
+                                             current_computed_values: Option<&'a Arc<ComputedValues>>,
+                                             pseudo: Option<&PseudoElement>)
         -> Option<&'a <Self::ConcreteRestyleDamage as TRestyleDamage>::PreExistingComputedValues>;
 }
 
 pub trait TDocument : Sized + Copy + Clone {
     type ConcreteNode: TNode<ConcreteElement = Self::ConcreteElement, ConcreteDocument = Self>;
     type ConcreteElement: TElement<ConcreteNode = Self::ConcreteNode, ConcreteDocument = Self>;
 
     fn as_node(&self) -> Self::ConcreteNode;
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -7,29 +7,31 @@
 #![allow(unsafe_code)]
 
 use animation;
 use arc_ptr_eq;
 use cache::{LRUCache, SimpleHashCache};
 use context::{StyleContext, SharedStyleContext};
 use data::PrivateStyleData;
 use dom::{TElement, TNode, TRestyleDamage};
+use properties::longhands::display::computed_value as display;
 use properties::{ComputedValues, PropertyDeclaration, cascade};
 use selector_impl::{ElementExt, TheSelectorImpl, PseudoElement};
 use selector_matching::{DeclarationBlock, Stylist};
 use selectors::bloom::BloomFilter;
 use selectors::{Element, MatchAttr};
 use sink::ForgetfulSink;
 use smallvec::SmallVec;
 use std::borrow::Borrow;
 use std::collections::HashMap;
 use std::hash::{BuildHasherDefault, Hash, Hasher};
 use std::slice::Iter;
 use std::sync::Arc;
 use string_cache::{Atom, Namespace};
+use traversal::RestyleResult;
 use util::opts;
 
 fn create_common_style_affecting_attributes_from_element<E: TElement>(element: &E)
                                                          -> CommonStyleAffectingAttributes {
     let mut flags = CommonStyleAffectingAttributes::empty();
     for attribute_info in &common_style_affecting_attributes() {
         match attribute_info.mode {
             CommonStyleAffectingAttributeMode::IsPresent(flag) => {
@@ -405,117 +407,109 @@ impl StyleSharingCandidateCache {
         self.cache.touch(index);
     }
 }
 
 /// The results of attempting to share a style.
 pub enum StyleSharingResult<ConcreteRestyleDamage: TRestyleDamage> {
     /// We didn't find anybody to share the style with.
     CannotShare,
-    /// The node's style can be shared. The integer specifies the index in the LRU cache that was
-    /// hit and the damage that was done.
-    StyleWasShared(usize, ConcreteRestyleDamage),
+    /// The node's style can be shared. The integer specifies the index in the
+    /// LRU cache that was hit and the damage that was done, and the restyle
+    /// result the original result of the candidate's styling, that is, whether
+    /// it should stop the traversal or not.
+    StyleWasShared(usize, ConcreteRestyleDamage, RestyleResult),
 }
 
 trait PrivateMatchMethods: TNode {
     /// Actually cascades style for a node or a pseudo-element of a node.
     ///
     /// Note that animations only apply to nodes or ::before or ::after
     /// pseudo-elements.
     fn cascade_node_pseudo_element<'a, Ctx>(&self,
                                             context: &Ctx,
                                             parent_style: Option<&Arc<ComputedValues>>,
                                             applicable_declarations: &[DeclarationBlock],
-                                            mut style: Option<&mut Arc<ComputedValues>>,
+                                            mut old_style: Option<&mut Arc<ComputedValues>>,
                                             applicable_declarations_cache:
                                              &mut ApplicableDeclarationsCache,
                                             shareable: bool,
                                             animate_properties: bool)
-                                            -> (Self::ConcreteRestyleDamage, Arc<ComputedValues>)
-    where Ctx: StyleContext<'a> {
+                                            -> Arc<ComputedValues>
+        where Ctx: StyleContext<'a>
+    {
         let mut cacheable = true;
         let shared_context = context.shared_context();
         if animate_properties {
             cacheable = !self.update_animations_for_cascade(shared_context,
-                                                            &mut style) && cacheable;
+                                                            &mut old_style) && cacheable;
         }
 
-        let this_style;
-        match parent_style {
+        let (this_style, is_cacheable) = match parent_style {
             Some(ref parent_style) => {
                 let cache_entry = applicable_declarations_cache.find(applicable_declarations);
                 let cached_computed_values = match cache_entry {
                     Some(ref style) => Some(&**style),
                     None => None,
                 };
 
-                let (the_style, is_cacheable) = cascade(shared_context.viewport_size,
-                                                        applicable_declarations,
-                                                        shareable,
-                                                        Some(&***parent_style),
-                                                        cached_computed_values,
-                                                        shared_context.error_reporter.clone());
-                cacheable = cacheable && is_cacheable;
-                this_style = the_style
+                cascade(shared_context.viewport_size,
+                        applicable_declarations,
+                        shareable,
+                        Some(&***parent_style),
+                        cached_computed_values,
+                        shared_context.error_reporter.clone())
             }
             None => {
-                let (the_style, is_cacheable) = cascade(shared_context.viewport_size,
-                                                        applicable_declarations,
-                                                        shareable,
-                                                        None,
-                                                        None,
-                                                        shared_context.error_reporter.clone());
-                cacheable = cacheable && is_cacheable;
-                this_style = the_style
+                cascade(shared_context.viewport_size,
+                        applicable_declarations,
+                        shareable,
+                        None,
+                        None,
+                        shared_context.error_reporter.clone())
             }
         };
 
+        cacheable = cacheable && is_cacheable;
+
         let mut this_style = Arc::new(this_style);
 
         if animate_properties {
             let new_animations_sender = &context.local_context().new_animations_sender;
             let this_opaque = self.opaque();
             // Trigger any present animations if necessary.
             let mut animations_started = animation::maybe_start_animations(
                 &shared_context,
                 new_animations_sender,
                 this_opaque,
                 &this_style);
 
             // Trigger transitions if necessary. This will reset `this_style` back
             // to its old value if it did trigger a transition.
-            if let Some(ref style) = style {
+            if let Some(ref style) = old_style {
                 animations_started |=
                     animation::start_transitions_if_applicable(
                         new_animations_sender,
                         this_opaque,
                         &**style,
                         &mut this_style,
                         &shared_context.timer);
             }
 
             cacheable = cacheable && !animations_started
         }
 
 
-        let existing_style =
-            self.existing_style_for_restyle_damage(style.map(|s| &*s));
-
-        // Calculate style difference.
-        let damage =
-            Self::ConcreteRestyleDamage::compute(existing_style, &this_style);
-
         // Cache the resolved style if it was cacheable.
         if cacheable {
             applicable_declarations_cache.insert(applicable_declarations.to_vec(),
                                                  this_style.clone());
         }
 
-        // Return the final style and the damage done to our caller.
-        (damage, this_style)
+        this_style
     }
 
     fn update_animations_for_cascade(&self,
                                      context: &SharedStyleContext,
                                      style: &mut Option<&mut Arc<ComputedValues>>)
                                      -> bool {
         let style = match *style {
             None => return false,
@@ -641,26 +635,37 @@ pub trait ElementMatchMethods : TElement
 
         for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() {
             if let Some(shared_style) = self.share_style_with_candidate_if_possible(parent.clone(), candidate) {
                 // Yay, cache hit. Share the style.
                 let node = self.as_node();
 
                 let style = &mut node.mutate_data().unwrap().style;
 
-                let damage = {
-                    let source =
-                        node.existing_style_for_restyle_damage((*style).as_ref());
-                    let damage = <<Self as TElement>::ConcreteNode as TNode>
-                                     ::ConcreteRestyleDamage::compute(source, &shared_style);
-                    damage
+                let damage =
+                    match node.existing_style_for_restyle_damage((*style).as_ref(), None) {
+                        Some(ref source) => {
+                            <<Self as TElement>::ConcreteNode as TNode>
+                            ::ConcreteRestyleDamage::compute(source, &shared_style)
+                        }
+                        None => {
+                            <<Self as TElement>::ConcreteNode as TNode>
+                            ::ConcreteRestyleDamage::rebuild_and_reflow()
+                        }
+                    };
+
+                let restyle_result = if shared_style.get_box().clone_display() == display::T::none {
+                    RestyleResult::Stop
+                } else {
+                    RestyleResult::Continue
                 };
 
                 *style = Some(shared_style);
-                return StyleSharingResult::StyleWasShared(i, damage)
+
+                return StyleSharingResult::StyleWasShared(i, damage, restyle_result)
             }
         }
 
         StyleSharingResult::CannotShare
     }
 }
 
 impl<E: TElement> ElementMatchMethods for E {}
@@ -709,99 +714,241 @@ pub trait MatchMethods : TNode {
             bf.remove(&*element.get_namespace());
             element.get_id().map(|id| bf.remove(&id));
 
             // TODO: case-sensitivity depends on the document type and quirks mode
             element.each_class(|class| bf.remove(class));
         }
     }
 
+    fn compute_restyle_damage(&self,
+                              old_style: Option<&Arc<ComputedValues>>,
+                              new_style: &Arc<ComputedValues>,
+                              pseudo: Option<&PseudoElement>)
+                              -> Self::ConcreteRestyleDamage
+    {
+        match self.existing_style_for_restyle_damage(old_style, pseudo) {
+            Some(ref source) => {
+                Self::ConcreteRestyleDamage::compute(source,
+                                                     new_style)
+            }
+            None => {
+                // If there's no style source, two things can happen:
+                //
+                //  1. This is not an incremental restyle (old_style is none).
+                //     In this case we can't do too much than sending
+                //     rebuild_and_reflow.
+                //
+                //  2. This is an incremental restyle, but the old display value
+                //     is none, so there's no effective way for Gecko to get the
+                //     style source. In this case, we could return either
+                //     RestyleDamage::empty(), in the case both displays are
+                //     none, or rebuild_and_reflow, otherwise. The first case
+                //     should be already handled when calling this function, so
+                //     we can assert that the new display value is not none.
+                //
+                //     Also, this can be a text node (in which case we don't
+                //     care of watching the new display value).
+                //
+                // Unfortunately we can't strongly assert part of this, since
+                // we style some nodes that in Gecko never generate a frame,
+                // like children of replaced content. Arguably, we shouldn't be
+                // styling those here, but until we implement that we'll have to
+                // stick without the assertions.
+                debug_assert!(pseudo.is_none() ||
+                              new_style.get_box().clone_display() != display::T::none);
+                Self::ConcreteRestyleDamage::rebuild_and_reflow()
+            }
+        }
+    }
+
     unsafe fn cascade_node<'a, Ctx>(&self,
                                     context: &Ctx,
                                     parent: Option<Self>,
                                     applicable_declarations: &ApplicableDeclarations)
-    where Ctx: StyleContext<'a> {
+                                    -> RestyleResult
+        where Ctx: StyleContext<'a>
+    {
         // Get our parent's style. This must be unsafe so that we don't touch the parent's
         // borrow flags.
         //
         // FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow
         // enforced safe, race-free access to the parent style.
         let parent_style = match parent {
             Some(parent_node) => {
                 let parent_style = (*parent_node.borrow_data_unchecked().unwrap()).style.as_ref().unwrap();
                 Some(parent_style)
             }
             None => None,
         };
 
         let mut applicable_declarations_cache =
             context.local_context().applicable_declarations_cache.borrow_mut();
 
-        let damage;
-        if self.is_text_node() {
+        let (damage, restyle_result) = if self.is_text_node() {
             let mut data_ref = self.mutate_data().unwrap();
             let mut data = &mut *data_ref;
             let cloned_parent_style = ComputedValues::style_for_child_text_node(parent_style.unwrap());
 
-            {
-                let existing_style =
-                    self.existing_style_for_restyle_damage(data.style.as_ref());
-                damage = Self::ConcreteRestyleDamage::compute(existing_style,
-                                                              &cloned_parent_style);
-            }
+            let damage =
+                self.compute_restyle_damage(data.style.as_ref(), &cloned_parent_style, None);
 
             data.style = Some(cloned_parent_style);
-        } else {
-            damage = {
-                let mut data_ref = self.mutate_data().unwrap();
-                let mut data = &mut *data_ref;
-                let (mut damage, final_style) = self.cascade_node_pseudo_element(
-                    context,
-                    parent_style,
-                    &applicable_declarations.normal,
-                    data.style.as_mut(),
-                    &mut applicable_declarations_cache,
-                    applicable_declarations.normal_shareable,
-                    true);
-
-                data.style = Some(final_style);
-
-                <Self::ConcreteElement as MatchAttr>::Impl::each_eagerly_cascaded_pseudo_element(|pseudo| {
-                    let applicable_declarations_for_this_pseudo =
-                        applicable_declarations.per_pseudo.get(&pseudo).unwrap();
 
-                    if !applicable_declarations_for_this_pseudo.is_empty() {
-                        // NB: Transitions and animations should only work for
-                        // pseudo-elements ::before and ::after
-                        let should_animate_properties =
-                            <Self::ConcreteElement as MatchAttr>::Impl::pseudo_is_before_or_after(&pseudo);
-                        let (new_damage, style) = self.cascade_node_pseudo_element(
-                            context,
-                            Some(data.style.as_ref().unwrap()),
-                            &*applicable_declarations_for_this_pseudo,
-                            data.per_pseudo.get_mut(&pseudo),
-                            &mut applicable_declarations_cache,
-                            false,
-                            should_animate_properties);
-                        data.per_pseudo.insert(pseudo, style);
+            (damage, RestyleResult::Continue)
+        } else {
+            let mut data_ref = self.mutate_data().unwrap();
+            let mut data = &mut *data_ref;
+            let final_style =
+                self.cascade_node_pseudo_element(context, parent_style,
+                                                 &applicable_declarations.normal,
+                                                 data.style.as_mut(),
+                                                 &mut applicable_declarations_cache,
+                                                 applicable_declarations.normal_shareable,
+                                                 /* should_animate = */ true);
 
-                        damage = damage | new_damage;
-                    }
-                });
+            let (damage, restyle_result) =
+                self.compute_damage_and_cascade_pseudos(final_style,
+                                                        data,
+                                                        context,
+                                                        applicable_declarations,
+                                                        &mut applicable_declarations_cache);
 
-                damage
-            };
-
-            // This method needs to borrow the data as mutable, so make sure data_ref goes out of
-            // scope first.
             self.set_can_be_fragmented(parent.map_or(false, |p| {
                 p.can_be_fragmented() ||
                 parent_style.as_ref().unwrap().is_multicol()
             }));
+
+            (damage, restyle_result)
+        };
+
+
+        // This method needs to borrow the data as mutable, so make sure
+        // data_ref goes out of scope first.
+        self.set_restyle_damage(damage);
+
+        restyle_result
+    }
+
+    fn compute_damage_and_cascade_pseudos<'a, Ctx>(&self,
+                                                   final_style: Arc<ComputedValues>,
+                                                   data: &mut PrivateStyleData,
+                                                   context: &Ctx,
+                                                   applicable_declarations: &ApplicableDeclarations,
+                                                   mut applicable_declarations_cache: &mut ApplicableDeclarationsCache)
+                                                   -> (Self::ConcreteRestyleDamage, RestyleResult)
+        where Ctx: StyleContext<'a>
+    {
+        // Here we optimise the case of the style changing but both the
+        // previous and the new styles having display: none. In this
+        // case, we can always optimize the traversal, regardless of the
+        // restyle hint.
+        let this_display = final_style.get_box().clone_display();
+        if this_display == display::T::none {
+            let old_display = data.style.as_ref().map(|old_style| {
+                old_style.get_box().clone_display()
+            });
+
+            // If display passed from none to something, then we need to reflow,
+            // otherwise, we don't do anything.
+            let damage = match old_display {
+                Some(display) if display == this_display => {
+                    Self::ConcreteRestyleDamage::empty()
+                }
+                _ => Self::ConcreteRestyleDamage::rebuild_and_reflow()
+            };
+
+            debug!("Short-circuiting traversal: {:?} {:?} {:?}",
+                   this_display, old_display, damage);
+
+            data.style = Some(final_style);
+            return (damage, RestyleResult::Stop);
         }
 
-        // This method needs to borrow the data as mutable, so make sure data_ref goes out of
-        // scope first.
-        self.set_restyle_damage(damage);
+        // Otherwise, we just compute the damage normally, and sum up the damage
+        // related to pseudo-elements.
+        let mut damage =
+            self.compute_restyle_damage(data.style.as_ref(), &final_style, None);
+
+        data.style = Some(final_style);
+
+        let data_per_pseudo = &mut data.per_pseudo;
+        let new_style = data.style.as_ref();
+
+        debug_assert!(new_style.is_some());
+
+        let rebuild_and_reflow =
+            Self::ConcreteRestyleDamage::rebuild_and_reflow();
+
+        <Self::ConcreteElement as MatchAttr>::Impl::each_eagerly_cascaded_pseudo_element(|pseudo| {
+            use std::collections::hash_map::Entry;
+
+            let applicable_declarations_for_this_pseudo =
+                applicable_declarations.per_pseudo.get(&pseudo).unwrap();
+
+            let has_declarations =
+                !applicable_declarations_for_this_pseudo.is_empty();
+
+            // If there are declarations matching, we're going to need to
+            // recompute the style anyway, so do it now to simplify the logic
+            // below.
+            let pseudo_style_if_declarations = if has_declarations {
+                // NB: Transitions and animations should only work for
+                // pseudo-elements ::before and ::after
+                let should_animate_properties =
+                    <Self::ConcreteElement as MatchAttr>::Impl::pseudo_is_before_or_after(&pseudo);
+
+                Some(self.cascade_node_pseudo_element(context,
+                                                      new_style,
+                                                      &*applicable_declarations_for_this_pseudo,
+                                                      data_per_pseudo.get_mut(&pseudo),
+                                                      &mut applicable_declarations_cache,
+                                                      /* shareable = */ false,
+                                                      should_animate_properties))
+            } else {
+                None
+            };
+
+            // Let's see what we had before.
+            match data_per_pseudo.entry(pseudo.clone()) {
+                Entry::Vacant(vacant_entry) => {
+                    // If we had a vacant entry, and no rules that match, we're
+                    // fine so far.
+                    if !has_declarations {
+                        return;
+                    }
+
+                    // Otherwise, we need to insert the new computed styles, and
+                    // generate a rebuild_and_reflow damage.
+                    damage = damage | Self::ConcreteRestyleDamage::rebuild_and_reflow();
+                    vacant_entry.insert(pseudo_style_if_declarations.unwrap());
+                }
+                Entry::Occupied(mut occupied_entry) => {
+                    // If there was an existing style, and no declarations, we
+                    // need to remove us from the map, and ensure we're
+                    // reconstructing.
+                    if !has_declarations {
+                        damage = damage | Self::ConcreteRestyleDamage::rebuild_and_reflow();
+                        occupied_entry.remove();
+                        return;
+                    }
+
+                    // If there's a new style, we need to diff it and add the
+                    // damage, except if the damage was already
+                    // rebuild_and_reflow, in which case we can avoid it.
+                    if damage != rebuild_and_reflow {
+                        damage = damage |
+                                 self.compute_restyle_damage(Some(occupied_entry.get()),
+                                                             pseudo_style_if_declarations.as_ref().unwrap(),
+                                                             Some(&pseudo));
+                    }
+
+                    // And now, of course, use the new style.
+                    occupied_entry.insert(pseudo_style_if_declarations.unwrap());
+                }
+            }
+        });
+
+        (damage, RestyleResult::Continue)
     }
 }
 
 impl<N: TNode> MatchMethods for N {}
--- a/servo/components/style/parallel.rs
+++ b/servo/components/style/parallel.rs
@@ -6,17 +6,17 @@
 //!
 //! This code is highly unsafe. Keep this file small and easy to audit.
 
 #![allow(unsafe_code)]
 
 use dom::{OpaqueNode, TNode, UnsafeNode};
 use std::mem;
 use std::sync::atomic::Ordering;
-use traversal::DomTraversalContext;
+use traversal::{RestyleResult, DomTraversalContext};
 use workqueue::{WorkQueue, WorkUnit, WorkerProxy};
 
 #[allow(dead_code)]
 fn static_assertion(node: UnsafeNode) {
     unsafe {
         let _: UnsafeNodeList = mem::transmute(node);
     }
 }
@@ -62,47 +62,48 @@ fn top_down_dom<N, C>(unsafe_nodes: Unsa
     for unsafe_node in *unsafe_nodes.0 {
         // Get a real layout node.
         let node = unsafe { N::from_unsafe(&unsafe_node) };
 
         if !context.should_process(node) {
             continue;
         }
 
-        // Perform the appropriate traversal.
-        context.process_preorder(node);
-
         // Possibly enqueue the children.
         let mut children_to_process = 0isize;
-        for kid in node.children() {
-            // Trigger the hook pre-adding the kid to the list. This can (and in
-            // fact uses to) change the result of the should_process operation.
-            //
-            // As of right now, this hook takes care of propagating the restyle
-            // flag down the tree. In the future, more accurate behavior is
-            // probably going to be needed.
-            context.pre_process_child_hook(node, kid);
-            if context.should_process(kid) {
-                children_to_process += 1;
-                discovered_child_nodes.push(kid.to_unsafe())
+        // Perform the appropriate traversal.
+        if let RestyleResult::Continue = context.process_preorder(node) {
+            for kid in node.children() {
+                // Trigger the hook pre-adding the kid to the list. This can
+                // (and in fact uses to) change the result of the should_process
+                // operation.
+                //
+                // As of right now, this hook takes care of propagating the
+                // restyle flag down the tree. In the future, more accurate
+                // behavior is probably going to be needed.
+                context.pre_process_child_hook(node, kid);
+                if context.should_process(kid) {
+                    children_to_process += 1;
+                    discovered_child_nodes.push(kid.to_unsafe())
+                }
             }
         }
 
-        // Reset the count of children.
-        {
-            let data = node.mutate_data().unwrap();
-            data.parallel.children_to_process
+        // Reset the count of children if we need to do a bottom-up traversal
+        // after the top up.
+        if context.needs_postorder_traversal() {
+            node.mutate_data().unwrap()
+                .parallel.children_to_process
                          .store(children_to_process,
                                 Ordering::Relaxed);
-        }
 
-
-        // If there were no more children, start walking back up.
-        if children_to_process == 0 {
-            bottom_up_dom::<N, C>(unsafe_nodes.1, unsafe_node, proxy)
+            // If there were no more children, start walking back up.
+            if children_to_process == 0 {
+                bottom_up_dom::<N, C>(unsafe_nodes.1, unsafe_node, proxy)
+            }
         }
     }
 
     for chunk in discovered_child_nodes.chunks(CHUNK_SIZE) {
         proxy.push(WorkUnit {
             fun:  top_down_dom::<N, C>,
             data: (Box::new(chunk.iter().cloned().collect()), unsafe_nodes.1),
         });
@@ -118,17 +119,19 @@ fn top_down_dom<N, C>(unsafe_nodes: Unsa
 /// the whole tree bottom-up because each parent will be processed exactly
 /// once (by the last child that finishes processing).
 ///
 /// The only communication between siblings is that they both
 /// fetch-and-subtract the parent's children count.
 fn bottom_up_dom<N, C>(root: OpaqueNode,
                        unsafe_node: UnsafeNode,
                        proxy: &mut WorkerProxy<C::SharedContext, UnsafeNodeList>)
-                       where N: TNode, C: DomTraversalContext<N> {
+    where N: TNode,
+          C: DomTraversalContext<N>
+{
     let context = C::new(proxy.user_data(), root);
 
     // Get a real layout node.
     let mut node = unsafe { N::from_unsafe(&unsafe_node) };
     loop {
         // Perform the appropriate operation.
         context.process_postorder(node);
 
--- a/servo/components/style/selector_impl.rs
+++ b/servo/components/style/selector_impl.rs
@@ -74,27 +74,29 @@ pub trait ElementExt: Element<Impl=TheSe
     type Snapshot: restyle_hints::ElementSnapshot + 'static;
 
     fn is_link(&self) -> bool;
 }
 
 impl TheSelectorImpl {
     #[inline]
     pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
-        where F: FnMut(<Self as SelectorImpl>::PseudoElement) {
+        where F: FnMut(PseudoElement)
+    {
         Self::each_pseudo_element(|pseudo| {
             if Self::pseudo_element_cascade_type(&pseudo).is_eager() {
                 fun(pseudo)
             }
         })
     }
 
     #[inline]
     pub fn each_precomputed_pseudo_element<F>(mut fun: F)
-        where F: FnMut(<Self as SelectorImpl>::PseudoElement) {
+        where F: FnMut(PseudoElement)
+    {
         Self::each_pseudo_element(|pseudo| {
             if Self::pseudo_element_cascade_type(&pseudo).is_precomputed() {
                 fun(pseudo)
             }
         })
     }
 }
 
--- a/servo/components/style/sequential.rs
+++ b/servo/components/style/sequential.rs
@@ -1,37 +1,39 @@
 /* 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/. */
 
 //! Implements sequential traversal over the DOM tree.
 
 use dom::TNode;
-use traversal::DomTraversalContext;
+use traversal::{RestyleResult, DomTraversalContext};
 
 pub fn traverse_dom<N, C>(root: N,
                           shared: &C::SharedContext)
     where N: TNode,
           C: DomTraversalContext<N>
 {
     fn doit<'a, N, C>(context: &'a C, node: N)
         where N: TNode,
               C: DomTraversalContext<N>
     {
         debug_assert!(context.should_process(node));
-        context.process_preorder(node);
-
-        for kid in node.children() {
-            context.pre_process_child_hook(node, kid);
-            if context.should_process(kid) {
-                doit::<N, C>(context, kid);
+        if let RestyleResult::Continue = context.process_preorder(node) {
+            for kid in node.children() {
+                context.pre_process_child_hook(node, kid);
+                if context.should_process(kid) {
+                    doit::<N, C>(context, kid);
+                }
             }
         }
 
-        context.process_postorder(node);
+        if context.needs_postorder_traversal() {
+            context.process_postorder(node);
+        }
     }
 
     let context = C::new(shared, root.opaque());
     if context.should_process(root) {
         doit::<N, C>(&context, root);
     }
 }
 
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -13,16 +13,26 @@ use std::cell::RefCell;
 use tid::tid;
 use util::opts;
 use values::HasViewportPercentage;
 
 /// Every time we do another layout, the old bloom filters are invalid. This is
 /// detected by ticking a generation number every layout.
 pub type Generation = u32;
 
+/// This enum tells us about whether we can stop restyling or not after styling
+/// an element.
+///
+/// So far this only happens where a display: none node is found.
+pub enum RestyleResult {
+    Continue,
+    Stop,
+}
+
+
 /// A pair of the bloom filter used for css selector matching, and the node to
 /// which it applies. This is used to efficiently do `Descendant` selector
 /// matches. Thanks to the bloom filter, we can avoid walking up the tree
 /// looking for ancestors that aren't there in the majority of cases.
 ///
 /// As we walk down the DOM tree a thread-local bloom filter is built of all the
 /// CSS `SimpleSelector`s which are part of a `Descendant` compound selector
 /// (i.e. paired with a `Descendant` combinator, in the `next` field of a
@@ -136,22 +146,33 @@ pub fn remove_from_bloom_filter<'a, N, C
             let unsafe_parent = parent.to_unsafe();
             put_thread_local_bloom_filter(bf, &unsafe_parent, &context.shared_context());
         },
     };
 }
 
 pub trait DomTraversalContext<N: TNode>  {
     type SharedContext: Sync + 'static;
+
     fn new<'a>(&'a Self::SharedContext, OpaqueNode) -> Self;
+
     /// Process `node` on the way down, before its children have been processed.
-    fn process_preorder(&self, node: N);
+    fn process_preorder(&self, node: N) -> RestyleResult;
+
     /// Process `node` on the way up, after its children have been processed.
+    ///
+    /// This is only executed if `needs_postorder_traversal` returns true.
     fn process_postorder(&self, node: N);
 
+    /// Boolean that specifies whether a bottom up traversal should be
+    /// performed.
+    ///
+    /// If it's false, then process_postorder has no effect at all.
+    fn needs_postorder_traversal(&self) -> bool { true }
+
     /// Returns if the node should be processed by the preorder traversal (and
     /// then by the post-order one).
     ///
     /// Note that this is true unconditionally for servo, since it requires to
     /// bubble the widths bottom-up for all the DOM.
     fn should_process(&self, node: N) -> bool {
         node.is_dirty() || node.has_dirty_descendants()
     }
@@ -167,34 +188,109 @@ pub trait DomTraversalContext<N: TNode> 
             unsafe {
                 kid.set_dirty(true);
                 parent.set_dirty_descendants(true);
             }
         }
     }
 }
 
+pub fn ensure_node_styled<'a, N, C>(node: N,
+                                    context: &'a C)
+    where N: TNode,
+          C: StyleContext<'a>
+{
+    let mut display_none = false;
+    ensure_node_styled_internal(node, context, &mut display_none);
+}
+
+#[allow(unsafe_code)]
+fn ensure_node_styled_internal<'a, N, C>(node: N,
+                                         context: &'a C,
+                                         parents_had_display_none: &mut bool)
+    where N: TNode,
+          C: StyleContext<'a>
+{
+    use properties::longhands::display::computed_value as display;
+
+    // Ensure we have style data available. This must be done externally because
+    // there's no way to initialize the style data from the style system
+    // (because in Servo it's coupled with the layout data too).
+    //
+    // Ideally we'd have an initialize_data() or something similar but just for
+    // style data.
+    debug_assert!(node.borrow_data().is_some(),
+                  "Need to initialize the data before calling ensure_node_styled");
+
+    // We need to go to the root and ensure their style is up to date.
+    //
+    // This means potentially a bit of wasted work (usually not much). We could
+    // add a flag at the node at which point we stopped the traversal to know
+    // where should we stop, but let's not add that complication unless needed.
+    let parent = match node.parent_node() {
+        Some(parent) if parent.is_element() => Some(parent),
+        _ => None,
+    };
+
+    if let Some(parent) = parent {
+        ensure_node_styled_internal(parent, context, parents_had_display_none);
+    }
+
+    // Common case: our style is already resolved and none of our ancestors had
+    // display: none.
+    //
+    // We only need to mark whether we have display none, and forget about it,
+    // our style is up to date.
+    if let Some(ref style) = node.borrow_data().unwrap().style {
+        if !*parents_had_display_none {
+            *parents_had_display_none = style.get_box().clone_display() == display::T::none;
+            return;
+        }
+    }
+
+    // Otherwise, our style might be out of date. Time to do selector matching
+    // if appropriate and cascade the node.
+    //
+    // Note that we could add the bloom filter's complexity here, but that's
+    // probably not necessary since we're likely to be matching only a few
+    // nodes, at best.
+    let mut applicable_declarations = ApplicableDeclarations::new();
+    if let Some(element) = node.as_element() {
+        let stylist = &context.shared_context().stylist;
+
+        element.match_element(&**stylist,
+                              None,
+                              &mut applicable_declarations);
+    }
+
+    unsafe {
+        node.cascade_node(context, parent, &applicable_declarations);
+    }
+}
+
 /// Calculates the style for a single node.
 #[inline]
 #[allow(unsafe_code)]
 pub fn recalc_style_at<'a, N, C>(context: &'a C,
                                  root: OpaqueNode,
-                                 node: N)
+                                 node: N) -> RestyleResult
     where N: TNode,
-          C: StyleContext<'a> {
+          C: StyleContext<'a>
+{
     // Get the parent node.
     let parent_opt = match node.parent_node() {
         Some(parent) if parent.is_element() => Some(parent),
         _ => None,
     };
 
     // Get the style bloom filter.
     let mut bf = take_thread_local_bloom_filter(parent_opt, root, context.shared_context());
 
     let nonincremental_layout = opts::get().nonincremental_layout;
+    let mut restyle_result = RestyleResult::Continue;
     if nonincremental_layout || node.is_dirty() {
         // Remove existing CSS styles from nodes whose content has changed (e.g. text changed),
         // to force non-incremental reflow.
         if node.has_changed() {
             node.unstyle();
         }
 
         // Check to see whether we can share a style with someone.
@@ -234,27 +330,28 @@ pub fn recalc_style_at<'a, N, C>(context
                             node.set_restyle_damage(N::ConcreteRestyleDamage::rebuild_and_reflow())
                         }
                         None
                     },
                 };
 
                 // Perform the CSS cascade.
                 unsafe {
-                    node.cascade_node(context,
-                                      parent_opt,
-                                      &applicable_declarations);
+                    restyle_result = node.cascade_node(context,
+                                                    parent_opt,
+                                                    &applicable_declarations);
                 }
 
                 // Add ourselves to the LRU cache.
                 if let Some(element) = shareable_element {
                     style_sharing_candidate_cache.insert_if_possible::<'ln, N>(&element);
                 }
             }
-            StyleSharingResult::StyleWasShared(index, damage) => {
+            StyleSharingResult::StyleWasShared(index, damage, restyle_result_cascade) => {
+                restyle_result = restyle_result_cascade;
                 style_sharing_candidate_cache.touch(index);
                 node.set_restyle_damage(damage);
             }
         }
     } else {
         // Finish any expired transitions.
         animation::complete_expired_transitions(
             node.opaque(),
@@ -281,9 +378,15 @@ pub fn recalc_style_at<'a, N, C>(context
                 if property_declaration_block.declarations().any(|d| d.0.has_viewport_percentage()) {
                     unsafe {
                         node.set_dirty_on_viewport_size_changed();
                     }
                 }
             }
         }
     }
+
+    if nonincremental_layout {
+        RestyleResult::Continue
+    } else {
+        restyle_result
+    }
 }
--- a/servo/ports/geckolib/traversal.rs
+++ b/servo/ports/geckolib/traversal.rs
@@ -1,16 +1,17 @@
 /* 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 context::StandaloneStyleContext;
 use std::mem;
 use style::context::SharedStyleContext;
 use style::dom::OpaqueNode;
+use style::traversal::RestyleResult;
 use style::traversal::{DomTraversalContext, recalc_style_at};
 use wrapper::GeckoNode;
 
 pub struct RecalcStyleOnly<'lc> {
     context: StandaloneStyleContext<'lc>,
     root: OpaqueNode,
 }
 
@@ -22,18 +23,23 @@ impl<'lc, 'ln> DomTraversalContext<Gecko
         // necessary.
         let shared_lc: &'lc Self::SharedContext = unsafe { mem::transmute(shared) };
         RecalcStyleOnly {
             context: StandaloneStyleContext::new(shared_lc),
             root: root,
         }
     }
 
-    fn process_preorder(&self, node: GeckoNode<'ln>) {
+    fn process_preorder(&self, node: GeckoNode<'ln>) -> RestyleResult {
         // FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML
         // parser.
         node.initialize_data();
 
-        recalc_style_at(&self.context, self.root, node);
+        recalc_style_at(&self.context, self.root, node)
     }
 
-    fn process_postorder(&self, _: GeckoNode<'ln>) {}
+    fn process_postorder(&self, _: GeckoNode<'ln>) {
+        unreachable!();
+    }
+
+    /// We don't use the post-order traversal for anything.
+    fn needs_postorder_traversal(&self) -> bool { false }
 }
--- a/servo/ports/geckolib/wrapper.rs
+++ b/servo/ports/geckolib/wrapper.rs
@@ -39,17 +39,17 @@ use std::ops::BitOr;
 use std::ptr;
 use std::sync::Arc;
 use style::data::PrivateStyleData;
 use style::dom::{OpaqueNode, PresentationalHintsSynthetizer};
 use style::dom::{TDocument, TElement, TNode, TRestyleDamage, UnsafeNode};
 use style::element_state::ElementState;
 use style::error_reporting::StdoutErrorReporter;
 use style::gecko_glue::ArcHelpers;
-use style::gecko_selector_impl::{GeckoSelectorImpl, NonTSPseudoClass};
+use style::gecko_selector_impl::{GeckoSelectorImpl, NonTSPseudoClass, PseudoElement};
 use style::parser::ParserContextExtraData;
 use style::properties::{ComputedValues, parse_style_attribute};
 use style::properties::{PropertyDeclaration, PropertyDeclarationBlock};
 use style::refcell::{Ref, RefCell, RefMut};
 use style::selector_impl::ElementExt;
 use style::sink::Push;
 use url::Url;
 
@@ -90,28 +90,31 @@ impl<'ln> GeckoNode<'ln> {
             if self.get_node_data().is_null() {
                 let ptr: NonOpaqueStyleDataPtr = Box::into_raw(Box::new(RefCell::new(PrivateStyleData::new())));
                 Gecko_SetNodeData(self.node, ptr as *mut ServoNodeData);
             }
         }
     }
 }
 
-#[derive(Clone, Copy)]
+#[derive(Clone, Copy, Debug, PartialEq)]
 pub struct GeckoRestyleDamage(nsChangeHint);
 
 impl TRestyleDamage for GeckoRestyleDamage {
     type PreExistingComputedValues = nsStyleContext;
-    fn compute(source: Option<&nsStyleContext>,
+
+    fn empty() -> Self {
+        use std::mem;
+        GeckoRestyleDamage(unsafe { mem::transmute(0u32) })
+    }
+
+    fn compute(source: &nsStyleContext,
                new_style: &Arc<ComputedValues>) -> Self {
         type Helpers = ArcHelpers<ServoComputedValues, ComputedValues>;
-        let context = match source {
-            Some(ctx) => ctx as *const nsStyleContext as *mut nsStyleContext,
-            None => return Self::rebuild_and_reflow(),
-        };
+        let context = source as *const nsStyleContext as *mut nsStyleContext;
 
         Helpers::borrow(new_style, |new_style| {
             let hint = unsafe { Gecko_CalcStyleDifference(context, new_style) };
             GeckoRestyleDamage(hint)
         })
     }
 
     fn rebuild_and_reflow() -> Self {
@@ -189,20 +192,19 @@ impl<'ln> TNode for GeckoNode<'ln> {
             None
         }
     }
 
     fn as_document(&self) -> Option<GeckoDocument<'ln>> {
         unimplemented!()
     }
 
-    fn has_changed(&self) -> bool {
-        // FIXME(bholley) - Implement this to allow incremental reflows!
-        true
-    }
+    // NOTE: This is not relevant for Gecko, since we get explicit restyle hints
+    // when a content has changed.
+    fn has_changed(&self) -> bool { false }
 
     unsafe fn set_changed(&self, _value: bool) {
         unimplemented!()
     }
 
     fn is_dirty(&self) -> bool {
         // Return true unconditionally if we're not yet styled. This is a hack
         // and should go away soon.
@@ -305,23 +307,31 @@ impl<'ln> TNode for GeckoNode<'ln> {
 
     fn next_sibling(&self) -> Option<GeckoNode<'ln>> {
         unsafe {
             Gecko_GetNextSibling(self.node).as_ref().map(|n| GeckoNode::from_ref(n))
         }
     }
 
     fn existing_style_for_restyle_damage<'a>(&'a self,
-                                             current_cv: Option<&'a Arc<ComputedValues>>)
+                                             current_cv: Option<&'a Arc<ComputedValues>>,
+                                             pseudo: Option<&PseudoElement>)
                                              -> Option<&'a nsStyleContext> {
         if current_cv.is_none() {
             // Don't bother in doing an ffi call to get null back.
             return None;
         }
 
+        if pseudo.is_some() {
+            // FIXME(emilio): This makes us reconstruct frame for pseudos every
+            // restyle, add a FFI call to get the style context associated with
+            // a PE.
+            return None;
+        }
+
         unsafe {
             let context_ptr = Gecko_GetStyleContext(self.node);
             context_ptr.as_ref()
         }
     }
 
     fn needs_dirty_on_viewport_size_changed(&self) -> bool {
         // Gecko's node doesn't have the DIRTY_ON_VIEWPORT_SIZE_CHANGE flag,