servo: Merge #14751 - style: Add a special, explicit path for lazy style resolution and use it for getComputedStyle (from heycam:transient); r=heycam
authorBobby Holley <bobbyholley@gmail.com>
Tue, 27 Dec 2016 19:55:01 -0800
changeset 478600 50b3f89cc07050ac88173b769f45a24da24f57fe
parent 478599 7ad8391097b045c4aed972b1fa8f967c4c451f5c
child 478601 4af226cf8caa446a7a68b37484fab465d26f9f90
push id44079
push userbmo:gps@mozilla.com
push dateSat, 04 Feb 2017 00:14:49 +0000
reviewersheycam
servo: Merge #14751 - style: Add a special, explicit path for lazy style resolution and use it for getComputedStyle (from heycam:transient); r=heycam <!-- Please describe your changes on the following line: --> This is the Servo-side part of @bholley's final patch of https://bugzilla.mozilla.org/show_bug.cgi?id=1324627, which I've already r+ed. Source-Repo: https://github.com/servo/servo Source-Revision: f36b5531cb7a28036bdb29cf5619ec0da1030849
servo/components/layout/query.rs
servo/components/style/build_gecko.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/gecko_bindings/bindings.rs
servo/components/style/matching.rs
servo/components/style/traversal.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/layout/query.rs
+++ b/servo/components/layout/query.rs
@@ -1,17 +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/. */
 
 //! Utilities for querying the layout, as needed by the layout thread.
 
 use app_units::Au;
 use construct::ConstructionResult;
-use context::{ScopedThreadLocalLayoutContext, SharedLayoutContext};
+use context::SharedLayoutContext;
 use euclid::point::Point2D;
 use euclid::rect::Rect;
 use euclid::size::Size2D;
 use flow::{self, Flow};
 use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
 use gfx::display_list::{DisplayItemMetadata, DisplayList, OpaqueNode, ScrollOffsetMap};
 use gfx_traits::ScrollRootId;
 use ipc_channel::ipc::IpcSender;
@@ -24,17 +24,17 @@ use script_layout_interface::rpc::{NodeS
 use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
 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 style::computed_values;
-use style::context::StyleContext;
+use style::context::{StyleContext, ThreadLocalStyleContext};
 use style::dom::TElement;
 use style::logical_geometry::{WritingMode, BlockFlowDirection, InlineBaseDirection};
 use style::properties::{style_structs, PropertyId, PropertyDeclarationId, LonghandId};
 use style::properties::longhands::{display, position};
 use style::selector_parser::PseudoElement;
 use style::stylist::Stylist;
 use style_traits::ToCss;
 use style_traits::cursor::Cursor;
@@ -621,47 +621,56 @@ pub fn process_node_scroll_area_request<
                       Size2D::new(right, iterator.origin_rect.size.height))
         }
     }
 }
 
 /// Return the resolved value of property for a given (pseudo)element.
 /// https://drafts.csswg.org/cssom/#resolved-value
 pub fn process_resolved_style_request<'a, N>(shared: &SharedLayoutContext,
