AttrArray.h (13057B)
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 * Storage of the attributes of a DOM node. 9 */ 10 11 #ifndef AttrArray_h___ 12 #define AttrArray_h___ 13 14 #include "mozilla/BindgenUniquePtr.h" 15 #include "mozilla/MemoryReporting.h" 16 #include "mozilla/Span.h" 17 #include "mozilla/dom/BorrowedAttrInfo.h" 18 #include "nsAttrName.h" 19 #include "nsAttrValue.h" 20 #include "nsCaseTreatment.h" 21 #include "nscore.h" 22 23 namespace mozilla { 24 class AttributeStyles; 25 struct StyleLockedDeclarationBlock; 26 27 namespace dom { 28 class Element; 29 class ElementInternals; 30 } // namespace dom 31 } // namespace mozilla 32 33 class AttrArray { 34 using BorrowedAttrInfo = mozilla::dom::BorrowedAttrInfo; 35 36 // Declare as friend to grant access to call SetAndSwapAttr. 37 friend class mozilla::dom::Element; 38 friend class mozilla::dom::ElementInternals; 39 40 public: 41 AttrArray() { 42 // Initialize bloom filter. 43 SetTaggedBloom(0x1ULL); 44 } 45 ~AttrArray() { 46 // If mImpl contains a tagged bloom filter (bit 0 = 1), we must clear it 47 // before the BindgenUniquePtr destructor tries to free it as a pointer. 48 if (HasTaggedBloom()) { 49 mImpl.release(); 50 } 51 } 52 53 bool HasAttrs() const { return !!AttrCount(); } 54 55 uint32_t AttrCount() const { return HasImpl() ? GetImpl()->mAttrCount : 0; } 56 57 const nsAttrValue* GetAttr(const nsAtom* aLocalName) const; 58 59 const nsAttrValue* GetAttr(const nsAtom* aLocalName, 60 int32_t aNamespaceID) const; 61 // As above but using a string attr name and always using 62 // kNameSpaceID_None. This is always case-sensitive. 63 const nsAttrValue* GetAttr(const nsAString& aName) const; 64 // Get an nsAttrValue by qualified name. Can optionally do 65 // ASCII-case-insensitive name matching. 66 const nsAttrValue* GetAttr(const nsAString& aName, 67 nsCaseTreatment aCaseSensitive) const; 68 const nsAttrValue* AttrAt(uint32_t aPos) const; 69 70 // This stores the argument and clears the pending mapped attribute evaluation 71 // bit, so after calling this IsPendingMappedAttributeEvaluation() is 72 // guaranteed to return false. 73 void SetMappedDeclarationBlock( 74 already_AddRefed<mozilla::StyleLockedDeclarationBlock>); 75 76 bool IsPendingMappedAttributeEvaluation() const { 77 return HasImpl() && GetImpl()->mMappedAttributeBits & 1; 78 } 79 80 mozilla::StyleLockedDeclarationBlock* GetMappedDeclarationBlock() const { 81 return HasImpl() ? GetImpl()->GetMappedDeclarationBlock() : nullptr; 82 } 83 84 // Remove the attr at position aPos. The value of the attr is placed in 85 // aValue; any value that was already in aValue is destroyed. 86 nsresult RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue); 87 88 // Returns attribute name at given position, *not* out-of-bounds safe 89 const nsAttrName* AttrNameAt(uint32_t aPos) const; 90 91 // Returns the attribute info at a given position, *not* out-of-bounds safe 92 BorrowedAttrInfo AttrInfoAt(uint32_t aPos) const; 93 94 // If aPos is in bounds, set aResult to the attribute at the given position 95 // without AddRefing it and return true. Otherwise, return false. 96 [[nodiscard]] bool GetSafeAttrNameAt(uint32_t aPos, 97 const nsAttrName** aResult) const; 98 99 // If aPos is in bounds, return the attribute at the given position. 100 // Otherwise, crash. 101 const nsAttrName* GetSafeAttrNameAt(uint32_t aPos) const; 102 103 const nsAttrName* GetExistingAttrNameFromQName(const nsAString& aName) const; 104 int32_t IndexOfAttr(const nsAtom* aLocalName) const; 105 int32_t IndexOfAttr(const nsAtom* aLocalName, int32_t aNamespaceID) const; 106 107 void Compact(); 108 109 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; 110 111 // Mark the element as pending mapped attribute evaluation. This should be 112 // called when a mapped attribute is changed (regardless of connectedness). 113 bool MarkAsPendingPresAttributeEvaluation() { 114 // It'd be great to be able to assert that HasImpl() is true or we're the 115 // <body> or <svg> elements. 116 if (MOZ_UNLIKELY(!HasImpl()) && !GrowBy(1)) { 117 return false; 118 } 119 InfallibleMarkAsPendingPresAttributeEvaluation(); 120 return true; 121 } 122 123 // See above. 124 void InfallibleMarkAsPendingPresAttributeEvaluation() { 125 MOZ_ASSERT(HasImpl()); 126 GetImpl()->mMappedAttributeBits |= 1; 127 } 128 129 // Clear the servo declaration block on the mapped attributes, if any 130 // Will assert off main thread 131 void ClearMappedServoStyle(); 132 133 // Increases capacity (if necessary) to have enough space to accomodate the 134 // unmapped attributes of |aOther|. 135 nsresult EnsureCapacityToClone(const AttrArray& aOther); 136 137 enum AttrValuesState { ATTR_MISSING = -1, ATTR_VALUE_NO_MATCH = -2 }; 138 using AttrValuesArray = nsStaticAtom* const; 139 int32_t FindAttrValueIn(int32_t aNameSpaceID, const nsAtom* aName, 140 AttrValuesArray* aValues, 141 nsCaseTreatment aCaseSensitive) const; 142 143 inline bool GetAttr(int32_t aNameSpaceID, const nsAtom* aName, 144 nsAString& aResult) const { 145 MOZ_ASSERT(aResult.IsEmpty(), "Should have empty string coming in"); 146 const nsAttrValue* val = GetAttr(aName, aNameSpaceID); 147 if (!val) { 148 return false; 149 } 150 val->ToString(aResult); 151 return true; 152 } 153 154 inline bool GetAttr(const nsAtom* aName, nsAString& aResult) const { 155 MOZ_ASSERT(aResult.IsEmpty(), "Should have empty string coming in"); 156 const nsAttrValue* val = GetAttr(aName); 157 if (!val) { 158 return false; 159 } 160 val->ToString(aResult); 161 return true; 162 } 163 164 inline bool HasAttr(const nsAtom* aName) const { return !!GetAttr(aName); } 165 166 inline bool HasAttr(int32_t aNameSpaceID, const nsAtom* aName) const { 167 return !!GetAttr(aName, aNameSpaceID); 168 } 169 170 inline bool AttrValueIs(int32_t aNameSpaceID, const nsAtom* aName, 171 const nsAString& aValue, 172 nsCaseTreatment aCaseSensitive) const { 173 NS_ASSERTION(aName, "Must have attr name"); 174 NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown, "Must have namespace"); 175 const nsAttrValue* val = GetAttr(aName, aNameSpaceID); 176 return val && val->Equals(aValue, aCaseSensitive); 177 } 178 179 inline bool AttrValueIs(int32_t aNameSpaceID, const nsAtom* aName, 180 const nsAtom* aValue, 181 nsCaseTreatment aCaseSensitive) const { 182 NS_ASSERTION(aName, "Must have attr name"); 183 NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown, "Must have namespace"); 184 NS_ASSERTION(aValue, "Null value atom"); 185 186 const nsAttrValue* val = GetAttr(aName, aNameSpaceID); 187 return val && val->Equals(aValue, aCaseSensitive); 188 } 189 190 struct InternalAttr { 191 nsAttrName mName; 192 nsAttrValue mValue; 193 }; 194 195 AttrArray(const AttrArray& aOther) = delete; 196 AttrArray& operator=(const AttrArray& aOther) = delete; 197 198 bool GrowBy(uint32_t aGrowSize); 199 bool GrowTo(uint32_t aCapacity); 200 201 void Clear() { 202 // If mImpl contains a tagged bloom filter, release it first to prevent 203 // unique_ptr from trying to delete it as a pointer 204 if (HasTaggedBloom()) { 205 mImpl.release(); 206 } else { 207 mImpl.reset(); 208 } 209 // Reinitialize to default tagged bloom filter 210 SetTaggedBloom(0x1ULL); 211 } 212 213 private: 214 // Tries to create an attribute, growing the buffer if needed, with the given 215 // name and value. 216 // 217 // The value is moved from the argument. 218 // 219 // `Name` can be anything you construct a `nsAttrName` with (either an atom or 220 // a NodeInfo pointer). 221 template <typename Name> 222 nsresult AddNewAttribute(Name*, nsAttrValue&); 223 224 class Impl { 225 public: 226 constexpr static size_t AllocationSizeForAttributes(uint32_t aAttrCount) { 227 return sizeof(Impl) + aAttrCount * sizeof(InternalAttr); 228 } 229 230 mozilla::StyleLockedDeclarationBlock* GetMappedDeclarationBlock() const { 231 return reinterpret_cast<mozilla::StyleLockedDeclarationBlock*>( 232 mMappedAttributeBits & ~uintptr_t(1)); 233 } 234 235 auto Attrs() const { 236 return mozilla::Span<const InternalAttr>{mBuffer, mAttrCount}; 237 } 238 239 auto Attrs() { return mozilla::Span<InternalAttr>{mBuffer, mAttrCount}; } 240 241 Impl(const Impl&) = delete; 242 Impl(Impl&&) = delete; 243 ~Impl(); 244 245 uint32_t mAttrCount; 246 uint32_t mCapacity; // In number of InternalAttrs 247 248 // mMappedAttributeBits is a tagged pointer of a 249 // StyleLockedDeclarationBlock, which holds the style information that our 250 // attributes map to. 251 // 252 // If the lower bit is set, then our mapped attributes are dirty. This just 253 // means that we might have mapped attributes (or used to and no longer 254 // have), and are pending an update to recompute our declaration. 255 uintptr_t mMappedAttributeBits = 0; 256 257 // Combined bloom filter (63 bits) for both classes and attributes 258 // Bit 0: Always 1 to match tagged pointer implementation. 259 // Bits 1-63: Combined bloom filter (63 bits) 260 uint64_t mSubtreeBloomFilter; 261 262 public: 263 Impl() : mSubtreeBloomFilter(0xFFFFFFFFFFFFFFFFULL) {} 264 265 // Allocated in the same buffer as `Impl`. 266 InternalAttr mBuffer[0]; 267 }; 268 269 mozilla::Span<InternalAttr> Attrs() { 270 return HasImpl() ? GetImpl()->Attrs() : mozilla::Span<InternalAttr>(); 271 } 272 273 mozilla::Span<const InternalAttr> Attrs() const { 274 return HasImpl() ? GetImpl()->Attrs() : mozilla::Span<const InternalAttr>(); 275 } 276 277 bool HasTaggedBloom() const { 278 return (reinterpret_cast<uintptr_t>(mImpl.get()) & 1) != 0; 279 } 280 281 bool HasImpl() const { 282 MOZ_ASSERT(mImpl.get() != nullptr); 283 return !HasTaggedBloom(); 284 } 285 286 Impl* GetImpl() { 287 MOZ_ASSERT(HasImpl()); 288 return mImpl.get(); 289 } 290 291 const Impl* GetImpl() const { 292 MOZ_ASSERT(HasImpl()); 293 return mImpl.get(); 294 } 295 296 uint64_t GetTaggedBloom() const { 297 MOZ_ASSERT(HasTaggedBloom()); 298 return reinterpret_cast<uint64_t>(mImpl.get()); 299 } 300 301 void SetTaggedBloom(uint64_t aBloom) { 302 // If we already have a tagged bloom, release it first 303 if (HasTaggedBloom()) { 304 mImpl.release(); 305 } 306 // Ensure bit 0 is set (tag bit) and upper bit is clear (valid pointer 307 // range) 308 MOZ_ASSERT((aBloom & 1) != 0); 309 mImpl.reset(reinterpret_cast<Impl*>(static_cast<uintptr_t>(aBloom))); 310 } 311 312 void SetImpl(Impl* aImpl) { 313 MOZ_ASSERT(aImpl != nullptr && 314 (reinterpret_cast<uintptr_t>(aImpl) & 1) == 0); 315 // If we currently have a tagged bloom filter, release it first to prevent 316 // unique_ptr from trying to delete it as a pointer 317 if (HasTaggedBloom()) { 318 mImpl.release(); 319 } 320 mImpl.reset(aImpl); 321 } 322 323 public: 324 // Set bloom filter directly from 64-bit value 325 void SetSubtreeBloomFilter(uint64_t aBloom) { 326 if (HasImpl()) { 327 GetImpl()->mSubtreeBloomFilter = aBloom; 328 } else { 329 SetTaggedBloom(aBloom); 330 } 331 } 332 333 // Get bloom filter, used for fast querySelector 334 uint64_t GetSubtreeBloomFilter() const { 335 if (HasImpl()) { 336 return GetImpl()->mSubtreeBloomFilter; 337 } else if (HasTaggedBloom()) { 338 return GetTaggedBloom(); 339 } 340 MOZ_ASSERT_UNREACHABLE("Bloom filter should never be nullptr"); 341 return 0xFFFFFFFFFFFFFFFFULL; 342 } 343 344 // Update bloom filter with new 64-bit hash 345 void UpdateSubtreeBloomFilter(uint64_t aHash) { 346 if (HasImpl()) { 347 GetImpl()->mSubtreeBloomFilter |= aHash; 348 } else { 349 uint64_t current = GetSubtreeBloomFilter(); 350 SetTaggedBloom(current | aHash); 351 } 352 } 353 354 // Check if bloom may contain the given hash 355 bool BloomMayHave(uint64_t aHash) const { 356 uint64_t bloom = GetSubtreeBloomFilter(); 357 return (bloom & aHash) == aHash; 358 } 359 360 private: 361 // Internal method that swaps the current attribute value with aValue. 362 // Does NOT update bloom filters - external code should always use 363 // Element::SetAndSwapAttr instead, which calls this and updates bloom 364 // filters. If the attribute was unset, an empty value will be swapped into 365 // aValue and aHadValue will be set to false. Otherwise, aHadValue will be set 366 // to true. 367 nsresult SetAndSwapAttr(nsAtom* aLocalName, nsAttrValue& aValue, 368 bool* aHadValue); 369 nsresult SetAndSwapAttr(mozilla::dom::NodeInfo* aName, nsAttrValue& aValue, 370 bool* aHadValue); 371 372 // mImpl serves dual purposes using pointer tagging: 373 // 374 // 1. When the element has no attributes: 375 // - Stores a "tagged bloom filter" (bit 0 is 1). The remaining 376 // bits are used as a bloomfilter to identify the existence of 377 // attribute & class names in this subtree for querySelector. 378 // 379 // 2. When the element has attributes: 380 // - Points to an Impl struct (bit 0 is 0). 381 // - Contains actual attribute storage and the bloom filter 382 // is now copied as a member of Impl. 383 // 384 // Use HasTaggedBloom() vs HasImpl() to distinguish. 385 mozilla::BindgenUniquePtr<Impl> mImpl; 386 }; 387 388 #endif