зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to m-i
This commit is contained in:
Коммит
6bfdccc26e
|
@ -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
|
||||||
|
|
|
@ -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,22 +78,6 @@ class StorageCache : public StorageCacheBridge
|
||||||
public:
|
public:
|
||||||
NS_IMETHOD_(void) Release(void);
|
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
|
// Note: We pass aOriginNoSuffix through the ctor here, because
|
||||||
// StorageCacheHashKey's ctor is creating this class and
|
// 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.
|
||||||
|
@ -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
|
||||||
|
@ -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() {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче