Bug 1370802: Parse lang attributes as atoms. r?heycam draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Thu, 15 Jun 2017 21:48:26 +0200
changeset 596618 5a3e6b7c0e89a1ad4ae5edce8ae3ce750283da52
parent 596612 40732e7c221b01a33a2c41f7836116bc69b3840a
child 596619 ab3c142d6c5717e367daacf42e60add411554316
push id64699
push userbmo:emilio+bugs@crisal.io
push dateMon, 19 Jun 2017 15:05:42 +0000
reviewersheycam
bugs1370802
milestone56.0a1
Bug 1370802: Parse lang attributes as atoms. r?heycam MozReview-Commit-ID: Cnq3wB7aVB1
dom/base/Element.cpp
dom/base/FragmentOrElement.cpp
dom/base/nsIContent.h
dom/html/nsGenericHTMLElement.cpp
layout/style/GenericSpecifiedValues.h
layout/style/GenericSpecifiedValuesInlines.h
layout/style/ServoBindings.cpp
layout/style/ServoSpecifiedValues.cpp
layout/style/ServoSpecifiedValues.h
layout/style/nsCSSRuleProcessor.cpp
layout/style/nsCSSRuleProcessor.h
layout/style/nsRuleData.h
layout/style/nsRuleNode.cpp
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -2652,16 +2652,21 @@ Element::SetAttrAndNotify(int32_t aNames
 }
 
 bool
 Element::ParseAttribute(int32_t aNamespaceID,
                         nsIAtom* aAttribute,
                         const nsAString& aValue,
                         nsAttrValue& aResult)
 {
+  if (aAttribute == nsGkAtoms::lang) {
+    aResult.ParseAtom(aValue);
+    return true;
+  }
+
   if (aNamespaceID == kNameSpaceID_None) {
     MOZ_ASSERT(aAttribute != nsGkAtoms::_class,
                "The class attribute should be preparsed and therefore should "
                "never be passed to Element::ParseAttribute");
     if (aAttribute == nsGkAtoms::id) {
       // Store id as an atom.  id="" means that the element has no id,
       // not that it has an emptystring as the id.
       if (aValue.IsEmpty()) {
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -343,16 +343,43 @@ nsIContent::LookupNamespaceURIInternal(c
   const nsIContent* content = this;
   do {
     if (content->GetAttr(kNameSpaceID_XMLNS, name, aNamespaceURI))
       return NS_OK;
   } while ((content = content->GetParent()));
   return NS_ERROR_FAILURE;
 }
 
+nsIAtom*
+nsIContent::GetLang() const
+{
+  for (const auto* content = this; content; content = content->GetParent()) {
+    if (!content->GetAttrCount() || !content->IsElement()) {
+      continue;
+    }
+
+    auto* element = content->AsElement();
+
+    // xml:lang has precedence over lang on HTML elements (see
+    // XHTML1 section C.7).
+    const nsAttrValue* attr =
+      element->GetParsedAttr(nsGkAtoms::lang, kNameSpaceID_XML);
+    if (!attr && element->SupportsLangAttr()) {
+      attr = element->GetParsedAttr(nsGkAtoms::lang);
+    }
+    if (attr) {
+      MOZ_ASSERT(attr->Type() == nsAttrValue::eAtom);
+      MOZ_ASSERT(attr->GetAtomValue());
+      return attr->GetAtomValue();
+    }
+  }
+
+  return nullptr;
+}
+
 already_AddRefed<nsIURI>
 nsIContent::GetBaseURI(bool aTryUseXHRDocBaseURI) const
 {
   if (IsInAnonymousSubtree() && IsAnonymousContentInSVGUseSubtree()) {
     nsIContent* bindingParent = GetBindingParent();
     MOZ_ASSERT(bindingParent);
     SVGUseElement* useElement = static_cast<SVGUseElement*>(bindingParent);
     // XXX Ignore xml:base as we are removing it.
--- a/dom/base/nsIContent.h
+++ b/dom/base/nsIContent.h
@@ -923,36 +923,28 @@ public:
 
   bool SupportsLangAttr() const {
     return IsHTMLElement() || IsSVGElement() || IsXULElement();
   }
 
   /**
    * Determining language. Look at the nearest ancestor element that has a lang
    * attribute in the XML namespace or is an HTML/SVG element and has a lang in
-   * no namespace attribute.  Returns false if no language was specified.
+   * no namespace attribute.
+   *
+   * Returns null if no language was specified. Can return the empty atom.
    */
+  nsIAtom* GetLang() const;
+
   bool GetLang(nsAString& aResult) const {
-    for (const nsIContent* content = this; content; content = content->GetParent()) {
-      if (content->GetAttrCount() > 0) {
-        // xml:lang has precedence over lang on HTML elements (see
-        // XHTML1 section C.7).
-        bool hasAttr = content->GetAttr(kNameSpaceID_XML, nsGkAtoms::lang,
-                                          aResult);
-        if (!hasAttr && content->SupportsLangAttr()) {
-          hasAttr = content->GetAttr(kNameSpaceID_None, nsGkAtoms::lang,
-                                     aResult);
-        }
-        NS_ASSERTION(hasAttr || aResult.IsEmpty(),
-                     "GetAttr that returns false should not make string non-empty");
-        if (hasAttr) {
-          return true;
-        }
-      }
+    if (auto* lang = GetLang()) {
+      aResult.Assign(nsDependentAtomString(lang));
+      return true;
     }
+
     return false;
   }
 
   // Returns true if this element is native-anonymous scrollbar content.
   bool IsNativeScrollbarContent() const {
     return IsNativeAnonymous() &&
            IsAnyOfXULElements(nsGkAtoms::scrollbar,
                               nsGkAtoms::resizer,
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -1214,27 +1214,38 @@ static inline void
 MapLangAttributeInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData)
 {
   if (!aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Font) |
                                        NS_STYLE_INHERIT_BIT(Text))) {
     return;
   }
 
   const nsAttrValue* langValue = aAttributes->GetAttr(nsGkAtoms::lang);
-  if (!langValue || langValue->Type() != nsAttrValue::eString) {
+  if (!langValue) {
     return;
   }
-
+  MOZ_ASSERT(langValue->Type() == nsAttrValue::eAtom);
   if (aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Font))) {
-    aData->SetIdentStringValueIfUnset(eCSSProperty__x_lang,
-                                      langValue->GetStringValue());
+    nsIAtom* atom = langValue->GetAtomValue();
+
+    const nsDependentAtomString atomString(atom);
+    Maybe<nsCOMPtr<nsIAtom>> lowerAtom;
+    if (nsContentUtils::StringContainsASCIIUpper(atomString)) {
+      nsAutoString dest;
+      dest.SetCapacity(atomString.Length());
+      nsContentUtils::ASCIIToLower(atomString, dest);
+      lowerAtom.emplace(NS_AtomizeMainThread(dest));
+    }
+
+    aData->SetIdentAtomValueIfUnset(
+        eCSSProperty__x_lang, lowerAtom ? lowerAtom->get() : atom);
   }
   if (aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Text))) {
     if (!aData->PropertyIsSet(eCSSProperty_text_emphasis_position)) {
-      const nsAString& lang = langValue->GetStringValue();
+      const nsIAtom* lang = langValue->GetAtomValue();
       if (nsStyleUtil::MatchesLanguagePrefix(lang, u"zh")) {
         aData->SetKeywordValue(eCSSProperty_text_emphasis_position,
                                NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT_ZH);
       } else if (nsStyleUtil::MatchesLanguagePrefix(lang, u"ja") ||
                  nsStyleUtil::MatchesLanguagePrefix(lang, u"mn")) {
         // This branch is currently no part of the spec.
         // See bug 1040668 comment 69 and comment 75.
         aData->SetKeywordValue(eCSSProperty_text_emphasis_position,
--- a/layout/style/GenericSpecifiedValues.h
+++ b/layout/style/GenericSpecifiedValues.h
@@ -48,16 +48,19 @@ public:
     inline nsPresContext* PresContext() {
         return mPresContext;
     }
 
     // Set a property to an identifier (string)
     inline void SetIdentStringValue(nsCSSPropertyID aId, const nsString& aValue);
     inline void SetIdentStringValueIfUnset(nsCSSPropertyID aId, const nsString& aValue);
 
+    inline void SetIdentAtomValue(nsCSSPropertyID aId, nsIAtom* aValue);
+    inline void SetIdentAtomValueIfUnset(nsCSSPropertyID aId, nsIAtom* aValue);
+
     // Set a property to a keyword (usually NS_STYLE_* or StyleFoo::*)
     inline void SetKeywordValue(nsCSSPropertyID aId, int32_t aValue);
     inline void SetKeywordValueIfUnset(nsCSSPropertyID aId, int32_t aValue);
 
     template<typename T,
              typename = typename std::enable_if<std::is_enum<T>::value>::type>
     void SetKeywordValue(nsCSSPropertyID aId, T aValue) {
         static_assert(mozilla::EnumTypeFitsWithin<T, int32_t>::value,
--- a/layout/style/GenericSpecifiedValuesInlines.h
+++ b/layout/style/GenericSpecifiedValuesInlines.h
@@ -36,16 +36,28 @@ GenericSpecifiedValues::SetIdentStringVa
 
 void
 GenericSpecifiedValues::SetIdentStringValueIfUnset(nsCSSPropertyID aId, const nsString& aValue)
 {
   MOZ_STYLO_FORWARD(SetIdentStringValueIfUnset, (aId, aValue))
 }
 
 void
+GenericSpecifiedValues::SetIdentAtomValueIfUnset(nsCSSPropertyID aId, nsIAtom* aValue)
+{
+  MOZ_STYLO_FORWARD(SetIdentAtomValueIfUnset, (aId, aValue))
+}
+
+void
+GenericSpecifiedValues::SetIdentAtomValue(nsCSSPropertyID aId, nsIAtom* aValue)
+{
+  MOZ_STYLO_FORWARD(SetIdentAtomValue, (aId, aValue))
+}
+
+void
 GenericSpecifiedValues::SetKeywordValue(nsCSSPropertyID aId, int32_t aValue)
 {
   // there are some static asserts in MOZ_STYLO_FORWARD which
   // won't work with the overloaded SetKeywordValue function,
   // so we copy its expansion and use SetIntValue for decltype
   // instead
   static_assert(!mozilla::IsSame<decltype(&MOZ_STYLO_THIS_TYPE::SetIntValue),
                  decltype(&MOZ_STYLO_GECKO_TYPE::SetKeywordValue)>
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -816,61 +816,62 @@ Gecko_MatchLang(RawGeckoElementBorrowed 
   MOZ_ASSERT(!(aOverrideLang && !aHasOverrideLang),
              "aHasOverrideLang should only be set when aOverrideLang is null");
 
   if (!aHasOverrideLang) {
     return nsCSSRuleProcessor::LangPseudoMatches(aElement, nullptr, false,
                                                  aValue, aElement->OwnerDoc());
   }
 
-  if (aOverrideLang) {
-    nsDependentAtomString overrideLang(aOverrideLang);
-    return nsCSSRuleProcessor::LangPseudoMatches(aElement, &overrideLang, true,
-                                                 aValue, aElement->OwnerDoc());
-  }
-
-  return nsCSSRuleProcessor::LangPseudoMatches(aElement, nullptr, true,
+  return nsCSSRuleProcessor::LangPseudoMatches(aElement, aOverrideLang, true,
                                                aValue, aElement->OwnerDoc());
 }
 
 nsIAtom*
 Gecko_GetXMLLangValue(RawGeckoElementBorrowed aElement)
 {
-  nsString string;
-  if (aElement->GetAttr(kNameSpaceID_XML, nsGkAtoms::lang, string)) {
-    return NS_Atomize(string).take();
+  const nsAttrValue* attr =
+    aElement->GetParsedAttr(nsGkAtoms::lang, kNameSpaceID_XML);
+
+  if (!attr) {
+    return nullptr;
   }
-  return nullptr;
+
+  MOZ_ASSERT(attr->Type() == nsAttrValue::eAtom);
+
+  nsCOMPtr<nsIAtom> atom = attr->GetAtomValue();
+  return atom.forget().take();
 }
 
 template <typename Implementor>
 static nsIAtom*
 AtomAttrValue(Implementor* aElement, nsIAtom* aName)
 {
   const nsAttrValue* attr = aElement->GetParsedAttr(aName);
   return attr ? attr->GetAtomValue() : nullptr;
 }
 
 template <typename Implementor>
 static nsIAtom*
 LangValue(Implementor* aElement)
 {
+  // TODO(emilio): Deduplicate a bit with nsIContent::GetLang().
   const nsAttrValue* attr =
     aElement->GetParsedAttr(nsGkAtoms::lang, kNameSpaceID_XML);
   if (!attr && aElement->SupportsLangAttr()) {
     attr = aElement->GetParsedAttr(nsGkAtoms::lang);
   }
 
   if (!attr) {
     return nullptr;
   }
 
-  nsString lang;
-  attr->ToString(lang);
-  return NS_Atomize(lang).take();
+  MOZ_ASSERT(attr->Type() == nsAttrValue::eAtom);
+  nsCOMPtr<nsIAtom> atom = attr->GetAtomValue();
+  return atom.forget().take();
 }
 
 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,
--- a/layout/style/ServoSpecifiedValues.cpp
+++ b/layout/style/ServoSpecifiedValues.cpp
@@ -42,21 +42,27 @@ ServoSpecifiedValues::PropertyIsSet(nsCS
   return false;
 }
 
 void
 ServoSpecifiedValues::SetIdentStringValue(nsCSSPropertyID aId,
                                           const nsString& aValue)
 {
   nsCOMPtr<nsIAtom> atom = NS_Atomize(aValue);
-  Servo_DeclarationBlock_SetIdentStringValue(mDecl, aId, atom);
+  SetIdentAtomValue(aId, atom);
+}
+
+void
+ServoSpecifiedValues::SetIdentAtomValue(nsCSSPropertyID aId, nsIAtom* aValue)
+{
+  Servo_DeclarationBlock_SetIdentStringValue(mDecl, aId, aValue);
   if (aId == eCSSProperty__x_lang) {
     // This forces the lang prefs result to be cached
     // so that we can access them off main thread during traversal
-    mPresContext->ForceCacheLang(atom);
+    mPresContext->ForceCacheLang(aValue);
   }
 }
 
 void
 ServoSpecifiedValues::SetKeywordValue(nsCSSPropertyID aId, int32_t aValue)
 {
   Servo_DeclarationBlock_SetKeywordValue(mDecl, aId, aValue);
 }
@@ -124,9 +130,9 @@ ServoSpecifiedValues::SetTextDecorationC
 
 void
 ServoSpecifiedValues::SetBackgroundImage(nsAttrValue& aValue)
 {
   nsAutoString str;
   aValue.ToString(str);
   Servo_DeclarationBlock_SetBackgroundImage(mDecl, str,
         mPresContext->Document()->DefaultStyleAttrURLData());
-}
\ No newline at end of file
+}
--- a/layout/style/ServoSpecifiedValues.h
+++ b/layout/style/ServoSpecifiedValues.h
@@ -30,16 +30,25 @@ public:
 
   void SetIdentStringValueIfUnset(nsCSSPropertyID aId, const nsString& aValue)
   {
     if (!PropertyIsSet(aId)) {
       SetIdentStringValue(aId, aValue);
     }
   }
 
+  void SetIdentAtomValue(nsCSSPropertyID aId, nsIAtom* aValue);
+
+  void SetIdentAtomValueIfUnset(nsCSSPropertyID aId, nsIAtom* aValue)
+  {
+    if (!PropertyIsSet(aId)) {
+      SetIdentAtomValue(aId, aValue);
+    }
+  }
+
   void SetKeywordValue(nsCSSPropertyID aId, int32_t aValue);
 
   void SetKeywordValueIfUnset(nsCSSPropertyID aId, int32_t aValue)
   {
     if (!PropertyIsSet(aId)) {
       SetKeywordValue(aId, aValue);
     }
   }
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -1673,76 +1673,66 @@ IsSignificantChildMaybeThreadSafe(const 
   } else {
     auto content = const_cast<nsIContent*>(aContent);
     return IsSignificantChild(content, aTextIsSignificant, aWhitespaceIsSignificant);
   }
 }
 
 /* static */ bool
 nsCSSRuleProcessor::LangPseudoMatches(const mozilla::dom::Element* aElement,
-                                      const nsAString* aOverrideLang,
+                                      const nsIAtom* aOverrideLang,
                                       bool aHasOverrideLang,
                                       const char16_t* aString,
                                       const nsIDocument* aDocument)
 {
   NS_ASSERTION(aString, "null lang parameter");
   if (!aString || !*aString) {
     return false;
   }
 
   // We have to determine the language of the current element.  Since
   // this is currently no property and since the language is inherited
   // from the parent we have to be prepared to look at all parent
   // nodes.  The language itself is encoded in the LANG attribute.
-  bool haveLanguage = false;
-  nsAutoString language;
-  if (aHasOverrideLang) {
-    if (aOverrideLang) {
-      language = *aOverrideLang;
-      haveLanguage = true;
-    }
-  } else {
-    haveLanguage = aElement->GetLang(language);
-  }
-
-  if (haveLanguage) {
-    return nsStyleUtil::DashMatchCompare(language,
+  if (auto* language = aHasOverrideLang ? aOverrideLang : aElement->GetLang()) {
+    return nsStyleUtil::DashMatchCompare(nsDependentAtomString(language),
                                          nsDependentString(aString),
                                          nsASCIICaseInsensitiveStringComparator());
   }
 
-  if (aDocument) {
-    // Try to get the language from the HTTP header or if this
-    // is missing as well from the preferences.
-    // The content language can be a comma-separated list of
-    // language codes.
-    aDocument->GetContentLanguage(language);
-
-    nsDependentString langString(aString);
-    language.StripWhitespace();
-    int32_t begin = 0;
-    int32_t len = language.Length();
-    while (begin < len) {
-      int32_t end = language.FindChar(char16_t(','), begin);
-      if (end == kNotFound) {
-        end = len;
-      }
-      if (nsStyleUtil::DashMatchCompare(Substring(language, begin,
-                                                  end-begin),
-                                        langString,
-                                        nsASCIICaseInsensitiveStringComparator())) {
-        return true;
-      }
-      begin = end + 1;
+  if (!aDocument) {
+    return false;
+  }
+
+  // Try to get the language from the HTTP header or if this
+  // is missing as well from the preferences.
+  // The content language can be a comma-separated list of
+  // language codes.
+  nsAutoString language;
+  aDocument->GetContentLanguage(language);
+
+  nsDependentString langString(aString);
+  language.StripWhitespace();
+  int32_t begin = 0;
+  int32_t len = language.Length();
+  while (begin < len) {
+    int32_t end = language.FindChar(char16_t(','), begin);
+    if (end == kNotFound) {
+      end = len;
     }
-    if (begin < len) {
+    if (nsStyleUtil::DashMatchCompare(Substring(language, begin, end - begin),
+                                      langString,
+                                      nsASCIICaseInsensitiveStringComparator())) {
       return true;
     }
+    begin = end + 1;
   }
-
+  if (begin < len) {
+    return true;
+  }
   return false;
 }
 
 /* static */ bool
 nsCSSRuleProcessor::StringPseudoMatches(const mozilla::dom::Element* aElement,
                                         CSSPseudoClassType aPseudo,
                                         const char16_t* aString,
                                         const nsIDocument* aDocument,
--- a/layout/style/nsCSSRuleProcessor.h
+++ b/layout/style/nsCSSRuleProcessor.h
@@ -167,17 +167,17 @@ public:
                                   const char16_t* aString,
                                   const nsIDocument* aDocument,
                                   bool aForStyling,
                                   mozilla::EventStates aStateMask,
                                   bool* aSetSlowSelectorFlag,
                                   bool* const aDependence = nullptr);
 
   static bool LangPseudoMatches(const mozilla::dom::Element* aElement,
-                                const nsAString* aOverrideLang,
+                                const nsIAtom* aOverrideLang,
                                 bool aHasOverrideLang,
                                 const char16_t* aString,
                                 const nsIDocument* aDocument);
 
   // nsIStyleRuleProcessor
   virtual void RulesMatching(ElementRuleProcessorData* aData) override;
 
   virtual void RulesMatching(PseudoElementRuleProcessorData* aData) override;
--- a/layout/style/nsRuleData.h
+++ b/layout/style/nsRuleData.h
@@ -131,16 +131,29 @@ struct nsRuleData final: mozilla::Generi
 
   void SetIdentStringValueIfUnset(nsCSSPropertyID aId, const nsString& aValue)
   {
     if (!PropertyIsSet(aId)) {
       SetIdentStringValue(aId, aValue);
     }
   }
 
+  void SetIdentAtomValue(nsCSSPropertyID aId, nsIAtom* aValue)
+  {
+    nsCOMPtr<nsIAtom> atom = aValue;
+    ValueFor(aId)->SetAtomIdentValue(atom.forget());
+  }
+
+  void SetIdentAtomValueIfUnset(nsCSSPropertyID aId, nsIAtom* aValue)
+  {
+    if (!PropertyIsSet(aId)) {
+      SetIdentAtomValue(aId, aValue);
+    }
+  }
+
   void SetKeywordValue(nsCSSPropertyID aId, int32_t aValue)
   {
     ValueFor(aId)->SetIntValue(aValue, eCSSUnit_Enumerated);
   }
 
   void SetKeywordValueIfUnset(nsCSSPropertyID aId, int32_t aValue)
   {
     if (!PropertyIsSet(aId)) {
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -3560,22 +3560,21 @@ nsRuleNode::SetFont(nsPresContext* aPres
 
   // mLanguage must be set before before any of the CalcLengthWith calls
   // (direct calls or calls via SetFontSize) for the cases where |aParentFont|
   // is the same as |aFont|.
   //
   // -x-lang: string, inherit
   // This is not a real CSS property, it is an HTML attribute mapped to CSS.
   const nsCSSValue* langValue = aRuleData->ValueForLang();
-  if (eCSSUnit_Ident == langValue->GetUnit()) {
-    nsAutoString lang;
-    langValue->GetStringValue(lang);
-
-    nsContentUtils::ASCIIToLower(lang);
-    aFont->mLanguage = NS_Atomize(lang);
+  if (eCSSUnit_AtomIdent == langValue->GetUnit()) {
+    MOZ_ASSERT(!nsContentUtils::StringContainsASCIIUpper(
+                  nsDependentAtomString(langValue->GetAtomValue())));
+
+    aFont->mLanguage = langValue->GetAtomValue();
     aFont->mExplicitLanguage = true;
   }
 
   const nsFont* defaultVariableFont =
     aPresContext->GetDefaultFont(kPresContext_DefaultVariableFont_ID,
                                  aFont->mLanguage);
 
   // -moz-system-font: enum (never inherit!)