Bug 1367312 - stylo: Implement :-moz-lwtheme* pseudo-classes. r?emilio draft
authorMatt Brubeck <mbrubeck@mozilla.com>
Tue, 27 Jun 2017 14:16:30 -0700
changeset 600966 a6a237594c176898c2500ab9fd58455e79b8f4e1
parent 600616 f4e52135d9bdc6ce98bb37b450021445aed894ce
child 635141 677d2de7b6ae6be79d6638110c1248cf4c9746b1
push id65926
push userbmo:mbrubeck@mozilla.com
push dateTue, 27 Jun 2017 22:38:28 +0000
reviewersemilio
bugs1367312
milestone56.0a1
Bug 1367312 - stylo: Implement :-moz-lwtheme* pseudo-classes. r?emilio MozReview-Commit-ID: 8ARU4iVDNtY
dom/base/nsIDocument.h
dom/xul/XULDocument.cpp
dom/xul/XULDocument.h
layout/style/ServoBindings.cpp
layout/style/ServoBindings.h
layout/style/ServoBindings.toml
layout/style/test/stylo-failures.md
servo/components/style/gecko/non_ts_pseudo_class_list.rs
servo/components/style/gecko/wrapper.rs
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2402,17 +2402,18 @@ public:
   void SetStateObject(nsIStructuredCloneContainer *scContainer);
 
   /**
    * Returns Doc_Theme_None if there is no lightweight theme specified,
    * Doc_Theme_Dark for a dark theme, Doc_Theme_Bright for a light theme, and
    * Doc_Theme_Neutral for any other theme. This is used to determine the state
    * of the pseudoclasses :-moz-lwtheme and :-moz-lwtheme-text.
    */
-  virtual int GetDocumentLWTheme() { return Doc_Theme_None; }
+  virtual DocumentTheme GetDocumentLWTheme() { return Doc_Theme_None; }
+  virtual DocumentTheme ThreadSafeGetDocumentLWTheme() const { return Doc_Theme_None; }
 
   /**
    * Returns the document state.
    * Document state bits have the form NS_DOCUMENT_STATE_* and are declared in
    * nsIDocument.h.
    */
   virtual mozilla::EventStates GetDocumentState() = 0;
   virtual mozilla::EventStates ThreadSafeGetDocumentState() const = 0;
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -4549,40 +4549,50 @@ XULDocument::DirectionChanged(const char
 {
   // Reset the direction and restyle the document if necessary.
   XULDocument* doc = (XULDocument *)aData;
   if (doc) {
       doc->ResetDocumentDirection();
   }
 }
 
-int
+nsIDocument::DocumentTheme
 XULDocument::GetDocumentLWTheme()
 {
     if (mDocLWTheme == Doc_Theme_Uninitialized) {
-        mDocLWTheme = Doc_Theme_None; // No lightweight theme by default
+        mDocLWTheme = ThreadSafeGetDocumentLWTheme();
+    }
+    return mDocLWTheme;
+}
+
+nsIDocument::DocumentTheme
+XULDocument::ThreadSafeGetDocumentLWTheme() const
+{
+    DocumentTheme theme = mDocLWTheme;
+    if (theme == Doc_Theme_Uninitialized) {
+        theme = Doc_Theme_None; // No lightweight theme by default
 
         Element* element = GetRootElement();
         nsAutoString hasLWTheme;
         if (element &&
             element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwtheme, hasLWTheme) &&
             !(hasLWTheme.IsEmpty()) &&
             hasLWTheme.EqualsLiteral("true")) {
-            mDocLWTheme = Doc_Theme_Neutral;
+            theme = Doc_Theme_Neutral;
             nsAutoString lwTheme;
             element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwthemetextcolor, lwTheme);
             if (!(lwTheme.IsEmpty())) {
                 if (lwTheme.EqualsLiteral("dark"))
-                    mDocLWTheme = Doc_Theme_Dark;
+                    theme = Doc_Theme_Dark;
                 else if (lwTheme.EqualsLiteral("bright"))
-                    mDocLWTheme = Doc_Theme_Bright;
+                    theme = Doc_Theme_Bright;
             }
         }
     }
-    return mDocLWTheme;
+    return theme;
 }
 
 NS_IMETHODIMP
 XULDocument::GetBoxObjectFor(nsIDOMElement* aElement, nsIBoxObject** aResult)
 {
     ErrorResult rv;
     nsCOMPtr<Element> el = do_QueryInterface(aElement);
     *aResult = GetBoxObjectFor(el, rv).take();
--- a/dom/xul/XULDocument.h
+++ b/dom/xul/XULDocument.h
@@ -168,17 +168,18 @@ public:
                                 nsresult aStatus) override;
 
     virtual void EndUpdate(nsUpdateType aUpdateType) override;
 
     virtual bool IsDocumentRightToLeft() override;
 
     virtual void ResetDocumentDirection() override;
 
