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
This commit is contained in:
Jonathan Kew 2023-04-12 21:46:46 +00:00
parent a69748ae88
commit 86e7ebd055
3 changed files with 66 additions and 6 deletions

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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);