Bug 1338936 - Part 9: stylo: Support lang property; r?emilio draft
authorNazım Can Altınova <canaltinova@gmail.com>
Sun, 12 Feb 2017 16:02:29 -0800
changeset 485643 8a4401944dc01de4d338a8d668c84880009e8025
parent 485642 0026fcef352a7600765d0da8d2e998c53678ec6f
child 485647 c634b706ef3b6396f914384788f93d9e084d1ba5
push id45797
push userbmo:manishearth@gmail.com
push dateThu, 16 Feb 2017 23:47:53 +0000
reviewersemilio
bugs1338936
milestone54.0a1
Bug 1338936 - Part 9: stylo: Support lang property; r?emilio MozReview-Commit-ID: 6wg32flypt7
layout/style/ServoBindingList.h
layout/style/ServoBindingTypes.h.orig
layout/style/ServoBindings.cpp
layout/style/ServoBindings.cpp.orig
layout/style/ServoBindings.h
layout/style/ServoSpecifiedValues.cpp
servo/components/style/build_gecko.rs
servo/components/style/gecko_bindings/bindings.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/ports/geckolib/glue.rs
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -180,17 +180,17 @@ SERVO_BINDING_FUNC(Servo_DeclarationBloc
 
 // presentation attributes
 SERVO_BINDING_FUNC(Servo_DeclarationBlock_PropertyIsSet, bool,
                    RawServoDeclarationBlockBorrowed declarations,
                    nsCSSPropertyID property)
 SERVO_BINDING_FUNC(Servo_DeclarationBlock_SetIdentStringValue, void,
                    RawServoDeclarationBlockBorrowed declarations,
                    nsCSSPropertyID property,
-                   const nsAString& value)
+                   nsIAtom* value)
 SERVO_BINDING_FUNC(Servo_DeclarationBlock_SetKeywordValue, void,
                    RawServoDeclarationBlockBorrowed declarations,
                    nsCSSPropertyID property,
                    int32_t value)
 SERVO_BINDING_FUNC(Servo_DeclarationBlock_SetIntValue, void,
                    RawServoDeclarationBlockBorrowed declarations,
                    nsCSSPropertyID property,
                    int32_t value)
