Bug 1795723 - Unified extensions UI should support High Contrast Mode. r=ayeddi,deskt...
[gecko.git] / dom / localstorage / LSObject.cpp
blob7035a5b3a4cf4880ab0dfe3e793d535b917f8891
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/. */
7 #include "LSObject.h"
9 // Local includes
10 #include "ActorsChild.h"
11 #include "LSDatabase.h"
12 #include "LSObserver.h"
14 // Global includes
15 #include <utility>
16 #include "MainThreadUtils.h"
17 #include "mozilla/BasePrincipal.h"
18 #include "mozilla/ErrorResult.h"
19 #include "mozilla/MacroForEach.h"
20 #include "mozilla/OriginAttributes.h"
21 #include "mozilla/Preferences.h"
22 #include "mozilla/RemoteLazyInputStreamThread.h"
23 #include "mozilla/ScopeExit.h"
24 #include "mozilla/SpinEventLoopUntil.h"
25 #include "mozilla/StaticMutex.h"
26 #include "mozilla/StorageAccess.h"
27 #include "mozilla/Unused.h"
28 #include "mozilla/dom/ClientInfo.h"
29 #include "mozilla/dom/Document.h"
30 #include "mozilla/dom/LocalStorageCommon.h"
31 #include "mozilla/dom/PBackgroundLSRequest.h"
32 #include "mozilla/dom/PBackgroundLSSharedTypes.h"
33 #include "mozilla/dom/quota/QuotaManager.h"
34 #include "mozilla/ipc/BackgroundChild.h"
35 #include "mozilla/ipc/BackgroundUtils.h"
36 #include "mozilla/ipc/PBackgroundChild.h"
37 #include "mozilla/ipc/ProcessChild.h"
38 #include "nsCOMPtr.h"
39 #include "nsContentUtils.h"
40 #include "nsDebug.h"
41 #include "nsError.h"
42 #include "nsIEventTarget.h"
43 #include "nsIPrincipal.h"
44 #include "nsIRunnable.h"
45 #include "nsIScriptObjectPrincipal.h"
46 #include "nsISerialEventTarget.h"
47 #include "nsITimer.h"
48 #include "nsPIDOMWindow.h"
49 #include "nsString.h"
50 #include "nsTArray.h"
51 #include "nsTStringRepr.h"
52 #include "nsThread.h"
53 #include "nsThreadUtils.h"
54 #include "nsXULAppAPI.h"
55 #include "nscore.h"
57 /**
58 * Automatically cancel and abort synchronous LocalStorage requests (for example
59 * datastore preparation) if they take this long. We've chosen a value that is
60 * long enough that it is unlikely for the problem to be falsely triggered by
61 * slow system I/O. We've also chosen a value long enough so that automated
62 * tests should time out and fail if LocalStorage hangs. Also, this value is
63 * long enough so that testers can notice the (content process) hang; we want to
64 * know about the hangs, not hide them. On the other hand this value is less
65 * than 60 seconds which is used by nsTerminator to crash a hung main process.
67 #define FAILSAFE_CANCEL_SYNC_OP_MS 50000
69 namespace mozilla::dom {
71 namespace {
73 class RequestHelper;
75 /**
76 * Main-thread helper that implements the blocking logic required by
77 * LocalStorage's synchronous semantics. StartAndReturnResponse pushes an
78 * event queue which is a new event target and spins its nested event loop until
79 * a result is received or an abort is necessary due to a PContent-managed sync
80 * IPC message being received. Note that because the event queue is its own
81 * event target, there is no re-entrancy. Normal main-thread runnables will not
82 * get a chance to run. See StartAndReturnResponse() for info on this choice.
84 * The normal life-cycle of this method looks like:
85 * - Main Thread: LSObject::DoRequestSynchronously creates a RequestHelper and
86 * invokes StartAndReturnResponse(). It pushes the event queue and Dispatches
87 * the RequestHelper to the RemoteLazyInputStream thread.
88 * - RemoteLazyInputStream Thread: RequestHelper::Run is called, invoking
89 * Start() which invokes LSObject::StartRequest, which gets-or-creates the
90 * PBackground actor if necessary (which may dispatch a runnable to the nested
91 * event queue on the main thread), sends LSRequest constructor which is
92 * provided with a callback reference to the RequestHelper. State advances to
93 * ResponsePending.
94 * - RemoteLazyInputStreamThread: LSRequestChild::Recv__delete__ is received,
95 * which invokes RequestHelepr::OnResponse, advancing the state to Finishing
96 * and dispatching RequestHelper to its own nested event target.
97 * - Main Thread: RequestHelper::Run is called, invoking Finish() which advances
98 * the state to Complete and sets mWaiting to false, allowing the nested event
99 * loop being spun by StartAndReturnResponse to cease spinning and return the
100 * received response.
102 * See LocalStorageCommon.h for high-level context and method comments for
103 * low-level details.
105 class RequestHelper final : public Runnable, public LSRequestChildCallback {
106 enum class State {
108 * The RequestHelper has been created and dispatched to the
109 * RemoteLazyInputStream Thread.
111 Initial,
113 * Start() has been invoked on the RemoteLazyInputStream Thread and
114 * LSObject::StartRequest has been invoked from there, sending an IPC
115 * message to PBackground to service the request. We stay in this state
116 * until a response is received.
118 ResponsePending,
120 * A response has been received and RequestHelper has been dispatched back
121 * to the nested event loop to call Finish().
123 Finishing,
125 * Finish() has been called on the main thread. The nested event loop will
126 * terminate imminently and the received response returned to the caller of
127 * StartAndReturnResponse.
129 Complete
132 // The object we are issuing a request on behalf of. Present because of the
133 // need to invoke LSObject::StartRequest off the main thread. Dropped on
134 // return to the main-thread in Finish().
135 RefPtr<LSObject> mObject;
136 // The thread the RequestHelper was created on. This should be the main
137 // thread.
138 nsCOMPtr<nsIEventTarget> mOwningEventTarget;
139 // The pushed event queue that we use to spin the event loop without
140 // processing any of the events dispatched at the mOwningEventTarget (which
141 // would result in re-entrancy and violate LocalStorage semantics).
142 nsCOMPtr<nsISerialEventTarget> mNestedEventTarget;
143 // The IPC actor handling the request with standard IPC allocation rules.
144 // Our reference is nulled in OnResponse which corresponds to the actor's
145 // __destroy__ method.
146 LSRequestChild* mActor;
147 const LSRequestParams mParams;
148 LSRequestResponse mResponse;
149 nsresult mResultCode;
150 State mState;
151 // Control flag for the nested event loop; once set to false, the loop ends.
152 bool mWaiting;
154 public:
155 RequestHelper(LSObject* aObject, const LSRequestParams& aParams)
156 : Runnable("dom::RequestHelper"),
157 mObject(aObject),
158 mOwningEventTarget(GetCurrentEventTarget()),
159 mActor(nullptr),
160 mParams(aParams),
161 mResultCode(NS_OK),
162 mState(State::Initial),
163 mWaiting(true) {}
165 bool IsOnOwningThread() const {
166 MOZ_ASSERT(mOwningEventTarget);
168 bool current;
169 return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)) &&
170 current;
173 void AssertIsOnOwningThread() const {
174 MOZ_ASSERT(NS_IsMainThread());
175 MOZ_ASSERT(IsOnOwningThread());
178 nsresult StartAndReturnResponse(LSRequestResponse& aResponse);
180 private:
181 ~RequestHelper() = default;
183 nsresult Start();
185 void Finish();
187 NS_DECL_ISUPPORTS_INHERITED
189 NS_DECL_NSIRUNNABLE
191 // LSRequestChildCallback
192 void OnResponse(const LSRequestResponse& aResponse) override;
195 void AssertExplicitSnapshotInvariants(const LSObject& aObject) {
196 // Can be only called if the mInExplicitSnapshot flag is true.
197 // An explicit snapshot must have been created.
198 MOZ_ASSERT(aObject.InExplicitSnapshot());
200 // If an explicit snapshot has been created then mDatabase must be not null.
201 // DropDatabase could be called in the meatime, but that must be preceded by
202 // Disconnect which sets mInExplicitSnapshot to false. EnsureDatabase could
203 // be called in the meantime too, but that can't set mDatabase to null or to
204 // a new value. See the comment below.
205 MOZ_ASSERT(aObject.DatabaseStrongRef());
207 // Existence of a snapshot prevents the database from allowing to close. See
208 // LSDatabase::RequestAllowToClose and LSDatabase::NoteFinishedSnapshot.
209 // If the database is not allowed to close then mDatabase could not have been
210 // nulled out or set to a new value. See EnsureDatabase.
211 MOZ_ASSERT(!aObject.DatabaseStrongRef()->IsAllowedToClose());
214 } // namespace
216 LSObject::LSObject(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
217 nsIPrincipal* aStoragePrincipal)
218 : Storage(aWindow, aPrincipal, aStoragePrincipal),
219 mPrivateBrowsingId(0),
220 mInExplicitSnapshot(false) {
221 AssertIsOnOwningThread();
222 MOZ_ASSERT(NextGenLocalStorageEnabled());
225 LSObject::~LSObject() {
226 AssertIsOnOwningThread();
228 DropObserver();
231 // static
232 nsresult LSObject::CreateForWindow(nsPIDOMWindowInner* aWindow,
233 Storage** aStorage) {
234 MOZ_ASSERT(NS_IsMainThread());
235 MOZ_ASSERT(aWindow);
236 MOZ_ASSERT(aStorage);
237 MOZ_ASSERT(NextGenLocalStorageEnabled());
238 MOZ_ASSERT(StorageAllowedForWindow(aWindow) != StorageAccess::eDeny);
240 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
241 MOZ_ASSERT(sop);
243 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
244 if (NS_WARN_IF(!principal)) {
245 return NS_ERROR_FAILURE;
248 nsCOMPtr<nsIPrincipal> storagePrincipal = sop->GetEffectiveStoragePrincipal();
249 if (NS_WARN_IF(!storagePrincipal)) {
250 return NS_ERROR_FAILURE;
253 if (principal->IsSystemPrincipal()) {
254 return NS_ERROR_NOT_AVAILABLE;
257 // localStorage is not available on some pages on purpose, for example
258 // about:home. Match the old implementation by using GenerateOriginKey
259 // for the check.
260 nsCString originAttrSuffix;
261 nsCString originKey;
262 nsresult rv = storagePrincipal->GetStorageOriginKey(originKey);
263 storagePrincipal->OriginAttributesRef().CreateSuffix(originAttrSuffix);
265 if (NS_FAILED(rv)) {
266 return NS_ERROR_NOT_AVAILABLE;
269 auto principalInfo = MakeUnique<PrincipalInfo>();
270 rv = PrincipalToPrincipalInfo(principal, principalInfo.get());
271 if (NS_WARN_IF(NS_FAILED(rv))) {
272 return rv;
275 MOZ_ASSERT(principalInfo->type() == PrincipalInfo::TContentPrincipalInfo);
277 auto storagePrincipalInfo = MakeUnique<PrincipalInfo>();
278 rv = PrincipalToPrincipalInfo(storagePrincipal, storagePrincipalInfo.get());
279 if (NS_WARN_IF(NS_FAILED(rv))) {
280 return rv;
283 MOZ_ASSERT(storagePrincipalInfo->type() ==
284 PrincipalInfo::TContentPrincipalInfo);
286 if (NS_WARN_IF(
287 !quota::QuotaManager::IsPrincipalInfoValid(*storagePrincipalInfo))) {
288 return NS_ERROR_FAILURE;
291 #ifdef DEBUG
292 QM_TRY_INSPECT(
293 const auto& principalMetadata,
294 quota::QuotaManager::GetInfoFromPrincipal(storagePrincipal.get()));
296 MOZ_ASSERT(originAttrSuffix == principalMetadata.mSuffix);
298 const auto& origin = principalMetadata.mOrigin;
299 #else
300 QM_TRY_INSPECT(
301 const auto& origin,
302 quota::QuotaManager::GetOriginFromPrincipal(storagePrincipal.get()));
303 #endif
305 uint32_t privateBrowsingId;
306 rv = storagePrincipal->GetPrivateBrowsingId(&privateBrowsingId);
307 if (NS_WARN_IF(NS_FAILED(rv))) {
308 return rv;
311 Maybe<ClientInfo> clientInfo = aWindow->GetClientInfo();
312 if (clientInfo.isNothing()) {
313 return NS_ERROR_FAILURE;
316 Maybe<nsID> clientId = Some(clientInfo.ref().Id());
318 Maybe<PrincipalInfo> clientPrincipalInfo =
319 Some(clientInfo.ref().PrincipalInfo());
321 nsString documentURI;
322 if (nsCOMPtr<Document> doc = aWindow->GetExtantDoc()) {
323 rv = doc->GetDocumentURI(documentURI);
324 if (NS_WARN_IF(NS_FAILED(rv))) {
325 return rv;
329 RefPtr<LSObject> object = new LSObject(aWindow, principal, storagePrincipal);
330 object->mPrincipalInfo = std::move(principalInfo);
331 object->mStoragePrincipalInfo = std::move(storagePrincipalInfo);
332 object->mPrivateBrowsingId = privateBrowsingId;
333 object->mClientId = clientId;
334 object->mClientPrincipalInfo = clientPrincipalInfo;
335 object->mOrigin = origin;
336 object->mOriginKey = originKey;
337 object->mDocumentURI = documentURI;
339 object.forget(aStorage);
340 return NS_OK;
343 // static
344 nsresult LSObject::CreateForPrincipal(nsPIDOMWindowInner* aWindow,
345 nsIPrincipal* aPrincipal,
346 nsIPrincipal* aStoragePrincipal,
347 const nsAString& aDocumentURI,
348 bool aPrivate, LSObject** aObject) {
349 MOZ_ASSERT(NS_IsMainThread());
350 MOZ_ASSERT(aPrincipal);
351 MOZ_ASSERT(aStoragePrincipal);
352 MOZ_ASSERT(aObject);
354 nsCString originAttrSuffix;
355 nsCString originKey;
356 nsresult rv = aStoragePrincipal->GetStorageOriginKey(originKey);
357 aStoragePrincipal->OriginAttributesRef().CreateSuffix(originAttrSuffix);
358 if (NS_FAILED(rv)) {
359 return NS_ERROR_NOT_AVAILABLE;
362 auto principalInfo = MakeUnique<PrincipalInfo>();
363 rv = PrincipalToPrincipalInfo(aPrincipal, principalInfo.get());
364 if (NS_WARN_IF(NS_FAILED(rv))) {
365 return rv;
368 MOZ_ASSERT(principalInfo->type() == PrincipalInfo::TContentPrincipalInfo ||
369 principalInfo->type() == PrincipalInfo::TSystemPrincipalInfo);
371 auto storagePrincipalInfo = MakeUnique<PrincipalInfo>();
372 rv = PrincipalToPrincipalInfo(aStoragePrincipal, storagePrincipalInfo.get());
373 if (NS_WARN_IF(NS_FAILED(rv))) {
374 return rv;
377 MOZ_ASSERT(
378 storagePrincipalInfo->type() == PrincipalInfo::TContentPrincipalInfo ||
379 storagePrincipalInfo->type() == PrincipalInfo::TSystemPrincipalInfo);
381 if (NS_WARN_IF(
382 !quota::QuotaManager::IsPrincipalInfoValid(*storagePrincipalInfo))) {
383 return NS_ERROR_FAILURE;
386 #ifdef DEBUG
387 QM_TRY_INSPECT(
388 const auto& principalMetadata,
389 ([&storagePrincipalInfo,
390 &aPrincipal]() -> Result<quota::PrincipalMetadata, nsresult> {
391 if (storagePrincipalInfo->type() ==
392 PrincipalInfo::TSystemPrincipalInfo) {
393 return quota::QuotaManager::GetInfoForChrome();
396 QM_TRY_RETURN(quota::QuotaManager::GetInfoFromPrincipal(aPrincipal));
397 }()));
399 MOZ_ASSERT(originAttrSuffix == principalMetadata.mSuffix);
401 const auto& origin = principalMetadata.mOrigin;
402 #else
403 QM_TRY_INSPECT(
404 const auto& origin, ([&storagePrincipalInfo,
405 &aPrincipal]() -> Result<nsAutoCString, nsresult> {
406 if (storagePrincipalInfo->type() ==
407 PrincipalInfo::TSystemPrincipalInfo) {
408 return nsAutoCString{quota::QuotaManager::GetOriginForChrome()};
411 QM_TRY_RETURN(quota::QuotaManager::GetOriginFromPrincipal(aPrincipal));
412 }()));
413 #endif
415 Maybe<nsID> clientId;
416 if (aWindow) {
417 Maybe<ClientInfo> clientInfo = aWindow->GetClientInfo();
418 if (clientInfo.isNothing()) {
419 return NS_ERROR_FAILURE;
422 clientId = Some(clientInfo.ref().Id());
423 } else if (Preferences::GetBool("dom.storage.client_validation")) {
424 return NS_ERROR_FAILURE;
427 RefPtr<LSObject> object =
428 new LSObject(aWindow, aPrincipal, aStoragePrincipal);
429 object->mPrincipalInfo = std::move(principalInfo);
430 object->mStoragePrincipalInfo = std::move(storagePrincipalInfo);
431 object->mPrivateBrowsingId = aPrivate ? 1 : 0;
432 object->mClientId = clientId;
433 object->mOrigin = origin;
434 object->mOriginKey = originKey;
435 object->mDocumentURI = aDocumentURI;
437 object.forget(aObject);
438 return NS_OK;
439 } // namespace dom
441 LSRequestChild* LSObject::StartRequest(nsIEventTarget* aMainEventTarget,
442 const LSRequestParams& aParams,
443 LSRequestChildCallback* aCallback) {
444 AssertIsOnDOMFileThread();
446 mozilla::ipc::PBackgroundChild* backgroundActor =
447 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
448 if (NS_WARN_IF(!backgroundActor)) {
449 return nullptr;
452 LSRequestChild* actor = new LSRequestChild();
454 if (!backgroundActor->SendPBackgroundLSRequestConstructor(actor, aParams)) {
455 return nullptr;
458 // Must set callback after calling SendPBackgroundLSRequestConstructor since
459 // it can be called synchronously when SendPBackgroundLSRequestConstructor
460 // fails.
461 actor->SetCallback(aCallback);
463 return actor;
466 Storage::StorageType LSObject::Type() const {
467 AssertIsOnOwningThread();
469 return eLocalStorage;
472 bool LSObject::IsForkOf(const Storage* aStorage) const {
473 AssertIsOnOwningThread();
474 MOZ_ASSERT(aStorage);
476 if (aStorage->Type() != eLocalStorage) {
477 return false;
480 return static_cast<const LSObject*>(aStorage)->mOrigin == mOrigin;
483 int64_t LSObject::GetOriginQuotaUsage() const {
484 AssertIsOnOwningThread();
486 // It's not necessary to return an actual value here. This method is
487 // implemented only because the SessionStore currently needs it to cap the
488 // amount of data it persists to disk (via nsIDOMWindowUtils.getStorageUsage).
489 // Any callers that want to know about storage usage should be asking
490 // QuotaManager directly.
492 // Note: This may change as LocalStorage is repurposed to be the new
493 // SessionStorage backend.
494 return 0;
497 void LSObject::Disconnect() {
498 // Explicit snapshots which were not ended in JS, must be ended here while
499 // IPC is still available. We can't do that in DropDatabase because actors
500 // may have been destroyed already at that point.
501 if (mInExplicitSnapshot) {
502 AssertExplicitSnapshotInvariants(*this);
504 nsresult rv = mDatabase->EndExplicitSnapshot();
505 Unused << NS_WARN_IF(NS_FAILED(rv));
507 mInExplicitSnapshot = false;
511 uint32_t LSObject::GetLength(nsIPrincipal& aSubjectPrincipal,
512 ErrorResult& aError) {
513 AssertIsOnOwningThread();
515 if (!CanUseStorage(aSubjectPrincipal)) {
516 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
517 return 0;
520 nsresult rv = EnsureDatabase();
521 if (NS_WARN_IF(NS_FAILED(rv))) {
522 aError.Throw(rv);
523 return 0;
526 uint32_t result;
527 rv = mDatabase->GetLength(this, &result);
528 if (NS_WARN_IF(NS_FAILED(rv))) {
529 aError.Throw(rv);
530 return 0;
533 return result;
536 void LSObject::Key(uint32_t aIndex, nsAString& aResult,
537 nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
538 AssertIsOnOwningThread();
540 if (!CanUseStorage(aSubjectPrincipal)) {
541 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
542 return;
545 nsresult rv = EnsureDatabase();
546 if (NS_WARN_IF(NS_FAILED(rv))) {
547 aError.Throw(rv);
548 return;
551 nsString result;
552 rv = mDatabase->GetKey(this, aIndex, result);
553 if (NS_WARN_IF(NS_FAILED(rv))) {
554 aError.Throw(rv);
555 return;
558 aResult = result;
561 void LSObject::GetItem(const nsAString& aKey, nsAString& aResult,
562 nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
563 AssertIsOnOwningThread();
565 if (!CanUseStorage(aSubjectPrincipal)) {
566 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
567 return;
570 nsresult rv = EnsureDatabase();
571 if (NS_WARN_IF(NS_FAILED(rv))) {
572 aError.Throw(rv);
573 return;
576 nsString result;
577 rv = mDatabase->GetItem(this, aKey, result);
578 if (NS_WARN_IF(NS_FAILED(rv))) {
579 aError.Throw(rv);
580 return;
583 aResult = result;
586 void LSObject::GetSupportedNames(nsTArray<nsString>& aNames) {
587 AssertIsOnOwningThread();
589 if (!CanUseStorage(*nsContentUtils::SubjectPrincipal())) {
590 // Return just an empty array.
591 aNames.Clear();
592 return;
595 nsresult rv = EnsureDatabase();
596 if (NS_WARN_IF(NS_FAILED(rv))) {
597 return;
600 rv = mDatabase->GetKeys(this, aNames);
601 if (NS_WARN_IF(NS_FAILED(rv))) {
602 return;
606 void LSObject::SetItem(const nsAString& aKey, const nsAString& aValue,
607 nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
608 AssertIsOnOwningThread();
610 if (!CanUseStorage(aSubjectPrincipal)) {
611 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
612 return;
615 nsresult rv = EnsureDatabase();
616 if (NS_WARN_IF(NS_FAILED(rv))) {
617 aError.Throw(rv);
618 return;
621 LSNotifyInfo info;
622 rv = mDatabase->SetItem(this, aKey, aValue, info);
623 if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
624 rv = NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
626 if (NS_WARN_IF(NS_FAILED(rv))) {
627 aError.Throw(rv);
628 return;
631 if (info.changed()) {
632 OnChange(aKey, info.oldValue(), aValue);
636 void LSObject::RemoveItem(const nsAString& aKey,
637 nsIPrincipal& aSubjectPrincipal,
638 ErrorResult& aError) {
639 AssertIsOnOwningThread();
641 if (!CanUseStorage(aSubjectPrincipal)) {
642 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
643 return;
646 nsresult rv = EnsureDatabase();
647 if (NS_WARN_IF(NS_FAILED(rv))) {
648 aError.Throw(rv);
649 return;
652 LSNotifyInfo info;
653 rv = mDatabase->RemoveItem(this, aKey, info);
654 if (NS_WARN_IF(NS_FAILED(rv))) {
655 aError.Throw(rv);
656 return;
659 if (info.changed()) {
660 OnChange(aKey, info.oldValue(), VoidString());
664 void LSObject::Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
665 AssertIsOnOwningThread();
667 if (!CanUseStorage(aSubjectPrincipal)) {
668 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
669 return;
672 nsresult rv = EnsureDatabase();
673 if (NS_WARN_IF(NS_FAILED(rv))) {
674 aError.Throw(rv);
675 return;
678 LSNotifyInfo info;
679 rv = mDatabase->Clear(this, info);
680 if (NS_WARN_IF(NS_FAILED(rv))) {
681 aError.Throw(rv);
682 return;
685 if (info.changed()) {
686 OnChange(VoidString(), VoidString(), VoidString());
690 void LSObject::Open(nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
691 AssertIsOnOwningThread();
693 if (!CanUseStorage(aSubjectPrincipal)) {
694 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
695 return;
698 nsresult rv = EnsureDatabase();
699 if (NS_WARN_IF(NS_FAILED(rv))) {
700 aError.Throw(rv);
701 return;
705 void LSObject::Close(nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
706 AssertIsOnOwningThread();
708 if (!CanUseStorage(aSubjectPrincipal)) {
709 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
710 return;
713 DropDatabase();
716 void LSObject::BeginExplicitSnapshot(nsIPrincipal& aSubjectPrincipal,
717 ErrorResult& aError) {
718 AssertIsOnOwningThread();
720 if (!CanUseStorage(aSubjectPrincipal)) {
721 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
722 return;
725 if (mInExplicitSnapshot) {
726 aError.Throw(NS_ERROR_ALREADY_INITIALIZED);
727 return;
730 nsresult rv = EnsureDatabase();
731 if (NS_WARN_IF(NS_FAILED(rv))) {
732 aError.Throw(rv);
733 return;
736 rv = mDatabase->BeginExplicitSnapshot(this);
737 if (NS_WARN_IF(NS_FAILED(rv))) {
738 aError.Throw(rv);
739 return;
742 mInExplicitSnapshot = true;
745 void LSObject::CheckpointExplicitSnapshot(nsIPrincipal& aSubjectPrincipal,
746 ErrorResult& aError) {
747 AssertIsOnOwningThread();
749 if (!CanUseStorage(aSubjectPrincipal)) {
750 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
751 return;
754 if (!mInExplicitSnapshot) {
755 aError.Throw(NS_ERROR_NOT_INITIALIZED);
756 return;
759 AssertExplicitSnapshotInvariants(*this);
761 nsresult rv = mDatabase->CheckpointExplicitSnapshot();
762 if (NS_WARN_IF(NS_FAILED(rv))) {
763 aError.Throw(rv);
764 return;
768 void LSObject::EndExplicitSnapshot(nsIPrincipal& aSubjectPrincipal,
769 ErrorResult& aError) {
770 AssertIsOnOwningThread();
772 if (!CanUseStorage(aSubjectPrincipal)) {
773 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
774 return;
777 if (!mInExplicitSnapshot) {
778 aError.Throw(NS_ERROR_NOT_INITIALIZED);
779 return;
782 AssertExplicitSnapshotInvariants(*this);
784 nsresult rv = mDatabase->EndExplicitSnapshot();
785 if (NS_WARN_IF(NS_FAILED(rv))) {
786 aError.Throw(rv);
787 return;
790 mInExplicitSnapshot = false;
793 bool LSObject::GetHasSnapshot(nsIPrincipal& aSubjectPrincipal,
794 ErrorResult& aError) {
795 AssertIsOnOwningThread();
797 if (!CanUseStorage(aSubjectPrincipal)) {
798 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
799 return false;
802 // We can't call `HasSnapshot` on the database if it's being closed, but we
803 // know that a database which is being closed can't have a snapshot, so we
804 // return false in that case directly here.
805 if (!mDatabase || mDatabase->IsAllowedToClose()) {
806 return false;
809 return mDatabase->HasSnapshot();
812 int64_t LSObject::GetSnapshotUsage(nsIPrincipal& aSubjectPrincipal,
813 ErrorResult& aError) {
814 AssertIsOnOwningThread();
816 if (!CanUseStorage(aSubjectPrincipal)) {
817 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
818 return 0;
821 if (!mDatabase || mDatabase->IsAllowedToClose()) {
822 aError.Throw(NS_ERROR_NOT_AVAILABLE);
823 return 0;
826 if (!mDatabase->HasSnapshot()) {
827 aError.Throw(NS_ERROR_NOT_AVAILABLE);
828 return 0;
831 return mDatabase->GetSnapshotUsage();
834 NS_IMPL_ADDREF_INHERITED(LSObject, Storage)
835 NS_IMPL_RELEASE_INHERITED(LSObject, Storage)
837 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LSObject)
838 NS_INTERFACE_MAP_END_INHERITING(Storage)
840 NS_IMPL_CYCLE_COLLECTION_CLASS(LSObject)
842 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(LSObject, Storage)
843 tmp->AssertIsOnOwningThread();
844 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
846 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(LSObject, Storage)
847 tmp->AssertIsOnOwningThread();
848 tmp->DropDatabase();
849 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
851 nsresult LSObject::DoRequestSynchronously(const LSRequestParams& aParams,
852 LSRequestResponse& aResponse) {
853 // We don't need this yet, but once the request successfully finishes, it's
854 // too late to initialize PBackground child on the owning thread, because
855 // it can fail and parent would keep an extra strong ref to the datastore or
856 // observer.
857 mozilla::ipc::PBackgroundChild* backgroundActor =
858 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
859 if (NS_WARN_IF(!backgroundActor)) {
860 return NS_ERROR_FAILURE;
863 RefPtr<RequestHelper> helper = new RequestHelper(this, aParams);
865 // This will start and finish the request on the RemoteLazyInputStream thread.
866 // The owning thread is synchronously blocked while the request is
867 // asynchronously processed on the RemoteLazyInputStream thread.
868 nsresult rv = helper->StartAndReturnResponse(aResponse);
869 if (NS_WARN_IF(NS_FAILED(rv))) {
870 return rv;
873 if (aResponse.type() == LSRequestResponse::Tnsresult) {
874 nsresult errorCode = aResponse.get_nsresult();
876 if (errorCode == NS_ERROR_FILE_NO_DEVICE_SPACE) {
877 errorCode = NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
880 return errorCode;
883 return NS_OK;
886 nsresult LSObject::EnsureDatabase() {
887 AssertIsOnOwningThread();
889 if (mDatabase && !mDatabase->IsAllowedToClose()) {
890 return NS_OK;
893 mDatabase = LSDatabase::Get(mOrigin);
895 if (mDatabase) {
896 MOZ_ASSERT(!mDatabase->IsAllowedToClose());
897 return NS_OK;
900 // We don't need this yet, but once the request successfully finishes, it's
901 // too late to initialize PBackground child on the owning thread, because
902 // it can fail and parent would keep an extra strong ref to the datastore.
903 mozilla::ipc::PBackgroundChild* backgroundActor =
904 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
905 if (NS_WARN_IF(!backgroundActor)) {
906 return NS_ERROR_FAILURE;
909 LSRequestCommonParams commonParams;
910 commonParams.principalInfo() = *mPrincipalInfo;
911 commonParams.storagePrincipalInfo() = *mStoragePrincipalInfo;
912 commonParams.originKey() = mOriginKey;
914 LSRequestPrepareDatastoreParams params;
915 params.commonParams() = commonParams;
916 params.clientId() = mClientId;
917 params.clientPrincipalInfo() = mClientPrincipalInfo;
919 LSRequestResponse response;
921 nsresult rv = DoRequestSynchronously(params, response);
922 if (NS_WARN_IF(NS_FAILED(rv))) {
923 return rv;
926 MOZ_ASSERT(response.type() ==
927 LSRequestResponse::TLSRequestPrepareDatastoreResponse);
929 const LSRequestPrepareDatastoreResponse& prepareDatastoreResponse =
930 response.get_LSRequestPrepareDatastoreResponse();
932 uint64_t datastoreId = prepareDatastoreResponse.datastoreId();
934 // The datastore is now ready on the parent side (prepared by the asynchronous
935 // request on the RemoteLazyInputStream thread).
936 // Let's create a direct connection to the datastore (through a database
937 // actor) from the owning thread.
938 // Note that we now can't error out, otherwise parent will keep an extra
939 // strong reference to the datastore.
941 RefPtr<LSDatabase> database = new LSDatabase(mOrigin);
943 LSDatabaseChild* actor = new LSDatabaseChild(database);
945 MOZ_ALWAYS_TRUE(backgroundActor->SendPBackgroundLSDatabaseConstructor(
946 actor, *mStoragePrincipalInfo, mPrivateBrowsingId, datastoreId));
948 database->SetActor(actor);
950 mDatabase = std::move(database);
952 return NS_OK;
955 void LSObject::DropDatabase() {
956 AssertIsOnOwningThread();
958 mDatabase = nullptr;
961 nsresult LSObject::EnsureObserver() {
962 AssertIsOnOwningThread();
964 if (mObserver) {
965 return NS_OK;
968 mObserver = LSObserver::Get(mOrigin);
970 if (mObserver) {
971 return NS_OK;
974 LSRequestPrepareObserverParams params;
975 params.principalInfo() = *mPrincipalInfo;
976 params.storagePrincipalInfo() = *mStoragePrincipalInfo;
977 params.clientId() = mClientId;
978 params.clientPrincipalInfo() = mClientPrincipalInfo;
980 LSRequestResponse response;
982 nsresult rv = DoRequestSynchronously(params, response);
983 if (NS_WARN_IF(NS_FAILED(rv))) {
984 return rv;
987 MOZ_ASSERT(response.type() ==
988 LSRequestResponse::TLSRequestPrepareObserverResponse);
990 const LSRequestPrepareObserverResponse& prepareObserverResponse =
991 response.get_LSRequestPrepareObserverResponse();
993 uint64_t observerId = prepareObserverResponse.observerId();
995 // The obsserver is now ready on the parent side (prepared by the asynchronous
996 // request on the RemoteLazyInputStream thread).
997 // Let's create a direct connection to the observer (through an observer
998 // actor) from the owning thread.
999 // Note that we now can't error out, otherwise parent will keep an extra
1000 // strong reference to the observer.
1002 mozilla::ipc::PBackgroundChild* backgroundActor =
1003 mozilla::ipc::BackgroundChild::GetForCurrentThread();
1004 MOZ_ASSERT(backgroundActor);
1006 RefPtr<LSObserver> observer = new LSObserver(mOrigin);
1008 LSObserverChild* actor = new LSObserverChild(observer);
1010 MOZ_ALWAYS_TRUE(
1011 backgroundActor->SendPBackgroundLSObserverConstructor(actor, observerId));
1013 observer->SetActor(actor);
1015 mObserver = std::move(observer);
1017 return NS_OK;
1020 void LSObject::DropObserver() {
1021 AssertIsOnOwningThread();
1023 if (mObserver) {
1024 mObserver = nullptr;
1028 void LSObject::OnChange(const nsAString& aKey, const nsAString& aOldValue,
1029 const nsAString& aNewValue) {
1030 AssertIsOnOwningThread();
1032 NotifyChange(/* aStorage */ this, StoragePrincipal(), aKey, aOldValue,
1033 aNewValue, /* aStorageType */ kLocalStorageType, mDocumentURI,
1034 /* aIsPrivate */ !!mPrivateBrowsingId,
1035 /* aImmediateDispatch */ false);
1038 void LSObject::LastRelease() {
1039 AssertIsOnOwningThread();
1041 DropDatabase();
1044 nsresult RequestHelper::StartAndReturnResponse(LSRequestResponse& aResponse) {
1045 AssertIsOnOwningThread();
1047 // Normally, we would use the standard way of blocking the thread using
1048 // a monitor.
1049 // The problem is that BackgroundChild::GetOrCreateForCurrentThread()
1050 // called on the RemoteLazyInputStream thread may dispatch a runnable to the
1051 // main thread to finish initialization of PBackground. A monitor would block
1052 // the main thread and the runnable would never get executed causing the
1053 // helper to be stuck in a wait loop.
1054 // However, BackgroundChild::GetOrCreateForCurrentThread() supports passing
1055 // a custom main event target, so we can create a nested event target and
1056 // spin the event loop. Nothing can dispatch to the nested event target
1057 // except BackgroundChild::GetOrCreateForCurrentThread(), so spinning of the
1058 // event loop can't fire any other events.
1059 // This way the thread is synchronously blocked in a safe manner and the
1060 // runnable gets executed.
1062 auto thread = static_cast<nsThread*>(NS_GetCurrentThread());
1064 const nsLocalExecutionGuard localExecution(thread->EnterLocalExecution());
1065 mNestedEventTarget = localExecution.GetEventTarget();
1066 MOZ_ASSERT(mNestedEventTarget);
1068 nsCOMPtr<nsIEventTarget> domFileThread =
1069 RemoteLazyInputStreamThread::GetOrCreate();
1070 if (NS_WARN_IF(!domFileThread)) {
1071 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
1074 nsresult rv;
1077 rv = domFileThread->Dispatch(this, NS_DISPATCH_NORMAL);
1078 if (NS_WARN_IF(NS_FAILED(rv))) {
1079 return rv;
1082 nsCOMPtr<nsITimer> timer = NS_NewTimer();
1084 MOZ_ALWAYS_SUCCEEDS(timer->SetTarget(domFileThread));
1086 auto cancelRequest = [](nsITimer* aTimer, void* aClosure) {
1087 // The request is taking too much time. At this point we don't care
1088 // about the result anymore so we can cancel the request.
1090 // However, we don't abort the event loop spinning before the
1091 // request is actually finished because that would cause races
1092 // between the current thread and DOM File thread. Instead, we send
1093 // the cancel message to the parent and wait for the request to
1094 // finish like in the normal case when the request is successfully
1095 // finished on time. OnResponse is called as the final step in both
1096 // cases.
1098 auto helper = static_cast<RequestHelper*>(aClosure);
1100 LSRequestChild* actor = helper->mActor;
1102 // Start() could fail or OnResponse was already called, so we need
1103 // to check if actor is not null. The actor can also be in the
1104 // final (finishing) state, in that case we are not allowed to send
1105 // the cancel message and it wouldn't make any sense because the
1106 // request is about to be destroyed anyway.
1107 if (actor && !actor->Finishing()) {
1108 actor->SendCancel();
1112 MOZ_ALWAYS_SUCCEEDS(timer->InitWithNamedFuncCallback(
1113 cancelRequest, this, FAILSAFE_CANCEL_SYNC_OP_MS,
1114 nsITimer::TYPE_ONE_SHOT,
1115 "RequestHelper::StartAndReturnResponse::SpinEventLoopTimer"));
1117 MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
1118 "RequestHelper::StartAndReturnResponse"_ns,
1119 [&]() {
1120 if (mozilla::ipc::ProcessChild::ExpectingShutdown()) {
1121 MOZ_ALWAYS_SUCCEEDS(timer->Cancel());
1122 MOZ_ALWAYS_SUCCEEDS(timer->InitWithNamedFuncCallback(
1123 cancelRequest, this, 0, nsITimer::TYPE_ONE_SHOT,
1124 "RequestHelper::StartAndReturnResponse::SpinEventLoopAbort"));
1127 return !mWaiting;
1129 thread));
1131 MOZ_ALWAYS_SUCCEEDS(timer->Cancel());
1134 // We can touch all member variables (including mResponse, mResultCode or
1135 // mState) here because the request must have been finished.
1136 MOZ_ASSERT(mState == State::Complete);
1138 // Additionally, mWaiting is only ever touched on the owning thread.
1139 MOZ_ASSERT(!mWaiting);
1141 // localExecution will be destructed when we leave this scope. We need to
1142 // clear the nested event target before that happens, so we will know if
1143 // something still tries to incorrectly dispatch runnables to it.
1144 mNestedEventTarget = nullptr;
1147 if (NS_WARN_IF(NS_FAILED(mResultCode))) {
1148 return mResultCode;
1151 aResponse = std::move(mResponse);
1152 return NS_OK;
1155 nsresult RequestHelper::Start() {
1156 AssertIsOnDOMFileThread();
1157 MOZ_ASSERT(mState == State::Initial);
1159 mState = State::ResponsePending;
1161 LSRequestChild* actor =
1162 mObject->StartRequest(mNestedEventTarget, mParams, this);
1163 if (NS_WARN_IF(!actor)) {
1164 return NS_ERROR_FAILURE;
1167 mActor = actor;
1169 return NS_OK;
1172 void RequestHelper::Finish() {
1173 AssertIsOnOwningThread();
1174 MOZ_ASSERT(mState == State::Finishing);
1176 mObject = nullptr;
1178 mWaiting = false;
1180 mState = State::Complete;
1183 NS_IMPL_ISUPPORTS_INHERITED0(RequestHelper, Runnable)
1185 NS_IMETHODIMP
1186 RequestHelper::Run() {
1187 nsresult rv;
1189 switch (mState) {
1190 case State::Initial:
1191 rv = Start();
1192 break;
1194 case State::Finishing:
1195 Finish();
1196 return NS_OK;
1198 default:
1199 MOZ_CRASH("Bad state!");
1202 if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::Finishing) {
1203 if (NS_SUCCEEDED(mResultCode)) {
1204 mResultCode = rv;
1207 mState = State::Finishing;
1209 if (IsOnOwningThread()) {
1210 Finish();
1211 } else {
1212 MOZ_ALWAYS_SUCCEEDS(
1213 mNestedEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
1217 return NS_OK;
1220 void RequestHelper::OnResponse(const LSRequestResponse& aResponse) {
1221 AssertIsOnDOMFileThread();
1222 MOZ_ASSERT(mState == State::ResponsePending);
1224 mActor = nullptr;
1226 mResponse = aResponse;
1228 mState = State::Finishing;
1229 MOZ_ALWAYS_SUCCEEDS(mNestedEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
1232 } // namespace mozilla::dom