gecko-dev/dom/localstorage/LSSnapshot.cpp

737 строки
17 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 "LSSnapshot.h"
#include "nsContentUtils.h"
namespace mozilla {
namespace dom {
namespace {
const uint32_t kSnapshotTimeoutMs = 20000;
} // namespace
LSSnapshot::LSSnapshot(LSDatabase* aDatabase)
: mDatabase(aDatabase),
mActor(nullptr),
mInitLength(0),
mLength(0),
mExactUsage(0),
mPeakUsage(0),
mLoadState(LoadState::Initial),
mExplicit(false),
mHasPendingStableStateCallback(false),
mHasPendingTimerCallback(false),
mDirty(false)
#ifdef DEBUG
,
mInitialized(false),
mSentFinish(false)
#endif
{
AssertIsOnOwningThread();
}
LSSnapshot::~LSSnapshot() {
AssertIsOnOwningThread();
MOZ_ASSERT(mDatabase);
MOZ_ASSERT(!mHasPendingStableStateCallback);
MOZ_ASSERT(!mHasPendingTimerCallback);
MOZ_ASSERT_IF(mInitialized, mSentFinish);
if (mActor) {
mActor->SendDeleteMeInternal();
MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
}
}
void LSSnapshot::SetActor(LSSnapshotChild* aActor) {
AssertIsOnOwningThread();
MOZ_ASSERT(aActor);
MOZ_ASSERT(!mActor);
mActor = aActor;
}
nsresult LSSnapshot::Init(const LSSnapshotInitInfo& aInitInfo, bool aExplicit) {
AssertIsOnOwningThread();
MOZ_ASSERT(!mSelfRef);
MOZ_ASSERT(mActor);
MOZ_ASSERT(mLoadState == LoadState::Initial);
MOZ_ASSERT(!mInitialized);
MOZ_ASSERT(!mSentFinish);
mSelfRef = this;
LoadState loadState = aInitInfo.loadState();
const nsTArray<LSItemInfo>& itemInfos = aInitInfo.itemInfos();
for (uint32_t i = 0; i < itemInfos.Length(); i++) {
const LSItemInfo& itemInfo = itemInfos[i];
const nsString& value = itemInfo.value();
if (loadState != LoadState::AllOrderedItems && !value.IsVoid()) {
mLoadedItems.PutEntry(itemInfo.key());
}
mValues.Put(itemInfo.key(), value);
}
if (loadState == LoadState::Partial) {
mInitLength = aInitInfo.totalLength();
mLength = mInitLength;
} else if (loadState == LoadState::AllOrderedKeys) {
mInitLength = aInitInfo.totalLength();
} else {
MOZ_ASSERT(loadState == LoadState::AllOrderedItems);
}
mExactUsage = aInitInfo.initialUsage();
mPeakUsage = aInitInfo.peakUsage();
mLoadState = aInitInfo.loadState();
mExplicit = aExplicit;
#ifdef DEBUG
mInitialized = true;
#endif
if (!mExplicit) {
mTimer = NS_NewTimer();
MOZ_ASSERT(mTimer);
ScheduleStableStateCallback();
}
return NS_OK;
}
nsresult LSSnapshot::GetLength(uint32_t* aResult) {
AssertIsOnOwningThread();
MOZ_ASSERT(mActor);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
MaybeScheduleStableStateCallback();
if (mLoadState == LoadState::Partial) {
*aResult = mLength;
} else {
*aResult = mValues.Count();
}
return NS_OK;
}
nsresult LSSnapshot::GetKey(uint32_t aIndex, nsAString& aResult) {
AssertIsOnOwningThread();
MOZ_ASSERT(mActor);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
MaybeScheduleStableStateCallback();
nsresult rv = EnsureAllKeys();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aResult.SetIsVoid(true);
for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
if (aIndex == 0) {
aResult = iter.Key();
return NS_OK;
}
aIndex--;
}
return NS_OK;
}
nsresult LSSnapshot::GetItem(const nsAString& aKey, nsAString& aResult) {
AssertIsOnOwningThread();
MOZ_ASSERT(mActor);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
MaybeScheduleStableStateCallback();
nsString result;
nsresult rv = GetItemInternal(aKey, Optional<nsString>(), result);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aResult = result;
return NS_OK;
}
nsresult LSSnapshot::GetKeys(nsTArray<nsString>& aKeys) {
AssertIsOnOwningThread();
MOZ_ASSERT(mActor);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
MaybeScheduleStableStateCallback();
nsresult rv = EnsureAllKeys();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
aKeys.AppendElement(iter.Key());
}
return NS_OK;
}
nsresult LSSnapshot::SetItem(const nsAString& aKey, const nsAString& aValue,
LSNotifyInfo& aNotifyInfo) {
AssertIsOnOwningThread();
MOZ_ASSERT(mActor);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
MaybeScheduleStableStateCallback();
nsString oldValue;
nsresult rv =
GetItemInternal(aKey, Optional<nsString>(nsString(aValue)), oldValue);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
bool changed;
if (oldValue == aValue && oldValue.IsVoid() == aValue.IsVoid()) {
changed = false;
} else {
changed = true;
int64_t delta = static_cast<int64_t>(aValue.Length()) -
static_cast<int64_t>(oldValue.Length());
if (oldValue.IsVoid()) {
delta += static_cast<int64_t>(aKey.Length());
}
rv = UpdateUsage(delta);
if (NS_WARN_IF(NS_FAILED(rv))) {
if (oldValue.IsVoid()) {
mValues.Remove(aKey);
} else {
mValues.Put(aKey, oldValue);
}
return rv;
}
if (oldValue.IsVoid() && mLoadState == LoadState::Partial) {
mLength++;
}
LSSetItemInfo setItemInfo;
setItemInfo.key() = aKey;
setItemInfo.oldValue() = oldValue;
setItemInfo.value() = aValue;
mWriteInfos.AppendElement(std::move(setItemInfo));
}
aNotifyInfo.changed() = changed;
aNotifyInfo.oldValue() = oldValue;
return NS_OK;
}
nsresult LSSnapshot::RemoveItem(const nsAString& aKey,
LSNotifyInfo& aNotifyInfo) {
AssertIsOnOwningThread();
MOZ_ASSERT(mActor);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
MaybeScheduleStableStateCallback();
nsString oldValue;
nsresult rv =
GetItemInternal(aKey, Optional<nsString>(VoidString()), oldValue);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
bool changed;
if (oldValue.IsVoid()) {
changed = false;
} else {
changed = true;
int64_t delta = -(static_cast<int64_t>(aKey.Length()) +
static_cast<int64_t>(oldValue.Length()));
DebugOnly<nsresult> rv = UpdateUsage(delta);
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (mLoadState == LoadState::Partial) {
mLength--;
}
LSRemoveItemInfo removeItemInfo;
removeItemInfo.key() = aKey;
removeItemInfo.oldValue() = oldValue;
mWriteInfos.AppendElement(std::move(removeItemInfo));
}
aNotifyInfo.changed() = changed;
aNotifyInfo.oldValue() = oldValue;
return NS_OK;
}
nsresult LSSnapshot::Clear(LSNotifyInfo& aNotifyInfo) {
AssertIsOnOwningThread();
MOZ_ASSERT(mActor);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
MaybeScheduleStableStateCallback();
uint32_t length;
if (mLoadState == LoadState::Partial) {
length = mLength;
MOZ_ASSERT(length);
MOZ_ALWAYS_TRUE(mActor->SendLoaded());
mLoadedItems.Clear();
mUnknownItems.Clear();
mLength = 0;
mLoadState = LoadState::AllOrderedItems;
} else {
length = mValues.Count();
}
bool changed;
if (!length) {
changed = false;
} else {
changed = true;
DebugOnly<nsresult> rv = UpdateUsage(-mExactUsage);
MOZ_ASSERT(NS_SUCCEEDED(rv));
mValues.Clear();
LSClearInfo clearInfo;
mWriteInfos.AppendElement(std::move(clearInfo));
}
aNotifyInfo.changed() = changed;
return NS_OK;
}
void LSSnapshot::MarkDirty() {
AssertIsOnOwningThread();
MOZ_ASSERT(mActor);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
if (mDirty) {
return;
}
mDirty = true;
if (!mExplicit && !mHasPendingStableStateCallback) {
CancelTimer();
MOZ_ALWAYS_SUCCEEDS(Checkpoint());
MOZ_ALWAYS_SUCCEEDS(Finish());
} else {
MOZ_ASSERT(!mHasPendingTimerCallback);
}
}
nsresult LSSnapshot::End() {
AssertIsOnOwningThread();
MOZ_ASSERT(mActor);
MOZ_ASSERT(mExplicit);
MOZ_ASSERT(!mHasPendingStableStateCallback);
MOZ_ASSERT(!mHasPendingTimerCallback);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
nsresult rv = Checkpoint();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
RefPtr<LSSnapshot> kungFuDeathGrip = this;
rv = Finish();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (NS_WARN_IF(!mActor->SendPing())) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
void LSSnapshot::ScheduleStableStateCallback() {
AssertIsOnOwningThread();
MOZ_ASSERT(mTimer);
MOZ_ASSERT(!mExplicit);
MOZ_ASSERT(!mHasPendingStableStateCallback);
CancelTimer();
nsCOMPtr<nsIRunnable> runnable = this;
nsContentUtils::RunInStableState(runnable.forget());
mHasPendingStableStateCallback = true;
}
void LSSnapshot::MaybeScheduleStableStateCallback() {
AssertIsOnOwningThread();
if (!mExplicit && !mHasPendingStableStateCallback) {
ScheduleStableStateCallback();
} else {
MOZ_ASSERT(!mHasPendingTimerCallback);
}
}
nsresult LSSnapshot::GetItemInternal(const nsAString& aKey,
const Optional<nsString>& aValue,
nsAString& aResult) {
AssertIsOnOwningThread();
MOZ_ASSERT(mActor);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
nsString result;
switch (mLoadState) {
case LoadState::Partial: {
if (mValues.Get(aKey, &result)) {
MOZ_ASSERT(!result.IsVoid());
} else if (mLoadedItems.GetEntry(aKey) || mUnknownItems.GetEntry(aKey)) {
result.SetIsVoid(true);
} else {
if (NS_WARN_IF(!mActor->SendLoadItem(nsString(aKey), &result))) {
return NS_ERROR_FAILURE;
}
if (result.IsVoid()) {
mUnknownItems.PutEntry(aKey);
} else {
mLoadedItems.PutEntry(aKey);
mValues.Put(aKey, result);
if (mLoadedItems.Count() == mInitLength) {
mLoadedItems.Clear();
mUnknownItems.Clear();
mLength = 0;
mLoadState = LoadState::AllUnorderedItems;
}
}
}
if (aValue.WasPassed()) {
const nsString& value = aValue.Value();
if (!value.IsVoid()) {
mValues.Put(aKey, value);
} else if (!result.IsVoid()) {
mValues.Remove(aKey);
}
}
break;
}
case LoadState::AllOrderedKeys: {
if (mValues.Get(aKey, &result)) {
if (result.IsVoid()) {
if (NS_WARN_IF(!mActor->SendLoadItem(nsString(aKey), &result))) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(!result.IsVoid());
mLoadedItems.PutEntry(aKey);
mValues.Put(aKey, result);
if (mLoadedItems.Count() == mInitLength) {
mLoadedItems.Clear();
MOZ_ASSERT(mLength == 0);
mLoadState = LoadState::AllOrderedItems;
}
}
} else {
result.SetIsVoid(true);
}
if (aValue.WasPassed()) {
const nsString& value = aValue.Value();
if (!value.IsVoid()) {
mValues.Put(aKey, value);
} else if (!result.IsVoid()) {
mValues.Remove(aKey);
}
}
break;
}
case LoadState::AllUnorderedItems:
case LoadState::AllOrderedItems: {
if (aValue.WasPassed()) {
const nsString& value = aValue.Value();
if (!value.IsVoid()) {
auto entry = mValues.LookupForAdd(aKey);
if (entry) {
result = entry.Data();
entry.Data() = value;
} else {
result.SetIsVoid(true);
entry.OrInsert([value]() { return value; });
}
} else {
if (auto entry = mValues.Lookup(aKey)) {
result = entry.Data();
MOZ_ASSERT(!result.IsVoid());
entry.Remove();
} else {
result.SetIsVoid(true);
}
}
} else {
if (mValues.Get(aKey, &result)) {
MOZ_ASSERT(!result.IsVoid());
} else {
result.SetIsVoid(true);
}
}
break;
}
default:
MOZ_CRASH("Bad state!");
}
aResult = result;
return NS_OK;
}
nsresult LSSnapshot::EnsureAllKeys() {
AssertIsOnOwningThread();
MOZ_ASSERT(mActor);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
MOZ_ASSERT(mLoadState != LoadState::Initial);
if (mLoadState == LoadState::AllOrderedKeys ||
mLoadState == LoadState::AllOrderedItems) {
return NS_OK;
}
nsTArray<nsString> keys;
if (NS_WARN_IF(!mActor->SendLoadKeys(&keys))) {
return NS_ERROR_FAILURE;
}
nsDataHashtable<nsStringHashKey, nsString> newValues;
for (auto key : keys) {
newValues.Put(key, VoidString());
}
for (uint32_t index = 0; index < mWriteInfos.Length(); index++) {
const LSWriteInfo& writeInfo = mWriteInfos[index];
switch (writeInfo.type()) {
case LSWriteInfo::TLSSetItemInfo: {
newValues.Put(writeInfo.get_LSSetItemInfo().key(), VoidString());
break;
}
case LSWriteInfo::TLSRemoveItemInfo: {
newValues.Remove(writeInfo.get_LSRemoveItemInfo().key());
break;
}
case LSWriteInfo::TLSClearInfo: {
newValues.Clear();
break;
}
default:
MOZ_CRASH("Should never get here!");
}
}
MOZ_ASSERT_IF(mLoadState == LoadState::AllUnorderedItems,
newValues.Count() == mValues.Count());
for (auto iter = newValues.Iter(); !iter.Done(); iter.Next()) {
nsString value;
if (mValues.Get(iter.Key(), &value)) {
iter.Data() = value;
}
}
mValues.SwapElements(newValues);
if (mLoadState == LoadState::Partial) {
mUnknownItems.Clear();
mLength = 0;
mLoadState = LoadState::AllOrderedKeys;
} else {
MOZ_ASSERT(mLoadState == LoadState::AllUnorderedItems);
MOZ_ASSERT(mUnknownItems.Count() == 0);
MOZ_ASSERT(mLength == 0);
mLoadState = LoadState::AllOrderedItems;
}
return NS_OK;
}
nsresult LSSnapshot::UpdateUsage(int64_t aDelta) {
AssertIsOnOwningThread();
MOZ_ASSERT(mDatabase);
MOZ_ASSERT(mActor);
MOZ_ASSERT(mPeakUsage >= mExactUsage);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
int64_t newExactUsage = mExactUsage + aDelta;
if (newExactUsage > mPeakUsage) {
int64_t minSize = newExactUsage - mPeakUsage;
int64_t requestedSize = minSize + 4096;
int64_t size;
if (NS_WARN_IF(
!mActor->SendIncreasePeakUsage(requestedSize, minSize, &size))) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(size >= 0);
if (size == 0) {
return NS_ERROR_FILE_NO_DEVICE_SPACE;
}
mPeakUsage += size;
}
mExactUsage = newExactUsage;
return NS_OK;
}
nsresult LSSnapshot::Checkpoint() {
AssertIsOnOwningThread();
MOZ_ASSERT(mActor);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
if (!mWriteInfos.IsEmpty()) {
MOZ_ALWAYS_TRUE(mActor->SendCheckpoint(mWriteInfos));
mWriteInfos.Clear();
}
return NS_OK;
}
nsresult LSSnapshot::Finish() {
AssertIsOnOwningThread();
MOZ_ASSERT(mDatabase);
MOZ_ASSERT(mActor);
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(!mSentFinish);
MOZ_ALWAYS_TRUE(mActor->SendFinish());
mDatabase->NoteFinishedSnapshot(this);
#ifdef DEBUG
mSentFinish = true;
#endif
// Clear the self reference added in Init method.
MOZ_ASSERT(mSelfRef);
mSelfRef = nullptr;
return NS_OK;
}
void LSSnapshot::CancelTimer() {
AssertIsOnOwningThread();
MOZ_ASSERT(mTimer);
if (mHasPendingTimerCallback) {
MOZ_ALWAYS_SUCCEEDS(mTimer->Cancel());
mHasPendingTimerCallback = false;
}
}
// static
void LSSnapshot::TimerCallback(nsITimer* aTimer, void* aClosure) {
MOZ_ASSERT(aTimer);
auto* self = static_cast<LSSnapshot*>(aClosure);
MOZ_ASSERT(self);
MOZ_ASSERT(self->mTimer);
MOZ_ASSERT(SameCOMIdentity(self->mTimer, aTimer));
MOZ_ASSERT(!self->mHasPendingStableStateCallback);
MOZ_ASSERT(self->mHasPendingTimerCallback);
self->mHasPendingTimerCallback = false;
MOZ_ALWAYS_SUCCEEDS(self->Finish());
}
NS_IMPL_ISUPPORTS(LSSnapshot, nsIRunnable)
NS_IMETHODIMP
LSSnapshot::Run() {
AssertIsOnOwningThread();
MOZ_ASSERT(!mExplicit);
MOZ_ASSERT(mHasPendingStableStateCallback);
MOZ_ASSERT(!mHasPendingTimerCallback);
mHasPendingStableStateCallback = false;
MOZ_ALWAYS_SUCCEEDS(Checkpoint());
if (mDirty || !Preferences::GetBool("dom.storage.snapshot_reusing")) {
MOZ_ALWAYS_SUCCEEDS(Finish());
} else if (!mExplicit) {
MOZ_ASSERT(mTimer);
MOZ_ALWAYS_SUCCEEDS(mTimer->InitWithNamedFuncCallback(
TimerCallback, this, kSnapshotTimeoutMs, nsITimer::TYPE_ONE_SHOT,
"LSSnapshot::TimerCallback"));
mHasPendingTimerCallback = true;
}
return NS_OK;
}
} // namespace dom
} // namespace mozilla