new file mode 100644
--- /dev/null
+++ b/layout/style/ServoBindingTypes.h.orig
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_ServoBindingTypes_h
+#define mozilla_ServoBindingTypes_h
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ServoTypes.h"
+#include "nsTArray.h"
+
+struct RawServoAnimationValue;
+struct RawServoStyleSet;
+
+#define SERVO_ARC_TYPE(name_, type_) struct type_;
+#include "mozilla/ServoArcTypeList.h"
+#undef SERVO_ARC_TYPE
+
+namespace mozilla {
+  class ServoElementSnapshot;
+namespace dom {
+class Element;
+class StyleChildrenIterator;
+} // namespace dom
+struct PropertyStyleAnimationValuePair;
+} // namespace mozilla
+
+class nsCSSValue;
+class nsIDocument;
+class nsINode;
+class nsPresContext;
+
+using mozilla::dom::StyleChildrenIterator;
+using mozilla::ServoElementSnapshot;
+
+typedef nsINode RawGeckoNode;
+typedef mozilla::dom::Element RawGeckoElement;
+typedef nsIDocument RawGeckoDocument;
+<<<<<<< HEAD
+typedef nsPresContext RawGeckoPresContext;
+||||||| parent of 019e175... Bug 1317208 - Stylo: Store servo computed values for animation properties; r?birtles,heycam
+=======
+typedef nsTArray<mozilla::PropertyStyleAnimationValuePair> RawGeckoAnimationValueList;
+>>>>>>> 019e175... Bug 1317208 - Stylo: Store servo computed values for animation properties; r?birtles,heycam
+
+// We have these helper types so that we can directly generate
+// things like &T or Borrowed<T> on the Rust side in the function, providing
+// additional safety benefits.
+//
+// FFI has a problem with templated types, so we just use raw pointers here.
+//
+// The "Borrowed" types generate &T or Borrowed<T> in the nullable case.
+//
+// The "Owned" types generate Owned<T> or OwnedOrNull<T>. Some of these
+// are Servo-managed and can be converted to Box<ServoType> on the
+// Servo side.
+//
+// The "Arc" types are Servo-managed Arc<ServoType>s, which are passed
+// over FFI as Strong<T> (which is nullable).
+// Note that T != ServoType, rather T is ArcInner<ServoType>
+#define DECL_BORROWED_REF_TYPE_FOR(type_) typedef type_ const* type_##Borrowed;
+#define DECL_NULLABLE_BORROWED_REF_TYPE_FOR(type_) typedef type_ const* type_##BorrowedOrNull;
+#define DECL_BORROWED_MUT_REF_TYPE_FOR(type_) typedef type_* type_##BorrowedMut;
+#define DECL_NULLABLE_BORROWED_MUT_REF_TYPE_FOR(type_) typedef type_* type_##BorrowedMutOrNull;
+
+#define SERVO_ARC_TYPE(name_, type_)         \
+  DECL_NULLABLE_BORROWED_REF_TYPE_FOR(type_) \
+  DECL_BORROWED_REF_TYPE_FOR(type_)          \
+  struct MOZ_MUST_USE_TYPE type_##Strong     \
+  {                                          \
+    type_* mPtr;                             \
+    already_AddRefed<type_> Consume();       \
+  };
+#include "mozilla/ServoArcTypeList.h"
+#undef SERVO_ARC_TYPE
+
+#define DECL_OWNED_REF_TYPE_FOR(type_)    \
+  typedef type_* type_##Owned;            \
+  DECL_BORROWED_REF_TYPE_FOR(type_)       \
+  DECL_BORROWED_MUT_REF_TYPE_FOR(type_)
+
+#define DECL_NULLABLE_OWNED_REF_TYPE_FOR(type_)    \
+  typedef type_* type_##OwnedOrNull;               \
+  DECL_NULLABLE_BORROWED_REF_TYPE_FOR(type_)       \
+  DECL_NULLABLE_BORROWED_MUT_REF_TYPE_FOR(type_)
+
+// This is a reference to a reference of RawServoDeclarationBlock, which
+// corresponds to Option<&Arc<RawServoDeclarationBlock>> in Servo side.
+DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawServoDeclarationBlockStrong)
+
+DECL_OWNED_REF_TYPE_FOR(RawServoStyleSet)
+DECL_NULLABLE_OWNED_REF_TYPE_FOR(StyleChildrenIterator)
+DECL_OWNED_REF_TYPE_FOR(StyleChildrenIterator)
+DECL_OWNED_REF_TYPE_FOR(ServoElementSnapshot)
+
+// We don't use BorrowedMut because the nodes may alias
+// Servo itself doesn't directly read or mutate these;
+// it only asks Gecko to do so. In case we wish to in
+// the future, we should ensure that things being mutated
+// are protected from noalias violations by a cell type
+DECL_BORROWED_REF_TYPE_FOR(RawGeckoNode)
+DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoNode)
+DECL_BORROWED_REF_TYPE_FOR(RawGeckoElement)
+DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoElement)
+DECL_BORROWED_REF_TYPE_FOR(RawGeckoDocument)
+DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoDocument)
+DECL_BORROWED_MUT_REF_TYPE_FOR(StyleChildrenIterator)
+DECL_BORROWED_MUT_REF_TYPE_FOR(ServoElementSnapshot)
+DECL_BORROWED_REF_TYPE_FOR(nsCSSValue)
+DECL_BORROWED_MUT_REF_TYPE_FOR(nsCSSValue)
+<<<<<<< HEAD
+DECL_BORROWED_REF_TYPE_FOR(RawGeckoPresContext)
+||||||| parent of 019e175... Bug 1317208 - Stylo: Store servo computed values for animation properties; r?birtles,heycam
+=======
+DECL_BORROWED_MUT_REF_TYPE_FOR(RawGeckoAnimationValueList)
+>>>>>>> 019e175... Bug 1317208 - Stylo: Store servo computed values for animation properties; r?birtles,heycam
+
+#undef DECL_ARC_REF_TYPE_FOR
+#undef DECL_OWNED_REF_TYPE_FOR
+#undef DECL_NULLABLE_OWNED_REF_TYPE_FOR
+#undef DECL_BORROWED_REF_TYPE_FOR
+#undef DECL_NULLABLE_BORROWED_REF_TYPE_FOR
+#undef DECL_BORROWED_MUT_REF_TYPE_FOR
+#undef DECL_NULLABLE_BORROWED_MUT_REF_TYPE_FOR
+
+#define SERVO_ARC_TYPE(name_, type_)                 \
+  extern "C" {                                       \
+  void Servo_##name_##_AddRef(type_##Borrowed ptr);  \
+  void Servo_##name_##_Release(type_##Borrowed ptr); \
+  }                                                  \
+  namespace mozilla {                                \
+  template<> struct RefPtrTraits<type_> {            \
+    static void AddRef(type_* aPtr) {                \
+      Servo_##name_##_AddRef(aPtr);                  \
+    }                                                \
+    static void Release(type_* aPtr) {               \
+      Servo_##name_##_Release(aPtr);                 \
+    }                                                \
+  };                                                 \
+  }
+#include "mozilla/ServoArcTypeList.h"
+#undef SERVO_ARC_TYPE
+
+#define DEFINE_BOXED_TYPE(name_, type_)                     \
+  extern "C" void Servo_##name_##_Drop(type_##Owned ptr);   \
+  namespace mozilla {                                       \
+  template<>                                                \
+  class DefaultDelete<type_>                                \
+  {                                                         \
+  public:                                                   \
+    void operator()(type_* aPtr) const                      \
+    {                                                       \
+      Servo_##name_##_Drop(aPtr);                           \
+    }                                                       \
+  };                                                        \
+  }
+
+DEFINE_BOXED_TYPE(StyleSet, RawServoStyleSet);
+
+#undef DEFINE_BOXED_TYPE
+
+#endif // mozilla_ServoBindingTypes_h
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -1198,16 +1198,29 @@ Gecko_PropertyId_IsPrefEnabled(nsCSSProp
 
 void
 Gecko_CSSValue_Drop(nsCSSValueBorrowedMut aCSSValue)
 {
   aCSSValue->~nsCSSValue();
 }
 
 void
+Gecko_nsStyleFont_SetLang(nsStyleFont* aFont, nsIAtom* atom)
+{
+  aFont->mLanguage = atom;
+  aFont->mExplicitLanguage = true;
+}
+
+void
+Gecko_nsStyleFont_CopyLangFrom(nsStyleFont* aFont, const nsStyleFont* aSource)
+{
+  aFont->mLanguage = aSource->mLanguage;
+}
+
+void
 Gecko_LoadStyleSheet(css::Loader* aLoader,
                      ServoStyleSheet* aParent,
                      RawServoImportRuleBorrowed aImportRule,
                      const uint8_t* aURLString,
                      uint32_t aURLStringLength,
                      const uint8_t* aMediaString,
                      uint32_t aMediaStringLength)
 {
new file mode 100644
--- /dev/null
+++ b/layout/style/ServoBindings.cpp.orig
@@ -0,0 +1,1306 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "mozilla/ServoBindings.h"
+
+#include "ChildIterator.h"
+#include "gfxFontFamilyList.h"
+#include "nsAttrValueInlines.h"
+#include "nsCSSProps.h"
+#include "nsCSSParser.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCSSRuleProcessor.h"
+#include "nsContentUtils.h"
+#include "nsDOMTokenList.h"
+#include "nsIContentInlines.h"
+#include "nsIDOMNode.h"
+#include "nsIDocument.h"
+#include "nsIFrame.h"
+#include "nsINode.h"
+#include "nsIPrincipal.h"
+#include "nsMappedAttributes.h"
+#include "nsMediaFeatures.h"
+#include "nsNameSpaceManager.h"
+#include "nsNetUtil.h"
+#include "nsRuleNode.h"
+#include "nsString.h"
+#include "nsStyleStruct.h"
+#include "nsStyleUtil.h"
+#include "nsTArray.h"
+
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/Keyframe.h"
+#include "mozilla/ServoAnimationRule.h"
+#include "mozilla/ServoElementSnapshot.h"
+#include "mozilla/ServoRestyleManager.h"
+#include "mozilla/StyleAnimationValue.h"
+#include "mozilla/DeclarationBlockInlines.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementInlines.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define SERVO_ARC_TYPE(name_, type_) \
+  already_AddRefed<type_>            \
+  type_##Strong::Consume() {         \
+    RefPtr<type_> result;            \
+    result.swap(mPtr);               \
+    return result.forget();          \
+  }
+#include "mozilla/ServoArcTypeList.h"
+#undef SERVO_ARC_TYPE
+
+uint32_t
+Gecko_ChildrenCount(RawGeckoNodeBorrowed aNode)
+{
+  return aNode->GetChildCount();
+}
+
+bool
+Gecko_NodeIsElement(RawGeckoNodeBorrowed aNode)
+{
+  return aNode->IsElement();
+}
+
+bool
+Gecko_IsInDocument(RawGeckoNodeBorrowed aNode)
+{
+  return aNode->IsInComposedDoc();
+}
+
+#ifdef DEBUG
+bool
+Gecko_FlattenedTreeParentIsParent(RawGeckoNodeBorrowed aNode)
+{
+  // Servo calls this in debug builds to verify the result of its own
+  // flattened_tree_parent_is_parent() function.
+  return FlattenedTreeParentIsParent<nsIContent::eForStyle>(aNode);
+}
+#endif
+
+RawGeckoNodeBorrowedOrNull
+Gecko_GetParentNode(RawGeckoNodeBorrowed aNode)
+{
+  MOZ_ASSERT(!FlattenedTreeParentIsParent<nsIContent::eForStyle>(aNode),
+             "Should have taken the inline path");
+  MOZ_ASSERT(aNode->IsContent(), "Slow path only applies to content");
+  const nsIContent* c = aNode->AsContent();
+  return c->GetFlattenedTreeParentNodeInternal(nsIContent::eForStyle);
+}
+
+RawGeckoNodeBorrowedOrNull
+Gecko_GetFirstChild(RawGeckoNodeBorrowed aNode)
+{
+  return aNode->GetFirstChild();
+}
+
+RawGeckoNodeBorrowedOrNull
+Gecko_GetLastChild(RawGeckoNodeBorrowed aNode)
+{
+  return aNode->GetLastChild();
+}
+
+RawGeckoNodeBorrowedOrNull
+Gecko_GetPrevSibling(RawGeckoNodeBorrowed aNode)
+{
+  return aNode->GetPreviousSibling();
+}
+
+RawGeckoNodeBorrowedOrNull
+Gecko_GetNextSibling(RawGeckoNodeBorrowed aNode)
+{
+  return aNode->GetNextSibling();
+}
+
+RawGeckoElementBorrowedOrNull
+Gecko_GetParentElement(RawGeckoElementBorrowed aElement)
+{
+  return aElement->GetFlattenedTreeParentElementForStyle();
+}
+
+RawGeckoElementBorrowedOrNull
+Gecko_GetFirstChildElement(RawGeckoElementBorrowed aElement)
+{
+  return aElement->GetFirstElementChild();
+}
+
+RawGeckoElementBorrowedOrNull Gecko_GetLastChildElement(RawGeckoElementBorrowed aElement)
+{
+  return aElement->GetLastElementChild();
+}
+
+RawGeckoElementBorrowedOrNull
+Gecko_GetPrevSiblingElement(RawGeckoElementBorrowed aElement)
+{
+  return aElement->GetPreviousElementSibling();
+}
+
+RawGeckoElementBorrowedOrNull
+Gecko_GetNextSiblingElement(RawGeckoElementBorrowed aElement)
+{
+  return aElement->GetNextElementSibling();
+}
+
+RawGeckoElementBorrowedOrNull
+Gecko_GetDocumentElement(RawGeckoDocumentBorrowed aDoc)
+{
+  return aDoc->GetDocumentElement();
+}
+
+StyleChildrenIteratorOwnedOrNull
+Gecko_MaybeCreateStyleChildrenIterator(RawGeckoNodeBorrowed aNode)
+{
+  if (!aNode->IsElement()) {
+    return nullptr;
+  }
+
+  const Element* el = aNode->AsElement();
+  return StyleChildrenIterator::IsNeeded(el) ? new StyleChildrenIterator(el)
+                                             : nullptr;
+}
+
+void
+Gecko_DropStyleChildrenIterator(StyleChildrenIteratorOwned aIterator)
+{
+  MOZ_ASSERT(aIterator);
+  delete aIterator;
+}
+
+RawGeckoNodeBorrowed
+Gecko_GetNextStyleChild(StyleChildrenIteratorBorrowedMut aIterator)
+{
+  MOZ_ASSERT(aIterator);
+  return aIterator->GetNextChild();
+}
+
+EventStates::ServoType
+Gecko_ElementState(RawGeckoElementBorrowed aElement)
+{
+  return aElement->StyleState().ServoValue();
+}
+
+bool
+Gecko_IsLink(RawGeckoElementBorrowed aElement)
+{
+  return nsCSSRuleProcessor::IsLink(aElement);
+}
+
+bool
+Gecko_IsTextNode(RawGeckoNodeBorrowed aNode)
+{
+  return aNode->NodeInfo()->NodeType() == nsIDOMNode::TEXT_NODE;
+}
+
+bool
+Gecko_IsVisitedLink(RawGeckoElementBorrowed aElement)
+{
+  return aElement->StyleState().HasState(NS_EVENT_STATE_VISITED);
+}
+
+bool
+Gecko_IsUnvisitedLink(RawGeckoElementBorrowed aElement)
+{
+  return aElement->StyleState().HasState(NS_EVENT_STATE_UNVISITED);
+}
+
+bool
+Gecko_IsRootElement(RawGeckoElementBorrowed aElement)
+{
+  return aElement->OwnerDoc()->GetRootElement() == aElement;
+}
+
+bool
+Gecko_MatchesElement(CSSPseudoClassType aType,
+                     RawGeckoElementBorrowed aElement)
+{
+  return nsCSSPseudoClasses::MatchesElement(aType, aElement).value();
+}
+
+nsIAtom*
+Gecko_LocalName(RawGeckoElementBorrowed aElement)
+{
+  return aElement->NodeInfo()->NameAtom();
+}
+
+nsIAtom*
+Gecko_Namespace(RawGeckoElementBorrowed aElement)
+{
+  int32_t id = aElement->NodeInfo()->NamespaceID();
+  return nsContentUtils::NameSpaceManager()->NameSpaceURIAtomForServo(id);
+}
+
+nsIAtom*
+Gecko_GetElementId(RawGeckoElementBorrowed aElement)
+{
+  const nsAttrValue* attr = aElement->GetParsedAttr(nsGkAtoms::id);
+  return attr ? attr->GetAtomValue() : nullptr;
+}
+
+// Dirtiness tracking.
+uint32_t
+Gecko_GetNodeFlags(RawGeckoNodeBorrowed aNode)
+{
+  return aNode->GetFlags();
+}
+
+void
+Gecko_SetNodeFlags(RawGeckoNodeBorrowed aNode, uint32_t aFlags)
+{
+  const_cast<nsINode*>(aNode)->SetFlags(aFlags);
+}
+
+void
+Gecko_UnsetNodeFlags(RawGeckoNodeBorrowed aNode, uint32_t aFlags)
+{
+  const_cast<nsINode*>(aNode)->UnsetFlags(aFlags);
+}
+
+nsStyleContext*
+Gecko_GetStyleContext(RawGeckoNodeBorrowed aNode, nsIAtom* aPseudoTagOrNull)
+{
+  MOZ_ASSERT(aNode->IsContent());
+  nsIFrame* relevantFrame =
+    ServoRestyleManager::FrameForPseudoElement(aNode->AsContent(),
+                                               aPseudoTagOrNull);
+  if (!relevantFrame) {
+    return nullptr;
+  }
+
+  return relevantFrame->StyleContext();
+}
+
+nsChangeHint
+Gecko_CalcStyleDifference(nsStyleContext* aOldStyleContext,
+                          ServoComputedValuesBorrowed aComputedValues)
+{
+  MOZ_ASSERT(aOldStyleContext);
+  MOZ_ASSERT(aComputedValues);
+
+  // Pass the safe thing, which causes us to miss a potential optimization. See
+  // bug 1289863.
+  nsChangeHint forDescendants = nsChangeHint_Hints_NotHandledForDescendants;
+
+  // Eventually, we should compute things out of these flags like
+  // ElementRestyler::RestyleSelf does and pass the result to the caller to
+  // potentially halt traversal. See bug 1289868.
+  uint32_t equalStructs, samePointerStructs;
+  nsChangeHint result =
+    aOldStyleContext->CalcStyleDifference(aComputedValues,
+                                          forDescendants,
+                                          &equalStructs,
+                                          &samePointerStructs);
+
+  return result;
+}
+
+ServoElementSnapshotOwned
+Gecko_CreateElementSnapshot(RawGeckoElementBorrowed aElement)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return new ServoElementSnapshot(aElement);
+}
+
+void
+Gecko_DropElementSnapshot(ServoElementSnapshotOwned aSnapshot)
+{
+  // Proxy deletes have a lot of overhead, so Servo tries hard to only drop
+  // snapshots on the main thread. However, there are certain cases where
+  // it's unavoidable (i.e. synchronously dropping the style data for the
+  // descendants of a new display:none root).
+  if (MOZ_UNLIKELY(!NS_IsMainThread())) {
+    nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction([=]() { delete aSnapshot; });
+    NS_DispatchToMainThread(task.forget());
+  } else {
+    delete aSnapshot;
+  }
+}
+
+RawServoDeclarationBlockStrongBorrowedOrNull
+Gecko_GetStyleAttrDeclarationBlock(RawGeckoElementBorrowed aElement)
+{
+  DeclarationBlock* decl = aElement->GetInlineStyleDeclaration();
+  if (!decl) {
+    return nullptr;
+  }
+  if (decl->IsGecko()) {
+    // XXX This can happen when nodes are adopted from a Gecko-style-backend
+    //     document into a Servo-style-backend document.  See bug 1330051.
+    NS_WARNING("stylo: requesting a Gecko declaration block?");
+    return nullptr;
+  }
+  return reinterpret_cast<const RawServoDeclarationBlockStrong*>
+    (decl->AsServo()->RefRaw());
+}
+
+RawServoDeclarationBlockStrongBorrowedOrNull
+Gecko_GetHTMLPresentationAttrDeclarationBlock(RawGeckoElementBorrowed aElement)
+{
+  static_assert(sizeof(RefPtr<RawServoDeclarationBlock>) ==
+                sizeof(RawServoDeclarationBlockStrong),
+                "RefPtr should just be a pointer");
+  const nsMappedAttributes* attrs = aElement->GetMappedAttributes();
+  if (!attrs) {
+    return nullptr;
+  }
+
+  const RefPtr<RawServoDeclarationBlock>& servo = attrs->GetServoStyle();
+  return reinterpret_cast<const RawServoDeclarationBlockStrong*>(&servo);
+}
+
+RawServoDeclarationBlockStrong
+Gecko_GetAnimationRule(RawGeckoElementBorrowed aElement,
+                       nsIAtom* aPseudoTag,
+                       EffectCompositor::CascadeLevel aCascadeLevel)
+{
+  MOZ_ASSERT(aElement, "Invalid GeckoElement");
+
+  const RawServoDeclarationBlockStrong emptyDeclarationBlock{ nullptr };
+  nsIDocument* doc = aElement->GetComposedDoc();
+  if (!doc || !doc->GetShell()) {
+    return emptyDeclarationBlock;
+  }
+  nsPresContext* presContext = doc->GetShell()->GetPresContext();
+  if (!presContext) {
+    return emptyDeclarationBlock;
+  }
+
+  CSSPseudoElementType pseudoType =
+    aPseudoTag
+    ? nsCSSPseudoElements::GetPseudoType(
+        aPseudoTag,
+        nsCSSProps::EnabledState::eIgnoreEnabledState)
+    : CSSPseudoElementType::NotPseudo;
+  if (pseudoType != CSSPseudoElementType::NotPseudo &&
+      pseudoType != CSSPseudoElementType::before &&
+      pseudoType != CSSPseudoElementType::after) {
+    return emptyDeclarationBlock;
+  }
+
+  ServoAnimationRule* rule =
+    presContext->EffectCompositor()
+               ->GetServoAnimationRule(aElement, pseudoType, aCascadeLevel);
+  if (!rule) {
+    return emptyDeclarationBlock;
+  }
+  return rule->GetValues();
+}
+
+void
+Gecko_FillAllBackgroundLists(nsStyleImageLayers* aLayers, uint32_t aMaxLen)
+{
+  nsRuleNode::FillAllBackgroundLists(*aLayers, aMaxLen);
+}
+
+void
+Gecko_FillAllMaskLists(nsStyleImageLayers* aLayers, uint32_t aMaxLen)
+{
+  nsRuleNode::FillAllMaskLists(*aLayers, aMaxLen);
+}
+
+template <typename Implementor>
+static nsIAtom*
+AtomAttrValue(Implementor* aElement, nsIAtom* aName)
+{
+  const nsAttrValue* attr = aElement->GetParsedAttr(aName);
+  return attr ? attr->GetAtomValue() : nullptr;
+}
+
+template <typename Implementor, typename MatchFn>
+static bool
+DoMatch(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName, MatchFn aMatch)
+{
+  if (aNS) {
+    int32_t ns = nsContentUtils::NameSpaceManager()->GetNameSpaceID(aNS,
+                                                                    aElement->IsInChromeDocument());
+    NS_ENSURE_TRUE(ns != kNameSpaceID_Unknown, false);
+    const nsAttrValue* value = aElement->GetParsedAttr(aName, ns);
+    return value && aMatch(value);
+  }
+  // No namespace means any namespace - we have to check them all. :-(
+  BorrowedAttrInfo attrInfo;
+  for (uint32_t i = 0; (attrInfo = aElement->GetAttrInfoAt(i)); ++i) {
+    if (attrInfo.mName->LocalName() != aName) {
+      continue;
+    }
+    if (aMatch(attrInfo.mValue)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+template <typename Implementor>
+static bool
+HasAttr(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName)
+{
+  auto match = [](const nsAttrValue* aValue) { return true; };
+  return DoMatch(aElement, aNS, aName, match);
+}
+
+template <typename Implementor>
+static bool
+AttrEquals(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName, nsIAtom* aStr,
+           bool aIgnoreCase)
+{
+  auto match = [aStr, aIgnoreCase](const nsAttrValue* aValue) {
+    return aValue->Equals(aStr, aIgnoreCase ? eIgnoreCase : eCaseMatters);
+  };
+  return DoMatch(aElement, aNS, aName, match);
+}
+
+template <typename Implementor>
+static bool
+AttrDashEquals(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+               nsIAtom* aStr)
+{
+  auto match = [aStr](const nsAttrValue* aValue) {
+    nsAutoString str;
+    aValue->ToString(str);
+    const nsDefaultStringComparator c;
+    return nsStyleUtil::DashMatchCompare(str, nsDependentAtomString(aStr), c);
+  };
+  return DoMatch(aElement, aNS, aName, match);
+}
+
+template <typename Implementor>
+static bool
+AttrIncludes(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+             nsIAtom* aStr)
+{
+  auto match = [aStr](const nsAttrValue* aValue) {
+    nsAutoString str;
+    aValue->ToString(str);
+    const nsDefaultStringComparator c;
+    return nsStyleUtil::ValueIncludes(str, nsDependentAtomString(aStr), c);
+  };
+  return DoMatch(aElement, aNS, aName, match);
+}
+
+template <typename Implementor>
+static bool
+AttrHasSubstring(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+                 nsIAtom* aStr)
+{
+  auto match = [aStr](const nsAttrValue* aValue) {
+    nsAutoString str;
+    aValue->ToString(str);
+    return FindInReadable(str, nsDependentAtomString(aStr));
+  };
+  return DoMatch(aElement, aNS, aName, match);
+}
+
+template <typename Implementor>
+static bool
+AttrHasPrefix(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+              nsIAtom* aStr)
+{
+  auto match = [aStr](const nsAttrValue* aValue) {
+    nsAutoString str;
+    aValue->ToString(str);
+    return StringBeginsWith(str, nsDependentAtomString(aStr));
+  };
+  return DoMatch(aElement, aNS, aName, match);
+}
+
+template <typename Implementor>
+static bool
+AttrHasSuffix(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+              nsIAtom* aStr)
+{
+  auto match = [aStr](const nsAttrValue* aValue) {
+    nsAutoString str;
+    aValue->ToString(str);
+    return StringEndsWith(str, nsDependentAtomString(aStr));
+  };
+  return DoMatch(aElement, aNS, aName, match);
+}
+
+/**
+ * Gets the class or class list (if any) of the implementor. The calling
+ * convention here is rather hairy, and is optimized for getting Servo the
+ * information it needs for hot calls.
+ *
+ * The return value indicates the number of classes. If zero, neither outparam
+ * is valid. If one, the class_ outparam is filled with the atom of the class.
+ * If two or more, the classList outparam is set to point to an array of atoms
+ * representing the class list.
+ *
+ * The array is borrowed and the atoms are not addrefed. These values can be
+ * invalidated by any DOM mutation. Use them in a tight scope.
+ */
+template <typename Implementor>
+static uint32_t
+ClassOrClassList(Implementor* aElement, nsIAtom** aClass, nsIAtom*** aClassList)
+{
+  const nsAttrValue* attr = aElement->GetParsedAttr(nsGkAtoms::_class);
+  if (!attr) {
+    return 0;
+  }
+
+  // For class values with only whitespace, Gecko just stores a string. For the
+  // purposes of the style system, there is no class in this case.
+  if (attr->Type() == nsAttrValue::eString) {
+    MOZ_ASSERT(nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
+                 attr->GetStringValue()).IsEmpty());
+    return 0;
+  }
+
+  // Single tokens are generally stored as an atom. Check that case.
+  if (attr->Type() == nsAttrValue::eAtom) {
+    *aClass = attr->GetAtomValue();
+    return 1;
+  }
+
+  // At this point we should have an atom array. It is likely, but not
+  // guaranteed, that we have two or more elements in the array.
+  MOZ_ASSERT(attr->Type() == nsAttrValue::eAtomArray);
+  nsTArray<nsCOMPtr<nsIAtom>>* atomArray = attr->GetAtomArrayValue();
+  uint32_t length = atomArray->Length();
+
+  // Special case: zero elements.
+  if (length == 0) {
+    return 0;
+  }
+
+  // Special case: one element.
+  if (length == 1) {
+    *aClass = atomArray->ElementAt(0);
+    return 1;
+  }
+
+  // General case: Two or more elements.
+  //
+  // Note: We could also expose this array as an array of nsCOMPtrs, since
+  // bindgen knows what those look like, and eliminate the reinterpret_cast.
+  // But it's not obvious that that would be preferable.
+  static_assert(sizeof(nsCOMPtr<nsIAtom>) == sizeof(nsIAtom*), "Bad simplification");
+  static_assert(alignof(nsCOMPtr<nsIAtom>) == alignof(nsIAtom*), "Bad simplification");
+
+  nsCOMPtr<nsIAtom>* elements = atomArray->Elements();
+  nsIAtom** rawElements = reinterpret_cast<nsIAtom**>(elements);
+  *aClassList = rawElements;
+  return atomArray->Length();
+}
+
+#define SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(prefix_, implementor_)      \
+  nsIAtom* prefix_##AtomAttrValue(implementor_ aElement, nsIAtom* aName)       \
+  {                                                                            \
+    return AtomAttrValue(aElement, aName);                                     \
+  }                                                                            \
+  bool prefix_##HasAttr(implementor_ aElement, nsIAtom* aNS, nsIAtom* aName)   \
+  {                                                                            \
+    return HasAttr(aElement, aNS, aName);                                      \
+  }                                                                            \
+  bool prefix_##AttrEquals(implementor_ aElement, nsIAtom* aNS,                \
+                           nsIAtom* aName, nsIAtom* aStr, bool aIgnoreCase)    \
+  {                                                                            \
+    return AttrEquals(aElement, aNS, aName, aStr, aIgnoreCase);                \
+  }                                                                            \
+  bool prefix_##AttrDashEquals(implementor_ aElement, nsIAtom* aNS,            \
+                               nsIAtom* aName, nsIAtom* aStr)                  \
+  {                                                                            \
+    return AttrDashEquals(aElement, aNS, aName, aStr);                         \
+  }                                                                            \
+  bool prefix_##AttrIncludes(implementor_ aElement, nsIAtom* aNS,              \
+                             nsIAtom* aName, nsIAtom* aStr)                    \
+  {                                                                            \
+    return AttrIncludes(aElement, aNS, aName, aStr);                           \
+  }                                                                            \
+  bool prefix_##AttrHasSubstring(implementor_ aElement, nsIAtom* aNS,          \
+                                 nsIAtom* aName, nsIAtom* aStr)                \
+  {                                                                            \
+    return AttrHasSubstring(aElement, aNS, aName, aStr);                       \
+  }                                                                            \
+  bool prefix_##AttrHasPrefix(implementor_ aElement, nsIAtom* aNS,             \
+                              nsIAtom* aName, nsIAtom* aStr)                   \
+  {                                                                            \
+    return AttrHasPrefix(aElement, aNS, aName, aStr);                          \
+  }                                                                            \
+  bool prefix_##AttrHasSuffix(implementor_ aElement, nsIAtom* aNS,             \
+                              nsIAtom* aName, nsIAtom* aStr)                   \
+  {                                                                            \
+    return AttrHasSuffix(aElement, aNS, aName, aStr);                          \
+  }                                                                            \
+  uint32_t prefix_##ClassOrClassList(implementor_ aElement, nsIAtom** aClass,  \
+                                     nsIAtom*** aClassList)                    \
+  {                                                                            \
+    return ClassOrClassList(aElement, aClass, aClassList);                     \
+  }
+
+SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_, RawGeckoElementBorrowed)
+SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_Snapshot, const ServoElementSnapshot*)
+
+#undef SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS
+
+nsIAtom*
+Gecko_Atomize(const char* aString, uint32_t aLength)
+{
+  return NS_Atomize(nsDependentCSubstring(aString, aLength)).take();
+}
+
+void
+Gecko_AddRefAtom(nsIAtom* aAtom)
+{
+  NS_ADDREF(aAtom);
+}
+
+void
+Gecko_ReleaseAtom(nsIAtom* aAtom)
+{
+  NS_RELEASE(aAtom);
+}
+
+const uint16_t*
+Gecko_GetAtomAsUTF16(nsIAtom* aAtom, uint32_t* aLength)
+{
+  static_assert(sizeof(char16_t) == sizeof(uint16_t), "Servo doesn't know what a char16_t is");
+  MOZ_ASSERT(aAtom);
+  *aLength = aAtom->GetLength();
+
+  // We need to manually cast from char16ptr_t to const char16_t* to handle the
+  // MOZ_USE_CHAR16_WRAPPER we use on WIndows.
+  return reinterpret_cast<const uint16_t*>(static_cast<const char16_t*>(aAtom->GetUTF16String()));
+}
+
+bool
+Gecko_AtomEqualsUTF8(nsIAtom* aAtom, const char* aString, uint32_t aLength)
+{
+  // XXXbholley: We should be able to do this without converting, I just can't
+  // find the right thing to call.
+  nsDependentAtomString atomStr(aAtom);
+  NS_ConvertUTF8toUTF16 inStr(nsDependentCSubstring(aString, aLength));
+  return atomStr.Equals(inStr);
+}
+
+bool
+Gecko_AtomEqualsUTF8IgnoreCase(nsIAtom* aAtom, const char* aString, uint32_t aLength)
+{
+  // XXXbholley: We should be able to do this without converting, I just can't
+  // find the right thing to call.
+  nsDependentAtomString atomStr(aAtom);
+  NS_ConvertUTF8toUTF16 inStr(nsDependentCSubstring(aString, aLength));
+  return nsContentUtils::EqualsIgnoreASCIICase(atomStr, inStr);
+}
+
+void
+Gecko_FontFamilyList_Clear(FontFamilyList* aList) {
+  aList->Clear();
+}
+
+void
+Gecko_FontFamilyList_AppendNamed(FontFamilyList* aList, nsIAtom* aName)
+{
+  // Servo doesn't record whether the name was quoted or unquoted, so just
+  // assume unquoted for now.
+  FontFamilyName family;
+  aName->ToString(family.mName);
+  aList->Append(family);
+}
+
+void
+Gecko_FontFamilyList_AppendGeneric(FontFamilyList* aList, FontFamilyType aType)
+{
+  aList->Append(FontFamilyName(aType));
+}
+
+void
+Gecko_CopyFontFamilyFrom(nsFont* dst, const nsFont* src)
+{
+  dst->fontlist = src->fontlist;
+}
+
+void
+Gecko_SetListStyleType(nsStyleList* style_struct, uint32_t type)
+{
+  // Builtin counter styles are static and use no-op refcounting, and thus are
+  // safe to use off-main-thread.
+  style_struct->SetCounterStyle(CounterStyleManager::GetBuiltinStyle(type));
+}
+
+void
+Gecko_CopyListStyleTypeFrom(nsStyleList* dst, const nsStyleList* src)
+{
+  dst->SetCounterStyle(src->GetCounterStyle());
+}
+
+NS_IMPL_HOLDER_FFI_REFCOUNTING(nsIPrincipal, Principal)
+NS_IMPL_HOLDER_FFI_REFCOUNTING(nsIURI, URI)
+
+void
+Gecko_SetMozBinding(nsStyleDisplay* aDisplay,
+                    const uint8_t* aURLString, uint32_t aURLStringLength,
+                    ThreadSafeURIHolder* aBaseURI,
+                    ThreadSafeURIHolder* aReferrer,
+                    ThreadSafePrincipalHolder* aPrincipal)
+{
+  if (!aURLString) {
+    aDisplay->mBinding = nullptr;
+    return;
+  }
+
+  MOZ_ASSERT(aDisplay);
+  MOZ_ASSERT(aBaseURI);
+  MOZ_ASSERT(aReferrer);
+  MOZ_ASSERT(aPrincipal);
+
+  nsString url;
+  nsDependentCSubstring urlString(reinterpret_cast<const char*>(aURLString),
+                                  aURLStringLength);
+  AppendUTF8toUTF16(urlString, url);
+  RefPtr<nsStringBuffer> urlBuffer = nsCSSValue::BufferFromString(url);
+
+  aDisplay->mBinding =
+    new css::URLValue(urlBuffer, do_AddRef(aBaseURI),
+                      do_AddRef(aReferrer), do_AddRef(aPrincipal));
+}
+
+void
+Gecko_CopyMozBindingFrom(nsStyleDisplay* aDest, const nsStyleDisplay* aSrc)
+{
+  aDest->mBinding = aSrc->mBinding;
+}
+
+
+void
+Gecko_SetNullImageValue(nsStyleImage* aImage)
+{
+  MOZ_ASSERT(aImage);
+  aImage->SetNull();
+}
+
+void
+Gecko_SetGradientImageValue(nsStyleImage* aImage, nsStyleGradient* aGradient)
+{
+  MOZ_ASSERT(aImage);
+  aImage->SetGradientData(aGradient);
+}
+
+static already_AddRefed<nsStyleImageRequest>
+CreateStyleImageRequest(nsStyleImageRequest::Mode aModeFlags,
+                        const uint8_t* aURLString, uint32_t aURLStringLength,
+                        ThreadSafeURIHolder* aBaseURI,
+                        ThreadSafeURIHolder* aReferrer,
+                        ThreadSafePrincipalHolder* aPrincipal)
+{
+  MOZ_ASSERT(aURLString);
+  MOZ_ASSERT(aBaseURI);
+  MOZ_ASSERT(aReferrer);
+  MOZ_ASSERT(aPrincipal);
+
+  nsString url;
+  nsDependentCSubstring urlString(reinterpret_cast<const char*>(aURLString),
+                                  aURLStringLength);
+  AppendUTF8toUTF16(urlString, url);
+  RefPtr<nsStringBuffer> urlBuffer = nsCSSValue::BufferFromString(url);
+
+  RefPtr<nsStyleImageRequest> req =
+    new nsStyleImageRequest(aModeFlags, urlBuffer, do_AddRef(aBaseURI),
+                            do_AddRef(aReferrer), do_AddRef(aPrincipal));
+  return req.forget();
+}
+
+void
+Gecko_SetUrlImageValue(nsStyleImage* aImage,
+                       const uint8_t* aURLString, uint32_t aURLStringLength,
+                       ThreadSafeURIHolder* aBaseURI,
+                       ThreadSafeURIHolder* aReferrer,
+                       ThreadSafePrincipalHolder* aPrincipal)
+{
+  RefPtr<nsStyleImageRequest> req =
+    CreateStyleImageRequest(nsStyleImageRequest::Mode::Track,
+                            aURLString, aURLStringLength,
+                            aBaseURI, aReferrer, aPrincipal);
+  aImage->SetImageRequest(req.forget());
+}
+
+void
+Gecko_CopyImageValueFrom(nsStyleImage* aImage, const nsStyleImage* aOther)
+{
+  MOZ_ASSERT(aImage);
+  MOZ_ASSERT(aOther);
+
+  *aImage = *aOther;
+}
+
+void
+Gecko_SetCursorArrayLength(nsStyleUserInterface* aStyleUI, size_t aLen)
+{
+  aStyleUI->mCursorImages.Clear();
+  aStyleUI->mCursorImages.SetLength(aLen);
+}
+
+void
+Gecko_SetCursorImage(nsCursorImage* aCursor,
+                     const uint8_t* aURLString, uint32_t aURLStringLength,
+                     ThreadSafeURIHolder* aBaseURI,
+                     ThreadSafeURIHolder* aReferrer,
+                     ThreadSafePrincipalHolder* aPrincipal)
+{
+  aCursor->mImage =
+    CreateStyleImageRequest(nsStyleImageRequest::Mode::Discard,
+                            aURLString, aURLStringLength,
+                            aBaseURI, aReferrer, aPrincipal);
+}
+
+void
+Gecko_CopyCursorArrayFrom(nsStyleUserInterface* aDest,
+                          const nsStyleUserInterface* aSrc)
+{
+  aDest->mCursorImages = aSrc->mCursorImages;
+}
+
+nsStyleGradient*
+Gecko_CreateGradient(uint8_t aShape,
+                     uint8_t aSize,
+                     bool aRepeating,
+                     bool aLegacySyntax,
+                     uint32_t aStopCount)
+{
+  nsStyleGradient* result = new nsStyleGradient();
+
+  result->mShape = aShape;
+  result->mSize = aSize;
+  result->mRepeating = aRepeating;
+  result->mLegacySyntax = aLegacySyntax;
+
+  result->mAngle.SetNoneValue();
+  result->mBgPosX.SetNoneValue();
+  result->mBgPosY.SetNoneValue();
+  result->mRadiusX.SetNoneValue();
+  result->mRadiusY.SetNoneValue();
+
+  nsStyleGradientStop dummyStop;
+  dummyStop.mLocation.SetNoneValue();
+  dummyStop.mColor = NS_RGB(0, 0, 0);
+  dummyStop.mIsInterpolationHint = 0;
+
+  for (uint32_t i = 0; i < aStopCount; i++) {
+    result->mStops.AppendElement(dummyStop);
+  }
+
+  return result;
+}
+
+void
+Gecko_SetListStyleImageNone(nsStyleList* aList)
+{
+  aList->mListStyleImage = nullptr;
+}
+
+void
+Gecko_SetListStyleImage(nsStyleList* aList,
+                        const uint8_t* aURLString, uint32_t aURLStringLength,
+                        ThreadSafeURIHolder* aBaseURI,
+                        ThreadSafeURIHolder* aReferrer,
+                        ThreadSafePrincipalHolder* aPrincipal)
+{
+  aList->mListStyleImage =
+    CreateStyleImageRequest(nsStyleImageRequest::Mode(0),
+                            aURLString, aURLStringLength,
+                            aBaseURI, aReferrer, aPrincipal);
+}
+
+void
+Gecko_CopyListStyleImageFrom(nsStyleList* aList, const nsStyleList* aSource)
+{
+  aList->mListStyleImage = aSource->mListStyleImage;
+}
+
+void
+Gecko_EnsureTArrayCapacity(void* aArray, size_t aCapacity, size_t aElemSize)
+{
+  auto base =
+    reinterpret_cast<nsTArray_base<nsTArrayInfallibleAllocator,
+                                   nsTArray_CopyWithMemutils>*>(aArray);
+
+  base->EnsureCapacity<nsTArrayInfallibleAllocator>(aCapacity, aElemSize);
+}
+
+void
+Gecko_ClearPODTArray(void* aArray, size_t aElementSize, size_t aElementAlign)
+{
+  auto base =
+    reinterpret_cast<nsTArray_base<nsTArrayInfallibleAllocator,
+                                   nsTArray_CopyWithMemutils>*>(aArray);
+
+  base->template ShiftData<nsTArrayInfallibleAllocator>(0, base->Length(), 0,
+                                                        aElementSize, aElementAlign);
+}
+
+void
+Gecko_ClearAndResizeStyleContents(nsStyleContent* aContent, uint32_t aHowMany)
+{
+  aContent->AllocateContents(aHowMany);
+}
+
+void
+Gecko_CopyStyleContentsFrom(nsStyleContent* aContent, const nsStyleContent* aOther)
+{
+  uint32_t count = aOther->ContentCount();
+
+  aContent->AllocateContents(count);
+
+  for (uint32_t i = 0; i < count; ++i) {
+    aContent->ContentAt(i) = aOther->ContentAt(i);
+  }
+}
+
+void
+Gecko_EnsureImageLayersLength(nsStyleImageLayers* aLayers, size_t aLen,
+                              nsStyleImageLayers::LayerType aLayerType)
+{
+  size_t oldLength = aLayers->mLayers.Length();
+
+  aLayers->mLayers.EnsureLengthAtLeast(aLen);
+
+  for (size_t i = oldLength; i < aLen; ++i) {
+    aLayers->mLayers[i].Initialize(aLayerType);
+  }
+}
+
+void
+Gecko_EnsureStyleAnimationArrayLength(void* aArray, size_t aLen)
+{
+  auto base =
+    reinterpret_cast<nsStyleAutoArray<StyleAnimation>*>(aArray);
+
+  size_t oldLength = base->Length();
+
+  base->EnsureLengthAtLeast(aLen);
+
+  for (size_t i = oldLength; i < aLen; ++i) {
+    (*base)[i].SetInitialValues();
+  }
+}
+
+Keyframe*
+Gecko_AnimationAppendKeyframe(RawGeckoKeyframeListBorrowedMut aKeyframes,
+                              float aOffset,
+                              const nsTimingFunction* aTimingFunction)
+{
+  Keyframe* keyframe = aKeyframes->AppendElement();
+  keyframe->mOffset.emplace(aOffset);
+  if (aTimingFunction &&
+      aTimingFunction->mType != nsTimingFunction::Type::Linear) {
+    keyframe->mTimingFunction.emplace();
+    keyframe->mTimingFunction->Init(*aTimingFunction);
+  }
+
+  return keyframe;
+}
+
+void
+Gecko_ResetStyleCoord(nsStyleUnit* aUnit, nsStyleUnion* aValue)
+{
+  nsStyleCoord::Reset(*aUnit, *aValue);
+}
+
+void
+Gecko_SetStyleCoordCalcValue(nsStyleUnit* aUnit, nsStyleUnion* aValue, nsStyleCoord::CalcValue aCalc)
+{
+  // Calc units should be cleaned up first
+  MOZ_ASSERT(*aUnit != nsStyleUnit::eStyleUnit_Calc);
+  nsStyleCoord::Calc* calcRef = new nsStyleCoord::Calc();
+  calcRef->mLength = aCalc.mLength;
+  calcRef->mPercent = aCalc.mPercent;
+  calcRef->mHasPercent = aCalc.mHasPercent;
+  *aUnit = nsStyleUnit::eStyleUnit_Calc;
+  aValue->mPointer = calcRef;
+  calcRef->AddRef();
+}
+
+void
+Gecko_CopyClipPathValueFrom(mozilla::StyleClipPath* aDst, const mozilla::StyleClipPath* aSrc)
+{
+  MOZ_ASSERT(aDst);
+  MOZ_ASSERT(aSrc);
+
+  *aDst = *aSrc;
+}
+
+void
+Gecko_DestroyClipPath(mozilla::StyleClipPath* aClip)
+{
+  aClip->~StyleClipPath();
+}
+
+mozilla::StyleBasicShape*
+Gecko_NewBasicShape(mozilla::StyleBasicShapeType aType)
+{
+  RefPtr<StyleBasicShape> ptr = new mozilla::StyleBasicShape(aType);
+  return ptr.forget().take();
+}
+
+void
+Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len)
+{
+  effects->mFilters.Clear();
+  effects->mFilters.SetLength(new_len);
+}
+
+void
+Gecko_CopyFiltersFrom(nsStyleEffects* aSrc, nsStyleEffects* aDest)
+{
+  aDest->mFilters = aSrc->mFilters;
+}
+
+NS_IMPL_THREADSAFE_FFI_REFCOUNTING(nsStyleCoord::Calc, Calc);
+
+nsCSSShadowArray*
+Gecko_NewCSSShadowArray(uint32_t aLen)
+{
+  RefPtr<nsCSSShadowArray> arr = new(aLen) nsCSSShadowArray(aLen);
+  return arr.forget().take();
+}
+
+NS_IMPL_THREADSAFE_FFI_REFCOUNTING(nsCSSShadowArray, CSSShadowArray);
+
+nsStyleQuoteValues*
+Gecko_NewStyleQuoteValues(uint32_t aLen)
+{
+  RefPtr<nsStyleQuoteValues> values = new nsStyleQuoteValues;
+  values->mQuotePairs.SetLength(aLen);
+  return values.forget().take();
+}
+
+NS_IMPL_THREADSAFE_FFI_REFCOUNTING(nsStyleQuoteValues, QuoteValues);
+
+nsCSSValueSharedList*
+Gecko_NewCSSValueSharedList(uint32_t aLen)
+{
+  RefPtr<nsCSSValueSharedList> list = new nsCSSValueSharedList;
+  if (aLen == 0) {
+    return list.forget().take();
+  }
+
+  list->mHead = new nsCSSValueList;
+  nsCSSValueList* cur = list->mHead;
+  for (uint32_t i = 0; i < aLen - 1; i++) {
+    cur->mNext = new nsCSSValueList;
+    cur = cur->mNext;
+  }
+
+  return list.forget().take();
+}
+
+void
+Gecko_CSSValue_SetAbsoluteLength(nsCSSValueBorrowedMut aCSSValue, nscoord aLen)
+{
+  aCSSValue->SetIntegerCoordValue(aLen);
+}
+
+nscoord
+Gecko_CSSValue_GetAbsoluteLength(nsCSSValueBorrowed aCSSValue)
+{
+  // SetIntegerCoordValue() which is used in Gecko_CSSValue_SetAbsoluteLength()
+  // converts values by nsPresContext::AppUnitsToFloatCSSPixels() and stores
+  // values in eCSSUnit_Pixel unit. We need to convert the values back to app
+  // units by GetPixelLength().
+  MOZ_ASSERT(aCSSValue->GetUnit() == eCSSUnit_Pixel,
+             "The unit should be eCSSUnit_Pixel");
+  return aCSSValue->GetPixelLength();
+}
+
+void
+Gecko_CSSValue_SetNumber(nsCSSValueBorrowedMut aCSSValue, float aNumber)
+{
+  aCSSValue->SetFloatValue(aNumber, eCSSUnit_Number);
+}
+
+float
+Gecko_CSSValue_GetNumber(nsCSSValueBorrowed aCSSValue)
+{
+  return aCSSValue->GetFloatValue();
+}
+
+void
+Gecko_CSSValue_SetKeyword(nsCSSValueBorrowedMut aCSSValue, nsCSSKeyword aKeyword)
+{
+  aCSSValue->SetEnumValue(aKeyword);
+}
+
+nsCSSKeyword
+Gecko_CSSValue_GetKeyword(nsCSSValueBorrowed aCSSValue)
+{
+  return aCSSValue->GetKeywordValue();
+}
+
+void
+Gecko_CSSValue_SetPercentage(nsCSSValueBorrowedMut aCSSValue, float aPercent)
+{
+  aCSSValue->SetPercentValue(aPercent);
+}
+
+float
+Gecko_CSSValue_GetPercentage(nsCSSValueBorrowed aCSSValue)
+{
+  return aCSSValue->GetPercentValue();
+}
+
+void
+Gecko_CSSValue_SetAngle(nsCSSValueBorrowedMut aCSSValue, float aRadians)
+{
+  aCSSValue->SetFloatValue(aRadians, eCSSUnit_Radian);
+}
+
+float
+Gecko_CSSValue_GetAngle(nsCSSValueBorrowed aCSSValue)
+{
+  // Unfortunately nsCSSValue.GetAngleValueInRadians() returns double,
+  // so we use GetAngleValue() instead.
+  MOZ_ASSERT(aCSSValue->GetUnit() == eCSSUnit_Radian,
+             "The unit should be eCSSUnit_Radian");
+  return aCSSValue->GetAngleValue();
+}
+
+void
+Gecko_CSSValue_SetCalc(nsCSSValueBorrowedMut aCSSValue, nsStyleCoord::CalcValue aCalc)
+{
+  aCSSValue->SetCalcValue(&aCalc);
+}
+
+nsStyleCoord::CalcValue
+Gecko_CSSValue_GetCalc(nsCSSValueBorrowed aCSSValue)
+{
+  return aCSSValue->GetCalcValue();
+}
+
+void
+Gecko_CSSValue_SetFunction(nsCSSValueBorrowedMut aCSSValue, int32_t aLen)
+{
+  nsCSSValue::Array* arr = nsCSSValue::Array::Create(aLen);
+  aCSSValue->SetArrayValue(arr, eCSSUnit_Function);
+}
+
+nsCSSValueBorrowedMut
+Gecko_CSSValue_GetArrayItem(nsCSSValueBorrowedMut aCSSValue, int32_t aIndex)
+{
+  return &aCSSValue->GetArrayValue()->Item(aIndex);
+}
+
+nsCSSValueBorrowed
+Gecko_CSSValue_GetArrayItemConst(nsCSSValueBorrowed aCSSValue, int32_t aIndex)
+{
+  return &aCSSValue->GetArrayValue()->Item(aIndex);
+}
+
+
+bool
+Gecko_PropertyId_IsPrefEnabled(nsCSSPropertyID id)
+{
+  return nsCSSProps::IsEnabled(id);
+}
+
+void
+Gecko_CSSValue_Drop(nsCSSValueBorrowedMut aCSSValue)
+{
+  aCSSValue->~nsCSSValue();
+}
+
+void
+Gecko_nsStyleFont_SetLang(nsStyleFont* aFont, nsString& aString)
+{
+  nsContentUtils::ASCIIToLower(aString);
+  aFont->mLanguage = NS_Atomize(aString);
+  aFont->mExplicitLanguage = true;
+}
+
+void
+Gecko_nsStyleFont_CopyLangFrom(nsStyleFont* aFont, const nsStyleFont* aSource)
+{
+  aFont->mLanguage = aSource->mLanguage;
+}
+
+void
+Gecko_LoadStyleSheet(css::Loader* aLoader,
+                     ServoStyleSheet* aParent,
+                     RawServoImportRuleBorrowed aImportRule,
+                     const uint8_t* aURLString,
+                     uint32_t aURLStringLength,
+                     const uint8_t* aMediaString,
+                     uint32_t aMediaStringLength)
+{
+  MOZ_ASSERT(aLoader, "Should've catched this before");
+  MOZ_ASSERT(aParent, "Only used for @import, so parent should exist!");
+  MOZ_ASSERT(aURLString, "Invalid URLs shouldn't be loaded!");
+  RefPtr<nsMediaList> media = new nsMediaList();
+  if (aMediaStringLength) {
+    MOZ_ASSERT(aMediaString);
+    // TODO(emilio, bug 1325878): This is not great, though this is going away
+    // soon anyway, when we can have a Servo-backed nsMediaList.
+    nsDependentCSubstring medium(reinterpret_cast<const char*>(aMediaString),
+                                 aMediaStringLength);
+    nsCSSParser mediumParser(aLoader);
+    mediumParser.ParseMediaList(
+        NS_ConvertUTF8toUTF16(medium), nullptr, 0, media);
+  }
+
+  nsDependentCSubstring urlSpec(reinterpret_cast<const char*>(aURLString),
+                                aURLStringLength);
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), urlSpec);
+
+  if (NS_FAILED(rv)) {
+    // Servo and Gecko have different ideas of what a valid URL is, so we might
+    // get in here with a URL string that NS_NewURI can't handle.  If so,
+    // silently do nothing.  Eventually we should be able to assert that the
+    // NS_NewURI succeeds, here.
+    return;
+  }
+
+  aLoader->LoadChildSheet(aParent, uri, media, nullptr, aImportRule, nullptr);
+}
+
+const nsMediaFeature*
+Gecko_GetMediaFeatures()
+{
+  return nsMediaFeatures::features;
+}
+
+NS_IMPL_THREADSAFE_FFI_REFCOUNTING(nsCSSValueSharedList, CSSValueSharedList);
+
+#define STYLE_STRUCT(name, checkdata_cb)                                      \
+                                                                              \
+void                                                                          \
+Gecko_Construct_Default_nsStyle##name(nsStyle##name* ptr,                     \
+                                      const nsPresContext* pres_context)      \
+{                                                                             \
+  new (ptr) nsStyle##name(pres_context);                                      \
+}                                                                             \
+                                                                              \
+void                                                                          \
+Gecko_CopyConstruct_nsStyle##name(nsStyle##name* ptr,                         \
+                                  const nsStyle##name* other)                 \
+{                                                                             \
+  new (ptr) nsStyle##name(*other);                                            \
+}                                                                             \
+                                                                              \
+void                                                                          \
+Gecko_Destroy_nsStyle##name(nsStyle##name* ptr)                               \
+{                                                                             \
+  ptr->~nsStyle##name();                                                      \
+}
+
+void
+Gecko_Construct_nsStyleVariables(nsStyleVariables* ptr)
+{
+  new (ptr) nsStyleVariables();
+}
+
+#include "nsStyleStructList.h"
+
+#undef STYLE_STRUCT
+
+#ifndef MOZ_STYLO
+#define SERVO_BINDING_FUNC(name_, return_, ...)                               \
+  return_ name_(__VA_ARGS__) {                                                \
+    MOZ_CRASH("stylo: shouldn't be calling " #name_ "in a non-stylo build");  \
+  }
+#include "ServoBindingList.h"
+#undef SERVO_BINDING_FUNC
+#endif
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -317,16 +317,19 @@ void Gecko_CSSValue_SetKeyword(nsCSSValu
 void Gecko_CSSValue_SetPercentage(nsCSSValueBorrowedMut css_value, float percent);
 void Gecko_CSSValue_SetAngle(nsCSSValueBorrowedMut css_value, float radians);
 void Gecko_CSSValue_SetCalc(nsCSSValueBorrowedMut css_value, nsStyleCoord::CalcValue calc);
 void Gecko_CSSValue_SetFunction(nsCSSValueBorrowedMut css_value, int32_t len);
 void Gecko_CSSValue_Drop(nsCSSValueBorrowedMut css_value);
 NS_DECL_THREADSAFE_FFI_REFCOUNTING(nsCSSValueSharedList, CSSValueSharedList);
 bool Gecko_PropertyId_IsPrefEnabled(nsCSSPropertyID id);
 
+void Gecko_nsStyleFont_SetLang(nsStyleFont* font, nsIAtom* atom);
+void Gecko_nsStyleFont_CopyLangFrom(nsStyleFont* aFont, const nsStyleFont* aSource);
+
 const nsMediaFeature* Gecko_GetMediaFeatures();
 
 // Style-struct management.
 #define STYLE_STRUCT(name, checkdata_cb)                                       \
   void Gecko_Construct_Default_nsStyle##name(                                  \
     nsStyle##name* ptr,                                                        \
     RawGeckoPresContextBorrowed pres_context);                                 \
   void Gecko_CopyConstruct_nsStyle##name(nsStyle##name* ptr,                   \
--- a/layout/style/ServoSpecifiedValues.cpp
+++ b/layout/style/ServoSpecifiedValues.cpp
@@ -40,17 +40,18 @@ ServoSpecifiedValues::PropertyIsSet(nsCS
              "Presentation attribute mappers should never attempt to set the same property twice");
   return false;
 }
 
 void
 ServoSpecifiedValues::SetIdentStringValue(nsCSSPropertyID aId,
                                           const nsString& aValue)
 {
-  Servo_DeclarationBlock_SetIdentStringValue(mDecl, aId, aValue);
+  nsCOMPtr<nsIAtom> atom = NS_Atomize(aValue);
+  Servo_DeclarationBlock_SetIdentStringValue(mDecl, aId, atom);
 }
 
 void
 ServoSpecifiedValues::SetKeywordValue(nsCSSPropertyID aId, int32_t aValue)
 {
   Servo_DeclarationBlock_SetKeywordValue(mDecl, aId, aValue);
 }
 
