nsAttrValue.cpp (65615B)
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 * A struct that represents the value (type and actual data) of an 9 * attribute. 10 */ 11 12 #include "nsAttrValue.h" 13 14 #include <algorithm> 15 16 #include "ReferrerInfo.h" 17 #include "mozilla/ArrayUtils.h" 18 #include "mozilla/AttributeStyles.h" 19 #include "mozilla/BloomFilter.h" 20 #include "mozilla/ClearOnShutdown.h" 21 #include "mozilla/DebugOnly.h" 22 #include "mozilla/DeclarationBlock.h" 23 #include "mozilla/HashFunctions.h" 24 #include "mozilla/MemoryReporting.h" 25 #include "mozilla/SVGAttrValueWrapper.h" 26 #include "mozilla/ServoBindingTypes.h" 27 #include "mozilla/ServoUtils.h" 28 #include "mozilla/ShadowParts.h" 29 #include "mozilla/URLExtraData.h" 30 #include "mozilla/dom/Document.h" 31 #include "nsAttrValueInlines.h" 32 #include "nsContentUtils.h" 33 #include "nsIURI.h" 34 #include "nsReadableUtils.h" 35 #include "nsStyledElement.h" 36 #include "nsUnicharUtils.h" 37 38 using namespace mozilla; 39 40 constexpr uint32_t kMiscContainerCacheSize = 128; 41 static void* gMiscContainerCache[kMiscContainerCacheSize]; 42 static uint32_t gMiscContainerCount = 0; 43 44 /** 45 * Global cache for eAtomArray MiscContainer objects, to speed up the parsing 46 * of class attributes with multiple class names. 47 * This cache doesn't keep anything alive - a MiscContainer removes itself from 48 * the cache once its last reference is dropped. 49 */ 50 struct AtomArrayCache { 51 // We don't keep any strong references, neither to the atom nor to the 52 // MiscContainer. The MiscContainer removes itself from the cache when 53 // the last reference to it is dropped, and the atom is kept alive by 54 // the MiscContainer. 55 using MapType = nsTHashMap<nsAtom*, MiscContainer*>; 56 57 static MiscContainer* Lookup(nsAtom* aValue) { 58 if (auto* instance = GetInstance()) { 59 return instance->LookupImpl(aValue); 60 } 61 return nullptr; 62 } 63 64 static void Insert(nsAtom* aValue, MiscContainer* aCont) { 65 if (auto* instance = GetInstance()) { 66 instance->InsertImpl(aValue, aCont); 67 } 68 } 69 70 static void Remove(nsAtom* aValue) { 71 if (auto* instance = GetInstance()) { 72 instance->RemoveImpl(aValue); 73 } 74 } 75 76 static AtomArrayCache* GetInstance() { 77 static StaticAutoPtr<AtomArrayCache> sInstance; 78 if (!sInstance && !PastShutdownPhase(ShutdownPhase::XPCOMShutdownFinal)) { 79 sInstance = new AtomArrayCache(); 80 ClearOnShutdown(&sInstance, ShutdownPhase::XPCOMShutdownFinal); 81 } 82 return sInstance; 83 } 84 85 private: 86 MiscContainer* LookupImpl(nsAtom* aValue) { 87 auto lookupResult = mMap.Lookup(aValue); 88 return lookupResult ? *lookupResult : nullptr; 89 } 90 91 void InsertImpl(nsAtom* aValue, MiscContainer* aCont) { 92 MOZ_ASSERT(aCont); 93 mMap.InsertOrUpdate(aValue, aCont); 94 } 95 96 void RemoveImpl(nsAtom* aValue) { mMap.Remove(aValue); } 97 98 MapType mMap; 99 }; 100 101 /* static */ 102 MiscContainer* nsAttrValue::AllocMiscContainer() { 103 MOZ_ASSERT(NS_IsMainThread()); 104 105 static_assert(sizeof(gMiscContainerCache) <= 1024); 106 static_assert(sizeof(MiscContainer) <= 32); 107 108 // Allocate MiscContainer objects in batches to improve performance. 109 if (gMiscContainerCount == 0) { 110 for (; gMiscContainerCount < kMiscContainerCacheSize; 111 ++gMiscContainerCount) { 112 gMiscContainerCache[gMiscContainerCount] = 113 moz_xmalloc(sizeof(MiscContainer)); 114 } 115 } 116 117 return new (gMiscContainerCache[--gMiscContainerCount]) MiscContainer(); 118 } 119 120 /* static */ 121 void nsAttrValue::DeallocMiscContainer(MiscContainer* aCont) { 122 MOZ_ASSERT(NS_IsMainThread()); 123 if (!aCont) { 124 return; 125 } 126 127 aCont->~MiscContainer(); 128 129 if (gMiscContainerCount < kMiscContainerCacheSize) { 130 gMiscContainerCache[gMiscContainerCount++] = aCont; 131 return; 132 } 133 134 free(aCont); 135 } 136 137 bool MiscContainer::GetString(nsAString& aString) const { 138 bool isString; 139 void* ptr = GetStringOrAtomPtr(isString); 140 if (!ptr) { 141 return false; 142 } 143 if (isString) { 144 auto* buffer = static_cast<mozilla::StringBuffer*>(ptr); 145 aString.Assign(buffer, buffer->StorageSize() / sizeof(char16_t) - 1); 146 } else { 147 static_cast<nsAtom*>(ptr)->ToString(aString); 148 } 149 return true; 150 } 151 152 void MiscContainer::Cache() { 153 switch (mType) { 154 case nsAttrValue::eCSSDeclaration: { 155 MOZ_ASSERT(IsRefCounted()); 156 MOZ_ASSERT(mValue.mRefCount > 0); 157 MOZ_ASSERT(!mValue.mCached); 158 159 AttributeStyles* attrStyles = 160 mValue.mCSSDeclaration->GetAttributeStyles(); 161 if (!attrStyles) { 162 return; 163 } 164 165 nsString str; 166 bool gotString = GetString(str); 167 if (!gotString) { 168 return; 169 } 170 171 attrStyles->CacheStyleAttr(str, this); 172 mValue.mCached = 1; 173 174 // This has to be immutable once it goes into the cache. 175 mValue.mCSSDeclaration->SetImmutable(); 176 break; 177 } 178 case nsAttrValue::eAtomArray: { 179 MOZ_ASSERT(IsRefCounted()); 180 MOZ_ASSERT(mValue.mRefCount > 0); 181 MOZ_ASSERT(!mValue.mCached); 182 183 nsAtom* atom = GetStoredAtom(); 184 if (!atom) { 185 return; 186 } 187 188 AtomArrayCache::Insert(atom, this); 189 mValue.mCached = 1; 190 break; 191 } 192 default: 193 MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type"); 194 break; 195 } 196 } 197 198 void MiscContainer::Evict() { 199 switch (mType) { 200 case nsAttrValue::eCSSDeclaration: { 201 MOZ_ASSERT(IsRefCounted()); 202 MOZ_ASSERT(mValue.mRefCount == 0); 203 204 if (!mValue.mCached) { 205 return; 206 } 207 208 AttributeStyles* attrStyles = 209 mValue.mCSSDeclaration->GetAttributeStyles(); 210 MOZ_ASSERT(attrStyles); 211 212 nsString str; 213 DebugOnly<bool> gotString = GetString(str); 214 MOZ_ASSERT(gotString); 215 216 attrStyles->EvictStyleAttr(str, this); 217 mValue.mCached = 0; 218 break; 219 } 220 case nsAttrValue::eAtomArray: { 221 MOZ_ASSERT(IsRefCounted()); 222 MOZ_ASSERT(mValue.mRefCount == 0); 223 224 if (!mValue.mCached) { 225 return; 226 } 227 228 nsAtom* atom = GetStoredAtom(); 229 MOZ_ASSERT(atom); 230 231 AtomArrayCache::Remove(atom); 232 233 mValue.mCached = 0; 234 break; 235 } 236 default: 237 238 MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type"); 239 break; 240 } 241 } 242 243 nsTArray<nsAttrValue::EnumTableSpan>* nsAttrValue::sEnumTableArray = nullptr; 244 245 nsAttrValue::nsAttrValue() : mBits(0) {} 246 247 nsAttrValue::nsAttrValue(const nsAttrValue& aOther) : mBits(0) { 248 SetTo(aOther); 249 } 250 251 nsAttrValue::nsAttrValue(const nsAString& aValue) : mBits(0) { SetTo(aValue); } 252 253 nsAttrValue::nsAttrValue(nsAtom* aValue) : mBits(0) { SetTo(aValue); } 254 255 nsAttrValue::nsAttrValue(already_AddRefed<DeclarationBlock> aValue, 256 const nsAString* aSerialized) 257 : mBits(0) { 258 SetTo(std::move(aValue), aSerialized); 259 } 260 261 nsAttrValue::~nsAttrValue() { ResetIfSet(); } 262 263 /* static */ 264 void nsAttrValue::Init() { 265 MOZ_ASSERT(!sEnumTableArray, "nsAttrValue already initialized"); 266 sEnumTableArray = new nsTArray<EnumTableSpan>; 267 } 268 269 /* static */ 270 void nsAttrValue::Shutdown() { 271 MOZ_ASSERT(NS_IsMainThread()); 272 delete sEnumTableArray; 273 sEnumTableArray = nullptr; 274 275 for (uint32_t i = 0; i < gMiscContainerCount; ++i) { 276 free(gMiscContainerCache[i]); 277 } 278 gMiscContainerCount = 0; 279 } 280 281 void nsAttrValue::Reset() { 282 switch (BaseType()) { 283 case eStringBase: { 284 if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) { 285 str->Release(); 286 } 287 break; 288 } 289 case eOtherBase: { 290 MiscContainer* cont = GetMiscContainer(); 291 if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) { 292 NS_RELEASE(cont); 293 break; 294 } 295 296 DeallocMiscContainer(ClearMiscContainer()); 297 298 break; 299 } 300 case eAtomBase: { 301 nsAtom* atom = GetAtomValue(); 302 NS_RELEASE(atom); 303 304 break; 305 } 306 case eIntegerBase: { 307 break; 308 } 309 } 310 311 mBits = 0; 312 } 313 314 void nsAttrValue::SetTo(const nsAttrValue& aOther) { 315 if (this == &aOther) { 316 return; 317 } 318 319 switch (aOther.BaseType()) { 320 case eStringBase: { 321 ResetIfSet(); 322 if (auto* str = static_cast<mozilla::StringBuffer*>(aOther.GetPtr())) { 323 str->AddRef(); 324 SetPtrValueAndType(str, eStringBase); 325 } 326 return; 327 } 328 case eOtherBase: { 329 break; 330 } 331 case eAtomBase: { 332 ResetIfSet(); 333 nsAtom* atom = aOther.GetAtomValue(); 334 NS_ADDREF(atom); 335 SetPtrValueAndType(atom, eAtomBase); 336 return; 337 } 338 case eIntegerBase: { 339 ResetIfSet(); 340 mBits = aOther.mBits; 341 return; 342 } 343 } 344 345 MiscContainer* otherCont = aOther.GetMiscContainer(); 346 if (otherCont->IsRefCounted()) { 347 DeallocMiscContainer(ClearMiscContainer()); 348 NS_ADDREF(otherCont); 349 SetPtrValueAndType(otherCont, eOtherBase); 350 return; 351 } 352 353 MiscContainer* cont = EnsureEmptyMiscContainer(); 354 switch (otherCont->mType) { 355 case eInteger: { 356 cont->mValue.mInteger = otherCont->mValue.mInteger; 357 break; 358 } 359 case eEnum: { 360 cont->mValue.mEnumValue = otherCont->mValue.mEnumValue; 361 break; 362 } 363 case ePercent: { 364 cont->mDoubleValue = otherCont->mDoubleValue; 365 break; 366 } 367 case eColor: { 368 cont->mValue.mColor = otherCont->mValue.mColor; 369 break; 370 } 371 case eAtomArray: 372 case eShadowParts: 373 case eCSSDeclaration: { 374 MOZ_CRASH("These should be refcounted!"); 375 } 376 case eURL: { 377 NS_ADDREF(cont->mValue.mURL = otherCont->mValue.mURL); 378 break; 379 } 380 case eDoubleValue: { 381 cont->mDoubleValue = otherCont->mDoubleValue; 382 break; 383 } 384 default: { 385 if (IsSVGType(otherCont->mType)) { 386 // All SVG types are just pointers to classes and will therefore have 387 // the same size so it doesn't really matter which one we assign 388 cont->mValue.mSVGLength = otherCont->mValue.mSVGLength; 389 } else { 390 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer"); 391 } 392 break; 393 } 394 } 395 396 bool isString; 397 if (void* otherPtr = otherCont->GetStringOrAtomPtr(isString)) { 398 if (isString) { 399 static_cast<mozilla::StringBuffer*>(otherPtr)->AddRef(); 400 } else { 401 static_cast<nsAtom*>(otherPtr)->AddRef(); 402 } 403 cont->SetStringBitsMainThread(otherCont->mStringBits); 404 } 405 // Note, set mType after switch-case, otherwise EnsureEmptyAtomArray doesn't 406 // work correctly. 407 cont->mType = otherCont->mType; 408 } 409 410 void nsAttrValue::SetTo(const nsAString& aValue) { 411 ResetIfSet(); 412 mozilla::StringBuffer* buf = GetStringBuffer(aValue).take(); 413 if (buf) { 414 SetPtrValueAndType(buf, eStringBase); 415 } 416 } 417 418 void nsAttrValue::SetTo(nsAtom* aValue) { 419 ResetIfSet(); 420 if (aValue) { 421 NS_ADDREF(aValue); 422 SetPtrValueAndType(aValue, eAtomBase); 423 } 424 } 425 426 void nsAttrValue::SetTo(int16_t aInt) { 427 ResetIfSet(); 428 SetIntValueAndType(aInt, eInteger, nullptr); 429 } 430 431 void nsAttrValue::SetTo(int32_t aInt, const nsAString* aSerialized) { 432 ResetIfSet(); 433 SetIntValueAndType(aInt, eInteger, aSerialized); 434 } 435 436 void nsAttrValue::SetTo(double aValue, const nsAString* aSerialized) { 437 MiscContainer* cont = EnsureEmptyMiscContainer(); 438 cont->mDoubleValue = aValue; 439 cont->mType = eDoubleValue; 440 SetMiscAtomOrString(aSerialized); 441 } 442 443 void nsAttrValue::SetTo(already_AddRefed<DeclarationBlock> aValue, 444 const nsAString* aSerialized) { 445 MiscContainer* cont = EnsureEmptyMiscContainer(); 446 MOZ_ASSERT(cont->mValue.mRefCount == 0); 447 cont->mValue.mCSSDeclaration = aValue.take(); 448 cont->mType = eCSSDeclaration; 449 NS_ADDREF(cont); 450 SetMiscAtomOrString(aSerialized); 451 MOZ_ASSERT(cont->mValue.mRefCount == 1); 452 } 453 454 void nsAttrValue::SetTo(nsIURI* aValue, const nsAString* aSerialized) { 455 MiscContainer* cont = EnsureEmptyMiscContainer(); 456 NS_ADDREF(cont->mValue.mURL = aValue); 457 cont->mType = eURL; 458 SetMiscAtomOrString(aSerialized); 459 } 460 461 void nsAttrValue::SetToSerialized(const nsAttrValue& aOther) { 462 if (aOther.Type() != nsAttrValue::eString && 463 aOther.Type() != nsAttrValue::eAtom) { 464 nsAutoString val; 465 aOther.ToString(val); 466 SetTo(val); 467 } else { 468 SetTo(aOther); 469 } 470 } 471 472 void nsAttrValue::SetTo(const SVGAnimatedOrient& aValue, 473 const nsAString* aSerialized) { 474 SetSVGType(eSVGOrient, &aValue, aSerialized); 475 } 476 477 void nsAttrValue::SetTo(const SVGAnimatedIntegerPair& aValue, 478 const nsAString* aSerialized) { 479 SetSVGType(eSVGIntegerPair, &aValue, aSerialized); 480 } 481 482 void nsAttrValue::SetTo(const SVGAnimatedLength& aValue, 483 const nsAString* aSerialized) { 484 SetSVGType(eSVGLength, &aValue, aSerialized); 485 } 486 487 void nsAttrValue::SetTo(const SVGLengthList& aValue, 488 const nsAString* aSerialized) { 489 // While an empty string will parse as a length list, there's no need to store 490 // it (and SetMiscAtomOrString will assert if we try) 491 if (aSerialized && aSerialized->IsEmpty()) { 492 aSerialized = nullptr; 493 } 494 SetSVGType(eSVGLengthList, &aValue, aSerialized); 495 } 496 497 void nsAttrValue::SetTo(const SVGNumberList& aValue, 498 const nsAString* aSerialized) { 499 // While an empty string will parse as a number list, there's no need to store 500 // it (and SetMiscAtomOrString will assert if we try) 501 if (aSerialized && aSerialized->IsEmpty()) { 502 aSerialized = nullptr; 503 } 504 SetSVGType(eSVGNumberList, &aValue, aSerialized); 505 } 506 507 void nsAttrValue::SetTo(const SVGAnimatedNumberPair& aValue, 508 const nsAString* aSerialized) { 509 SetSVGType(eSVGNumberPair, &aValue, aSerialized); 510 } 511 512 void nsAttrValue::SetTo(const SVGPathData& aValue, 513 const nsAString* aSerialized) { 514 // While an empty string will parse as path data, there's no need to store it 515 // (and SetMiscAtomOrString will assert if we try) 516 if (aSerialized && aSerialized->IsEmpty()) { 517 aSerialized = nullptr; 518 } 519 SetSVGType(eSVGPathData, &aValue, aSerialized); 520 } 521 522 void nsAttrValue::SetTo(const SVGPointList& aValue, 523 const nsAString* aSerialized) { 524 // While an empty string will parse as a point list, there's no need to store 525 // it (and SetMiscAtomOrString will assert if we try) 526 if (aSerialized && aSerialized->IsEmpty()) { 527 aSerialized = nullptr; 528 } 529 SetSVGType(eSVGPointList, &aValue, aSerialized); 530 } 531 532 void nsAttrValue::SetTo(const SVGAnimatedPreserveAspectRatio& aValue, 533 const nsAString* aSerialized) { 534 SetSVGType(eSVGPreserveAspectRatio, &aValue, aSerialized); 535 } 536 537 void nsAttrValue::SetTo(const SVGStringList& aValue, 538 const nsAString* aSerialized) { 539 // While an empty string will parse as a string list, there's no need to store 540 // it (and SetMiscAtomOrString will assert if we try) 541 if (aSerialized && aSerialized->IsEmpty()) { 542 aSerialized = nullptr; 543 } 544 SetSVGType(eSVGStringList, &aValue, aSerialized); 545 } 546 547 void nsAttrValue::SetTo(const SVGTransformList& aValue, 548 const nsAString* aSerialized) { 549 // While an empty string will parse as a transform list, there's no need to 550 // store it (and SetMiscAtomOrString will assert if we try) 551 if (aSerialized && aSerialized->IsEmpty()) { 552 aSerialized = nullptr; 553 } 554 SetSVGType(eSVGTransformList, &aValue, aSerialized); 555 } 556 557 void nsAttrValue::SetTo(const SVGAnimatedViewBox& aValue, 558 const nsAString* aSerialized) { 559 SetSVGType(eSVGViewBox, &aValue, aSerialized); 560 } 561 562 void nsAttrValue::SwapValueWith(nsAttrValue& aOther) { 563 uintptr_t tmp = aOther.mBits; 564 aOther.mBits = mBits; 565 mBits = tmp; 566 } 567 568 void nsAttrValue::RemoveDuplicatesFromAtomArray() { 569 if (Type() != eAtomArray) { 570 return; 571 } 572 573 const AttrAtomArray* currentAtomArray = GetMiscContainer()->mValue.mAtomArray; 574 UniquePtr<AttrAtomArray> deduplicatedAtomArray = 575 currentAtomArray->CreateDeduplicatedCopyIfDifferent(); 576 577 if (!deduplicatedAtomArray) { 578 // No duplicates found. Leave this value unchanged. 579 return; 580 } 581 582 // We found duplicates. Wrap the new atom array into a fresh MiscContainer, 583 // and copy over the existing container's string or atom. 584 585 MiscContainer* oldCont = GetMiscContainer(); 586 MOZ_ASSERT(oldCont->IsRefCounted()); 587 588 uintptr_t stringBits = 0; 589 bool isString = false; 590 if (void* otherPtr = oldCont->GetStringOrAtomPtr(isString)) { 591 stringBits = oldCont->mStringBits; 592 if (isString) { 593 static_cast<mozilla::StringBuffer*>(otherPtr)->AddRef(); 594 } else { 595 static_cast<nsAtom*>(otherPtr)->AddRef(); 596 } 597 } 598 599 MiscContainer* cont = EnsureEmptyMiscContainer(); 600 MOZ_ASSERT(cont->mValue.mRefCount == 0); 601 cont->mValue.mAtomArray = deduplicatedAtomArray.release(); 602 cont->mType = eAtomArray; 603 NS_ADDREF(cont); 604 MOZ_ASSERT(cont->mValue.mRefCount == 1); 605 cont->SetStringBitsMainThread(stringBits); 606 607 // Don't cache the new container. It would stomp over the undeduplicated 608 // value in the cache. But we could have a separate cache for deduplicated 609 // atom arrays, if repeated deduplication shows up in profiles. 610 } 611 612 void nsAttrValue::ToString(nsAString& aResult) const { 613 MiscContainer* cont = nullptr; 614 if (BaseType() == eOtherBase) { 615 cont = GetMiscContainer(); 616 617 if (cont->GetString(aResult)) { 618 return; 619 } 620 } 621 622 switch (Type()) { 623 case eString: { 624 if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) { 625 aResult.Assign(str, str->StorageSize() / sizeof(char16_t) - 1); 626 } else { 627 aResult.Truncate(); 628 } 629 break; 630 } 631 case eAtom: { 632 auto* atom = static_cast<nsAtom*>(GetPtr()); 633 atom->ToString(aResult); 634 break; 635 } 636 case eInteger: { 637 nsAutoString intStr; 638 intStr.AppendInt(GetIntegerValue()); 639 aResult = intStr; 640 641 break; 642 } 643 #ifdef DEBUG 644 case eColor: { 645 MOZ_ASSERT_UNREACHABLE("color attribute without string data"); 646 aResult.Truncate(); 647 break; 648 } 649 #endif 650 case eEnum: { 651 GetEnumString(aResult, false); 652 break; 653 } 654 case ePercent: { 655 nsAutoString str; 656 if (cont) { 657 str.AppendFloat(cont->mDoubleValue); 658 } else { 659 str.AppendInt(GetIntInternal()); 660 } 661 aResult = str + u"%"_ns; 662 663 break; 664 } 665 case eCSSDeclaration: { 666 aResult.Truncate(); 667 MiscContainer* container = GetMiscContainer(); 668 if (DeclarationBlock* decl = container->mValue.mCSSDeclaration) { 669 nsAutoCString result; 670 decl->ToString(result); 671 CopyUTF8toUTF16(result, aResult); 672 } 673 674 // This can be reached during parallel selector matching with attribute 675 // selectors on the style attribute. SetMiscAtomOrString handles this 676 // case, and as of this writing this is the only consumer that needs it. 677 const_cast<nsAttrValue*>(this)->SetMiscAtomOrString(&aResult); 678 679 break; 680 } 681 case eDoubleValue: { 682 aResult.Truncate(); 683 aResult.AppendFloat(GetDoubleValue()); 684 break; 685 } 686 case eSVGIntegerPair: { 687 SVGAttrValueWrapper::ToString( 688 GetMiscContainer()->mValue.mSVGAnimatedIntegerPair, aResult); 689 break; 690 } 691 case eSVGOrient: { 692 SVGAttrValueWrapper::ToString( 693 GetMiscContainer()->mValue.mSVGAnimatedOrient, aResult); 694 break; 695 } 696 case eSVGLength: { 697 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLength, 698 aResult); 699 break; 700 } 701 case eSVGLengthList: { 702 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLengthList, 703 aResult); 704 break; 705 } 706 case eSVGNumberList: { 707 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGNumberList, 708 aResult); 709 break; 710 } 711 case eSVGNumberPair: { 712 SVGAttrValueWrapper::ToString( 713 GetMiscContainer()->mValue.mSVGAnimatedNumberPair, aResult); 714 break; 715 } 716 case eSVGPathData: { 717 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPathData, 718 aResult); 719 break; 720 } 721 case eSVGPointList: { 722 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPointList, 723 aResult); 724 break; 725 } 726 case eSVGPreserveAspectRatio: { 727 SVGAttrValueWrapper::ToString( 728 GetMiscContainer()->mValue.mSVGAnimatedPreserveAspectRatio, aResult); 729 break; 730 } 731 case eSVGStringList: { 732 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGStringList, 733 aResult); 734 break; 735 } 736 case eSVGTransformList: { 737 SVGAttrValueWrapper::ToString( 738 GetMiscContainer()->mValue.mSVGTransformList, aResult); 739 break; 740 } 741 case eSVGViewBox: { 742 SVGAttrValueWrapper::ToString( 743 GetMiscContainer()->mValue.mSVGAnimatedViewBox, aResult); 744 break; 745 } 746 default: { 747 aResult.Truncate(); 748 break; 749 } 750 } 751 } 752 753 already_AddRefed<nsAtom> nsAttrValue::GetAsAtom() const { 754 switch (Type()) { 755 case eString: 756 return NS_AtomizeMainThread(GetStringValue()); 757 758 case eAtom: { 759 RefPtr<nsAtom> atom = GetAtomValue(); 760 return atom.forget(); 761 } 762 763 default: { 764 nsAutoString val; 765 ToString(val); 766 return NS_AtomizeMainThread(val); 767 } 768 } 769 } 770 771 const nsCheapString nsAttrValue::GetStringValue() const { 772 MOZ_ASSERT(Type() == eString, "wrong type"); 773 774 return nsCheapString(static_cast<mozilla::StringBuffer*>(GetPtr())); 775 } 776 777 bool nsAttrValue::GetColorValue(nscolor& aColor) const { 778 if (Type() != eColor) { 779 // Unparseable value, treat as unset. 780 NS_ASSERTION(Type() == eString, "unexpected type for color-valued attr"); 781 return false; 782 } 783 784 aColor = GetMiscContainer()->mValue.mColor; 785 return true; 786 } 787 788 void nsAttrValue::GetEnumString(nsAString& aResult, bool aRealTag) const { 789 MOZ_ASSERT(Type() == eEnum, "wrong type"); 790 791 uint32_t allEnumBits = (BaseType() == eIntegerBase) 792 ? static_cast<uint32_t>(GetIntInternal()) 793 : GetMiscContainer()->mValue.mEnumValue; 794 int16_t val = allEnumBits >> NS_ATTRVALUE_ENUMTABLEINDEX_BITS; 795 EnumTableSpan table = sEnumTableArray->ElementAt( 796 allEnumBits & NS_ATTRVALUE_ENUMTABLEINDEX_MASK); 797 for (const auto& entry : table) { 798 if (entry.value == val) { 799 aResult.AssignASCII(entry.tag); 800 if (!aRealTag && 801 allEnumBits & NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER) { 802 nsContentUtils::ASCIIToUpper(aResult); 803 } 804 return; 805 } 806 } 807 808 MOZ_ASSERT_UNREACHABLE("couldn't find value in EnumTable"); 809 } 810 811 UniquePtr<AttrAtomArray> AttrAtomArray::CreateDeduplicatedCopyIfDifferentImpl() 812 const { 813 MOZ_ASSERT(mMayContainDuplicates); 814 815 bool usingHashTable = false; 816 BitBloomFilter<8, nsAtom> filter; 817 nsTHashSet<nsAtom*> hash; 818 819 auto CheckDuplicate = [&](size_t i) { 820 nsAtom* atom = mArray[i]; 821 if (!usingHashTable) { 822 if (!filter.mightContain(atom)) { 823 filter.add(atom); 824 return false; 825 } 826 for (size_t j = 0; j < i; ++j) { 827 hash.Insert(mArray[j]); 828 } 829 usingHashTable = true; 830 } 831 return !hash.EnsureInserted(atom); 832 }; 833 834 size_t len = mArray.Length(); 835 UniquePtr<AttrAtomArray> deduplicatedArray; 836 for (size_t i = 0; i < len; ++i) { 837 if (!CheckDuplicate(i)) { 838 if (deduplicatedArray) { 839 deduplicatedArray->mArray.AppendElement(mArray[i]); 840 } 841 continue; 842 } 843 // We've found a duplicate! 844 if (!deduplicatedArray) { 845 // Allocate the deduplicated copy and copy the preceding elements into it. 846 deduplicatedArray = MakeUnique<AttrAtomArray>(); 847 deduplicatedArray->mMayContainDuplicates = false; 848 deduplicatedArray->mArray.SetCapacity(len - 1); 849 for (size_t indexToCopy = 0; indexToCopy < i; indexToCopy++) { 850 deduplicatedArray->mArray.AppendElement(mArray[indexToCopy]); 851 } 852 } 853 } 854 855 if (!deduplicatedArray) { 856 // This AttrAtomArray doesn't contain any duplicates, cache this information 857 // for future invocations. 858 mMayContainDuplicates = false; 859 } 860 return deduplicatedArray; 861 } 862 863 uint32_t nsAttrValue::GetAtomCount() const { 864 ValueType type = Type(); 865 866 if (type == eAtom) { 867 return 1; 868 } 869 870 if (type == eAtomArray) { 871 return GetAtomArrayValue()->mArray.Length(); 872 } 873 874 return 0; 875 } 876 877 nsAtom* nsAttrValue::AtomAt(int32_t aIndex) const { 878 MOZ_ASSERT(aIndex >= 0, "Index must not be negative"); 879 MOZ_ASSERT(GetAtomCount() > uint32_t(aIndex), "aIndex out of range"); 880 881 if (BaseType() == eAtomBase) { 882 return GetAtomValue(); 883 } 884 885 NS_ASSERTION(Type() == eAtomArray, "GetAtomCount must be confused"); 886 return GetAtomArrayValue()->mArray.ElementAt(aIndex); 887 } 888 889 uint32_t nsAttrValue::HashValue() const { 890 switch (BaseType()) { 891 case eStringBase: { 892 if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) { 893 uint32_t len = str->StorageSize() / sizeof(char16_t) - 1; 894 return HashString(static_cast<char16_t*>(str->Data()), len); 895 } 896 897 return 0; 898 } 899 case eOtherBase: { 900 break; 901 } 902 case eAtomBase: 903 case eIntegerBase: { 904 // mBits and uint32_t might have different size. This should silence 905 // any warnings or compile-errors. This is what the implementation of 906 // NS_PTR_TO_INT32 does to take care of the same problem. 907 return mBits - 0; 908 } 909 } 910 911 MiscContainer* cont = GetMiscContainer(); 912 if (static_cast<ValueBaseType>(cont->mStringBits & 913 NS_ATTRVALUE_BASETYPE_MASK) == eAtomBase) { 914 return cont->mStringBits - 0; 915 } 916 917 switch (cont->mType) { 918 case eInteger: { 919 return cont->mValue.mInteger; 920 } 921 case eEnum: { 922 return cont->mValue.mEnumValue; 923 } 924 case ePercent: { 925 return cont->mDoubleValue; 926 } 927 case eColor: { 928 return cont->mValue.mColor; 929 } 930 case eCSSDeclaration: { 931 return NS_PTR_TO_INT32(cont->mValue.mCSSDeclaration); 932 } 933 case eURL: { 934 nsString str; 935 ToString(str); 936 return HashString(str); 937 } 938 case eAtomArray: { 939 uint32_t hash = 0; 940 for (const auto& atom : cont->mValue.mAtomArray->mArray) { 941 hash = AddToHash(hash, atom.get()); 942 } 943 return hash; 944 } 945 case eDoubleValue: { 946 // XXX this is crappy, but oh well 947 return cont->mDoubleValue; 948 } 949 default: { 950 if (IsSVGType(cont->mType)) { 951 // All SVG types are just pointers to classes so we can treat them alike 952 return NS_PTR_TO_INT32(cont->mValue.mSVGLength); 953 } 954 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer"); 955 return 0; 956 } 957 } 958 } 959 960 bool nsAttrValue::Equals(const nsAttrValue& aOther) const { 961 if (BaseType() != aOther.BaseType()) { 962 return false; 963 } 964 965 switch (BaseType()) { 966 case eStringBase: { 967 return GetStringValue().Equals(aOther.GetStringValue()); 968 } 969 case eOtherBase: { 970 break; 971 } 972 case eAtomBase: 973 case eIntegerBase: { 974 return mBits == aOther.mBits; 975 } 976 } 977 978 MiscContainer* thisCont = GetMiscContainer(); 979 MiscContainer* otherCont = aOther.GetMiscContainer(); 980 if (thisCont == otherCont) { 981 return true; 982 } 983 984 if (thisCont->mType != otherCont->mType) { 985 return false; 986 } 987 988 bool needsStringComparison = false; 989 990 switch (thisCont->mType) { 991 case eInteger: { 992 if (thisCont->mValue.mInteger == otherCont->mValue.mInteger) { 993 needsStringComparison = true; 994 } 995 break; 996 } 997 case eEnum: { 998 if (thisCont->mValue.mEnumValue == otherCont->mValue.mEnumValue) { 999 needsStringComparison = true; 1000 } 1001 break; 1002 } 1003 case ePercent: { 1004 if (thisCont->mDoubleValue == otherCont->mDoubleValue) { 1005 needsStringComparison = true; 1006 } 1007 break; 1008 } 1009 case eColor: { 1010 if (thisCont->mValue.mColor == otherCont->mValue.mColor) { 1011 needsStringComparison = true; 1012 } 1013 break; 1014 } 1015 case eCSSDeclaration: { 1016 return thisCont->mValue.mCSSDeclaration == 1017 otherCont->mValue.mCSSDeclaration; 1018 } 1019 case eURL: { 1020 return thisCont->mValue.mURL == otherCont->mValue.mURL; 1021 } 1022 case eAtomArray: { 1023 // For classlists we could be insensitive to order, however 1024 // classlists are never mapped attributes so they are never compared. 1025 1026 if (!(*thisCont->mValue.mAtomArray == *otherCont->mValue.mAtomArray)) { 1027 return false; 1028 } 1029 1030 needsStringComparison = true; 1031 break; 1032 } 1033 case eDoubleValue: { 1034 return thisCont->mDoubleValue == otherCont->mDoubleValue; 1035 } 1036 default: { 1037 if (IsSVGType(thisCont->mType)) { 1038 // Currently this method is never called for nsAttrValue objects that 1039 // point to SVG data types. 1040 // If that changes then we probably want to add methods to the 1041 // corresponding SVG types to compare their base values. 1042 // As a shortcut, however, we can begin by comparing the pointers. 1043 MOZ_ASSERT(false, "Comparing nsAttrValues that point to SVG data"); 1044 return false; 1045 } 1046 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer"); 1047 return false; 1048 } 1049 } 1050 if (needsStringComparison) { 1051 if (thisCont->mStringBits == otherCont->mStringBits) { 1052 return true; 1053 } 1054 if ((static_cast<ValueBaseType>(thisCont->mStringBits & 1055 NS_ATTRVALUE_BASETYPE_MASK) == 1056 eStringBase) && 1057 (static_cast<ValueBaseType>(otherCont->mStringBits & 1058 NS_ATTRVALUE_BASETYPE_MASK) == 1059 eStringBase)) { 1060 return nsCheapString(reinterpret_cast<mozilla::StringBuffer*>( 1061 static_cast<uintptr_t>(thisCont->mStringBits))) 1062 .Equals(nsCheapString(reinterpret_cast<mozilla::StringBuffer*>( 1063 static_cast<uintptr_t>(otherCont->mStringBits)))); 1064 } 1065 } 1066 return false; 1067 } 1068 1069 bool nsAttrValue::Equals(const nsAString& aValue, 1070 nsCaseTreatment aCaseSensitive) const { 1071 switch (BaseType()) { 1072 case eStringBase: { 1073 if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) { 1074 nsDependentString dep(static_cast<char16_t*>(str->Data()), 1075 str->StorageSize() / sizeof(char16_t) - 1); 1076 return aCaseSensitive == eCaseMatters 1077 ? aValue.Equals(dep) 1078 : nsContentUtils::EqualsIgnoreASCIICase(aValue, dep); 1079 } 1080 return aValue.IsEmpty(); 1081 } 1082 case eAtomBase: { 1083 auto* atom = static_cast<nsAtom*>(GetPtr()); 1084 if (aCaseSensitive == eCaseMatters) { 1085 return atom->Equals(aValue); 1086 } 1087 return nsContentUtils::EqualsIgnoreASCIICase(nsDependentAtomString(atom), 1088 aValue); 1089 } 1090 default: 1091 break; 1092 } 1093 1094 nsAutoString val; 1095 ToString(val); 1096 return aCaseSensitive == eCaseMatters 1097 ? val.Equals(aValue) 1098 : nsContentUtils::EqualsIgnoreASCIICase(val, aValue); 1099 } 1100 1101 bool nsAttrValue::Equals(const nsAtom* aValue, 1102 nsCaseTreatment aCaseSensitive) const { 1103 switch (BaseType()) { 1104 case eAtomBase: { 1105 auto* atom = static_cast<nsAtom*>(GetPtr()); 1106 if (atom == aValue) { 1107 return true; 1108 } 1109 if (aCaseSensitive == eCaseMatters) { 1110 return false; 1111 } 1112 if (atom->IsAsciiLowercase() && aValue->IsAsciiLowercase()) { 1113 return false; 1114 } 1115 return nsContentUtils::EqualsIgnoreASCIICase( 1116 nsDependentAtomString(atom), nsDependentAtomString(aValue)); 1117 } 1118 case eStringBase: { 1119 if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) { 1120 size_t strLen = str->StorageSize() / sizeof(char16_t) - 1; 1121 if (aValue->GetLength() != strLen) { 1122 return false; 1123 } 1124 const char16_t* strData = static_cast<char16_t*>(str->Data()); 1125 const char16_t* valData = aValue->GetUTF16String(); 1126 if (aCaseSensitive == eCaseMatters) { 1127 // Avoid string construction / destruction for the easy case. 1128 return ArrayEqual(strData, valData, strLen); 1129 } 1130 nsDependentSubstring depStr(strData, strLen); 1131 nsDependentSubstring depVal(valData, strLen); 1132 return nsContentUtils::EqualsIgnoreASCIICase(depStr, depVal); 1133 } 1134 return aValue->IsEmpty(); 1135 } 1136 default: 1137 break; 1138 } 1139 1140 nsAutoString val; 1141 ToString(val); 1142 nsDependentAtomString dep(aValue); 1143 return aCaseSensitive == eCaseMatters 1144 ? val.Equals(dep) 1145 : nsContentUtils::EqualsIgnoreASCIICase(val, dep); 1146 } 1147 1148 struct HasPrefixFn { 1149 static bool Check(const char16_t* aAttrValue, size_t aAttrLen, 1150 const nsAString& aSearchValue, 1151 nsCaseTreatment aCaseSensitive) { 1152 if (aCaseSensitive == eCaseMatters) { 1153 if (aSearchValue.Length() > aAttrLen) { 1154 return false; 1155 } 1156 return !memcmp(aAttrValue, aSearchValue.BeginReading(), 1157 aSearchValue.Length() * sizeof(char16_t)); 1158 } 1159 return StringBeginsWith(nsDependentString(aAttrValue, aAttrLen), 1160 aSearchValue, 1161 nsASCIICaseInsensitiveStringComparator); 1162 } 1163 }; 1164 1165 struct HasSuffixFn { 1166 static bool Check(const char16_t* aAttrValue, size_t aAttrLen, 1167 const nsAString& aSearchValue, 1168 nsCaseTreatment aCaseSensitive) { 1169 if (aCaseSensitive == eCaseMatters) { 1170 if (aSearchValue.Length() > aAttrLen) { 1171 return false; 1172 } 1173 return !memcmp(aAttrValue + aAttrLen - aSearchValue.Length(), 1174 aSearchValue.BeginReading(), 1175 aSearchValue.Length() * sizeof(char16_t)); 1176 } 1177 return StringEndsWith(nsDependentString(aAttrValue, aAttrLen), aSearchValue, 1178 nsASCIICaseInsensitiveStringComparator); 1179 } 1180 }; 1181 1182 struct HasSubstringFn { 1183 static bool Check(const char16_t* aAttrValue, size_t aAttrLen, 1184 const nsAString& aSearchValue, 1185 nsCaseTreatment aCaseSensitive) { 1186 if (aCaseSensitive == eCaseMatters) { 1187 if (aSearchValue.IsEmpty()) { 1188 return true; 1189 } 1190 const char16_t* end = aAttrValue + aAttrLen; 1191 return std::search(aAttrValue, end, aSearchValue.BeginReading(), 1192 aSearchValue.EndReading()) != end; 1193 } 1194 return FindInReadable(aSearchValue, nsDependentString(aAttrValue, aAttrLen), 1195 nsASCIICaseInsensitiveStringComparator); 1196 } 1197 }; 1198 1199 template <typename F> 1200 bool nsAttrValue::SubstringCheck(const nsAString& aValue, 1201 nsCaseTreatment aCaseSensitive) const { 1202 switch (BaseType()) { 1203 case eStringBase: { 1204 if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) { 1205 return F::Check(static_cast<char16_t*>(str->Data()), 1206 str->StorageSize() / sizeof(char16_t) - 1, aValue, 1207 aCaseSensitive); 1208 } 1209 return aValue.IsEmpty(); 1210 } 1211 case eAtomBase: { 1212 auto* atom = static_cast<nsAtom*>(GetPtr()); 1213 return F::Check(atom->GetUTF16String(), atom->GetLength(), aValue, 1214 aCaseSensitive); 1215 } 1216 default: 1217 break; 1218 } 1219 1220 nsAutoString val; 1221 ToString(val); 1222 return F::Check(val.BeginReading(), val.Length(), aValue, aCaseSensitive); 1223 } 1224 1225 bool nsAttrValue::HasPrefix(const nsAString& aValue, 1226 nsCaseTreatment aCaseSensitive) const { 1227 return SubstringCheck<HasPrefixFn>(aValue, aCaseSensitive); 1228 } 1229 1230 bool nsAttrValue::HasSuffix(const nsAString& aValue, 1231 nsCaseTreatment aCaseSensitive) const { 1232 return SubstringCheck<HasSuffixFn>(aValue, aCaseSensitive); 1233 } 1234 1235 bool nsAttrValue::HasSubstring(const nsAString& aValue, 1236 nsCaseTreatment aCaseSensitive) const { 1237 return SubstringCheck<HasSubstringFn>(aValue, aCaseSensitive); 1238 } 1239 1240 bool nsAttrValue::EqualsAsStrings(const nsAttrValue& aOther) const { 1241 if (Type() == aOther.Type()) { 1242 return Equals(aOther); 1243 } 1244 1245 // We need to serialize at least one nsAttrValue before passing to 1246 // Equals(const nsAString&), but we can avoid unnecessarily serializing both 1247 // by checking if one is already of a string type. 1248 bool thisIsString = (BaseType() == eStringBase || BaseType() == eAtomBase); 1249 const nsAttrValue& lhs = thisIsString ? *this : aOther; 1250 const nsAttrValue& rhs = thisIsString ? aOther : *this; 1251 1252 switch (rhs.BaseType()) { 1253 case eAtomBase: 1254 return lhs.Equals(rhs.GetAtomValue(), eCaseMatters); 1255 1256 case eStringBase: 1257 return lhs.Equals(rhs.GetStringValue(), eCaseMatters); 1258 1259 default: { 1260 nsAutoString val; 1261 rhs.ToString(val); 1262 return lhs.Equals(val, eCaseMatters); 1263 } 1264 } 1265 } 1266 1267 bool nsAttrValue::Contains(nsAtom* aValue, 1268 nsCaseTreatment aCaseSensitive) const { 1269 switch (BaseType()) { 1270 case eAtomBase: { 1271 nsAtom* atom = GetAtomValue(); 1272 if (aCaseSensitive == eCaseMatters) { 1273 return aValue == atom; 1274 } 1275 1276 // For performance reasons, don't do a full on unicode case insensitive 1277 // string comparison. This is only used for quirks mode anyway. 1278 return nsContentUtils::EqualsIgnoreASCIICase(aValue, atom); 1279 } 1280 default: { 1281 if (Type() == eAtomArray) { 1282 const AttrAtomArray* array = GetAtomArrayValue(); 1283 if (aCaseSensitive == eCaseMatters) { 1284 return array->mArray.Contains(aValue); 1285 } 1286 1287 for (const RefPtr<nsAtom>& cur : array->mArray) { 1288 // For performance reasons, don't do a full on unicode case 1289 // insensitive string comparison. This is only used for quirks mode 1290 // anyway. 1291 if (nsContentUtils::EqualsIgnoreASCIICase(aValue, cur)) { 1292 return true; 1293 } 1294 } 1295 } 1296 } 1297 } 1298 1299 return false; 1300 } 1301 1302 struct AtomArrayStringComparator { 1303 bool Equals(nsAtom* atom, const nsAString& string) const { 1304 return atom->Equals(string); 1305 } 1306 }; 1307 1308 bool nsAttrValue::Contains(const nsAString& aValue) const { 1309 switch (BaseType()) { 1310 case eAtomBase: { 1311 nsAtom* atom = GetAtomValue(); 1312 return atom->Equals(aValue); 1313 } 1314 default: { 1315 if (Type() == eAtomArray) { 1316 const AttrAtomArray* array = GetAtomArrayValue(); 1317 return array->mArray.Contains(aValue, AtomArrayStringComparator()); 1318 } 1319 } 1320 } 1321 1322 return false; 1323 } 1324 1325 void nsAttrValue::ParseAtom(const nsAString& aValue) { 1326 ResetIfSet(); 1327 1328 RefPtr<nsAtom> atom = NS_Atomize(aValue); 1329 if (atom) { 1330 SetPtrValueAndType(atom.forget().take(), eAtomBase); 1331 } 1332 } 1333 1334 void nsAttrValue::ParseAtomArray(nsAtom* aValue) { 1335 if (MiscContainer* cont = AtomArrayCache::Lookup(aValue)) { 1336 // Set our MiscContainer to the cached one. 1337 NS_ADDREF(cont); 1338 SetPtrValueAndType(cont, eOtherBase); 1339 return; 1340 } 1341 1342 const char16_t* iter = aValue->GetUTF16String(); 1343 const char16_t* end = iter + aValue->GetLength(); 1344 bool hasSpace = false; 1345 1346 // skip initial whitespace 1347 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { 1348 hasSpace = true; 1349 ++iter; 1350 } 1351 1352 if (iter == end) { 1353 // The value is empty or only contains whitespace. 1354 // Set this attribute to the string value. 1355 // We don't call the SetTo(nsAtom*) overload because doing so would 1356 // leave us with a classList of length 1. 1357 SetTo(nsDependentAtomString(aValue)); 1358 return; 1359 } 1360 1361 const char16_t* start = iter; 1362 1363 // get first - and often only - atom 1364 do { 1365 ++iter; 1366 } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter)); 1367 1368 RefPtr<nsAtom> classAtom = iter == end && !hasSpace 1369 ? RefPtr<nsAtom>(aValue).forget() 1370 : NS_AtomizeMainThread(Substring(start, iter)); 1371 if (!classAtom) { 1372 ResetIfSet(); 1373 return; 1374 } 1375 1376 // skip whitespace 1377 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { 1378 hasSpace = true; 1379 ++iter; 1380 } 1381 1382 if (iter == end && !hasSpace) { 1383 // we only found one classname and there was no whitespace so 1384 // don't bother storing a list 1385 ResetIfSet(); 1386 nsAtom* atom = nullptr; 1387 classAtom.swap(atom); 1388 SetPtrValueAndType(atom, eAtomBase); 1389 return; 1390 } 1391 1392 // We have at least one class atom. Create a new AttrAtomArray. 1393 AttrAtomArray* array = new AttrAtomArray; 1394 1395 // XXX(Bug 1631371) Check if this should use a fallible operation as it 1396 // pretended earlier. 1397 array->mArray.AppendElement(std::move(classAtom)); 1398 1399 // parse the rest of the classnames 1400 while (iter != end) { 1401 start = iter; 1402 1403 do { 1404 ++iter; 1405 } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter)); 1406 1407 classAtom = NS_AtomizeMainThread(Substring(start, iter)); 1408 1409 // XXX(Bug 1631371) Check if this should use a fallible operation as it 1410 // pretended earlier. 1411 array->mArray.AppendElement(std::move(classAtom)); 1412 array->mMayContainDuplicates = true; 1413 1414 // skip whitespace 1415 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { 1416 ++iter; 1417 } 1418 } 1419 1420 // Wrap the AtomArray into a fresh MiscContainer. 1421 MiscContainer* cont = EnsureEmptyMiscContainer(); 1422 MOZ_ASSERT(cont->mValue.mRefCount == 0); 1423 cont->mValue.mAtomArray = array; 1424 cont->mType = eAtomArray; 1425 NS_ADDREF(cont); 1426 MOZ_ASSERT(cont->mValue.mRefCount == 1); 1427 1428 // Assign the atom to the container's string bits (like SetMiscAtomOrString 1429 // would do). 1430 MOZ_ASSERT(!IsInServoTraversal()); 1431 aValue->AddRef(); 1432 uintptr_t bits = reinterpret_cast<uintptr_t>(aValue) | eAtomBase; 1433 cont->SetStringBitsMainThread(bits); 1434 1435 // Put the container in the cache. 1436 cont->Cache(); 1437 } 1438 1439 void nsAttrValue::ParseAtomArray(const nsAString& aValue) { 1440 if (aValue.IsVoid()) { 1441 ResetIfSet(); 1442 } else { 1443 RefPtr<nsAtom> atom = NS_AtomizeMainThread(aValue); 1444 ParseAtomArray(atom); 1445 } 1446 } 1447 1448 void nsAttrValue::ParseStringOrAtom(const nsAString& aValue) { 1449 uint32_t len = aValue.Length(); 1450 // Don't bother with atoms if it's an empty string since 1451 // we can store those efficiently anyway. 1452 if (len && len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) { 1453 ParseAtom(aValue); 1454 } else { 1455 SetTo(aValue); 1456 } 1457 } 1458 1459 void nsAttrValue::ParsePartMapping(const nsAString& aValue) { 1460 ResetIfSet(); 1461 MiscContainer* cont = EnsureEmptyMiscContainer(); 1462 1463 cont->mType = eShadowParts; 1464 cont->mValue.mShadowParts = new ShadowParts(ShadowParts::Parse(aValue)); 1465 NS_ADDREF(cont); 1466 SetMiscAtomOrString(&aValue); 1467 MOZ_ASSERT(cont->mValue.mRefCount == 1); 1468 } 1469 1470 void nsAttrValue::SetIntValueAndType(int32_t aValue, ValueType aType, 1471 const nsAString* aStringValue) { 1472 if (aStringValue || aValue > NS_ATTRVALUE_INTEGERTYPE_MAXVALUE || 1473 aValue < NS_ATTRVALUE_INTEGERTYPE_MINVALUE) { 1474 MiscContainer* cont = EnsureEmptyMiscContainer(); 1475 switch (aType) { 1476 case eInteger: { 1477 cont->mValue.mInteger = aValue; 1478 break; 1479 } 1480 case ePercent: { 1481 cont->mDoubleValue = aValue; 1482 break; 1483 } 1484 case eEnum: { 1485 cont->mValue.mEnumValue = aValue; 1486 break; 1487 } 1488 default: { 1489 MOZ_ASSERT_UNREACHABLE("unknown integer type"); 1490 break; 1491 } 1492 } 1493 cont->mType = aType; 1494 SetMiscAtomOrString(aStringValue); 1495 } else { 1496 NS_ASSERTION(!mBits, "Reset before calling SetIntValueAndType!"); 1497 mBits = (aValue * NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER) | aType; 1498 } 1499 } 1500 1501 void nsAttrValue::SetDoubleValueAndType(double aValue, ValueType aType, 1502 const nsAString* aStringValue) { 1503 MOZ_ASSERT(aType == eDoubleValue || aType == ePercent, "Unexpected type"); 1504 MiscContainer* cont = EnsureEmptyMiscContainer(); 1505 cont->mDoubleValue = aValue; 1506 cont->mType = aType; 1507 SetMiscAtomOrString(aStringValue); 1508 } 1509 1510 nsAtom* nsAttrValue::GetStoredAtom() const { 1511 if (BaseType() == eAtomBase) { 1512 return static_cast<nsAtom*>(GetPtr()); 1513 } 1514 if (BaseType() == eOtherBase) { 1515 return GetMiscContainer()->GetStoredAtom(); 1516 } 1517 return nullptr; 1518 } 1519 1520 mozilla::StringBuffer* nsAttrValue::GetStoredStringBuffer() const { 1521 if (BaseType() == eStringBase) { 1522 return static_cast<mozilla::StringBuffer*>(GetPtr()); 1523 } 1524 if (BaseType() == eOtherBase) { 1525 return GetMiscContainer()->GetStoredStringBuffer(); 1526 } 1527 return nullptr; 1528 } 1529 1530 /** 1531 * Compares two `EnumTableItem` spans by comparing their data pointer. 1532 */ 1533 struct EnumTablesHaveEqualContent { 1534 bool Equals(const nsAttrValue::EnumTableSpan& aSpan1, 1535 const nsAttrValue::EnumTableSpan& aSpan2) const { 1536 return aSpan1.Elements() == aSpan2.Elements(); 1537 } 1538 }; 1539 1540 int16_t nsAttrValue::GetEnumTableIndex(EnumTableSpan aTable) { 1541 int16_t index = 1542 sEnumTableArray->IndexOf(aTable, 0, EnumTablesHaveEqualContent()); 1543 if (index < 0) { 1544 index = sEnumTableArray->Length(); 1545 NS_ASSERTION(index <= NS_ATTRVALUE_ENUMTABLEINDEX_MAXVALUE, 1546 "too many enum tables"); 1547 sEnumTableArray->AppendElement(aTable); 1548 } 1549 1550 return index; 1551 } 1552 1553 int32_t nsAttrValue::EnumTableEntryToValue(EnumTableSpan aEnumTable, 1554 const EnumTableEntry& aTableEntry) { 1555 int16_t index = GetEnumTableIndex(aEnumTable); 1556 int32_t value = 1557 (aTableEntry.value << NS_ATTRVALUE_ENUMTABLEINDEX_BITS) + index; 1558 return value; 1559 } 1560 1561 bool nsAttrValue::ParseEnumValue(const nsAString& aValue, EnumTableSpan aTable, 1562 bool aCaseSensitive, 1563 const EnumTableEntry* aDefaultValue) { 1564 ResetIfSet(); 1565 for (const auto& tableEntry : aTable) { 1566 if (aCaseSensitive ? aValue.EqualsASCII(tableEntry.tag) 1567 : aValue.LowerCaseEqualsASCII(tableEntry.tag)) { 1568 int32_t value = EnumTableEntryToValue(aTable, tableEntry); 1569 1570 bool equals = aCaseSensitive || aValue.EqualsASCII(tableEntry.tag); 1571 if (!equals) { 1572 nsAutoString tag; 1573 tag.AssignASCII(tableEntry.tag); 1574 nsContentUtils::ASCIIToUpper(tag); 1575 if ((equals = tag.Equals(aValue))) { 1576 value |= NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER; 1577 } 1578 } 1579 SetIntValueAndType(value, eEnum, equals ? nullptr : &aValue); 1580 NS_ASSERTION(GetEnumValue() == tableEntry.value, 1581 "failed to store enum properly"); 1582 1583 return true; 1584 } 1585 } 1586 1587 if (aDefaultValue) { 1588 SetIntValueAndType(EnumTableEntryToValue(aTable, *aDefaultValue), eEnum, 1589 &aValue); 1590 return true; 1591 } 1592 1593 return false; 1594 } 1595 1596 bool nsAttrValue::DoParseHTMLDimension(const nsAString& aInput, 1597 bool aEnsureNonzero) { 1598 ResetIfSet(); 1599 1600 // We don't use nsContentUtils::ParseHTMLInteger here because we 1601 // need a bunch of behavioral differences from it. We _could_ try to 1602 // use it, but it would not be a great fit. 1603 1604 // https://html.spec.whatwg.org/multipage/#rules-for-parsing-dimension-values 1605 1606 // Steps 1 and 2. 1607 const char16_t* position = aInput.BeginReading(); 1608 const char16_t* end = aInput.EndReading(); 1609 1610 // We will need to keep track of whether this was a canonical representation 1611 // or not. It's non-canonical if it has leading whitespace, leading '+', 1612 // leading '0' characters, or trailing garbage. 1613 bool canonical = true; 1614 1615 // Step 3. 1616 while (position != end && nsContentUtils::IsHTMLWhitespace(*position)) { 1617 canonical = false; // Leading whitespace 1618 ++position; 1619 } 1620 1621 // Step 4. 1622 if (position == end || *position < char16_t('0') || 1623 *position > char16_t('9')) { 1624 return false; 1625 } 1626 1627 // Step 5. 1628 CheckedInt32 value = 0; 1629 1630 // Collect up leading '0' first to avoid extra branching in the main 1631 // loop to set 'canonical' properly. 1632 while (position != end && *position == char16_t('0')) { 1633 canonical = false; // Leading '0' 1634 ++position; 1635 } 1636 1637 // Now collect up other digits. 1638 while (position != end && *position >= char16_t('0') && 1639 *position <= char16_t('9')) { 1640 value = value * 10 + (*position - char16_t('0')); 1641 if (!value.isValid()) { 1642 // The spec assumes we can deal with arbitrary-size integers here, but we 1643 // really can't. If someone sets something too big, just bail out and 1644 // ignore it. 1645 return false; 1646 } 1647 ++position; 1648 } 1649 1650 // Step 6 is implemented implicitly via the various "position != end" guards 1651 // from this point on. 1652 1653 Maybe<double> doubleValue; 1654 // Step 7. The return in step 7.2 is handled by just falling through to the 1655 // code below this block when we reach end of input or a non-digit, because 1656 // the while loop will terminate at that point. 1657 if (position != end && *position == char16_t('.')) { 1658 canonical = false; // Let's not rely on double serialization reproducing 1659 // the string we started with. 1660 // Step 7.1. 1661 ++position; 1662 // If we have a '.' _not_ followed by digits, this is not as efficient as it 1663 // could be, because we will store as a double while we could have stored as 1664 // an int. But that seems like a pretty rare case. 1665 doubleValue.emplace(value.value()); 1666 // Step 7.3. 1667 double divisor = 1.0f; 1668 // Step 7.4. 1669 while (position != end && *position >= char16_t('0') && 1670 *position <= char16_t('9')) { 1671 // Step 7.4.1. 1672 divisor = divisor * 10.0f; 1673 // Step 7.4.2. 1674 doubleValue.ref() += (*position - char16_t('0')) / divisor; 1675 // Step 7.4.3. 1676 ++position; 1677 // Step 7.4.4 and 7.4.5 are captured in the while loop condition and the 1678 // "position != end" checks below. 1679 } 1680 } 1681 1682 if (aEnsureNonzero && value.value() == 0 && 1683 (!doubleValue || *doubleValue == 0.0f)) { 1684 // Not valid. Just drop it. 1685 return false; 1686 } 1687 1688 // Step 8 and the spec's early return from step 7.2. 1689 ValueType type; 1690 if (position != end && *position == char16_t('%')) { 1691 type = ePercent; 1692 ++position; 1693 } else if (doubleValue) { 1694 type = eDoubleValue; 1695 } else { 1696 type = eInteger; 1697 } 1698 1699 if (position != end) { 1700 canonical = false; 1701 } 1702 1703 if (doubleValue) { 1704 MOZ_ASSERT(!canonical, "We set it false above!"); 1705 SetDoubleValueAndType(*doubleValue, type, &aInput); 1706 } else { 1707 SetIntValueAndType(value.value(), type, canonical ? nullptr : &aInput); 1708 } 1709 1710 #ifdef DEBUG 1711 nsAutoString str; 1712 ToString(str); 1713 MOZ_ASSERT(str == aInput, "We messed up our 'canonical' boolean!"); 1714 #endif 1715 1716 return true; 1717 } 1718 1719 bool nsAttrValue::ParseIntWithBounds(const nsAString& aString, int32_t aMin, 1720 int32_t aMax) { 1721 MOZ_ASSERT(aMin < aMax, "bad boundaries"); 1722 1723 ResetIfSet(); 1724 1725 nsContentUtils::ParseHTMLIntegerResultFlags result; 1726 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result); 1727 if (result & nsContentUtils::eParseHTMLInteger_Error) { 1728 return false; 1729 } 1730 1731 int32_t val = std::max(originalVal, aMin); 1732 val = std::min(val, aMax); 1733 bool nonStrict = 1734 (val != originalVal) || 1735 (result & nsContentUtils::eParseHTMLInteger_NonStandard) || 1736 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput); 1737 1738 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr); 1739 1740 return true; 1741 } 1742 1743 void nsAttrValue::ParseIntWithFallback(const nsAString& aString, 1744 int32_t aDefault, int32_t aMax) { 1745 ResetIfSet(); 1746 1747 nsContentUtils::ParseHTMLIntegerResultFlags result; 1748 int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result); 1749 bool nonStrict = false; 1750 if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 1) { 1751 val = aDefault; 1752 nonStrict = true; 1753 } 1754 1755 if (val > aMax) { 1756 val = aMax; 1757 nonStrict = true; 1758 } 1759 1760 if ((result & nsContentUtils::eParseHTMLInteger_NonStandard) || 1761 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput)) { 1762 nonStrict = true; 1763 } 1764 1765 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr); 1766 } 1767 1768 void nsAttrValue::ParseClampedNonNegativeInt(const nsAString& aString, 1769 int32_t aDefault, int32_t aMin, 1770 int32_t aMax) { 1771 ResetIfSet(); 1772 1773 nsContentUtils::ParseHTMLIntegerResultFlags result; 1774 int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result); 1775 bool nonStrict = 1776 (result & nsContentUtils::eParseHTMLInteger_NonStandard) || 1777 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput); 1778 1779 if (result & nsContentUtils::eParseHTMLInteger_ErrorOverflow) { 1780 if (result & nsContentUtils::eParseHTMLInteger_Negative) { 1781 val = aDefault; 1782 } else { 1783 val = aMax; 1784 } 1785 nonStrict = true; 1786 } else if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 0) { 1787 val = aDefault; 1788 nonStrict = true; 1789 } else if (val < aMin) { 1790 val = aMin; 1791 nonStrict = true; 1792 } else if (val > aMax) { 1793 val = aMax; 1794 nonStrict = true; 1795 } 1796 1797 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr); 1798 } 1799 1800 bool nsAttrValue::ParseNonNegativeIntValue(const nsAString& aString) { 1801 ResetIfSet(); 1802 1803 nsContentUtils::ParseHTMLIntegerResultFlags result; 1804 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result); 1805 if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal < 0) { 1806 return false; 1807 } 1808 1809 bool nonStrict = 1810 (result & nsContentUtils::eParseHTMLInteger_NonStandard) || 1811 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput); 1812 1813 SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr); 1814 1815 return true; 1816 } 1817 1818 bool nsAttrValue::ParsePositiveIntValue(const nsAString& aString) { 1819 ResetIfSet(); 1820 1821 nsContentUtils::ParseHTMLIntegerResultFlags result; 1822 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result); 1823 if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal <= 0) { 1824 return false; 1825 } 1826 1827 bool nonStrict = 1828 (result & nsContentUtils::eParseHTMLInteger_NonStandard) || 1829 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput); 1830 1831 SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr); 1832 1833 return true; 1834 } 1835 1836 bool nsAttrValue::SetColorValue(nscolor aColor, const nsAString& aString) { 1837 mozilla::StringBuffer* buf = GetStringBuffer(aString).take(); 1838 if (!buf) { 1839 return false; 1840 } 1841 1842 MiscContainer* cont = EnsureEmptyMiscContainer(); 1843 cont->mValue.mColor = aColor; 1844 cont->mType = eColor; 1845 1846 // Save the literal string we were passed for round-tripping. 1847 cont->SetStringBitsMainThread(reinterpret_cast<uintptr_t>(buf) | eStringBase); 1848 return true; 1849 } 1850 1851 bool nsAttrValue::ParseColor(const nsAString& aString) { 1852 ResetIfSet(); 1853 1854 // FIXME (partially, at least): HTML5's algorithm says we shouldn't do 1855 // the whitespace compression, trimming, or the test for emptiness. 1856 // (I'm a little skeptical that we shouldn't do the whitespace 1857 // trimming; WebKit also does it.) 1858 nsAutoString colorStr(aString); 1859 colorStr.CompressWhitespace(true, true); 1860 if (colorStr.IsEmpty()) { 1861 return false; 1862 } 1863 1864 nscolor color; 1865 // No color names begin with a '#'; in standards mode, all acceptable 1866 // numeric colors do. 1867 if (colorStr.First() == '#') { 1868 nsDependentString withoutHash(colorStr.get() + 1, colorStr.Length() - 1); 1869 if (NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) { 1870 return SetColorValue(color, aString); 1871 } 1872 } else if (colorStr.LowerCaseEqualsLiteral("transparent")) { 1873 return SetColorValue(NS_RGBA(0, 0, 0, 0), aString); 1874 } else { 1875 const NS_ConvertUTF16toUTF8 colorNameU8(colorStr); 1876 if (Servo_ColorNameToRgb(&colorNameU8, &color)) { 1877 return SetColorValue(color, aString); 1878 } 1879 } 1880 1881 // FIXME (maybe): HTML5 says we should handle system colors. This 1882 // means we probably need another storage type, since we'd need to 1883 // handle dynamic changes. However, I think this is a bad idea: 1884 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-May/026449.html 1885 1886 // Use NS_LooseHexToRGB as a fallback if nothing above worked. 1887 if (NS_LooseHexToRGB(colorStr, &color)) { 1888 return SetColorValue(color, aString); 1889 } 1890 1891 return false; 1892 } 1893 1894 bool nsAttrValue::ParseDoubleValue(const nsAString& aString) { 1895 ResetIfSet(); 1896 1897 nsresult ec; 1898 double val = PromiseFlatString(aString).ToDouble(&ec); 1899 if (NS_FAILED(ec)) { 1900 return false; 1901 } 1902 1903 MiscContainer* cont = EnsureEmptyMiscContainer(); 1904 cont->mDoubleValue = val; 1905 cont->mType = eDoubleValue; 1906 nsAutoString serializedFloat; 1907 serializedFloat.AppendFloat(val); 1908 SetMiscAtomOrString(serializedFloat.Equals(aString) ? nullptr : &aString); 1909 return true; 1910 } 1911 1912 bool nsAttrValue::ParseStyleAttribute(const nsAString& aString, 1913 nsIPrincipal* aMaybeScriptedPrincipal, 1914 nsStyledElement* aElement) { 1915 dom::Document* doc = aElement->OwnerDoc(); 1916 AttributeStyles* attrStyles = doc->GetAttributeStyles(); 1917 NS_ASSERTION(aElement->NodePrincipal() == doc->NodePrincipal(), 1918 "This is unexpected"); 1919 1920 nsIPrincipal* principal = aMaybeScriptedPrincipal ? aMaybeScriptedPrincipal 1921 : aElement->NodePrincipal(); 1922 RefPtr<URLExtraData> data = aElement->GetURLDataForStyleAttr(principal); 1923 1924 // If the (immutable) document URI does not match the element's base URI 1925 // (the common case is that they do match) do not cache the rule. This is 1926 // because the results of the CSS parser are dependent on these URIs, and we 1927 // do not want to have to account for the URIs in the hash lookup. 1928 // Similarly, if the triggering principal does not match the node principal, 1929 // do not cache the rule, since the principal will be encoded in any parsed 1930 // URLs in the rule. 1931 const bool cachingAllowed = attrStyles && 1932 doc->GetDocumentURI() == data->BaseURI() && 1933 principal == aElement->NodePrincipal(); 1934 if (cachingAllowed) { 1935 if (MiscContainer* cont = attrStyles->LookupStyleAttr(aString)) { 1936 // Set our MiscContainer to the cached one. 1937 NS_ADDREF(cont); 1938 SetPtrValueAndType(cont, eOtherBase); 1939 return true; 1940 } 1941 } 1942 1943 RefPtr<DeclarationBlock> decl = DeclarationBlock::FromCssText( 1944 aString, data, doc->GetCompatibilityMode(), doc->GetExistingCSSLoader(), 1945 StyleCssRuleType::Style); 1946 if (!decl) { 1947 return false; 1948 } 1949 decl->SetAttributeStyles(attrStyles); 1950 SetTo(decl.forget(), &aString); 1951 1952 if (cachingAllowed) { 1953 MiscContainer* cont = GetMiscContainer(); 1954 cont->Cache(); 1955 } 1956 1957 return true; 1958 } 1959 1960 void nsAttrValue::SetMiscAtomOrString(const nsAString* aValue) { 1961 NS_ASSERTION(GetMiscContainer(), "Must have MiscContainer!"); 1962 NS_ASSERTION(!GetMiscContainer()->mStringBits || IsInServoTraversal(), 1963 "Trying to re-set atom or string!"); 1964 if (aValue) { 1965 uint32_t len = aValue->Length(); 1966 // * We're allowing eCSSDeclaration attributes to store empty 1967 // strings as it can be beneficial to store an empty style 1968 // attribute as a parsed rule. 1969 // * We're allowing enumerated values because sometimes the empty 1970 // string corresponds to a particular enumerated value, especially 1971 // for enumerated values that are not limited enumerated. 1972 // Add other types as needed. 1973 NS_ASSERTION(len || Type() == eCSSDeclaration || Type() == eEnum, 1974 "Empty string?"); 1975 MiscContainer* cont = GetMiscContainer(); 1976 1977 if (len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) { 1978 nsAtom* atom = MOZ_LIKELY(!IsInServoTraversal()) 1979 ? NS_AtomizeMainThread(*aValue).take() 1980 : NS_Atomize(*aValue).take(); 1981 NS_ENSURE_TRUE_VOID(atom); 1982 uintptr_t bits = reinterpret_cast<uintptr_t>(atom) | eAtomBase; 1983 1984 // In the common case we're not in the servo traversal, and we can just 1985 // set the bits normally. The parallel case requires more care. 1986 if (MOZ_LIKELY(!IsInServoTraversal())) { 1987 cont->SetStringBitsMainThread(bits); 1988 } else if (!cont->mStringBits.compareExchange(0, bits)) { 1989 // We raced with somebody else setting the bits. Release our copy. 1990 atom->Release(); 1991 } 1992 } else { 1993 mozilla::StringBuffer* buffer = GetStringBuffer(*aValue).take(); 1994 NS_ENSURE_TRUE_VOID(buffer); 1995 uintptr_t bits = reinterpret_cast<uintptr_t>(buffer) | eStringBase; 1996 1997 // In the common case we're not in the servo traversal, and we can just 1998 // set the bits normally. The parallel case requires more care. 1999 if (MOZ_LIKELY(!IsInServoTraversal())) { 2000 cont->SetStringBitsMainThread(bits); 2001 } else if (!cont->mStringBits.compareExchange(0, bits)) { 2002 // We raced with somebody else setting the bits. Release our copy. 2003 buffer->Release(); 2004 } 2005 } 2006 } 2007 } 2008 2009 void nsAttrValue::ResetMiscAtomOrString() { 2010 MiscContainer* cont = GetMiscContainer(); 2011 bool isString; 2012 if (void* ptr = cont->GetStringOrAtomPtr(isString)) { 2013 if (isString) { 2014 static_cast<mozilla::StringBuffer*>(ptr)->Release(); 2015 } else { 2016 static_cast<nsAtom*>(ptr)->Release(); 2017 } 2018 cont->SetStringBitsMainThread(0); 2019 } 2020 } 2021 2022 void nsAttrValue::SetSVGType(ValueType aType, const void* aValue, 2023 const nsAString* aSerialized) { 2024 MOZ_ASSERT(IsSVGType(aType), "Not an SVG type"); 2025 2026 MiscContainer* cont = EnsureEmptyMiscContainer(); 2027 // All SVG types are just pointers to classes so just setting any of them 2028 // will do. We'll lose type-safety but the signature of the calling 2029 // function should ensure we don't get anything unexpected, and once we 2030 // stick aValue in a union we lose type information anyway. 2031 cont->mValue.mSVGLength = static_cast<const SVGAnimatedLength*>(aValue); 2032 cont->mType = aType; 2033 SetMiscAtomOrString(aSerialized); 2034 } 2035 2036 MiscContainer* nsAttrValue::ClearMiscContainer() { 2037 MiscContainer* cont = nullptr; 2038 if (BaseType() == eOtherBase) { 2039 cont = GetMiscContainer(); 2040 if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) { 2041 // This MiscContainer is shared, we need a new one. 2042 NS_RELEASE(cont); 2043 2044 cont = AllocMiscContainer(); 2045 SetPtrValueAndType(cont, eOtherBase); 2046 } else { 2047 switch (cont->mType) { 2048 case eCSSDeclaration: { 2049 MOZ_ASSERT(cont->mValue.mRefCount == 1); 2050 cont->Release(); 2051 cont->Evict(); 2052 NS_RELEASE(cont->mValue.mCSSDeclaration); 2053 break; 2054 } 2055 case eShadowParts: { 2056 MOZ_ASSERT(cont->mValue.mRefCount == 1); 2057 cont->Release(); 2058 delete cont->mValue.mShadowParts; 2059 break; 2060 } 2061 case eURL: { 2062 NS_RELEASE(cont->mValue.mURL); 2063 break; 2064 } 2065 case eAtomArray: { 2066 MOZ_ASSERT(cont->mValue.mRefCount == 1); 2067 cont->Release(); 2068 cont->Evict(); 2069 delete cont->mValue.mAtomArray; 2070 break; 2071 } 2072 default: { 2073 break; 2074 } 2075 } 2076 } 2077 ResetMiscAtomOrString(); 2078 } else { 2079 ResetIfSet(); 2080 } 2081 2082 return cont; 2083 } 2084 2085 MiscContainer* nsAttrValue::EnsureEmptyMiscContainer() { 2086 MiscContainer* cont = ClearMiscContainer(); 2087 if (cont) { 2088 MOZ_ASSERT(BaseType() == eOtherBase); 2089 ResetMiscAtomOrString(); 2090 cont = GetMiscContainer(); 2091 } else { 2092 cont = AllocMiscContainer(); 2093 SetPtrValueAndType(cont, eOtherBase); 2094 } 2095 2096 return cont; 2097 } 2098 2099 already_AddRefed<mozilla::StringBuffer> nsAttrValue::GetStringBuffer( 2100 const nsAString& aValue) const { 2101 uint32_t len = aValue.Length(); 2102 if (!len) { 2103 return nullptr; 2104 } 2105 if (mozilla::StringBuffer* buf = aValue.GetStringBuffer(); 2106 buf && (buf->StorageSize() / sizeof(char16_t) - 1) == len) { 2107 // We can only reuse the buffer if it's exactly sized, since we rely on 2108 // StorageSize() to get the string length in ToString(). 2109 return do_AddRef(buf); 2110 } 2111 return mozilla::StringBuffer::Create(aValue.Data(), aValue.Length()); 2112 } 2113 2114 size_t nsAttrValue::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { 2115 size_t n = 0; 2116 2117 switch (BaseType()) { 2118 case eStringBase: { 2119 mozilla::StringBuffer* str = 2120 static_cast<mozilla::StringBuffer*>(GetPtr()); 2121 n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0; 2122 break; 2123 } 2124 case eOtherBase: { 2125 MiscContainer* container = GetMiscContainer(); 2126 if (!container) { 2127 break; 2128 } 2129 if (container->IsRefCounted() && container->mValue.mRefCount > 1) { 2130 // We don't report this MiscContainer at all in order to avoid 2131 // twice-reporting it. 2132 // TODO DMD, bug 1027551 - figure out how to report this ref-counted 2133 // object just once. 2134 break; 2135 } 2136 n += aMallocSizeOf(container); 2137 2138 // We only count the size of the object pointed by otherPtr if it's a 2139 // string. When it's an atom, it's counted separately. 2140 if (mozilla::StringBuffer* buf = container->GetStoredStringBuffer()) { 2141 n += buf->SizeOfIncludingThisIfUnshared(aMallocSizeOf); 2142 } 2143 2144 if (Type() == eCSSDeclaration && container->mValue.mCSSDeclaration) { 2145 // TODO: mCSSDeclaration might be owned by another object which 2146 // would make us count them twice, bug 677493. 2147 // Bug 1281964: For DeclarationBlock if we do measure we'll 2148 // need a way to call the Servo heap_size_of function. 2149 // n += container->mCSSDeclaration->SizeOfIncludingThis(aMallocSizeOf); 2150 } else if (Type() == eAtomArray && container->mValue.mAtomArray) { 2151 // Don't measure each nsAtom, because they are measured separately. 2152 n += container->mValue.mAtomArray->ShallowSizeOfIncludingThis( 2153 aMallocSizeOf); 2154 } 2155 break; 2156 } 2157 case eAtomBase: // Atoms are counted separately. 2158 case eIntegerBase: // The value is in mBits, nothing to do. 2159 break; 2160 } 2161 2162 return n; 2163 }