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/. */
10 #include "ActorsChild.h"
11 #include "LSDatabase.h"
12 #include "LSObserver.h"
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"
39 #include "nsContentUtils.h"
42 #include "nsIEventTarget.h"
43 #include "nsIPrincipal.h"
44 #include "nsIRunnable.h"
45 #include "nsIScriptObjectPrincipal.h"
46 #include "nsISerialEventTarget.h"
48 #include "nsPIDOMWindow.h"
51 #include "nsTStringRepr.h"
53 #include "nsThreadUtils.h"
54 #include "nsXULAppAPI.h"
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
{
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
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
102 * See LocalStorageCommon.h for high-level context and method comments for
105 class RequestHelper final
: public Runnable
, public LSRequestChildCallback
{
108 * The RequestHelper has been created and dispatched to the
109 * RemoteLazyInputStream Thread.
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.
120 * A response has been received and RequestHelper has been dispatched back
121 * to the nested event loop to call Finish().
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.
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
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
;
151 // Control flag for the nested event loop; once set to false, the loop ends.
155 RequestHelper(LSObject
* aObject
, const LSRequestParams
& aParams
)
156 : Runnable("dom::RequestHelper"),
158 mOwningEventTarget(GetCurrentEventTarget()),
162 mState(State::Initial
),
165 bool IsOnOwningThread() const {
166 MOZ_ASSERT(mOwningEventTarget
);
169 return NS_SUCCEEDED(mOwningEventTarget
->IsOnCurrentThread(¤t
)) &&
173 void AssertIsOnOwningThread() const {
174 MOZ_ASSERT(NS_IsMainThread());
175 MOZ_ASSERT(IsOnOwningThread());
178 nsresult
StartAndReturnResponse(LSRequestResponse
& aResponse
);
181 ~RequestHelper() = default;
187 NS_DECL_ISUPPORTS_INHERITED
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());
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();
232 nsresult
LSObject::CreateForWindow(nsPIDOMWindowInner
* aWindow
,
233 Storage
** aStorage
) {
234 MOZ_ASSERT(NS_IsMainThread());
236 MOZ_ASSERT(aStorage
);
237 MOZ_ASSERT(NextGenLocalStorageEnabled());
238 MOZ_ASSERT(StorageAllowedForWindow(aWindow
) != StorageAccess::eDeny
);
240 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(aWindow
);
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
260 nsCString originAttrSuffix
;
262 nsresult rv
= storagePrincipal
->GetStorageOriginKey(originKey
);
263 storagePrincipal
->OriginAttributesRef().CreateSuffix(originAttrSuffix
);
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
))) {
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
))) {
283 MOZ_ASSERT(storagePrincipalInfo
->type() ==
284 PrincipalInfo::TContentPrincipalInfo
);
287 !quota::QuotaManager::IsPrincipalInfoValid(*storagePrincipalInfo
))) {
288 return NS_ERROR_FAILURE
;
293 const auto& principalMetadata
,
294 quota::QuotaManager::GetInfoFromPrincipal(storagePrincipal
.get()));
296 MOZ_ASSERT(originAttrSuffix
== principalMetadata
.mSuffix
);
298 const auto& origin
= principalMetadata
.mOrigin
;
302 quota::QuotaManager::GetOriginFromPrincipal(storagePrincipal
.get()));
305 uint32_t privateBrowsingId
;
306 rv
= storagePrincipal
->GetPrivateBrowsingId(&privateBrowsingId
);
307 if (NS_WARN_IF(NS_FAILED(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
))) {
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
);
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
);
354 nsCString originAttrSuffix
;
356 nsresult rv
= aStoragePrincipal
->GetStorageOriginKey(originKey
);
357 aStoragePrincipal
->OriginAttributesRef().CreateSuffix(originAttrSuffix
);
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
))) {
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
))) {
378 storagePrincipalInfo
->type() == PrincipalInfo::TContentPrincipalInfo
||
379 storagePrincipalInfo
->type() == PrincipalInfo::TSystemPrincipalInfo
);
382 !quota::QuotaManager::IsPrincipalInfoValid(*storagePrincipalInfo
))) {
383 return NS_ERROR_FAILURE
;
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
));
399 MOZ_ASSERT(originAttrSuffix
== principalMetadata
.mSuffix
);
401 const auto& origin
= principalMetadata
.mOrigin
;
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
));
415 Maybe
<nsID
> clientId
;
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
);
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
)) {
452 LSRequestChild
* actor
= new LSRequestChild();
454 if (!backgroundActor
->SendPBackgroundLSRequestConstructor(actor
, aParams
)) {
458 // Must set callback after calling SendPBackgroundLSRequestConstructor since
459 // it can be called synchronously when SendPBackgroundLSRequestConstructor
461 actor
->SetCallback(aCallback
);
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
) {
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.
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
);
520 nsresult rv
= EnsureDatabase();
521 if (NS_WARN_IF(NS_FAILED(rv
))) {
527 rv
= mDatabase
->GetLength(this, &result
);
528 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
);
545 nsresult rv
= EnsureDatabase();
546 if (NS_WARN_IF(NS_FAILED(rv
))) {
552 rv
= mDatabase
->GetKey(this, aIndex
, result
);
553 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
);
570 nsresult rv
= EnsureDatabase();
571 if (NS_WARN_IF(NS_FAILED(rv
))) {
577 rv
= mDatabase
->GetItem(this, aKey
, result
);
578 if (NS_WARN_IF(NS_FAILED(rv
))) {
586 void LSObject::GetSupportedNames(nsTArray
<nsString
>& aNames
) {
587 AssertIsOnOwningThread();
589 if (!CanUseStorage(*nsContentUtils::SubjectPrincipal())) {
590 // Return just an empty array.
595 nsresult rv
= EnsureDatabase();
596 if (NS_WARN_IF(NS_FAILED(rv
))) {
600 rv
= mDatabase
->GetKeys(this, aNames
);
601 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
);
615 nsresult rv
= EnsureDatabase();
616 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
))) {
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
);
646 nsresult rv
= EnsureDatabase();
647 if (NS_WARN_IF(NS_FAILED(rv
))) {
653 rv
= mDatabase
->RemoveItem(this, aKey
, info
);
654 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
);
672 nsresult rv
= EnsureDatabase();
673 if (NS_WARN_IF(NS_FAILED(rv
))) {
679 rv
= mDatabase
->Clear(this, info
);
680 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
);
698 nsresult rv
= EnsureDatabase();
699 if (NS_WARN_IF(NS_FAILED(rv
))) {
705 void LSObject::Close(nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aError
) {
706 AssertIsOnOwningThread();
708 if (!CanUseStorage(aSubjectPrincipal
)) {
709 aError
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
716 void LSObject::BeginExplicitSnapshot(nsIPrincipal
& aSubjectPrincipal
,
717 ErrorResult
& aError
) {
718 AssertIsOnOwningThread();
720 if (!CanUseStorage(aSubjectPrincipal
)) {
721 aError
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
725 if (mInExplicitSnapshot
) {
726 aError
.Throw(NS_ERROR_ALREADY_INITIALIZED
);
730 nsresult rv
= EnsureDatabase();
731 if (NS_WARN_IF(NS_FAILED(rv
))) {
736 rv
= mDatabase
->BeginExplicitSnapshot(this);
737 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
);
754 if (!mInExplicitSnapshot
) {
755 aError
.Throw(NS_ERROR_NOT_INITIALIZED
);
759 AssertExplicitSnapshotInvariants(*this);
761 nsresult rv
= mDatabase
->CheckpointExplicitSnapshot();
762 if (NS_WARN_IF(NS_FAILED(rv
))) {
768 void LSObject::EndExplicitSnapshot(nsIPrincipal
& aSubjectPrincipal
,
769 ErrorResult
& aError
) {
770 AssertIsOnOwningThread();
772 if (!CanUseStorage(aSubjectPrincipal
)) {
773 aError
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
777 if (!mInExplicitSnapshot
) {
778 aError
.Throw(NS_ERROR_NOT_INITIALIZED
);
782 AssertExplicitSnapshotInvariants(*this);
784 nsresult rv
= mDatabase
->EndExplicitSnapshot();
785 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
);
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()) {
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
);
821 if (!mDatabase
|| mDatabase
->IsAllowedToClose()) {
822 aError
.Throw(NS_ERROR_NOT_AVAILABLE
);
826 if (!mDatabase
->HasSnapshot()) {
827 aError
.Throw(NS_ERROR_NOT_AVAILABLE
);
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();
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
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
))) {
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
;
886 nsresult
LSObject::EnsureDatabase() {
887 AssertIsOnOwningThread();
889 if (mDatabase
&& !mDatabase
->IsAllowedToClose()) {
893 mDatabase
= LSDatabase::Get(mOrigin
);
896 MOZ_ASSERT(!mDatabase
->IsAllowedToClose());
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
))) {
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
);
955 void LSObject::DropDatabase() {
956 AssertIsOnOwningThread();
961 nsresult
LSObject::EnsureObserver() {
962 AssertIsOnOwningThread();
968 mObserver
= LSObserver::Get(mOrigin
);
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
))) {
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
);
1011 backgroundActor
->SendPBackgroundLSObserverConstructor(actor
, observerId
));
1013 observer
->SetActor(actor
);
1015 mObserver
= std::move(observer
);
1020 void LSObject::DropObserver() {
1021 AssertIsOnOwningThread();
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();
1044 nsresult
RequestHelper::StartAndReturnResponse(LSRequestResponse
& aResponse
) {
1045 AssertIsOnOwningThread();
1047 // Normally, we would use the standard way of blocking the thread using
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
;
1077 rv
= domFileThread
->Dispatch(this, NS_DISPATCH_NORMAL
);
1078 if (NS_WARN_IF(NS_FAILED(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
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
,
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"));
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
))) {
1151 aResponse
= std::move(mResponse
);
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
;
1172 void RequestHelper::Finish() {
1173 AssertIsOnOwningThread();
1174 MOZ_ASSERT(mState
== State::Finishing
);
1180 mState
= State::Complete
;
1183 NS_IMPL_ISUPPORTS_INHERITED0(RequestHelper
, Runnable
)
1186 RequestHelper::Run() {
1190 case State::Initial
:
1194 case State::Finishing
:
1199 MOZ_CRASH("Bad state!");
1202 if (NS_WARN_IF(NS_FAILED(rv
)) && mState
!= State::Finishing
) {
1203 if (NS_SUCCEEDED(mResultCode
)) {
1207 mState
= State::Finishing
;
1209 if (IsOnOwningThread()) {
1212 MOZ_ALWAYS_SUCCEEDS(
1213 mNestedEventTarget
->Dispatch(this, NS_DISPATCH_NORMAL
));
1220 void RequestHelper::OnResponse(const LSRequestResponse
& aResponse
) {
1221 AssertIsOnDOMFileThread();
1222 MOZ_ASSERT(mState
== State::ResponsePending
);
1226 mResponse
= aResponse
;
1228 mState
= State::Finishing
;
1229 MOZ_ALWAYS_SUCCEEDS(mNestedEventTarget
->Dispatch(this, NS_DISPATCH_NORMAL
));
1232 } // namespace mozilla::dom