Bug 1397644 part 1 - Implement XUL tree pseudo style resolution for stylo. r?emilio draft
authorXidorn Quan <me@upsuper.org>
Thu, 19 Oct 2017 14:54:08 +1100
changeset 683745 9b33c3d14c7d0e0ef535a2dca3fbe236cc22cb6f
parent 683744 bbff59f2b23db52205af71f6751a580257d9ff63
child 683746 1ffdec249b398504d0b7c28d66ebb07b48641021
push id85454
push userxquan@mozilla.com
push dateFri, 20 Oct 2017 05:32:42 +0000
reviewersemilio
bugs1397644
milestone58.0a1
Bug 1397644 part 1 - Implement XUL tree pseudo style resolution for stylo. r?emilio MozReview-Commit-ID: 5JqvLv3tt5m
layout/style/ServoBindingList.h
layout/style/ServoBindings.h
layout/style/ServoBindings.toml
layout/style/ServoStyleSet.cpp
layout/style/ServoStyleSet.h
layout/style/StyleSetHandle.h
layout/style/StyleSetHandleInlines.h
layout/xul/tree/nsTreeStyleCache.cpp
servo/components/script_layout_interface/wrapper_traits.rs
servo/components/selectors/context.rs
servo/components/selectors/matching.rs
servo/components/style/gecko/generated/bindings.rs
servo/components/style/gecko/generated/pseudo_element_definition.rs
servo/components/style/gecko/generated/structs.rs
servo/components/style/gecko/pseudo_element_definition.mako.rs
servo/components/style/stylist.rs
servo/ports/geckolib/glue.rs
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -578,16 +578,23 @@ SERVO_BINDING_FUNC(Servo_ResolveStyle, S
                    RawGeckoElementBorrowed element,
                    RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_ResolvePseudoStyle, ServoStyleContextStrong,
                    RawGeckoElementBorrowed element,
                    mozilla::CSSPseudoElementType pseudo_type,
                    bool is_probe,
                    ServoStyleContextBorrowedOrNull inherited_style,
                    RawServoStyleSetBorrowed set)
