This commit is contained in:
Phil Ringnalda 2017-02-02 22:11:09 -08:00
Родитель fd06930679 b1aadb3572
Коммит 6bfdccc26e
19 изменённых файлов: 150 добавлений и 730 удалений

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

@ -3211,11 +3211,7 @@ nsGlobalWindow::PreloadLocalStorage()
// private browsing windows do not persist local storage to disk so we should // 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. // only try to precache storage when we're not a private browsing window.
if (principal->GetPrivateBrowsingId() == 0) { if (principal->GetPrivateBrowsingId() == 0) {
nsCOMPtr<nsIDOMStorage> storage; storageManager->PrecacheStorage(principal);
rv = storageManager->PrecacheStorage(principal, getter_AddRefs(storage));
if (NS_SUCCEEDED(rv)) {
mLocalStorage = static_cast<Storage*>(storage.get());
}
} }
} }
@ -11805,43 +11801,48 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
} }
bool isPrivateBrowsing = IsPrivateBrowsing(); 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) || if ((!nsCRT::strcmp(aTopic, "dom-storage2-changed") && !isPrivateBrowsing) ||
(!nsCRT::strcmp(aTopic, "dom-private-storage2-changed") && isPrivateBrowsing)) { (!nsCRT::strcmp(aTopic, "dom-private-storage2-changed") && isPrivateBrowsing)) {
if (!IsInnerWindow() || !AsInner()->IsCurrentInnerWindow()) { if (!IsInnerWindow() || !AsInner()->IsCurrentInnerWindow()) {
return NS_OK; return NS_OK;
} }
nsIPrincipal *principal = GetPrincipal(); nsIPrincipal *principal;
if (!principal) { nsresult rv;
return NS_OK;
}
RefPtr<StorageEvent> event = static_cast<StorageEvent*>(aSubject); RefPtr<StorageEvent> event = static_cast<StorageEvent*>(aSubject);
if (!event) { if (!event) {
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
RefPtr<Storage> changingStorage = event->GetStorageArea();
if (!changingStorage) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDOMStorage> istorage = changingStorage.get();
bool fireMozStorageChanged = false; bool fireMozStorageChanged = false;
nsAutoString eventType; nsAutoString eventType;
eventType.AssignLiteral("storage"); eventType.AssignLiteral("storage");
principal = GetPrincipal();
if (!principal) {
return NS_OK;
}
if (!NS_strcmp(aData, u"sessionStorage")) { if (changingStorage->IsPrivate() != IsPrivateBrowsing()) {
nsCOMPtr<nsIDOMStorage> changingStorage = event->GetStorageArea(); return NS_OK;
MOZ_ASSERT(changingStorage); }
switch (changingStorage->GetType())
{
case Storage::SessionStorage:
{
bool check = false; bool check = false;
nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(GetDocShell()); nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(GetDocShell());
if (storageManager) { if (storageManager) {
nsresult rv = storageManager->CheckStorage(principal, changingStorage, rv = storageManager->CheckStorage(principal, istorage, &check);
&check);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; return rv;
} }
@ -11862,43 +11863,44 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
if (fireMozStorageChanged) { if (fireMozStorageChanged) {
eventType.AssignLiteral("MozSessionStorageChanged"); eventType.AssignLiteral("MozSessionStorageChanged");
} }
break;
} }
else { case Storage::LocalStorage:
MOZ_ASSERT(!NS_strcmp(aData, u"localStorage")); {
nsIPrincipal* storagePrincipal = event->GetPrincipal(); // Allow event fire only for the same principal storages
if (!storagePrincipal) { // XXX We have to use EqualsIgnoreDomain after bug 495337 lands
return NS_OK; nsIPrincipal* storagePrincipal = changingStorage->GetPrincipal();
}
bool equals = false; bool equals = false;
nsresult rv = storagePrincipal->Equals(principal, &equals); rv = storagePrincipal->Equals(principal, &equals);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
if (!equals) { if (!equals)
return NS_OK; return NS_OK;
}
fireMozStorageChanged = mLocalStorage == event->GetStorageArea();
fireMozStorageChanged = mLocalStorage == changingStorage;
if (fireMozStorageChanged) { if (fireMozStorageChanged) {
eventType.AssignLiteral("MozLocalStorageChanged"); eventType.AssignLiteral("MozLocalStorageChanged");
} }
break;
}
default:
return NS_OK;
} }
// Clone the storage event included in the observer notification. We want // Clone the storage event included in the observer notification. We want
// to dispatch clones rather than the original event. // to dispatch clones rather than the original event.
ErrorResult error; ErrorResult error;
RefPtr<StorageEvent> clonedEvent = RefPtr<StorageEvent> newEvent = CloneStorageEvent(eventType, event, error);
CloneStorageEvent(eventType, event, error);
if (error.Failed()) { if (error.Failed()) {
return error.StealNSResult(); return error.StealNSResult();
} }
clonedEvent->SetTrusted(true); newEvent->SetTrusted(true);
if (fireMozStorageChanged) { if (fireMozStorageChanged) {
WidgetEvent* internalEvent = clonedEvent->WidgetEventPtr(); WidgetEvent* internalEvent = newEvent->WidgetEventPtr();
internalEvent->mFlags.mOnlyChromeDispatch = true; internalEvent->mFlags.mOnlyChromeDispatch = true;
} }
@ -11907,12 +11909,12 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
// store the domain in which the change happened and fire the // store the domain in which the change happened and fire the
// events if we're ever thawed. // events if we're ever thawed.
mPendingStorageEvents.AppendElement(clonedEvent); mPendingStorageEvents.AppendElement(newEvent);
return NS_OK; return NS_OK;
} }
bool defaultActionEnabled; bool defaultActionEnabled;
DispatchEvent(clonedEvent, &defaultActionEnabled); DispatchEvent(newEvent, &defaultActionEnabled);
return NS_OK; return NS_OK;
} }
@ -12003,19 +12005,10 @@ nsGlobalWindow::CloneStorageEvent(const nsAString& aType,
aEvent->GetUrl(dict.mUrl); aEvent->GetUrl(dict.mUrl);
RefPtr<Storage> storageArea = aEvent->GetStorageArea(); RefPtr<Storage> storageArea = aEvent->GetStorageArea();
MOZ_ASSERT(storageArea);
RefPtr<Storage> storage; RefPtr<Storage> storage;
if (storageArea->GetType() == Storage::LocalStorage) {
// 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) {
storage = GetLocalStorage(aRv); storage = GetLocalStorage(aRv);
} else { } else {
MOZ_ASSERT(storageArea->GetType() == Storage::SessionStorage); MOZ_ASSERT(storageArea->GetType() == Storage::SessionStorage);
@ -12027,7 +12020,7 @@ nsGlobalWindow::CloneStorageEvent(const nsAString& aType,
} }
MOZ_ASSERT(storage); MOZ_ASSERT(storage);
MOZ_ASSERT_IF(storageArea, storage->IsForkOf(storageArea)); MOZ_ASSERT(storage->IsForkOf(storageArea));
dict.mStorageArea = storage; dict.mStorageArea = storage;
@ -13190,13 +13183,6 @@ nsGlobalWindow::EventListenerAdded(nsIAtom* aType)
aType == nsGkAtoms::onvrdisplaypresentchange) { aType == nsGkAtoms::onvrdisplaypresentchange) {
NotifyVREventListenerAdded(); NotifyVREventListenerAdded();
} }
// We need to initialize localStorage in order to receive notifications.
if (aType == nsGkAtoms::onstorage) {
ErrorResult rv;
GetLocalStorage(rv);
rv.SuppressException();
}
} }
void void

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

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

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