-    virtual int GetDocumentLWTheme() override;
+    virtual nsIDocument::DocumentTheme GetDocumentLWTheme() override;
+    virtual nsIDocument::DocumentTheme ThreadSafeGetDocumentLWTheme() const override;
 
     virtual void ResetDocumentLWTheme() override { mDocLWTheme = Doc_Theme_Uninitialized; }
 
     NS_IMETHOD OnScriptCompileComplete(JSScript* aScript, nsresult aStatus) override;
 
     static bool
     MatchAttribute(Element* aContent,
                    int32_t aNameSpaceID,
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -814,16 +814,21 @@ Gecko_GetXMLLangValue(RawGeckoElementBor
   }
 
   MOZ_ASSERT(attr->Type() == nsAttrValue::eAtom);
 
   nsCOMPtr<nsIAtom> atom = attr->GetAtomValue();
   return atom.forget().take();
 }
 
+int
+Gecko_GetDocumentLWTheme(const nsIDocument *aDocument) {
+  return aDocument->ThreadSafeGetDocumentLWTheme();
+}
+
 template <typename Implementor>
 static nsIAtom*
 AtomAttrValue(Implementor* aElement, nsIAtom* aName)
 {
   const nsAttrValue* attr = aElement->GetParsedAttr(aName);
   return attr ? attr->GetAtomValue() : nullptr;
 }
 
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -24,16 +24,17 @@
  * API for Servo to access Gecko data structures. This file must compile as valid
  * C code in order for the binding generator to parse it.
  *
  * Functions beginning with Gecko_ are implemented in Gecko and invoked from Servo.
  * Functions beginning with Servo_ are implemented in Servo and invoked from Gecko.
  */
 
 class nsIAtom;
