Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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/. */
6 #ifndef NSEXPIRATIONTRACKER_H_
7 #define NSEXPIRATIONTRACKER_H_
9 #include "mozilla/Attributes.h"
11 #include "prlog.h"
12 #include "nsTArray.h"
13 #include "nsITimer.h"
14 #include "nsCOMPtr.h"
15 #include "nsAutoPtr.h"
16 #include "nsComponentManagerUtils.h"
17 #include "nsIObserver.h"
18 #include "nsIObserverService.h"
19 #include "mozilla/Services.h"
20 #include "mozilla/Attributes.h"
22 /**
23 * Data used to track the expiration state of an object. We promise that this
24 * is 32 bits so that objects that includes this as a field can pad and align
25 * efficiently.
26 */
27 struct nsExpirationState {
28 enum { NOT_TRACKED = (1U << 4) - 1,
29 MAX_INDEX_IN_GENERATION = (1U << 28) - 1 };
31 nsExpirationState() : mGeneration(NOT_TRACKED) {}
32 bool IsTracked() { return mGeneration != NOT_TRACKED; }
34 /**
35 * The generation that this object belongs to, or NOT_TRACKED.
36 */
37 uint32_t mGeneration:4;
38 uint32_t mIndexInGeneration:28;
39 };
41 /**
42 * nsExpirationTracker can track the lifetimes and usage of a large number of
43 * objects, and send a notification some window of time after a live object was
44 * last used. This is very useful when you manage a large number of objects
45 * and want to flush some after they haven't been used for a while.
46 * nsExpirationTracker is designed to be very space and time efficient.
47 *
48 * The type parameter T is the object type that we will track pointers to. T
49 * must include an accessible method GetExpirationState() that returns a
50 * pointer to an nsExpirationState associated with the object (preferably,
51 * stored in a field of the object).
52 *
53 * The parameter K is the number of generations that will be used. Increasing
54 * the number of generations narrows the window within which we promise
55 * to fire notifications, at a slight increase in space cost for the tracker.
56 * We require 2 <= K <= nsExpirationState::NOT_TRACKED (currently 15).
57 *
58 * To use this class, you need to inherit from it and override the
59 * NotifyExpired() method.
60 *
61 * The approach is to track objects in K generations. When an object is accessed
62 * it moves from its current generation to the newest generation. Generations
63 * are stored in a cyclic array; when a timer interrupt fires, we advance
64 * the current generation pointer to effectively age all objects very efficiently.
65 * By storing information in each object about its generation and index within its
66 * generation array, we make removal of objects from a generation very cheap.
67 *
68 * Future work:
69 * -- Add a method to change the timer period?
70 */
71 template <class T, uint32_t K> class nsExpirationTracker {
72 public:
73 /**
74 * Initialize the tracker.
75 * @param aTimerPeriod the timer period in milliseconds. The guarantees
76 * provided by the tracker are defined in terms of this period. If the
77 * period is zero, then we don't use a timer and rely on someone calling
78 * AgeOneGeneration explicitly.
79 */
80 nsExpirationTracker(uint32_t aTimerPeriod)
81 : mTimerPeriod(aTimerPeriod), mNewestGeneration(0),
82 mInAgeOneGeneration(false) {
83 static_assert(K >= 2 && K <= nsExpirationState::NOT_TRACKED,
84 "Unsupported number of generations (must be 2 <= K <= 15)");
85 mObserver = new ExpirationTrackerObserver();
86 mObserver->Init(this);
87 }
88 ~nsExpirationTracker() {
89 if (mTimer) {
90 mTimer->Cancel();
91 }
92 mObserver->Destroy();
93 }
95 /**
96 * Add an object to be tracked. It must not already be tracked. It will
97 * be added to the newest generation, i.e., as if it was just used.
98 * @return an error on out-of-memory
99 */
100 nsresult AddObject(T* aObj) {
101 nsExpirationState* state = aObj->GetExpirationState();
102 NS_ASSERTION(!state->IsTracked(), "Tried to add an object that's already tracked");
103 nsTArray<T*>& generation = mGenerations[mNewestGeneration];
104 uint32_t index = generation.Length();
105 if (index > nsExpirationState::MAX_INDEX_IN_GENERATION) {
106 NS_WARNING("More than 256M elements tracked, this is probably a problem");
107 return NS_ERROR_OUT_OF_MEMORY;
108 }
109 if (index == 0) {
110 // We might need to start the timer
111 nsresult rv = CheckStartTimer();
112 if (NS_FAILED(rv))
113 return rv;
114 }
115 if (!generation.AppendElement(aObj))
116 return NS_ERROR_OUT_OF_MEMORY;
117 state->mGeneration = mNewestGeneration;
118 state->mIndexInGeneration = index;
119 return NS_OK;
120 }
122 /**
123 * Remove an object from the tracker. It must currently be tracked.
124 */
125 void RemoveObject(T* aObj) {
126 nsExpirationState* state = aObj->GetExpirationState();
127 NS_ASSERTION(state->IsTracked(), "Tried to remove an object that's not tracked");
128 nsTArray<T*>& generation = mGenerations[state->mGeneration];
129 uint32_t index = state->mIndexInGeneration;
130 NS_ASSERTION(generation.Length() > index &&
131 generation[index] == aObj, "Object is lying about its index");
132 // Move the last object to fill the hole created by removing aObj
133 uint32_t last = generation.Length() - 1;
134 T* lastObj = generation[last];
135 generation[index] = lastObj;
136 lastObj->GetExpirationState()->mIndexInGeneration = index;
137 generation.RemoveElementAt(last);
138 state->mGeneration = nsExpirationState::NOT_TRACKED;
139 // We do not check whether we need to stop the timer here. The timer
140 // will check that itself next time it fires. Checking here would not
141 // be efficient since we'd need to track all generations. Also we could
142 // thrash by incessantly creating and destroying timers if someone
143 // kept adding and removing an object from the tracker.
144 }
146 /**
147 * Notify that an object has been used.
148 * @return an error if we lost the object from the tracker...
149 */
150 nsresult MarkUsed(T* aObj) {
151 nsExpirationState* state = aObj->GetExpirationState();
152 if (mNewestGeneration == state->mGeneration)
153 return NS_OK;
154 RemoveObject(aObj);
155 return AddObject(aObj);
156 }
158 /**
159 * The timer calls this, but it can also be manually called if you want
160 * to age objects "artifically". This can result in calls to NotifyExpired.
161 */
162 void AgeOneGeneration() {
163 if (mInAgeOneGeneration) {
164 NS_WARNING("Can't reenter AgeOneGeneration from NotifyExpired");
165 return;
166 }
168 mInAgeOneGeneration = true;
169 uint32_t reapGeneration =
170 mNewestGeneration > 0 ? mNewestGeneration - 1 : K - 1;
171 nsTArray<T*>& generation = mGenerations[reapGeneration];
172 // The following is rather tricky. We have to cope with objects being
173 // removed from this generation either because of a call to RemoveObject
174 // (or indirectly via MarkUsed) inside NotifyExpired. Fortunately no
175 // objects can be added to this generation because it's not the newest
176 // generation. We depend on the fact that RemoveObject can only cause
177 // the indexes of objects in this generation to *decrease*, not increase.
178 // So if we start from the end and work our way backwards we are guaranteed
179 // to see each object at least once.
180 uint32_t index = generation.Length();
181 for (;;) {
182 // Objects could have been removed so index could be outside
183 // the array
184 index = XPCOM_MIN(index, generation.Length());
185 if (index == 0)
186 break;
187 --index;
188 NotifyExpired(generation[index]);
189 }
190 // Any leftover objects from reapGeneration just end up in the new
191 // newest-generation. This is bad form, though, so warn if there are any.
192 if (!generation.IsEmpty()) {
193 NS_WARNING("Expired objects were not removed or marked used");
194 }
195 // Free excess memory used by the generation array, since we probably
196 // just removed most or all of its elements.
197 generation.Compact();
198 mNewestGeneration = reapGeneration;
199 mInAgeOneGeneration = false;
200 }
202 /**
203 * This just calls AgeOneGeneration K times. Under normal circumstances this
204 * will result in all objects getting NotifyExpired called on them, but
205 * if NotifyExpired itself marks some objects as used, then those objects
206 * might not expire. This would be a good thing to call if we get into
207 * a critically-low memory situation.
208 */
209 void AgeAllGenerations() {
210 uint32_t i;
211 for (i = 0; i < K; ++i) {
212 AgeOneGeneration();
213 }
214 }
216 class Iterator {
217 private:
218 nsExpirationTracker<T,K>* mTracker;
219 uint32_t mGeneration;
220 uint32_t mIndex;
221 public:
222 Iterator(nsExpirationTracker<T,K>* aTracker)
223 : mTracker(aTracker), mGeneration(0), mIndex(0) {}
224 T* Next() {
225 while (mGeneration < K) {
226 nsTArray<T*>* generation = &mTracker->mGenerations[mGeneration];
227 if (mIndex < generation->Length()) {
228 ++mIndex;
229 return (*generation)[mIndex - 1];
230 }
231 ++mGeneration;
232 mIndex = 0;
233 }
234 return nullptr;
235 }
236 };
238 friend class Iterator;
240 bool IsEmpty() {
241 for (uint32_t i = 0; i < K; ++i) {
242 if (!mGenerations[i].IsEmpty())
243 return false;
244 }
245 return true;
246 }
248 protected:
249 /**
250 * This must be overridden to catch notifications. It is called whenever
251 * we detect that an object has not been used for at least (K-1)*mTimerPeriod
252 * milliseconds. If timer events are not delayed, it will be called within
253 * roughly K*mTimerPeriod milliseconds after the last use. (Unless AgeOneGeneration
254 * or AgeAllGenerations have been called to accelerate the aging process.)
255 *
256 * NOTE: These bounds ignore delays in timer firings due to actual work being
257 * performed by the browser. We use a slack timer so there is always at least
258 * mTimerPeriod milliseconds between firings, which gives us (K-1)*mTimerPeriod
259 * as a pretty solid lower bound. The upper bound is rather loose, however.
260 * If the maximum amount by which any given timer firing is delayed is D, then
261 * the upper bound before NotifyExpired is called is K*(mTimerPeriod + D).
262 *
263 * The NotifyExpired call is expected to remove the object from the tracker,
264 * but it need not. The object (or other objects) could be "resurrected"
265 * by calling MarkUsed() on them, or they might just not be removed.
266 * Any objects left over that have not been resurrected or removed
267 * are placed in the new newest-generation, but this is considered "bad form"
268 * and should be avoided (we'll issue a warning). (This recycling counts
269 * as "a use" for the purposes of the expiry guarantee above...)
270 *
271 * For robustness and simplicity, we allow objects to be notified more than
272 * once here in the same timer tick.
273 */
274 virtual void NotifyExpired(T* aObj) = 0;
276 private:
277 class ExpirationTrackerObserver;
278 nsRefPtr<ExpirationTrackerObserver> mObserver;
279 nsTArray<T*> mGenerations[K];
280 nsCOMPtr<nsITimer> mTimer;
281 uint32_t mTimerPeriod;
282 uint32_t mNewestGeneration;
283 bool mInAgeOneGeneration;
285 /**
286 * Whenever "memory-pressure" is observed, it calls AgeAllGenerations()
287 * to minimize memory usage.
288 */
289 class ExpirationTrackerObserver MOZ_FINAL : public nsIObserver {
290 public:
291 void Init(nsExpirationTracker<T,K> *obj) {
292 mOwner = obj;
293 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
294 if (obs) {
295 obs->AddObserver(this, "memory-pressure", false);
296 }
297 }
298 void Destroy() {
299 mOwner = nullptr;
300 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
301 if (obs)
302 obs->RemoveObserver(this, "memory-pressure");
303 }
304 NS_DECL_ISUPPORTS
305 NS_DECL_NSIOBSERVER
306 private:
307 nsExpirationTracker<T,K> *mOwner;
308 };
310 static void TimerCallback(nsITimer* aTimer, void* aThis) {
311 nsExpirationTracker* tracker = static_cast<nsExpirationTracker*>(aThis);
312 tracker->AgeOneGeneration();
313 // Cancel the timer if we have no objects to track
314 if (tracker->IsEmpty()) {
315 tracker->mTimer->Cancel();
316 tracker->mTimer = nullptr;
317 }
318 }
320 nsresult CheckStartTimer() {
321 if (mTimer || !mTimerPeriod)
322 return NS_OK;
323 mTimer = do_CreateInstance("@mozilla.org/timer;1");
324 if (!mTimer)
325 return NS_ERROR_OUT_OF_MEMORY;
326 mTimer->InitWithFuncCallback(TimerCallback, this, mTimerPeriod,
327 nsITimer::TYPE_REPEATING_SLACK);
328 return NS_OK;
329 }
330 };
332 template<class T, uint32_t K>
333 NS_IMETHODIMP
334 nsExpirationTracker<T, K>::ExpirationTrackerObserver::Observe(nsISupports *aSubject,
335 const char *aTopic,
336 const char16_t *aData)
337 {
338 if (!strcmp(aTopic, "memory-pressure") && mOwner)
339 mOwner->AgeAllGenerations();
340 return NS_OK;
341 }
343 template <class T, uint32_t K>
344 NS_IMETHODIMP_(MozExternalRefCountType)
345 nsExpirationTracker<T,K>::ExpirationTrackerObserver::AddRef(void)
346 {
347 MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
348 NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver);
349 ++mRefCnt;
350 NS_LOG_ADDREF(this, mRefCnt, "ExpirationTrackerObserver", sizeof(*this));
351 return mRefCnt;
352 }
354 template <class T, uint32_t K>
355 NS_IMETHODIMP_(MozExternalRefCountType)
356 nsExpirationTracker<T,K>::ExpirationTrackerObserver::Release(void)
357 {
358 MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
359 NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver);
360 --mRefCnt;
361 NS_LOG_RELEASE(this, mRefCnt, "ExpirationTrackerObserver");
362 if (mRefCnt == 0) {
363 NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver);
364 mRefCnt = 1; /* stabilize */
365 delete (this);
366 return 0;
367 }
368 return mRefCnt;
369 }
371 template <class T, uint32_t K>
372 NS_IMETHODIMP
373 nsExpirationTracker<T,K>::ExpirationTrackerObserver::QueryInterface(REFNSIID aIID,
374 void** aInstancePtr)
375 {
376 NS_ASSERTION(aInstancePtr,
377 "QueryInterface requires a non-NULL destination!");
378 nsresult rv = NS_ERROR_FAILURE;
379 NS_INTERFACE_TABLE(ExpirationTrackerObserver, nsIObserver)
380 return rv;
381 }
383 #endif /*NSEXPIRATIONTRACKER_H_*/