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