Bug 1285898 - [e10s-multi] Localstorage "storage" event is not fired with multiple content processes. r=asuth

--HG--
extra : rebase_source : 724b38c52139dee1c6f746152fefff05333dfc24
extra : intermediate-source : e56108122b9088555f29a28e6086d4d117a68c4e
extra : source : 6681b50c1f6d0d2d22d5f631234402e020c0b78a
This commit is contained in:
Andrea Marchesini 2017-02-19 22:16:48 -05:00
Родитель 09858c1716
Коммит d32aa97041
13 изменённых файлов: 246 добавлений и 147 удалений

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

@ -2179,7 +2179,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWakeLock)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleRequestExecutor)
for (IdleRequest* request : tmp->mIdleRequestCallbacks) {
@ -2260,7 +2259,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleService)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWakeLock)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingStorageEvents)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleObservers)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads)
@ -11904,49 +11902,66 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
bool isPrivateBrowsing = IsPrivateBrowsing();
if ((!nsCRT::strcmp(aTopic, "dom-storage2-changed") && !isPrivateBrowsing) ||
(!nsCRT::strcmp(aTopic, "dom-private-storage2-changed") && isPrivateBrowsing)) {
if (!IsInnerWindow() || !AsInner()->IsCurrentInnerWindow()) {
// We need these for our private-browsing check below; so save them off.
// (And we can't do the private-browsing enforcement check as part of our
// outer conditional because otherwise we'll trigger an NS_WARNING if control
// flow reaches the bottom of this method.)
bool isNonPrivateLocalStorageChange =
!nsCRT::strcmp(aTopic, "dom-storage2-changed");
bool isPrivateLocalStorageChange =
!nsCRT::strcmp(aTopic, "dom-private-storage2-changed");
if (isNonPrivateLocalStorageChange || isPrivateLocalStorageChange) {
// 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).
bool isPrivateBrowsing = IsPrivateBrowsing();
if ((isNonPrivateLocalStorageChange && isPrivateBrowsing) ||
(isPrivateLocalStorageChange && !isPrivateBrowsing)) {
return NS_OK;
}
nsIPrincipal *principal;
nsresult rv;
// We require that aData be either u"SessionStorage" or u"localStorage".
// Assert under debug, but ignore the bogus event under non-debug.
MOZ_ASSERT(aData);
if (!aData) {
return NS_ERROR_INVALID_ARG;
}
// LocalStorage can only exist on an inner window, and we don't want to
// generate events on frozen or otherwise-navigated-away from windows.
// (Actually, this code used to try and buffer events for frozen windows,
// but it never worked, so we've removed it. See bug 1285898.)
if (!IsInnerWindow() || !AsInner()->IsCurrentInnerWindow() || IsFrozen()) {
return NS_OK;
}
nsIPrincipal *principal = GetPrincipal();
if (!principal) {
return NS_OK;
}
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 (changingStorage->IsPrivate() != IsPrivateBrowsing()) {
return NS_OK;
}
if (!NS_strcmp(aData, u"sessionStorage")) {
nsCOMPtr<nsIDOMStorage> changingStorage = event->GetStorageArea();
MOZ_ASSERT(changingStorage);
switch (changingStorage->GetType())
{
case Storage::SessionStorage:
{
bool check = false;
nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(GetDocShell());
if (storageManager) {
rv = storageManager->CheckStorage(principal, istorage, &check);
nsresult rv = storageManager->CheckStorage(principal, changingStorage,
&check);
if (NS_FAILED(rv)) {
return rv;
}
@ -11967,58 +11982,48 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
if (fireMozStorageChanged) {
eventType.AssignLiteral("MozSessionStorageChanged");
}
break;
}
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();
else {
MOZ_ASSERT(!NS_strcmp(aData, u"localStorage"));
nsIPrincipal* storagePrincipal = event->GetPrincipal();
if (!storagePrincipal) {
return NS_OK;
}
bool equals = false;
rv = storagePrincipal->Equals(principal, &equals);
nsresult 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> newEvent = CloneStorageEvent(eventType, event, error);
RefPtr<StorageEvent> clonedEvent =
CloneStorageEvent(eventType, event, error);
if (error.Failed()) {
return error.StealNSResult();
}
newEvent->SetTrusted(true);
clonedEvent->SetTrusted(true);
if (fireMozStorageChanged) {
WidgetEvent* internalEvent = newEvent->WidgetEventPtr();
WidgetEvent* internalEvent = clonedEvent->WidgetEventPtr();
internalEvent->mFlags.mOnlyChromeDispatch = true;
}
if (IsFrozen()) {
// This window is frozen, rather than firing the events here,
// store the domain in which the change happened and fire the
// events if we're ever thawed.
mPendingStorageEvents.AppendElement(newEvent);
return NS_OK;
}
bool defaultActionEnabled;
DispatchEvent(newEvent, &defaultActionEnabled);
DispatchEvent(clonedEvent, &defaultActionEnabled);
return NS_OK;
}
@ -12109,10 +12114,19 @@ nsGlobalWindow::CloneStorageEvent(const nsAString& aType,
aEvent->GetUrl(dict.mUrl);
RefPtr<Storage> storageArea = aEvent->GetStorageArea();
MOZ_ASSERT(storageArea);
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);
} else {
MOZ_ASSERT(storageArea->GetType() == Storage::SessionStorage);
@ -12124,7 +12138,7 @@ nsGlobalWindow::CloneStorageEvent(const nsAString& aType,
}
MOZ_ASSERT(storage);
MOZ_ASSERT(storage->IsForkOf(storageArea));
MOZ_ASSERT_IF(storageArea, storage->IsForkOf(storageArea));
dict.mStorageArea = storage;
@ -12417,11 +12431,6 @@ nsGlobalWindow::FireDelayedDOMEvents()
{
FORWARD_TO_INNER(FireDelayedDOMEvents, (), NS_ERROR_UNEXPECTED);
for (uint32_t i = 0, len = mPendingStorageEvents.Length(); i < len; ++i) {
Observe(mPendingStorageEvents[i], "dom-storage2-changed", nullptr);
Observe(mPendingStorageEvents[i], "dom-private-storage2-changed", nullptr);
}
if (mApplicationCache) {
static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->FirePendingEvents();
}
@ -13291,6 +13300,13 @@ 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

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

@ -1943,9 +1943,6 @@ protected:
// These member variables are used on both inner and the outer windows.
nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
typedef nsTArray<RefPtr<mozilla::dom::StorageEvent>> nsStorageEventArray;
nsStorageEventArray mPendingStorageEvents;
uint32_t mSuspendDepth;
uint32_t mFreezeDepth;

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

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

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

@ -34,6 +34,7 @@
#include "mozilla/dom/MemoryReportRequest.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"
@ -3035,6 +3036,20 @@ 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()

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

@ -391,6 +391,14 @@ 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,

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

@ -51,6 +51,7 @@
#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"
@ -4768,6 +4769,25 @@ 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)
{

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

@ -574,6 +574,14 @@ 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;

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

@ -676,6 +676,12 @@ child:
async BlobURLUnregistration(nsCString aURI);
async DispatchLocalStorageChange(nsString documentURI,
nsString key,
nsString oldValue,
nsString newValue,
Principal principal,
bool isPrivate);
async GMPsChanged(GMPCapabilityData[] capabilities);
@ -1176,6 +1182,13 @@ 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,6 +14,9 @@
#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"
@ -25,6 +28,9 @@
#include "nsServiceManagerUtils.h"
namespace mozilla {
using namespace ipc;
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Storage, mManager, mPrincipal, mWindow)
@ -58,7 +64,6 @@ Storage::Storage(nsPIDOMWindowInner* aWindow,
Storage::~Storage()
{
mCache->KeepAlive();
}
/* virtual */ JSObject*
@ -215,6 +220,29 @@ 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;
@ -222,21 +250,62 @@ Storage::BroadcastChangeNotification(const nsSubstring& aKey,
dict.mKey = aKey;
dict.mNewValue = aNewValue;
dict.mOldValue = aOldValue;
dict.mStorageArea = this;
dict.mUrl = mDocumentURI;
dict.mStorageArea = aStorage;
dict.mUrl = aDocumentURI;
// 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,
GetType() == LocalStorage
aStorageType == LocalStorage
? u"localStorage"
: u"sessionStorage",
IsPrivate());
aIsPrivate);
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);
return;
}
// No new value means removing the key.
if (value.IsVoid()) {
mCache->RemoveItem(this, key, old);
return;
}
// Otherwise, we set the new value.
mCache->SetItem(this, key, value, old);
}
static const char kPermissionType[] = "cookie";

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

@ -24,6 +24,7 @@ namespace dom {
class StorageManagerBase;
class StorageCache;
class StorageEvent;
class Storage final
: public nsIDOMStorage
@ -129,6 +130,20 @@ 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

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

@ -246,73 +246,6 @@ 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, public nsINamed
{
virtual ~StorageCacheHolder() {}
NS_DECL_ISUPPORTS
NS_IMETHOD
Notify(nsITimer* aTimer) override
{
mCache = nullptr;
return NS_OK;
}
NS_IMETHOD
GetName(nsACString& aName) override
{
aName.AssignASCII("StorageCacheHolder_timer");
return NS_OK;
}
NS_IMETHOD
SetName(const char* aName) override
{
return NS_ERROR_NOT_IMPLEMENTED;
}
RefPtr<StorageCache> mCache;
public:
explicit StorageCacheHolder(StorageCache* aCache) : mCache(aCache) {}
};
NS_IMPL_ISUPPORTS(StorageCacheHolder, nsITimerCallback, nsINamed)
} // 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.
@ -680,9 +613,6 @@ 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;

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

@ -96,10 +96,6 @@ public:
// Starts async preload of this cache if it persistent and not loaded.
void Preload();
// Keeps the cache alive (i.e. present in the manager's hash table) for a
// time.
void KeepAlive();
// The set of methods that are invoked by DOM storage web API.
// We are passing the Storage object just to let the cache
// read properties like mPrivate, mPrincipal and mSessionOnly.
@ -196,9 +192,6 @@ 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

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

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