-                                             requested_node: N,
+                                             node: N,
                                              pseudo: &Option<PseudoElement>,
                                              property: &PropertyId,
                                              layout_root: &mut Flow) -> String
     where N: LayoutNode,
 {
-    use style::traversal::{clear_descendant_data, style_element_in_display_none_subtree};
-    let element = requested_node.as_element().unwrap();
+    use style::traversal::resolve_style;
+    let element = node.as_element().unwrap();
 
     // We call process_resolved_style_request after performing a whole-document
-    // traversal, so the only reason we wouldn't have an up-to-date style here
-    // is that the requested node is in a display:none subtree. We currently
-    // maintain the invariant that elements in display:none subtrees always have
-    // no ElementData, so we need to temporarily bend those invariants here, and
-    // then throw them the style data away again before returning to preserve them.
-    // We could optimize this later to keep the style data cached somehow, but
-    // we'd need a mechanism to prevent detect when it's stale (since we don't
-    // traverse display:none subtrees during restyle).
-    let display_none_root = if element.get_data().is_none() {
-        let mut tlc = ScopedThreadLocalLayoutContext::new(shared);
-        let context = StyleContext {
-            shared: &shared.style_context,
-            thread_local: &mut tlc.style_context,
-        };
+    // traversal, so in the common case, the element is styled.
+    if element.get_data().is_some() {
+        return process_resolved_style_request_internal(node, pseudo, property, layout_root);
+    }
 
-        Some(style_element_in_display_none_subtree(&context, element,
-                                                   &|e| e.as_node().initialize_data()))
-    } else {
-        None
+    // However, the element may be in a display:none subtree. The style system
+    // has a mechanism to give us that within a defined scope (after which point
+    // it's cleared to maintained style system invariants).
+    let mut tlc = ThreadLocalStyleContext::new(&shared.style_context);
+    let context = StyleContext {
+        shared: &shared.style_context,
+        thread_local: &mut tlc,
     };
+    let mut result = None;
+    let ensure = |el: N::ConcreteElement| el.as_node().initialize_data();
+    let clear = |el: N::ConcreteElement| el.as_node().clear_data();
+    resolve_style(&context, element, &ensure, &clear, |_: &_| {
+        let s = process_resolved_style_request_internal(node, pseudo, property, layout_root);
+        result = Some(s);
+    });
+    result.unwrap()
+}
 
+/// The primary resolution logic, which assumes that the element is styled.
+fn process_resolved_style_request_internal<'a, N>(requested_node: N,
+                                                  pseudo: &Option<PseudoElement>,
+                                                  property: &PropertyId,
+                                                  layout_root: &mut Flow) -> String
+    where N: LayoutNode,
+{
     let layout_el = requested_node.to_threadsafe().as_element().unwrap();
     let layout_el = match *pseudo {
         Some(PseudoElement::Before) => layout_el.get_before_pseudo(),
         Some(PseudoElement::After) => layout_el.get_after_pseudo(),
         Some(PseudoElement::DetailsSummary) |
         Some(PseudoElement::DetailsContent) |
         Some(PseudoElement::Selection) => None,
         _ => Some(layout_el)
@@ -686,20 +695,16 @@ pub fn process_resolved_style_request<'a
         // so this should be web-compatible.
         PropertyId::Shorthand(_) => return String::new(),
 
         PropertyId::Custom(ref name) => {
             return style.computed_value_to_string(PropertyDeclarationId::Custom(name))
         }
     };
 
-    // Clear any temporarily-resolved data to maintain our invariants. See the comment
-    // at the top of this function.
-    display_none_root.map(|r| clear_descendant_data(r, &|e| e.as_node().clear_data()));
-
     let positioned = match style.get_box().position {
         position::computed_value::T::relative |
         /*position::computed_value::T::sticky |*/
         position::computed_value::T::fixed |
         position::computed_value::T::absolute => true,
         _ => false
     };
 
--- a/servo/components/style/build_gecko.rs
+++ b/servo/components/style/build_gecko.rs
@@ -219,17 +219,16 @@ mod bindings {
             "NS_RADIUS_.*",
             "BORDER_COLOR_.*",
             "BORDER_STYLE_.*"
         ];
         let whitelist = [
             "RawGecko.*",
             "mozilla::ServoElementSnapshot.*",
             "mozilla::ConsumeStyleBehavior",
-            "mozilla::LazyComputeBehavior",
             "mozilla::css::SheetParsingMode",
             "mozilla::TraversalRootBehavior",
             "mozilla::DisplayItemClip",  // Needed because bindgen generates
                                          // specialization tests for this even
                                          // though it shouldn't.
             "mozilla::StyleShapeRadius",
             ".*ThreadSafe.*Holder",
             "AnonymousContent",
@@ -246,17 +245,16 @@ mod bindings {
             "FragmentOrURL",
             "FrameRequestCallback",
             "gfxAlternateValue",
             "gfxFontFeature",
             "gfxFontVariation",
             "GridNamedArea",
             "Image",
             "ImageURL",
-            "LazyComputeBehavior",
             "nsAttrName",
             "nsAttrValue",
             "nsBorderColors",
             "nscolor",
             "nsChangeHint",
             "nsCSSKeyword",
             "nsCSSPropertyID",
             "nsCSSRect",
@@ -446,17 +444,16 @@ mod bindings {
             .whitelisted_function("Gecko_.*");
         let structs_types = [
             "RawGeckoDocument",
             "RawGeckoElement",
             "RawGeckoNode",
             "ThreadSafeURIHolder",
             "ThreadSafePrincipalHolder",
             "ConsumeStyleBehavior",
-            "LazyComputeBehavior",
             "TraversalRootBehavior",
             "FontFamilyList",
             "FontFamilyType",
             "ServoElementSnapshot",
             "SheetParsingMode",
             "StyleBasicShape",
             "StyleBasicShapeType",
             "StyleClipPath",
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -255,24 +255,16 @@ impl<'le> GeckoElement<'le> {
 
             // Perform a mutable borrow of the data in debug builds. This
             // serves as an assertion that there are no outstanding borrows
             // when we destroy the data.
             debug_assert!({ let _ = data.borrow_mut(); true });
         }
     }
 
-    pub fn get_pseudo_style(&self, pseudo: &PseudoElement) -> Option<Arc<ComputedValues>> {
-        // FIXME(bholley): Gecko sometimes resolves pseudos after an element has
-        // already been marked for restyle. We should consider fixing this, and
-        // then assert has_current_styles here.
-        self.borrow_data().and_then(|data| data.styles().pseudos
-                                               .get(pseudo).map(|c| c.values.clone()))
-    }
-
     // Only safe to call with exclusive access to the element.
     pub unsafe fn ensure_data(&self) -> &AtomicRefCell<ElementData> {
         match self.get_data() {
             Some(x) => x,
             None => {
                 debug!("Creating ElementData for {:?}", self);
                 let ptr = Box::into_raw(Box::new(AtomicRefCell::new(ElementData::new(None))));
                 self.0.mServoData.set(ptr);
--- a/servo/components/style/gecko_bindings/bindings.rs
+++ b/servo/components/style/gecko_bindings/bindings.rs
@@ -4,17 +4,16 @@ pub use nsstring::{nsACString, nsAString
 type nsACString_internal = nsACString;
 type nsAString_internal = nsAString;
 use gecko_bindings::structs::RawGeckoDocument;
 use gecko_bindings::structs::RawGeckoElement;
 use gecko_bindings::structs::RawGeckoNode;
 use gecko_bindings::structs::ThreadSafeURIHolder;
 use gecko_bindings::structs::ThreadSafePrincipalHolder;
 use gecko_bindings::structs::ConsumeStyleBehavior;
-use gecko_bindings::structs::LazyComputeBehavior;
 use gecko_bindings::structs::TraversalRootBehavior;
 use gecko_bindings::structs::FontFamilyList;
 use gecko_bindings::structs::FontFamilyType;
 use gecko_bindings::structs::ServoElementSnapshot;
 use gecko_bindings::structs::SheetParsingMode;
 use gecko_bindings::structs::StyleBasicShape;
 use gecko_bindings::structs::StyleBasicShapeType;
 use gecko_bindings::structs::StyleClipPath;
@@ -1204,28 +1203,33 @@ extern "C" {
                                    change_hint: nsChangeHint);
 }
 extern "C" {
     pub fn Servo_CheckChangeHint(element: RawGeckoElementBorrowed)
      -> nsChangeHint;
 }
 extern "C" {
     pub fn Servo_ResolveStyle(element: RawGeckoElementBorrowed,
-                              set: RawServoStyleSetBorrowed,
-                              consume: ConsumeStyleBehavior,
-                              compute: LazyComputeBehavior)
+                              consume: ConsumeStyleBehavior)
      -> ServoComputedValuesStrong;
 }
 extern "C" {
     pub fn Servo_ResolvePseudoStyle(element: RawGeckoElementBorrowed,
                                     pseudo_tag: *mut nsIAtom, is_probe: bool,
                                     set: RawServoStyleSetBorrowed)
      -> ServoComputedValuesStrong;
 }
 extern "C" {
+    pub fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
+                                    pseudo_tag: *mut nsIAtom,
+                                    consume: ConsumeStyleBehavior,
+                                    set: RawServoStyleSetBorrowed)
+     -> ServoComputedValuesStrong;
+}
+extern "C" {
     pub fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed,
                                  set: RawServoStyleSetBorrowed,
                                  root_behavior: TraversalRootBehavior);
 }
 extern "C" {
     pub fn Servo_AssertTreeIsClean(root: RawGeckoElementBorrowed);
 }
 extern "C" {
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -704,23 +704,23 @@ pub trait MatchMethods : TElement {
                 // stick without the assertions.
                 debug_assert!(pseudo.is_none() ||
                               new_style.get_box().clone_display() != display::T::none);
                 RestyleDamage::rebuild_and_reflow()
             }
         }
     }
 
-    unsafe fn cascade_node(&self,
-                           context: &StyleContext<Self>,
-                           mut data: &mut AtomicRefMut<ElementData>,
-                           parent: Option<Self>,
-                           primary_rule_node: StrongRuleNode,
-                           pseudo_rule_nodes: PseudoRuleNodes,
-                           primary_is_shareable: bool)
+    fn cascade_node(&self,
+                    context: &StyleContext<Self>,
+                    mut data: &mut AtomicRefMut<ElementData>,
+                    parent: Option<Self>,
+                    primary_rule_node: StrongRuleNode,
+                    pseudo_rule_nodes: PseudoRuleNodes,
+                    primary_is_shareable: bool)
     {
         // Get our parent's style.
         let parent_data = parent.as_ref().map(|x| x.borrow_data().unwrap());
         let parent_style = parent_data.as_ref().map(|d| {
             // Sometimes Gecko eagerly styles things without processing pending
             // restyles first. In general we'd like to avoid this, but there can
             // be good reasons (for example, needing to construct a frame for
             // some small piece of newly-added content in order to do something
@@ -765,20 +765,22 @@ pub trait MatchMethods : TElement {
                 self.compute_damage_and_cascade_pseudos(old_primary,
                                                         old_pseudos,
                                                         &new_styles.primary.values,
                                                         &mut new_styles.pseudos,
                                                         context,
                                                         pseudo_rule_nodes,
                                                         &mut possibly_expired_animations);
 
-            self.as_node().set_can_be_fragmented(parent.map_or(false, |p| {
-                p.as_node().can_be_fragmented() ||
-                parent_style.unwrap().is_multicol()
-            }));
+            unsafe {
+                self.as_node().set_can_be_fragmented(parent.map_or(false, |p| {
+                    p.as_node().can_be_fragmented() ||
+                    parent_style.unwrap().is_multicol()
+                }));
+            }
 
             damage
         };
 
         data.finish_styling(new_styles, damage);
     }
 
     fn compute_damage_and_cascade_pseudos(
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -1,17 +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/. */
 
 //! Traversing the DOM tree; the bloom filter.
 
 use atomic_refcell::{AtomicRefCell, AtomicRefMut};
 use context::{SharedStyleContext, StyleContext};
-use data::{ElementData, StoredRestyleHint};
+use data::{ElementData, ElementStyles, StoredRestyleHint};
 use dom::{TElement, TNode};
 use matching::{MatchMethods, StyleSharingResult};
 use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF};
 use selector_parser::RestyleDamage;
 use selectors::Element;
 use selectors::matching::StyleRelations;
 use servo_config::opts;
 use std::mem;
@@ -260,50 +260,87 @@ pub fn relations_are_shareable(relations
     use selectors::matching::*;
     !relations.intersects(AFFECTED_BY_ID_SELECTOR |
                           AFFECTED_BY_PSEUDO_ELEMENTS | AFFECTED_BY_STATE |
                           AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR |
                           AFFECTED_BY_STYLE_ATTRIBUTE |
                           AFFECTED_BY_PRESENTATIONAL_HINTS)
 }
 
-/// Handles lazy resolution of style in display:none subtrees. See the comment
-/// at the callsite in query.rs.
-pub fn style_element_in_display_none_subtree<E, F>(context: &StyleContext<E>,
-                                                   element: E, init_data: &F) -> E
+/// Helper for the function below.
+fn resolve_style_internal<E, F>(context: &StyleContext<E>, element: E, ensure_data: &F)
+                                -> Option<E>
     where E: TElement,
           F: Fn(E),
 {
-    // Check the base case.
-    if element.get_data().is_some() {
-        // See the comment on `cascade_node` for why we allow this on Gecko.
-        debug_assert!(cfg!(feature = "gecko") || element.borrow_data().unwrap().has_current_styles());
-        debug_assert!(element.borrow_data().unwrap().styles().is_display_none());
-        return element;
-    }
+    ensure_data(element);
+    let mut data = element.mutate_data().unwrap();
+    let mut display_none_root = None;
 
-    // Ensure the parent is styled.
-    let parent = element.parent_element().unwrap();
-    let display_none_root = style_element_in_display_none_subtree(context, parent, init_data);
-
-    // Initialize our data.
-    init_data(element);
+    // If the Element isn't styled, we need to compute its style.
+    if data.get_styles().is_none() {
+        // Compute the parent style if necessary.
+        if let Some(parent) = element.parent_element() {
+            display_none_root = resolve_style_internal(context, parent, ensure_data);
+        }
 
-    // Resolve our style.
-    let mut data = element.mutate_data().unwrap();
-    let match_results = element.match_element(context, None);
-    unsafe {
+        // Compute our style.
+        let match_results = element.match_element(context, None);
         let shareable = match_results.primary_is_shareable();
-        element.cascade_node(context, &mut data, Some(parent),
+        element.cascade_node(context, &mut data, element.parent_element(),
                              match_results.primary,
                              match_results.per_pseudo,
                              shareable);
+
+        // Conservatively mark us as having dirty descendants, since there might
+        // be other unstyled siblings we miss when walking straight up the parent
+        // chain.
+        unsafe { element.set_dirty_descendants() };
+    }
+
+    // If we're display:none and none of our ancestors are, we're the root
+    // of a display:none subtree.
+    if display_none_root.is_none() && data.styles().is_display_none() {
+        display_none_root = Some(element);
     }
 
-    display_none_root
+    return display_none_root
+}
+
+/// Manually resolve style by sequentially walking up the parent chain to the
+/// first styled Element, ignoring pending restyles. The resolved style is
+/// made available via a callback, and can be dropped by the time this function
+/// returns in the display:none subtree case.
+pub fn resolve_style<E, F, G, H>(context: &StyleContext<E>, element: E,
+                                 ensure_data: &F, clear_data: &G, callback: H)
+    where E: TElement,
+          F: Fn(E),
+          G: Fn(E),
+          H: FnOnce(&ElementStyles)
+{
+    // Resolve styles up the tree.
+    let display_none_root = resolve_style_internal(context, element, ensure_data);
+
+    // Make them available for the scope of the callback. The callee may use the
+    // argument, or perform any other processing that requires the styles to exist
+    // on the Element.
+    callback(element.borrow_data().unwrap().styles());
+
+    // Clear any styles in display:none subtrees to leave the tree in a valid state.
+    if let Some(root) = display_none_root {
+        let mut curr = element;
+        loop {
+            unsafe { curr.unset_dirty_descendants(); }
+            if curr == root {
+                break;
+            }
+            clear_data(curr);
+            curr = curr.parent_element().unwrap();
+        }
+    }
 }
 
 /// Calculates the style for a single node.
 #[inline]
 #[allow(unsafe_code)]
 pub fn recalc_style_at<E, D>(traversal: &D,
                              traversal_data: &mut PerLevelTraversalData,
                              context: &mut StyleContext<E>,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -53,17 +53,17 @@ use style::properties::{PropertyDeclarat
 use style::properties::{apply_declarations, parse_one_declaration};
 use style::restyle_hints::RestyleHint;
 use style::selector_parser::PseudoElementCascadeType;
 use style::sequential;
 use style::string_cache::Atom;
 use style::stylesheets::{CssRule, CssRules, Origin, Stylesheet, StyleRule};
 use style::thread_state;
 use style::timer::Timer;
-use style::traversal::{recalc_style_at, DomTraversal, PerLevelTraversalData};
+use style::traversal::{resolve_style, DomTraversal};
 use style_traits::ToCss;
 use stylesheet_loader::StylesheetLoader;
 
 /*
  * For Gecko->Servo function calls, we need to redeclare the same signature that was declared in
  * the C header in Gecko. In order to catch accidental mismatches, we run rust-bindgen against
  * those signatures as well, giving us a second declaration of all the Servo_* functions in this
  * crate. If there's a mismatch, LLVM will assert and abort, which is a rather awful thing to
@@ -854,61 +854,24 @@ pub extern "C" fn Servo_CheckChangeHint(
     }
 
     debug!("Servo_GetChangeHint: {:?}, damage={:?}", element, damage);
     damage.as_change_hint()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed,
-                                     raw_data: RawServoStyleSetBorrowed,
-                                     consume: structs::ConsumeStyleBehavior,
-                                     compute: structs::LazyComputeBehavior) -> ServoComputedValuesStrong
+                                     consume: structs::ConsumeStyleBehavior)
+                                     -> ServoComputedValuesStrong
 {
     let element = GeckoElement(element);
-    debug!("Servo_ResolveStyle: {:?}, consume={:?}, compute={:?}", element, consume, compute);
+    debug!("Servo_ResolveStyle: {:?}, consume={:?}", element, consume);
 
     let mut data = unsafe { element.ensure_data() }.borrow_mut();
 
-    if compute == structs::LazyComputeBehavior::Allow {
-        let should_compute = !data.has_current_styles();
-        if should_compute {
-            debug!("Performing manual style computation");
-            if let Some(parent) = element.parent_element() {
-                if parent.borrow_data().map_or(true, |d| !d.has_current_styles()) {
-                    error!("Attempting manual style computation with unstyled parent");
-                    return Arc::new(ComputedValues::initial_values().clone()).into_strong();
-                }
-            }
-
-            let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
-            let shared_style_context = create_shared_context(&per_doc_data);
-            let traversal = RecalcStyleOnly::new(shared_style_context);
-
-            let mut traversal_data = PerLevelTraversalData {
-                current_dom_depth: None,
-            };
-
-            let mut tlc = ThreadLocalStyleContext::new(traversal.shared_context());
-            let mut context = StyleContext {
-                shared: traversal.shared_context(),
-                thread_local: &mut tlc,
-            };
-
-            recalc_style_at(&traversal, &mut traversal_data, &mut context, element, &mut data);
-
-            // The element was either unstyled or needed restyle. If it was unstyled, it may have
-            // additional unstyled children that subsequent traversals won't find now that the style
-            // on this element is up-to-date. Mark dirty descendants in that case.
-            if element.first_child_element().is_some() {
-                unsafe { element.set_dirty_descendants() };
-            }
-        }
-    }
-
     if !data.has_current_styles() {
         error!("Resolving style on unstyled element with lazy computation forbidden.");
         return Arc::new(ComputedValues::initial_values().clone()).into_strong();
     }
 
     let values = data.styles().primary.values.clone();
 
     if consume == structs::ConsumeStyleBehavior::Consume {
@@ -916,16 +879,73 @@ pub extern "C" fn Servo_ResolveStyle(ele
         // drop the data here instead.
         data.persist();
     }
 
     values.into_strong()
 }
 
 #[no_mangle]
+pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
+                                           pseudo_tag: *mut nsIAtom,
+                                           consume: structs::ConsumeStyleBehavior,
+                                           raw_data: RawServoStyleSetBorrowed)
+     -> ServoComputedValuesStrong
+{
+    let element = GeckoElement(element);
+    let doc_data = PerDocumentStyleData::from_ffi(raw_data);
+    let finish = |styles: &ElementStyles| -> Arc<ComputedValues> {
+        let maybe_pseudo = if !pseudo_tag.is_null() {
+            get_pseudo_style(element, pseudo_tag, styles, doc_data)
+        } else {
+            None
+        };
+        maybe_pseudo.unwrap_or_else(|| styles.primary.values.clone())
+    };
+
+    // In the common case we already have the style. Check that before setting
+    // up all the computation machinery.
+    let mut result = element.mutate_data()
+                            .and_then(|d| d.get_styles().map(&finish));
+    if result.is_some() {
+        if consume == structs::ConsumeStyleBehavior::Consume {
+            let mut d = element.mutate_data().unwrap();
+            if !d.is_persistent() {
+                // XXXheycam is it right to persist an ElementData::Restyle?
+                // Couldn't we lose restyle hints that would cause us to
+                // restyle descendants?
+                d.persist();
+            }
+        }
+        return result.unwrap().into_strong();
+    }
+
+    // We don't have the style ready. Go ahead and compute it as necessary.
+    let shared = create_shared_context(&mut doc_data.borrow_mut());
+    let mut tlc = ThreadLocalStyleContext::new(&shared);
+    let mut context = StyleContext {
+        shared: &shared,
+        thread_local: &mut tlc,
+    };
+    let ensure = |el: GeckoElement| { unsafe { el.ensure_data(); } };
+    let clear = |el: GeckoElement| el.clear_data();
+    resolve_style(&mut context, element, &ensure, &clear,
+                  |styles| result = Some(finish(styles)));
+
+    // Consume the style if requested, though it may not exist anymore if the
+    // element is in a display:none subtree.
+    if consume == structs::ConsumeStyleBehavior::Consume {
+        element.mutate_data().map(|mut d| d.persist());
+    }
+
+    result.unwrap().into_strong()
+}
+
+
+#[no_mangle]
 pub extern "C" fn Servo_AssertTreeIsClean(root: RawGeckoElementBorrowed) {
     if !cfg!(debug_assertions) {
         panic!("Calling Servo_AssertTreeIsClean in release build");
     }
 
     let root = GeckoElement(root);
     fn assert_subtree_is_clean<'le>(el: GeckoElement<'le>) {
         debug_assert!(!el.has_dirty_descendants());