From 86e7ebd055dbea6531d5f7839a416f50d8ee2fb5 Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Wed, 12 Apr 2023 21:46:46 +0000 Subject: [PATCH] Bug 1121792 - Use more complete BCP47/RFC4647 language-range matching rather than simple prefix-dash match for the :lang() pseudo. r=emilio This is a Selectors-4 enhancement to the spec for the :lang() pseudo-class. It seems Safari has been shipping this behavior for some time. Differential Revision: https://phabricator.services.mozilla.com/D174999 --- layout/style/GeckoBindings.cpp | 10 +++--- layout/style/nsStyleUtil.cpp | 59 ++++++++++++++++++++++++++++++++++ layout/style/nsStyleUtil.h | 3 ++ 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/layout/style/GeckoBindings.cpp b/layout/style/GeckoBindings.cpp index 86ea37ceeb7e..5b97fc5e3008 100644 --- a/layout/style/GeckoBindings.cpp +++ b/layout/style/GeckoBindings.cpp @@ -774,9 +774,8 @@ bool Gecko_MatchLang(const Element* aElement, nsAtom* aOverrideLang, // from the parent we have to be prepared to look at all parent // nodes. The language itself is encoded in the LANG attribute. if (auto* language = aHasOverrideLang ? aOverrideLang : aElement->GetLang()) { - return nsStyleUtil::DashMatchCompare( - nsDependentAtomString(language), nsDependentString(aValue), - nsASCIICaseInsensitiveStringComparator); + return nsStyleUtil::LangTagCompare(nsAtomCString(language), + NS_ConvertUTF16toUTF8(aValue)); } // Try to get the language from the HTTP header or if this @@ -786,11 +785,10 @@ bool Gecko_MatchLang(const Element* aElement, nsAtom* aOverrideLang, nsAutoString language; aElement->OwnerDoc()->GetContentLanguage(language); - nsDependentString langString(aValue); + NS_ConvertUTF16toUTF8 langString(aValue); language.StripWhitespace(); for (auto const& lang : language.Split(char16_t(','))) { - if (nsStyleUtil::DashMatchCompare(lang, langString, - nsASCIICaseInsensitiveStringComparator)) { + if (nsStyleUtil::LangTagCompare(NS_ConvertUTF16toUTF8(lang), langString)) { return true; } } diff --git a/layout/style/nsStyleUtil.cpp b/layout/style/nsStyleUtil.cpp index 8faa926acd9b..4bfab913dc90 100644 --- a/layout/style/nsStyleUtil.cpp +++ b/layout/style/nsStyleUtil.cpp @@ -9,6 +9,8 @@ #include "mozilla/dom/Document.h" #include "mozilla/ExpandedPrincipal.h" +#include "mozilla/intl/MozLocaleBindings.h" +#include "mozilla/TextUtils.h" #include "nsIContent.h" #include "nsCSSProps.h" #include "nsContentUtils.h" @@ -51,6 +53,63 @@ bool nsStyleUtil::DashMatchCompare(const nsAString& aAttributeValue, return result; } +bool nsStyleUtil::LangTagCompare(const nsACString& aAttributeValue, + const nsACString& aSelectorValue) { + class AutoLangId { + public: + AutoLangId() = delete; + AutoLangId(const AutoLangId& aOther) = delete; + explicit AutoLangId(const nsACString& aLangTag) : mIsValid(false) { + mLangId = intl::ffi::unic_langid_new(&aLangTag, &mIsValid); + } + + ~AutoLangId() { intl::ffi::unic_langid_destroy(mLangId); } + + operator intl::ffi::LanguageIdentifier*() const { return mLangId; } + bool IsValid() const { return mIsValid; } + + void Reset(const nsACString& aLangTag) { + intl::ffi::unic_langid_destroy(mLangId); + mLangId = intl::ffi::unic_langid_new(&aLangTag, &mIsValid); + } + + private: + intl::ffi::LanguageIdentifier* mLangId; + bool mIsValid; + }; + + if (aAttributeValue.IsEmpty() || aSelectorValue.IsEmpty()) { + return false; + } + + AutoLangId attrLangId(aAttributeValue); + if (!attrLangId.IsValid()) { + return false; + } + + AutoLangId selectorId(aSelectorValue); + if (!selectorId.IsValid()) { + // If it was "invalid" because of a wildcard language subtag, replace that + // with 'und' and try again. + // XXX Should unic_langid_new handle the wildcard internally? + if (aSelectorValue[0] == '*') { + nsAutoCString temp(aSelectorValue); + temp.Replace(0, 1, "und"); + selectorId.Reset(temp); + if (!selectorId.IsValid()) { + return false; + } + intl::ffi::unic_langid_clear_language(selectorId); + } else { + return false; + } + } + + return intl::ffi::unic_langid_matches(attrLangId, selectorId, + /* match addrLangId as range */ false, + /* match selectorId as range */ true); +} + bool nsStyleUtil::ValueIncludes(const nsAString& aValueList, const nsAString& aValue, const nsStringComparator& aComparator) { diff --git a/layout/style/nsStyleUtil.h b/layout/style/nsStyleUtil.h index 2983239bd70e..990996fdbc3e 100644 --- a/layout/style/nsStyleUtil.h +++ b/layout/style/nsStyleUtil.h @@ -37,6 +37,9 @@ class nsStyleUtil { const nsAString& aSelectorValue, const nsStringComparator& aComparator); + static bool LangTagCompare(const nsACString& aAttributeValue, + const nsACString& aSelectorValue); + static bool ValueIncludes(const nsAString& aValueList, const nsAString& aValue, const nsStringComparator& aComparator);