+SERVO_BINDING_FUNC(Servo_ComputedValues_ResolveXULTreePseudoStyle,
+                   ServoStyleContextStrong,
+                   RawGeckoElementBorrowed element,
+                   nsAtom* pseudo_tag,
+                   ServoStyleContextBorrowed inherited_style,
+                   const mozilla::AtomArray* input_word,
+                   RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_SetExplicitStyle, void,
                    RawGeckoElementBorrowed element,
                    ServoStyleContextBorrowed primary_style)
 SERVO_BINDING_FUNC(Servo_HasAuthorSpecifiedRules, bool,
                    ServoStyleContextBorrowed style,
                    RawGeckoElementBorrowed element,
                    mozilla::CSSPseudoElementType pseudo_type,
                    uint32_t rule_type_mask,
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_ServoBindings_h
 #define mozilla_ServoBindings_h
 
 #include <stdint.h>
 
+#include "mozilla/AtomArray.h"
 #include "mozilla/ServoTypes.h"
 #include "mozilla/ServoBindingTypes.h"
 #include "mozilla/ServoElementSnapshot.h"
 #include "mozilla/css/SheetParsingMode.h"
 #include "mozilla/css/URLMatchingFunction.h"
 #include "mozilla/EffectCompositor.h"
 #include "mozilla/ComputedTimingFunction.h"
 #include "nsChangeHint.h"
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -137,16 +137,17 @@ whitelist-vars = [
     "kNameSpaceID_.*",
     "kGenericFont_.*",
     "kPresContext_.*",
 ]
 whitelist-types = [
     "RawGecko.*",
     "mozilla::AnimationPropertySegment",
     "mozilla::AnonymousCounterStyle",
+    "mozilla::AtomArray",
     "mozilla::ComputedTiming",
     "mozilla::ComputedTimingFunction",
     "mozilla::ComputedTimingFunction::BeforeFlag",
     "mozilla::SeenPtrs",
     "mozilla::ServoElementSnapshot.*",
     "mozilla::ServoStyleContext",
     "mozilla::ServoStyleSheetInner",
     "mozilla::ServoStyleSetSizes",
@@ -385,16 +386,17 @@ raw-lines = [
 whitelist-functions = ["Servo_.*", "Gecko_.*"]
 structs-types = [
     "mozilla::css::GridTemplateAreasValue",
     "mozilla::css::ErrorReporter",
     "mozilla::css::ImageValue",
     "mozilla::css::URLValue",
     "mozilla::css::URLValueData",
     "mozilla::AnonymousCounterStyle",
+    "mozilla::AtomArray",
     "mozilla::MallocSizeOf",
     "mozilla::OriginFlags",
     "mozilla::UniquePtr",
     "ServoRawOffsetArc",
     "nsIContent",
     "nsIDocument",
     "nsIDocument_DocumentTheme",
     "RawGeckoAnimationPropertySegment",
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -696,16 +696,37 @@ ServoStyleSet::ResolveNonInheritingAnony
     MOZ_CRASH();
   }
 #endif
 
   cache = computedValues;
   return computedValues.forget();
 }
 
+#ifdef MOZ_XUL
+already_AddRefed<ServoStyleContext>
+ServoStyleSet::ResolveXULTreePseudoStyle(dom::Element* aParentElement,
+                                         nsICSSAnonBoxPseudo* aPseudoTag,
+                                         ServoStyleContext* aParentContext,
+                                         const AtomArray& aInputWord)
+{
+  MOZ_ASSERT(nsCSSAnonBoxes::IsTreePseudoElement(aPseudoTag));
+  MOZ_ASSERT(aParentContext);
+  MOZ_ASSERT(!StylistNeedsUpdate());
+
+  return Servo_ComputedValues_ResolveXULTreePseudoStyle(
+      aParentElement,
+      aPseudoTag,
+      aParentContext,
+      &aInputWord,
+      mRawSet.get()
+  ).Consume();
+}
+#endif
+
 // manage the set of style sheets in the style set
 nsresult
 ServoStyleSet::AppendStyleSheet(SheetType aType,
                                 ServoStyleSheet* aSheet)
 {
   MOZ_ASSERT(aSheet);
   MOZ_ASSERT(aSheet->IsApplicable());
   MOZ_ASSERT(nsStyleSet::IsCSSSheetType(aType));
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef mozilla_ServoStyleSet_h
 #define mozilla_ServoStyleSet_h
 
+#include "mozilla/AtomArray.h"
 #include "mozilla/EffectCompositor.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/PostTraversalTask.h"
 #include "mozilla/ServoBindingTypes.h"
 #include "mozilla/ServoElementSnapshot.h"
 #include "mozilla/ServoUtils.h"
 #include "mozilla/StyleSheetInlines.h"
@@ -234,16 +235,24 @@ public:
                                      ServoStyleContext* aParentContext);
 
   // Get a style context for an anonymous box that does not inherit style from
   // anything.  aPseudoTag is the pseudo-tag to use and must be non-null.  It
   // must be an anon box, and must be a non-inheriting one.
   already_AddRefed<ServoStyleContext>
   ResolveNonInheritingAnonymousBoxStyle(nsAtom* aPseudoTag);
 
+#ifdef MOZ_XUL
+  already_AddRefed<ServoStyleContext>
+  ResolveXULTreePseudoStyle(dom::Element* aParentElement,
+                            nsICSSAnonBoxPseudo* aPseudoTag,
+                            ServoStyleContext* aParentContext,
+                            const AtomArray& aInputWord);
+#endif
+
   // manage the set of style sheets in the style set
   nsresult AppendStyleSheet(SheetType aType, ServoStyleSheet* aSheet);
   nsresult PrependStyleSheet(SheetType aType, ServoStyleSheet* aSheet);
   nsresult RemoveStyleSheet(SheetType aType, ServoStyleSheet* aSheet);
   nsresult ReplaceSheets(SheetType aType,
                          const nsTArray<RefPtr<ServoStyleSheet>>& aNewSheets);
   nsresult InsertStyleSheetBefore(SheetType aType,
                                   ServoStyleSheet* aNewSheet,
--- a/layout/style/StyleSetHandle.h
+++ b/layout/style/StyleSetHandle.h
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef mozilla_StyleSetHandle_h
 #define mozilla_StyleSetHandle_h
 
+#include "mozilla/AtomArray.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/ServoTypes.h"
 #include "mozilla/SheetType.h"
 #include "mozilla/StyleBackendType.h"
 #include "mozilla/StyleSheet.h"
 #include "nsChangeHint.h"
 #include "nsCSSPseudoElements.h"
@@ -24,16 +25,17 @@ namespace dom {
 class Element;
 class ShadowRoot;
 } // namespace dom
 } // namespace mozilla
 class nsBindingManager;
 class nsCSSCounterStyleRule;
 struct nsFontFaceRuleContainer;
 class nsAtom;
+class nsICSSAnonBoxPseudo;
 class nsIContent;
 class nsIDocument;
 class nsStyleContext;
 class nsStyleSet;
 class nsPresContext;
 class gfxFontFeatureValueSet;
 struct TreeMatchContext;
 
@@ -139,16 +141,23 @@ public:
                               mozilla::CSSPseudoElementType aType,
                               nsStyleContext* aParentContext,
                               dom::Element* aPseudoElement);
     inline already_AddRefed<nsStyleContext>
     ResolveInheritingAnonymousBoxStyle(nsAtom* aPseudoTag,
                                        nsStyleContext* aParentContext);
     inline already_AddRefed<nsStyleContext>
     ResolveNonInheritingAnonymousBoxStyle(nsAtom* aPseudoTag);
+#ifdef MOZ_XUL
+    inline already_AddRefed<nsStyleContext>
+    ResolveXULTreePseudoStyle(dom::Element* aParentElement,
+                              nsICSSAnonBoxPseudo* aPseudoTag,
+                              nsStyleContext* aParentContext,
+                              const AtomArray& aInputWord);
+#endif
     inline nsresult AppendStyleSheet(SheetType aType, StyleSheet* aSheet);
     inline nsresult PrependStyleSheet(SheetType aType, StyleSheet* aSheet);
     inline nsresult RemoveStyleSheet(SheetType aType, StyleSheet* aSheet);
     inline nsresult ReplaceSheets(SheetType aType,
                            const nsTArray<RefPtr<StyleSheet>>& aNewSheets);
     inline nsresult InsertStyleSheetBefore(SheetType aType,
                                     StyleSheet* aNewSheet,
                                     StyleSheet* aReferenceSheet);
--- a/layout/style/StyleSetHandleInlines.h
+++ b/layout/style/StyleSetHandleInlines.h
@@ -147,16 +147,28 @@ StyleSetHandle::Ptr::ResolveInheritingAn
 }
 
 already_AddRefed<nsStyleContext>
 StyleSetHandle::Ptr::ResolveNonInheritingAnonymousBoxStyle(nsAtom* aPseudoTag)
 {
   FORWARD(ResolveNonInheritingAnonymousBoxStyle, (aPseudoTag));
 }
 