--- a/servo/components/style/build_gecko.rs
+++ b/servo/components/style/build_gecko.rs
@@ -485,17 +485,17 @@ mod bindings {
             .disable_name_namespacing()
             .with_codegen_config(CodegenConfig {
                 functions: true,
                 ..CodegenConfig::nothing()
             })
             .header(add_include("mozilla/ServoBindings.h"))
             .hide_type("nsACString_internal")
             .hide_type("nsAString_internal")
-            .raw_line("pub use nsstring::{nsACString, nsAString};")
+            .raw_line("pub use nsstring::{nsACString, nsAString, nsString};")
             .raw_line("type nsACString_internal = nsACString;")
             .raw_line("type nsAString_internal = nsAString;")
             .whitelisted_function("Servo_.*")
             .whitelisted_function("Gecko_.*");
         let structs_types = [
             "RawGeckoDocument",
             "RawGeckoElement",
             "RawGeckoKeyframeList",
--- a/servo/components/style/gecko_bindings/bindings.rs
+++ b/servo/components/style/gecko_bindings/bindings.rs
@@ -1,11 +1,11 @@
 /* automatically generated by rust-bindgen */
 
-pub use nsstring::{nsACString, nsAString};
+pub use nsstring::{nsACString, nsAString, nsString};
 type nsACString_internal = nsACString;
 type nsAString_internal = nsAString;
 use gecko_bindings::structs::RawGeckoDocument;
 use gecko_bindings::structs::RawGeckoElement;
 use gecko_bindings::structs::RawGeckoKeyframeList;
 use gecko_bindings::structs::RawGeckoNode;
 use gecko_bindings::structs::RawGeckoAnimationValueList;
 use gecko_bindings::structs::RawServoAnimationValue;
@@ -813,16 +813,24 @@ extern "C" {
 extern "C" {
     pub fn Gecko_ReleaseCSSValueSharedListArbitraryThread(aPtr:
                                                               *mut nsCSSValueSharedList);
 }
 extern "C" {
     pub fn Gecko_PropertyId_IsPrefEnabled(id: nsCSSPropertyID) -> bool;
 }
 extern "C" {
+    pub fn Gecko_nsStyleFont_SetLang(font: *mut nsStyleFont,
+                                     atom: *mut nsIAtom);
+}
+extern "C" {
+    pub fn Gecko_nsStyleFont_CopyLangFrom(aFont: *mut nsStyleFont,
+                                          aSource: *const nsStyleFont);
+}
+extern "C" {
     pub fn Gecko_GetMediaFeatures() -> *const nsMediaFeature;
 }
 extern "C" {
     pub fn Gecko_Construct_Default_nsStyleFont(ptr: *mut nsStyleFont,
                                                pres_context:
                                                    RawGeckoPresContextBorrowed);
 }
 extern "C" {
@@ -1416,18 +1424,17 @@ extern "C" {
                                                 property: nsCSSPropertyID)
      -> bool;
 }
 extern "C" {
     pub fn Servo_DeclarationBlock_SetIdentStringValue(declarations:
                                                           RawServoDeclarationBlockBorrowed,
                                                       property:
                                                           nsCSSPropertyID,
-                                                      value:
-                                                          *const nsAString_internal);
+                                                      value: *mut nsIAtom);
 }
 extern "C" {
     pub fn Servo_DeclarationBlock_SetKeywordValue(declarations:
                                                       RawServoDeclarationBlockBorrowed,
                                                   property: nsCSSPropertyID,
                                                   value: i32);
 }
 extern "C" {
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -28,16 +28,18 @@ use gecko_bindings::bindings::Gecko_Copy
 use gecko_bindings::bindings::Gecko_CopyMozBindingFrom;
 use gecko_bindings::bindings::Gecko_EnsureImageLayersLength;
 use gecko_bindings::bindings::Gecko_FontFamilyList_AppendGeneric;
 use gecko_bindings::bindings::Gecko_FontFamilyList_AppendNamed;
 use gecko_bindings::bindings::Gecko_FontFamilyList_Clear;
 use gecko_bindings::bindings::Gecko_SetCursorArrayLength;
 use gecko_bindings::bindings::Gecko_SetCursorImage;
 use gecko_bindings::bindings::Gecko_NewCSSShadowArray;
+use gecko_bindings::bindings::Gecko_nsStyleFont_SetLang;
+use gecko_bindings::bindings::Gecko_nsStyleFont_CopyLangFrom;
 use gecko_bindings::bindings::Gecko_SetListStyleImage;
 use gecko_bindings::bindings::Gecko_SetListStyleImageNone;
 use gecko_bindings::bindings::Gecko_SetListStyleType;
 use gecko_bindings::bindings::Gecko_SetMozBinding;
 use gecko_bindings::bindings::Gecko_SetNullImageValue;
 use gecko_bindings::bindings::ServoComputedValuesBorrowedOrNull;
 use gecko_bindings::bindings::{Gecko_ResetFilters, Gecko_CopyFiltersFrom};
 use gecko_bindings::bindings::RawGeckoPresContextBorrowed;
@@ -49,17 +51,17 @@ use gecko::values::convert_nscolor_to_rg
 use gecko::values::convert_rgba_to_nscolor;
 use gecko::values::GeckoStyleCoordConvertible;
 use gecko::values::round_border_to_device_pixels;
 use logical_geometry::WritingMode;
 use properties::longhands;
 use properties::{DeclaredValue, Importance, LonghandId};
 use properties::{PropertyDeclaration, PropertyDeclarationBlock, PropertyDeclarationId};
 use std::fmt::{self, Debug};
-use std::mem::{transmute, zeroed};
+use std::mem::{forget, transmute, zeroed};
 use std::ptr;
 use std::sync::Arc;
 use std::cmp;
 use values::computed::ToComputedValue;
 use values::{Either, Auto};
 use computed_values::border_style;
 
 pub mod style_structs {
@@ -967,17 +969,17 @@ fn static_assert() {
     % endfor
 
     pub fn outline_has_nonzero_width(&self) -> bool {
         self.gecko.mActualOutlineWidth != 0
     }
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="Font"
-    skip_longhands="font-family font-size font-size-adjust font-weight font-synthesis"
+    skip_longhands="font-family font-size font-size-adjust font-weight font-synthesis -x-lang"
     skip_additionals="*">
 
     pub fn set_font_family(&mut self, v: longhands::font_family::computed_value::T) {
         use properties::longhands::font_family::computed_value::FontFamily;
         use gecko_bindings::structs::FontFamilyType;
 
         let list = &mut self.gecko.mFont.fontlist;
         unsafe { Gecko_FontFamilyList_Clear(list); }
@@ -1076,16 +1078,31 @@ fn static_assert() {
         use values::specified::Number;
 
         match self.gecko.mFont.sizeAdjust {
             -1.0 => T::None,
             _ => T::Number(Number(self.gecko.mFont.sizeAdjust)),
         }
     }
 
+    #[allow(non_snake_case)]
+    pub fn set__x_lang(&mut self, v: longhands::_x_lang::computed_value::T) {
+        let ptr = v.0.as_ptr();
+        forget(v);
+        unsafe {
+            Gecko_nsStyleFont_SetLang(&mut self.gecko, ptr);
+        }
+    }
+
+    #[allow(non_snake_case)]
+    pub fn copy__x_lang_from(&mut self, other: &Self) {
+        unsafe {
+            Gecko_nsStyleFont_CopyLangFrom(&mut self.gecko, &other.gecko);
+        }
+    }
 </%self:impl_trait>
 
 <%def name="impl_copy_animation_value(ident, gecko_ffi_name)">
     #[allow(non_snake_case)]
     pub fn copy_animation_${ident}_from(&mut self, other: &Self) {
         unsafe { self.gecko.mAnimations.ensure_len(other.gecko.mAnimations.len()) };
 
         let count = other.gecko.mAnimation${gecko_ffi_name}Count;
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -756,8 +756,45 @@
             Ok(SpecifiedValue::Normal)
         } else {
             input.expect_string().map(|cow| {
                 SpecifiedValue::Override(cow.into_owned())
             })
         }
     }
 </%helpers:longhand>
+
+<%helpers:longhand name="-x-lang" products="gecko" animatable="False" internal="True"
+                   spec="Internal (not web-exposed)"
+                   internal="True">
+    use values::HasViewportPercentage;
+    use values::computed::ComputedValueAsSpecified;
+    pub use self::computed_value::T as SpecifiedValue;
+
+    impl ComputedValueAsSpecified for SpecifiedValue {}
+    no_viewport_percentage!(SpecifiedValue);
+
+    pub mod computed_value {
+        use Atom;
+        use std::fmt;
+        use style_traits::ToCss;
+
+        impl ToCss for T {
+            fn to_css<W>(&self, _: &mut W) -> fmt::Result where W: fmt::Write {
+                Ok(())
+            }
+        }
+
+        #[derive(Clone, Debug, PartialEq)]
+        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+        pub struct T(pub Atom);
+    }
+
+    #[inline]
+    pub fn get_initial_value() -> computed_value::T {
+        computed_value::T(atom!(""))
+    }
+
+    pub fn parse(_context: &ParserContext, _input: &mut Parser) -> Result<SpecifiedValue, ()> {
+        debug_assert!(false, "Should be set directly by presentation attributes only.");
+        Err(())
+    }
+</%helpers:longhand>
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -1008,24 +1008,31 @@ pub extern "C" fn Servo_DeclarationBlock
         -> bool {
     use style::properties::PropertyDeclarationId;
     let declarations = RwLock::<PropertyDeclarationBlock>::as_arc(&declarations);
     let long = get_longhand_from_id!(property, false);
     declarations.read().get(PropertyDeclarationId::Longhand(long)).is_some()
 }
 
 #[no_mangle]
-pub extern "C" fn Servo_DeclarationBlock_SetIdentStringValue(_:
+pub extern "C" fn Servo_DeclarationBlock_SetIdentStringValue(declarations:
                                                              RawServoDeclarationBlockBorrowed,
-                                                             _:
+                                                             property:
                                                              nsCSSPropertyID,
-                                                             _:
-                                                             *const nsAString) {
-    // 
-    error!("stylo: Don't know how to handle ident presentation attributes (-x-lang)");
+                                                             value:
+                                                             *mut nsIAtom) {
+    use style::properties::{DeclaredValue, PropertyDeclaration, LonghandId};
+    use style::properties::longhands::_x_lang::computed_value::T as Lang;
+
+    let declarations = RwLock::<PropertyDeclarationBlock>::as_arc(&declarations);
+    let long = get_longhand_from_id!(property);
+    let prop = match_wrap_declared! { long,
+        XLang => Lang(Atom::from(value)),
+    };
+    declarations.write().declarations.push((prop, Default::default()));
 }
 
 #[no_mangle]
 #[allow(unreachable_code)]
 pub extern "C" fn Servo_DeclarationBlock_SetKeywordValue(declarations:
                                                          RawServoDeclarationBlockBorrowed,
                                                          property: nsCSSPropertyID,
                                                          value: i32) {