@ -20,15 +20,8 @@ interface nsIDOMStorageManager : nsISupports
/** /**
* This starts async preloading of a storage cache for scope * This starts async preloading of a storage cache for scope
* defined by the principal. * 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. * Returns instance of DOM storage object for given principal.

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

@ -35,7 +35,6 @@
#include "mozilla/dom/PCrashReporterChild.h" #include "mozilla/dom/PCrashReporterChild.h"
#include "mozilla/dom/ProcessGlobal.h" #include "mozilla/dom/ProcessGlobal.h"
#include "mozilla/dom/PushNotifier.h" #include "mozilla/dom/PushNotifier.h"
#include "mozilla/dom/Storage.h"
#include "mozilla/dom/StorageIPC.h" #include "mozilla/dom/StorageIPC.h"
#include "mozilla/dom/TabGroup.h" #include "mozilla/dom/TabGroup.h"
#include "mozilla/dom/workers/ServiceWorkerManager.h" #include "mozilla/dom/workers/ServiceWorkerManager.h"
@ -3041,20 +3040,6 @@ ContentChild::RecvBlobURLUnregistration(const nsCString& aURI)
return IPC_OK(); 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, true);
return IPC_OK();
}
#if defined(XP_WIN) && defined(ACCESSIBILITY) #if defined(XP_WIN) && defined(ACCESSIBILITY)
bool bool
ContentChild::SendGetA11yContentId() ContentChild::SendGetA11yContentId()

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

@ -394,14 +394,6 @@ public:
virtual mozilla::ipc::IPCResult virtual mozilla::ipc::IPCResult
RecvInitBlobURLs(nsTArray<BlobURLRegistrationData>&& aRegistations) override; 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 RecvLastPrivateDocShellDestroyed() override;
virtual mozilla::ipc::IPCResult RecvFilePathUpdate(const nsString& aStorageType, virtual mozilla::ipc::IPCResult RecvFilePathUpdate(const nsString& aStorageType,

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

@ -51,7 +51,6 @@
#include "mozilla/dom/PContentPermissionRequestParent.h" #include "mozilla/dom/PContentPermissionRequestParent.h"
#include "mozilla/dom/PCycleCollectWithLogsParent.h" #include "mozilla/dom/PCycleCollectWithLogsParent.h"
#include "mozilla/dom/ServiceWorkerRegistrar.h" #include "mozilla/dom/ServiceWorkerRegistrar.h"
#include "mozilla/dom/Storage.h"
#include "mozilla/dom/StorageIPC.h" #include "mozilla/dom/StorageIPC.h"
#include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h" #include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h"
#include "mozilla/dom/power/PowerManagerService.h" #include "mozilla/dom/power/PowerManagerService.h"
@ -4753,25 +4752,6 @@ ContentParent::RecvUnstoreAndBroadcastBlobURLUnregistration(const nsCString& aUR
return IPC_OK(); 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 mozilla::ipc::IPCResult
ContentParent::RecvGetA11yContentId(uint32_t* aContentId) ContentParent::RecvGetA11yContentId(uint32_t* aContentId)
{ {

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

@ -578,14 +578,6 @@ public:
virtual mozilla::ipc::IPCResult virtual mozilla::ipc::IPCResult
RecvUnstoreAndBroadcastBlobURLUnregistration(const nsCString& aURI) override; 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 virtual mozilla::ipc::IPCResult
RecvGetA11yContentId(uint32_t* aContentId) override; RecvGetA11yContentId(uint32_t* aContentId) override;

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

@ -644,12 +644,6 @@ child:
async BlobURLUnregistration(nsCString aURI); async BlobURLUnregistration(nsCString aURI);
async DispatchLocalStorageChange(nsString documentURI,
nsString key,
nsString oldValue,
nsString newValue,
Principal principal,
bool isPrivate);
async GMPsChanged(GMPCapabilityData[] capabilities); async GMPsChanged(GMPCapabilityData[] capabilities);
@ -1159,13 +1153,6 @@ parent:
async UnstoreAndBroadcastBlobURLUnregistration(nsCString url); 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 * Messages for communicating child Telemetry to the parent process
*/ */

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

