1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=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 file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "LSDatabase.h"
8 
9 namespace mozilla {
10 namespace dom {
11 
12 using namespace mozilla::services;
13 
14 namespace {
15 
16 #define XPCOM_SHUTDOWN_OBSERVER_TOPIC "xpcom-shutdown"
17 
18 typedef nsDataHashtable<nsCStringHashKey, LSDatabase*> LSDatabaseHashtable;
19 
20 StaticAutoPtr<LSDatabaseHashtable> gLSDatabases;
21 
22 }  // namespace
23 
24 StaticRefPtr<LSDatabase::Observer> LSDatabase::sObserver;
25 
26 class LSDatabase::Observer final : public nsIObserver {
27   bool mInvalidated;
28 
29  public:
Observer()30   Observer() : mInvalidated(false) { MOZ_ASSERT(NS_IsMainThread()); }
31 
Invalidate()32   void Invalidate() { mInvalidated = true; }
33 
34  private:
~Observer()35   ~Observer() { MOZ_ASSERT(NS_IsMainThread()); }
36 
37   NS_DECL_ISUPPORTS
38   NS_DECL_NSIOBSERVER
39 };
40 
LSDatabase(const nsACString & aOrigin)41 LSDatabase::LSDatabase(const nsACString& aOrigin)
42     : mActor(nullptr),
43       mSnapshot(nullptr),
44       mOrigin(aOrigin),
45       mAllowedToClose(false),
46       mRequestedAllowToClose(false) {
47   AssertIsOnOwningThread();
48 
49   if (!gLSDatabases) {
50     gLSDatabases = new LSDatabaseHashtable();
51 
52     MOZ_ASSERT(!sObserver);
53 
54     sObserver = new Observer();
55 
56     nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
57     MOZ_ASSERT(obsSvc);
58 
59     MOZ_ALWAYS_SUCCEEDS(
60         obsSvc->AddObserver(sObserver, XPCOM_SHUTDOWN_OBSERVER_TOPIC, false));
61   }
62 
63   MOZ_ASSERT(!gLSDatabases->Get(mOrigin));
64   gLSDatabases->Put(mOrigin, this);
65 }
66 
~LSDatabase()67 LSDatabase::~LSDatabase() {
68   AssertIsOnOwningThread();
69   MOZ_ASSERT(!mSnapshot);
70 
71   if (!mAllowedToClose) {
72     AllowToClose();
73   }
74 
75   if (mActor) {
76     mActor->SendDeleteMeInternal();
77     MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
78   }
79 }
80 
81 // static
Get(const nsACString & aOrigin)82 LSDatabase* LSDatabase::Get(const nsACString& aOrigin) {
83   return gLSDatabases ? gLSDatabases->Get(aOrigin) : nullptr;
84 }
85 
SetActor(LSDatabaseChild * aActor)86 void LSDatabase::SetActor(LSDatabaseChild* aActor) {
87   AssertIsOnOwningThread();
88   MOZ_ASSERT(aActor);
89   MOZ_ASSERT(!mActor);
90 
91   mActor = aActor;
92 }
93 
RequestAllowToClose()94 void LSDatabase::RequestAllowToClose() {
95   AssertIsOnOwningThread();
96 
97   if (mRequestedAllowToClose) {
98     return;
99   }
100 
101   mRequestedAllowToClose = true;
102 
103   if (mSnapshot) {
104     mSnapshot->MarkDirty();
105   } else {
106     AllowToClose();
107   }
108 }
109 
NoteFinishedSnapshot(LSSnapshot * aSnapshot)110 void LSDatabase::NoteFinishedSnapshot(LSSnapshot* aSnapshot) {
111   AssertIsOnOwningThread();
112   MOZ_ASSERT(aSnapshot == mSnapshot);
113 
114   mSnapshot = nullptr;
115 
116   if (mRequestedAllowToClose) {
117     AllowToClose();
118   }
119 }
120 
GetLength(LSObject * aObject,uint32_t * aResult)121 nsresult LSDatabase::GetLength(LSObject* aObject, uint32_t* aResult) {
122   AssertIsOnOwningThread();
123   MOZ_ASSERT(aObject);
124   MOZ_ASSERT(mActor);
125   MOZ_ASSERT(!mAllowedToClose);
126 
127   nsresult rv = EnsureSnapshot(aObject, VoidString());
128   if (NS_WARN_IF(NS_FAILED(rv))) {
129     return rv;
130   }
131 
132   rv = mSnapshot->GetLength(aResult);
133   if (NS_WARN_IF(NS_FAILED(rv))) {
134     return rv;
135   }
136 
137   return NS_OK;
138 }
139 
GetKey(LSObject * aObject,uint32_t aIndex,nsAString & aResult)140 nsresult LSDatabase::GetKey(LSObject* aObject, uint32_t aIndex,
141                             nsAString& aResult) {
142   AssertIsOnOwningThread();
143   MOZ_ASSERT(aObject);
144   MOZ_ASSERT(mActor);
145   MOZ_ASSERT(!mAllowedToClose);
146 
147   nsresult rv = EnsureSnapshot(aObject, VoidString());
148   if (NS_WARN_IF(NS_FAILED(rv))) {
149     return rv;
150   }
151 
152   rv = mSnapshot->GetKey(aIndex, aResult);
153   if (NS_WARN_IF(NS_FAILED(rv))) {
154     return rv;
155   }
156 
157   return NS_OK;
158 }
159 
GetItem(LSObject * aObject,const nsAString & aKey,nsAString & aResult)160 nsresult LSDatabase::GetItem(LSObject* aObject, const nsAString& aKey,
161                              nsAString& aResult) {
162   AssertIsOnOwningThread();
163   MOZ_ASSERT(aObject);
164   MOZ_ASSERT(mActor);
165   MOZ_ASSERT(!mAllowedToClose);
166 
167   nsresult rv = EnsureSnapshot(aObject, aKey);
168   if (NS_WARN_IF(NS_FAILED(rv))) {
169     return rv;
170   }
171 
172   rv = mSnapshot->GetItem(aKey, aResult);
173   if (NS_WARN_IF(NS_FAILED(rv))) {
174     return rv;
175   }
176 
177   return NS_OK;
178 }
179 
GetKeys(LSObject * aObject,nsTArray<nsString> & aKeys)180 nsresult LSDatabase::GetKeys(LSObject* aObject, nsTArray<nsString>& aKeys) {
181   AssertIsOnOwningThread();
182   MOZ_ASSERT(aObject);
183   MOZ_ASSERT(mActor);
184   MOZ_ASSERT(!mAllowedToClose);
185 
186   nsresult rv = EnsureSnapshot(aObject, VoidString());
187   if (NS_WARN_IF(NS_FAILED(rv))) {
188     return rv;
189   }
190 
191   rv = mSnapshot->GetKeys(aKeys);
192   if (NS_WARN_IF(NS_FAILED(rv))) {
193     return rv;
194   }
195 
196   return NS_OK;
197 }
198 
SetItem(LSObject * aObject,const nsAString & aKey,const nsAString & aValue,LSNotifyInfo & aNotifyInfo)199 nsresult LSDatabase::SetItem(LSObject* aObject, const nsAString& aKey,
200                              const nsAString& aValue,
201                              LSNotifyInfo& aNotifyInfo) {
202   AssertIsOnOwningThread();
203   MOZ_ASSERT(aObject);
204   MOZ_ASSERT(mActor);
205   MOZ_ASSERT(!mAllowedToClose);
206 
207   nsresult rv = EnsureSnapshot(aObject, aKey);
208   if (NS_WARN_IF(NS_FAILED(rv))) {
209     return rv;
210   }
211 
212   rv = mSnapshot->SetItem(aKey, aValue, aNotifyInfo);
213   if (NS_WARN_IF(NS_FAILED(rv))) {
214     return rv;
215   }
216 
217   return NS_OK;
218 }
219 
RemoveItem(LSObject * aObject,const nsAString & aKey,LSNotifyInfo & aNotifyInfo)220 nsresult LSDatabase::RemoveItem(LSObject* aObject, const nsAString& aKey,
221                                 LSNotifyInfo& aNotifyInfo) {
222   AssertIsOnOwningThread();
223   MOZ_ASSERT(aObject);
224   MOZ_ASSERT(mActor);
225   MOZ_ASSERT(!mAllowedToClose);
226 
227   nsresult rv = EnsureSnapshot(aObject, aKey);
228   if (NS_WARN_IF(NS_FAILED(rv))) {
229     return rv;
230   }
231 
232   rv = mSnapshot->RemoveItem(aKey, aNotifyInfo);
233   if (NS_WARN_IF(NS_FAILED(rv))) {
234     return rv;
235   }
236 
237   return NS_OK;
238 }
239 
Clear(LSObject * aObject,LSNotifyInfo & aNotifyInfo)240 nsresult LSDatabase::Clear(LSObject* aObject, LSNotifyInfo& aNotifyInfo) {
241   AssertIsOnOwningThread();
242   MOZ_ASSERT(aObject);
243   MOZ_ASSERT(mActor);
244   MOZ_ASSERT(!mAllowedToClose);
245 
246   nsresult rv = EnsureSnapshot(aObject, VoidString());
247   if (NS_WARN_IF(NS_FAILED(rv))) {
248     return rv;
249   }
250 
251   rv = mSnapshot->Clear(aNotifyInfo);
252   if (NS_WARN_IF(NS_FAILED(rv))) {
253     return rv;
254   }
255 
256   return NS_OK;
257 }
258 
BeginExplicitSnapshot(LSObject * aObject)259 nsresult LSDatabase::BeginExplicitSnapshot(LSObject* aObject) {
260   AssertIsOnOwningThread();
261   MOZ_ASSERT(aObject);
262   MOZ_ASSERT(mActor);
263   MOZ_ASSERT(!mAllowedToClose);
264 
265   if (mSnapshot) {
266     return NS_ERROR_ALREADY_INITIALIZED;
267   }
268 
269   nsresult rv = EnsureSnapshot(aObject, VoidString(), /* aExplicit */ true);
270   if (NS_WARN_IF(NS_FAILED(rv))) {
271     return rv;
272   }
273 
274   return NS_OK;
275 }
276 
EndExplicitSnapshot(LSObject * aObject)277 nsresult LSDatabase::EndExplicitSnapshot(LSObject* aObject) {
278   AssertIsOnOwningThread();
279   MOZ_ASSERT(aObject);
280   MOZ_ASSERT(mActor);
281   MOZ_ASSERT(!mAllowedToClose);
282 
283   if (!mSnapshot) {
284     return NS_ERROR_NOT_INITIALIZED;
285   }
286 
287   MOZ_ASSERT(mSnapshot->Explicit());
288 
289   nsresult rv = mSnapshot->End();
290   if (NS_WARN_IF(NS_FAILED(rv))) {
291     return rv;
292   }
293 
294   return NS_OK;
295 }
296 
EnsureSnapshot(LSObject * aObject,const nsAString & aKey,bool aExplicit)297 nsresult LSDatabase::EnsureSnapshot(LSObject* aObject, const nsAString& aKey,
298                                     bool aExplicit) {
299   MOZ_ASSERT(aObject);
300   MOZ_ASSERT(mActor);
301   MOZ_ASSERT_IF(mSnapshot, !aExplicit);
302   MOZ_ASSERT(!mAllowedToClose);
303 
304   if (mSnapshot) {
305     return NS_OK;
306   }
307 
308   RefPtr<LSSnapshot> snapshot = new LSSnapshot(this);
309 
310   LSSnapshotChild* actor = new LSSnapshotChild(snapshot);
311 
312   LSSnapshotInitInfo initInfo;
313   bool ok = mActor->SendPBackgroundLSSnapshotConstructor(
314       actor, aObject->DocumentURI(), nsString(aKey),
315       /* increasePeakUsage */ true,
316       /* requestedSize */ 131072,
317       /* minSize */ 4096, &initInfo);
318   if (NS_WARN_IF(!ok)) {
319     return NS_ERROR_FAILURE;
320   }
321 
322   snapshot->SetActor(actor);
323 
324   // This add refs snapshot.
325   nsresult rv = snapshot->Init(aKey, initInfo, aExplicit);
326   if (NS_WARN_IF(NS_FAILED(rv))) {
327     return rv;
328   }
329 
330   // This is cleared in LSSnapshot::Run() before the snapshot is destroyed.
331   mSnapshot = snapshot;
332 
333   return NS_OK;
334 }
335 
AllowToClose()336 void LSDatabase::AllowToClose() {
337   AssertIsOnOwningThread();
338   MOZ_ASSERT(!mAllowedToClose);
339   MOZ_ASSERT(!mSnapshot);
340 
341   mAllowedToClose = true;
342 
343   if (mActor) {
344     mActor->SendAllowToClose();
345   }
346 
347   MOZ_ASSERT(gLSDatabases);
348   MOZ_ASSERT(gLSDatabases->Get(mOrigin));
349   gLSDatabases->Remove(mOrigin);
350 
351   if (!gLSDatabases->Count()) {
352     gLSDatabases = nullptr;
353 
354     MOZ_ASSERT(sObserver);
355 
356     nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
357     MOZ_ASSERT(obsSvc);
358 
359     MOZ_ALWAYS_SUCCEEDS(
360         obsSvc->RemoveObserver(sObserver, XPCOM_SHUTDOWN_OBSERVER_TOPIC));
361 
362     // We also need to invalidate the observer because AllowToClose can be
363     // triggered by an indirectly related observer, so the observer service
364     // may still keep our observer alive and call Observe on it. This is
365     // possible because observer service snapshots the observer list for given
366     // subject before looping over the list.
367     sObserver->Invalidate();
368 
369     sObserver = nullptr;
370   }
371 }
372 
NS_IMPL_ISUPPORTS(LSDatabase::Observer,nsIObserver)373 NS_IMPL_ISUPPORTS(LSDatabase::Observer, nsIObserver)
374 
375 NS_IMETHODIMP
376 LSDatabase::Observer::Observe(nsISupports* aSubject, const char* aTopic,
377                               const char16_t* aData) {
378   MOZ_ASSERT(NS_IsMainThread());
379   MOZ_ASSERT(!strcmp(aTopic, XPCOM_SHUTDOWN_OBSERVER_TOPIC));
380 
381   if (mInvalidated) {
382     return NS_OK;
383   }
384 
385   MOZ_ASSERT(gLSDatabases);
386 
387   nsTArray<RefPtr<LSDatabase>> databases;
388 
389   for (auto iter = gLSDatabases->ConstIter(); !iter.Done(); iter.Next()) {
390     LSDatabase* database = iter.Data();
391     MOZ_ASSERT(database);
392 
393     databases.AppendElement(database);
394   }
395 
396   for (RefPtr<LSDatabase>& database : databases) {
397     database->RequestAllowToClose();
398   }
399 
400   return NS_OK;
401 }
402 
403 }  // namespace dom
404 }  // namespace mozilla
405