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