+#ifdef MOZ_XUL
+already_AddRefed<nsStyleContext>
+StyleSetHandle::Ptr::ResolveXULTreePseudoStyle(dom::Element* aParentElement,
+                                               nsICSSAnonBoxPseudo* aPseudoTag,
+                                               nsStyleContext* aParentContext,
+                                               const AtomArray& aInputWord)
+{
+  FORWARD_WITH_PARENT(ResolveXULTreePseudoStyle, aParentContext,
+                      (aParentElement, aPseudoTag, parent, aInputWord));
+}
+#endif
+
 // manage the set of style sheets in the style set
 nsresult
 StyleSetHandle::Ptr::AppendStyleSheet(SheetType aType, StyleSheet* aSheet)
 {
   FORWARD_CONCRETE(AppendStyleSheet, (aType, aSheet->AsGecko()),
                                      (aType, aSheet->AsServo()));
 }
 
--- a/layout/xul/tree/nsTreeStyleCache.cpp
+++ b/layout/xul/tree/nsTreeStyleCache.cpp
@@ -74,27 +74,19 @@ nsTreeStyleCache::GetStyleContext(nsPres
   // We're in a final state.
   // Look up our style context for this state.
   nsStyleContext* result = nullptr;
   if (mCache) {
     result = mCache->GetWeak(currState);
   }
   if (!result) {
     // We missed the cache. Resolve this pseudo-style.
-    // XXXheycam ServoStyleSets do not support XUL tree styles.
-    RefPtr<nsStyleContext> newResult;
-    if (aPresContext->StyleSet()->IsServo()) {
-      NS_ERROR("stylo: ServoStyleSets should not support XUL tree styles yet");
-      newResult = aPresContext->StyleSet()->
-        ResolveStyleForPlaceholder();
-    } else {
-      newResult = aPresContext->StyleSet()->AsGecko()->
-        ResolveXULTreePseudoStyle(aContent->AsElement(), aPseudoElement,
-                                  aContext->AsGecko(), aInputWord);
-    }
+    RefPtr<nsStyleContext> newResult = aPresContext->StyleSet()->
+        ResolveXULTreePseudoStyle(aContent->AsElement(),
+                                  aPseudoElement, aContext, aInputWord);
 
     // Put the style context in our table, transferring the owning reference to the table.
     if (!mCache) {
       mCache = new StyleContextCache();
     }
     result = newResult.get();
     mCache->Put(currState, newResult.forget());
   }
--- a/servo/components/script_layout_interface/wrapper_traits.rs
+++ b/servo/components/script_layout_interface/wrapper_traits.rs
@@ -403,17 +403,18 @@ pub trait ThreadSafeLayoutElement: Clone
                         context.stylist
                                .lazily_compute_pseudo_element_style(
                                    &context.guards,
                                    unsafe { &self.unsafe_get() },
                                    &style_pseudo,
                                    RuleInclusion::All,
                                    data.styles.primary(),
                                    /* is_probe = */ false,
-                                   &ServoMetricsProvider)
+                                   &ServoMetricsProvider,
+                                   /* matching_func = */ None)
                                .unwrap()
                                .clone()
                     }
                 }
             }
         }
     }
 
