nsDOMTokenList.cpp (9809B)
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 /* 8 * Implementation of DOMTokenList specified by HTML5. 9 */ 10 11 #include "nsDOMTokenList.h" 12 13 #include "mozilla/ErrorResult.h" 14 #include "mozilla/dom/DOMTokenListBinding.h" 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/dom/Element.h" 17 #include "nsAttrValue.h" 18 #include "nsAttrValueInlines.h" 19 #include "nsError.h" 20 #include "nsHashKeys.h" 21 #include "nsTHashMap.h" 22 23 using namespace mozilla; 24 using namespace mozilla::dom; 25 26 nsDOMTokenList::nsDOMTokenList( 27 Element* aElement, nsAtom* aAttrAtom, 28 const DOMTokenListSupportedTokenArray aSupportedTokens) 29 : mElement(aElement), 30 mAttrAtom(aAttrAtom), 31 mSupportedTokens(aSupportedTokens) { 32 // We don't add a reference to our element. If it goes away, 33 // we'll be told to drop our reference 34 } 35 36 nsDOMTokenList::~nsDOMTokenList() = default; 37 38 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMTokenList, mElement) 39 40 NS_INTERFACE_MAP_BEGIN(nsDOMTokenList) 41 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 42 NS_INTERFACE_MAP_ENTRY(nsISupports) 43 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMTokenList) 44 NS_INTERFACE_MAP_END 45 46 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMTokenList) 47 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMTokenList) 48 49 const nsAttrValue* nsDOMTokenList::GetParsedAttr() { 50 if (!mElement) { 51 return nullptr; 52 } 53 return mElement->GetAttrInfo(kNameSpaceID_None, mAttrAtom).mValue; 54 } 55 56 static void RemoveDuplicates(const nsAttrValue* aAttr) { 57 if (!aAttr || aAttr->Type() != nsAttrValue::eAtomArray) { 58 return; 59 } 60 const_cast<nsAttrValue*>(aAttr)->RemoveDuplicatesFromAtomArray(); 61 } 62 63 uint32_t nsDOMTokenList::Length() { 64 const nsAttrValue* attr = GetParsedAttr(); 65 if (!attr) { 66 return 0; 67 } 68 69 RemoveDuplicates(attr); 70 return attr->GetAtomCount(); 71 } 72 73 void nsDOMTokenList::IndexedGetter(uint32_t aIndex, bool& aFound, 74 nsAString& aResult) { 75 const nsAttrValue* attr = GetParsedAttr(); 76 77 if (!attr || aIndex >= static_cast<uint32_t>(attr->GetAtomCount())) { 78 aFound = false; 79 return; 80 } 81 82 RemoveDuplicates(attr); 83 84 if (attr && aIndex < static_cast<uint32_t>(attr->GetAtomCount())) { 85 aFound = true; 86 attr->AtomAt(aIndex)->ToString(aResult); 87 } else { 88 aFound = false; 89 } 90 } 91 92 void nsDOMTokenList::GetValue(nsAString& aResult) { 93 if (!mElement) { 94 aResult.Truncate(); 95 return; 96 } 97 98 mElement->GetAttr(mAttrAtom, aResult); 99 } 100 101 void nsDOMTokenList::SetValue(const nsAString& aValue, ErrorResult& rv) { 102 if (!mElement) { 103 return; 104 } 105 106 rv = mElement->SetAttr(kNameSpaceID_None, mAttrAtom, aValue, true); 107 } 108 109 void nsDOMTokenList::CheckToken(const nsAString& aToken, ErrorResult& aRv) { 110 if (aToken.IsEmpty()) { 111 return aRv.ThrowSyntaxError("The empty string is not a valid token."); 112 } 113 114 nsAString::const_iterator iter, end; 115 aToken.BeginReading(iter); 116 aToken.EndReading(end); 117 118 while (iter != end) { 119 if (nsContentUtils::IsHTMLWhitespace(*iter)) { 120 return aRv.ThrowInvalidCharacterError( 121 "The token can not contain whitespace."); 122 } 123 ++iter; 124 } 125 } 126 127 void nsDOMTokenList::CheckTokens(const nsTArray<nsString>& aTokens, 128 ErrorResult& aRv) { 129 for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) { 130 CheckToken(aTokens[i], aRv); 131 if (aRv.Failed()) { 132 return; 133 } 134 } 135 } 136 137 bool nsDOMTokenList::Contains(const nsAString& aToken) { 138 const nsAttrValue* attr = GetParsedAttr(); 139 return attr && attr->Contains(aToken); 140 } 141 142 void nsDOMTokenList::AddInternal(const nsAttrValue* aAttr, 143 const nsTArray<nsString>& aTokens) { 144 if (!mElement) { 145 return; 146 } 147 148 nsAutoString resultStr; 149 150 if (aAttr) { 151 RemoveDuplicates(aAttr); 152 for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) { 153 if (i != 0) { 154 resultStr.AppendLiteral(" "); 155 } 156 resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i))); 157 } 158 } 159 160 AutoTArray<nsString, 10> addedClasses; 161 162 for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) { 163 const nsString& aToken = aTokens[i]; 164 165 if ((aAttr && aAttr->Contains(aToken)) || addedClasses.Contains(aToken)) { 166 continue; 167 } 168 169 if (!resultStr.IsEmpty()) { 170 resultStr.Append(' '); 171 } 172 resultStr.Append(aToken); 173 174 addedClasses.AppendElement(aToken); 175 } 176 177 mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true); 178 } 179 180 void nsDOMTokenList::Add(const nsTArray<nsString>& aTokens, 181 ErrorResult& aError) { 182 CheckTokens(aTokens, aError); 183 if (aError.Failed()) { 184 return; 185 } 186 187 const nsAttrValue* attr = GetParsedAttr(); 188 AddInternal(attr, aTokens); 189 } 190 191 void nsDOMTokenList::Add(const nsAString& aToken, ErrorResult& aError) { 192 AutoTArray<nsString, 1> tokens; 193 tokens.AppendElement(aToken); 194 Add(tokens, aError); 195 } 196 197 void nsDOMTokenList::RemoveInternal(const nsAttrValue* aAttr, 198 const nsTArray<nsString>& aTokens) { 199 MOZ_ASSERT(aAttr, "Need an attribute"); 200 201 RemoveDuplicates(aAttr); 202 203 nsAutoString resultStr; 204 for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) { 205 if (aTokens.Contains(nsDependentAtomString(aAttr->AtomAt(i)))) { 206 continue; 207 } 208 if (!resultStr.IsEmpty()) { 209 resultStr.AppendLiteral(" "); 210 } 211 resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i))); 212 } 213 214 mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true); 215 } 216 217 void nsDOMTokenList::Remove(const nsTArray<nsString>& aTokens, 218 ErrorResult& aError) { 219 CheckTokens(aTokens, aError); 220 if (aError.Failed()) { 221 return; 222 } 223 224 const nsAttrValue* attr = GetParsedAttr(); 225 if (!attr) { 226 return; 227 } 228 229 RemoveInternal(attr, aTokens); 230 } 231 232 void nsDOMTokenList::Remove(const nsAString& aToken, ErrorResult& aError) { 233 AutoTArray<nsString, 1> tokens; 234 tokens.AppendElement(aToken); 235 Remove(tokens, aError); 236 } 237 238 bool nsDOMTokenList::Toggle(const nsAString& aToken, 239 const Optional<bool>& aForce, ErrorResult& aError) { 240 CheckToken(aToken, aError); 241 if (aError.Failed()) { 242 return false; 243 } 244 245 const nsAttrValue* attr = GetParsedAttr(); 246 const bool forceOn = aForce.WasPassed() && aForce.Value(); 247 const bool forceOff = aForce.WasPassed() && !aForce.Value(); 248 249 bool isPresent = attr && attr->Contains(aToken); 250 AutoTArray<nsString, 1> tokens; 251 (*tokens.AppendElement()).Rebind(aToken.Data(), aToken.Length()); 252 253 if (isPresent) { 254 if (!forceOn) { 255 RemoveInternal(attr, tokens); 256 isPresent = false; 257 } 258 } else { 259 if (!forceOff) { 260 AddInternal(attr, tokens); 261 isPresent = true; 262 } 263 } 264 265 return isPresent; 266 } 267 268 bool nsDOMTokenList::Replace(const nsAString& aToken, 269 const nsAString& aNewToken, ErrorResult& aError) { 270 // Doing this here instead of using `CheckToken` because if aToken had invalid 271 // characters, and aNewToken is empty, the returned error should be a 272 // SyntaxError, not an InvalidCharacterError. 273 if (aNewToken.IsEmpty()) { 274 aError.ThrowSyntaxError("The empty string is not a valid token."); 275 return false; 276 } 277 278 CheckToken(aToken, aError); 279 if (aError.Failed()) { 280 return false; 281 } 282 283 CheckToken(aNewToken, aError); 284 if (aError.Failed()) { 285 return false; 286 } 287 288 const nsAttrValue* attr = GetParsedAttr(); 289 if (!attr) { 290 return false; 291 } 292 293 return ReplaceInternal(attr, aToken, aNewToken); 294 } 295 296 bool nsDOMTokenList::ReplaceInternal(const nsAttrValue* aAttr, 297 const nsAString& aToken, 298 const nsAString& aNewToken) { 299 RemoveDuplicates(aAttr); 300 301 // Trying to do a single pass here leads to really complicated code. Just do 302 // the simple thing. 303 bool haveOld = false; 304 for (uint32_t i = 0; i < aAttr->GetAtomCount(); ++i) { 305 if (aAttr->AtomAt(i)->Equals(aToken)) { 306 haveOld = true; 307 break; 308 } 309 } 310 if (!haveOld) { 311 // Make sure to not touch the attribute value in this case. 312 return false; 313 } 314 315 bool sawIt = false; 316 nsAutoString resultStr; 317 for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) { 318 if (aAttr->AtomAt(i)->Equals(aToken) || 319 aAttr->AtomAt(i)->Equals(aNewToken)) { 320 if (sawIt) { 321 // We keep only the first 322 continue; 323 } 324 sawIt = true; 325 if (!resultStr.IsEmpty()) { 326 resultStr.AppendLiteral(" "); 327 } 328 resultStr.Append(aNewToken); 329 continue; 330 } 331 if (!resultStr.IsEmpty()) { 332 resultStr.AppendLiteral(" "); 333 } 334 resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i))); 335 } 336 337 MOZ_ASSERT(sawIt, "How could we not have found our token this time?"); 338 mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true); 339 return true; 340 } 341 342 bool nsDOMTokenList::Supports(const nsAString& aToken, ErrorResult& aError) { 343 if (!mSupportedTokens) { 344 aError.ThrowTypeError<MSG_TOKENLIST_NO_SUPPORTED_TOKENS>( 345 NS_ConvertUTF16toUTF8(mElement->LocalName()), 346 NS_ConvertUTF16toUTF8(nsDependentAtomString(mAttrAtom))); 347 return false; 348 } 349 350 for (DOMTokenListSupportedToken* supportedToken = mSupportedTokens; 351 *supportedToken; ++supportedToken) { 352 if (aToken.LowerCaseEqualsASCII(*supportedToken)) { 353 return true; 354 } 355 } 356 357 return false; 358 } 359 360 DocGroup* nsDOMTokenList::GetDocGroup() const { 361 return mElement ? mElement->OwnerDoc()->GetDocGroup() : nullptr; 362 } 363 364 JSObject* nsDOMTokenList::WrapObject(JSContext* cx, 365 JS::Handle<JSObject*> aGivenProto) { 366 return DOMTokenList_Binding::Wrap(cx, this, aGivenProto); 367 }