FrameProperties.h (15028B)
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 #ifndef FRAMEPROPERTIES_H_ 8 #define FRAMEPROPERTIES_H_ 9 10 #include "mozilla/MemoryReporting.h" 11 #include "nsTArray.h" 12 #include "nsThreadUtils.h" 13 14 class nsIFrame; 15 16 namespace mozilla { 17 18 struct FramePropertyDescriptorUntyped { 19 /** 20 * mDestructor will be called if it's non-null. 21 */ 22 typedef void UntypedDestructor(void* aPropertyValue); 23 UntypedDestructor* mDestructor; 24 /** 25 * mDestructorWithFrame will be called if it's non-null and mDestructor 26 * is null. WARNING: The frame passed to mDestructorWithFrame may 27 * be a dangling frame pointer, if this is being called during 28 * presshell teardown. Do not use it except to compare against 29 * other frame pointers. No frame will have been allocated with 30 * the same address yet. 31 */ 32 typedef void UntypedDestructorWithFrame(const nsIFrame* aFrame, 33 void* aPropertyValue); 34 UntypedDestructorWithFrame* mDestructorWithFrame; 35 /** 36 * mDestructor and mDestructorWithFrame may both be null, in which case 37 * no value destruction is a no-op. 38 */ 39 40 protected: 41 /** 42 * At most one destructor should be passed in. In general, you should 43 * just use the static function FramePropertyDescriptor::New* below 44 * instead of using this constructor directly. 45 */ 46 constexpr FramePropertyDescriptorUntyped( 47 UntypedDestructor* aDtor, UntypedDestructorWithFrame* aDtorWithFrame) 48 : mDestructor(aDtor), mDestructorWithFrame(aDtorWithFrame) {} 49 }; 50 51 /** 52 * A pointer to a FramePropertyDescriptor serves as a unique property ID. 53 * The FramePropertyDescriptor stores metadata about the property. 54 * Currently the only metadata is a destructor function. The destructor 55 * function is called on property values when they are overwritten or 56 * deleted. 57 * 58 * To use this class, declare a global (i.e., file, class or function-scope 59 * static member) FramePropertyDescriptor and pass its address as 60 * aProperty in the FrameProperties methods. 61 */ 62 template <typename T> 63 struct FramePropertyDescriptor : public FramePropertyDescriptorUntyped { 64 typedef void Destructor(T* aPropertyValue); 65 typedef void DestructorWithFrame(const nsIFrame* aFrame, T* aPropertyValue); 66 67 template <Destructor Dtor> 68 static constexpr const FramePropertyDescriptor<T> NewWithDestructor() { 69 return {Destruct<Dtor>, nullptr}; 70 } 71 72 template <DestructorWithFrame Dtor> 73 static constexpr const FramePropertyDescriptor<T> 74 NewWithDestructorWithFrame() { 75 return {nullptr, DestructWithFrame<Dtor>}; 76 } 77 78 static constexpr const FramePropertyDescriptor<T> NewWithoutDestructor() { 79 return {nullptr, nullptr}; 80 } 81 82 private: 83 constexpr FramePropertyDescriptor(UntypedDestructor* aDtor, 84 UntypedDestructorWithFrame* aDtorWithFrame) 85 : FramePropertyDescriptorUntyped(aDtor, aDtorWithFrame) {} 86 87 template <Destructor Dtor> 88 static void Destruct(void* aPropertyValue) { 89 Dtor(static_cast<T*>(aPropertyValue)); 90 } 91 92 template <DestructorWithFrame Dtor> 93 static void DestructWithFrame(const nsIFrame* aFrame, void* aPropertyValue) { 94 Dtor(aFrame, static_cast<T*>(aPropertyValue)); 95 } 96 }; 97 98 // SmallValueHolder<T> is a placeholder intended to be used as template 99 // argument of FramePropertyDescriptor for types which can fit directly into our 100 // internal value slot (i.e. types that can fit in 64 bits). This class should 101 // never be defined, so that we won't use it for unexpected purpose by mistake. 102 template <typename T> 103 class SmallValueHolder; 104 105 namespace detail { 106 107 template <typename T> 108 struct FramePropertyTypeHelper { 109 typedef T* Type; 110 }; 111 template <typename T> 112 struct FramePropertyTypeHelper<SmallValueHolder<T>> { 113 typedef T Type; 114 }; 115 116 } // namespace detail 117 118 /** 119 * The FrameProperties class is optimized for storing 0 or 1 properties on 120 * a given frame. Storing very large numbers of properties on a single 121 * frame will not be efficient. 122 */ 123 class FrameProperties { 124 public: 125 template <typename T> 126 using Descriptor = const FramePropertyDescriptor<T>*; 127 using UntypedDescriptor = const FramePropertyDescriptorUntyped*; 128 129 template <typename T> 130 using PropertyType = typename detail::FramePropertyTypeHelper<T>::Type; 131 132 explicit FrameProperties() = default; 133 134 ~FrameProperties() { 135 MOZ_ASSERT(mProperties.Length() == 0, "forgot to delete properties"); 136 } 137 138 /** 139 * Return true if we have no properties, otherwise return false. 140 */ 141 bool IsEmpty() const { return mProperties.IsEmpty(); } 142 143 /** 144 * Set a property value. This requires a linear search through 145 * the properties of the frame. Any existing value for the property 146 * is destroyed. 147 */ 148 template <typename T> 149 void Set(Descriptor<T> aProperty, PropertyType<T> aValue, 150 const nsIFrame* aFrame) { 151 uint64_t v = ReinterpretHelper<T>::ToInternalValue(aValue); 152 SetInternal(aProperty, v, aFrame); 153 } 154 155 /** 156 * Add a property value; the descriptor MUST NOT already be present. 157 */ 158 template <typename T> 159 void Add(Descriptor<T> aProperty, PropertyType<T> aValue) { 160 MOZ_ASSERT(!Has(aProperty), "duplicate frame property"); 161 uint64_t v = ReinterpretHelper<T>::ToInternalValue(aValue); 162 AddInternal(aProperty, v); 163 } 164 165 /** 166 * @return true if @aProperty is set. This requires a linear search through 167 * the properties of the frame. 168 * 169 * In most cases, this shouldn't be used outside of assertions, because if 170 * you're doing a lookup anyway it would be far more efficient to call Get() 171 * or Take() and check the aFoundResult outparam to find out whether the 172 * property is set. Legitimate non-assertion uses include: 173 * 174 * - Checking if a frame property is set in cases where that's all we want 175 * to know (i.e., we don't intend to read the actual value or remove the 176 * property). 177 * 178 * - Calling Has() before Set() in cases where we don't want to overwrite 179 * an existing value for the frame property. 180 */ 181 template <typename T> 182 bool Has(Descriptor<T> aProperty) const { 183 return mProperties.Contains(aProperty, PropertyComparator()); 184 } 185 186 /** 187 * Get a property value. This requires a linear search through 188 * the properties of the frame. If the frame has no such property, 189 * returns zero-filled result, which means null for pointers and 190 * zero for integers and floating point types. 191 * @param aFoundResult if non-null, receives a value 'true' iff 192 * the frame has a value for the property. This lets callers 193 * disambiguate a null result, which can mean 'no such property' or 194 * 'property value is null'. 195 */ 196 template <typename T> 197 PropertyType<T> Get(Descriptor<T> aProperty, 198 bool* aFoundResult = nullptr) const { 199 uint64_t v = GetInternal(aProperty, aFoundResult); 200 return ReinterpretHelper<T>::FromInternalValue(v); 201 } 202 203 /** 204 * Remove a property value, and return it without destroying it. 205 * 206 * This requires a linear search through the properties of the frame. 207 * If the frame has no such property, returns zero-filled result, which means 208 * null for pointers and zero for integers and floating point types. 209 * @param aFoundResult if non-null, receives a value 'true' iff 210 * the frame had a value for the property. This lets callers 211 * disambiguate a null result, which can mean 'no such property' or 212 * 'property value is null'. 213 */ 214 template <typename T> 215 PropertyType<T> Take(Descriptor<T> aProperty, bool* aFoundResult = nullptr) { 216 uint64_t v = TakeInternal(aProperty, aFoundResult); 217 return ReinterpretHelper<T>::FromInternalValue(v); 218 } 219 220 /** 221 * Remove and destroy a property value. This requires a linear search through 222 * the properties of the frame. If the frame has no such property, nothing 223 * happens and false is returned. 224 */ 225 template <typename T> 226 bool Remove(Descriptor<T> aProperty, const nsIFrame* aFrame) { 227 return RemoveInternal(aProperty, aFrame); 228 } 229 230 /** 231 * Call @aFunction for each property or until @aFunction returns false. 232 */ 233 template <class F> 234 void ForEach(F aFunction) const { 235 #ifdef DEBUG 236 size_t len = mProperties.Length(); 237 #endif 238 for (const auto& prop : mProperties) { 239 bool shouldContinue = aFunction(prop.mProperty, prop.mValue); 240 MOZ_ASSERT(len == mProperties.Length(), 241 "frame property list was modified by ForEach callback!"); 242 if (!shouldContinue) { 243 return; 244 } 245 } 246 } 247 248 /** 249 * Remove and destroy all property values for the frame. 250 */ 251 void RemoveAll(const nsIFrame* aFrame) { 252 nsTArray<PropertyValue> toDelete = std::move(mProperties); 253 for (auto& prop : toDelete) { 254 prop.DestroyValueFor(aFrame); 255 } 256 MOZ_ASSERT(mProperties.IsEmpty(), "a property dtor added new properties"); 257 } 258 259 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { 260 // We currently report only the shallow size of the mProperties array. 261 // As for the PropertyValue entries: we don't need to measure the mProperty 262 // field of because it always points to static memory, and we can't measure 263 // mValue because the type is opaque. 264 // XXX Can we do better, e.g. with a method on the descriptor? 265 return mProperties.ShallowSizeOfExcludingThis(aMallocSizeOf); 266 } 267 268 private: 269 // Prevent copying of FrameProperties; we should always return/pass around 270 // references to it, not copies! 271 FrameProperties(const FrameProperties&) = delete; 272 FrameProperties& operator=(const FrameProperties&) = delete; 273 274 inline void SetInternal(UntypedDescriptor aProperty, uint64_t aValue, 275 const nsIFrame* aFrame); 276 277 inline void AddInternal(UntypedDescriptor aProperty, uint64_t aValue); 278 279 inline uint64_t GetInternal(UntypedDescriptor aProperty, 280 bool* aFoundResult) const; 281 282 inline uint64_t TakeInternal(UntypedDescriptor aProperty, bool* aFoundResult); 283 284 // Returns whether the property was removed. 285 inline bool RemoveInternal(UntypedDescriptor aProperty, 286 const nsIFrame* aFrame); 287 288 template <typename T> 289 struct ReinterpretHelper { 290 static_assert(sizeof(PropertyType<T>) <= sizeof(uint64_t), 291 "size of the value must never be larger than 64 bits"); 292 293 static uint64_t ToInternalValue(PropertyType<T> aValue) { 294 uint64_t v = 0; 295 memcpy(&v, &aValue, sizeof(aValue)); 296 return v; 297 } 298 299 static PropertyType<T> FromInternalValue(uint64_t aInternalValue) { 300 PropertyType<T> value; 301 memcpy(&value, &aInternalValue, sizeof(value)); 302 return value; 303 } 304 }; 305 306 /** 307 * Stores a property descriptor/value pair. 308 */ 309 struct PropertyValue { 310 PropertyValue() : mProperty(nullptr), mValue(0) {} 311 PropertyValue(UntypedDescriptor aProperty, uint64_t aValue) 312 : mProperty(aProperty), mValue(aValue) {} 313 314 // NOTE: This function converts our internal 64-bit-integer representation 315 // to a pointer-type representation. This is lossy on 32-bit systems, but it 316 // should be fine, as long as we *only* do this in cases where we're sure 317 // that the stored property-value is in fact a pointer. And we should have 318 // that assurance, since only pointer-typed frame properties are expected to 319 // have a destructor 320 void DestroyValueFor(const nsIFrame* aFrame) { 321 if (mProperty->mDestructor) { 322 mProperty->mDestructor( 323 ReinterpretHelper<void*>::FromInternalValue(mValue)); 324 } else if (mProperty->mDestructorWithFrame) { 325 mProperty->mDestructorWithFrame( 326 aFrame, ReinterpretHelper<void*>::FromInternalValue(mValue)); 327 } 328 } 329 330 UntypedDescriptor mProperty; 331 uint64_t mValue; 332 }; 333 334 /** 335 * Used with an array of PropertyValues to allow lookups that compare 336 * only on the FramePropertyDescriptor. 337 */ 338 class PropertyComparator { 339 public: 340 bool Equals(const PropertyValue& a, const PropertyValue& b) const { 341 return a.mProperty == b.mProperty; 342 } 343 bool Equals(UntypedDescriptor a, const PropertyValue& b) const { 344 return a == b.mProperty; 345 } 346 bool Equals(const PropertyValue& a, UntypedDescriptor b) const { 347 return a.mProperty == b; 348 } 349 }; 350 351 nsTArray<PropertyValue> mProperties; 352 }; 353 354 inline uint64_t FrameProperties::GetInternal(UntypedDescriptor aProperty, 355 bool* aFoundResult) const { 356 MOZ_ASSERT(aProperty, "Null property?"); 357 358 return mProperties.ApplyIf( 359 aProperty, 0, PropertyComparator(), 360 [&aFoundResult](const PropertyValue& aPV) -> uint64_t { 361 if (aFoundResult) { 362 *aFoundResult = true; 363 } 364 return aPV.mValue; 365 }, 366 [&aFoundResult]() -> uint64_t { 367 if (aFoundResult) { 368 *aFoundResult = false; 369 } 370 return 0; 371 }); 372 } 373 374 inline void FrameProperties::SetInternal(UntypedDescriptor aProperty, 375 uint64_t aValue, 376 const nsIFrame* aFrame) { 377 MOZ_ASSERT(NS_IsMainThread()); 378 MOZ_ASSERT(aProperty, "Null property?"); 379 380 mProperties.ApplyIf( 381 aProperty, 0, PropertyComparator(), 382 [&](PropertyValue& aPV) { 383 aPV.DestroyValueFor(aFrame); 384 aPV.mValue = aValue; 385 }, 386 [&]() { mProperties.AppendElement(PropertyValue(aProperty, aValue)); }); 387 } 388 389 inline void FrameProperties::AddInternal(UntypedDescriptor aProperty, 390 uint64_t aValue) { 391 MOZ_ASSERT(NS_IsMainThread()); 392 MOZ_ASSERT(aProperty, "Null property?"); 393 394 mProperties.AppendElement(PropertyValue(aProperty, aValue)); 395 } 396 397 inline uint64_t FrameProperties::TakeInternal(UntypedDescriptor aProperty, 398 bool* aFoundResult) { 399 MOZ_ASSERT(NS_IsMainThread()); 400 MOZ_ASSERT(aProperty, "Null property?"); 401 402 auto index = mProperties.IndexOf(aProperty, 0, PropertyComparator()); 403 if (index == nsTArray<PropertyValue>::NoIndex) { 404 if (aFoundResult) { 405 *aFoundResult = false; 406 } 407 return 0; 408 } 409 410 if (aFoundResult) { 411 *aFoundResult = true; 412 } 413 414 uint64_t result = mProperties.Elements()[index].mValue; 415 mProperties.RemoveElementAtUnsafe(index); 416 417 return result; 418 } 419 420 inline bool FrameProperties::RemoveInternal(UntypedDescriptor aProperty, 421 const nsIFrame* aFrame) { 422 MOZ_ASSERT(NS_IsMainThread()); 423 MOZ_ASSERT(aProperty, "Null property?"); 424 425 auto index = mProperties.IndexOf(aProperty, 0, PropertyComparator()); 426 if (index == nsTArray<PropertyValue>::NoIndex) { 427 return false; 428 } 429 mProperties.Elements()[index].DestroyValueFor(aFrame); 430 mProperties.RemoveElementAtUnsafe(index); 431 return true; 432 } 433 434 } // namespace mozilla 435 436 #endif /* FRAMEPROPERTIES_H_ */