зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
6a8dda71e3
Коммит
85843f4cff
|
@ -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() {
|
||||
|
|
Загрузка…
Ссылка в новой задаче