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/. */
7 #ifndef FRAMEPROPERTIES_H_
8 #define FRAMEPROPERTIES_H_
10 #include "mozilla/DebugOnly.h"
11 #include "mozilla/MemoryReporting.h"
12 #include "mozilla/Unused.h"
14 #include "nsThreadUtils.h"
20 struct FramePropertyDescriptorUntyped
{
22 * mDestructor will be called if it's non-null.
24 typedef void UntypedDestructor(void* aPropertyValue
);
25 UntypedDestructor
* mDestructor
;
27 * mDestructorWithFrame will be called if it's non-null and mDestructor
28 * is null. WARNING: The frame passed to mDestructorWithFrame may
29 * be a dangling frame pointer, if this is being called during
30 * presshell teardown. Do not use it except to compare against
31 * other frame pointers. No frame will have been allocated with
32 * the same address yet.
34 typedef void UntypedDestructorWithFrame(const nsIFrame
* aFrame
,
35 void* aPropertyValue
);
36 UntypedDestructorWithFrame
* mDestructorWithFrame
;
38 * mDestructor and mDestructorWithFrame may both be null, in which case
39 * no value destruction is a no-op.
44 * At most one destructor should be passed in. In general, you should
45 * just use the static function FramePropertyDescriptor::New* below
46 * instead of using this constructor directly.
48 constexpr FramePropertyDescriptorUntyped(
49 UntypedDestructor
* aDtor
, UntypedDestructorWithFrame
* aDtorWithFrame
)
50 : mDestructor(aDtor
), mDestructorWithFrame(aDtorWithFrame
) {}
54 * A pointer to a FramePropertyDescriptor serves as a unique property ID.
55 * The FramePropertyDescriptor stores metadata about the property.
56 * Currently the only metadata is a destructor function. The destructor
57 * function is called on property values when they are overwritten or
60 * To use this class, declare a global (i.e., file, class or function-scope
61 * static member) FramePropertyDescriptor and pass its address as
62 * aProperty in the FrameProperties methods.
65 struct FramePropertyDescriptor
: public FramePropertyDescriptorUntyped
{
66 typedef void Destructor(T
* aPropertyValue
);
67 typedef void DestructorWithFrame(const nsIFrame
* aaFrame
, T
* aPropertyValue
);
69 template <Destructor Dtor
>
70 static constexpr const FramePropertyDescriptor
<T
> NewWithDestructor() {
71 return {Destruct
<Dtor
>, nullptr};
74 template <DestructorWithFrame Dtor
>
75 static constexpr const FramePropertyDescriptor
<T
>
76 NewWithDestructorWithFrame() {
77 return {nullptr, DestructWithFrame
<Dtor
>};
80 static constexpr const FramePropertyDescriptor
<T
> NewWithoutDestructor() {
81 return {nullptr, nullptr};
85 constexpr FramePropertyDescriptor(UntypedDestructor
* aDtor
,
86 UntypedDestructorWithFrame
* aDtorWithFrame
)
87 : FramePropertyDescriptorUntyped(aDtor
, aDtorWithFrame
) {}
89 template <Destructor Dtor
>
90 static void Destruct(void* aPropertyValue
) {
91 Dtor(static_cast<T
*>(aPropertyValue
));
94 template <DestructorWithFrame Dtor
>
95 static void DestructWithFrame(const nsIFrame
* aFrame
, void* aPropertyValue
) {
96 Dtor(aFrame
, static_cast<T
*>(aPropertyValue
));
100 // SmallValueHolder<T> is a placeholder intended to be used as template
101 // argument of FramePropertyDescriptor for types which can fit into the
102 // size of a pointer directly. This class should never be defined, so
103 // that we won't use it for unexpected purpose by mistake.
104 template <typename T
>
105 class SmallValueHolder
;
109 template <typename T
>
110 struct FramePropertyTypeHelper
{
113 template <typename T
>
114 struct FramePropertyTypeHelper
<SmallValueHolder
<T
>> {
118 } // namespace detail
121 * The FrameProperties class is optimized for storing 0 or 1 properties on
122 * a given frame. Storing very large numbers of properties on a single
123 * frame will not be efficient.
125 * Property values are passed as void* but do not actually have to be
126 * valid pointers. You can use NS_INT32_TO_PTR/NS_PTR_TO_INT32 to
127 * store int32_t values. Null/zero values can be stored and retrieved.
128 * Of course, the destructor function (if any) must handle such values
131 class FrameProperties
{
133 template <typename T
>
134 using Descriptor
= const FramePropertyDescriptor
<T
>*;
135 using UntypedDescriptor
= const FramePropertyDescriptorUntyped
*;
137 template <typename T
>
138 using PropertyType
= typename
detail::FramePropertyTypeHelper
<T
>::Type
;
140 explicit FrameProperties() = default;
143 MOZ_ASSERT(mProperties
.Length() == 0, "forgot to delete properties");
147 * Return true if we have no properties, otherwise return false.
149 bool IsEmpty() const { return mProperties
.IsEmpty(); }
152 * Set a property value. This requires a linear search through
153 * the properties of the frame. Any existing value for the property
156 template <typename T
>
157 void Set(Descriptor
<T
> aProperty
, PropertyType
<T
> aValue
,
158 const nsIFrame
* aFrame
) {
159 void* ptr
= ReinterpretHelper
<T
>::ToPointer(aValue
);
160 SetInternal(aProperty
, ptr
, aFrame
);
164 * Add a property value; the descriptor MUST NOT already be present.
166 template <typename T
>
167 void Add(Descriptor
<T
> aProperty
, PropertyType
<T
> aValue
) {
168 MOZ_ASSERT(!Has(aProperty
), "duplicate frame property");
169 void* ptr
= ReinterpretHelper
<T
>::ToPointer(aValue
);
170 AddInternal(aProperty
, ptr
);
174 * @return true if @aProperty is set. This requires a linear search through
175 * the properties of the frame.
177 * In most cases, this shouldn't be used outside of assertions, because if
178 * you're doing a lookup anyway it would be far more efficient to call Get()
179 * or Take() and check the aFoundResult outparam to find out whether the
180 * property is set. Legitimate non-assertion uses include:
182 * - Checking if a frame property is set in cases where that's all we want
183 * to know (i.e., we don't intend to read the actual value or remove the
186 * - Calling Has() before Set() in cases where we don't want to overwrite
187 * an existing value for the frame property.
189 * The HasSkippingBitCheck variant doesn't test NS_FRAME_HAS_PROPERTIES
190 * on aFrame, so it is safe to call after aFrame has been destroyed as
191 * long as, since that destruction happened, it isn't possible for a
192 * new frame to have been created and the same property added.
194 template <typename T
>
195 bool Has(Descriptor
<T
> aProperty
) const {
196 return mProperties
.Contains(aProperty
, PropertyComparator());
200 * Get a property value. This requires a linear search through
201 * the properties of the frame. If the frame has no such property,
202 * returns zero-filled result, which means null for pointers and
203 * zero for integers and floating point types.
204 * @param aFoundResult if non-null, receives a value 'true' iff
205 * the frame has a value for the property. This lets callers
206 * disambiguate a null result, which can mean 'no such property' or
207 * 'property value is null'.
209 template <typename T
>
210 PropertyType
<T
> Get(Descriptor
<T
> aProperty
,
211 bool* aFoundResult
= nullptr) const {
212 void* ptr
= GetInternal(aProperty
, aFoundResult
);
213 return ReinterpretHelper
<T
>::FromPointer(ptr
);
217 * Remove a property value, and return it without destroying it.
219 * This requires a linear search through the properties of the frame.
220 * If the frame has no such property, returns zero-filled result, which means
221 * null for pointers and zero for integers and floating point types.
222 * @param aFoundResult if non-null, receives a value 'true' iff
223 * the frame had a value for the property. This lets callers
224 * disambiguate a null result, which can mean 'no such property' or
225 * 'property value is null'.
227 template <typename T
>
228 PropertyType
<T
> Take(Descriptor
<T
> aProperty
, bool* aFoundResult
= nullptr) {
229 void* ptr
= TakeInternal(aProperty
, aFoundResult
);
230 return ReinterpretHelper
<T
>::FromPointer(ptr
);
234 * Remove and destroy a property value. This requires a linear search through
235 * the properties of the frame. If the frame has no such property, nothing
238 template <typename T
>
239 void Remove(Descriptor
<T
> aProperty
, const nsIFrame
* aFrame
) {
240 RemoveInternal(aProperty
, aFrame
);
244 * Call @aFunction for each property or until @aFunction returns false.
247 void ForEach(F aFunction
) const {
249 size_t len
= mProperties
.Length();
251 for (const auto& prop
: mProperties
) {
252 bool shouldContinue
= aFunction(prop
.mProperty
, prop
.mValue
);
253 MOZ_ASSERT(len
== mProperties
.Length(),
254 "frame property list was modified by ForEach callback!");
255 if (!shouldContinue
) {
262 * Remove and destroy all property values for the frame.
264 void RemoveAll(const nsIFrame
* aFrame
) {
265 nsTArray
<PropertyValue
> toDelete
;
266 toDelete
.SwapElements(mProperties
);
267 for (auto& prop
: toDelete
) {
268 prop
.DestroyValueFor(aFrame
);
270 MOZ_ASSERT(mProperties
.IsEmpty(), "a property dtor added new properties");
273 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const {
274 // We currently report only the shallow size of the mProperties array.
275 // As for the PropertyValue entries: we don't need to measure the mProperty
276 // field of because it always points to static memory, and we can't measure
277 // mValue because the type is opaque.
278 // XXX Can we do better, e.g. with a method on the descriptor?
279 return mProperties
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
283 // Prevent copying of FrameProperties; we should always return/pass around
284 // references to it, not copies!
285 FrameProperties(const FrameProperties
&) = delete;
286 FrameProperties
& operator=(const FrameProperties
&) = delete;
288 inline void SetInternal(UntypedDescriptor aProperty
, void* aValue
,
289 const nsIFrame
* aFrame
);
291 inline void AddInternal(UntypedDescriptor aProperty
, void* aValue
);
293 inline void* GetInternal(UntypedDescriptor aProperty
,
294 bool* aFoundResult
) const;
296 inline void* TakeInternal(UntypedDescriptor aProperty
, bool* aFoundResult
);
298 inline void RemoveInternal(UntypedDescriptor aProperty
,
299 const nsIFrame
* aFrame
);
301 template <typename T
>
302 struct ReinterpretHelper
{
303 static_assert(sizeof(PropertyType
<T
>) <= sizeof(void*),
304 "size of the value must never be larger than a pointer");
306 static void* ToPointer(PropertyType
<T
> aValue
) {
308 memcpy(&ptr
, &aValue
, sizeof(aValue
));
312 static PropertyType
<T
> FromPointer(void* aPtr
) {
313 PropertyType
<T
> value
;
314 memcpy(&value
, &aPtr
, sizeof(value
));
319 template <typename T
>
320 struct ReinterpretHelper
<T
*> {
321 static void* ToPointer(T
* aValue
) { return static_cast<void*>(aValue
); }
323 static T
* FromPointer(void* aPtr
) { return static_cast<T
*>(aPtr
); }
327 * Stores a property descriptor/value pair.
329 struct PropertyValue
{
330 PropertyValue() : mProperty(nullptr), mValue(nullptr) {}
331 PropertyValue(UntypedDescriptor aProperty
, void* aValue
)
332 : mProperty(aProperty
), mValue(aValue
) {}
334 void DestroyValueFor(const nsIFrame
* aFrame
) {
335 if (mProperty
->mDestructor
) {
336 mProperty
->mDestructor(mValue
);
337 } else if (mProperty
->mDestructorWithFrame
) {
338 mProperty
->mDestructorWithFrame(aFrame
, mValue
);
342 UntypedDescriptor mProperty
;
347 * Used with an array of PropertyValues to allow lookups that compare
348 * only on the FramePropertyDescriptor.
350 class PropertyComparator
{
352 bool Equals(const PropertyValue
& a
, const PropertyValue
& b
) const {
353 return a
.mProperty
== b
.mProperty
;
355 bool Equals(UntypedDescriptor a
, const PropertyValue
& b
) const {
356 return a
== b
.mProperty
;
358 bool Equals(const PropertyValue
& a
, UntypedDescriptor b
) const {
359 return a
.mProperty
== b
;
363 nsTArray
<PropertyValue
> mProperties
;
366 inline void* FrameProperties::GetInternal(UntypedDescriptor aProperty
,
367 bool* aFoundResult
) const {
368 MOZ_ASSERT(aProperty
, "Null property?");
370 return mProperties
.ApplyIf(
371 aProperty
, 0, PropertyComparator(),
372 [&aFoundResult
](const PropertyValue
& aPV
) -> void* {
374 *aFoundResult
= true;
378 [&aFoundResult
]() -> void* {
380 *aFoundResult
= false;
386 inline void FrameProperties::SetInternal(UntypedDescriptor aProperty
,
387 void* aValue
, const nsIFrame
* aFrame
) {
388 MOZ_ASSERT(NS_IsMainThread());
389 MOZ_ASSERT(aProperty
, "Null property?");
392 aProperty
, 0, PropertyComparator(),
393 [&](PropertyValue
& aPV
) {
394 aPV
.DestroyValueFor(aFrame
);
397 [&]() { mProperties
.AppendElement(PropertyValue(aProperty
, aValue
)); });
400 inline void FrameProperties::AddInternal(UntypedDescriptor aProperty
,
402 MOZ_ASSERT(NS_IsMainThread());
403 MOZ_ASSERT(aProperty
, "Null property?");
405 mProperties
.AppendElement(PropertyValue(aProperty
, aValue
));
408 inline void* FrameProperties::TakeInternal(UntypedDescriptor aProperty
,
409 bool* aFoundResult
) {
410 MOZ_ASSERT(NS_IsMainThread());
411 MOZ_ASSERT(aProperty
, "Null property?");
413 auto index
= mProperties
.IndexOf(aProperty
, 0, PropertyComparator());
414 if (index
== nsTArray
<PropertyValue
>::NoIndex
) {
416 *aFoundResult
= false;
422 *aFoundResult
= true;
425 void* result
= mProperties
.Elements()[index
].mValue
;
426 mProperties
.RemoveElementAt(index
);
431 inline void FrameProperties::RemoveInternal(UntypedDescriptor aProperty
,
432 const nsIFrame
* aFrame
) {
433 MOZ_ASSERT(NS_IsMainThread());
434 MOZ_ASSERT(aProperty
, "Null property?");
436 auto index
= mProperties
.IndexOf(aProperty
, 0, PropertyComparator());
437 if (index
!= nsTArray
<PropertyValue
>::NoIndex
) {
438 mProperties
.Elements()[index
].DestroyValueFor(aFrame
);
439 mProperties
.RemoveElementAt(index
);
443 } // namespace mozilla
445 #endif /* FRAMEPROPERTIES_H_ */