@ -14,9 +14,6 @@
#include "nsIPrincipal.h" #include "nsIPrincipal.h"
#include "nsICookiePermission.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/StorageBinding.h"
#include "mozilla/dom/StorageEvent.h" #include "mozilla/dom/StorageEvent.h"
#include "mozilla/dom/StorageEventBinding.h" #include "mozilla/dom/StorageEventBinding.h"
@ -28,9 +25,6 @@
#include "nsServiceManagerUtils.h" #include "nsServiceManagerUtils.h"
namespace mozilla { namespace mozilla {
using namespace ipc;
namespace dom { namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Storage, mManager, mPrincipal, mWindow) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Storage, mManager, mPrincipal, mWindow)
@ -64,6 +58,7 @@ Storage::Storage(nsPIDOMWindowInner* aWindow,
Storage::~Storage() Storage::~Storage()
{ {
mCache->KeepAlive();
} }
/* virtual */ JSObject* /* virtual */ JSObject*
@ -217,30 +212,6 @@ void
Storage::BroadcastChangeNotification(const nsSubstring& aKey, Storage::BroadcastChangeNotification(const nsSubstring& aKey,
const nsSubstring& aOldValue, const nsSubstring& aOldValue,
const nsSubstring& aNewValue) 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, false);
}
/* static */ void
Storage::DispatchStorageEvent(StorageType aStorageType,
const nsAString& aDocumentURI,
const nsAString& aKey,
const nsAString& aOldValue,
const nsAString& aNewValue,
nsIPrincipal* aPrincipal,
bool aIsPrivate,
Storage* aStorage,
bool aImmediateDispatch)
{ {
StorageEventInit dict; StorageEventInit dict;
dict.mBubbles = false; dict.mBubbles = false;
@ -248,67 +219,21 @@ Storage::DispatchStorageEvent(StorageType aStorageType,
dict.mKey = aKey; dict.mKey = aKey;
dict.mNewValue = aNewValue; dict.mNewValue = aNewValue;
dict.mOldValue = aOldValue; dict.mOldValue = aOldValue;
dict.mStorageArea = aStorage; dict.mStorageArea = this;
dict.mUrl = aDocumentURI; dict.mUrl = mDocumentURI;
// Note, this DOM event should never reach JS. It is cloned later in // Note, this DOM event should never reach JS. It is cloned later in
// nsGlobalWindow. // nsGlobalWindow.
RefPtr<StorageEvent> event = RefPtr<StorageEvent> event =
StorageEvent::Constructor(nullptr, NS_LITERAL_STRING("storage"), dict); StorageEvent::Constructor(nullptr, NS_LITERAL_STRING("storage"), dict);
event->SetPrincipal(aPrincipal);
RefPtr<StorageNotifierRunnable> r = RefPtr<StorageNotifierRunnable> r =
new StorageNotifierRunnable(event, new StorageNotifierRunnable(event,
aStorageType == LocalStorage GetType() == LocalStorage
? u"localStorage" ? u"localStorage"
: u"sessionStorage", : u"sessionStorage",
aIsPrivate); IsPrivate());
NS_DispatchToMainThread(r);
if (aImmediateDispatch) {
Unused << r->Run();
} else {
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"; static const char kPermissionType[] = "cookie";

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

@ -24,7 +24,6 @@ namespace dom {
class StorageManagerBase; class StorageManagerBase;
class StorageCache; class StorageCache;
class StorageEvent;
class Storage final class Storage final
: public nsIDOMStorage : public nsIDOMStorage
@ -130,28 +129,6 @@ public:
return mCache == aOther->mCache; return mCache == aOther->mCache;
} }
// aStorage can be null if this method is called by ContentChild.
//
// aImmediateDispatch is for use by (main-thread) IPC code so that PContent
// ordering can be maintained. Without this, the event would be enqueued and
// run in a future turn of the event loop, potentially allowing other PContent
// Recv* methods to trigger script that wants to assume our localstorage
// changes have already been applied. This is the case for message manager
// messages which are used by ContentTask testing logic and webextensions.
static void
DispatchStorageEvent(StorageType aStorageType,
const nsAString& aDocumentURI,
const nsAString& aKey,
const nsAString& aOldValue,
const nsAString& aNewValue,
nsIPrincipal* aPrincipal,
bool aIsPrivate,
Storage* aStorage,
bool aImmediateDispatch);
void
ApplyEvent(StorageEvent* aStorageEvent);
protected: protected:
// The method checks whether the caller can use a storage. // The method checks whether the caller can use a storage.
// CanUseStorage is called before any DOM initiated operation // CanUseStorage is called before any DOM initiated operation

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

@ -59,7 +59,7 @@ GetDataSetIndex(const Storage* aStorage)
NS_IMPL_ADDREF(StorageCacheBridge) 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 // method to void to make implementation of asynchronous StorageCache::Release
// much simpler. // much simpler.
NS_IMETHODIMP_(void) StorageCacheBridge::Release(void) 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 // Check the quota string has (or has not) the identical origin suffix as
// this storage cache is bound to. // this storage cache is bound to.
MOZ_ASSERT(StringBeginsWith(mQuotaOriginScope, mOriginSuffix)); MOZ_ASSERT(StringBeginsWith(mQuotaOriginScope, mOriginSuffix));
MOZ_ASSERT(mOriginSuffix.IsEmpty() != StringBeginsWith(mQuotaOriginScope, MOZ_ASSERT(mOriginSuffix.IsEmpty() != StringBeginsWith(mQuotaOriginScope,
NS_LITERAL_CSTRING("^"))); NS_LITERAL_CSTRING("^")));
mUsage = aManager->GetOriginUsage(mQuotaOriginScope); mUsage = aManager->GetOriginUsage(mQuotaOriginScope);
@ -198,33 +198,28 @@ StorageCache::DataSet(const Storage* aStorage)
} }
bool bool
StorageCache::ProcessUsageDelta(const Storage* aStorage, int64_t aDelta, StorageCache::ProcessUsageDelta(const Storage* aStorage, int64_t aDelta)
const MutationSource aSource)
{ {
return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta, aSource); return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta);
} }
bool bool
StorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta, StorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta)
const MutationSource aSource)
{ {
// Check if we are in a low disk space situation // Check if we are in a low disk space situation
if (aSource == ContentMutation && if (aDelta > 0 && mManager && mManager->IsLowDiskSpace()) {
aDelta > 0 && mManager && mManager->IsLowDiskSpace()) {
return false; return false;
} }
// Check limit per this origin // Check limit per this origin
Data& data = mData[aGetDataSetIndex]; Data& data = mData[aGetDataSetIndex];
uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta; uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta;
if (aSource == ContentMutation && if (aDelta > 0 && newOriginUsage > StorageManagerBase::GetQuota()) {
aDelta > 0 && newOriginUsage > StorageManagerBase::GetQuota()) {
return false; return false;
} }
// Now check eTLD+1 limit // Now check eTLD+1 limit
if (mUsage && if (mUsage && !mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta)) {
!mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta, aSource)) {
return false; return false;
} }
@ -251,6 +246,60 @@ StorageCache::Preload()
namespace { 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, // 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. // i.e. compile time known ID, but here we know the ID only at run time.
// Hence a new class. // Hence a new class.
@ -390,8 +439,7 @@ StorageCache::GetItem(const Storage* aStorage, const nsAString& aKey,
nsresult nsresult
StorageCache::SetItem(const Storage* aStorage, const nsAString& aKey, StorageCache::SetItem(const Storage* aStorage, const nsAString& aKey,
const nsString& aValue, nsString& aOld, const nsString& aValue, nsString& aOld)
const MutationSource aSource)
{ {
// Size of the cache that will change after this action. // Size of the cache that will change after this action.
int64_t delta = 0; int64_t delta = 0;
@ -414,7 +462,7 @@ StorageCache::SetItem(const Storage* aStorage, const nsAString& aKey,
delta += static_cast<int64_t>(aValue.Length()) - delta += static_cast<int64_t>(aValue.Length()) -
static_cast<int64_t>(aOld.Length()); static_cast<int64_t>(aOld.Length());
if (!ProcessUsageDelta(aStorage, delta, aSource)) { if (!ProcessUsageDelta(aStorage, delta)) {
return NS_ERROR_DOM_QUOTA_REACHED; return NS_ERROR_DOM_QUOTA_REACHED;
} }
@ -424,7 +472,7 @@ StorageCache::SetItem(const Storage* aStorage, const nsAString& aKey,
data.mKeys.Put(aKey, aValue); data.mKeys.Put(aKey, aValue);
if (aSource == ContentMutation && Persist(aStorage)) { if (Persist(aStorage)) {
if (!sDatabase) { if (!sDatabase) {
NS_ERROR("Writing to localStorage after the database has been shut down" NS_ERROR("Writing to localStorage after the database has been shut down"
", data lose!"); ", data lose!");
@ -443,7 +491,7 @@ StorageCache::SetItem(const Storage* aStorage, const nsAString& aKey,
nsresult nsresult
StorageCache::RemoveItem(const Storage* aStorage, const nsAString& aKey, StorageCache::RemoveItem(const Storage* aStorage, const nsAString& aKey,
nsString& aOld, const MutationSource aSource) nsString& aOld)
{ {
if (Persist(aStorage)) { if (Persist(aStorage)) {
WaitForPreload(Telemetry::LOCALDOMSTORAGE_REMOVEKEY_BLOCKING_MS); WaitForPreload(Telemetry::LOCALDOMSTORAGE_REMOVEKEY_BLOCKING_MS);
@ -461,10 +509,10 @@ StorageCache::RemoveItem(const Storage* aStorage, const nsAString& aKey,
// Recalculate the cached data size // Recalculate the cached data size
const int64_t delta = -(static_cast<int64_t>(aOld.Length()) + const int64_t delta = -(static_cast<int64_t>(aOld.Length()) +
static_cast<int64_t>(aKey.Length())); static_cast<int64_t>(aKey.Length()));
Unused << ProcessUsageDelta(aStorage, delta, aSource); Unused << ProcessUsageDelta(aStorage, delta);
data.mKeys.Remove(aKey); data.mKeys.Remove(aKey);
if (aSource == ContentMutation && Persist(aStorage)) { if (Persist(aStorage)) {
if (!sDatabase) { if (!sDatabase) {
NS_ERROR("Writing to localStorage after the database has been shut down" NS_ERROR("Writing to localStorage after the database has been shut down"
", data lose!"); ", data lose!");
@ -478,7 +526,7 @@ StorageCache::RemoveItem(const Storage* aStorage, const nsAString& aKey,
} }
nsresult nsresult
StorageCache::Clear(const Storage* aStorage, const MutationSource aSource) StorageCache::Clear(const Storage* aStorage)
{ {
bool refresh = false; bool refresh = false;
if (Persist(aStorage)) { if (Persist(aStorage)) {
@ -500,11 +548,11 @@ StorageCache::Clear(const Storage* aStorage, const MutationSource aSource)
bool hadData = !!data.mKeys.Count(); bool hadData = !!data.mKeys.Count();
if (hadData) { if (hadData) {
Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage, aSource); Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage);
data.mKeys.Clear(); data.mKeys.Clear();
} }
if (aSource == ContentMutation && Persist(aStorage) && (refresh || hadData)) { if (Persist(aStorage) && (refresh || hadData)) {
if (!sDatabase) { if (!sDatabase) {
NS_ERROR("Writing to localStorage after the database has been shut down" NS_ERROR("Writing to localStorage after the database has been shut down"
", data lose!"); ", data lose!");
@ -619,6 +667,9 @@ StorageCache::LoadItem(const nsAString& aKey, const nsString& aValue)
void void
StorageCache::LoadDone(nsresult aRv) StorageCache::LoadDone(nsresult aRv)
{ {
// Keep the preloaded cache alive for a time
KeepAlive();
MonitorAutoLock monitor(mMonitor); MonitorAutoLock monitor(mMonitor);
mLoadResult = aRv; mLoadResult = aRv;
mLoaded = true; mLoaded = true;
@ -679,13 +730,12 @@ StorageUsage::LoadUsage(const int64_t aUsage)
bool bool
StorageUsage::CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, StorageUsage::CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex,
const int64_t aDelta, const StorageCache::MutationSource aSource) const int64_t aDelta)
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
int64_t newUsage = mUsage[aDataSetIndex] + aDelta; int64_t newUsage = mUsage[aDataSetIndex] + aDelta;
if (aSource == StorageCache::ContentMutation && if (aDelta > 0 && newUsage > StorageManagerBase::GetQuota()) {
aDelta > 0 && newUsage > StorageManagerBase::GetQuota()) {
return false; return false;
} }

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

@ -78,24 +78,8 @@ class StorageCache : public StorageCacheBridge
public: public:
NS_IMETHOD_(void) Release(void); NS_IMETHOD_(void) Release(void);
enum MutationSource { // Note: We pass aOriginNoSuffix through the ctor here, because
// The mutation is a result of an explicit JS mutation in this process. // StorageCacheHashKey's ctor is creating this class and
// 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
// accepts reversed-origin-no-suffix as an argument - the hashing key. // accepts reversed-origin-no-suffix as an argument - the hashing key.
explicit StorageCache(const nsACString* aOriginNoSuffix); explicit StorageCache(const nsACString* aOriginNoSuffix);
@ -125,13 +109,10 @@ public:
nsresult GetItem(const Storage* aStorage, const nsAString& aKey, nsresult GetItem(const Storage* aStorage, const nsAString& aKey,
nsAString& aRetval); nsAString& aRetval);
nsresult SetItem(const Storage* aStorage, const nsAString& aKey, nsresult SetItem(const Storage* aStorage, const nsAString& aKey,
const nsString& aValue, nsString& aOld, const nsString& aValue, nsString& aOld);
const MutationSource aSource=ContentMutation);
nsresult RemoveItem(const Storage* aStorage, const nsAString& aKey, nsresult RemoveItem(const Storage* aStorage, const nsAString& aKey,
nsString& aOld, nsString& aOld);
const MutationSource aSource=ContentMutation); nsresult Clear(const Storage* aStorage);
nsresult Clear(const Storage* aStorage,
const MutationSource aSource=ContentMutation);
void GetKeys(const Storage* aStorage, nsTArray<nsString>& aKeys); 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. // 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. // 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 bool ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta);
// if the change would put us over quota. This is done to ensure coherency of bool ProcessUsageDelta(const Storage* aStorage, const int64_t aDelta);
// 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);
private: private:
// When a cache is reponsible for its life time (in case of localStorage data // 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). // Obtained from the manager during initialization (Init method).
RefPtr<StorageUsage> mUsage; 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 // Principal the cache has been initially created for, this is used only for
// sessionStorage access checks since sessionStorage objects are strictly // sessionStorage access checks since sessionStorage objects are strictly
// scoped by a principal. localStorage objects on the other hand are scoped // scoped by a principal. localStorage objects on the other hand are scoped
@ -237,7 +211,7 @@ private:
// The origin attributes suffix // The origin attributes suffix
nsCString mOriginSuffix; 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. // and contains the origin attributes suffix.
nsCString mQuotaOriginScope; nsCString mQuotaOriginScope;
@ -303,8 +277,7 @@ class StorageUsage : public StorageUsageBridge
public: public:
explicit StorageUsage(const nsACString& aOriginScope); explicit StorageUsage(const nsACString& aOriginScope);
bool CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, int64_t aUsageDelta, bool CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, int64_t aUsageDelta);
const StorageCache::MutationSource aSource);
private: private:
virtual const nsCString& OriginScope() { return mOriginScope; } virtual const nsCString& OriginScope() { return mOriginScope; }

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

@ -224,12 +224,6 @@ StorageDBChild::RecvObserve(const nsCString& aTopic,
mozilla::ipc::IPCResult mozilla::ipc::IPCResult
StorageDBChild::RecvOriginsHavingData(nsTArray<nsCString>&& aOrigins) 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) { for (uint32_t i = 0; i < aOrigins.Length(); ++i) {
OriginsHavingData().PutEntry(aOrigins[i]); OriginsHavingData().PutEntry(aOrigins[i]);
} }

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

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

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

@ -90,17 +90,8 @@ private:
const nsACString& aOriginNoSuffix, const nsACString& aOriginNoSuffix,
nsIPrincipal* aPrincipal); 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 // Helper for creation of DOM storage objects
nsresult GetStorageInternal(CreateMode aCreate, nsresult GetStorageInternal(bool aCreate,
mozIDOMWindow* aWindow, mozIDOMWindow* aWindow,
nsIPrincipal* aPrincipal, nsIPrincipal* aPrincipal,
const nsAString& aDocumentURI, const nsAString& aDocumentURI,

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

@ -2,7 +2,6 @@
support-files = support-files =
browser_frame_elements.html browser_frame_elements.html
page_privatestorageevent.html page_privatestorageevent.html
page_localstorage_e10s.html
position.html position.html
test-console-api.html test-console-api.html
test_bug1004814.html test_bug1004814.html
@ -41,8 +40,6 @@ skip-if = e10s
skip-if = !e10s || os != "win" || processor != "x86" # Large-Allocation requires e10s skip-if = !e10s || os != "win" || processor != "x86" # Large-Allocation requires e10s
[browser_largeAllocation_non_win32.js] [browser_largeAllocation_non_win32.js]
skip-if = !e10s || (os == "win" && processor == "x86") # Large-Allocation requires e10s skip-if = !e10s || (os == "win" && processor == "x86") # Large-Allocation requires e10s
[browser_localStorage_e10s.js]
skip-if = !e10s # This is a test of e10s functionality.
[browser_localStorage_privatestorageevent.js] [browser_localStorage_privatestorageevent.js]
[browser_test__content.js] [browser_test__content.js]
[browser_test_new_window_from_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.waitForExplicitFinish();
SimpleTest.waitForFocus(function() { SimpleTest.waitForFocus(function() {
SpecialPowers.pushPrefEnv({"set":[["middlemouse.paste", true]]}, startTests); SpecialPowers.pushPrefEnv({"set":[["middlemouse.paste", true], ["dom.ipc.processCount", 1]]}, startTests);
}); });
function startTests() { function startTests() {