Backed out 4 changesets (bug 1285898) for browser_localStorage_e10s.js failure

Backed out changeset 9c6057cde326 (bug 1285898)
Backed out changeset bd68ebab64fa (bug 1285898)
Backed out changeset 6fdb24e1256d (bug 1285898)
Backed out changeset 6681b50c1f6d (bug 1285898)
This commit is contained in:
Phil Ringnalda 2017-01-30 21:49:42 -08:00
Родитель 6a8dda71e3
Коммит 85843f4cff
19 изменённых файлов: 149 добавлений и 715 удалений

Просмотреть файл

@ -2981,11 +2981,7 @@ nsGlobalWindow::PreloadLocalStorage()
// private browsing windows do not persist local storage to disk so we should
// only try to precache storage when we're not a private browsing window.
if (principal->GetPrivateBrowsingId() == 0) {
nsCOMPtr<nsIDOMStorage> storage;
rv = storageManager->PrecacheStorage(principal, getter_AddRefs(storage));
if (NS_SUCCEEDED(rv)) {
mLocalStorage = static_cast<Storage*>(storage.get());
}
storageManager->PrecacheStorage(principal);
}
}
@ -11567,43 +11563,48 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
}
bool isPrivateBrowsing = IsPrivateBrowsing();
// In addition to determining if this is a storage event, the
// isPrivateBrowsing checks here enforce that the source storage area's
// private browsing state matches this window's state. These flag checks
// and their maintenance independent from the principal's OriginAttributes
// matter because chrome docshells that are part of private browsing windows
// can be private browsing without having their OriginAttributes set (because
// they have the system principal).
if ((!nsCRT::strcmp(aTopic, "dom-storage2-changed") && !isPrivateBrowsing) ||
(!nsCRT::strcmp(aTopic, "dom-private-storage2-changed") && isPrivateBrowsing)) {
if (!IsInnerWindow() || !AsInner()->IsCurrentInnerWindow()) {
return NS_OK;
}
nsIPrincipal *principal = GetPrincipal();
if (!principal) {
return NS_OK;
}
nsIPrincipal *principal;
nsresult rv;
RefPtr<StorageEvent> event = static_cast<StorageEvent*>(aSubject);
if (!event) {
return NS_ERROR_FAILURE;
}
RefPtr<Storage> changingStorage = event->GetStorageArea();
if (!changingStorage) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDOMStorage> istorage = changingStorage.get();
bool fireMozStorageChanged = false;
nsAutoString eventType;
eventType.AssignLiteral("storage");
principal = GetPrincipal();
if (!principal) {
return NS_OK;
}
if (!NS_strcmp(aData, u"sessionStorage")) {
nsCOMPtr<nsIDOMStorage> changingStorage = event->GetStorageArea();
MOZ_ASSERT(changingStorage);
if (changingStorage->IsPrivate() != IsPrivateBrowsing()) {
return NS_OK;
}
switch (changingStorage->GetType())
{
case Storage::SessionStorage:
{
bool check = false;
nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(GetDocShell());
if (storageManager) {
nsresult rv = storageManager->CheckStorage(principal, changingStorage,
&check);
rv = storageManager->CheckStorage(principal, istorage, &check);
if (NS_FAILED(rv)) {
return rv;
}
@ -11624,43 +11625,44 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
if (fireMozStorageChanged) {
eventType.AssignLiteral("MozSessionStorageChanged");
}
break;
}
else {
MOZ_ASSERT(!NS_strcmp(aData, u"localStorage"));
nsIPrincipal* storagePrincipal = event->GetPrincipal();
if (!storagePrincipal) {
return NS_OK;
}
case Storage::LocalStorage:
{
// Allow event fire only for the same principal storages
// XXX We have to use EqualsIgnoreDomain after bug 495337 lands
nsIPrincipal* storagePrincipal = changingStorage->GetPrincipal();
bool equals = false;
nsresult rv = storagePrincipal->Equals(principal, &equals);
rv = storagePrincipal->Equals(principal, &equals);
NS_ENSURE_SUCCESS(rv, rv);
if (!equals) {
if (!equals)
return NS_OK;
}
fireMozStorageChanged = mLocalStorage == event->GetStorageArea();
fireMozStorageChanged = mLocalStorage == changingStorage;
if (fireMozStorageChanged) {
eventType.AssignLiteral("MozLocalStorageChanged");
}
break;
}
default:
return NS_OK;
}
// Clone the storage event included in the observer notification. We want
// to dispatch clones rather than the original event.
ErrorResult error;
RefPtr<StorageEvent> clonedEvent =
CloneStorageEvent(eventType, event, error);
RefPtr<StorageEvent> newEvent = CloneStorageEvent(eventType, event, error);
if (error.Failed()) {
return error.StealNSResult();
}
clonedEvent->SetTrusted(true);
newEvent->SetTrusted(true);
if (fireMozStorageChanged) {
WidgetEvent* internalEvent = clonedEvent->WidgetEventPtr();
WidgetEvent* internalEvent = newEvent->WidgetEventPtr();
internalEvent->mFlags.mOnlyChromeDispatch = true;
}
@ -11669,12 +11671,12 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
// store the domain in which the change happened and fire the
// events if we're ever thawed.
mPendingStorageEvents.AppendElement(clonedEvent);
mPendingStorageEvents.AppendElement(newEvent);
return NS_OK;
}
bool defaultActionEnabled;
DispatchEvent(clonedEvent, &defaultActionEnabled);
DispatchEvent(newEvent, &defaultActionEnabled);
return NS_OK;
}
@ -11765,19 +11767,10 @@ nsGlobalWindow::CloneStorageEvent(const nsAString& aType,
aEvent->GetUrl(dict.mUrl);
RefPtr<Storage> storageArea = aEvent->GetStorageArea();
MOZ_ASSERT(storageArea);
RefPtr<Storage> storage;
// If null, this is a localStorage event received by IPC.
if (!storageArea) {
storage = GetLocalStorage(aRv);
if (aRv.Failed() || !storage) {
return nullptr;
}
// We must apply the current change to the 'local' localStorage.
storage->ApplyEvent(aEvent);
} else if (storageArea->GetType() == Storage::LocalStorage) {
if (storageArea->GetType() == Storage::LocalStorage) {
storage = GetLocalStorage(aRv);
} else {
MOZ_ASSERT(storageArea->GetType() == Storage::SessionStorage);
@ -11789,7 +11782,7 @@ nsGlobalWindow::CloneStorageEvent(const nsAString& aType,
}
MOZ_ASSERT(storage);
MOZ_ASSERT_IF(storageArea, storage->IsForkOf(storageArea));
MOZ_ASSERT(storage->IsForkOf(storageArea));
dict.mStorageArea = storage;
@ -12950,13 +12943,6 @@ nsGlobalWindow::EventListenerAdded(nsIAtom* aType)
aType == nsGkAtoms::onvrdisplaypresentchange) {
NotifyVREventListenerAdded();
}
// We need to initialize localStorage in order to receive notifications.
if (aType == nsGkAtoms::onstorage) {
ErrorResult rv;
GetLocalStorage(rv);
rv.SuppressException();
}
}
void

Просмотреть файл

@ -13,8 +13,6 @@
#include "mozilla/dom/Event.h"
#include "mozilla/dom/StorageEventBinding.h"
class nsIPrincipal;
namespace mozilla {
namespace dom {
@ -36,7 +34,6 @@ protected:
nsString mNewValue;
nsString mUrl;
RefPtr<Storage> mStorageArea;
nsCOMPtr<nsIPrincipal> mPrincipal;
public:
virtual StorageEvent* AsStorageEvent();
@ -82,18 +79,6 @@ public:
{
return mStorageArea;
}
// Non WebIDL methods
void SetPrincipal(nsIPrincipal* aPrincipal)
{
MOZ_ASSERT(!mPrincipal);
mPrincipal = aPrincipal;
}
nsIPrincipal* GetPrincipal() const
{
return mPrincipal;
}
};
} // namespace dom

Просмотреть файл

@ -20,15 +20,8 @@ interface nsIDOMStorageManager : nsISupports
/**
* This starts async preloading of a storage cache for scope
* defined by the principal.
*
* Because of how multi-e10s support was implemented in bug 1285898, the
* StorageCache instance can no longer use a timer to keep itself alive. So a
* Storage instance is returned if precaching believes the principal may have
* localStorage data. (Previously the StorageCache would be brought into
* existence and kept alive by the timer so that it could be returned if a
* call to createStorage was made due to a request by the page.)
*/
nsIDOMStorage precacheStorage(in nsIPrincipal aPrincipal);
void precacheStorage(in nsIPrincipal aPrincipal);
/**
* Returns instance of DOM storage object for given principal.

Просмотреть файл

@ -35,7 +35,6 @@
#include "mozilla/dom/PCrashReporterChild.h"
#include "mozilla/dom/ProcessGlobal.h"
#include "mozilla/dom/PushNotifier.h"
#include "mozilla/dom/Storage.h"
#include "mozilla/dom/StorageIPC.h"
#include "mozilla/dom/TabGroup.h"
#include "mozilla/dom/workers/ServiceWorkerManager.h"
@ -3034,20 +3033,6 @@ ContentChild::RecvBlobURLUnregistration(const nsCString& aURI)
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentChild::RecvDispatchLocalStorageChange(const nsString& aDocumentURI,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue,
const IPC::Principal& aPrincipal,
const bool& aIsPrivate)
{
Storage::DispatchStorageEvent(Storage::LocalStorage,
aDocumentURI, aKey, aOldValue, aNewValue,
aPrincipal, aIsPrivate, nullptr);
return IPC_OK();
}
#if defined(XP_WIN) && defined(ACCESSIBILITY)
bool
ContentChild::SendGetA11yContentId()

Просмотреть файл

@ -412,14 +412,6 @@ public:
virtual mozilla::ipc::IPCResult
RecvInitBlobURLs(nsTArray<BlobURLRegistrationData>&& aRegistations) override;
virtual mozilla::ipc::IPCResult
RecvDispatchLocalStorageChange(const nsString& aDocumentURI,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue,
const IPC::Principal& aPrincipal,
const bool& aIsPrivate) override;
virtual mozilla::ipc::IPCResult RecvLastPrivateDocShellDestroyed() override;
virtual mozilla::ipc::IPCResult RecvFilePathUpdate(const nsString& aStorageType,

Просмотреть файл

@ -52,7 +52,6 @@
#include "mozilla/dom/PContentPermissionRequestParent.h"
#include "mozilla/dom/PCycleCollectWithLogsParent.h"
#include "mozilla/dom/ServiceWorkerRegistrar.h"
#include "mozilla/dom/Storage.h"
#include "mozilla/dom/StorageIPC.h"
#include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h"
#include "mozilla/dom/power/PowerManagerService.h"
@ -4635,25 +4634,6 @@ ContentParent::RecvUnstoreAndBroadcastBlobURLUnregistration(const nsCString& aUR
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvBroadcastLocalStorageChange(const nsString& aDocumentURI,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue,
const Principal& aPrincipal,
const bool& aIsPrivate)
{
for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
if (cp != this) {
Unused << cp->SendDispatchLocalStorageChange(
nsString(aDocumentURI), nsString(aKey), nsString(aOldValue),
nsString(aNewValue), IPC::Principal(aPrincipal), aIsPrivate);
}
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvGetA11yContentId(uint32_t* aContentId)
{

Просмотреть файл

@ -549,14 +549,6 @@ public:
virtual mozilla::ipc::IPCResult
RecvUnstoreAndBroadcastBlobURLUnregistration(const nsCString& aURI) override;
virtual mozilla::ipc::IPCResult
RecvBroadcastLocalStorageChange(const nsString& aDocumentURI,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue,
const IPC::Principal& aPrincipal,
const bool& aIsPrivate) override;
virtual mozilla::ipc::IPCResult
RecvGetA11yContentId(uint32_t* aContentId) override;

Просмотреть файл

@ -645,12 +645,6 @@ child:
async BlobURLUnregistration(nsCString aURI);
async DispatchLocalStorageChange(nsString documentURI,
nsString key,
nsString oldValue,
nsString newValue,
Principal principal,
bool isPrivate);
async GMPsChanged(GMPCapabilityData[] capabilities);
@ -1155,13 +1149,6 @@ parent:
async UnstoreAndBroadcastBlobURLUnregistration(nsCString url);
async BroadcastLocalStorageChange(nsString documentURI,
nsString key,
nsString oldValue,
nsString newValue,
Principal principal,
bool isPrivate);
/**
* Messages for communicating child Telemetry to the parent process
*/

Просмотреть файл

@ -14,9 +14,6 @@
#include "nsIPrincipal.h"
#include "nsICookiePermission.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/StorageBinding.h"
#include "mozilla/dom/StorageEvent.h"
#include "mozilla/dom/StorageEventBinding.h"
@ -28,9 +25,6 @@
#include "nsServiceManagerUtils.h"
namespace mozilla {
using namespace ipc;
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Storage, mManager, mPrincipal, mWindow)
@ -64,6 +58,7 @@ Storage::Storage(nsPIDOMWindowInner* aWindow,
Storage::~Storage()
{
mCache->KeepAlive();
}
/* virtual */ JSObject*
@ -217,29 +212,6 @@ void
Storage::BroadcastChangeNotification(const nsSubstring& aKey,
const nsSubstring& aOldValue,
const nsSubstring& aNewValue)
{
if (!XRE_IsParentProcess() && GetType() == LocalStorage && mPrincipal) {
// If we are in a child process, we want to send a message to the parent in
// order to broadcast the StorageEvent correctly to any child process.
dom::ContentChild* cc = dom::ContentChild::GetSingleton();
Unused << NS_WARN_IF(!cc->SendBroadcastLocalStorageChange(
mDocumentURI, nsString(aKey), nsString(aOldValue), nsString(aNewValue),
IPC::Principal(mPrincipal), mIsPrivate));
}
DispatchStorageEvent(GetType(), mDocumentURI, aKey, aOldValue, aNewValue,
mPrincipal, mIsPrivate, this);
}
/* static */ void
Storage::DispatchStorageEvent(StorageType aStorageType,
const nsAString& aDocumentURI,
const nsAString& aKey,
const nsAString& aOldValue,
const nsAString& aNewValue,
nsIPrincipal* aPrincipal,
bool aIsPrivate,
Storage* aStorage)
{
StorageEventInit dict;
dict.mBubbles = false;
@ -247,62 +219,21 @@ Storage::DispatchStorageEvent(StorageType aStorageType,
dict.mKey = aKey;
dict.mNewValue = aNewValue;
dict.mOldValue = aOldValue;
dict.mStorageArea = aStorage;
dict.mUrl = aDocumentURI;
dict.mStorageArea = this;
dict.mUrl = mDocumentURI;
// Note, this DOM event should never reach JS. It is cloned later in
// nsGlobalWindow.
RefPtr<StorageEvent> event =
StorageEvent::Constructor(nullptr, NS_LITERAL_STRING("storage"), dict);
event->SetPrincipal(aPrincipal);
RefPtr<StorageNotifierRunnable> r =
new StorageNotifierRunnable(event,
aStorageType == LocalStorage
GetType() == LocalStorage
? u"localStorage"
: u"sessionStorage",
aIsPrivate);
IsPrivate());
NS_DispatchToMainThread(r);
// If we are in the parent process and we have the principal, we want to
// broadcast this event to every other process.
if (aStorageType == LocalStorage && XRE_IsParentProcess() && aPrincipal) {
for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
Unused << cp->SendDispatchLocalStorageChange(
nsString(aDocumentURI), nsString(aKey), nsString(aOldValue),
nsString(aNewValue), IPC::Principal(aPrincipal), aIsPrivate);
}
}
}
void
Storage::ApplyEvent(StorageEvent* aStorageEvent)
{
MOZ_ASSERT(aStorageEvent);
nsAutoString key;
nsAutoString old;
nsAutoString value;
aStorageEvent->GetKey(key);
aStorageEvent->GetNewValue(value);
// No key means clearing the full storage.
if (key.IsVoid()) {
MOZ_ASSERT(value.IsVoid());
mCache->Clear(this, StorageCache::E10sPropagated);
return;
}
// No new value means removing the key.
if (value.IsVoid()) {
mCache->RemoveItem(this, key, old, StorageCache::E10sPropagated);
return;
}
// Otherwise, we set the new value.
mCache->SetItem(this, key, value, old, StorageCache::E10sPropagated);
}
static const char kPermissionType[] = "cookie";

Просмотреть файл

@ -24,7 +24,6 @@ namespace dom {
class StorageManagerBase;
class StorageCache;
class StorageEvent;
class Storage final
: public nsIDOMStorage
@ -130,20 +129,6 @@ public:
return mCache == aOther->mCache;
}
// aStorage can be null if this method is called by ContentChild.
static void
DispatchStorageEvent(StorageType aStorageType,
const nsAString& aDocumentURI,
const nsAString& aKey,
const nsAString& aOldValue,
const nsAString& aNewValue,
nsIPrincipal* aPrincipal,
bool aIsPrivate,
Storage* aStorage);
void
ApplyEvent(StorageEvent* aStorageEvent);
protected:
// The method checks whether the caller can use a storage.
// CanUseStorage is called before any DOM initiated operation

Просмотреть файл

@ -59,7 +59,7 @@ GetDataSetIndex(const Storage* aStorage)
NS_IMPL_ADDREF(StorageCacheBridge)
// Since there is no consumer of return value of Release, we can turn this
// Since there is no consumer of return value of Release, we can turn this
// method to void to make implementation of asynchronous StorageCache::Release
// much simpler.
NS_IMETHODIMP_(void) StorageCacheBridge::Release(void)
@ -149,7 +149,7 @@ StorageCache::Init(StorageManagerBase* aManager,
// 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,
MOZ_ASSERT(mOriginSuffix.IsEmpty() != StringBeginsWith(mQuotaOriginScope,
NS_LITERAL_CSTRING("^")));
mUsage = aManager->GetOriginUsage(mQuotaOriginScope);
@ -198,33 +198,28 @@ StorageCache::DataSet(const Storage* aStorage)
}
bool
StorageCache::ProcessUsageDelta(const Storage* aStorage, int64_t aDelta,
const MutationSource aSource)
StorageCache::ProcessUsageDelta(const Storage* aStorage, int64_t aDelta)
{
return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta, aSource);
return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta);
}
bool
StorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta,
const MutationSource aSource)
StorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta)
{
// Check if we are in a low disk space situation
if (aSource == ContentMutation &&
aDelta > 0 && mManager && mManager->IsLowDiskSpace()) {
if (aDelta > 0 && mManager && mManager->IsLowDiskSpace()) {
return false;
}
// Check limit per this origin
Data& data = mData[aGetDataSetIndex];
uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta;
if (aSource == ContentMutation &&
aDelta > 0 && newOriginUsage > StorageManagerBase::GetQuota()) {
if (aDelta > 0 && newOriginUsage > StorageManagerBase::GetQuota()) {
return false;
}
// Now check eTLD+1 limit
if (mUsage &&
!mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta, aSource)) {
if (mUsage && !mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta)) {
return false;
}
@ -251,6 +246,60 @@ StorageCache::Preload()
namespace {
// This class is passed to timer as a tick observer. It refers the cache
// and keeps it alive for a time.
class StorageCacheHolder : public nsITimerCallback
{
virtual ~StorageCacheHolder() {}
NS_DECL_ISUPPORTS
NS_IMETHOD
Notify(nsITimer* aTimer) override
{
mCache = nullptr;
return NS_OK;
}
RefPtr<StorageCache> mCache;
public:
explicit StorageCacheHolder(StorageCache* aCache) : mCache(aCache) {}
};
NS_IMPL_ISUPPORTS(StorageCacheHolder, nsITimerCallback)
} // namespace
void
StorageCache::KeepAlive()
{
// Missing reference back to the manager means the cache is not responsible
// for its lifetime. Used for keeping sessionStorage live forever.
if (!mManager) {
return;
}
if (!NS_IsMainThread()) {
// Timer and the holder must be initialized on the main thread.
NS_DispatchToMainThread(NewRunnableMethod(this, &StorageCache::KeepAlive));
return;
}
nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1");
if (!timer) {
return;
}
RefPtr<StorageCacheHolder> holder = new StorageCacheHolder(this);
timer->InitWithCallback(holder, DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS,
nsITimer::TYPE_ONE_SHOT);
mKeepAliveTimer.swap(timer);
}
namespace {
// The AutoTimer provided by telemetry headers is only using static,
// i.e. compile time known ID, but here we know the ID only at run time.
// Hence a new class.
@ -390,8 +439,7 @@ StorageCache::GetItem(const Storage* aStorage, const nsAString& aKey,
nsresult
StorageCache::SetItem(const Storage* aStorage, const nsAString& aKey,
const nsString& aValue, nsString& aOld,
const MutationSource aSource)
const nsString& aValue, nsString& aOld)
{
// Size of the cache that will change after this action.
int64_t delta = 0;
@ -414,7 +462,7 @@ StorageCache::SetItem(const Storage* aStorage, const nsAString& aKey,
delta += static_cast<int64_t>(aValue.Length()) -
static_cast<int64_t>(aOld.Length());
if (!ProcessUsageDelta(aStorage, delta, aSource)) {
if (!ProcessUsageDelta(aStorage, delta)) {
return NS_ERROR_DOM_QUOTA_REACHED;
}
@ -424,7 +472,7 @@ StorageCache::SetItem(const Storage* aStorage, const nsAString& aKey,
data.mKeys.Put(aKey, aValue);
if (aSource == ContentMutation && Persist(aStorage)) {
if (Persist(aStorage)) {
if (!sDatabase) {
NS_ERROR("Writing to localStorage after the database has been shut down"
", data lose!");
@ -443,7 +491,7 @@ StorageCache::SetItem(const Storage* aStorage, const nsAString& aKey,
nsresult
StorageCache::RemoveItem(const Storage* aStorage, const nsAString& aKey,
nsString& aOld, const MutationSource aSource)
nsString& aOld)
{
if (Persist(aStorage)) {
WaitForPreload(Telemetry::LOCALDOMSTORAGE_REMOVEKEY_BLOCKING_MS);
@ -461,10 +509,10 @@ StorageCache::RemoveItem(const Storage* aStorage, const nsAString& aKey,
// Recalculate the cached data size
const int64_t delta = -(static_cast<int64_t>(aOld.Length()) +
static_cast<int64_t>(aKey.Length()));
Unused << ProcessUsageDelta(aStorage, delta, aSource);
Unused << ProcessUsageDelta(aStorage, delta);
data.mKeys.Remove(aKey);
if (aSource == ContentMutation && Persist(aStorage)) {
if (Persist(aStorage)) {
if (!sDatabase) {
NS_ERROR("Writing to localStorage after the database has been shut down"
", data lose!");
@ -478,7 +526,7 @@ StorageCache::RemoveItem(const Storage* aStorage, const nsAString& aKey,
}
nsresult
StorageCache::Clear(const Storage* aStorage, const MutationSource aSource)
StorageCache::Clear(const Storage* aStorage)
{
bool refresh = false;
if (Persist(aStorage)) {
@ -500,11 +548,11 @@ StorageCache::Clear(const Storage* aStorage, const MutationSource aSource)
bool hadData = !!data.mKeys.Count();
if (hadData) {
Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage, aSource);
Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage);
data.mKeys.Clear();
}
if (aSource == ContentMutation && Persist(aStorage) && (refresh || hadData)) {
if (Persist(aStorage) && (refresh || hadData)) {
if (!sDatabase) {
NS_ERROR("Writing to localStorage after the database has been shut down"
", data lose!");
@ -619,6 +667,9 @@ StorageCache::LoadItem(const nsAString& aKey, const nsString& aValue)
void
StorageCache::LoadDone(nsresult aRv)
{
// Keep the preloaded cache alive for a time
KeepAlive();
MonitorAutoLock monitor(mMonitor);
mLoadResult = aRv;
mLoaded = true;
@ -679,13 +730,12 @@ StorageUsage::LoadUsage(const int64_t aUsage)
bool
StorageUsage::CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex,
const int64_t aDelta, const StorageCache::MutationSource aSource)
const int64_t aDelta)
{
MOZ_ASSERT(NS_IsMainThread());
int64_t newUsage = mUsage[aDataSetIndex] + aDelta;
if (aSource == StorageCache::ContentMutation &&
aDelta > 0 && newUsage > StorageManagerBase::GetQuota()) {
if (aDelta > 0 && newUsage > StorageManagerBase::GetQuota()) {
return false;
}

Просмотреть файл

@ -78,24 +78,8 @@ class StorageCache : public StorageCacheBridge
public:
NS_IMETHOD_(void) Release(void);
enum MutationSource {
// The mutation is a result of an explicit JS mutation in this process.
// The mutation should be sent to the sDatabase. Quota will be checked and
// QuotaExceededError may be returned without the mutation being applied.
ContentMutation,
// The mutation initially was triggered in a different process and is being
// propagated to this cache via Storage::ApplyEvent. The mutation should
// not be sent to the sDatabase because the originating process is already
// doing that. (In addition to the redundant writes being wasteful, there
// is the potential for other processes to see inconsistent state from the
// database while preloading.) Quota will be updated but not checked
// because it's assumed it was checked in another process and data-coherency
// is more important than slightly exceeding quota.
E10sPropagated
};
// Note: We pass aOriginNoSuffix through the ctor here, because
// StorageCacheHashKey's ctor is creating this class and
// Note: We pass aOriginNoSuffix through the ctor here, because
// StorageCacheHashKey's ctor is creating this class and
// accepts reversed-origin-no-suffix as an argument - the hashing key.
explicit StorageCache(const nsACString* aOriginNoSuffix);
@ -125,13 +109,10 @@ public:
nsresult GetItem(const Storage* aStorage, const nsAString& aKey,
nsAString& aRetval);
nsresult SetItem(const Storage* aStorage, const nsAString& aKey,
const nsString& aValue, nsString& aOld,
const MutationSource aSource=ContentMutation);
const nsString& aValue, nsString& aOld);
nsresult RemoveItem(const Storage* aStorage, const nsAString& aKey,
nsString& aOld,
const MutationSource aSource=ContentMutation);
nsresult Clear(const Storage* aStorage,
const MutationSource aSource=ContentMutation);
nsString& aOld);
nsresult Clear(const Storage* aStorage);
void GetKeys(const Storage* aStorage, nsTArray<nsString>& aKeys);
@ -201,18 +182,8 @@ private:
// Changes the quota usage on the given data set if it fits the quota.
// If not, then false is returned and no change to the set must be done.
// A special case is if aSource==E10sPropagated, then we will return true even
// if the change would put us over quota. This is done to ensure coherency of
// caches between processes in the face of races. It does allow an attacker
// to potentially use N multiples of the quota storage limit if they can
// arrange for their origin to execute code in N processes. However, this is
// not considered a particularly concerning threat model because it's already
// very possible for a rogue page to attempt to intentionally fill up the
// user's storage through the use of multiple domains.
bool ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta,
const MutationSource aSource=ContentMutation);
bool ProcessUsageDelta(const Storage* aStorage, const int64_t aDelta,
const MutationSource aSource=ContentMutation);
bool ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta);
bool ProcessUsageDelta(const Storage* aStorage, const int64_t aDelta);
private:
// When a cache is reponsible for its life time (in case of localStorage data
@ -225,6 +196,9 @@ private:
// Obtained from the manager during initialization (Init method).
RefPtr<StorageUsage> mUsage;
// Timer that holds this cache alive for a while after it has been preloaded.
nsCOMPtr<nsITimer> mKeepAliveTimer;
// Principal the cache has been initially created for, this is used only for
// sessionStorage access checks since sessionStorage objects are strictly
// scoped by a principal. localStorage objects on the other hand are scoped
@ -237,7 +211,7 @@ private:
// The origin attributes suffix
nsCString mOriginSuffix;
// The eTLD+1 scope used to count quota usage. It is in the reversed format
// The eTLD+1 scope used to count quota usage. It is in the reversed format
// and contains the origin attributes suffix.
nsCString mQuotaOriginScope;
@ -303,8 +277,7 @@ class StorageUsage : public StorageUsageBridge
public:
explicit StorageUsage(const nsACString& aOriginScope);
bool CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, int64_t aUsageDelta,
const StorageCache::MutationSource aSource);
bool CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, int64_t aUsageDelta);
private:
virtual const nsCString& OriginScope() { return mOriginScope; }

Просмотреть файл

@ -224,12 +224,6 @@ StorageDBChild::RecvObserve(const nsCString& aTopic,
mozilla::ipc::IPCResult
StorageDBChild::RecvOriginsHavingData(nsTArray<nsCString>&& aOrigins)
{
// Force population of mOriginsHavingData even if there are no origins so that
// ShouldPreloadOrigin does not generate false positives for all origins.
if (!aOrigins.Length()) {
Unused << OriginsHavingData();
}
for (uint32_t i = 0; i < aOrigins.Length(); ++i) {
OriginsHavingData().PutEntry(aOrigins[i]);
}

Просмотреть файл

@ -309,7 +309,7 @@ StorageManagerBase::DropCache(StorageCache* aCache)
}
nsresult
StorageManagerBase::GetStorageInternal(CreateMode aCreateMode,
StorageManagerBase::GetStorageInternal(bool aCreate,
mozIDOMWindow* aWindow,
nsIPrincipal* aPrincipal,
const nsAString& aDocumentURI,
@ -331,12 +331,12 @@ StorageManagerBase::GetStorageInternal(CreateMode aCreateMode,
// Get or create a cache for the given scope
if (!cache) {
if (aCreateMode == CreateMode::UseIfExistsNeverCreate) {
if (!aCreate) {
*aRetval = nullptr;
return NS_OK;
}
if (aCreateMode == CreateMode::CreateIfShouldPreload) {
if (!aRetval) {
// This is a demand to just preload the cache, if the scope has
// no data stored, bypass creation and preload of the cache.
StorageDBBridge* db = StorageCache::GetDatabase();
@ -372,11 +372,10 @@ StorageManagerBase::GetStorageInternal(CreateMode aCreateMode,
}
NS_IMETHODIMP
StorageManagerBase::PrecacheStorage(nsIPrincipal* aPrincipal,
nsIDOMStorage** aRetval)
StorageManagerBase::PrecacheStorage(nsIPrincipal* aPrincipal)
{
return GetStorageInternal(CreateMode::CreateIfShouldPreload, nullptr,
aPrincipal, EmptyString(), false, aRetval);
return GetStorageInternal(true, nullptr, aPrincipal, EmptyString(), false,
nullptr);
}
NS_IMETHODIMP
@ -386,8 +385,8 @@ StorageManagerBase::CreateStorage(mozIDOMWindow* aWindow,
bool aPrivate,
nsIDOMStorage** aRetval)
{
return GetStorageInternal(CreateMode::CreateAlways, aWindow, aPrincipal,
aDocumentURI, aPrivate, aRetval);
return GetStorageInternal(true, aWindow, aPrincipal, aDocumentURI, aPrivate,
aRetval);
}
NS_IMETHODIMP
@ -396,8 +395,8 @@ StorageManagerBase::GetStorage(mozIDOMWindow* aWindow,
bool aPrivate,
nsIDOMStorage** aRetval)
{
return GetStorageInternal(CreateMode::UseIfExistsNeverCreate, aWindow,
aPrincipal, EmptyString(), aPrivate, aRetval);
return GetStorageInternal(false, aWindow, aPrincipal, EmptyString(), aPrivate,
aRetval);
}
NS_IMETHODIMP

Просмотреть файл

@ -90,17 +90,8 @@ private:
const nsACString& aOriginNoSuffix,
nsIPrincipal* aPrincipal);
enum class CreateMode {
// GetStorage: do not create if it's not already in memory.
UseIfExistsNeverCreate,
// CreateStorage: Create it if it's not already in memory.
CreateAlways,
// PrecacheStorage: Create only if the database says we ShouldPreloadOrigin.
CreateIfShouldPreload
};
// Helper for creation of DOM storage objects
nsresult GetStorageInternal(CreateMode aCreate,
nsresult GetStorageInternal(bool aCreate,
mozIDOMWindow* aWindow,
nsIPrincipal* aPrincipal,
const nsAString& aDocumentURI,

Просмотреть файл

@ -2,7 +2,6 @@
support-files =
browser_frame_elements.html
page_privatestorageevent.html
page_localstorage_e10s.html
position.html
test-console-api.html
test_bug1004814.html
@ -38,8 +37,6 @@ skip-if = e10s
[browser_frame_elements.js]
[browser_largeAllocation.js]
skip-if = true || !e10s # Large-Allocation requires e10s, turned off for e10s-multi Bug 1315042
[browser_localStorage_e10s.js]
skip-if = !e10s # This is a test of e10s functionality.
[browser_localStorage_privatestorageevent.js]
[browser_test__content.js]
[browser_test_new_window_from_content.js]

Просмотреть файл

@ -1,330 +0,0 @@
const HELPER_PAGE_URL =
"http://example.com/browser/dom/tests/browser/page_localstorage_e10s.html";
const HELPER_PAGE_ORIGIN = "http://example.com/";
// Simple tab wrapper abstracting our messaging mechanism;
class KnownTab {
constructor(name, tab) {
this.name = name;
this.tab = tab;
}
cleanup() {
this.tab = null;
}
}
// Simple data structure class to help us track opened tabs and their pids.
class KnownTabs {
constructor() {
this.byPid = new Map();
this.byName = new Map();
}
cleanup() {
this.byPid = null;
this.byName = null;
}
}
/**
* Open our helper page in a tab in its own content process, asserting that it
* really is in its own process.
*/
function* openTestTabInOwnProcess(name, knownTabs) {
let url = HELPER_PAGE_URL + '?' + encodeURIComponent(name);
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
let pid = tab.linkedBrowser.frameLoader.tabParent.osPid;
ok(!knownTabs.byName.has(name), "tab needs its own name: " + name);
ok(!knownTabs.byPid.has(pid), "tab needs to be in its own process: " + pid);
let knownTab = new KnownTab(name, tab);
knownTabs.byPid.set(pid, knownTab);
knownTabs.byName.set(name, knownTab);
return knownTab;
}
/**
* Close all the tabs we opened.
*/
function* cleanupTabs(knownTabs) {
for (let knownTab of knownTabs.byName.values()) {
yield BrowserTestUtils.removeTab(knownTab.tab);
knownTab.cleanup();
}
knownTabs.cleanup();
}
/**
* Clear the origin's storage so that "OriginsHavingData" will return false for
* our origin. Note that this is only the case for AsyncClear() which is
* explicitly issued against a cache, or AsyncClearAll() which we can trigger
* by wiping all storage. However, the more targeted domain clearings that
* we can trigger via observer, AsyncClearMatchingOrigin and
* AsyncClearMatchingOriginAttributes will not clear the hashtable entry for
* the origin.
*
* So we explicitly access the cache here in the parent for the origin and issue
* an explicit clear. Clearing all storage might be a little easier but seems
* like asking for intermittent failures.
*/
function clearOriginStorageEnsuringNoPreload() {
let principal =
Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(
HELPER_PAGE_ORIGIN);
// We want to use createStorage to force the cache to be created so we can
// issue the clear. It's possible for getStorage to return false but for the
// origin preload hash to still have our origin in it.
let storage = Services.domStorageManager.createStorage(null, principal, "");
storage.clear();
// We don't need to wait for anything. The clear call will have queued the
// clear operation on the database thread, and the child process requests
// for origins will likewise be answered via the database thread.
}
function* verifyTabPreload(knownTab, expectStorageExists) {
let storageExists = yield ContentTask.spawn(
knownTab.tab.linkedBrowser,
HELPER_PAGE_ORIGIN,
function(origin) {
let principal =
Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(
origin);
return !!Services.domStorageManager.getStorage(null, principal);
});
is(storageExists, expectStorageExists, "Storage existence === preload");
}
/**
* Instruct the given tab to execute the given series of mutations. For
* simplicity, the mutations representation matches the expected events rep.
*/
function* mutateTabStorage(knownTab, mutations) {
yield ContentTask.spawn(
knownTab.tab.linkedBrowser,
{ mutations },
function(args) {
return content.wrappedJSObject.mutateStorage(args.mutations);
});
}
/**
* Instruct the given tab to add a "storage" event listener and record all
* received events. verifyTabStorageEvents is the corresponding method to
* check and assert the recorded events.
*/
function* recordTabStorageEvents(knownTab) {
yield ContentTask.spawn(
knownTab.tab.linkedBrowser,
{},
function() {
return content.wrappedJSObject.listenForStorageEvents();
});
}
/**
* Retrieve the current localStorage contents perceived by the tab and assert
* that they match the provided expected state.
*/
function* verifyTabStorageState(knownTab, expectedState) {
let actualState = yield ContentTask.spawn(
knownTab.tab.linkedBrowser,
{},
function() {
return content.wrappedJSObject.getStorageState();
});
for (let [expectedKey, expectedValue] of Object.entries(expectedState)) {
ok(actualState.hasOwnProperty(expectedKey), "key present: " + expectedKey);
is(actualState[expectedKey], expectedValue, "value correct");
}
for (let actualKey of Object.keys(actualState)) {
if (!expectedState.hasOwnProperty(actualKey)) {
ok(false, "actual state has key it shouldn't have: " + actualKey);
}
}
}
/**
* Retrieve and clear the storage events recorded by the tab and assert that
* they match the provided expected events. For simplicity, the expected events
* representation is the same as that used by mutateTabStorage.
*/
function* verifyTabStorageEvents(knownTab, expectedEvents) {
let actualEvents = yield ContentTask.spawn(
knownTab.tab.linkedBrowser,
{},
function() {
return content.wrappedJSObject.returnAndClearStorageEvents();
});
is(actualEvents.length, expectedEvents.length, "right number of events");
for (let i = 0; i < actualEvents.length; i++) {
let [actualKey, actualNewValue, actualOldValue] = actualEvents[i];
let [expectedKey, expectedNewValue, expectedOldValue] = expectedEvents[i];
is(actualKey, expectedKey, "keys match");
is(actualNewValue, expectedNewValue, "new values match");
is(actualOldValue, expectedOldValue, "old values match");
}
}
// We spin up a ton of child processes.
requestLongerTimeout(4);
/**
* Verify the basics of our multi-e10s localStorage support. We are focused on
* whitebox testing two things. When this is being written, broadcast filtering
* is not in place, but the test is intended to attempt to verify that its
* implementation does not break things.
*
* 1) That pages see the same localStorage state in a timely fashion when
* engaging in non-conflicting operations. We are not testing races or
* conflict resolution; the spec does not cover that.
*
* 2) That there are no edge-cases related to when the Storage instance is
* created for the page or the StorageCache for the origin. (StorageCache is
* what actually backs the Storage binding exposed to the page.) This
* matters because the following reasons can exist for them to be created:
* - Preload, on the basis of knowing the origin uses localStorage. The
* interesting edge case is when we have the same origin open in different
* processes and the origin starts using localStorage when it did not
* before. Preload will not have instantiated bindings, which could impact
* correctness.
* - The page accessing localStorage for read or write purposes. This is the
* obvious, boring one.
* - The page adding a "storage" listener. This is less obvious and
* interacts with the preload edge-case mentioned above. The page needs to
* hear "storage" events even if the page has not touched localStorage
* itself and its origin had nothing stored in localStorage when the page
* was created.
*
* We use the same simple child page in all tabs that:
* - can be instructed to listen for and record "storage" events
* - can be instructed to issue a series of localStorage writes
* - can be instructed to return the current entire localStorage contents
*
* We open the 5 following tabs:
* - Open a "writer" tab that does not listen for "storage" events and will
* issue only writes.
* - Open a "listener" tab instructed to listen for "storage" events
* immediately. We expect it to capture all events.
* - Open an "reader" tab that does not listen for "storage" events and will
* only issue reads when instructed.
* - Open a "lateWriteThenListen" tab that initially does nothing. We will
* later tell it to issue a write and then listen for events to make sure it
* captures the later events.
* - Open "lateOpenSeesPreload" tab after we've done everything and ensure that
* it preloads/precaches the data without us having touched localStorage or
* added an event listener.
*/
add_task(function*() {
// (There's already one about:blank page open and we open 5 new tabs, so 6
// processes. Actually, 7, just in case.)
yield SpecialPowers.pushPrefEnv({
set: [
["dom.ipc.processCount", 7]
]
});
// Ensure that there is no localstorage data or potential false positives for
// localstorage preloads by forcing the origin to be cleared prior to the
// start of our test.
clearOriginStorageEnsuringNoPreload();
// - Open tabs. Don't configure any of them yet.
const knownTabs = new KnownTabs();
const writerTab = yield* openTestTabInOwnProcess("writer", knownTabs);
const listenerTab = yield* openTestTabInOwnProcess("listener", knownTabs);
const readerTab = yield* openTestTabInOwnProcess("reader", knownTabs);
const lateWriteThenListenTab = yield* openTestTabInOwnProcess(
"lateWriteThenListen", knownTabs);
// Sanity check that preloading did not occur in the tabs.
yield* verifyTabPreload(writerTab, false);
yield* verifyTabPreload(listenerTab, false);
yield* verifyTabPreload(readerTab, false);
// - Configure the tabs.
yield* recordTabStorageEvents(listenerTab);
// - Issue the initial batch of writes and verify.
const initialWriteMutations = [
//[key (null=clear), newValue (null=delete), oldValue (verification)]
["getsCleared", "1", null],
["alsoGetsCleared", "2", null],
[null, null, null],
["stays", "3", null],
["clobbered", "pre", null],
["getsDeletedLater", "4", null],
["getsDeletedImmediately", "5", null],
["getsDeletedImmediately", null, "5"],
["alsoStays", "6", null],
["getsDeletedLater", null, "4"],
["clobbered", "post", "pre"]
];
const initialWriteState = {
stays: "3",
clobbered: "post",
alsoStays: "6"
};
yield* mutateTabStorage(writerTab, initialWriteMutations);
yield* verifyTabStorageState(writerTab, initialWriteState);
yield* verifyTabStorageEvents(listenerTab, initialWriteMutations);
yield* verifyTabStorageState(listenerTab, initialWriteState);
yield* verifyTabStorageState(readerTab, initialWriteState);
// - Issue second set of writes from lateWriteThenListen
const lateWriteMutations = [
["lateStays", "10", null],
["lateClobbered", "latePre", null],
["lateDeleted", "11", null],
["lateClobbered", "lastPost", "latePre"],
["lateDeleted", null, "11"]
];
const lateWriteState = Object.assign({}, initialWriteState, {
lateStays: "10",
lateClobbered: "lastPost"
});
yield* mutateTabStorage(lateWriteThenListenTab, lateWriteMutations);
yield* recordTabStorageEvents(lateWriteThenListenTab);
yield* verifyTabStorageState(writerTab, lateWriteState);
yield* verifyTabStorageEvents(listenerTab, lateWriteMutations);
yield* verifyTabStorageState(listenerTab, lateWriteState);
yield* verifyTabStorageState(readerTab, lateWriteState);
// - Issue last set of writes from writerTab.
const lastWriteMutations = [
["lastStays", "20", null],
["lastDeleted", "21", null],
["lastClobbered", "lastPre", null],
["lastClobbered", "lastPost", "lastPre"],
["lastDeleted", null, "21"]
];
const lastWriteState = Object.assign({}, lateWriteState, {
lastStays: "20",
lastClobbered: "lastPost"
});
yield* mutateTabStorage(writerTab, lastWriteMutations);
yield* verifyTabStorageState(writerTab, lastWriteState);
yield* verifyTabStorageEvents(listenerTab, lastWriteMutations);
yield* verifyTabStorageState(listenerTab, lastWriteState);
yield* verifyTabStorageState(readerTab, lastWriteState);
yield* verifyTabStorageEvents(lateWriteThenListenTab, lastWriteMutations);
yield* verifyTabStorageState(lateWriteThenListenTab, lastWriteState);
// - Open a fresh tab and make sure it sees the precache/preload
const lateOpenSeesPreload =
yield* openTestTabInOwnProcess("lateOpenSeesPreload", knownTabs);
yield* verifyTabPreload(lateOpenSeesPreload, true);
// - Clean up.
yield* cleanupTabs(knownTabs);
clearOriginStorageEnsuringNoPreload();
});

Просмотреть файл

@ -1,56 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script>
/**
* Helper page used by browser_localStorage_e10s.js.
**/
var pageName = document.location.search.substring(1);
window.addEventListener(
"load",
() => { document.getElementById("pageNameH").textContent = pageName; });
var recordedEvents = null;
function storageListener(event) {
recordedEvents.push([event.key, event.newValue, event.oldValue]);
}
function listenForStorageEvents() {
recordedEvents = [];
window.addEventListener("storage", storageListener);
}
function mutateStorage(mutations) {
mutations.forEach(function([key, value]) {
if (key !== null) {
if (value === null) {
localStorage.removeItem(key);
} else {
localStorage.setItem(key, value);
}
} else {
localStorage.clear();
}
});
}
function getStorageState() {
let numKeys = localStorage.length;
let state = {};
for (var iKey = 0; iKey < numKeys; iKey++) {
let key = localStorage.key(iKey);
state[key] = localStorage.getItem(key);
}
return state;
}
function returnAndClearStorageEvents() {
let loggedEvents = recordedEvents;
recordedEvents = [];
return loggedEvents;
}
</script>
</head>
<body><h2 id="pageNameH"></h2></body>
</html>

Просмотреть файл

@ -24,7 +24,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=674770
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(function() {
SpecialPowers.pushPrefEnv({"set":[["middlemouse.paste", true]]}, startTests);
SpecialPowers.pushPrefEnv({"set":[["middlemouse.paste", true], ["dom.ipc.processCount", 1]]}, startTests);
});
function startTests() {