mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:18:48 +00:00
6ecfad14f8
- Bug 1140558 - Part 2 - Make the testing deepEqual implementation shared properly in ObjectUtils.jsm. r=yoric (f1a217e34a) - Bug 1154773 - Change undefined property a.prototype. r=Yoric (8a4dc37d6c) - Bug 1202971 (part 1) - Fix compile warnings in libmar. r=rstrong. (30cdf015c2) - bug 1200360 - fix $(DEFINES) usage in modules/libmar/tool/moz.build. r=mshal (26f1c5640b) - Bug 903135 - Updates to libmar needed to support B2G MAR signature verification. r=bbondy (81612e2200) - Bug 1228332 - Fix a potentially uninitialized pointer read, flagged by static analysis. r=spohl (9cac770c90) - Bug 1202971 (part 2) - Fix compile warnings in toolkit/mozapps/update/tests. r=rstrong. (814f2b3b8b) - Bug 973933 - Fix libmar warnings. r=rstrong. a=Callek (e0c2d3edcc) - Bug 1232219 (part 1) - Fix -Wunused warnings in libmar/. r=bbondy. (dc046504df) - Bug 1228281 - [GTK3] add padding to buttons. r=karlt (fe8ca55f58) - Bug 1232219 (part 2.5) - Fix -Wunused warnings in parser/expat/lib/. r=hsivonen. (e4d10c5d68) - Bug 1232219 (part 3) - Fix remaining -Wunused warnings. r=glandium. (35c5f3c38e) - Bug 1232219 (follow-up) - Fix nsinstall.c bustage in SM(e) builds. r=me. (d62c12cf35) - Bug 1186815 (part 1) - Replace nsBaseHashtable::Enumerate() calls in modules/libjar/ with iterators. r=mwu. (6959b19fab) - Bug 1186815 (part 2) - Replace nsBaseHashtable::Enumerate() calls in modules/libjar/ with iterators. r=mwu. (02c6f50482) - Bug 1181444 (part 1.5) - Remove dead PLDHashOperator declarations. r=froydnj. (c956e03dc0) - Bug 1225407 - Replace nsInterfaceHashtable::EnumerateRead() call in SubstitutingProtocolHandler with an iterator. r=michal (aa71f5499c) - Bug 1187781 (part 1) - Replace nsBaseHashtable::EnumerateRead() calls in dom/xbl/ with iterators. r=mrbkap. (68f51d05ba) - Bug 1187781 (part 2) - Replace nsBaseHashtable::EnumerateRead() calls in dom/xbl/ with iterators. r=mrbkap. (1dfc261267) - Bug 1187781 (part 3) - Replace nsBaseHashtable::EnumerateRead() calls in dom/xbl/ with iterators. r=mrbkap. (922fc4e4ef) - Bug 1187781 (part 4) - Replace nsBaseHashtable::EnumerateRead() calls in dom/xbl/ with iterators. r=mrbkap. (7b1915053f) - Bug 1187781 (part 5) - Replace nsBaseHashtable::EnumerateRead() calls in dom/xbl/ with iterators. r=mrbkap. (5df4cc1d09) - Bug 1181444 (part 2) - Remove nsBaseHashtable::Enumerate(). r=froydnj. (c606342f1d) - Bug 1243912 - Remove unused Loader::RemoveEntriesWithURI declaration. r=njn (e59b2ef892) - Bug 1187137 (part 4) - Replace nsBaseHashtable::Enumerate() calls in netwerk/protocol/ with iterators. r=valentin. (17fb17c48f) - Bug 1187137 (part 5) - Replace nsBaseHashtable::Enumerate() calls in netwerk/protocol/ with iterators. r=valentin. (89d430cf98) - Bug 1187137 (part 6) - Replace nsBaseHashtable::Enumerate() calls in netwerk/protocol/ with iterators. r=valentin. (37befe08c6) - Bug 1187137 (part 7) - Replace nsBaseHashtable::Enumerate() calls in netwerk/protocol/ with iterators. r=valentin (a8227c4b3e) - Bug 1187137 (part 1) - Replace nsBaseHashtable::Enumerate() calls in netwerk/protocol/ with iterators. r=michal. (05f71a4a94) - Bug 1187137 (part 2) - Replace nsBaseHashtable::Enumerate() calls in netwerk/protocol/ with iterators. r=michal. (5defa991b5) - Bug 1187137 (part 3) - Replace nsBaseHashtable::Enumerate() calls in netwerk/protocol/ with iterators. r=michal. (f2e0a4be0f) - Bug 1187137 (part 8) - Replace nsBaseHashtable::Enumerate() calls in netwerk/protocol/ with iterators. r=valentin. (11b77f3b67) - Bug 1187137 (part 9) - Replace nsBaseHashtable::Enumerate() calls in netwerk/protocol/ with iterators. r=valentin. (7a6bd9a17e) - Bug 1230743 - telemtry for nsConnectionEntry hit rate r=hurley (a75d6ec52c) - Bug 1218297 - eventtokenbucket shutdown leak r=valentin (1723059684) - Bug 1239961 - Minimize amount of PR_Poll and PR_Read calls during shutdown. r=mcmanus (127259afb6) - Bug 1187137 (part 10) - Replace nsBaseHashtable::Enumerate() calls in netwerk/protocol/ with iterators. r=valentin. (eee20459fb) - Bug 1187137 (part 11) - Replace nsBaseHashtable::Enumerate() calls in netwerk/protocol/ with iterators. r=valentin. (864bc96862) - Bug 1187137 (part 12) - Replace nsBaseHashtable::Enumerate() calls in netwerk/protocol/ with iterators. r=valentin. (4fd9540167) - Bug 1187137 (part 13) - Replace nsBaseHashtable::Enumerate() calls in netwerk/protocol/ with iterators. r=valentin. (65a01f4083) - Bug 1187151 (part 14) - Replace nsBaseHashtable::Enumerate() calls in dom/ with iterators. r=baku. (a71b4169a9) - Bug 1187141 - Replace nsBaseHashtable::Enumerate() calls in storage/ with iterators. r=mak. (d95fb168c9) - Bug 1187151 (part 11) - Replace nsBaseHashtable::Enumerate() calls in dom/ with iterators. r=khuey. (dda92d0455) - Bug 1187151 (part 10) - Replace nsBaseHashtable::Enumerate() calls in dom/ with iterators. r=khuey. (f59270049c) - Bug 1191460 Contextual Identity tests r=tanvi,r=ttaubert (03c079ede9) - Bug 1187151 (part 12) - Replace nsBaseHashtable::Enumerate() calls in dom/ with iterators. r=khuey. (0aa9356990) - Bug 1187151 (part 15) - Replace nsBaseHashtable::Enumerate() calls in dom/ with iterators. r=baku. (10e60a4b55) - Bug 1187151 (part 16) - Replace nsBaseHashtable::Enumerate() calls in dom/ with iterators. r=mccr8. (2ef1033aae) - Bug 1187151 (part 17) - Replace nsBaseHashtable::Enumerate() calls in dom/ with iterators. r=mccr8. (dc37dfc17b) - Bug 1241763: Don't fire dom-window-destroyed on outer windows. r=bz (ea30677af6) - Bug 1192128 - In DOM memory reporter, handle WindowID() being a uint64_t. r=mccr8. (b3f834b8f6) - Bug 1187151 (part 18) - Replace nsBaseHashtable::Enumerate() calls in dom/ with iterators. r=mccr8. (22d4fbe0f0) - Bug 1187138 (part 2) - Replace nsBaseHashtable::Enumerate() calls in toolkit/ with iterators. r=froydnj. (b4740ef44b) - Bug 1187138 (part 3) - Replace nsBaseHashtable::Enumerate() calls in toolkit/ with iterators. r=froydnj. (931eb9e813) - Bug 1187138 (part 4) - Replace nsBaseHashtable::Enumerate() calls in toolkit/ with iterators. r=froydnj. (a92fef3586) - Bug 1192189 - Fix assertion condition to use to-be-restyled element's composed document. r=dbaron (6c7733b7cc) - Bug 1187144 (part 10) - Replace nsBaseHashtable::Enumerate() calls in layout/ with iterators. r=heycam. (e93ad4330c) - Bug 1250525 - remove #IFDEF ENABLE_TESTS from dom/quota/ActorsParent.cpp. r=janv (22bc8fc0af) - Bug 1236632 - remove unused variable in FactoryOp::WaitForTransactions; r=janv (3c56b90fff) - Bug 1187116 (part 1) - Replace nsBaseHashtable::EnumerateRead() calls in dom/indexedDB/ with iterators. r=khuey. (f37ac71816) - Bug 1187116 (part 2) - Replace nsBaseHashtable::EnumerateRead() calls in dom/indexedDB/ with iterators. r=khuey. (6bf16c51fe) - Bug 1187116 (part 3) - Replace nsBaseHashtable::EnumerateRead() calls in dom/indexedDB/ with iterators. r=khuey. (351b7d5d34) - Bug 1187116 (part 4) - Replace nsBaseHashtable::EnumerateRead() calls in dom/indexedDB/ with iterators. r=khuey. (330678dde4) - Bug 1187116 (part 5) - Replace nsBaseHashtable::EnumerateRead() calls in dom/indexedDB/ with iterators. r=khuey. (43d20a7fa3) - Bug 1187116 (part 6) - Replace nsBaseHashtable::EnumerateRead() calls in dom/indexedDB/ with iterators. r=khuey. (d448d80726) - quick fix by revert of anticipated patch (00a093055d) - Bug 1186814 - Replace nsBaseHashtable::EnumerateRead() calls in extensions/spellcheck/ with iterators. r=ehsan. (ff78039ba0)
1328 lines
36 KiB
C++
1328 lines
36 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=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 "DOMStorageDBThread.h"
|
|
#include "DOMStorageCache.h"
|
|
|
|
#include "nsIEffectiveTLDService.h"
|
|
#include "nsDirectoryServiceUtils.h"
|
|
#include "nsAppDirectoryServiceDefs.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsProxyRelease.h"
|
|
#include "mozStorageCID.h"
|
|
#include "mozStorageHelper.h"
|
|
#include "mozIStorageService.h"
|
|
#include "mozIStorageBindingParamsArray.h"
|
|
#include "mozIStorageBindingParams.h"
|
|
#include "mozIStorageValueArray.h"
|
|
#include "mozIStorageFunction.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsVariant.h"
|
|
#include "mozilla/IOInterposer.h"
|
|
#include "mozilla/Services.h"
|
|
|
|
// How long we collect write oprerations
|
|
// before they are flushed to the database
|
|
// In milliseconds.
|
|
#define FLUSHING_INTERVAL_MS 5000
|
|
|
|
// Write Ahead Log's maximum size is 512KB
|
|
#define MAX_WAL_SIZE_BYTES 512 * 1024
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
DOMStorageDBBridge::DOMStorageDBBridge()
|
|
{
|
|
}
|
|
|
|
|
|
DOMStorageDBThread::DOMStorageDBThread()
|
|
: mThread(nullptr)
|
|
, mThreadObserver(new ThreadObserver())
|
|
, mStopIOThread(false)
|
|
, mWALModeEnabled(false)
|
|
, mDBReady(false)
|
|
, mStatus(NS_OK)
|
|
, mWorkerStatements(mWorkerConnection)
|
|
, mReaderStatements(mReaderConnection)
|
|
, mDirtyEpoch(0)
|
|
, mFlushImmediately(false)
|
|
, mPriorityCounter(0)
|
|
{
|
|
}
|
|
|
|
nsresult
|
|
DOMStorageDBThread::Init()
|
|
{
|
|
nsresult rv;
|
|
|
|
// Need to determine location on the main thread, since
|
|
// NS_GetSpecialDirectory access the atom table that can
|
|
// be accessed only on the main thread.
|
|
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
|
|
getter_AddRefs(mDatabaseFile));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = mDatabaseFile->Append(NS_LITERAL_STRING("webappsstore.sqlite"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Ensure mozIStorageService init on the main thread first.
|
|
nsCOMPtr<mozIStorageService> service =
|
|
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Need to keep the lock to avoid setting mThread later then
|
|
// the thread body executes.
|
|
MonitorAutoLock monitor(mThreadObserver->GetMonitor());
|
|
|
|
mThread = PR_CreateThread(PR_USER_THREAD, &DOMStorageDBThread::ThreadFunc, this,
|
|
PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
|
|
262144);
|
|
if (!mThread) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DOMStorageDBThread::Shutdown()
|
|
{
|
|
if (!mThread) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer;
|
|
|
|
{
|
|
MonitorAutoLock monitor(mThreadObserver->GetMonitor());
|
|
|
|
// After we stop, no other operations can be accepted
|
|
mFlushImmediately = true;
|
|
mStopIOThread = true;
|
|
monitor.Notify();
|
|
}
|
|
|
|
PR_JoinThread(mThread);
|
|
mThread = nullptr;
|
|
|
|
return mStatus;
|
|
}
|
|
|
|
void
|
|
DOMStorageDBThread::SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync)
|
|
{
|
|
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::STORAGE);
|
|
if (!aForceSync && aCache->LoadedCount()) {
|
|
// Preload already started for this cache, just wait for it to finish.
|
|
// LoadWait will exit after LoadDone on the cache has been called.
|
|
SetHigherPriority();
|
|
aCache->LoadWait();
|
|
SetDefaultPriority();
|
|
return;
|
|
}
|
|
|
|
// Bypass sync load when an update is pending in the queue to write, we would
|
|
// get incosistent data in the cache. Also don't allow sync main-thread preload
|
|
// when DB open and init is still pending on the background thread.
|
|
if (mDBReady && mWALModeEnabled) {
|
|
bool pendingTasks;
|
|
{
|
|
MonitorAutoLock monitor(mThreadObserver->GetMonitor());
|
|
pendingTasks = mPendingTasks.IsScopeUpdatePending(aCache->Scope()) ||
|
|
mPendingTasks.IsScopeClearPending(aCache->Scope());
|
|
}
|
|
|
|
if (!pendingTasks) {
|
|
// WAL is enabled, thus do the load synchronously on the main thread.
|
|
DBOperation preload(DBOperation::opPreload, aCache);
|
|
preload.PerformAndFinalize(this);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Need to go asynchronously since WAL is not allowed or scheduled updates
|
|
// need to be flushed first.
|
|
// Schedule preload for this cache as the first operation.
|
|
nsresult rv = InsertDBOp(new DBOperation(DBOperation::opPreloadUrgent, aCache));
|
|
|
|
// LoadWait exits after LoadDone of the cache has been called.
|
|
if (NS_SUCCEEDED(rv)) {
|
|
aCache->LoadWait();
|
|
}
|
|
}
|
|
|
|
void
|
|
DOMStorageDBThread::AsyncFlush()
|
|
{
|
|
MonitorAutoLock monitor(mThreadObserver->GetMonitor());
|
|
mFlushImmediately = true;
|
|
monitor.Notify();
|
|
}
|
|
|
|
bool
|
|
DOMStorageDBThread::ShouldPreloadScope(const nsACString& aScope)
|
|
{
|
|
MonitorAutoLock monitor(mThreadObserver->GetMonitor());
|
|
return mScopesHavingData.Contains(aScope);
|
|
}
|
|
|
|
void
|
|
DOMStorageDBThread::GetScopesHavingData(InfallibleTArray<nsCString>* aScopes)
|
|
{
|
|
MonitorAutoLock monitor(mThreadObserver->GetMonitor());
|
|
for (auto iter = mScopesHavingData.Iter(); !iter.Done(); iter.Next()) {
|
|
aScopes->AppendElement(iter.Get()->GetKey());
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
DOMStorageDBThread::InsertDBOp(DOMStorageDBThread::DBOperation* aOperation)
|
|
{
|
|
MonitorAutoLock monitor(mThreadObserver->GetMonitor());
|
|
|
|
// Sentinel to don't forget to delete the operation when we exit early.
|
|
nsAutoPtr<DOMStorageDBThread::DBOperation> opScope(aOperation);
|
|
|
|
if (NS_FAILED(mStatus)) {
|
|
MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
|
|
aOperation->Finalize(mStatus);
|
|
return mStatus;
|
|
}
|
|
|
|
if (mStopIOThread) {
|
|
// Thread use after shutdown demanded.
|
|
MOZ_ASSERT(false);
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
switch (aOperation->Type()) {
|
|
case DBOperation::opPreload:
|
|
case DBOperation::opPreloadUrgent:
|
|
if (mPendingTasks.IsScopeUpdatePending(aOperation->Scope())) {
|
|
// If there is a pending update operation for the scope first do the flush
|
|
// before we preload the cache. This may happen in an extremely rare case
|
|
// when a child process throws away its cache before flush on the parent
|
|
// has finished. If we would preloaded the cache as a priority operation
|
|
// before the pending flush, we would have got an inconsistent cache content.
|
|
mFlushImmediately = true;
|
|
} else if (mPendingTasks.IsScopeClearPending(aOperation->Scope())) {
|
|
// The scope is scheduled to be cleared, so just quickly load as empty.
|
|
// We need to do this to prevent load of the DB data before the scope has
|
|
// actually been cleared from the database. Preloads are processed
|
|
// immediately before update and clear operations on the database that
|
|
// are flushed periodically in batches.
|
|
MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
|
|
aOperation->Finalize(NS_OK);
|
|
return NS_OK;
|
|
}
|
|
// NO BREAK
|
|
|
|
case DBOperation::opGetUsage:
|
|
if (aOperation->Type() == DBOperation::opPreloadUrgent) {
|
|
SetHigherPriority(); // Dropped back after urgent preload execution
|
|
mPreloads.InsertElementAt(0, aOperation);
|
|
} else {
|
|
mPreloads.AppendElement(aOperation);
|
|
}
|
|
|
|
// DB operation adopted, don't delete it.
|
|
opScope.forget();
|
|
|
|
// Immediately start executing this.
|
|
monitor.Notify();
|
|
break;
|
|
|
|
default:
|
|
// Update operations are first collected, coalesced and then flushed
|
|
// after a short time.
|
|
mPendingTasks.Add(aOperation);
|
|
|
|
// DB operation adopted, don't delete it.
|
|
opScope.forget();
|
|
|
|
ScheduleFlush();
|
|
break;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DOMStorageDBThread::SetHigherPriority()
|
|
{
|
|
++mPriorityCounter;
|
|
PR_SetThreadPriority(mThread, PR_PRIORITY_URGENT);
|
|
}
|
|
|
|
void
|
|
DOMStorageDBThread::SetDefaultPriority()
|
|
{
|
|
if (--mPriorityCounter <= 0) {
|
|
PR_SetThreadPriority(mThread, PR_PRIORITY_LOW);
|
|
}
|
|
}
|
|
|
|
void
|
|
DOMStorageDBThread::ThreadFunc(void* aArg)
|
|
{
|
|
PR_SetCurrentThreadName("localStorage DB");
|
|
mozilla::IOInterposer::RegisterCurrentThread();
|
|
|
|
DOMStorageDBThread* thread = static_cast<DOMStorageDBThread*>(aArg);
|
|
thread->ThreadFunc();
|
|
mozilla::IOInterposer::UnregisterCurrentThread();
|
|
}
|
|
|
|
void
|
|
DOMStorageDBThread::ThreadFunc()
|
|
{
|
|
nsresult rv = InitDatabase();
|
|
|
|
MonitorAutoLock lockMonitor(mThreadObserver->GetMonitor());
|
|
|
|
if (NS_FAILED(rv)) {
|
|
mStatus = rv;
|
|
mStopIOThread = true;
|
|
return;
|
|
}
|
|
|
|
// Create an nsIThread for the current PRThread, so we can observe runnables
|
|
// dispatched to it.
|
|
nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
|
|
nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread);
|
|
MOZ_ASSERT(threadInternal); // Should always succeed.
|
|
threadInternal->SetObserver(mThreadObserver);
|
|
|
|
while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() ||
|
|
mPendingTasks.HasTasks() ||
|
|
mThreadObserver->HasPendingEvents())) {
|
|
// Process xpcom events first.
|
|
while (MOZ_UNLIKELY(mThreadObserver->HasPendingEvents())) {
|
|
mThreadObserver->ClearPendingEvents();
|
|
MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
|
|
bool processedEvent;
|
|
do {
|
|
rv = thread->ProcessNextEvent(false, &processedEvent);
|
|
} while (NS_SUCCEEDED(rv) && processedEvent);
|
|
}
|
|
|
|
if (MOZ_UNLIKELY(TimeUntilFlush() == 0)) {
|
|
// Flush time is up or flush has been forced, do it now.
|
|
UnscheduleFlush();
|
|
if (mPendingTasks.Prepare()) {
|
|
{
|
|
MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
|
|
rv = mPendingTasks.Execute(this);
|
|
}
|
|
|
|
if (!mPendingTasks.Finalize(rv)) {
|
|
mStatus = rv;
|
|
NS_WARNING("localStorage DB access broken");
|
|
}
|
|
}
|
|
NotifyFlushCompletion();
|
|
} else if (MOZ_LIKELY(mPreloads.Length())) {
|
|
nsAutoPtr<DBOperation> op(mPreloads[0]);
|
|
mPreloads.RemoveElementAt(0);
|
|
{
|
|
MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
|
|
op->PerformAndFinalize(this);
|
|
}
|
|
|
|
if (op->Type() == DBOperation::opPreloadUrgent) {
|
|
SetDefaultPriority(); // urgent preload unscheduled
|
|
}
|
|
} else if (MOZ_UNLIKELY(!mStopIOThread)) {
|
|
lockMonitor.Wait(TimeUntilFlush());
|
|
}
|
|
} // thread loop
|
|
|
|
mStatus = ShutdownDatabase();
|
|
|
|
if (threadInternal) {
|
|
threadInternal->SetObserver(nullptr);
|
|
}
|
|
}
|
|
|
|
|
|
NS_IMPL_ISUPPORTS(DOMStorageDBThread::ThreadObserver, nsIThreadObserver)
|
|
|
|
NS_IMETHODIMP
|
|
DOMStorageDBThread::ThreadObserver::OnDispatchedEvent(nsIThreadInternal *thread)
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
mHasPendingEvents = true;
|
|
lock.Notify();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DOMStorageDBThread::ThreadObserver::OnProcessNextEvent(nsIThreadInternal *thread,
|
|
bool mayWait)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DOMStorageDBThread::ThreadObserver::AfterProcessNextEvent(nsIThreadInternal *thread,
|
|
bool eventWasProcessed)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
extern void
|
|
ReverseString(const nsCSubstring& aSource, nsCSubstring& aResult);
|
|
|
|
namespace {
|
|
|
|
class nsReverseStringSQLFunction final : public mozIStorageFunction
|
|
{
|
|
~nsReverseStringSQLFunction() {}
|
|
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_MOZISTORAGEFUNCTION
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(nsReverseStringSQLFunction, mozIStorageFunction)
|
|
|
|
NS_IMETHODIMP
|
|
nsReverseStringSQLFunction::OnFunctionCall(
|
|
mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsAutoCString stringToReverse;
|
|
rv = aFunctionArguments->GetUTF8String(0, stringToReverse);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsAutoCString result;
|
|
ReverseString(stringToReverse, result);
|
|
|
|
RefPtr<nsVariant> outVar(new nsVariant());
|
|
rv = outVar->SetAsAUTF8String(result);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
outVar.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
nsresult
|
|
DOMStorageDBThread::OpenDatabaseConnection()
|
|
{
|
|
nsresult rv;
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
nsCOMPtr<mozIStorageService> service
|
|
= do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<mozIStorageConnection> connection;
|
|
rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
|
|
if (rv == NS_ERROR_FILE_CORRUPTED) {
|
|
// delete the db and try opening again
|
|
rv = mDatabaseFile->Remove(false);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
|
|
}
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DOMStorageDBThread::InitDatabase()
|
|
{
|
|
Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_INIT_DATABASE_MS> timer;
|
|
|
|
nsresult rv;
|
|
|
|
// Here we are on the worker thread. This opens the worker connection.
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
rv = OpenDatabaseConnection();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = TryJournalMode();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Create a read-only clone
|
|
(void)mWorkerConnection->Clone(true, getter_AddRefs(mReaderConnection));
|
|
NS_ENSURE_TRUE(mReaderConnection, NS_ERROR_FAILURE);
|
|
|
|
mozStorageTransaction transaction(mWorkerConnection, false);
|
|
|
|
// Ensure Gecko 1.9.1 storage table
|
|
rv = mWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE IF NOT EXISTS webappsstore2 ("
|
|
"scope TEXT, "
|
|
"key TEXT, "
|
|
"value TEXT, "
|
|
"secure INTEGER, "
|
|
"owner TEXT)"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = mWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index"
|
|
" ON webappsstore2(scope, key)"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<mozIStorageFunction> function1(new nsReverseStringSQLFunction());
|
|
NS_ENSURE_TRUE(function1, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
rv = mWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("REVERSESTRING"), 1, function1);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
bool exists;
|
|
|
|
// Check if there is storage of Gecko 1.9.0 and if so, upgrade that storage
|
|
// to actual webappsstore2 table and drop the obsolete table. First process
|
|
// this newer table upgrade to priority potential duplicates from older
|
|
// storage table.
|
|
rv = mWorkerConnection->TableExists(NS_LITERAL_CSTRING("webappsstore"),
|
|
&exists);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (exists) {
|
|
rv = mWorkerConnection->ExecuteSimpleSQL(
|
|
NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
|
|
"webappsstore2(scope, key, value, secure, owner) "
|
|
"SELECT REVERSESTRING(domain) || '.:', key, value, secure, owner "
|
|
"FROM webappsstore"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = mWorkerConnection->ExecuteSimpleSQL(
|
|
NS_LITERAL_CSTRING("DROP TABLE webappsstore"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
// Check if there is storage of Gecko 1.8 and if so, upgrade that storage
|
|
// to actual webappsstore2 table and drop the obsolete table. Potential
|
|
// duplicates will be ignored.
|
|
rv = mWorkerConnection->TableExists(NS_LITERAL_CSTRING("moz_webappsstore"),
|
|
&exists);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (exists) {
|
|
rv = mWorkerConnection->ExecuteSimpleSQL(
|
|
NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
|
|
"webappsstore2(scope, key, value, secure, owner) "
|
|
"SELECT REVERSESTRING(domain) || '.:', key, value, secure, domain "
|
|
"FROM moz_webappsstore"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = mWorkerConnection->ExecuteSimpleSQL(
|
|
NS_LITERAL_CSTRING("DROP TABLE moz_webappsstore"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
rv = transaction.Commit();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Database open and all initiation operation are done. Switching this flag
|
|
// to true allow main thread to read directly from the database.
|
|
// If we would allow this sooner, we would have opened a window where main thread
|
|
// read might operate on a totaly broken and incosistent database.
|
|
mDBReady = true;
|
|
|
|
// List scopes having any stored data
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING("SELECT DISTINCT scope FROM webappsstore2"),
|
|
getter_AddRefs(stmt));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
mozStorageStatementScoper scope(stmt);
|
|
|
|
while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
|
|
nsAutoCString foundScope;
|
|
rv = stmt->GetUTF8String(0, foundScope);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
MonitorAutoLock monitor(mThreadObserver->GetMonitor());
|
|
mScopesHavingData.PutEntry(foundScope);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DOMStorageDBThread::SetJournalMode(bool aIsWal)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsAutoCString stmtString(
|
|
MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = ");
|
|
if (aIsWal) {
|
|
stmtString.AppendLiteral("wal");
|
|
} else {
|
|
stmtString.AppendLiteral("truncate");
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = mWorkerConnection->CreateStatement(stmtString, getter_AddRefs(stmt));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
mozStorageStatementScoper scope(stmt);
|
|
|
|
bool hasResult = false;
|
|
rv = stmt->ExecuteStep(&hasResult);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (!hasResult) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsAutoCString journalMode;
|
|
rv = stmt->GetUTF8String(0, journalMode);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if ((aIsWal && !journalMode.EqualsLiteral("wal")) ||
|
|
(!aIsWal && !journalMode.EqualsLiteral("truncate"))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DOMStorageDBThread::TryJournalMode()
|
|
{
|
|
nsresult rv;
|
|
|
|
rv = SetJournalMode(true);
|
|
if (NS_FAILED(rv)) {
|
|
mWALModeEnabled = false;
|
|
|
|
rv = SetJournalMode(false);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
} else {
|
|
mWALModeEnabled = true;
|
|
|
|
rv = ConfigureWALBehavior();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DOMStorageDBThread::ConfigureWALBehavior()
|
|
{
|
|
// Get the DB's page size
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
nsresult rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"
|
|
), getter_AddRefs(stmt));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
bool hasResult = false;
|
|
rv = stmt->ExecuteStep(&hasResult);
|
|
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
|
|
|
|
int32_t pageSize = 0;
|
|
rv = stmt->GetInt32(0, &pageSize);
|
|
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && pageSize > 0, NS_ERROR_UNEXPECTED);
|
|
|
|
// Set the threshold for auto-checkpointing the WAL.
|
|
// We don't want giant logs slowing down reads & shutdown.
|
|
int32_t thresholdInPages = static_cast<int32_t>(MAX_WAL_SIZE_BYTES / pageSize);
|
|
nsAutoCString thresholdPragma("PRAGMA wal_autocheckpoint = ");
|
|
thresholdPragma.AppendInt(thresholdInPages);
|
|
rv = mWorkerConnection->ExecuteSimpleSQL(thresholdPragma);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Set the maximum WAL log size to reduce footprint on mobile (large empty
|
|
// WAL files will be truncated)
|
|
nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
|
|
// bug 600307: mak recommends setting this to 3 times the auto-checkpoint threshold
|
|
journalSizePragma.AppendInt(MAX_WAL_SIZE_BYTES * 3);
|
|
rv = mWorkerConnection->ExecuteSimpleSQL(journalSizePragma);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DOMStorageDBThread::ShutdownDatabase()
|
|
{
|
|
// Has to be called on the worker thread.
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
nsresult rv = mStatus;
|
|
|
|
mDBReady = false;
|
|
|
|
// Finalize the cached statements.
|
|
mReaderStatements.FinalizeStatements();
|
|
mWorkerStatements.FinalizeStatements();
|
|
|
|
if (mReaderConnection) {
|
|
// No need to sync access to mReaderConnection since the main thread
|
|
// is right now joining this thread, unable to execute any events.
|
|
mReaderConnection->Close();
|
|
mReaderConnection = nullptr;
|
|
}
|
|
|
|
if (mWorkerConnection) {
|
|
rv = mWorkerConnection->Close();
|
|
mWorkerConnection = nullptr;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
DOMStorageDBThread::ScheduleFlush()
|
|
{
|
|
if (mDirtyEpoch) {
|
|
return; // Already scheduled
|
|
}
|
|
|
|
mDirtyEpoch = PR_IntervalNow() | 1; // Must be non-zero to indicate we are scheduled
|
|
|
|
// Wake the monitor from indefinite sleep...
|
|
(mThreadObserver->GetMonitor()).Notify();
|
|
}
|
|
|
|
void
|
|
DOMStorageDBThread::UnscheduleFlush()
|
|
{
|
|
// We are just about to do the flush, drop flags
|
|
mFlushImmediately = false;
|
|
mDirtyEpoch = 0;
|
|
}
|
|
|
|
PRIntervalTime
|
|
DOMStorageDBThread::TimeUntilFlush()
|
|
{
|
|
if (mFlushImmediately) {
|
|
return 0; // Do it now regardless the timeout.
|
|
}
|
|
|
|
static_assert(PR_INTERVAL_NO_TIMEOUT != 0,
|
|
"PR_INTERVAL_NO_TIMEOUT must be non-zero");
|
|
|
|
if (!mDirtyEpoch) {
|
|
return PR_INTERVAL_NO_TIMEOUT; // No pending task...
|
|
}
|
|
|
|
static const PRIntervalTime kMaxAge = PR_MillisecondsToInterval(FLUSHING_INTERVAL_MS);
|
|
|
|
PRIntervalTime now = PR_IntervalNow() | 1;
|
|
PRIntervalTime age = now - mDirtyEpoch;
|
|
if (age > kMaxAge) {
|
|
return 0; // It is time.
|
|
}
|
|
|
|
return kMaxAge - age; // Time left, this is used to sleep the monitor
|
|
}
|
|
|
|
void
|
|
DOMStorageDBThread::NotifyFlushCompletion()
|
|
{
|
|
#ifdef DOM_STORAGE_TESTS
|
|
if (!NS_IsMainThread()) {
|
|
RefPtr<nsRunnableMethod<DOMStorageDBThread, void, false> > event =
|
|
NS_NewNonOwningRunnableMethod(this, &DOMStorageDBThread::NotifyFlushCompletion);
|
|
NS_DispatchToMainThread(event);
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (obs) {
|
|
obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// DOMStorageDBThread::DBOperation
|
|
|
|
DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
|
|
DOMStorageCacheBridge* aCache,
|
|
const nsAString& aKey,
|
|
const nsAString& aValue)
|
|
: mType(aType)
|
|
, mCache(aCache)
|
|
, mKey(aKey)
|
|
, mValue(aValue)
|
|
{
|
|
MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
|
|
}
|
|
|
|
DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
|
|
DOMStorageUsageBridge* aUsage)
|
|
: mType(aType)
|
|
, mUsage(aUsage)
|
|
{
|
|
MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
|
|
}
|
|
|
|
DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
|
|
const nsACString& aScope)
|
|
: mType(aType)
|
|
, mCache(nullptr)
|
|
, mScope(aScope)
|
|
{
|
|
MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
|
|
}
|
|
|
|
DOMStorageDBThread::DBOperation::~DBOperation()
|
|
{
|
|
MOZ_COUNT_DTOR(DOMStorageDBThread::DBOperation);
|
|
}
|
|
|
|
const nsCString
|
|
DOMStorageDBThread::DBOperation::Scope()
|
|
{
|
|
if (mCache) {
|
|
return mCache->Scope();
|
|
}
|
|
|
|
return mScope;
|
|
}
|
|
|
|
const nsCString
|
|
DOMStorageDBThread::DBOperation::Target()
|
|
{
|
|
switch (mType) {
|
|
case opAddItem:
|
|
case opUpdateItem:
|
|
case opRemoveItem:
|
|
return Scope() + NS_LITERAL_CSTRING("|") + NS_ConvertUTF16toUTF8(mKey);
|
|
|
|
default:
|
|
return Scope();
|
|
}
|
|
}
|
|
|
|
void
|
|
DOMStorageDBThread::DBOperation::PerformAndFinalize(DOMStorageDBThread* aThread)
|
|
{
|
|
Finalize(Perform(aThread));
|
|
}
|
|
|
|
nsresult
|
|
DOMStorageDBThread::DBOperation::Perform(DOMStorageDBThread* aThread)
|
|
{
|
|
nsresult rv;
|
|
|
|
switch (mType) {
|
|
case opPreload:
|
|
case opPreloadUrgent:
|
|
{
|
|
// Already loaded?
|
|
if (mCache->Loaded()) {
|
|
break;
|
|
}
|
|
|
|
StatementCache* statements;
|
|
if (MOZ_UNLIKELY(NS_IsMainThread())) {
|
|
statements = &aThread->mReaderStatements;
|
|
} else {
|
|
statements = &aThread->mWorkerStatements;
|
|
}
|
|
|
|
// OFFSET is an optimization when we have to do a sync load
|
|
// and cache has already loaded some parts asynchronously.
|
|
// It skips keys we have already loaded.
|
|
nsCOMPtr<mozIStorageStatement> stmt = statements->GetCachedStatement(
|
|
"SELECT key, value FROM webappsstore2 "
|
|
"WHERE scope = :scope ORDER BY key "
|
|
"LIMIT -1 OFFSET :offset");
|
|
NS_ENSURE_STATE(stmt);
|
|
mozStorageStatementScoper scope(stmt);
|
|
|
|
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
|
|
mCache->Scope());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("offset"),
|
|
static_cast<int32_t>(mCache->LoadedCount()));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
bool exists;
|
|
while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
|
|
nsAutoString key;
|
|
rv = stmt->GetString(0, key);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsAutoString value;
|
|
rv = stmt->GetString(1, value);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!mCache->LoadItem(key, value)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// The loop condition's call to ExecuteStep() may have terminated because
|
|
// !NS_SUCCEEDED(), we need an early return to cover that case. This also
|
|
// covers success cases as well, but that's inductively safe.
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
break;
|
|
}
|
|
|
|
case opGetUsage:
|
|
{
|
|
nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
|
|
"SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2"
|
|
" WHERE scope LIKE :scope"
|
|
);
|
|
NS_ENSURE_STATE(stmt);
|
|
|
|
mozStorageStatementScoper scope(stmt);
|
|
|
|
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
|
|
mUsage->Scope() + NS_LITERAL_CSTRING("%"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
bool exists;
|
|
rv = stmt->ExecuteStep(&exists);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
int64_t usage = 0;
|
|
if (exists) {
|
|
rv = stmt->GetInt64(0, &usage);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
mUsage->LoadUsage(usage);
|
|
break;
|
|
}
|
|
|
|
case opAddItem:
|
|
case opUpdateItem:
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
|
|
"INSERT OR REPLACE INTO webappsstore2 (scope, key, value) "
|
|
"VALUES (:scope, :key, :value) "
|
|
);
|
|
NS_ENSURE_STATE(stmt);
|
|
|
|
mozStorageStatementScoper scope(stmt);
|
|
|
|
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
|
|
mCache->Scope());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
|
|
mKey);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"),
|
|
mValue);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = stmt->Execute();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
aThread->mScopesHavingData.PutEntry(Scope());
|
|
break;
|
|
}
|
|
|
|
case opRemoveItem:
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
|
|
"DELETE FROM webappsstore2 "
|
|
"WHERE scope = :scope "
|
|
"AND key = :key "
|
|
);
|
|
NS_ENSURE_STATE(stmt);
|
|
mozStorageStatementScoper scope(stmt);
|
|
|
|
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
|
|
mCache->Scope());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
|
|
mKey);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = stmt->Execute();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
break;
|
|
}
|
|
|
|
case opClear:
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
|
|
"DELETE FROM webappsstore2 "
|
|
"WHERE scope = :scope"
|
|
);
|
|
NS_ENSURE_STATE(stmt);
|
|
mozStorageStatementScoper scope(stmt);
|
|
|
|
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
|
|
mCache->Scope());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = stmt->Execute();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
aThread->mScopesHavingData.RemoveEntry(Scope());
|
|
break;
|
|
}
|
|
|
|
case opClearAll:
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
|
|
"DELETE FROM webappsstore2"
|
|
);
|
|
NS_ENSURE_STATE(stmt);
|
|
mozStorageStatementScoper scope(stmt);
|
|
|
|
rv = stmt->Execute();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
aThread->mScopesHavingData.Clear();
|
|
break;
|
|
}
|
|
|
|
case opClearMatchingScope:
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
|
|
"DELETE FROM webappsstore2"
|
|
" WHERE scope GLOB :scope"
|
|
);
|
|
NS_ENSURE_STATE(stmt);
|
|
mozStorageStatementScoper scope(stmt);
|
|
|
|
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
|
|
mScope + NS_LITERAL_CSTRING("*"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = stmt->Execute();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
NS_ERROR("Unknown task type");
|
|
break;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DOMStorageDBThread::DBOperation::Finalize(nsresult aRv)
|
|
{
|
|
switch (mType) {
|
|
case opPreloadUrgent:
|
|
case opPreload:
|
|
if (NS_FAILED(aRv)) {
|
|
// When we are here, something failed when loading from the database.
|
|
// Notify that the storage is loaded to prevent deadlock of the main thread,
|
|
// even though it is actually empty or incomplete.
|
|
NS_WARNING("Failed to preload localStorage");
|
|
}
|
|
|
|
mCache->LoadDone(aRv);
|
|
break;
|
|
|
|
case opGetUsage:
|
|
if (NS_FAILED(aRv)) {
|
|
mUsage->LoadUsage(0);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
if (NS_FAILED(aRv)) {
|
|
NS_WARNING("localStorage update/clear operation failed,"
|
|
" data may not persist or clean up");
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// DOMStorageDBThread::PendingOperations
|
|
|
|
DOMStorageDBThread::PendingOperations::PendingOperations()
|
|
: mFlushFailureCount(0)
|
|
{
|
|
}
|
|
|
|
bool
|
|
DOMStorageDBThread::PendingOperations::HasTasks()
|
|
{
|
|
return !!mUpdates.Count() || !!mClears.Count();
|
|
}
|
|
|
|
namespace {
|
|
|
|
} // namespace
|
|
|
|
bool
|
|
DOMStorageDBThread::PendingOperations::CheckForCoalesceOpportunity(DBOperation* aNewOp,
|
|
DBOperation::OperationType aPendingType,
|
|
DBOperation::OperationType aNewType)
|
|
{
|
|
if (aNewOp->Type() != aNewType) {
|
|
return false;
|
|
}
|
|
|
|
DOMStorageDBThread::DBOperation* pendingTask;
|
|
if (!mUpdates.Get(aNewOp->Target(), &pendingTask)) {
|
|
return false;
|
|
}
|
|
|
|
if (pendingTask->Type() != aPendingType) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
DOMStorageDBThread::PendingOperations::Add(DOMStorageDBThread::DBOperation* aOperation)
|
|
{
|
|
// Optimize: when a key to remove has never been written to disk
|
|
// just bypass this operation. A kew is new when an operation scheduled
|
|
// to write it to the database is of type opAddItem.
|
|
if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem, DBOperation::opRemoveItem)) {
|
|
mUpdates.Remove(aOperation->Target());
|
|
delete aOperation;
|
|
return;
|
|
}
|
|
|
|
// Optimize: when changing a key that is new and has never been
|
|
// written to disk, keep type of the operation to store it at opAddItem.
|
|
// This allows optimization to just forget adding a new key when
|
|
// it is removed from the storage before flush.
|
|
if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem, DBOperation::opUpdateItem)) {
|
|
aOperation->mType = DBOperation::opAddItem;
|
|
}
|
|
|
|
// Optimize: to prevent lose of remove operation on a key when doing
|
|
// remove/set/remove on a previously existing key we have to change
|
|
// opAddItem to opUpdateItem on the new operation when there is opRemoveItem
|
|
// pending for the key.
|
|
if (CheckForCoalesceOpportunity(aOperation, DBOperation::opRemoveItem, DBOperation::opAddItem)) {
|
|
aOperation->mType = DBOperation::opUpdateItem;
|
|
}
|
|
|
|
switch (aOperation->Type())
|
|
{
|
|
// Operations on single keys
|
|
|
|
case DBOperation::opAddItem:
|
|
case DBOperation::opUpdateItem:
|
|
case DBOperation::opRemoveItem:
|
|
// Override any existing operation for the target (=scope+key).
|
|
mUpdates.Put(aOperation->Target(), aOperation);
|
|
break;
|
|
|
|
// Clear operations
|
|
|
|
case DBOperation::opClear:
|
|
case DBOperation::opClearMatchingScope:
|
|
// Drop all update (insert/remove) operations for equivavelent or matching scope.
|
|
// We do this as an optimization as well as a must based on the logic,
|
|
// if we would not delete the update tasks, changes would have been stored
|
|
// to the database after clear operations have been executed.
|
|
for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
|
|
nsAutoPtr<DBOperation>& pendingTask = iter.Data();
|
|
|
|
if (aOperation->Type() == DBOperation::opClear &&
|
|
pendingTask->Scope() != aOperation->Scope()) {
|
|
continue;
|
|
}
|
|
|
|
if (aOperation->Type() == DBOperation::opClearMatchingScope &&
|
|
!StringBeginsWith(pendingTask->Scope(), aOperation->Scope())) {
|
|
continue;
|
|
}
|
|
|
|
iter.Remove();
|
|
}
|
|
|
|
mClears.Put(aOperation->Target(), aOperation);
|
|
break;
|
|
|
|
case DBOperation::opClearAll:
|
|
// Drop simply everything, this is a super-operation.
|
|
mUpdates.Clear();
|
|
mClears.Clear();
|
|
mClears.Put(aOperation->Target(), aOperation);
|
|
break;
|
|
|
|
default:
|
|
MOZ_ASSERT(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool
|
|
DOMStorageDBThread::PendingOperations::Prepare()
|
|
{
|
|
// Called under the lock
|
|
|
|
// First collect clear operations and then updates, we can
|
|
// do this since whenever a clear operation for a scope is
|
|
// scheduled, we drop all updates matching that scope. So,
|
|
// all scope-related update operations we have here now were
|
|
// scheduled after the clear operations.
|
|
for (auto iter = mClears.Iter(); !iter.Done(); iter.Next()) {
|
|
mExecList.AppendElement(iter.Data().forget());
|
|
}
|
|
mClears.Clear();
|
|
|
|
for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
|
|
mExecList.AppendElement(iter.Data().forget());
|
|
}
|
|
mUpdates.Clear();
|
|
|
|
return !!mExecList.Length();
|
|
}
|
|
|
|
nsresult
|
|
DOMStorageDBThread::PendingOperations::Execute(DOMStorageDBThread* aThread)
|
|
{
|
|
// Called outside the lock
|
|
|
|
mozStorageTransaction transaction(aThread->mWorkerConnection, false);
|
|
|
|
nsresult rv;
|
|
|
|
for (uint32_t i = 0; i < mExecList.Length(); ++i) {
|
|
DOMStorageDBThread::DBOperation* task = mExecList[i];
|
|
rv = task->Perform(aThread);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = transaction.Commit();
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
DOMStorageDBThread::PendingOperations::Finalize(nsresult aRv)
|
|
{
|
|
// Called under the lock
|
|
|
|
// The list is kept on a failure to retry it
|
|
if (NS_FAILED(aRv)) {
|
|
// XXX Followup: we may try to reopen the database and flush these
|
|
// pending tasks, however testing showed that even though I/O is actually
|
|
// broken some amount of operations is left in sqlite+system buffers and
|
|
// seems like successfully flushed to disk.
|
|
// Tested by removing a flash card and disconnecting from network while
|
|
// using a network drive on Windows system.
|
|
NS_WARNING("Flush operation on localStorage database failed");
|
|
|
|
++mFlushFailureCount;
|
|
|
|
return mFlushFailureCount >= 5;
|
|
}
|
|
|
|
mFlushFailureCount = 0;
|
|
mExecList.Clear();
|
|
return true;
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool
|
|
FindPendingClearForScope(const nsACString& aScope,
|
|
DOMStorageDBThread::DBOperation* aPendingOperation)
|
|
{
|
|
if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearAll) {
|
|
return true;
|
|
}
|
|
|
|
if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClear &&
|
|
aScope == aPendingOperation->Scope()) {
|
|
return true;
|
|
}
|
|
|
|
if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearMatchingScope &&
|
|
StringBeginsWith(aScope, aPendingOperation->Scope())) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool
|
|
DOMStorageDBThread::PendingOperations::IsScopeClearPending(const nsACString& aScope)
|
|
{
|
|
// Called under the lock
|
|
|
|
for (auto iter = mClears.Iter(); !iter.Done(); iter.Next()) {
|
|
if (FindPendingClearForScope(aScope, iter.UserData())) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < mExecList.Length(); ++i) {
|
|
if (FindPendingClearForScope(aScope, mExecList[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool
|
|
FindPendingUpdateForScope(const nsACString& aScope,
|
|
DOMStorageDBThread::DBOperation* aPendingOperation)
|
|
{
|
|
if ((aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opAddItem ||
|
|
aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opUpdateItem ||
|
|
aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opRemoveItem) &&
|
|
aScope == aPendingOperation->Scope()) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool
|
|
DOMStorageDBThread::PendingOperations::IsScopeUpdatePending(const nsACString& aScope)
|
|
{
|
|
// Called under the lock
|
|
|
|
for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
|
|
if (FindPendingUpdateForScope(aScope, iter.UserData())) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < mExecList.Length(); ++i) {
|
|
if (FindPendingUpdateForScope(aScope, mExecList[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|