1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 /* A global table that tracks images referenced by CSS variables. */
7
8 #ifndef mozilla_CSSVariableImageTable_h
9 #define mozilla_CSSVariableImageTable_h
10
11 #include "nsClassHashtable.h"
12 #include "nsCSSPropertyID.h"
13 #include "nsCSSValue.h"
14 #include "nsStyleContext.h"
15 #include "nsTArray.h"
16
17 /**
18 * CSSVariableImageTable maintains a global mapping
19 * (nsStyleContext, nsCSSPropertyID) -> nsTArray<ImageValue>
20 * which allows us to track the relationship between CSS property values
21 * involving variables and any images they may reference.
22 *
23 * When properties like background-image contain a normal url(), the
24 * Declaration's data block will hold a reference to the ImageValue. When a
25 * token stream is used, the Declaration only holds on to an
26 * nsCSSValueTokenStream object, and the ImageValue would only exist for the
27 * duration of nsRuleNode::WalkRuleTree, in the AutoCSSValueArray. So instead
28 * when we re-parse a token stream and get an ImageValue, we record it in the
29 * CSSVariableImageTable to keep the ImageValue alive. Such ImageValues are
30 * eventually freed the next time the token stream is re-parsed, or when the
31 * associated style context is destroyed.
32 *
33 * To add ImageValues to the CSSVariableImageTable, callers should pass a lambda
34 * to CSSVariableImageTable::ReplaceAll() that calls
35 * CSSVariableImageTable::Add() for each ImageValue that needs to be added to
36 * the table. When callers are sure that the ImageValues for a given
37 * nsStyleContext won't be needed anymore, they can call
38 * CSSVariableImageTable::RemoveAll() to release them.
39 */
40
41 namespace mozilla {
42 namespace CSSVariableImageTable {
43
44 namespace detail {
45
46 typedef nsTArray<RefPtr<css::ImageValue>> ImageValueArray;
47 typedef nsClassHashtable<nsGenericHashKey<nsCSSPropertyID>, ImageValueArray>
48 PerPropertyImageHashtable;
49 typedef nsClassHashtable<nsPtrHashKey<nsStyleContext>, PerPropertyImageHashtable>
50 CSSVariableImageHashtable;
51
GetTable()52 inline CSSVariableImageHashtable& GetTable()
53 {
54 static CSSVariableImageHashtable imageTable;
55 return imageTable;
56 }
57
58 #ifdef DEBUG
IsReplacing()59 inline bool& IsReplacing()
60 {
61 static bool isReplacing = false;
62 return isReplacing;
63 }
64 #endif
65
66 } // namespace detail
67
68 /**
69 * ReplaceAll() allows callers to replace the ImageValues associated with a
70 * (nsStyleContext, nsCSSPropertyID) pair. The memory used by the previous list of
71 * ImageValues is automatically released.
72 *
73 * @param aContext The style context the ImageValues are associated with.
74 * @param aProp The CSS property the ImageValues are associated with.
75 * @param aFunc A lambda that calls CSSVariableImageTable::Add() to add new
76 * ImageValues which will replace the old ones.
77 */
78 template <typename Lambda>
ReplaceAll(nsStyleContext * aContext,nsCSSPropertyID aProp,Lambda aFunc)79 inline void ReplaceAll(nsStyleContext* aContext,
80 nsCSSPropertyID aProp,
81 Lambda aFunc)
82 {
83 MOZ_ASSERT(aContext);
84
85 auto& imageTable = detail::GetTable();
86
87 // Clear the existing image array, if any, for this property.
88 {
89 auto* perPropertyImageTable = imageTable.Get(aContext);
90 auto* imageList = perPropertyImageTable ? perPropertyImageTable->Get(aProp)
91 : nullptr;
92 if (imageList) {
93 imageList->ClearAndRetainStorage();
94 }
95 }
96
97 #ifdef DEBUG
98 MOZ_ASSERT(!detail::IsReplacing());
99 detail::IsReplacing() = true;
100 #endif
101
102 aFunc();
103
104 #ifdef DEBUG
105 detail::IsReplacing() = false;
106 #endif
107
108 // Clean up.
109 auto* perPropertyImageTable = imageTable.Get(aContext);
110 auto* imageList = perPropertyImageTable ? perPropertyImageTable->Get(aProp)
111 : nullptr;
112 if (imageList) {
113 if (imageList->IsEmpty()) {
114 // We used to have an image array for this property, but now we don't.
115 // Remove the entry in the per-property image table for this property.
116 // That may then allow us to remove the entire per-property image table.
117 perPropertyImageTable->Remove(aProp);
118 if (perPropertyImageTable->Count() == 0) {
119 imageTable.Remove(aContext);
120 }
121 } else {
122 // We still have a non-empty image array for this property. Compact the
123 // storage it's using if possible.
124 imageList->Compact();
125 }
126 }
127 }
128
129 /**
130 * Adds a new ImageValue @aValue to the CSSVariableImageTable, which will be
131 * associated with @aContext and @aProp.
132 *
133 * It's illegal to call this function outside of a lambda passed to
134 * CSSVariableImageTable::ReplaceAll().
135 */
136 inline void
Add(nsStyleContext * aContext,nsCSSPropertyID aProp,css::ImageValue * aValue)137 Add(nsStyleContext* aContext, nsCSSPropertyID aProp, css::ImageValue* aValue)
138 {
139 MOZ_ASSERT(aValue);
140 MOZ_ASSERT(aContext);
141 MOZ_ASSERT(detail::IsReplacing());
142
143 auto& imageTable = detail::GetTable();
144
145 // Ensure there's a per-property image table for this style context.
146 auto* perPropertyImageTable = imageTable.Get(aContext);
147 if (!perPropertyImageTable) {
148 perPropertyImageTable = new detail::PerPropertyImageHashtable();
149 imageTable.Put(aContext, perPropertyImageTable);
150 }
151
152 // Ensure there's an image array for this property.
153 auto* imageList = perPropertyImageTable->Get(aProp);
154 if (!imageList) {
155 imageList = new detail::ImageValueArray();
156 perPropertyImageTable->Put(aProp, imageList);
157 }
158
159 // Append the provided ImageValue to the list.
160 imageList->AppendElement(aValue);
161 }
162
163 /**
164 * Removes all ImageValues stored in the CSSVariableImageTable for the provided
165 * @aContext.
166 */
167 inline void
RemoveAll(nsStyleContext * aContext)168 RemoveAll(nsStyleContext* aContext)
169 {
170 // Move all ImageValue references into removedImageList so that we can
171 // release them outside of any hashtable methods. (If we just call
172 // Remove(aContext) on the table then we can end up calling back
173 // re-entrantly into hashtable methods, as other style contexts
174 // are released.)
175 detail::ImageValueArray removedImages;
176 auto& imageTable = detail::GetTable();
177 auto* perPropertyImageTable = imageTable.Get(aContext);
178 if (perPropertyImageTable) {
179 for (auto it = perPropertyImageTable->Iter(); !it.Done(); it.Next()) {
180 auto* imageList = it.UserData();
181 removedImages.AppendElements(Move(*imageList));
182 }
183 }
184 imageTable.Remove(aContext);
185 }
186
187 } // namespace CSSVariableImageTable
188 } // namespace mozilla
189
190 #endif // mozilla_CSSVariableImageTable_h
191