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