+class nsIDocument;
 class nsIPrincipal;
 class nsIURI;
 struct nsFont;
 namespace mozilla {
   class FontFamilyList;
   enum FontFamilyType : uint32_t;
   enum class CSSPseudoElementType : uint8_t;
   struct Keyframe;
@@ -159,16 +160,17 @@ bool Gecko_IsRootElement(RawGeckoElement
 bool Gecko_MatchesElement(mozilla::CSSPseudoClassType type, RawGeckoElementBorrowed element);
 nsIAtom* Gecko_LocalName(RawGeckoElementBorrowed element);
 nsIAtom* Gecko_Namespace(RawGeckoElementBorrowed element);
 nsIAtom* Gecko_GetElementId(RawGeckoElementBorrowed element);
 bool Gecko_MatchLang(RawGeckoElementBorrowed element,
                      nsIAtom* override_lang, bool has_override_lang,
                      const char16_t* value);
 nsIAtom* Gecko_GetXMLLangValue(RawGeckoElementBorrowed element);
+int Gecko_GetDocumentLWTheme(const nsIDocument *aDocument);
 
 // Attributes.
 #define SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(prefix_, implementor_)  \
   nsIAtom* prefix_##AtomAttrValue(implementor_ element, nsIAtom* attribute);  \
   nsIAtom* prefix_##LangValue(implementor_ element);                          \
   bool prefix_##HasAttr(implementor_ element, nsIAtom* ns, nsIAtom* name);    \
   bool prefix_##AttrEquals(implementor_ element, nsIAtom* ns, nsIAtom* name,  \
                            nsIAtom* str, bool ignoreCase);                    \
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -325,16 +325,17 @@ raw-lines = [
 whitelist-functions = ["Servo_.*", "Gecko_.*"]
 structs-types = [
     "mozilla::css::GridTemplateAreasValue",
     "mozilla::css::ImageValue",
     "mozilla::css::URLValue",
     "mozilla::MallocSizeOf",
     "mozilla::Side",
     "nsIContent",
+    "nsIDocument",
     "RawGeckoAnimationPropertySegment",
     "RawGeckoComputedTiming",
     "RawGeckoCSSPropertyIDList",
     "RawGeckoDocument",
     "RawGeckoElement",
     "RawGeckoKeyframeList",
     "RawGeckoComputedKeyframeValuesList",
     "RawGeckoFontFaceRuleList",
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -82,18 +82,16 @@ to mochitest command.
   * serialize subprops to -moz-use-system-font when using system font bug 1364289
     * test_value_storage.html `'font'` [224]
   * different serialization for gradient functions in computed value bug 1367274
     * test_computed_style.html `gradient` [13]
 * Unsupported pseudo-elements or anon boxes
   * :-moz-tree bits bug 1348488
     * test_selectors.html `:-moz-tree` [10]
 * Unsupported pseudo-classes
-  * :-moz-lwtheme-* bug 1367312
-    * test_selectors.html `:-moz-lwtheme` [3]
   * :-moz-window-inactive bug 1348489
     * test_selectors.html `:-moz-window-inactive` [2]
 * Unit should be preserved after parsing servo/servo#15346
   * test_units_time.html [1]
 * getComputedStyle style doesn't contain custom properties bug 1336891
   * test_variables.html `custom property name` [2]
 * test_css_supports.html: issues around @supports syntax servo/servo#15482 [2]
 * test_author_specified_style.html: support serializing color as author specified bug 1348165 [27]
--- a/servo/components/style/gecko/non_ts_pseudo_class_list.rs
+++ b/servo/components/style/gecko/non_ts_pseudo_class_list.rs
@@ -21,17 +21,16 @@
  * apply_non_ts_list!(pseudo_class_macro)
  * ```
  *
  * The `string` and `keyword` variables will be applied to pseudoclasses that are of the form of
  * functions with string or keyword arguments.
  *
  * Pending pseudo-classes:
  *
- *  :-moz-lwtheme, :-moz-lwtheme-brighttext, :-moz-lwtheme-darktext,
  *  :-moz-window-inactive.
  *
  *  :scope -> <style scoped>, pending discussion.
  *
  * This follows the order defined in layout/style/nsCSSPseudoClassList.h when
  * possible.
  *
  * $gecko_type can be either "_" or an ident in Gecko's CSSPseudoClassType.
@@ -109,16 +108,19 @@ macro_rules! apply_non_ts_list {
 
                 ("-moz-first-node", MozFirstNode, firstNode, _, _),
                 ("-moz-last-node", MozLastNode, lastNode, _, _),
                 ("-moz-only-whitespace", MozOnlyWhitespace, mozOnlyWhitespace, _, _),
                 ("-moz-native-anonymous", MozNativeAnonymous, mozNativeAnonymous, _, PSEUDO_CLASS_INTERNAL),
                 ("-moz-use-shadow-tree-root", MozUseShadowTreeRoot, mozUseShadowTreeRoot, _, PSEUDO_CLASS_INTERNAL),
                 ("-moz-is-html", MozIsHTML, mozIsHTML, _, _),
                 ("-moz-placeholder", MozPlaceholder, mozPlaceholder, _, _),
+                ("-moz-lwtheme", MozLWTheme, mozLWTheme, _, _),
+                ("-moz-lwtheme-brighttext", MozLWThemeBrightText, mozLWThemeBrightText, _, _),
+                ("-moz-lwtheme-darktext", MozLWThemeDarktext, mozLWThemeDarkText, _, _),
             ],
             string: [
                 ("-moz-system-metric", MozSystemMetric, mozSystemMetric, _, PSEUDO_CLASS_INTERNAL),
                 ("-moz-empty-except-children-with-localname", MozEmptyExceptChildrenWithLocalname,
                  mozEmptyExceptChildrenWithLocalname, _, PSEUDO_CLASS_INTERNAL),
                 ("lang", Lang, lang, _, _),
             ],
             keyword: [
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -26,17 +26,18 @@ use element_state::ElementState;
 use error_reporting::create_error_reporter;
 use font_metrics::{FontMetrics, FontMetricsProvider, FontMetricsQueryResult};
 use gecko::data::PerDocumentStyleData;
 use gecko::global_style_data::GLOBAL_STYLE_DATA;
 use gecko::selector_parser::{SelectorImpl, NonTSPseudoClass, PseudoElement};
 use gecko::snapshot_helpers;
 use gecko_bindings::bindings;
 use gecko_bindings::bindings::{Gecko_DropStyleChildrenIterator, Gecko_MaybeCreateStyleChildrenIterator};
-use gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetLastChild, Gecko_GetNextStyleChild};
+use gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetDocumentLWTheme};
+use gecko_bindings::bindings::{Gecko_GetLastChild, Gecko_GetNextStyleChild};
 use gecko_bindings::bindings::{Gecko_IsRootElement, Gecko_MatchesElement, Gecko_Namespace};
 use gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags};
 use gecko_bindings::bindings::Gecko_ClassOrClassList;
 use gecko_bindings::bindings::Gecko_ElementHasAnimations;
 use gecko_bindings::bindings::Gecko_ElementHasCSSAnimations;
 use gecko_bindings::bindings::Gecko_ElementHasCSSTransitions;
 use gecko_bindings::bindings::Gecko_GetActiveLinkAttrDeclarationBlock;
 use gecko_bindings::bindings::Gecko_GetAnimationRule;
@@ -57,16 +58,17 @@ use gecko_bindings::structs::{RawGeckoEl
 use gecko_bindings::structs::{nsIAtom, nsIContent, nsINode_BooleanFlag, nsStyleContext};
 use gecko_bindings::structs::ELEMENT_HANDLED_SNAPSHOT;
 use gecko_bindings::structs::ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO;
 use gecko_bindings::structs::ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO;
 use gecko_bindings::structs::ELEMENT_HAS_SNAPSHOT;
 use gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel;
 use gecko_bindings::structs::NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE;
 use gecko_bindings::structs::NODE_IS_NATIVE_ANONYMOUS;
+use gecko_bindings::structs::nsIDocument_DocumentTheme as DocumentTheme;
 use gecko_bindings::sugar::ownership::{HasArcFFI, HasSimpleFFI};
 use logical_geometry::WritingMode;
 use media_queries::Device;
 use properties::{ComputedValues, parse_style_attribute};
 use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock};
 use properties::animated_properties::{AnimatableLonghand, AnimationValue, AnimationValueMap};
 use properties::animated_properties::TransitionProperty;
 use properties::style_structs::Font;
@@ -80,16 +82,17 @@ use selectors::sink::Push;
 use shared_lock::Locked;
 use smallvec::VecLike;
 use std::cell::RefCell;
 use std::collections::HashMap;
 use std::fmt;
 use std::hash::{Hash, Hasher};
 use std::mem;
 use std::ops::DerefMut;
+use std::os::raw::c_int;
 use std::ptr;
 use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
 use stylearc::Arc;
 use stylesheets::UrlExtraData;
 
 /// A simple wrapper over a non-null Gecko node (`nsINode`) pointer.
 ///
 /// Important: We don't currently refcount the DOM, because the wrapper lifetime
@@ -581,16 +584,22 @@ impl<'le> GeckoElement<'le> {
                 .map(GeckoElement)
         }
     }
 
     #[inline]
     fn may_have_style_attribute(&self) -> bool {
         self.as_node().get_bool_flag(nsINode_BooleanFlag::ElementMayHaveStyle)
     }
+
+    #[inline]
+    fn get_document_theme(&self) -> c_int {
+        let node = self.as_node();
+        unsafe { Gecko_GetDocumentLWTheme(node.owner_doc()) }
+    }
 }
 
 /// Converts flags from the layout used by rust-selectors to the layout used
 /// by Gecko. We could align these and then do this without conditionals, but
 /// it's probably not worth the trouble.
 fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 {
     use gecko_bindings::structs::*;
     use selectors::matching::*;
@@ -1654,16 +1663,25 @@ impl<'le> ::selectors::Element for Gecko
             NonTSPseudoClass::MozBrowserFrame |
             NonTSPseudoClass::MozNativeAnonymous |
             NonTSPseudoClass::MozUseShadowTreeRoot => unsafe {
                 Gecko_MatchesElement(pseudo_class.to_gecko_pseudoclasstype().unwrap(), self.0)
             },
             NonTSPseudoClass::MozIsHTML => {
                 self.is_html_element_in_html_document()
             }
+            NonTSPseudoClass::MozLWTheme => {
+                self.get_document_theme() != DocumentTheme::Doc_Theme_None as c_int
+            }
+            NonTSPseudoClass::MozLWThemeBrightText => {
+                self.get_document_theme() == DocumentTheme::Doc_Theme_Bright as c_int
+            }
+            NonTSPseudoClass::MozLWThemeDarktext => {
+                self.get_document_theme() == DocumentTheme::Doc_Theme_Dark as c_int
+            }
             NonTSPseudoClass::MozPlaceholder => false,
             NonTSPseudoClass::MozAny(ref sels) => {
                 let old_value = context.hover_active_quirk_disabled;
                 context.hover_active_quirk_disabled = true;
                 let result = sels.iter().any(|s| {
                     matches_complex_selector(s.iter(), self, context, flags_setter)
                 });
                 context.hover_active_quirk_disabled = old_value;