SVGTests.cpp (8186B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/SVGTests.h" 8 9 #include "DOMSVGStringList.h" 10 #include "mozilla/dom/SVGSwitchElement.h" 11 #include "mozilla/intl/LocaleService.h" 12 #include "mozilla/intl/oxilangtag_ffi_generated.h" 13 #include "nsCharSeparatedTokenizer.h" 14 #include "nsIContent.h" 15 #include "nsIContentInlines.h" 16 17 namespace mozilla::dom { 18 19 nsStaticAtom* const SVGTests::sStringListNames[2] = { 20 nsGkAtoms::requiredExtensions, 21 nsGkAtoms::systemLanguage, 22 }; 23 24 SVGTests::SVGTests() { 25 mStringListAttributes[LANGUAGE].SetIsCommaSeparated(true); 26 } 27 28 already_AddRefed<DOMSVGStringList> SVGTests::RequiredExtensions() { 29 return DOMSVGStringList::GetDOMWrapper(&mStringListAttributes[EXTENSIONS], 30 AsSVGElement(), true, EXTENSIONS); 31 } 32 33 already_AddRefed<DOMSVGStringList> SVGTests::SystemLanguage() { 34 return DOMSVGStringList::GetDOMWrapper(&mStringListAttributes[LANGUAGE], 35 AsSVGElement(), true, LANGUAGE); 36 } 37 38 bool SVGTests::HasExtension(const nsAString& aExtension) const { 39 #define SVG_SUPPORTED_EXTENSION(str) \ 40 if (aExtension.EqualsLiteral(str)) return true; 41 SVG_SUPPORTED_EXTENSION("http://www.w3.org/1999/xhtml") 42 nsNameSpaceManager* nameSpaceManager = nsNameSpaceManager::GetInstance(); 43 if (AsSVGElement()->IsInChromeDocument() || 44 !nameSpaceManager->mMathMLDisabled) { 45 SVG_SUPPORTED_EXTENSION("http://www.w3.org/1998/Math/MathML") 46 } 47 #undef SVG_SUPPORTED_EXTENSION 48 49 return false; 50 } 51 52 bool SVGTests::IsConditionalProcessingAttribute( 53 const nsAtom* aAttribute) const { 54 for (uint32_t i = 0; i < std::size(sStringListNames); i++) { 55 if (aAttribute == sStringListNames[i]) { 56 return true; 57 } 58 } 59 return false; 60 } 61 62 // Find the best match from aAvailLangs for the users accept-languages, 63 // returning the index in the aAvailLangs list, or -1 if no match. 64 static int32_t FindBestLanguage(const nsTArray<nsCString>& aAvailLangs, 65 const Document* aDoc) { 66 AutoTArray<nsCString, 16> reqLangs; 67 if (aDoc->ShouldResistFingerprinting(RFPTarget::JSLocale)) { 68 reqLangs.AppendElements(Span(std::array{"en-US", "en"})); 69 } else { 70 nsCString acceptLangs; 71 intl::LocaleService::GetInstance()->GetAcceptLanguages(acceptLangs); 72 nsCCharSeparatedTokenizer languageTokenizer(acceptLangs, ','); 73 while (languageTokenizer.hasMoreTokens()) { 74 reqLangs.AppendElement(languageTokenizer.nextToken()); 75 } 76 } 77 for (const auto& req : reqLangs) { 78 for (const auto& avail : aAvailLangs) { 79 if (avail.Length() > req.Length()) { 80 // Ensure that en does not match en-us, i.e. you need to have en in 81 // intl.accept_languages to match en in markup. 82 continue; 83 } 84 using namespace intl::ffi; 85 struct LangTagDelete { 86 void operator()(LangTag* aLangTag) const { lang_tag_destroy(aLangTag); } 87 }; 88 UniquePtr<LangTag, LangTagDelete> langTag(lang_tag_new(&avail)); 89 if (langTag && lang_tag_matches(langTag.get(), &req)) { 90 return &avail - &aAvailLangs[0]; 91 } 92 } 93 } 94 return -1; 95 } 96 97 nsIContent* SVGTests::FindActiveSwitchChild( 98 const dom::SVGSwitchElement* aSwitch) { 99 AutoTArray<nsCString, 16> availLocales; 100 AutoTArray<nsIContent*, 16> children; 101 nsIContent* defaultChild = nullptr; 102 for (auto* child = aSwitch->GetFirstChild(); child; 103 child = child->GetNextSibling()) { 104 if (!child->IsElement()) { 105 continue; 106 } 107 nsCOMPtr<SVGTests> tests(do_QueryInterface(child)); 108 if (tests) { 109 if (!tests->mPassesConditionalProcessingTests.valueOr(true) || 110 !tests->PassesRequiredExtensionsTests()) { 111 continue; 112 } 113 const auto& languages = tests->mStringListAttributes[LANGUAGE]; 114 if (!languages.IsExplicitlySet()) { 115 if (!defaultChild) { 116 defaultChild = child; 117 } 118 continue; 119 } 120 for (uint32_t i = 0; i < languages.Length(); i++) { 121 children.AppendElement(child); 122 availLocales.AppendElement(NS_ConvertUTF16toUTF8(languages[i])); 123 } 124 } 125 } 126 127 // For each entry in availLocales, we expect to have a corresponding entry 128 // in children that provides the child node associated with that locale. 129 MOZ_ASSERT(children.Length() == availLocales.Length()); 130 131 if (availLocales.IsEmpty()) { 132 return defaultChild; 133 } 134 135 int32_t index = FindBestLanguage(availLocales, aSwitch->OwnerDoc()); 136 if (index >= 0) { 137 return children[index]; 138 } 139 140 return defaultChild; 141 } 142 143 bool SVGTests::PassesRequiredExtensionsTests() const { 144 // Required Extensions 145 // 146 // The requiredExtensions attribute defines a list of required language 147 // extensions. Language extensions are capabilities within a user agent that 148 // go beyond the feature set defined in the SVG specification. 149 // Each extension is identified by a URI reference. 150 // For now, claim that mozilla's SVG implementation supports XHTML and MathML. 151 const auto& extensions = mStringListAttributes[EXTENSIONS]; 152 if (extensions.IsExplicitlySet()) { 153 if (extensions.IsEmpty()) { 154 mPassesConditionalProcessingTests = Some(false); 155 return false; 156 } 157 for (uint32_t i = 0; i < extensions.Length(); i++) { 158 if (!HasExtension(extensions[i])) { 159 mPassesConditionalProcessingTests = Some(false); 160 return false; 161 } 162 } 163 } 164 return true; 165 } 166 167 bool SVGTests::PassesConditionalProcessingTests() const { 168 if (mPassesConditionalProcessingTests) { 169 return mPassesConditionalProcessingTests.value(); 170 } 171 if (!PassesRequiredExtensionsTests()) { 172 return false; 173 } 174 175 // systemLanguage 176 // 177 // Evaluates to true if there's a BCP 47 match for the one of the user 178 // preference languages with one of the languages given in the value of 179 // this parameter. 180 const auto& languages = mStringListAttributes[LANGUAGE]; 181 if (languages.IsExplicitlySet()) { 182 if (languages.IsEmpty()) { 183 mPassesConditionalProcessingTests = Some(false); 184 return false; 185 } 186 187 AutoTArray<nsCString, 4> availLocales; 188 for (uint32_t i = 0; i < languages.Length(); i++) { 189 availLocales.AppendElement(NS_ConvertUTF16toUTF8(languages[i])); 190 } 191 192 mPassesConditionalProcessingTests = 193 Some(FindBestLanguage(availLocales, AsSVGElement()->OwnerDoc()) >= 0); 194 return mPassesConditionalProcessingTests.value(); 195 } 196 197 mPassesConditionalProcessingTests = Some(true); 198 return true; 199 } 200 201 bool SVGTests::ParseConditionalProcessingAttribute(nsAtom* aAttribute, 202 const nsAString& aValue, 203 nsAttrValue& aResult) { 204 for (uint32_t i = 0; i < std::size(sStringListNames); i++) { 205 if (aAttribute == sStringListNames[i]) { 206 nsresult rv = mStringListAttributes[i].SetValue(aValue); 207 if (NS_FAILED(rv)) { 208 mStringListAttributes[i].Clear(); 209 } 210 mPassesConditionalProcessingTests = Nothing(); 211 MaybeInvalidate(); 212 return true; 213 } 214 } 215 return false; 216 } 217 218 void SVGTests::UnsetAttr(const nsAtom* aAttribute) { 219 for (uint32_t i = 0; i < std::size(sStringListNames); i++) { 220 if (aAttribute == sStringListNames[i]) { 221 mStringListAttributes[i].Clear(); 222 mPassesConditionalProcessingTests = Nothing(); 223 MaybeInvalidate(); 224 return; 225 } 226 } 227 } 228 229 nsStaticAtom* SVGTests::GetAttrName(uint8_t aAttrEnum) const { 230 return sStringListNames[aAttrEnum]; 231 } 232 233 void SVGTests::GetAttrValue(uint8_t aAttrEnum, nsAttrValue& aValue) const { 234 MOZ_ASSERT(aAttrEnum < std::size(sStringListNames), "aAttrEnum out of range"); 235 aValue.SetTo(mStringListAttributes[aAttrEnum], nullptr); 236 } 237 238 void SVGTests::MaybeInvalidate() { 239 nsIContent* parent = AsSVGElement()->GetFlattenedTreeParent(); 240 241 if (auto* svgSwitch = SVGSwitchElement::FromNodeOrNull(parent)) { 242 svgSwitch->MaybeInvalidate(); 243 } 244 } 245 246 } // namespace mozilla::dom