fune/dom/localstorage/LSObject.cpp
Noemi Erli cb0b4097d6 Backed out 9 changesets (bug 1731982) for causing Bp-hybrid bustages CLOSED TREE
Backed out changeset 82bb76c2e531 (bug 1731982)
Backed out changeset 8240ef0e1771 (bug 1731982)
Backed out changeset f46f05792c6a (bug 1731982)
Backed out changeset 449c821bc56e (bug 1731982)
Backed out changeset cb813505b5a9 (bug 1731982)
Backed out changeset 06fad8cd9345 (bug 1731982)
Backed out changeset 4b8cf4756686 (bug 1731982)
Backed out changeset 5468bf77f732 (bug 1731982)
Backed out changeset 7800585e3d6a (bug 1731982)
2021-10-12 23:12:19 +03:00

1376 lines
40 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "LSObject.h"
// Local includes
#include "ActorsChild.h"
#include "LSDatabase.h"
#include "LSObserver.h"
// Global includes
#include <utility>
#include "MainThreadUtils.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/MacroForEach.h"
#include "mozilla/OriginAttributes.h"
#include "mozilla/Preferences.h"
#include "mozilla/RemoteLazyInputStreamThread.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StorageAccess.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/ClientInfo.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/LocalStorageCommon.h"
#include "mozilla/dom/PBackgroundLSRequest.h"
#include "mozilla/dom/PBackgroundLSSharedTypes.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsIEventTarget.h"
#include "nsIPrincipal.h"
#include "nsIRunnable.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsISerialEventTarget.h"
#include "nsITimer.h"
#include "nsPIDOMWindow.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsTStringRepr.h"
#include "nsThread.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "nscore.h"
/**
* Automatically cancel and abort synchronous LocalStorage requests (for example
* datastore preparation) if they take this long. We've chosen a value that is
* long enough that it is unlikely for the problem to be falsely triggered by
* slow system I/O. We've also chosen a value long enough so that automated
* tests should time out and fail if LocalStorage hangs. Also, this value is
* long enough so that testers can notice the (content process) hang; we want to
* know about the hangs, not hide them. On the other hand this value is less
* than 60 seconds which is used by nsTerminator to crash a hung main process.
*/
#define FAILSAFE_CANCEL_SYNC_OP_MS 50000
namespace mozilla::dom {
namespace {
class RequestHelper;
StaticMutex gRequestHelperMutex;
nsISerialEventTarget* gSyncLoopEventTarget = nullptr;
/**
* Tracks whether a sync message has been received to the main thread but not
* yet processed. Used by RequestHelper logic to abort effectively synchronous
* calls if a sync IPC message is received which could result in deadlock.
* This is a boolean because, by definition, the parent can only send one sync
* message to the child at a time.
*/
bool gPendingSyncMessage = false;
/*
* Wrapper for the pushed event queue. The wrapper automatically dispatches
* runnables to the main thread when pushed event queue is no longer active.
* This exists because the event loop spinning can be aborted.
*/
class NestedEventTargetWrapper final : public nsISerialEventTarget {
nsCOMPtr<nsISerialEventTarget> mNestedEventTarget;
bool mDisconnected;
public:
explicit NestedEventTargetWrapper(nsISerialEventTarget* aNestedEventTarget)
: mNestedEventTarget(aNestedEventTarget), mDisconnected(false) {}
private:
~NestedEventTargetWrapper() = default;
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIEVENTTARGET_FULL
};
/**
* Main-thread helper that implements the blocking logic required by
* LocalStorage's synchronous semantics. StartAndReturnResponse pushes an
* event queue which is a new event target and spins its nested event loop until
* a result is received or an abort is necessary due to a PContent-managed sync
* IPC message being received. Note that because the event queue is its own
* event target, there is no re-entrancy. Normal main-thread runnables will not
* get a chance to run. See StartAndReturnResponse() for info on this choice.
*
* The normal life-cycle of this method looks like:
* - Main Thread: LSObject::DoRequestSynchronously creates a RequestHelper and
* invokes StartAndReturnResponse(). It pushes the event queue and Dispatches
* the RequestHelper to the RemoteLazyInputStream thread.
* - RemoteLazyInputStream Thread: RequestHelper::Run is called, invoking
* Start() which invokes LSObject::StartRequest, which gets-or-creates the
* PBackground actor if necessary (which may dispatch a runnable to the nested
* event queue on the main thread), sends LSRequest constructor which is
* provided with a callback reference to the RequestHelper. State advances to
* ResponsePending.
* - RemoteLazyInputStreamThread: LSRequestChild::Recv__delete__ is received,
* which invokes RequestHelepr::OnResponse, advancing the state to Finishing
* and dispatching RequestHelper to its own nested event target.
* - Main Thread: RequestHelper::Run is called, invoking Finish() which advances
* the state to Complete and sets mWaiting to false, allowing the nested event
* loop being spun by StartAndReturnResponse to cease spinning and return the
* received response.
*
* See LocalStorageCommon.h for high-level context and method comments for
* low-level details.
*/
class RequestHelper final : public Runnable, public LSRequestChildCallback {
enum class State {
/**
* The RequestHelper has been created and dispatched to the
* RemoteLazyInputStream Thread.
*/
Initial,
/**
* Start() has been invoked on the RemoteLazyInputStream Thread and
* LSObject::StartRequest has been invoked from there, sending an IPC
* message to PBackground to service the request. We stay in this state
* until a response is received.
*/
ResponsePending,
/**
* A response has been received and RequestHelper has been dispatched back
* to the nested event loop to call Finish().
*/
Finishing,
/**
* Finish() has been called on the main thread. The nested event loop will
* terminate imminently and the received response returned to the caller of
* StartAndReturnResponse.
*/
Complete
};
// The object we are issuing a request on behalf of. Present because of the
// need to invoke LSObject::StartRequest off the main thread. Dropped on
// return to the main-thread in Finish().
RefPtr<LSObject> mObject;
// The thread the RequestHelper was created on. This should be the main
// thread.
nsCOMPtr<nsIEventTarget> mOwningEventTarget;
// The pushed event queue that we use to spin the event loop without
// processing any of the events dispatched at the mOwningEventTarget (which
// would result in re-entrancy and violate LocalStorage semantics).
nsCOMPtr<nsISerialEventTarget> mNestedEventTarget;
// The wrapper for the pushed event queue. The wrapper automatically
// dispatches runnables to the main thread when pushed event queue is no
// longer active. This exists because the event loop spinning can be aborted.
nsCOMPtr<nsISerialEventTarget> mNestedEventTargetWrapper;
// The IPC actor handling the request with standard IPC allocation rules.
// Our reference is nulled in OnResponse which corresponds to the actor's
// __destroy__ method.
LSRequestChild* mActor;
const LSRequestParams mParams;
LSRequestResponse mResponse;
nsresult mResultCode;
State mState;
// Control flag for the nested event loop; once set to false, the loop ends.
bool mWaiting;
bool mCancelled;
public:
RequestHelper(LSObject* aObject, const LSRequestParams& aParams)
: Runnable("dom::RequestHelper"),
mObject(aObject),
mOwningEventTarget(GetCurrentEventTarget()),
mActor(nullptr),
mParams(aParams),
mResultCode(NS_OK),
mState(State::Initial),
mWaiting(true),
mCancelled(false) {}
bool IsOnOwningThread() const {
MOZ_ASSERT(mOwningEventTarget);
bool current;
return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)) &&
current;
}
void AssertIsOnOwningThread() const {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsOnOwningThread());
}
nsresult StartAndReturnResponse(LSRequestResponse& aResponse);
private:
~RequestHelper() = default;
nsresult Start();
void Finish();
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIRUNNABLE
// LSRequestChildCallback
void OnResponse(const LSRequestResponse& aResponse) override;
};
} // namespace
LSObject::LSObject(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
nsIPrincipal* aStoragePrincipal)
: Storage(aWindow, aPrincipal, aStoragePrincipal),
mPrivateBrowsingId(0),
mInExplicitSnapshot(false) {
AssertIsOnOwningThread();
MOZ_ASSERT(NextGenLocalStorageEnabled());
}
LSObject::~LSObject() {
AssertIsOnOwningThread();
DropObserver();
}
// static
void LSObject::Initialize() {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIEventTarget> domFileThread =
RemoteLazyInputStreamThread::GetOrCreate();
if (NS_WARN_IF(!domFileThread)) {
return;
}
RefPtr<Runnable> runnable =
NS_NewRunnableFunction("LSObject::Initialize", []() {
AssertIsOnDOMFileThread();
mozilla::ipc::PBackgroundChild* backgroundActor =
mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
if (NS_WARN_IF(!backgroundActor)) {
return;
}
});
if (NS_WARN_IF(
NS_FAILED(domFileThread->Dispatch(runnable, NS_DISPATCH_NORMAL)))) {
return;
}
}
// static
nsresult LSObject::CreateForWindow(nsPIDOMWindowInner* aWindow,
Storage** aStorage) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aWindow);
MOZ_ASSERT(aStorage);
MOZ_ASSERT(NextGenLocalStorageEnabled());
MOZ_ASSERT(StorageAllowedForWindow(aWindow) != StorageAccess::eDeny);
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
MOZ_ASSERT(sop);
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
if (NS_WARN_IF(!principal)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIPrincipal> storagePrincipal = sop->GetEffectiveStoragePrincipal();
if (NS_WARN_IF(!storagePrincipal)) {
return NS_ERROR_FAILURE;
}
if (principal->IsSystemPrincipal()) {
return NS_ERROR_NOT_AVAILABLE;
}
// localStorage is not available on some pages on purpose, for example
// about:home. Match the old implementation by using GenerateOriginKey
// for the check.
nsCString originAttrSuffix;
nsCString originKey;
nsresult rv = storagePrincipal->GetStorageOriginKey(originKey);
storagePrincipal->OriginAttributesRef().CreateSuffix(originAttrSuffix);
if (NS_FAILED(rv)) {
return NS_ERROR_NOT_AVAILABLE;
}
auto principalInfo = MakeUnique<PrincipalInfo>();
rv = PrincipalToPrincipalInfo(principal, principalInfo.get());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(principalInfo->type() == PrincipalInfo::TContentPrincipalInfo);
auto storagePrincipalInfo = MakeUnique<PrincipalInfo>();
rv = PrincipalToPrincipalInfo(storagePrincipal, storagePrincipalInfo.get());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(storagePrincipalInfo->type() ==
PrincipalInfo::TContentPrincipalInfo);
if (NS_WARN_IF(
!quota::QuotaManager::IsPrincipalInfoValid(*storagePrincipalInfo))) {
return NS_ERROR_FAILURE;
}
#ifdef DEBUG
QM_TRY_INSPECT(
const auto& principalMetadata,
quota::QuotaManager::GetInfoFromPrincipal(storagePrincipal.get()));
MOZ_ASSERT(originAttrSuffix == principalMetadata.mSuffix);
const auto& origin = principalMetadata.mOrigin;
#else
QM_TRY_INSPECT(
const auto& origin,
quota::QuotaManager::GetOriginFromPrincipal(storagePrincipal.get()));
#endif
uint32_t privateBrowsingId;
rv = storagePrincipal->GetPrivateBrowsingId(&privateBrowsingId);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
Maybe<ClientInfo> clientInfo = aWindow->GetClientInfo();
if (clientInfo.isNothing()) {
return NS_ERROR_FAILURE;
}
Maybe<nsID> clientId = Some(clientInfo.ref().Id());
nsString documentURI;
if (nsCOMPtr<Document> doc = aWindow->GetExtantDoc()) {
rv = doc->GetDocumentURI(documentURI);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
RefPtr<LSObject> object = new LSObject(aWindow, principal, storagePrincipal);
object->mPrincipalInfo = std::move(principalInfo);
object->mStoragePrincipalInfo = std::move(storagePrincipalInfo);
object->mPrivateBrowsingId = privateBrowsingId;
object->mClientId = clientId;
object->mOrigin = origin;
object->mOriginKey = originKey;
object->mDocumentURI = documentURI;
object.forget(aStorage);
return NS_OK;
}
// static
nsresult LSObject::CreateForPrincipal(nsPIDOMWindowInner* aWindow,
nsIPrincipal* aPrincipal,
nsIPrincipal* aStoragePrincipal,
const nsAString& aDocumentURI,
bool aPrivate, LSObject** aObject) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(aStoragePrincipal);
MOZ_ASSERT(aObject);
nsCString originAttrSuffix;
nsCString originKey;
nsresult rv = aStoragePrincipal->GetStorageOriginKey(originKey);
aStoragePrincipal->OriginAttributesRef().CreateSuffix(originAttrSuffix);
if (NS_FAILED(rv)) {
return NS_ERROR_NOT_AVAILABLE;
}
auto principalInfo = MakeUnique<PrincipalInfo>();
rv = PrincipalToPrincipalInfo(aPrincipal, principalInfo.get());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(principalInfo->type() == PrincipalInfo::TContentPrincipalInfo ||
principalInfo->type() == PrincipalInfo::TSystemPrincipalInfo);
auto storagePrincipalInfo = MakeUnique<PrincipalInfo>();
rv = PrincipalToPrincipalInfo(aStoragePrincipal, storagePrincipalInfo.get());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(
storagePrincipalInfo->type() == PrincipalInfo::TContentPrincipalInfo ||
storagePrincipalInfo->type() == PrincipalInfo::TSystemPrincipalInfo);
if (NS_WARN_IF(
!quota::QuotaManager::IsPrincipalInfoValid(*storagePrincipalInfo))) {
return NS_ERROR_FAILURE;
}
#ifdef DEBUG
QM_TRY_INSPECT(
const auto& principalMetadata,
([&storagePrincipalInfo,
&aPrincipal]() -> Result<quota::PrincipalMetadata, nsresult> {
if (storagePrincipalInfo->type() ==
PrincipalInfo::TSystemPrincipalInfo) {
return quota::QuotaManager::GetInfoForChrome();
}
QM_TRY_RETURN(quota::QuotaManager::GetInfoFromPrincipal(aPrincipal));
}()));
MOZ_ASSERT(originAttrSuffix == principalMetadata.mSuffix);
const auto& origin = principalMetadata.mOrigin;
#else
QM_TRY_INSPECT(
const auto& origin, ([&storagePrincipalInfo,
&aPrincipal]() -> Result<nsAutoCString, nsresult> {
if (storagePrincipalInfo->type() ==
PrincipalInfo::TSystemPrincipalInfo) {
return nsAutoCString{quota::QuotaManager::GetOriginForChrome()};
}
QM_TRY_RETURN(quota::QuotaManager::GetOriginFromPrincipal(aPrincipal));
}()));
#endif
Maybe<nsID> clientId;
if (aWindow) {
Maybe<ClientInfo> clientInfo = aWindow->GetClientInfo();
if (clientInfo.isNothing()) {
return NS_ERROR_FAILURE;
}
clientId = Some(clientInfo.ref().Id());
} else if (Preferences::GetBool("dom.storage.client_validation")) {
return NS_ERROR_FAILURE;
}
RefPtr<LSObject> object =
new LSObject(aWindow, aPrincipal, aStoragePrincipal);
object->mPrincipalInfo = std::move(principalInfo);
object->mStoragePrincipalInfo = std::move(storagePrincipalInfo);
object->mPrivateBrowsingId = aPrivate ? 1 : 0;
object->mClientId = clientId;
object->mOrigin = origin;
object->mOriginKey = originKey;
object->mDocumentURI = aDocumentURI;
object.forget(aObject);
return NS_OK;
} // namespace dom
// static
void LSObject::OnSyncMessageReceived() {
nsCOMPtr<nsISerialEventTarget> target;
{
StaticMutexAutoLock lock(gRequestHelperMutex);
target = gSyncLoopEventTarget;
gPendingSyncMessage = true;
}
if (target) {
RefPtr<Runnable> runnable =
NS_NewRunnableFunction("LSObject::CheckFlagRunnable", []() {});
MOZ_ALWAYS_SUCCEEDS(
target->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
}
}
// static
void LSObject::OnSyncMessageHandled() {
StaticMutexAutoLock lock(gRequestHelperMutex);
gPendingSyncMessage = false;
}
LSRequestChild* LSObject::StartRequest(nsIEventTarget* aMainEventTarget,
const LSRequestParams& aParams,
LSRequestChildCallback* aCallback) {
AssertIsOnDOMFileThread();
mozilla::ipc::PBackgroundChild* backgroundActor =
XRE_IsParentProcess()
? mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(
aMainEventTarget)
: mozilla::ipc::BackgroundChild::GetForCurrentThread();
if (NS_WARN_IF(!backgroundActor)) {
return nullptr;
}
LSRequestChild* actor = new LSRequestChild();
if (!backgroundActor->SendPBackgroundLSRequestConstructor(actor, aParams)) {
return nullptr;
}
// Must set callback after calling SendPBackgroundLSRequestConstructor since
// it can be called synchronously when SendPBackgroundLSRequestConstructor
// fails.
actor->SetCallback(aCallback);
return actor;
}
Storage::StorageType LSObject::Type() const {
AssertIsOnOwningThread();
return eLocalStorage;
}
bool LSObject::IsForkOf(const Storage* aStorage) const {
AssertIsOnOwningThread();
MOZ_ASSERT(aStorage);
if (aStorage->Type() != eLocalStorage) {
return false;
}
return static_cast<const LSObject*>(aStorage)->mOrigin == mOrigin;
}
int64_t LSObject::GetOriginQuotaUsage() const {
AssertIsOnOwningThread();
// It's not necessary to return an actual value here. This method is
// implemented only because the SessionStore currently needs it to cap the
// amount of data it persists to disk (via nsIDOMWindowUtils.getStorageUsage).
// Any callers that want to know about storage usage should be asking
// QuotaManager directly.
//
// Note: This may change as LocalStorage is repurposed to be the new
// SessionStorage backend.
return 0;
}
void LSObject::Disconnect() {
// Explicit snapshots which were not ended in JS, must be ended here while
// IPC is still available. We can't do that in DropDatabase because actors
// may have been destroyed already at that point.
if (mInExplicitSnapshot) {
nsresult rv = EndExplicitSnapshotInternal();
Unused << NS_WARN_IF(NS_FAILED(rv));
}
}
uint32_t LSObject::GetLength(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError) {
AssertIsOnOwningThread();
if (!CanUseStorage(aSubjectPrincipal)) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return 0;
}
nsresult rv = EnsureDatabase();
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return 0;
}
uint32_t result;
rv = mDatabase->GetLength(this, &result);
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return 0;
}
return result;
}
void LSObject::Key(uint32_t aIndex, nsAString& aResult,
nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
AssertIsOnOwningThread();
if (!CanUseStorage(aSubjectPrincipal)) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsresult rv = EnsureDatabase();
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
nsString result;
rv = mDatabase->GetKey(this, aIndex, result);
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
aResult = result;
}
void LSObject::GetItem(const nsAString& aKey, nsAString& aResult,
nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
AssertIsOnOwningThread();
if (!CanUseStorage(aSubjectPrincipal)) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsresult rv = EnsureDatabase();
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
nsString result;
rv = mDatabase->GetItem(this, aKey, result);
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
aResult = result;
}
void LSObject::GetSupportedNames(nsTArray<nsString>& aNames) {
AssertIsOnOwningThread();
if (!CanUseStorage(*nsContentUtils::SubjectPrincipal())) {
// Return just an empty array.
aNames.Clear();
return;
}
nsresult rv = EnsureDatabase();
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
rv = mDatabase->GetKeys(this, aNames);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
}
void LSObject::SetItem(const nsAString& aKey, const nsAString& aValue,
nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
AssertIsOnOwningThread();
if (!CanUseStorage(aSubjectPrincipal)) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsresult rv = EnsureDatabase();
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
LSNotifyInfo info;
rv = mDatabase->SetItem(this, aKey, aValue, info);
if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
rv = NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
}
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
if (info.changed()) {
OnChange(aKey, info.oldValue(), aValue);
}
}
void LSObject::RemoveItem(const nsAString& aKey,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError) {
AssertIsOnOwningThread();
if (!CanUseStorage(aSubjectPrincipal)) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsresult rv = EnsureDatabase();
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
LSNotifyInfo info;
rv = mDatabase->RemoveItem(this, aKey, info);
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
if (info.changed()) {
OnChange(aKey, info.oldValue(), VoidString());
}
}
void LSObject::Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
AssertIsOnOwningThread();
if (!CanUseStorage(aSubjectPrincipal)) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsresult rv = EnsureDatabase();
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
LSNotifyInfo info;
rv = mDatabase->Clear(this, info);
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
if (info.changed()) {
OnChange(VoidString(), VoidString(), VoidString());
}
}
void LSObject::Open(nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
AssertIsOnOwningThread();
if (!CanUseStorage(aSubjectPrincipal)) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsresult rv = EnsureDatabase();
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
}
void LSObject::Close(nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
AssertIsOnOwningThread();
if (!CanUseStorage(aSubjectPrincipal)) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
DropDatabase();
}
void LSObject::BeginExplicitSnapshot(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError) {
AssertIsOnOwningThread();
if (!CanUseStorage(aSubjectPrincipal)) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
if (mInExplicitSnapshot) {
aError.Throw(NS_ERROR_ALREADY_INITIALIZED);
return;
}
nsresult rv = EnsureDatabase();
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
rv = mDatabase->BeginExplicitSnapshot(this);
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
mInExplicitSnapshot = true;
}
void LSObject::EndExplicitSnapshot(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError) {
AssertIsOnOwningThread();
if (!CanUseStorage(aSubjectPrincipal)) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
if (!mInExplicitSnapshot) {
aError.Throw(NS_ERROR_NOT_INITIALIZED);
return;
}
nsresult rv = EndExplicitSnapshotInternal();
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
}
}
bool LSObject::GetHasActiveSnapshot(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError) {
AssertIsOnOwningThread();
if (!CanUseStorage(aSubjectPrincipal)) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return 0;
}
if (mDatabase && mDatabase->HasActiveSnapshot()) {
MOZ_ASSERT(!mDatabase->IsAllowedToClose());
return true;
}
return false;
}
NS_IMPL_ADDREF_INHERITED(LSObject, Storage)
NS_IMPL_RELEASE_INHERITED(LSObject, Storage)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LSObject)
NS_INTERFACE_MAP_END_INHERITING(Storage)
NS_IMPL_CYCLE_COLLECTION_CLASS(LSObject)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(LSObject, Storage)
tmp->AssertIsOnOwningThread();
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(LSObject, Storage)
tmp->AssertIsOnOwningThread();
tmp->DropDatabase();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
nsresult LSObject::DoRequestSynchronously(const LSRequestParams& aParams,
LSRequestResponse& aResponse) {
// We don't need this yet, but once the request successfully finishes, it's
// too late to initialize PBackground child on the owning thread, because
// it can fail and parent would keep an extra strong ref to the datastore or
// observer.
mozilla::ipc::PBackgroundChild* backgroundActor =
mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
if (NS_WARN_IF(!backgroundActor)) {
return NS_ERROR_FAILURE;
}
RefPtr<RequestHelper> helper = new RequestHelper(this, aParams);
// This will start and finish the request on the RemoteLazyInputStream thread.
// The owning thread is synchronously blocked while the request is
// asynchronously processed on the RemoteLazyInputStream thread.
nsresult rv = helper->StartAndReturnResponse(aResponse);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (aResponse.type() == LSRequestResponse::Tnsresult) {
nsresult errorCode = aResponse.get_nsresult();
if (errorCode == NS_ERROR_FILE_NO_DEVICE_SPACE) {
errorCode = NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
}
return errorCode;
}
return NS_OK;
}
nsresult LSObject::EnsureDatabase() {
AssertIsOnOwningThread();
if (mDatabase && !mDatabase->IsAllowedToClose()) {
return NS_OK;
}
mDatabase = LSDatabase::Get(mOrigin);
if (mDatabase) {
MOZ_ASSERT(!mDatabase->IsAllowedToClose());
return NS_OK;
}
// We don't need this yet, but once the request successfully finishes, it's
// too late to initialize PBackground child on the owning thread, because
// it can fail and parent would keep an extra strong ref to the datastore.
mozilla::ipc::PBackgroundChild* backgroundActor =
mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
if (NS_WARN_IF(!backgroundActor)) {
return NS_ERROR_FAILURE;
}
LSRequestCommonParams commonParams;
commonParams.principalInfo() = *mPrincipalInfo;
commonParams.storagePrincipalInfo() = *mStoragePrincipalInfo;
commonParams.originKey() = mOriginKey;
LSRequestPrepareDatastoreParams params;
params.commonParams() = commonParams;
params.clientId() = mClientId;
LSRequestResponse response;
nsresult rv = DoRequestSynchronously(params, response);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(response.type() ==
LSRequestResponse::TLSRequestPrepareDatastoreResponse);
const LSRequestPrepareDatastoreResponse& prepareDatastoreResponse =
response.get_LSRequestPrepareDatastoreResponse();
uint64_t datastoreId = prepareDatastoreResponse.datastoreId();
// The datastore is now ready on the parent side (prepared by the asynchronous
// request on the RemoteLazyInputStream thread).
// Let's create a direct connection to the datastore (through a database
// actor) from the owning thread.
// Note that we now can't error out, otherwise parent will keep an extra
// strong reference to the datastore.
RefPtr<LSDatabase> database = new LSDatabase(mOrigin);
LSDatabaseChild* actor = new LSDatabaseChild(database);
MOZ_ALWAYS_TRUE(backgroundActor->SendPBackgroundLSDatabaseConstructor(
actor, *mStoragePrincipalInfo, mPrivateBrowsingId, datastoreId));
database->SetActor(actor);
mDatabase = std::move(database);
return NS_OK;
}
void LSObject::DropDatabase() {
AssertIsOnOwningThread();
mDatabase = nullptr;
}
nsresult LSObject::EnsureObserver() {
AssertIsOnOwningThread();
if (mObserver) {
return NS_OK;
}
mObserver = LSObserver::Get(mOrigin);
if (mObserver) {
return NS_OK;
}
LSRequestPrepareObserverParams params;
params.principalInfo() = *mPrincipalInfo;
params.storagePrincipalInfo() = *mStoragePrincipalInfo;
params.clientId() = mClientId;
LSRequestResponse response;
nsresult rv = DoRequestSynchronously(params, response);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(response.type() ==
LSRequestResponse::TLSRequestPrepareObserverResponse);
const LSRequestPrepareObserverResponse& prepareObserverResponse =
response.get_LSRequestPrepareObserverResponse();
uint64_t observerId = prepareObserverResponse.observerId();
// The obsserver is now ready on the parent side (prepared by the asynchronous
// request on the RemoteLazyInputStream thread).
// Let's create a direct connection to the observer (through an observer
// actor) from the owning thread.
// Note that we now can't error out, otherwise parent will keep an extra
// strong reference to the observer.
mozilla::ipc::PBackgroundChild* backgroundActor =
mozilla::ipc::BackgroundChild::GetForCurrentThread();
MOZ_ASSERT(backgroundActor);
RefPtr<LSObserver> observer = new LSObserver(mOrigin);
LSObserverChild* actor = new LSObserverChild(observer);
MOZ_ALWAYS_TRUE(
backgroundActor->SendPBackgroundLSObserverConstructor(actor, observerId));
observer->SetActor(actor);
mObserver = std::move(observer);
return NS_OK;
}
void LSObject::DropObserver() {
AssertIsOnOwningThread();
if (mObserver) {
mObserver = nullptr;
}
}
void LSObject::OnChange(const nsAString& aKey, const nsAString& aOldValue,
const nsAString& aNewValue) {
AssertIsOnOwningThread();
NotifyChange(/* aStorage */ this, StoragePrincipal(), aKey, aOldValue,
aNewValue, /* aStorageType */ kLocalStorageType, mDocumentURI,
/* aIsPrivate */ !!mPrivateBrowsingId,
/* aImmediateDispatch */ false);
}
nsresult LSObject::EndExplicitSnapshotInternal() {
AssertIsOnOwningThread();
// Can be only called if the mInExplicitSnapshot flag is true.
// An explicit snapshot must have been created.
MOZ_ASSERT(mInExplicitSnapshot);
// If an explicit snapshot has been created then mDatabase must be not null.
// DropDatabase could be called in the meatime, but that must be preceded by
// Disconnect which sets mInExplicitSnapshot to false. EnsureDatabase could
// be called in the meantime too, but that can't set mDatabase to null or to
// a new value. See the comment below.
MOZ_ASSERT(mDatabase);
// Existence of a snapshot prevents the database from allowing to close. See
// LSDatabase::RequestAllowToClose and LSDatabase::NoteFinishedSnapshot.
// If the database is not allowed to close then mDatabase could not have been
// nulled out or set to a new value. See EnsureDatabase.
MOZ_ASSERT(!mDatabase->IsAllowedToClose());
nsresult rv = mDatabase->EndExplicitSnapshot(this);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mInExplicitSnapshot = false;
return NS_OK;
}
void LSObject::LastRelease() {
AssertIsOnOwningThread();
DropDatabase();
}
NS_IMPL_ISUPPORTS(NestedEventTargetWrapper, nsIEventTarget,
nsISerialEventTarget);
NS_IMETHODIMP_(bool)
NestedEventTargetWrapper::IsOnCurrentThreadInfallible() {
MOZ_CRASH(
"IsOnCurrentThreadInfallible should never be called on "
"NestedEventTargetWrapper");
}
NS_IMETHODIMP
NestedEventTargetWrapper::IsOnCurrentThread(bool* aResult) {
MOZ_CRASH(
"IsOnCurrentThread should never be called on "
"NestedEventTargetWrapper");
}
NS_IMETHODIMP
NestedEventTargetWrapper::Dispatch(already_AddRefed<nsIRunnable> aEvent,
uint32_t aFlags) {
MOZ_ASSERT(mNestedEventTarget);
if (mDisconnected) {
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(std::move(aEvent), aFlags));
return NS_OK;
}
nsCOMPtr<nsIRunnable> event(aEvent);
nsresult rv = mNestedEventTarget->Dispatch(event, aFlags);
if (rv == NS_ERROR_UNEXPECTED) {
mDisconnected = true;
// Dispatch leaked the event object on the NS_ERROR_UNEXPECTED failure, so
// we explicitly release this object once for that.
event.get()->Release();
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(event.forget(), aFlags));
} else if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
NestedEventTargetWrapper::DispatchFromScript(nsIRunnable* aEvent,
uint32_t aFlags) {
MOZ_CRASH(
"DispatchFromScript should never be called on "
"NestedEventTargetWrapper");
}
NS_IMETHODIMP
NestedEventTargetWrapper::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
uint32_t aDelayMs) {
MOZ_CRASH(
"DelayedDispatch should never be called on "
"NestedEventTargetWrapper");
}
nsresult RequestHelper::StartAndReturnResponse(LSRequestResponse& aResponse) {
AssertIsOnOwningThread();
// Normally, we would use the standard way of blocking the thread using
// a monitor.
// The problem is that BackgroundChild::GetOrCreateForCurrentThread()
// called on the RemoteLazyInputStream thread may dispatch a runnable to the
// main thread to finish initialization of PBackground. A monitor would block
// the main thread and the runnable would never get executed causing the
// helper to be stuck in a wait loop.
// However, BackgroundChild::GetOrCreateForCurrentThread() supports passing
// a custom main event target, so we can create a nested event target and
// spin the event loop. Nothing can dispatch to the nested event target
// except BackgroundChild::GetOrCreateForCurrentThread(), so spinning of the
// event loop can't fire any other events.
// This way the thread is synchronously blocked in a safe manner and the
// runnable gets executed.
{
auto thread = static_cast<nsThread*>(NS_GetCurrentThread());
const nsLocalExecutionGuard localExecution(thread->EnterLocalExecution());
mNestedEventTarget = localExecution.GetEventTarget();
MOZ_ASSERT(mNestedEventTarget);
mNestedEventTargetWrapper =
new NestedEventTargetWrapper(mNestedEventTarget);
nsCOMPtr<nsIEventTarget> domFileThread =
XRE_IsParentProcess() ? RemoteLazyInputStreamThread::GetOrCreate()
: RemoteLazyInputStreamThread::Get();
if (NS_WARN_IF(!domFileThread)) {
return NS_ERROR_FAILURE;
}
nsresult rv;
{
{
StaticMutexAutoLock lock(gRequestHelperMutex);
if (StaticPrefs::dom_storage_abort_on_sync_parent_to_child_messages() &&
NS_WARN_IF(gPendingSyncMessage)) {
return NS_ERROR_FAILURE;
}
gSyncLoopEventTarget = mNestedEventTargetWrapper;
}
auto autoClearSyncLoopEventTarget = mozilla::MakeScopeExit([&] {
StaticMutexAutoLock lock(gRequestHelperMutex);
gSyncLoopEventTarget = nullptr;
});
rv = domFileThread->Dispatch(this, NS_DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsITimer> timer = NS_NewTimer();
MOZ_ALWAYS_SUCCEEDS(timer->SetTarget(mNestedEventTarget));
MOZ_ALWAYS_SUCCEEDS(timer->InitWithNamedFuncCallback(
[](nsITimer* aTimer, void* aClosure) {
auto helper = static_cast<RequestHelper*>(aClosure);
helper->mCancelled = true;
},
this, FAILSAFE_CANCEL_SYNC_OP_MS, nsITimer::TYPE_ONE_SHOT,
"RequestHelper::StartAndReturnResponse::SpinEventLoopTimer"));
MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
"RequestHelper::StartAndReturnResponse"_ns,
[&]() {
if (mCancelled) {
return true;
}
if (!mWaiting) {
return true;
}
{
StaticMutexAutoLock lock(gRequestHelperMutex);
if (StaticPrefs::
dom_storage_abort_on_sync_parent_to_child_messages() &&
NS_WARN_IF(gPendingSyncMessage)) {
return true;
}
}
return false;
},
thread));
MOZ_ALWAYS_SUCCEEDS(timer->Cancel());
}
// If mWaiting is still set to true, it means that the event loop spinning
// was aborted and we need to cancel the request in the parent since we
// don't care about the result anymore.
// We can check mWaiting here because it's only ever touched on the main
// thread.
if (NS_WARN_IF(mWaiting)) {
// Don't touch mResponse, mResultCode or mState here! The
// RemoteLazyInputStream Thread may be accessing them at the same moment.
RefPtr<RequestHelper> self = this;
RefPtr<Runnable> runnable =
NS_NewRunnableFunction("RequestHelper::SendCancelRunnable", [self]() {
LSRequestChild* actor = self->mActor;
// Start() could fail or it hasn't had a chance to run yet, so we
// need to check if actor is not null.
// The actor can also be in the final (finishing) state, in that
// case we are not allowed to send the cancel message and it
// wouldn't make sense because the request is about to be destroyed
// anyway.
if (actor && !actor->Finishing()) {
actor->SendCancel();
}
});
rv = domFileThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_ERROR_FAILURE;
}
// localExecution will be destructed when we leave this scope. If the event
// loop spinning was aborted and other threads dispatched new runnables to
// the nested event queue, they will be moved to the main event queue here
// and later asynchronusly processed. So nothing will be lost.
}
if (NS_WARN_IF(NS_FAILED(mResultCode))) {
return mResultCode;
}
aResponse = std::move(mResponse);
return NS_OK;
}
nsresult RequestHelper::Start() {
AssertIsOnDOMFileThread();
MOZ_ASSERT(mState == State::Initial);
mState = State::ResponsePending;
LSRequestChild* actor =
mObject->StartRequest(mNestedEventTargetWrapper, mParams, this);
if (NS_WARN_IF(!actor)) {
return NS_ERROR_FAILURE;
}
mActor = actor;
return NS_OK;
}
void RequestHelper::Finish() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::Finishing);
mObject = nullptr;
mWaiting = false;
mState = State::Complete;
}
NS_IMPL_ISUPPORTS_INHERITED0(RequestHelper, Runnable)
NS_IMETHODIMP
RequestHelper::Run() {
nsresult rv;
switch (mState) {
case State::Initial:
rv = Start();
break;
case State::Finishing:
Finish();
return NS_OK;
default:
MOZ_CRASH("Bad state!");
}
if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::Finishing) {
if (NS_SUCCEEDED(mResultCode)) {
mResultCode = rv;
}
mState = State::Finishing;
if (IsOnOwningThread()) {
Finish();
} else {
MOZ_ALWAYS_SUCCEEDS(
mNestedEventTargetWrapper->Dispatch(this, NS_DISPATCH_NORMAL));
}
}
return NS_OK;
}
void RequestHelper::OnResponse(const LSRequestResponse& aResponse) {
AssertIsOnDOMFileThread();
MOZ_ASSERT(mState == State::ResponsePending);
mActor = nullptr;
mResponse = aResponse;
mState = State::Finishing;
MOZ_ALWAYS_SUCCEEDS(
mNestedEventTargetWrapper->Dispatch(this, NS_DISPATCH_NORMAL));
}
} // namespace mozilla::dom