--- a/servo/components/selectors/context.rs
+++ b/servo/components/selectors/context.rs
@@ -104,16 +104,20 @@ where
     /// When this is None, :scope will match the root element.
     ///
     /// See https://drafts.csswg.org/selectors-4/#scope-pseudo
     pub scope_element: Option<OpaqueElement>,
 
     /// The current nesting level of selectors that we're matching.
     pub nesting_level: usize,
 
+    /// An optional hook function for checking whether a pseudo-element
+    /// should match when matching_mode is ForStatelessPseudoElement.
+    pub pseudo_element_matching_fn: Option<&'a Fn(&Impl::PseudoElement) -> bool>,
+
     quirks_mode: QuirksMode,
     classes_and_ids_case_sensitivity: CaseSensitivity,
     _impl: ::std::marker::PhantomData<Impl>,
 }
 
 impl<'a, Impl> MatchingContext<'a, Impl>
 where
     Impl: SelectorImpl,
@@ -147,16 +151,17 @@ where
             bloom_filter,
             visited_handling,
             nth_index_cache,
             quirks_mode,
             relevant_link_found: false,
             classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
             scope_element: None,
             nesting_level: 0,
+            pseudo_element_matching_fn: None,
             _impl: ::std::marker::PhantomData,
         }
     }
 
     /// The quirks mode of the document.
     #[inline]
     pub fn quirks_mode(&self) -> QuirksMode {
         self.quirks_mode
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -382,19 +382,30 @@ where
     E: Element,
     F: FnMut(&E, ElementSelectorFlags),
 {
     // If this is the special pseudo-element mode, consume the ::pseudo-element
     // before proceeding, since the caller has already handled that part.
     if context.nesting_level == 0 &&
         context.matching_mode == MatchingMode::ForStatelessPseudoElement {
         // Consume the pseudo.
-        let pseudo = iter.next().unwrap();
-        debug_assert!(matches!(*pseudo, Component::PseudoElement(..)),
-                      "Used MatchingMode::ForStatelessPseudoElement in a non-pseudo selector");
+        match *iter.next().unwrap() {
+            Component::PseudoElement(ref pseudo) => {
+                if let Some(ref f) = context.pseudo_element_matching_fn {
+                    if !f(pseudo) {
+                        return false;
+                    }
+                }
+            }
+            _ => {
+                debug_assert!(false,
+                              "Used MatchingMode::ForStatelessPseudoElement \
+                               in a non-pseudo selector");
+            }
+        }
 
         // The only other parser-allowed Component in this sequence is a state
         // class. We just don't match in that case.
         if let Some(s) = iter.next() {
             debug_assert!(matches!(*s, Component::NonTSPseudoClass(..)),
                           "Someone messed up pseudo-element parsing");
             return false;
         }
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -10,16 +10,17 @@ pub type ServoStyleContextBorrowedOrNull
 pub type ServoComputedDataBorrowed<'a> = &'a ServoComputedData;
 pub type RawServoAnimationValueTableBorrowed<'a> = &'a ();
 use gecko_bindings::structs::mozilla::css::GridTemplateAreasValue;
 use gecko_bindings::structs::mozilla::css::ErrorReporter;
 use gecko_bindings::structs::mozilla::css::ImageValue;
 use gecko_bindings::structs::mozilla::css::URLValue;
 use gecko_bindings::structs::mozilla::css::URLValueData;
 use gecko_bindings::structs::mozilla::AnonymousCounterStyle;
+use gecko_bindings::structs::mozilla::AtomArray;
 use gecko_bindings::structs::mozilla::MallocSizeOf;
 use gecko_bindings::structs::mozilla::OriginFlags;
 use gecko_bindings::structs::mozilla::UniquePtr;
 use gecko_bindings::structs::ServoRawOffsetArc;
 use gecko_bindings::structs::nsIContent;
 use gecko_bindings::structs::nsIDocument;
 use gecko_bindings::structs::nsIDocument_DocumentTheme;
 use gecko_bindings::structs::RawGeckoAnimationPropertySegment;
@@ -2941,16 +2942,29 @@ extern "C" {
                                     pseudo_type: CSSPseudoElementType,
                                     is_probe: bool,
                                     inherited_style:
                                         ServoStyleContextBorrowedOrNull,
                                     set: RawServoStyleSetBorrowed)
      -> ServoStyleContextStrong;
 }
 extern "C" {
+    pub fn Servo_ComputedValues_ResolveXULTreePseudoStyle(element:
+                                                              RawGeckoElementBorrowed,
+                                                          pseudo_tag:
+                                                              *mut nsAtom,
+                                                          inherited_style:
+                                                              ServoStyleContextBorrowed,
+                                                          input_word:
+                                                              *const AtomArray,
+                                                          set:
+                                                              RawServoStyleSetBorrowed)
+     -> ServoStyleContextStrong;
+}
+extern "C" {
     pub fn Servo_SetExplicitStyle(element: RawGeckoElementBorrowed,
                                   primary_style: ServoStyleContextBorrowed);
 }
 extern "C" {
     pub fn Servo_HasAuthorSpecifiedRules(style: ServoStyleContextBorrowed,
                                          element: RawGeckoElementBorrowed,
                                          pseudo_type: CSSPseudoElementType,
                                          rule_type_mask: u32,
--- a/servo/components/style/gecko/generated/pseudo_element_definition.rs
+++ b/servo/components/style/gecko/generated/pseudo_element_definition.rs
@@ -1321,16 +1321,58 @@ impl PseudoElement {
                     return Some(PseudoElement::MozSVGForeignContent);
                 }
                 if atom == &atom!(":-moz-svg-text") {
                     return Some(PseudoElement::MozSVGText);
                 }
         None
     }
 
+    /// Construct a tree pseudo-element from atom and args.
+    #[inline]
+    pub fn from_tree_pseudo_atom(atom: &Atom, args: Box<[Atom]>) -> Option<Self> {
+                if atom == &atom!(":-moz-tree-column") {
+                    return Some(PseudoElement::MozTreeColumn(args));
+                }
+                if atom == &atom!(":-moz-tree-row") {
+                    return Some(PseudoElement::MozTreeRow(args));
+                }
+                if atom == &atom!(":-moz-tree-separator") {
+                    return Some(PseudoElement::MozTreeSeparator(args));
+                }
+                if atom == &atom!(":-moz-tree-cell") {
+                    return Some(PseudoElement::MozTreeCell(args));
+                }
+                if atom == &atom!(":-moz-tree-indentation") {
+                    return Some(PseudoElement::MozTreeIndentation(args));
+                }
+                if atom == &atom!(":-moz-tree-line") {
+                    return Some(PseudoElement::MozTreeLine(args));
+                }
+                if atom == &atom!(":-moz-tree-twisty") {
+                    return Some(PseudoElement::MozTreeTwisty(args));
+                }
+                if atom == &atom!(":-moz-tree-image") {
+                    return Some(PseudoElement::MozTreeImage(args));
+                }
+                if atom == &atom!(":-moz-tree-cell-text") {
+                    return Some(PseudoElement::MozTreeCellText(args));
+                }
+                if atom == &atom!(":-moz-tree-checkbox") {
+                    return Some(PseudoElement::MozTreeCheckbox(args));
+                }
+                if atom == &atom!(":-moz-tree-progressmeter") {
+                    return Some(PseudoElement::MozTreeProgressmeter(args));
+                }
+                if atom == &atom!(":-moz-tree-drop-feedback") {
+                    return Some(PseudoElement::MozTreeDropFeedback(args));
+                }
+        None
+    }
+
     /// Constructs an atom from a string of text, and whether we're in a
     /// user-agent stylesheet.
     ///
     /// If we're not in a user-agent stylesheet, we will never parse anonymous
     /// box pseudo-elements.
     ///
     /// Returns `None` if the pseudo-element is not recognised.
     #[inline]
--- a/servo/components/style/gecko/generated/structs.rs
+++ b/servo/components/style/gecko/generated/structs.rs
@@ -4170,16 +4170,17 @@ pub mod root {
         #[derive(Debug, Copy, Clone)]
         pub struct SupportsWeakPtr {
         }
         #[repr(C)]
         #[derive(Debug, Copy, Clone)]
         pub struct ShortcutKeyCandidate {
             _unused: [u8; 0],
         }
+        pub type AtomArray = root::nsTArray<root::RefPtr<root::nsAtom>>;
         /// EventStates is the class used to represent the event states of nsIContent
         /// instances. These states are calculated by IntrinsicState() and
         /// ContentStatesChanged() has to be called when one of them changes thus
         /// informing the layout/style engine of the change.
         /// Event states are associated with pseudo-classes.
         #[repr(C)]
         #[derive(Debug, Copy)]
         pub struct EventStates {
@@ -22380,18 +22381,16 @@ pub mod root {
                     "Alignment of field: " , stringify ! ( nsAttrName ) , "::"
                     , stringify ! ( mBits ) ));
     }
     #[repr(C)]
     #[derive(Debug)]
     pub struct nsAttrValue {
         pub mBits: usize,
     }
-    pub type nsAttrValue_AtomArray =
-        root::nsTArray<root::RefPtr<root::nsAtom>>;
     pub const nsAttrValue_ValueType_eSVGTypesBegin:
               root::nsAttrValue_ValueType =
         nsAttrValue_ValueType::eSVGAngle;
     pub const nsAttrValue_ValueType_eSVGTypesEnd: root::nsAttrValue_ValueType
               =
         nsAttrValue_ValueType::eSVGViewBox;
     #[repr(u32)]
     #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
--- a/servo/components/style/gecko/pseudo_element_definition.mako.rs
+++ b/servo/components/style/gecko/pseudo_element_definition.mako.rs
@@ -198,16 +198,29 @@ impl PseudoElement {
                 if atom == &atom!("${pseudo.value}") {
                     return Some(${pseudo_element_variant(pseudo)});
                 }
             % endif
         % endfor
         None
     }
 
+    /// Construct a tree pseudo-element from atom and args.
+    #[inline]
+    pub fn from_tree_pseudo_atom(atom: &Atom, args: Box<[Atom]>) -> Option<Self> {
+        % for pseudo in PSEUDOS:
+            % if pseudo.is_tree_pseudo_element():
+                if atom == &atom!("${pseudo.value}") {
+                    return Some(PseudoElement::${pseudo.capitalized()}(args));
+                }
+            % endif
+        % endfor
+        None
+    }
+
     /// Constructs an atom from a string of text, and whether we're in a
     /// user-agent stylesheet.
     ///
     /// If we're not in a user-agent stylesheet, we will never parse anonymous
     /// box pseudo-elements.
     ///
     /// Returns `None` if the pseudo-element is not recognised.
     #[inline]
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -812,23 +812,31 @@ impl Stylist {
     pub fn lazily_compute_pseudo_element_style<E>(
         &self,
         guards: &StylesheetGuards,
         element: &E,
         pseudo: &PseudoElement,
         rule_inclusion: RuleInclusion,
         parent_style: &ComputedValues,
         is_probe: bool,
-        font_metrics: &FontMetricsProvider
+        font_metrics: &FontMetricsProvider,
+        matching_fn: Option<&Fn(&PseudoElement) -> bool>,
     ) -> Option<Arc<ComputedValues>>
     where
         E: TElement,
     {
         let cascade_inputs =
-            self.lazy_pseudo_rules(guards, element, pseudo, is_probe, rule_inclusion);
+            self.lazy_pseudo_rules(
+                guards,
+                element,
+                pseudo,
+                is_probe,
+                rule_inclusion,
+                matching_fn
+            );
         self.compute_pseudo_element_style_with_inputs(
             &cascade_inputs,
             pseudo,
             guards,
             parent_style,
             font_metrics,
         )
     }
@@ -972,17 +980,18 @@ impl Stylist {
     /// See the documentation on lazy pseudo-elements in
     /// docs/components/style.md
     pub fn lazy_pseudo_rules<E>(
         &self,
         guards: &StylesheetGuards,
         element: &E,
         pseudo: &PseudoElement,
         is_probe: bool,
-        rule_inclusion: RuleInclusion
+        rule_inclusion: RuleInclusion,
+        matching_fn: Option<&Fn(&PseudoElement) -> bool>,
     ) -> CascadeInputs
     where
         E: TElement
     {
         let pseudo = pseudo.canonical();
         debug_assert!(pseudo.is_lazy());
 
         // Apply the selector flags. We should be in sequential mode
@@ -1019,16 +1028,17 @@ impl Stylist {
         let mut declarations = ApplicableDeclarationList::new();
         let mut matching_context =
             MatchingContext::new(
                 MatchingMode::ForStatelessPseudoElement,
                 None,
                 None,
                 self.quirks_mode,
             );
+        matching_context.pseudo_element_matching_fn = matching_fn;
 
         self.push_applicable_declarations(
             element,
             Some(&pseudo),
             None,
             None,
             AnimationRules(None, None),
             rule_inclusion,
@@ -1055,16 +1065,17 @@ impl Stylist {
             let mut matching_context =
                 MatchingContext::new_for_visited(
                     MatchingMode::ForStatelessPseudoElement,
                     None,
                     None,
                     VisitedHandlingMode::RelevantLinkVisited,
                     self.quirks_mode,
                 );
+            matching_context.pseudo_element_matching_fn = matching_fn;
 
             self.push_applicable_declarations(
                 element,
                 Some(&pseudo),
                 None,
                 None,
                 AnimationRules(None, None),
                 rule_inclusion,
@@ -1282,16 +1293,17 @@ impl Stylist {
                 // NOTE(emilio): This is needed because the XBL stylist may
                 // think it has a different quirks mode than the document.
                 let mut matching_context = MatchingContext::new(
                     context.matching_mode,
                     context.bloom_filter,
                     context.nth_index_cache.as_mut().map(|s| &mut **s),
                     stylist.quirks_mode,
                 );
+                matching_context.pseudo_element_matching_fn = context.pseudo_element_matching_fn;
 
                 map.get_all_matching_rules(
                     element,
                     &rule_hash_target,
                     applicable_declarations,
                     &mut matching_context,
                     stylist.quirks_mode,
                     flags_setter,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -79,16 +79,17 @@ use style::gecko_bindings::bindings::nsT
 use style::gecko_bindings::bindings::nsTimingFunctionBorrowedMut;
 use style::gecko_bindings::structs;
 use style::gecko_bindings::structs::{CSSPseudoElementType, CompositeOperation};
 use style::gecko_bindings::structs::{Loader, LoaderReusableStyleSheets};
 use style::gecko_bindings::structs::{RawServoStyleRule, ServoStyleContextStrong, RustString};
 use style::gecko_bindings::structs::{ServoStyleSheet, SheetParsingMode, nsAtom, nsCSSPropertyID};
 use style::gecko_bindings::structs::{nsCSSFontFaceRule, nsCSSCounterStyleRule};
 use style::gecko_bindings::structs::{nsRestyleHint, nsChangeHint, PropertyValuePair};
+use style::gecko_bindings::structs::AtomArray;
 use style::gecko_bindings::structs::IterationCompositeOperation;
 use style::gecko_bindings::structs::MallocSizeOf as GeckoMallocSizeOf;
 use style::gecko_bindings::structs::OriginFlags;
 use style::gecko_bindings::structs::OriginFlags_Author;
 use style::gecko_bindings::structs::OriginFlags_User;
 use style::gecko_bindings::structs::OriginFlags_UserAgent;
 use style::gecko_bindings::structs::RawGeckoGfxMatrix4x4;
 use style::gecko_bindings::structs::RawGeckoPresContextOwned;
@@ -121,17 +122,17 @@ use style::properties::{SKIP_ROOT_AND_IT
 use style::properties::PROHIBIT_DISPLAY_CONTENTS;
 use style::properties::animated_properties::AnimationValue;
 use style::properties::animated_properties::compare_property_priority;
 use style::properties::parse_one_declaration_into;
 use style::rule_cache::RuleCacheConditions;
 use style::rule_tree::{CascadeLevel, StrongRuleNode, StyleSource};
 use style::selector_parser::{PseudoElementCascadeType, SelectorImpl};
 use style::shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard, Locked};
-use style::string_cache::Atom;
+use style::string_cache::{Atom, WeakAtom};
 use style::style_adjuster::StyleAdjuster;
 use style::stylesheets::{CssRule, CssRules, CssRuleType, CssRulesHelpers, DocumentRule};
 use style::stylesheets::{FontFeatureValuesRule, ImportRule, KeyframesRule, MediaRule};
 use style::stylesheets::{NamespaceRule, Origin, OriginSet, PageRule, StyleRule};
 use style::stylesheets::{StylesheetContents, SupportsRule};
 use style::stylesheets::StylesheetLoader as StyleStylesheetLoader;
 use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframesStepValue};
 use style::stylesheets::supports_rule::parse_condition_or_declaration;
@@ -1906,27 +1907,88 @@ pub extern "C" fn Servo_ResolvePseudoSty
         &guard,
         element,
         &pseudo,
         RuleInclusion::All,
         &data.styles,
         inherited_style,
         &*doc_data,
         is_probe,
+        /* matching_func = */ None,
     );
 
     match style {
         Some(s) => s.into(),
         None => {
             debug_assert!(is_probe);
             Strong::null()
         }
     }
 }
 
+fn debug_atom_array(atoms: &AtomArray) -> String {
+    let mut result = String::from("[");
+    for atom in atoms.iter() {
+        if atom.mRawPtr.is_null() {
+            result += "(null), ";
+        } else {
+            let atom = unsafe { WeakAtom::new(atom.mRawPtr) };
+            write!(result, "{}, ", atom).unwrap();
+        }
+    }
+    result.push(']');
+    result
+}
+
+#[no_mangle]
+pub extern "C" fn Servo_ComputedValues_ResolveXULTreePseudoStyle(
+    element: RawGeckoElementBorrowed,
+    pseudo_tag: *mut nsAtom,
+    inherited_style: ServoStyleContextBorrowed,
+    input_word: *const AtomArray,
+    raw_data: RawServoStyleSetBorrowed
+) -> ServoStyleContextStrong {
+    let element = GeckoElement(element);
+    let data = element.borrow_data()
+        .expect("Calling ResolveXULTreePseudoStyle on unstyled element?");
+
+    let pseudo = unsafe {
+        Atom::with(pseudo_tag, |atom| {
+            PseudoElement::from_tree_pseudo_atom(atom, Box::new([]))
+        }).expect("ResolveXULTreePseudoStyle with a non-tree pseudo?")
+    };
+    let input_word = unsafe { input_word.as_ref().unwrap() };
+
+    let doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();
+
+    debug!("ResolveXULTreePseudoStyle: {:?} {:?} {}",
+           element, pseudo, debug_atom_array(input_word));
+
+    let matching_fn = |pseudo: &PseudoElement| {
+        let args = pseudo.tree_pseudo_args().expect("Not a tree pseudo-element?");
+        args.iter().all(|atom| {
+            input_word.iter().any(|item| atom.as_ptr() == item.mRawPtr)
+        })
+    };
+
+    let global_style_data = &*GLOBAL_STYLE_DATA;
+    let guard = global_style_data.shared_lock.read();
+    get_pseudo_style(
+        &guard,
+        element,
+        &pseudo,
+        RuleInclusion::All,
+        &data.styles,
+        Some(inherited_style),
+        &*doc_data,
+        /* is_probe = */ false,
+        Some(&matching_fn),
+    ).unwrap().into()
+}
+
 #[no_mangle]
 pub extern "C" fn Servo_SetExplicitStyle(element: RawGeckoElementBorrowed,
                                          style: ServoStyleContextBorrowed)
 {
     let element = GeckoElement(element);
     debug!("Servo_SetExplicitStyle: {:?}", element);
     // We only support this API for initial styling. There's no reason it couldn't
     // work for other things, we just haven't had a reason to do so.
@@ -1960,16 +2022,17 @@ fn get_pseudo_style(
     guard: &SharedRwLockReadGuard,
     element: GeckoElement,
     pseudo: &PseudoElement,
     rule_inclusion: RuleInclusion,
     styles: &ElementStyles,
     inherited_styles: Option<&ComputedValues>,
     doc_data: &PerDocumentStyleDataImpl,
     is_probe: bool,
+    matching_func: Option<&Fn(&PseudoElement) -> bool>,
 ) -> Option<Arc<ComputedValues>> {
     let style = match pseudo.cascade_type() {
         PseudoElementCascadeType::Eager => {
             match *pseudo {
                 PseudoElement::FirstLetter => {
                     styles.pseudos.get(&pseudo).and_then(|pseudo_styles| {
                         // inherited_styles can be None when doing lazy resolution
                         // (e.g. for computed style) or when probing.  In that case
@@ -2033,16 +2096,17 @@ fn get_pseudo_style(
                 .lazily_compute_pseudo_element_style(
                     &guards,
                     &element,
                     &pseudo,
                     rule_inclusion,
                     base,
                     is_probe,
                     &metrics,
+                    matching_func,
                 )
         },
     };
 
     if is_probe {
         return style;
     }
 
@@ -3264,16 +3328,17 @@ pub extern "C" fn Servo_ResolveStyleLazi
                     &guard,
                     element,
                     pseudo,
                     rule_inclusion,
                     styles,
                     /* inherited_styles = */ None,
                     &*data,
                     is_probe,
+                    /* matching_func = */ None,
                 )
             }
             None => Some(styles.primary().clone()),
         }
     };
 
     let is_before_or_after = pseudo.as_ref().map_or(false, |p| p.is_before_or_after());