Bug 1165214 - Use OriginAttributes in DOM Storage. r=smaug, r=bholley

--HG--
extra : rebase_source : b63ddb5a24a335f771a856cd20c69cdeb0c92ca0
This commit is contained in:
Honza Bambas 2016-01-05 07:25:00 -05:00
parent 7cb7d44fc1
commit a0a6f7e23c
16 changed files with 1114 additions and 471 deletions

View File

@ -80,8 +80,7 @@ add_task(function* test() {
// check each item in the title and validate it meets expectatations
for (let part of title) {
let [storageMethodName, value] = part.split("=");
let is_f = storageMethodName == "cookie" ? is : todo_is;
is_f(value, expectedContext,
is(value, expectedContext,
"the title reflects the expected contextual identity of " +
expectedContext + " for method " + storageMethodName + ": " + value);
}

View File

@ -132,6 +132,7 @@ OriginAttributes::CreateSuffix(nsACString& aStr) const
}
if (!mSignedPkg.IsEmpty()) {
MOZ_RELEASE_ASSERT(mSignedPkg.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) == kNotFound);
params->Set(NS_LITERAL_STRING("signedPkg"), mSignedPkg);
}

View File

@ -76,8 +76,8 @@ NS_IMETHODIMP_(void) DOMStorageCacheBridge::Release(void)
// DOMStorageCache
DOMStorageCache::DOMStorageCache(const nsACString* aScope)
: mScope(*aScope)
DOMStorageCache::DOMStorageCache(const nsACString* aOriginNoSuffix)
: mOriginNoSuffix(*aOriginNoSuffix)
, mMonitor("DOMStorageCache")
, mLoaded(false)
, mLoadResult(NS_OK)
@ -124,7 +124,7 @@ void
DOMStorageCache::Init(DOMStorageManager* aManager,
bool aPersistent,
nsIPrincipal* aPrincipal,
const nsACString& aQuotaScope)
const nsACString& aQuotaOriginScope)
{
if (mInitialized) {
return;
@ -132,15 +132,26 @@ DOMStorageCache::Init(DOMStorageManager* aManager,
mInitialized = true;
mPrincipal = aPrincipal;
BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(mOriginSuffix);
mPersistent = aPersistent;
mQuotaScope = aQuotaScope.IsEmpty() ? mScope : aQuotaScope;
if (aQuotaOriginScope.IsEmpty()) {
mQuotaOriginScope = Origin();
} else {
mQuotaOriginScope = aQuotaOriginScope;
}
if (mPersistent) {
mManager = aManager;
Preload();
}
mUsage = aManager->GetScopeUsage(mQuotaScope);
// Check the quota string has (or has not) the identical origin suffix as
// this storage cache is bound to.
MOZ_ASSERT(StringBeginsWith(mQuotaOriginScope, mOriginSuffix));
MOZ_ASSERT(mOriginSuffix.IsEmpty() != StringBeginsWith(mQuotaOriginScope,
NS_LITERAL_CSTRING("^")));
mUsage = aManager->GetOriginUsage(mQuotaOriginScope);
}
inline bool
@ -151,6 +162,12 @@ DOMStorageCache::Persist(const DOMStorage* aStorage) const
!aStorage->IsPrivate();
}
const nsCString
DOMStorageCache::Origin() const
{
return DOMStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix);
}
DOMStorageCache::Data&
DOMStorageCache::DataSet(const DOMStorage* aStorage)
{
@ -658,8 +675,8 @@ DOMStorageCache::LoadWait()
// DOMStorageUsage
DOMStorageUsage::DOMStorageUsage(const nsACString& aScope)
: mScope(aScope)
DOMStorageUsage::DOMStorageUsage(const nsACString& aOriginScope)
: mOriginScope(aOriginScope)
{
mUsage[kDefaultSet] = mUsage[kPrivateSet] = mUsage[kSessionSet] = 0LL;
}

View File

@ -33,8 +33,16 @@ public:
NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
NS_IMETHOD_(void) Release(void);
// The scope (origin) in the database usage format (reversed)
virtual const nsCString& Scope() const = 0;
// The origin of the cache, result is concatenation of OriginNoSuffix() and OriginSuffix(),
// see below.
virtual const nsCString Origin() const = 0;
// The origin attributes suffix alone, this is usually passed as an |aOriginSuffix|
// argument to various methods
virtual const nsCString& OriginSuffix() const = 0;
// The origin in the database usage format (reversed) and without the suffix
virtual const nsCString& OriginNoSuffix() const = 0;
// Whether the cache is already fully loaded
virtual bool Loaded() = 0;
@ -70,14 +78,17 @@ class DOMStorageCache : public DOMStorageCacheBridge
public:
NS_IMETHOD_(void) Release(void);
explicit DOMStorageCache(const nsACString* aScope);
// Note: We pass aOriginNoSuffix through the ctor here, because
// DOMStorageCacheHashKey's ctor is creating this class and
// accepts reversed-origin-no-suffix as an argument - the hashing key.
explicit DOMStorageCache(const nsACString* aOriginNoSuffix);
protected:
virtual ~DOMStorageCache();
public:
void Init(DOMStorageManager* aManager, bool aPersistent, nsIPrincipal* aPrincipal,
const nsACString& aQuotaScope);
const nsACString& aQuotaOriginScope);
// Copies all data from the other storage.
void CloneFrom(const DOMStorageCache* aThat);
@ -114,7 +125,9 @@ public:
// DOMStorageCacheBridge
virtual const nsCString& Scope() const { return mScope; }
virtual const nsCString Origin() const;
virtual const nsCString& OriginNoSuffix() const { return mOriginNoSuffix; }
virtual const nsCString& OriginSuffix() const { return mOriginSuffix; }
virtual bool Loaded() { return mLoaded; }
virtual uint32_t LoadedCount();
virtual bool LoadItem(const nsAString& aKey, const nsString& aValue);
@ -188,11 +201,15 @@ private:
// origin only.
nsCOMPtr<nsIPrincipal> mPrincipal;
// The scope this cache belongs to in the "DB format", i.e. reversed
nsCString mScope;
// The origin this cache belongs to in the "DB format", i.e. reversed
nsCString mOriginNoSuffix;
// The eTLD+1 scope used to count quota usage.
nsCString mQuotaScope;
// The origin attributes suffix
nsCString mOriginSuffix;
// The eTLD+1 scope used to count quota usage. It is in the reversed format
// and contains the origin attributes suffix.
nsCString mQuotaOriginScope;
// Non-private Browsing, Private Browsing and Session Only sets.
Data mData[kDataSetCount];
@ -241,7 +258,7 @@ class DOMStorageUsageBridge
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DOMStorageUsageBridge)
virtual const nsCString& Scope() = 0;
virtual const nsCString& OriginScope() = 0;
virtual void LoadUsage(const int64_t aUsage) = 0;
protected:
@ -252,15 +269,15 @@ protected:
class DOMStorageUsage : public DOMStorageUsageBridge
{
public:
explicit DOMStorageUsage(const nsACString& aScope);
explicit DOMStorageUsage(const nsACString& aOriginScope);
bool CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, int64_t aUsageDelta);
private:
virtual const nsCString& Scope() { return mScope; }
virtual const nsCString& OriginScope() { return mOriginScope; }
virtual void LoadUsage(const int64_t aUsage);
nsCString mScope;
nsCString mOriginScope;
int64_t mUsage[DOMStorageCache::kDataSetCount];
};

View File

@ -5,7 +5,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DOMStorageDBThread.h"
#include "DOMStorageDBUpdater.h"
#include "DOMStorageCache.h"
#include "DOMStorageManager.h"
#include "nsIEffectiveTLDService.h"
#include "nsDirectoryServiceUtils.h"
@ -19,10 +21,12 @@
#include "mozIStorageBindingParams.h"
#include "mozIStorageValueArray.h"
#include "mozIStorageFunction.h"
#include "mozilla/BasePrincipal.h"
#include "nsIObserverService.h"
#include "nsVariant.h"
#include "mozilla/IOInterposer.h"
#include "mozilla/Services.h"
#include "mozilla/Tokenizer.h"
// How long we collect write oprerations
// before they are flushed to the database
@ -32,9 +36,43 @@
// Write Ahead Log's maximum size is 512KB
#define MAX_WAL_SIZE_BYTES 512 * 1024
// Current version of the database schema
#define CURRENT_SCHEMA_VERSION 1
namespace mozilla {
namespace dom {
namespace { // anon
// This is only a compatibility code for schema version 0. Returns the 'scope' key
// in the schema version 0 format for the scope column.
nsCString
Scheme0Scope(DOMStorageCacheBridge* aCache)
{
nsCString result;
nsCString suffix = aCache->OriginSuffix();
PrincipalOriginAttributes oa;
if (!suffix.IsEmpty()) {
oa.PopulateFromSuffix(suffix);
}
if (oa.mAppId != nsIScriptSecurityManager::NO_APP_ID || oa.mInBrowser) {
result.AppendInt(oa.mAppId);
result.Append(':');
result.Append(oa.mInBrowser ? 't' : 'f');
result.Append(':');
}
result.Append(aCache->OriginNoSuffix());
return result;
}
} // anon
DOMStorageDBBridge::DOMStorageDBBridge()
{
}
@ -132,8 +170,8 @@ DOMStorageDBThread::SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync)
bool pendingTasks;
{
MonitorAutoLock monitor(mThreadObserver->GetMonitor());
pendingTasks = mPendingTasks.IsScopeUpdatePending(aCache->Scope()) ||
mPendingTasks.IsScopeClearPending(aCache->Scope());
pendingTasks = mPendingTasks.IsOriginUpdatePending(aCache->OriginSuffix(), aCache->OriginNoSuffix()) ||
mPendingTasks.IsOriginClearPending(aCache->OriginSuffix(), aCache->OriginNoSuffix());
}
if (!pendingTasks) {
@ -164,18 +202,18 @@ DOMStorageDBThread::AsyncFlush()
}
bool
DOMStorageDBThread::ShouldPreloadScope(const nsACString& aScope)
DOMStorageDBThread::ShouldPreloadOrigin(const nsACString& aOrigin)
{
MonitorAutoLock monitor(mThreadObserver->GetMonitor());
return mScopesHavingData.Contains(aScope);
return mOriginsHavingData.Contains(aOrigin);
}
void
DOMStorageDBThread::GetScopesHavingData(InfallibleTArray<nsCString>* aScopes)
DOMStorageDBThread::GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins)
{
MonitorAutoLock monitor(mThreadObserver->GetMonitor());
for (auto iter = mScopesHavingData.Iter(); !iter.Done(); iter.Next()) {
aScopes->AppendElement(iter.Get()->GetKey());
for (auto iter = mOriginsHavingData.Iter(); !iter.Done(); iter.Next()) {
aOrigins->AppendElement(iter.Get()->GetKey());
}
}
@ -202,14 +240,14 @@ DOMStorageDBThread::InsertDBOp(DOMStorageDBThread::DBOperation* aOperation)
switch (aOperation->Type()) {
case DBOperation::opPreload:
case DBOperation::opPreloadUrgent:
if (mPendingTasks.IsScopeUpdatePending(aOperation->Scope())) {
if (mPendingTasks.IsOriginUpdatePending(aOperation->OriginSuffix(), aOperation->OriginNoSuffix())) {
// 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())) {
} else if (mPendingTasks.IsOriginClearPending(aOperation->OriginSuffix(), aOperation->OriginNoSuffix())) {
// 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
@ -378,41 +416,6 @@ DOMStorageDBThread::ThreadObserver::AfterProcessNextEvent(nsIThreadInternal *thr
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()
{
@ -457,73 +460,7 @@ DOMStorageDBThread::InitDatabase()
(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();
rv = DOMStorageDBUpdater::Update(mWorkerConnection);
NS_ENSURE_SUCCESS(rv, rv);
// Database open and all initiation operation are done. Switching this flag
@ -534,18 +471,21 @@ DOMStorageDBThread::InitDatabase()
// List scopes having any stored data
nsCOMPtr<mozIStorageStatement> stmt;
rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING("SELECT DISTINCT scope FROM webappsstore2"),
getter_AddRefs(stmt));
// Note: result of this select must match DOMStorageManager::CreateOrigin()
rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING(
"SELECT DISTINCT originAttributes || ':' || originKey FROM webappsstore2"),
getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
mozStorageStatementScoper scope(stmt);
bool exists;
while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
nsAutoCString foundScope;
rv = stmt->GetUTF8String(0, foundScope);
nsAutoCString foundOrigin;
rv = stmt->GetUTF8String(0, foundOrigin);
NS_ENSURE_SUCCESS(rv, rv);
MonitorAutoLock monitor(mThreadObserver->GetMonitor());
mScopesHavingData.PutEntry(foundScope);
mOriginsHavingData.PutEntry(foundOrigin);
}
return NS_OK;
@ -738,6 +678,51 @@ DOMStorageDBThread::NotifyFlushCompletion()
#endif
}
// Helper SQL function classes
namespace {
class OriginAttrsPatternMatchSQLFunction final : public mozIStorageFunction
{
NS_DECL_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
explicit OriginAttrsPatternMatchSQLFunction(OriginAttributesPattern const& aPattern)
: mPattern(aPattern) {}
private:
OriginAttrsPatternMatchSQLFunction() = delete;
~OriginAttrsPatternMatchSQLFunction() {}
OriginAttributesPattern mPattern;
};
NS_IMPL_ISUPPORTS(OriginAttrsPatternMatchSQLFunction, mozIStorageFunction)
NS_IMETHODIMP
OriginAttrsPatternMatchSQLFunction::OnFunctionCall(
mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
{
nsresult rv;
nsAutoCString suffix;
rv = aFunctionArguments->GetUTF8String(0, suffix);
NS_ENSURE_SUCCESS(rv, rv);
PrincipalOriginAttributes oa;
oa.PopulateFromSuffix(suffix);
bool result = mPattern.Matches(oa);
RefPtr<nsVariant> outVar(new nsVariant());
rv = outVar->SetAsBool(result);
NS_ENSURE_SUCCESS(rv, rv);
outVar.forget(aResult);
return NS_OK;
}
} // namespace
// DOMStorageDBThread::DBOperation
DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
@ -749,6 +734,13 @@ DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
, mKey(aKey)
, mValue(aValue)
{
MOZ_ASSERT(mType == opPreload ||
mType == opPreloadUrgent ||
mType == opAddItem ||
mType == opUpdateItem ||
mType == opRemoveItem ||
mType == opClear ||
mType == opClearAll);
MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
}
@ -757,15 +749,27 @@ DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
: mType(aType)
, mUsage(aUsage)
{
MOZ_ASSERT(mType == opGetUsage);
MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
}
DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
const nsACString& aScope)
const nsACString& aOriginNoSuffix)
: mType(aType)
, mCache(nullptr)
, mScope(aScope)
, mOrigin(aOriginNoSuffix)
{
MOZ_ASSERT(mType == opClearMatchingOrigin);
MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
}
DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
const OriginAttributesPattern& aOriginNoSuffix)
: mType(aType)
, mCache(nullptr)
, mOriginPattern(aOriginNoSuffix)
{
MOZ_ASSERT(mType == opClearMatchingOriginAttributes);
MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
}
@ -775,26 +779,46 @@ DOMStorageDBThread::DBOperation::~DBOperation()
}
const nsCString
DOMStorageDBThread::DBOperation::Scope()
DOMStorageDBThread::DBOperation::OriginNoSuffix() const
{
if (mCache) {
return mCache->Scope();
return mCache->OriginNoSuffix();
}
return mScope;
return EmptyCString();
}
const nsCString
DOMStorageDBThread::DBOperation::Target()
DOMStorageDBThread::DBOperation::OriginSuffix() const
{
if (mCache) {
return mCache->OriginSuffix();
}
return EmptyCString();
}
const nsCString
DOMStorageDBThread::DBOperation::Origin() const
{
if (mCache) {
return mCache->Origin();
}
return mOrigin;
}
const nsCString
DOMStorageDBThread::DBOperation::Target() const
{
switch (mType) {
case opAddItem:
case opUpdateItem:
case opRemoveItem:
return Scope() + NS_LITERAL_CSTRING("|") + NS_ConvertUTF16toUTF8(mKey);
return Origin() + NS_LITERAL_CSTRING("|") + NS_ConvertUTF16toUTF8(mKey);
default:
return Scope();
return Origin();
}
}
@ -830,13 +854,17 @@ DOMStorageDBThread::DBOperation::Perform(DOMStorageDBThread* aThread)
// 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");
"WHERE originAttributes = :originAttributes AND originKey = :originKey "
"ORDER BY key LIMIT -1 OFFSET :offset");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scope(stmt);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
mCache->Scope());
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
mCache->OriginSuffix());
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
mCache->OriginNoSuffix());
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("offset"),
@ -865,15 +893,15 @@ DOMStorageDBThread::DBOperation::Perform(DOMStorageDBThread* aThread)
case opGetUsage:
{
nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
"SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2"
" WHERE scope LIKE :scope"
"SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2 "
"WHERE (originAttributes || ':' || originKey) LIKE :usageOrigin"
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scope(stmt);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
mUsage->Scope() + NS_LITERAL_CSTRING("%"));
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("usageOrigin"),
mUsage->OriginScope());
NS_ENSURE_SUCCESS(rv, rv);
bool exists;
@ -896,15 +924,22 @@ DOMStorageDBThread::DBOperation::Perform(DOMStorageDBThread* aThread)
MOZ_ASSERT(!NS_IsMainThread());
nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
"INSERT OR REPLACE INTO webappsstore2 (scope, key, value) "
"VALUES (:scope, :key, :value) "
"INSERT OR REPLACE INTO webappsstore2 (originAttributes, originKey, scope, key, value) "
"VALUES (:originAttributes, :originKey, :scope, :key, :value) "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scope(stmt);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
mCache->OriginSuffix());
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
mCache->OriginNoSuffix());
NS_ENSURE_SUCCESS(rv, rv);
// Filling the 'scope' column just for downgrade compatibility reasons
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
mCache->Scope());
Scheme0Scope(mCache));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
mKey);
@ -916,7 +951,8 @@ DOMStorageDBThread::DBOperation::Perform(DOMStorageDBThread* aThread)
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
aThread->mScopesHavingData.PutEntry(Scope());
MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
aThread->mOriginsHavingData.PutEntry(Origin());
break;
}
@ -926,14 +962,17 @@ DOMStorageDBThread::DBOperation::Perform(DOMStorageDBThread* aThread)
nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
"DELETE FROM webappsstore2 "
"WHERE scope = :scope "
"WHERE originAttributes = :originAttributes AND originKey = :originKey "
"AND key = :key "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scope(stmt);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
mCache->Scope());
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
mCache->OriginSuffix());
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
mCache->OriginNoSuffix());
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
mKey);
@ -951,19 +990,23 @@ DOMStorageDBThread::DBOperation::Perform(DOMStorageDBThread* aThread)
nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
"DELETE FROM webappsstore2 "
"WHERE scope = :scope"
"WHERE originAttributes = :originAttributes AND originKey = :originKey"
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scope(stmt);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
mCache->Scope());
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
mCache->OriginSuffix());
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
mCache->OriginNoSuffix());
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
aThread->mScopesHavingData.RemoveEntry(Scope());
MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
aThread->mOriginsHavingData.RemoveEntry(Origin());
break;
}
@ -980,28 +1023,68 @@ DOMStorageDBThread::DBOperation::Perform(DOMStorageDBThread* aThread)
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
aThread->mScopesHavingData.Clear();
MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
aThread->mOriginsHavingData.Clear();
break;
}
case opClearMatchingScope:
case opClearMatchingOrigin:
{
MOZ_ASSERT(!NS_IsMainThread());
nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
"DELETE FROM webappsstore2"
" WHERE scope GLOB :scope"
" WHERE originKey GLOB :scope"
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scope(stmt);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
mScope + NS_LITERAL_CSTRING("*"));
mOrigin + NS_LITERAL_CSTRING("*"));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// No need to selectively clear mOriginsHavingData here. That hashtable only
// prevents preload for scopes with no data. Leaving a false record in it has
// a negligible effect on performance.
break;
}
case opClearMatchingOriginAttributes:
{
MOZ_ASSERT(!NS_IsMainThread());
// Register the ORIGIN_ATTRS_PATTERN_MATCH function, initialized with the pattern
nsCOMPtr<mozIStorageFunction> patternMatchFunction(
new OriginAttrsPatternMatchSQLFunction(mOriginPattern));
rv = aThread->mWorkerConnection->CreateFunction(
NS_LITERAL_CSTRING("ORIGIN_ATTRS_PATTERN_MATCH"), 1, patternMatchFunction);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
"DELETE FROM webappsstore2"
" WHERE ORIGIN_ATTRS_PATTERN_MATCH(originAttributes)"
);
if (stmt) {
mozStorageStatementScoper scope(stmt);
rv = stmt->Execute();
} else {
rv = NS_ERROR_UNEXPECTED;
}
// Always remove the function
aThread->mWorkerConnection->RemoveFunction(
NS_LITERAL_CSTRING("ORIGIN_ATTRS_PATTERN_MATCH"));
NS_ENSURE_SUCCESS(rv, rv);
// No need to selectively clear mOriginsHavingData here. That hashtable only
// prevents preload for scopes with no data. Leaving a false record in it has
// a negligible effect on performance.
break;
}
@ -1054,27 +1137,41 @@ DOMStorageDBThread::PendingOperations::PendingOperations()
}
bool
DOMStorageDBThread::PendingOperations::HasTasks()
DOMStorageDBThread::PendingOperations::HasTasks() const
{
return !!mUpdates.Count() || !!mClears.Count();
}
namespace {
bool OriginPatternMatches(const nsACString& aOriginSuffix, const OriginAttributesPattern& aPattern)
{
PrincipalOriginAttributes oa;
DebugOnly<bool> rv = oa.PopulateFromSuffix(aOriginSuffix);
MOZ_ASSERT(rv);
return aPattern.Matches(oa);
}
PLDHashOperator
ForgetUpdatesForScope(const nsACString& aMapping,
ForgetUpdatesForOrigin(const nsACString& aMapping,
nsAutoPtr<DOMStorageDBThread::DBOperation>& aPendingTask,
void* aArg)
{
DOMStorageDBThread::DBOperation* newOp = static_cast<DOMStorageDBThread::DBOperation*>(aArg);
if (newOp->Type() == DOMStorageDBThread::DBOperation::opClear &&
aPendingTask->Scope() != newOp->Scope()) {
(aPendingTask->OriginNoSuffix() != newOp->OriginNoSuffix() ||
aPendingTask->OriginSuffix() != newOp->OriginSuffix())) {
return PL_DHASH_NEXT;
}
if (newOp->Type() == DOMStorageDBThread::DBOperation::opClearMatchingScope &&
!StringBeginsWith(aPendingTask->Scope(), newOp->Scope())) {
if (newOp->Type() == DOMStorageDBThread::DBOperation::opClearMatchingOrigin &&
!StringBeginsWith(aPendingTask->OriginNoSuffix(), newOp->Origin())) {
return PL_DHASH_NEXT;
}
if (newOp->Type() == DOMStorageDBThread::DBOperation::opClearMatchingOriginAttributes &&
!OriginPatternMatches(aPendingTask->OriginSuffix(), newOp->OriginPattern())) {
return PL_DHASH_NEXT;
}
@ -1108,7 +1205,7 @@ 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
// just bypass this operation. A key 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());
@ -1146,12 +1243,13 @@ DOMStorageDBThread::PendingOperations::Add(DOMStorageDBThread::DBOperation* aOpe
// Clear operations
case DBOperation::opClear:
case DBOperation::opClearMatchingScope:
case DBOperation::opClearMatchingOrigin:
case DBOperation::opClearMatchingOriginAttributes:
// 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.
mUpdates.Enumerate(ForgetUpdatesForScope, aOperation);
mUpdates.Enumerate(ForgetUpdatesForOrigin, aOperation);
mClears.Put(aOperation->Target(), aOperation);
break;
@ -1254,7 +1352,7 @@ DOMStorageDBThread::PendingOperations::Finalize(nsresult aRv)
namespace {
bool
FindPendingClearForScope(const nsACString& aScope,
FindPendingClearForOrigin(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
DOMStorageDBThread::DBOperation* aPendingOperation)
{
if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearAll) {
@ -1262,12 +1360,18 @@ FindPendingClearForScope(const nsACString& aScope,
}
if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClear &&
aScope == aPendingOperation->Scope()) {
aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
aOriginSuffix == aPendingOperation->OriginSuffix()) {
return true;
}
if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearMatchingScope &&
StringBeginsWith(aScope, aPendingOperation->Scope())) {
if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearMatchingOrigin &&
StringBeginsWith(aOriginNoSuffix, aPendingOperation->Origin())) {
return true;
}
if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearMatchingOriginAttributes &&
OriginPatternMatches(aOriginSuffix, aPendingOperation->OriginPattern())) {
return true;
}
@ -1277,18 +1381,19 @@ FindPendingClearForScope(const nsACString& aScope,
} // namespace
bool
DOMStorageDBThread::PendingOperations::IsScopeClearPending(const nsACString& aScope)
DOMStorageDBThread::PendingOperations::IsOriginClearPending(const nsACString& aOriginSuffix,
const nsACString& aOriginNoSuffix) const
{
// Called under the lock
for (auto iter = mClears.Iter(); !iter.Done(); iter.Next()) {
if (FindPendingClearForScope(aScope, iter.UserData())) {
for (auto iter = mClears.ConstIter(); !iter.Done(); iter.Next()) {
if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix, iter.UserData())) {
return true;
}
}
for (uint32_t i = 0; i < mExecList.Length(); ++i) {
if (FindPendingClearForScope(aScope, mExecList[i])) {
if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix, mExecList[i])) {
return true;
}
}
@ -1299,13 +1404,14 @@ DOMStorageDBThread::PendingOperations::IsScopeClearPending(const nsACString& aSc
namespace {
bool
FindPendingUpdateForScope(const nsACString& aScope,
DOMStorageDBThread::DBOperation* aPendingOperation)
FindPendingUpdateForOrigin(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
DOMStorageDBThread::DBOperation* aPendingOperation)
{
if ((aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opAddItem ||
aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opUpdateItem ||
aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opRemoveItem) &&
aScope == aPendingOperation->Scope()) {
aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
aOriginSuffix == aPendingOperation->OriginSuffix()) {
return true;
}
@ -1315,18 +1421,19 @@ FindPendingUpdateForScope(const nsACString& aScope,
} // namespace
bool
DOMStorageDBThread::PendingOperations::IsScopeUpdatePending(const nsACString& aScope)
DOMStorageDBThread::PendingOperations::IsOriginUpdatePending(const nsACString& aOriginSuffix,
const nsACString& aOriginNoSuffix) const
{
// Called under the lock
for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
if (FindPendingUpdateForScope(aScope, iter.UserData())) {
for (auto iter = mUpdates.ConstIter(); !iter.Done(); iter.Next()) {
if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, iter.UserData())) {
return true;
}
}
for (uint32_t i = 0; i < mExecList.Length(); ++i) {
if (FindPendingUpdateForScope(aScope, mExecList[i])) {
if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, mExecList[i])) {
return true;
}
}

View File

@ -12,6 +12,7 @@
#include "nsTArray.h"
#include "mozilla/Atomics.h"
#include "mozilla/Monitor.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/storage/StatementCache.h"
#include "nsString.h"
#include "nsCOMPtr.h"
@ -73,17 +74,20 @@ public:
// Called when chrome deletes e.g. cookies, schedules delete of the whole database
virtual void AsyncClearAll() = 0;
// Called when only a domain and its subdomains or an app data is about to clear
virtual void AsyncClearMatchingScope(const nsACString& aScope) = 0;
// Called when only a domain and its subdomains is about to clear
virtual void AsyncClearMatchingOrigin(const nsACString& aOriginNoSuffix) = 0;
// Called when data matching an origin pattern have to be cleared
virtual void AsyncClearMatchingOriginAttributes(const OriginAttributesPattern& aPattern) = 0;
// Forces scheduled DB operations to be early flushed to the disk
virtual void AsyncFlush() = 0;
// Check whether the scope has any data stored on disk and is thus allowed to preload
virtual bool ShouldPreloadScope(const nsACString& aScope) = 0;
virtual bool ShouldPreloadOrigin(const nsACString& aOriginNoSuffix) = 0;
// Get the complete list of scopes having data
virtual void GetScopesHavingData(InfallibleTArray<nsCString>* aScopes) = 0;
virtual void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins) = 0;
};
// The implementation of the the database engine, this directly works
@ -114,11 +118,17 @@ public:
opAddItem,
opUpdateItem,
opRemoveItem,
// Clears a specific single origin data
opClear,
// Operations invoked by chrome
// Clear all the data stored in the database, for all scopes, no exceptions
opClearAll,
opClearMatchingScope,
// Clear data under a domain and all its subdomains regardless OriginAttributes value
opClearMatchingOrigin,
// Clear all data matching an OriginAttributesPattern regardless a domain
opClearMatchingOriginAttributes,
} OperationType;
explicit DBOperation(const OperationType aType,
@ -128,7 +138,9 @@ public:
DBOperation(const OperationType aType,
DOMStorageUsageBridge* aUsage);
DBOperation(const OperationType aType,
const nsACString& aScope);
const nsACString& aOriginNoSuffix);
DBOperation(const OperationType aType,
const OriginAttributesPattern& aOriginNoSuffix);
~DBOperation();
// Executes the operation, doesn't necessarity have to be called on the I/O thread
@ -138,13 +150,23 @@ public:
void Finalize(nsresult aRv);
// The operation type
OperationType Type() { return mType; }
OperationType Type() const { return mType; }
// The operation scope (=origin)
const nsCString Scope();
// The origin in the database usage format (reversed)
const nsCString OriginNoSuffix() const;
// |Scope + key| the operation is working with
const nsCString Target();
// The origin attributes suffix
const nsCString OriginSuffix() const;
// |origin suffix + origin key| the operation is working with
// or a scope pattern to delete with simple SQL's "LIKE %" from the database.
const nsCString Origin() const;
// |origin suffix + origin key + key| the operation is working with
const nsCString Target() const;
// Pattern to delete matching data with this op
const OriginAttributesPattern& OriginPattern() const { return mOriginPattern; }
private:
// The operation implementation body
@ -154,9 +176,10 @@ public:
OperationType mType;
RefPtr<DOMStorageCacheBridge> mCache;
RefPtr<DOMStorageUsageBridge> mUsage;
nsString mKey;
nsString mValue;
nsCString mScope;
nsString const mKey;
nsString const mValue;
nsCString const mOrigin;
OriginAttributesPattern const mOriginPattern;
};
// Encapsulation of collective and coalescing logic for all pending operations
@ -166,11 +189,11 @@ public:
PendingOperations();
// Method responsible for coalescing redundant update operations with the same
// |Target()| or clear operations with the same or matching |Scope()|
// |Target()| or clear operations with the same or matching |Origin()|
void Add(DBOperation* aOperation);
// True when there are some scheduled operations to flush on disk
bool HasTasks();
bool HasTasks() const;
// Moves collected operations to a local flat list to allow execution of the operation
// list out of the thread lock
@ -184,12 +207,13 @@ public:
// to flush what indicates a long standing issue with the database access.
bool Finalize(nsresult aRv);
// true when a clear that deletes the given |scope| is among the pending operations;
// when a preload for that scope is being scheduled, it must be finished right away
bool IsScopeClearPending(const nsACString& aScope);
// true when a clear that deletes the given origin attr pattern and/or origin key
// is among the pending operations; when a preload for that scope is being scheduled,
// it must be finished right away
bool IsOriginClearPending(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) const;
// Checks whether there is a pending update (or clear, actually) operation for this scope.
bool IsScopeUpdatePending(const nsACString& aScope);
// Checks whether there is a pending update operation for this scope.
bool IsOriginUpdatePending(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) const;
private:
// Returns true iff new operation is of type newType and there is a pending
@ -269,13 +293,16 @@ public:
virtual void AsyncClearAll()
{ InsertDBOp(new DBOperation(DBOperation::opClearAll)); }
virtual void AsyncClearMatchingScope(const nsACString& aScope)
{ InsertDBOp(new DBOperation(DBOperation::opClearMatchingScope, aScope)); }
virtual void AsyncClearMatchingOrigin(const nsACString& aOriginNoSuffix)
{ InsertDBOp(new DBOperation(DBOperation::opClearMatchingOrigin, aOriginNoSuffix)); }
virtual void AsyncClearMatchingOriginAttributes(const OriginAttributesPattern& aPattern)
{ InsertDBOp(new DBOperation(DBOperation::opClearMatchingOriginAttributes, aPattern)); }
virtual void AsyncFlush();
virtual bool ShouldPreloadScope(const nsACString& aScope);
virtual void GetScopesHavingData(InfallibleTArray<nsCString>* aScopes);
virtual bool ShouldPreloadOrigin(const nsACString& aOrigin);
virtual void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins);
private:
nsCOMPtr<nsIFile> mDatabaseFile;
@ -298,8 +325,8 @@ private:
// State of the database initiation
nsresult mStatus;
// List of scopes having data, for optimization purposes only
nsTHashtable<nsCStringHashKey> mScopesHavingData;
// List of origins (including origin attributes suffix) having data, for optimization purposes only
nsTHashtable<nsCStringHashKey> mOriginsHavingData;
// Connection used by the worker thread for all read and write ops
nsCOMPtr<mozIStorageConnection> mWorkerConnection;
@ -328,7 +355,7 @@ private:
int32_t mPriorityCounter;
// Helper to direct an operation to one of the arrays above;
// also checks IsScopeClearPending for preloads
// also checks IsOriginClearPending for preloads
nsresult InsertDBOp(DBOperation* aOperation);
// Opens the database, first thing we do after start of the thread.

View File

@ -0,0 +1,372 @@
/* -*- 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 "DOMStorageManager.h"
#include "mozIStorageBindingParamsArray.h"
#include "mozIStorageBindingParams.h"
#include "mozIStorageValueArray.h"
#include "mozIStorageFunction.h"
#include "mozilla/BasePrincipal.h"
#include "nsVariant.h"
#include "mozilla/Services.h"
#include "mozilla/Tokenizer.h"
// Current version of the database schema
#define CURRENT_SCHEMA_VERSION 1
namespace mozilla {
namespace dom {
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;
}
// "scope" to "origin attributes suffix" and "origin key" convertor
class ExtractOriginData : protected mozilla::Tokenizer
{
public:
ExtractOriginData(const nsACString& scope, nsACString& suffix, nsACString& origin)
: mozilla::Tokenizer(scope)
{
using mozilla::OriginAttributes;
// Parse optional appId:isInBrowserElement: string, in case
// we don't find it, the scope is our new origin key and suffix
// is empty.
suffix.Truncate();
origin.Assign(scope);
// Bail out if it isn't appId.
uint32_t appId;
if (!ReadInteger(&appId)) {
return;
}
// Should be followed by a colon.
if (!CheckChar(':')) {
return;
}
// Bail out if it isn't 'browserFlag'.
nsDependentCSubstring browserFlag;
if (!ReadWord(browserFlag)) {
return;
}
bool inBrowser = browserFlag == "t";
bool notInBrowser = browserFlag == "f";
if (!inBrowser && !notInBrowser) {
return;
}
// Should be followed by a colon.
if (!CheckChar(':')) {
return;
}
// OK, we have found appId and inBrowser flag, create the suffix from it
// and take the rest as the origin key.
PrincipalOriginAttributes attrs(appId, inBrowser);
attrs.CreateSuffix(suffix);
// Consume the rest of the input as "origin".
origin.Assign(Substring(mCursor, mEnd));
}
};
class GetOriginParticular final : public mozIStorageFunction
{
public:
enum EParticular {
ORIGIN_ATTRIBUTES_SUFFIX,
ORIGIN_KEY
};
explicit GetOriginParticular(EParticular aParticular)
: mParticular(aParticular) {}
private:
GetOriginParticular() = delete;
~GetOriginParticular() {}
EParticular mParticular;
NS_DECL_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
};
NS_IMPL_ISUPPORTS(GetOriginParticular, mozIStorageFunction)
NS_IMETHODIMP
GetOriginParticular::OnFunctionCall(
mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
{
nsresult rv;
nsAutoCString scope;
rv = aFunctionArguments->GetUTF8String(0, scope);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString suffix, origin;
ExtractOriginData(scope, suffix, origin);
nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
switch (mParticular) {
case EParticular::ORIGIN_ATTRIBUTES_SUFFIX:
rv = outVar->SetAsAUTF8String(suffix);
break;
case EParticular::ORIGIN_KEY:
rv = outVar->SetAsAUTF8String(origin);
break;
}
NS_ENSURE_SUCCESS(rv, rv);
outVar.forget(aResult);
return NS_OK;
}
} // namespace
namespace DOMStorageDBUpdater {
nsresult CreateSchema1Tables(mozIStorageConnection *aWorkerConnection)
{
nsresult rv;
rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE IF NOT EXISTS webappsstore2 ("
"originAttributes TEXT, "
"originKey TEXT, "
"scope TEXT, " // Only for schema0 downgrade compatibility
"key TEXT, "
"value TEXT)"));
NS_ENSURE_SUCCESS(rv, rv);
rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE UNIQUE INDEX IF NOT EXISTS origin_key_index"
" ON webappsstore2(originAttributes, originKey, key)"));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult Update(mozIStorageConnection *aWorkerConnection)
{
nsresult rv;
mozStorageTransaction transaction(aWorkerConnection, false);
bool doVacuum = false;
int32_t schemaVer;
rv = aWorkerConnection->GetSchemaVersion(&schemaVer);
NS_ENSURE_SUCCESS(rv, rv);
// downgrade (v0) -> upgrade (v1+) specific code
if (schemaVer >= 1) {
bool schema0IndexExists;
rv = aWorkerConnection->IndexExists(NS_LITERAL_CSTRING("scope_key_index"),
&schema0IndexExists);
NS_ENSURE_SUCCESS(rv, rv);
if (schema0IndexExists) {
// If this index exists, the database (already updated to schema >1)
// has been run again on schema 0 code. That recreated that index
// and might store some new rows while updating only the 'scope' column.
// For such added rows we must fill the new 'origin*' columns correctly
// otherwise there would be a data loss. The safest way to do it is to
// simply run the whole update to schema 1 again.
schemaVer = 0;
}
}
switch (schemaVer) {
case 0: {
bool webappsstore2Exists, webappsstoreExists, moz_webappsstoreExists;
rv = aWorkerConnection->TableExists(NS_LITERAL_CSTRING("webappsstore2"),
&webappsstore2Exists);
NS_ENSURE_SUCCESS(rv, rv);
rv = aWorkerConnection->TableExists(NS_LITERAL_CSTRING("webappsstore"),
&webappsstoreExists);
NS_ENSURE_SUCCESS(rv, rv);
rv = aWorkerConnection->TableExists(NS_LITERAL_CSTRING("moz_webappsstore"),
&moz_webappsstoreExists);
NS_ENSURE_SUCCESS(rv, rv);
if (!webappsstore2Exists && !webappsstoreExists && !moz_webappsstoreExists) {
// The database is empty, this is the first start. Just create the schema table
// and break to the next version to update to, i.e. bypass update from the old version.
rv = CreateSchema1Tables(aWorkerConnection);
NS_ENSURE_SUCCESS(rv, rv);
rv = aWorkerConnection->SetSchemaVersion(CURRENT_SCHEMA_VERSION);
NS_ENSURE_SUCCESS(rv, rv);
break;
}
doVacuum = true;
// Ensure Gecko 1.9.1 storage table
rv = aWorkerConnection->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 = aWorkerConnection->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 = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("REVERSESTRING"), 1, function1);
NS_ENSURE_SUCCESS(rv, rv);
// 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.
if (webappsstoreExists) {
rv = aWorkerConnection->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 = aWorkerConnection->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.
if (moz_webappsstoreExists) {
rv = aWorkerConnection->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 = aWorkerConnection->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("DROP TABLE moz_webappsstore"));
NS_ENSURE_SUCCESS(rv, rv);
}
aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("REVERSESTRING"));
// Update the scoping to match the new implememntation: split to oa suffix and origin key
// First rename the old table, we want to remove some columns no longer needed.
rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE webappsstore2 RENAME TO webappsstore2_old"));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageFunction> oaSuffixFunc(
new GetOriginParticular(GetOriginParticular::ORIGIN_ATTRIBUTES_SUFFIX));
rv = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("GET_ORIGIN_SUFFIX"), 1, oaSuffixFunc);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageFunction> originKeyFunc(
new GetOriginParticular(GetOriginParticular::ORIGIN_KEY));
rv = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("GET_ORIGIN_KEY"), 1, originKeyFunc);
NS_ENSURE_SUCCESS(rv, rv);
// Here we ensure this schema tables when we are updating.
rv = CreateSchema1Tables(aWorkerConnection);
NS_ENSURE_SUCCESS(rv, rv);
rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT OR IGNORE INTO "
"webappsstore2 (originAttributes, originKey, scope, key, value) "
"SELECT GET_ORIGIN_SUFFIX(scope), GET_ORIGIN_KEY(scope), scope, key, value "
"FROM webappsstore2_old"));
NS_ENSURE_SUCCESS(rv, rv);
rv = aWorkerConnection->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("DROP TABLE webappsstore2_old"));
NS_ENSURE_SUCCESS(rv, rv);
aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("GET_ORIGIN_SUFFIX"));
aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("GET_ORIGIN_KEY"));
rv = aWorkerConnection->SetSchemaVersion(1);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_FALLTHROUGH;
}
case CURRENT_SCHEMA_VERSION:
// Nothing more to do here, this is the current schema version
break;
default:
MOZ_ASSERT(false);
break;
} // switch
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
if (doVacuum) {
// In some cases this can make the disk file of the database significantly smaller.
// VACUUM cannot be executed inside a transaction.
rv = aWorkerConnection->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("VACUUM"));
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
} // namespace DOMStorageDBUpdater
} // namespace dom
} // namespace mozilla

View File

@ -0,0 +1,22 @@
/* -*- 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/. */
#ifndef DOMStorageDBUpdater_h___
#define DOMStorageDBUpdater_h___
namespace mozilla {
namespace dom {
namespace DOMStorageDBUpdater {
nsresult Update(mozIStorageConnection *aWorkerConnection);
} // DOMStorageDBUpdater
} // dom
} // mozilla
#endif

View File

@ -68,13 +68,13 @@ DOMStorageDBChild::~DOMStorageDBChild()
}
nsTHashtable<nsCStringHashKey>&
DOMStorageDBChild::ScopesHavingData()
DOMStorageDBChild::OriginsHavingData()
{
if (!mScopesHavingData) {
mScopesHavingData = new nsTHashtable<nsCStringHashKey>;
if (!mOriginsHavingData) {
mOriginsHavingData = new nsTHashtable<nsCStringHashKey>;
}
return *mScopesHavingData;
return *mOriginsHavingData;
}
nsresult
@ -102,7 +102,7 @@ DOMStorageDBChild::AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority)
// Adding ref to cache for the time of preload. This ensures a reference to
// to the cache and that all keys will load into this cache object.
mLoadingCaches.PutEntry(aCache);
SendAsyncPreload(aCache->Scope(), aPriority);
SendAsyncPreload(aCache->OriginSuffix(), aCache->OriginNoSuffix(), aPriority);
} else {
// No IPC, no love. But the LoadDone call is expected.
aCache->LoadDone(NS_ERROR_UNEXPECTED);
@ -113,7 +113,7 @@ void
DOMStorageDBChild::AsyncGetUsage(DOMStorageUsageBridge* aUsage)
{
if (mIPCOpen) {
SendAsyncGetUsage(aUsage->Scope());
SendAsyncGetUsage(aUsage->OriginScope());
}
}
@ -136,7 +136,8 @@ DOMStorageDBChild::SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync)
// case the async preload has already loaded some keys.
InfallibleTArray<nsString> keys, values;
nsresult rv;
SendPreload(aCache->Scope(), aCache->LoadedCount(), &keys, &values, &rv);
SendPreload(aCache->OriginSuffix(), aCache->OriginNoSuffix(), aCache->LoadedCount(),
&keys, &values, &rv);
for (uint32_t i = 0; i < keys.Length(); ++i) {
aCache->LoadItem(keys[i], values[i]);
@ -154,8 +155,9 @@ DOMStorageDBChild::AsyncAddItem(DOMStorageCacheBridge* aCache,
return mStatus;
}
SendAsyncAddItem(aCache->Scope(), nsString(aKey), nsString(aValue));
ScopesHavingData().PutEntry(aCache->Scope());
SendAsyncAddItem(aCache->OriginSuffix(), aCache->OriginNoSuffix(),
nsString(aKey), nsString(aValue));
OriginsHavingData().PutEntry(aCache->Origin());
return NS_OK;
}
@ -168,8 +170,9 @@ DOMStorageDBChild::AsyncUpdateItem(DOMStorageCacheBridge* aCache,
return mStatus;
}
SendAsyncUpdateItem(aCache->Scope(), nsString(aKey), nsString(aValue));
ScopesHavingData().PutEntry(aCache->Scope());
SendAsyncUpdateItem(aCache->OriginSuffix(), aCache->OriginNoSuffix(),
nsString(aKey), nsString(aValue));
OriginsHavingData().PutEntry(aCache->Origin());
return NS_OK;
}
@ -181,7 +184,8 @@ DOMStorageDBChild::AsyncRemoveItem(DOMStorageCacheBridge* aCache,
return mStatus;
}
SendAsyncRemoveItem(aCache->Scope(), nsString(aKey));
SendAsyncRemoveItem(aCache->OriginSuffix(), aCache->OriginNoSuffix(),
nsString(aKey));
return NS_OK;
}
@ -192,44 +196,47 @@ DOMStorageDBChild::AsyncClear(DOMStorageCacheBridge* aCache)
return mStatus;
}
SendAsyncClear(aCache->Scope());
ScopesHavingData().RemoveEntry(aCache->Scope());
SendAsyncClear(aCache->OriginSuffix(), aCache->OriginNoSuffix());
OriginsHavingData().RemoveEntry(aCache->Origin());
return NS_OK;
}
bool
DOMStorageDBChild::ShouldPreloadScope(const nsACString& aScope)
DOMStorageDBChild::ShouldPreloadOrigin(const nsACString& aOrigin)
{
// Return true if we didn't receive the aScope list yet.
// Return true if we didn't receive the origins list yet.
// I tend to rather preserve a bit of early-after-start performance
// then a bit of memory here.
return !mScopesHavingData || mScopesHavingData->Contains(aScope);
// than a bit of memory here.
return !mOriginsHavingData || mOriginsHavingData->Contains(aOrigin);
}
bool
DOMStorageDBChild::RecvObserve(const nsCString& aTopic,
const nsCString& aScopePrefix)
const nsString& aOriginAttributesPattern,
const nsCString& aOriginScope)
{
DOMStorageObserver::Self()->Notify(aTopic.get(), aScopePrefix);
DOMStorageObserver::Self()->Notify(
aTopic.get(), aOriginAttributesPattern, aOriginScope);
return true;
}
bool
DOMStorageDBChild::RecvScopesHavingData(nsTArray<nsCString>&& aScopes)
DOMStorageDBChild::RecvOriginsHavingData(nsTArray<nsCString>&& aOrigins)
{
for (uint32_t i = 0; i < aScopes.Length(); ++i) {
ScopesHavingData().PutEntry(aScopes[i]);
for (uint32_t i = 0; i < aOrigins.Length(); ++i) {
OriginsHavingData().PutEntry(aOrigins[i]);
}
return true;
}
bool
DOMStorageDBChild::RecvLoadItem(const nsCString& aScope,
DOMStorageDBChild::RecvLoadItem(const nsCString& aOriginSuffix,
const nsCString& aOriginNoSuffix,
const nsString& aKey,
const nsString& aValue)
{
DOMStorageCache* aCache = mManager->GetCache(aScope);
DOMStorageCache* aCache = mManager->GetCache(aOriginSuffix, aOriginNoSuffix);
if (aCache) {
aCache->LoadItem(aKey, aValue);
}
@ -238,9 +245,11 @@ DOMStorageDBChild::RecvLoadItem(const nsCString& aScope,
}
bool
DOMStorageDBChild::RecvLoadDone(const nsCString& aScope, const nsresult& aRv)
DOMStorageDBChild::RecvLoadDone(const nsCString& aOriginSuffix,
const nsCString& aOriginNoSuffix,
const nsresult& aRv)
{
DOMStorageCache* aCache = mManager->GetCache(aScope);
DOMStorageCache* aCache = mManager->GetCache(aOriginSuffix, aOriginNoSuffix);
if (aCache) {
aCache->LoadDone(aRv);
@ -252,9 +261,9 @@ DOMStorageDBChild::RecvLoadDone(const nsCString& aScope, const nsresult& aRv)
}
bool
DOMStorageDBChild::RecvLoadUsage(const nsCString& aScope, const int64_t& aUsage)
DOMStorageDBChild::RecvLoadUsage(const nsCString& aOriginNoSuffix, const int64_t& aUsage)
{
RefPtr<DOMStorageUsageBridge> scopeUsage = mManager->GetScopeUsage(aScope);
RefPtr<DOMStorageUsageBridge> scopeUsage = mManager->GetOriginUsage(aOriginNoSuffix);
scopeUsage->LoadUsage(aUsage);
return true;
}
@ -308,8 +317,8 @@ private:
DOMStorageDBBridge* db = DOMStorageCache::GetDatabase();
if (db) {
InfallibleTArray<nsCString> scopes;
db->GetScopesHavingData(&scopes);
mozilla::Unused << mParent->SendScopesHavingData(scopes);
db->GetOriginsHavingData(&scopes);
mozilla::Unused << mParent->SendOriginsHavingData(scopes);
}
// We need to check if the device is in a low disk space situation, so
@ -317,14 +326,15 @@ private:
nsCOMPtr<nsIDiskSpaceWatcher> diskSpaceWatcher =
do_GetService("@mozilla.org/toolkit/disk-space-watcher;1");
if (!diskSpaceWatcher) {
NS_WARNING("Could not get disk information from DiskSpaceWatcher");
return NS_OK;
}
bool lowDiskSpace = false;
diskSpaceWatcher->GetIsDiskFull(&lowDiskSpace);
if (lowDiskSpace) {
mozilla::Unused << mParent->SendObserve(
nsDependentCString("low-disk-space"), EmptyCString());
nsDependentCString("low-disk-space"), EmptyString(), EmptyCString());
}
return NS_OK;
@ -374,9 +384,9 @@ DOMStorageDBParent::CloneProtocol(Channel* aChannel,
}
DOMStorageDBParent::CacheParentBridge*
DOMStorageDBParent::NewCache(const nsACString& aScope)
DOMStorageDBParent::NewCache(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix)
{
return new CacheParentBridge(this, aScope);
return new CacheParentBridge(this, aOriginSuffix, aOriginNoSuffix);
}
void
@ -386,19 +396,21 @@ DOMStorageDBParent::ActorDestroy(ActorDestroyReason aWhy)
}
bool
DOMStorageDBParent::RecvAsyncPreload(const nsCString& aScope, const bool& aPriority)
DOMStorageDBParent::RecvAsyncPreload(const nsCString& aOriginSuffix,
const nsCString& aOriginNoSuffix,
const bool& aPriority)
{
DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
if (!db) {
return false;
}
db->AsyncPreload(NewCache(aScope), aPriority);
db->AsyncPreload(NewCache(aOriginSuffix, aOriginNoSuffix), aPriority);
return true;
}
bool
DOMStorageDBParent::RecvAsyncGetUsage(const nsCString& aScope)
DOMStorageDBParent::RecvAsyncGetUsage(const nsCString& aOriginNoSuffix)
{
DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
if (!db) {
@ -406,7 +418,7 @@ DOMStorageDBParent::RecvAsyncGetUsage(const nsCString& aScope)
}
// The object releases it self in LoadUsage method
RefPtr<UsageParentBridge> usage = new UsageParentBridge(this, aScope);
RefPtr<UsageParentBridge> usage = new UsageParentBridge(this, aOriginNoSuffix);
db->AsyncGetUsage(usage);
return true;
}
@ -420,13 +432,15 @@ namespace {
class SyncLoadCacheHelper : public DOMStorageCacheBridge
{
public:
SyncLoadCacheHelper(const nsCString& aScope,
SyncLoadCacheHelper(const nsCString& aOriginSuffix,
const nsCString& aOriginNoSuffix,
uint32_t aAlreadyLoadedCount,
InfallibleTArray<nsString>* aKeys,
InfallibleTArray<nsString>* aValues,
nsresult* rv)
: mMonitor("DOM Storage SyncLoad IPC")
, mScope(aScope)
, mSuffix(aOriginSuffix)
, mOrigin(aOriginNoSuffix)
, mKeys(aKeys)
, mValues(aValues)
, mRv(rv)
@ -437,7 +451,12 @@ public:
*mRv = NS_ERROR_UNEXPECTED;
}
virtual const nsCString& Scope() const { return mScope; }
virtual const nsCString Origin() const
{
return DOMStorageManager::CreateOrigin(mSuffix, mOrigin);
}
virtual const nsCString& OriginNoSuffix() const { return mOrigin; }
virtual const nsCString& OriginSuffix() const { return mSuffix; }
virtual bool Loaded() { return mLoaded; }
virtual uint32_t LoadedCount() { return mLoadedCount; }
virtual bool LoadItem(const nsAString& aKey, const nsString& aValue)
@ -473,7 +492,7 @@ public:
private:
Monitor mMonitor;
nsCString mScope;
nsCString mSuffix, mOrigin;
InfallibleTArray<nsString>* mKeys;
InfallibleTArray<nsString>* mValues;
nsresult* mRv;
@ -484,7 +503,8 @@ private:
} // namespace
bool
DOMStorageDBParent::RecvPreload(const nsCString& aScope,
DOMStorageDBParent::RecvPreload(const nsCString& aOriginSuffix,
const nsCString& aOriginNoSuffix,
const uint32_t& aAlreadyLoadedCount,
InfallibleTArray<nsString>* aKeys,
InfallibleTArray<nsString>* aValues,
@ -496,14 +516,15 @@ DOMStorageDBParent::RecvPreload(const nsCString& aScope,
}
RefPtr<SyncLoadCacheHelper> cache(
new SyncLoadCacheHelper(aScope, aAlreadyLoadedCount, aKeys, aValues, aRv));
new SyncLoadCacheHelper(aOriginSuffix, aOriginNoSuffix, aAlreadyLoadedCount, aKeys, aValues, aRv));
db->SyncPreload(cache, true);
return true;
}
bool
DOMStorageDBParent::RecvAsyncAddItem(const nsCString& aScope,
DOMStorageDBParent::RecvAsyncAddItem(const nsCString& aOriginSuffix,
const nsCString& aOriginNoSuffix,
const nsString& aKey,
const nsString& aValue)
{
@ -512,7 +533,7 @@ DOMStorageDBParent::RecvAsyncAddItem(const nsCString& aScope,
return false;
}
nsresult rv = db->AsyncAddItem(NewCache(aScope), aKey, aValue);
nsresult rv = db->AsyncAddItem(NewCache(aOriginSuffix, aOriginNoSuffix), aKey, aValue);
if (NS_FAILED(rv) && mIPCOpen) {
mozilla::Unused << SendError(rv);
}
@ -521,7 +542,8 @@ DOMStorageDBParent::RecvAsyncAddItem(const nsCString& aScope,
}
bool
DOMStorageDBParent::RecvAsyncUpdateItem(const nsCString& aScope,
DOMStorageDBParent::RecvAsyncUpdateItem(const nsCString& aOriginSuffix,
const nsCString& aOriginNoSuffix,
const nsString& aKey,
const nsString& aValue)
{
@ -530,7 +552,7 @@ DOMStorageDBParent::RecvAsyncUpdateItem(const nsCString& aScope,
return false;
}
nsresult rv = db->AsyncUpdateItem(NewCache(aScope), aKey, aValue);
nsresult rv = db->AsyncUpdateItem(NewCache(aOriginSuffix, aOriginNoSuffix), aKey, aValue);
if (NS_FAILED(rv) && mIPCOpen) {
mozilla::Unused << SendError(rv);
}
@ -539,7 +561,8 @@ DOMStorageDBParent::RecvAsyncUpdateItem(const nsCString& aScope,
}
bool
DOMStorageDBParent::RecvAsyncRemoveItem(const nsCString& aScope,
DOMStorageDBParent::RecvAsyncRemoveItem(const nsCString& aOriginSuffix,
const nsCString& aOriginNoSuffix,
const nsString& aKey)
{
DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
@ -547,7 +570,7 @@ DOMStorageDBParent::RecvAsyncRemoveItem(const nsCString& aScope,
return false;
}
nsresult rv = db->AsyncRemoveItem(NewCache(aScope), aKey);
nsresult rv = db->AsyncRemoveItem(NewCache(aOriginSuffix, aOriginNoSuffix), aKey);
if (NS_FAILED(rv) && mIPCOpen) {
mozilla::Unused << SendError(rv);
}
@ -556,14 +579,15 @@ DOMStorageDBParent::RecvAsyncRemoveItem(const nsCString& aScope,
}
bool
DOMStorageDBParent::RecvAsyncClear(const nsCString& aScope)
DOMStorageDBParent::RecvAsyncClear(const nsCString& aOriginSuffix,
const nsCString& aOriginNoSuffix)
{
DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
if (!db) {
return false;
}
nsresult rv = db->AsyncClear(NewCache(aScope));
nsresult rv = db->AsyncClear(NewCache(aOriginSuffix, aOriginNoSuffix));
if (NS_FAILED(rv) && mIPCOpen) {
mozilla::Unused << SendError(rv);
}
@ -587,7 +611,8 @@ DOMStorageDBParent::RecvAsyncFlush()
nsresult
DOMStorageDBParent::Observe(const char* aTopic,
const nsACString& aScopePrefix)
const nsAString& aOriginAttributesPattern,
const nsACString& aOriginScope)
{
if (mIPCOpen) {
#ifdef MOZ_NUWA_PROCESS
@ -595,7 +620,8 @@ DOMStorageDBParent::Observe(const char* aTopic,
ContentParent::IsNuwaReady())) {
#endif
mozilla::Unused << SendObserve(nsDependentCString(aTopic),
nsCString(aScopePrefix));
nsString(aOriginAttributesPattern),
nsCString(aOriginScope));
#ifdef MOZ_NUWA_PROCESS
}
#endif
@ -617,30 +643,34 @@ public:
LoadRunnable(DOMStorageDBParent* aParent,
TaskType aType,
const nsACString& aScope,
const nsACString& aOriginSuffix,
const nsACString& aOriginNoSuffix,
const nsAString& aKey = EmptyString(),
const nsAString& aValue = EmptyString())
: mParent(aParent)
, mType(aType)
, mScope(aScope)
, mSuffix(aOriginSuffix)
, mOrigin(aOriginNoSuffix)
, mKey(aKey)
, mValue(aValue)
{ }
LoadRunnable(DOMStorageDBParent* aParent,
TaskType aType,
const nsACString& aScope,
const nsACString& aOriginSuffix,
const nsACString& aOriginNoSuffix,
nsresult aRv)
: mParent(aParent)
, mType(aType)
, mScope(aScope)
, mSuffix(aOriginSuffix)
, mOrigin(aOriginNoSuffix)
, mRv(aRv)
{ }
private:
RefPtr<DOMStorageDBParent> mParent;
TaskType mType;
nsCString mScope;
nsCString mSuffix, mOrigin;
nsString mKey;
nsString mValue;
nsresult mRv;
@ -654,10 +684,10 @@ private:
switch (mType)
{
case loadItem:
mozilla::Unused << mParent->SendLoadItem(mScope, mKey, mValue);
mozilla::Unused << mParent->SendLoadItem(mSuffix, mOrigin, mKey, mValue);
break;
case loadDone:
mozilla::Unused << mParent->SendLoadDone(mScope, mRv);
mozilla::Unused << mParent->SendLoadDone(mSuffix, mOrigin, mRv);
break;
}
@ -669,6 +699,12 @@ private:
// DOMStorageDBParent::CacheParentBridge
const nsCString
DOMStorageDBParent::CacheParentBridge::Origin() const
{
return DOMStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix);
}
bool
DOMStorageDBParent::CacheParentBridge::LoadItem(const nsAString& aKey, const nsString& aValue)
{
@ -679,7 +715,7 @@ DOMStorageDBParent::CacheParentBridge::LoadItem(const nsAString& aKey, const nsS
++mLoadedCount;
RefPtr<LoadRunnable> r =
new LoadRunnable(mParent, LoadRunnable::loadItem, mScope, aKey, aValue);
new LoadRunnable(mParent, LoadRunnable::loadItem, mOriginSuffix, mOriginNoSuffix, aKey, aValue);
NS_DispatchToMainThread(r);
return true;
}
@ -695,7 +731,7 @@ DOMStorageDBParent::CacheParentBridge::LoadDone(nsresult aRv)
mLoaded = true;
RefPtr<LoadRunnable> r =
new LoadRunnable(mParent, LoadRunnable::loadDone, mScope, aRv);
new LoadRunnable(mParent, LoadRunnable::loadDone, mOriginSuffix, mOriginNoSuffix, aRv);
NS_DispatchToMainThread(r);
}
@ -713,9 +749,9 @@ namespace {
class UsageRunnable : public nsRunnable
{
public:
UsageRunnable(DOMStorageDBParent* aParent, const nsACString& aScope, const int64_t& aUsage)
UsageRunnable(DOMStorageDBParent* aParent, const nsACString& aOriginScope, const int64_t& aUsage)
: mParent(aParent)
, mScope(aScope)
, mOriginScope(aOriginScope)
, mUsage(aUsage)
{}
@ -726,12 +762,12 @@ private:
return NS_OK;
}
mozilla::Unused << mParent->SendLoadUsage(mScope, mUsage);
mozilla::Unused << mParent->SendLoadUsage(mOriginScope, mUsage);
return NS_OK;
}
RefPtr<DOMStorageDBParent> mParent;
nsCString mScope;
nsCString mOriginScope;
int64_t mUsage;
};
@ -740,7 +776,7 @@ private:
void
DOMStorageDBParent::UsageParentBridge::LoadUsage(const int64_t aUsage)
{
RefPtr<UsageRunnable> r = new UsageRunnable(mParent, mScope, aUsage);
RefPtr<UsageRunnable> r = new UsageRunnable(mParent, mOriginScope, aUsage);
NS_DispatchToMainThread(r);
}

View File

@ -15,6 +15,9 @@
#include "mozilla/Mutex.h"
namespace mozilla {
class OriginAttributesPattern;
namespace dom {
class DOMLocalStorageManager;
@ -52,35 +55,41 @@ public:
virtual void AsyncClearAll()
{
if (mScopesHavingData) {
mScopesHavingData->Clear(); /* NO-OP on the child process otherwise */
if (mOriginsHavingData) {
mOriginsHavingData->Clear(); /* NO-OP on the child process otherwise */
}
}
virtual void AsyncClearMatchingScope(const nsACString& aScope)
virtual void AsyncClearMatchingOrigin(const nsACString& aOriginNoSuffix)
{ /* NO-OP on the child process */ }
virtual void AsyncClearMatchingOriginAttributes(const OriginAttributesPattern& aPattern)
{ /* NO-OP on the child process */ }
virtual void AsyncFlush()
{ SendAsyncFlush(); }
virtual bool ShouldPreloadScope(const nsACString& aScope);
virtual void GetScopesHavingData(InfallibleTArray<nsCString>* aScopes)
virtual bool ShouldPreloadOrigin(const nsACString& aOriginNoSuffix);
virtual void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins)
{ NS_NOTREACHED("Not implemented for child process"); }
private:
bool RecvObserve(const nsCString& aTopic,
const nsCString& aScopePrefix);
bool RecvLoadItem(const nsCString& aScope,
const nsString& aOriginAttributesPattern,
const nsCString& aOriginScope);
bool RecvLoadItem(const nsCString& aOriginSuffix,
const nsCString& aOriginNoSuffix,
const nsString& aKey,
const nsString& aValue);
bool RecvLoadDone(const nsCString& aScope,
bool RecvLoadDone(const nsCString& aOriginSuffix,
const nsCString& aOriginNoSuffix,
const nsresult& aRv);
bool RecvScopesHavingData(nsTArray<nsCString>&& aScopes);
bool RecvLoadUsage(const nsCString& aScope,
bool RecvOriginsHavingData(nsTArray<nsCString>&& aOrigins);
bool RecvLoadUsage(const nsCString& aOriginNoSuffix,
const int64_t& aUsage);
bool RecvError(const nsresult& aRv);
nsTHashtable<nsCStringHashKey>& ScopesHavingData();
nsTHashtable<nsCStringHashKey>& OriginsHavingData();
ThreadSafeAutoRefCnt mRefCnt;
NS_DECL_OWNINGTHREAD
@ -88,12 +97,12 @@ private:
// Held to get caches to forward answers to.
RefPtr<DOMLocalStorageManager> mManager;
// Scopes having data hash, for optimization purposes only
nsAutoPtr<nsTHashtable<nsCStringHashKey> > mScopesHavingData;
// Origins having data hash, for optimization purposes only
nsAutoPtr<nsTHashtable<nsCStringHashKey>> mOriginsHavingData;
// List of caches waiting for preload. This ensures the contract that
// AsyncPreload call references the cache for time of the preload.
nsTHashtable<nsRefPtrHashKey<DOMStorageCacheBridge> > mLoadingCaches;
nsTHashtable<nsRefPtrHashKey<DOMStorageCacheBridge>> mLoadingCaches;
// Status of the remote database
nsresult mStatus;
@ -132,13 +141,20 @@ public:
// them back to appropriate cache object on the child process.
class CacheParentBridge : public DOMStorageCacheBridge {
public:
CacheParentBridge(DOMStorageDBParent* aParentDB, const nsACString& aScope)
: mParent(aParentDB), mScope(aScope), mLoaded(false), mLoadedCount(0) {}
CacheParentBridge(DOMStorageDBParent* aParentDB,
const nsACString& aOriginSuffix,
const nsACString& aOriginNoSuffix)
: mParent(aParentDB)
, mOriginSuffix(aOriginSuffix), mOriginNoSuffix(aOriginNoSuffix)
, mLoaded(false), mLoadedCount(0) {}
virtual ~CacheParentBridge() {}
// DOMStorageCacheBridge
virtual const nsCString& Scope() const
{ return mScope; }
virtual const nsCString Origin() const;
virtual const nsCString& OriginNoSuffix() const
{ return mOriginNoSuffix; }
virtual const nsCString& OriginSuffix() const
{ return mOriginSuffix; }
virtual bool Loaded()
{ return mLoaded; }
virtual uint32_t LoadedCount()
@ -150,7 +166,7 @@ public:
private:
RefPtr<DOMStorageDBParent> mParent;
nsCString mScope;
nsCString mOriginSuffix, mOriginNoSuffix;
bool mLoaded;
uint32_t mLoadedCount;
};
@ -159,38 +175,38 @@ public:
class UsageParentBridge : public DOMStorageUsageBridge
{
public:
UsageParentBridge(DOMStorageDBParent* aParentDB, const nsACString& aScope)
: mParent(aParentDB), mScope(aScope) {}
UsageParentBridge(DOMStorageDBParent* aParentDB, const nsACString& aOriginScope)
: mParent(aParentDB), mOriginScope(aOriginScope) {}
virtual ~UsageParentBridge() {}
// DOMStorageUsageBridge
virtual const nsCString& Scope() { return mScope; }
virtual const nsCString& OriginScope() { return mOriginScope; }
virtual void LoadUsage(const int64_t usage);
private:
RefPtr<DOMStorageDBParent> mParent;
nsCString mScope;
nsCString mOriginScope;
};
private:
// IPC
virtual void ActorDestroy(ActorDestroyReason aWhy) override;
bool RecvAsyncPreload(const nsCString& aScope, const bool& aPriority) override;
bool RecvPreload(const nsCString& aScope, const uint32_t& aAlreadyLoadedCount,
bool RecvAsyncPreload(const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const bool& aPriority) override;
bool RecvPreload(const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const uint32_t& aAlreadyLoadedCount,
InfallibleTArray<nsString>* aKeys, InfallibleTArray<nsString>* aValues,
nsresult* aRv) override;
bool RecvAsyncGetUsage(const nsCString& aScope) override;
bool RecvAsyncAddItem(const nsCString& aScope, const nsString& aKey, const nsString& aValue) override;
bool RecvAsyncUpdateItem(const nsCString& aScope, const nsString& aKey, const nsString& aValue) override;
bool RecvAsyncRemoveItem(const nsCString& aScope, const nsString& aKey) override;
bool RecvAsyncClear(const nsCString& aScope) override;
bool RecvAsyncGetUsage(const nsCString& aOriginNoSuffix) override;
bool RecvAsyncAddItem(const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const nsString& aKey, const nsString& aValue) override;
bool RecvAsyncUpdateItem(const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const nsString& aKey, const nsString& aValue) override;
bool RecvAsyncRemoveItem(const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const nsString& aKey) override;
bool RecvAsyncClear(const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix) override;
bool RecvAsyncFlush() override;
// DOMStorageObserverSink
virtual nsresult Observe(const char* aTopic, const nsACString& aScopePrefix) override;
virtual nsresult Observe(const char* aTopic, const nsAString& aOriginAttrPattern, const nsACString& aOriginScope) override;
private:
CacheParentBridge* NewCache(const nsACString& aScope);
CacheParentBridge* NewCache(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix);
ThreadSafeAutoRefCnt mRefCnt;
NS_DECL_OWNINGTHREAD

View File

@ -124,64 +124,54 @@ DOMStorageManager::~DOMStorageManager()
namespace {
nsresult
CreateScopeKey(nsIPrincipal* aPrincipal,
nsACString& aKey)
AppendOriginNoSuffix(nsIPrincipal* aPrincipal,
nsACString& aKey)
{
nsresult rv;
nsCOMPtr<nsIURI> uri;
nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
rv = aPrincipal->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
if (!uri) {
return NS_ERROR_UNEXPECTED;
}
nsAutoCString domainScope;
rv = uri->GetAsciiHost(domainScope);
nsAutoCString domainOrigin;
rv = uri->GetAsciiHost(domainOrigin);
NS_ENSURE_SUCCESS(rv, rv);
if (domainScope.IsEmpty()) {
if (domainOrigin.IsEmpty()) {
// For the file:/// protocol use the exact directory as domain.
bool isScheme = false;
if (NS_SUCCEEDED(uri->SchemeIs("file", &isScheme)) && isScheme) {
nsCOMPtr<nsIURL> url = do_QueryInterface(uri, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = url->GetDirectory(domainScope);
rv = url->GetDirectory(domainOrigin);
NS_ENSURE_SUCCESS(rv, rv);
}
}
nsAutoCString key;
rv = CreateReversedDomain(domainScope, key);
// Append reversed domain
nsAutoCString reverseDomain;
rv = CreateReversedDomain(domainOrigin, reverseDomain);
if (NS_FAILED(rv)) {
return rv;
}
aKey.Append(reverseDomain);
// Append scheme
nsAutoCString scheme;
rv = uri->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
key.Append(':');
key.Append(scheme);
aKey.Append(':');
aKey.Append(scheme);
// Append port if any
int32_t port = NS_GetRealPort(uri);
if (port != -1) {
key.Append(nsPrintfCString(":%d", port));
}
if (!aPrincipal->GetUnknownAppId()) {
uint32_t appId = aPrincipal->GetAppId();
bool isInBrowserElement = aPrincipal->GetIsInBrowserElement();
if (appId == nsIScriptSecurityManager::NO_APP_ID && !isInBrowserElement) {
aKey.Assign(key);
return NS_OK;
}
aKey.Truncate();
aKey.AppendInt(appId);
aKey.Append(':');
aKey.Append(isInBrowserElement ? 't' : 'f');
aKey.Append(':');
aKey.Append(key);
aKey.Append(nsPrintfCString(":%d", port));
}
return NS_OK;
@ -193,7 +183,6 @@ CreateQuotaDBKey(nsIPrincipal* aPrincipal,
{
nsresult rv;
nsAutoCString subdomainsDBKey;
nsCOMPtr<nsIEffectiveTLDService> eTLDService(do_GetService(
NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
@ -211,33 +200,40 @@ CreateQuotaDBKey(nsIPrincipal* aPrincipal,
}
NS_ENSURE_SUCCESS(rv, rv);
aKey.Truncate();
BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(aKey);
nsAutoCString subdomainsDBKey;
CreateReversedDomain(eTLDplusOne, subdomainsDBKey);
if (!aPrincipal->GetUnknownAppId()) {
uint32_t appId = aPrincipal->GetAppId();
bool isInBrowserElement = aPrincipal->GetIsInBrowserElement();
if (appId == nsIScriptSecurityManager::NO_APP_ID && !isInBrowserElement) {
aKey.Assign(subdomainsDBKey);
return NS_OK;
}
aKey.Truncate();
aKey.AppendInt(appId);
aKey.Append(':');
aKey.Append(isInBrowserElement ? 't' : 'f');
aKey.Append(':');
aKey.Append(subdomainsDBKey);
}
aKey.Append(':');
aKey.Append(subdomainsDBKey);
return NS_OK;
}
} // namespace
DOMStorageCache*
DOMStorageManager::GetCache(const nsACString& aScope) const
// static
nsCString
DOMStorageManager::CreateOrigin(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix)
{
DOMStorageCacheHashKey* entry = mCaches.GetEntry(aScope);
// Note: some hard-coded sqlite statements are dependent on the format this
// method returns. Changing this without updating those sqlite statements
// will cause malfunction.
nsAutoCString scope;
scope.Append(aOriginSuffix);
scope.Append(':');
scope.Append(aOriginNoSuffix);
return scope;
}
DOMStorageCache*
DOMStorageManager::GetCache(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix)
{
CacheOriginHashtable* table = mCaches.LookupOrAdd(aOriginSuffix);
DOMStorageCacheHashKey* entry = table->GetEntry(aOriginNoSuffix);
if (!entry) {
return nullptr;
}
@ -246,14 +242,14 @@ DOMStorageManager::GetCache(const nsACString& aScope) const
}
already_AddRefed<DOMStorageUsage>
DOMStorageManager::GetScopeUsage(const nsACString& aScope)
DOMStorageManager::GetOriginUsage(const nsACString& aOriginNoSuffix)
{
RefPtr<DOMStorageUsage> usage;
if (mUsages.Get(aScope, &usage)) {
if (mUsages.Get(aOriginNoSuffix, &usage)) {
return usage.forget();
}
usage = new DOMStorageUsage(aScope);
usage = new DOMStorageUsage(aOriginNoSuffix);
if (mType == LocalStorage) {
DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
@ -262,31 +258,33 @@ DOMStorageManager::GetScopeUsage(const nsACString& aScope)
}
}
mUsages.Put(aScope, usage);
mUsages.Put(aOriginNoSuffix, usage);
return usage.forget();
}
already_AddRefed<DOMStorageCache>
DOMStorageManager::PutCache(const nsACString& aScope,
DOMStorageManager::PutCache(const nsACString& aOriginSuffix,
const nsACString& aOriginNoSuffix,
nsIPrincipal* aPrincipal)
{
DOMStorageCacheHashKey* entry = mCaches.PutEntry(aScope);
CacheOriginHashtable* table = mCaches.LookupOrAdd(aOriginSuffix);
DOMStorageCacheHashKey* entry = table->PutEntry(aOriginNoSuffix);
RefPtr<DOMStorageCache> cache = entry->cache();
nsAutoCString quotaScope;
CreateQuotaDBKey(aPrincipal, quotaScope);
nsAutoCString quotaOrigin;
CreateQuotaDBKey(aPrincipal, quotaOrigin);
switch (mType) {
case SessionStorage:
// Lifetime handled by the manager, don't persist
entry->HardRef();
cache->Init(this, false, aPrincipal, quotaScope);
cache->Init(this, false, aPrincipal, quotaOrigin);
break;
case LocalStorage:
// Lifetime handled by the cache, do persist
cache->Init(this, true, aPrincipal, quotaScope);
cache->Init(this, true, aPrincipal, quotaOrigin);
break;
default:
@ -303,7 +301,8 @@ DOMStorageManager::DropCache(DOMStorageCache* aCache)
NS_WARNING("DOMStorageManager::DropCache called on a non-main thread, shutting down?");
}
mCaches.RemoveEntry(aCache->Scope());
CacheOriginHashtable* table = mCaches.LookupOrAdd(aCache->OriginSuffix());
table->RemoveEntry(aCache->OriginNoSuffix());
}
nsresult
@ -316,13 +315,16 @@ DOMStorageManager::GetStorageInternal(bool aCreate,
{
nsresult rv;
nsAutoCString scope;
rv = CreateScopeKey(aPrincipal, scope);
nsAutoCString originAttrSuffix;
BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(originAttrSuffix);
nsAutoCString originKey;
rv = AppendOriginNoSuffix(aPrincipal, originKey);
if (NS_FAILED(rv)) {
return NS_ERROR_NOT_AVAILABLE;
}
RefPtr<DOMStorageCache> cache = GetCache(scope);
RefPtr<DOMStorageCache> cache = GetCache(originAttrSuffix, originKey);
// Get or create a cache for the given scope
if (!cache) {
@ -332,15 +334,15 @@ DOMStorageManager::GetStorageInternal(bool aCreate,
}
if (!aRetval) {
// This is demand to just preload the cache, if the scope has
// This is a demand to just preload the cache, if the scope has
// no data stored, bypass creation and preload of the cache.
DOMStorageDBBridge* db = DOMStorageCache::GetDatabase();
if (db) {
if (!db->ShouldPreloadScope(scope)) {
if (!db->ShouldPreloadOrigin(DOMStorageManager::CreateOrigin(originAttrSuffix, originKey))) {
return NS_OK;
}
} else {
if (scope.EqualsLiteral("knalb.:about")) {
if (originKey.EqualsLiteral("knalb.:about")) {
return NS_OK;
}
}
@ -348,7 +350,7 @@ DOMStorageManager::GetStorageInternal(bool aCreate,
// There is always a single instance of a cache per scope
// in a single instance of a DOM storage manager.
cache = PutCache(scope, aPrincipal);
cache = PutCache(originAttrSuffix, originKey, aPrincipal);
} else if (mType == SessionStorage) {
if (!cache->CheckPrincipal(aPrincipal)) {
return NS_ERROR_DOM_SECURITY_ERR;
@ -407,7 +409,8 @@ DOMStorageManager::CloneStorage(nsIDOMStorage* aStorage)
const DOMStorageCache* origCache = storage->GetCache();
DOMStorageCache* existingCache = GetCache(origCache->Scope());
DOMStorageCache* existingCache = GetCache(origCache->OriginSuffix(),
origCache->OriginNoSuffix());
if (existingCache) {
// Do not replace an existing sessionStorage.
return NS_ERROR_NOT_AVAILABLE;
@ -415,8 +418,9 @@ DOMStorageManager::CloneStorage(nsIDOMStorage* aStorage)
// Since this manager is sessionStorage manager, PutCache hard references
// the cache in our hashtable.
RefPtr<DOMStorageCache> newCache = PutCache(origCache->Scope(),
origCache->Principal());
RefPtr<DOMStorageCache> newCache = PutCache(origCache->OriginSuffix(),
origCache->OriginNoSuffix(),
origCache->Principal());
newCache->CloneFrom(origCache);
return NS_OK;
@ -427,6 +431,8 @@ DOMStorageManager::CheckStorage(nsIPrincipal* aPrincipal,
nsIDOMStorage* aStorage,
bool* aRetval)
{
nsresult rv;
RefPtr<DOMStorage> storage = static_cast<DOMStorage*>(aStorage);
if (!storage) {
return NS_ERROR_UNEXPECTED;
@ -438,13 +444,16 @@ DOMStorageManager::CheckStorage(nsIPrincipal* aPrincipal,
return NS_ERROR_NOT_AVAILABLE;
}
nsAutoCString scope;
nsresult rv = CreateScopeKey(aPrincipal, scope);
if (NS_FAILED(rv)) {
nsAutoCString suffix;
BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(suffix);
nsAutoCString origin;
rv = AppendOriginNoSuffix(aPrincipal, origin);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
DOMStorageCache* cache = GetCache(scope);
DOMStorageCache* cache = GetCache(suffix, origin);
if (cache != storage->GetCache()) {
return NS_OK;
}
@ -474,62 +483,79 @@ DOMStorageManager::GetLocalStorageForPrincipal(nsIPrincipal* aPrincipal,
void
DOMStorageManager::ClearCaches(uint32_t aUnloadFlags,
const nsACString& aKeyPrefix)
const OriginAttributesPattern& aPattern,
const nsACString& aOriginScope)
{
for (auto iter = mCaches.Iter(); !iter.Done(); iter.Next()) {
DOMStorageCache* cache = iter.Get()->cache();
nsCString& key = const_cast<nsCString&>(cache->Scope());
for (auto iter1 = mCaches.Iter(); !iter1.Done(); iter1.Next()) {
PrincipalOriginAttributes oa;
DebugOnly<bool> rv = oa.PopulateFromSuffix(iter1.Key());
MOZ_ASSERT(rv);
if (!aPattern.Matches(oa)) {
// This table doesn't match the given origin attributes pattern
continue;
}
if (aKeyPrefix.IsEmpty() || StringBeginsWith(key, aKeyPrefix)) {
cache->UnloadItems(aUnloadFlags);
CacheOriginHashtable* table = iter1.Data();
for (auto iter2 = table->Iter(); !iter2.Done(); iter2.Next()) {
DOMStorageCache* cache = iter2.Get()->cache();
if (aOriginScope.IsEmpty() ||
StringBeginsWith(cache->OriginNoSuffix(), aOriginScope)) {
cache->UnloadItems(aUnloadFlags);
}
}
}
}
nsresult
DOMStorageManager::Observe(const char* aTopic, const nsACString& aScopePrefix)
DOMStorageManager::Observe(const char* aTopic,
const nsAString& aOriginAttributesPattern,
const nsACString& aOriginScope)
{
OriginAttributesPattern pattern;
pattern.Init(aOriginAttributesPattern);
// Clear everything, caches + database
if (!strcmp(aTopic, "cookie-cleared")) {
ClearCaches(DOMStorageCache::kUnloadComplete, EmptyCString());
ClearCaches(DOMStorageCache::kUnloadComplete, pattern, EmptyCString());
return NS_OK;
}
// Clear from caches everything that has been stored
// while in session-only mode
if (!strcmp(aTopic, "session-only-cleared")) {
ClearCaches(DOMStorageCache::kUnloadSession, aScopePrefix);
ClearCaches(DOMStorageCache::kUnloadSession, pattern, aOriginScope);
return NS_OK;
}
// Clear everything (including so and pb data) from caches and database
// for the gived domain and subdomains.
if (!strcmp(aTopic, "domain-data-cleared")) {
ClearCaches(DOMStorageCache::kUnloadComplete, aScopePrefix);
ClearCaches(DOMStorageCache::kUnloadComplete, pattern, aOriginScope);
return NS_OK;
}
// Clear all private-browsing caches
if (!strcmp(aTopic, "private-browsing-data-cleared")) {
ClearCaches(DOMStorageCache::kUnloadPrivate, EmptyCString());
ClearCaches(DOMStorageCache::kUnloadPrivate, pattern, EmptyCString());
return NS_OK;
}
// Clear localStorage data beloging to an app.
if (!strcmp(aTopic, "app-data-cleared")) {
// Clear localStorage data beloging to an origin pattern
if (!strcmp(aTopic, "origin-attr-pattern-cleared")) {
// sessionStorage is expected to stay
if (mType == SessionStorage) {
return NS_OK;
}
ClearCaches(DOMStorageCache::kUnloadComplete, aScopePrefix);
ClearCaches(DOMStorageCache::kUnloadComplete, pattern, EmptyCString());
return NS_OK;
}
if (!strcmp(aTopic, "profile-change")) {
// For case caches are still referenced - clear them completely
ClearCaches(DOMStorageCache::kUnloadComplete, EmptyCString());
ClearCaches(DOMStorageCache::kUnloadComplete, pattern, EmptyCString());
mCaches.Clear();
return NS_OK;
}
@ -557,7 +583,7 @@ DOMStorageManager::Observe(const char* aTopic, const nsACString& aScopePrefix)
}
// This immediately completely reloads all caches from the database.
ClearCaches(DOMStorageCache::kTestReload, EmptyCString());
ClearCaches(DOMStorageCache::kTestReload, pattern, EmptyCString());
return NS_OK;
}

View File

@ -15,11 +15,15 @@
#include "nsTHashtable.h"
#include "nsDataHashtable.h"
#include "nsClassHashtable.h"
#include "nsHashKeys.h"
class nsIDOMWindow;
namespace mozilla {
class OriginAttributesPattern;
namespace dom {
const DOMStorage::StorageType SessionStorage = DOMStorage::SessionStorage;
@ -37,9 +41,11 @@ public:
// Reads the preference for DOM storage quota
static uint32_t GetQuota();
// Gets (but not ensures) cache for the given scope
DOMStorageCache* GetCache(const nsACString& aScope) const;
DOMStorageCache* GetCache(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix);
// Returns object keeping usage cache for the scope.
already_AddRefed<DOMStorageUsage> GetScopeUsage(const nsACString& aScope);
already_AddRefed<DOMStorageUsage> GetOriginUsage(const nsACString& aOriginNoSuffix);
static nsCString CreateOrigin(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix);
protected:
explicit DOMStorageManager(DOMStorage::StorageType aType);
@ -47,7 +53,9 @@ protected:
private:
// DOMStorageObserverSink, handler to various chrome clearing notification
virtual nsresult Observe(const char* aTopic, const nsACString& aScopePrefix) override;
virtual nsresult Observe(const char* aTopic,
const nsAString& aOriginAttributesPattern,
const nsACString& aOriginScope) override;
// Since nsTHashtable doesn't like multiple inheritance, we have to aggregate
// DOMStorageCache into the entry.
@ -78,7 +86,8 @@ private:
// Ensures cache for a scope, when it doesn't exist it is created and initalized,
// this also starts preload of persistent data.
already_AddRefed<DOMStorageCache> PutCache(const nsACString& aScope,
already_AddRefed<DOMStorageCache> PutCache(const nsACString& aOriginSuffix,
const nsACString& aOriginNoSuffix,
nsIPrincipal* aPrincipal);
// Helper for creation of DOM storage objects
@ -89,8 +98,10 @@ private:
bool aPrivate,
nsIDOMStorage** aRetval);
// Scope->cache map
nsTHashtable<DOMStorageCacheHashKey> mCaches;
// Suffix->origin->cache map
typedef nsTHashtable<DOMStorageCacheHashKey> CacheOriginHashtable;
nsClassHashtable<nsCStringHashKey, CacheOriginHashtable> mCaches;
const DOMStorage::StorageType mType;
// If mLowDiskSpace is true it indicates a low device storage situation and
@ -99,7 +110,9 @@ private:
bool mLowDiskSpace;
bool IsLowDiskSpace() const { return mLowDiskSpace; };
void ClearCaches(uint32_t aUnloadFlags, const nsACString& aKeyPrefix);
void ClearCaches(uint32_t aUnloadFlags,
const OriginAttributesPattern& aPattern,
const nsACString& aKeyPrefix);
protected:
// Keeps usage cache objects for eTLD+1 scopes we have touched.

View File

@ -9,13 +9,13 @@
#include "DOMStorageDBThread.h"
#include "DOMStorageCache.h"
#include "mozilla/BasePrincipal.h"
#include "nsIObserverService.h"
#include "nsIURI.h"
#include "nsIURL.h"
#include "nsIScriptSecurityManager.h"
#include "nsIPermission.h"
#include "nsIIDNService.h"
#include "mozIApplicationClearPrivateDataParams.h"
#include "nsICookiePermission.h"
#include "nsPrintfCString.h"
@ -62,7 +62,7 @@ DOMStorageObserver::Init()
obs->AddObserver(sSelf, "perm-changed", true);
obs->AddObserver(sSelf, "browser:purge-domain-data", true);
obs->AddObserver(sSelf, "last-pb-context-exited", true);
obs->AddObserver(sSelf, "webapps-clear-data", true);
obs->AddObserver(sSelf, "clear-origin-data", true);
// Shutdown
obs->AddObserver(sSelf, "profile-after-change", true);
@ -111,11 +111,13 @@ DOMStorageObserver::RemoveSink(DOMStorageObserverSink* aObs)
}
void
DOMStorageObserver::Notify(const char* aTopic, const nsACString& aData)
DOMStorageObserver::Notify(const char* aTopic,
const nsAString& aOriginAttributesPattern,
const nsACString& aOriginScope)
{
for (uint32_t i = 0; i < mSinks.Length(); ++i) {
DOMStorageObserverSink* sink = mSinks[i];
sink->Observe(aTopic, aData);
sink->Observe(aTopic, aOriginAttributesPattern, aOriginScope);
}
}
@ -202,6 +204,9 @@ DOMStorageObserver::Observe(nsISupports* aSubject,
return NS_OK;
}
nsAutoCString originSuffix;
BasePrincipal::Cast(principal)->OriginAttributesRef().CreateSuffix(originSuffix);
nsCOMPtr<nsIURI> origin;
principal->GetURI(getter_AddRefs(origin));
if (!origin) {
@ -214,11 +219,11 @@ DOMStorageObserver::Observe(nsISupports* aSubject,
return NS_OK;
}
nsAutoCString scope;
rv = CreateReversedDomain(host, scope);
nsAutoCString originScope;
rv = CreateReversedDomain(host, originScope);
NS_ENSURE_SUCCESS(rv, rv);
Notify("session-only-cleared", scope);
Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix), originScope);
return NS_OK;
}
@ -239,16 +244,16 @@ DOMStorageObserver::Observe(nsISupports* aSubject,
aceDomain);
}
nsAutoCString scopePrefix;
rv = CreateReversedDomain(aceDomain, scopePrefix);
nsAutoCString originScope;
rv = CreateReversedDomain(aceDomain, originScope);
NS_ENSURE_SUCCESS(rv, rv);
DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
db->AsyncClearMatchingScope(scopePrefix);
db->AsyncClearMatchingOrigin(originScope);
Notify("domain-data-cleared", scopePrefix);
Notify("domain-data-cleared", EmptyString(), originScope);
return NS_OK;
}
@ -260,42 +265,20 @@ DOMStorageObserver::Observe(nsISupports* aSubject,
return NS_OK;
}
// Clear data beloging to an app.
if (!strcmp(aTopic, "webapps-clear-data")) {
nsCOMPtr<mozIApplicationClearPrivateDataParams> params =
do_QueryInterface(aSubject);
if (!params) {
NS_ERROR("'webapps-clear-data' notification's subject should be a mozIApplicationClearPrivateDataParams");
return NS_ERROR_UNEXPECTED;
// Clear data of the origins whose prefixes will match the suffix.
if (!strcmp(aTopic, "clear-origin-data")) {
OriginAttributesPattern pattern;
if (!pattern.Init(nsDependentString(aData))) {
NS_ERROR("Cannot parse origin attributes pattern");
return NS_ERROR_FAILURE;
}
uint32_t appId;
bool browserOnly;
rv = params->GetAppId(&appId);
NS_ENSURE_SUCCESS(rv, rv);
rv = params->GetBrowserOnly(&browserOnly);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
nsAutoCString scope;
scope.AppendInt(appId);
scope.AppendLiteral(":t:");
db->AsyncClearMatchingScope(scope);
Notify("app-data-cleared", scope);
db->AsyncClearMatchingOriginAttributes(pattern);
if (!browserOnly) {
scope.Truncate();
scope.AppendInt(appId);
scope.AppendLiteral(":f:");
db->AsyncClearMatchingScope(scope);
Notify("app-data-cleared", scope);
}
Notify("origin-attr-pattern-cleared", nsDependentString(aData));
return NS_OK;
}

View File

@ -27,7 +27,9 @@ public:
private:
friend class DOMStorageObserver;
virtual nsresult Observe(const char* aTopic, const nsACString& aScopePrefix) = 0;
virtual nsresult Observe(const char* aTopic,
const nsAString& aOriginAttributesPattern,
const nsACString& aOriginScope) = 0;
};
// Statically (though layout statics) initialized observer receiving and processing
@ -45,7 +47,9 @@ public:
void AddSink(DOMStorageObserverSink* aObs);
void RemoveSink(DOMStorageObserverSink* aObs);
void Notify(const char* aTopic, const nsACString& aData = EmptyCString());
void Notify(const char* aTopic,
const nsAString& aOriginAttributesPattern = EmptyString(),
const nsACString& aOriginScope = EmptyCString());
private:
virtual ~DOMStorageObserver() {}

View File

@ -19,22 +19,24 @@ prio(normal upto urgent) sync protocol PStorage
parent:
async __delete__();
prio(urgent) sync Preload(nsCString scope, uint32_t alreadyLoadedCount)
prio(urgent) sync Preload(nsCString originSuffix, nsCString originNoSuffix, uint32_t alreadyLoadedCount)
returns (nsString[] keys, nsString[] values, nsresult rv);
async AsyncPreload(nsCString scope, bool priority);
async AsyncPreload(nsCString originSuffix, nsCString originNoSuffix, bool priority);
async AsyncGetUsage(nsCString scope);
async AsyncAddItem(nsCString scope, nsString key, nsString value);
async AsyncUpdateItem(nsCString scope, nsString key, nsString value);
async AsyncRemoveItem(nsCString scope, nsString key);
async AsyncClear(nsCString scope);
async AsyncAddItem(nsCString originSuffix, nsCString originNoSuffix, nsString key, nsString value);
async AsyncUpdateItem(nsCString originSuffix, nsCString originNoSuffix, nsString key, nsString value);
async AsyncRemoveItem(nsCString originSuffix, nsCString originNoSuffix, nsString key);
async AsyncClear(nsCString originSuffix, nsCString originNoSuffix);
async AsyncFlush();
child:
async Observe(nsCString topic, nsCString scopePrefix);
async ScopesHavingData(nsCString[] scopes);
async LoadItem(nsCString scope, nsString key, nsString value);
async LoadDone(nsCString scope, nsresult rv);
async Observe(nsCString topic,
nsString originAttributesPattern,
nsCString originScope);
async OriginsHavingData(nsCString[] origins);
async LoadItem(nsCString originSuffix, nsCString originNoSuffix, nsString key, nsString value);
async LoadDone(nsCString originSuffix, nsCString originNoSuffix, nsresult rv);
async LoadUsage(nsCString scope, int64_t usage);
async Error(nsresult rv);
};

View File

@ -13,6 +13,7 @@ UNIFIED_SOURCES += [
'DOMStorage.cpp',
'DOMStorageCache.cpp',
'DOMStorageDBThread.cpp',
'DOMStorageDBUpdater.cpp',
'DOMStorageIPC.cpp',
'DOMStorageManager.cpp',
'DOMStorageObserver.cpp',