Bug 1333981 - Introduce StorageNotifierService for the dispatching of StorageEvent using the correct mainthread event target, r=billm

This commit is contained in:
Andrea Marchesini 2017-07-06 19:35:33 +02:00
Родитель 50e4c4c5da
Коммит 68ee090884
7 изменённых файлов: 342 добавлений и 148 удалений

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

@ -23,6 +23,7 @@
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/StorageEvent.h"
#include "mozilla/dom/StorageEventBinding.h"
#include "mozilla/dom/StorageNotifierService.h"
#include "mozilla/dom/Timeout.h"
#include "mozilla/dom/TimeoutHandler.h"
#include "mozilla/dom/TimeoutManager.h"
@ -464,8 +465,9 @@ static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID);
* An indirect observer object that means we don't have to implement nsIObserver
* on nsGlobalWindow, where any script could see it.
*/
class nsGlobalWindowObserver final : public nsIObserver,
public nsIInterfaceRequestor
class nsGlobalWindowObserver final : public nsIObserver
, public nsIInterfaceRequestor
, public StorageNotificationObserver
{
public:
explicit nsGlobalWindowObserver(nsGlobalWindow* aWindow) : mWindow(aWindow) {}
@ -485,6 +487,23 @@ public:
return NS_NOINTERFACE;
}
void
ObserveStorageNotification(StorageEvent* aEvent,
const char16_t* aStorageType,
bool aPrivateBrowsing) override
{
if (mWindow) {
mWindow->ObserveStorageNotification(aEvent, aStorageType,
aPrivateBrowsing);
}
}
virtual nsIEventTarget*
GetEventTarget() const override
{
return mWindow ? mWindow->EventTargetFor(TaskCategory::Other) : nullptr;
}
private:
~nsGlobalWindowObserver() = default;
@ -1631,14 +1650,16 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow)
// a strong reference.
os->AddObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
false);
// Watch for dom-storage2-changed and dom-private-storage2-changed so we
// can fire storage events. Use a strong reference.
os->AddObserver(mObserver, "dom-storage2-changed", false);
os->AddObserver(mObserver, "dom-private-storage2-changed", false);
}
Preferences::AddStrongObserver(mObserver, "intl.accept_languages");
// Watch for storage notifications so we can fire storage events.
RefPtr<StorageNotifierService> sns =
StorageNotifierService::GetOrCreate();
if (sns) {
sns->Register(mObserver);
}
}
} else {
// |this| is an outer window. Outer windows start out frozen and
@ -1934,8 +1955,11 @@ nsGlobalWindow::CleanUp()
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
os->RemoveObserver(mObserver, "dom-storage2-changed");
os->RemoveObserver(mObserver, "dom-private-storage2-changed");
}
RefPtr<StorageNotifierService> sns = StorageNotifierService::GetOrCreate();
if (sns) {
sns->Unregister(mObserver);
}
#ifdef MOZ_B2G
@ -12269,131 +12293,6 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
// 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;
}
// 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;
}
bool fireMozStorageChanged = false;
nsAutoString eventType;
eventType.AssignLiteral("storage");
if (!NS_strcmp(aData, u"sessionStorage")) {
nsCOMPtr<nsIDOMStorage> changingStorage = event->GetStorageArea();
MOZ_ASSERT(changingStorage);
bool check = false;
nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(GetDocShell());
if (storageManager) {
nsresult rv = storageManager->CheckStorage(principal, changingStorage,
&check);
if (NS_FAILED(rv)) {
return rv;
}
}
if (!check) {
// This storage event is not coming from our storage or is coming
// from a different docshell, i.e. it is a clone, ignore this event.
return NS_OK;
}
MOZ_LOG(gDOMLeakPRLog, LogLevel::Debug,
("nsGlobalWindow %p with sessionStorage %p passing event from %p",
this, mSessionStorage.get(), changingStorage.get()));
fireMozStorageChanged = mSessionStorage == changingStorage;
if (fireMozStorageChanged) {
eventType.AssignLiteral("MozSessionStorageChanged");
}
}
else {
MOZ_ASSERT(!NS_strcmp(aData, u"localStorage"));
nsIPrincipal* storagePrincipal = event->GetPrincipal();
if (!storagePrincipal) {
return NS_OK;
}
bool equals = false;
nsresult rv = storagePrincipal->Equals(principal, &equals);
NS_ENSURE_SUCCESS(rv, rv);
if (!equals) {
return NS_OK;
}
fireMozStorageChanged = mLocalStorage == event->GetStorageArea();
if (fireMozStorageChanged) {
eventType.AssignLiteral("MozLocalStorageChanged");
}
}
// 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);
if (error.Failed()) {
return error.StealNSResult();
}
clonedEvent->SetTrusted(true);
if (fireMozStorageChanged) {
WidgetEvent* internalEvent = clonedEvent->WidgetEventPtr();
internalEvent->mFlags.mOnlyChromeDispatch = true;
}
bool defaultActionEnabled;
DispatchEvent(clonedEvent, &defaultActionEnabled);
return NS_OK;
}
if (!nsCRT::strcmp(aTopic, "offline-cache-update-added")) {
if (mApplicationCache)
return NS_OK;
@ -12463,6 +12362,114 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
return NS_ERROR_FAILURE;
}
void
nsGlobalWindow::ObserveStorageNotification(StorageEvent* aEvent,
const char16_t* aStorageType,
bool aPrivateBrowsing)
{
MOZ_ASSERT(aEvent);
// 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 (isPrivateBrowsing != aPrivateBrowsing) {
return;
}
// 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;
}
nsIPrincipal *principal = GetPrincipal();
if (!principal) {
return;
}
bool fireMozStorageChanged = false;
nsAutoString eventType;
eventType.AssignLiteral("storage");
if (!NS_strcmp(aStorageType, u"sessionStorage")) {
nsCOMPtr<nsIDOMStorage> changingStorage = aEvent->GetStorageArea();
MOZ_ASSERT(changingStorage);
bool check = false;
nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(GetDocShell());
if (storageManager) {
nsresult rv = storageManager->CheckStorage(principal, changingStorage,
&check);
if (NS_FAILED(rv)) {
return;
}
}
if (!check) {
// This storage event is not coming from our storage or is coming
// from a different docshell, i.e. it is a clone, ignore this event.
return;
}
MOZ_LOG(gDOMLeakPRLog, LogLevel::Debug,
("nsGlobalWindow %p with sessionStorage %p passing event from %p",
this, mSessionStorage.get(), changingStorage.get()));
fireMozStorageChanged = mSessionStorage == changingStorage;
if (fireMozStorageChanged) {
eventType.AssignLiteral("MozSessionStorageChanged");
}
}
else {
MOZ_ASSERT(!NS_strcmp(aStorageType, u"localStorage"));
nsIPrincipal* storagePrincipal = aEvent->GetPrincipal();
if (!storagePrincipal) {
return;
}
bool equals = false;
nsresult rv = storagePrincipal->Equals(principal, &equals);
NS_ENSURE_SUCCESS_VOID(rv);
if (!equals) {
return;
}
fireMozStorageChanged = mLocalStorage == aEvent->GetStorageArea();
if (fireMozStorageChanged) {
eventType.AssignLiteral("MozLocalStorageChanged");
}
}
// Clone the storage event included in the observer notification. We want
// to dispatch clones rather than the original event.
IgnoredErrorResult error;
RefPtr<StorageEvent> clonedEvent =
CloneStorageEvent(eventType, aEvent, error);
if (error.Failed()) {
return;
}
clonedEvent->SetTrusted(true);
if (fireMozStorageChanged) {
WidgetEvent* internalEvent = clonedEvent->WidgetEventPtr();
internalEvent->mFlags.mOnlyChromeDispatch = true;
}
bool defaultActionEnabled;
DispatchEvent(clonedEvent, &defaultActionEnabled);
}
already_AddRefed<StorageEvent>
nsGlobalWindow::CloneStorageEvent(const nsAString& aType,
const RefPtr<StorageEvent>& aEvent,

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

@ -611,6 +611,10 @@ public:
nsresult Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData);
void ObserveStorageNotification(mozilla::dom::StorageEvent* aEvent,
const char16_t* aStorageType,
bool aPrivateBrowsing);
// Outer windows only.
void UnblockScriptedClosing();

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

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Storage.h"
#include "StorageNotifierService.h"
#include "mozilla/dom/StorageBinding.h"
#include "nsIPrincipal.h"
@ -121,13 +122,21 @@ Storage::NotifyChange(Storage* aStorage, nsIPrincipal* aPrincipal,
event->SetPrincipal(aPrincipal);
// This will send the event to any registered window.
StorageNotifierService::Broadcast(event, aStorageType, aIsPrivate,
aImmediateDispatch);
// This runnable is mainly used by devtools. Windows receive notification by
// StorageNotifierService.
RefPtr<StorageNotifierRunnable> r =
new StorageNotifierRunnable(event, aStorageType, aIsPrivate);
if (aImmediateDispatch) {
Unused << r->Run();
} else {
NS_DispatchToMainThread(r, NS_DISPATCH_NORMAL);
SystemGroup::Dispatch("Storage::NotifyChange", TaskCategory::Other,
r.forget());
}
}

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

@ -0,0 +1,110 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "StorageNotifierService.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/StaticPtr.h"
namespace mozilla {
namespace dom {
namespace {
// This boolean is used to avoid the creation of the service after been
// distroyed on shutdown.
bool gStorageShuttingDown = false;
StaticRefPtr<StorageNotifierService> gStorageNotifierService;
} // anonymous
/* static */ StorageNotifierService*
StorageNotifierService::GetOrCreate()
{
MOZ_ASSERT(NS_IsMainThread());
if (!gStorageNotifierService && !gStorageShuttingDown) {
gStorageNotifierService = new StorageNotifierService();
ClearOnShutdown(&gStorageNotifierService);
}
return gStorageNotifierService;
}
StorageNotifierService::StorageNotifierService()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!gStorageNotifierService);
}
StorageNotifierService::~StorageNotifierService()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!gStorageNotifierService);
gStorageShuttingDown = true;
}
/* static */ void
StorageNotifierService::Broadcast(StorageEvent* aEvent,
const char16_t* aStorageType,
bool aPrivateBrowsing,
bool aImmediateDispatch)
{
MOZ_ASSERT(NS_IsMainThread());
RefPtr<StorageNotifierService> service = gStorageNotifierService;
if (!service) {
return;
}
RefPtr<StorageEvent> event = aEvent;
nsTObserverArray<RefPtr<StorageNotificationObserver>>::ForwardIterator
iter(service->mObservers);
while (iter.HasMore()) {
RefPtr<StorageNotificationObserver> observer = iter.GetNext();
RefPtr<Runnable> r = NS_NewRunnableFunction(
"StorageNotifierService::Broadcast",
[observer, event, aStorageType, aPrivateBrowsing] () {
observer->ObserveStorageNotification(event, aStorageType, aPrivateBrowsing);
});
if (aImmediateDispatch) {
r->Run();
} else {
nsCOMPtr<nsIEventTarget> et = observer->GetEventTarget();
if (et) {
et->Dispatch(r.forget());
}
}
}
}
void
StorageNotifierService::Register(StorageNotificationObserver* aObserver)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aObserver);
MOZ_ASSERT(!mObservers.Contains(aObserver));
mObservers.AppendElement(aObserver);
}
void
StorageNotifierService::Unregister(StorageNotificationObserver* aObserver)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aObserver);
// No assertion about mObservers containing aObserver because window calls
// this method multiple times.
mObservers.RemoveElement(aObserver);
}
} // namespace dom
} // namespace mozilla

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

@ -0,0 +1,59 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_StorageNotifierService_h
#define mozilla_dom_StorageNotifierService_h
class nsPIDOMWindowInner;
namespace mozilla {
namespace dom {
class StorageEvent;
class StorageNotificationObserver
{
public:
NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
virtual void
ObserveStorageNotification(StorageEvent* aEvent,
const char16_t* aStorageType,
bool aPrivateBrowsing) = 0;
virtual nsIEventTarget*
GetEventTarget() const = 0;
};
class StorageNotifierService final
{
public:
NS_INLINE_DECL_REFCOUNTING(StorageNotifierService)
static StorageNotifierService*
GetOrCreate();
static void
Broadcast(StorageEvent* aEvent, const char16_t* aStorageType,
bool aPrivateBrowsing, bool aImmediateDispatch);
void
Register(StorageNotificationObserver* aObserver);
void
Unregister(StorageNotificationObserver* aObserver);
private:
StorageNotifierService();
~StorageNotifierService();
nsTObserverArray<RefPtr<StorageNotificationObserver>> mObservers;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_StorageNotifierService_h

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

@ -13,6 +13,7 @@ EXPORTS.mozilla.dom += [
'SessionStorageManager.h',
'Storage.h',
'StorageIPC.h',
'StorageNotifierService.h',
]
UNIFIED_SOURCES += [
@ -26,6 +27,7 @@ UNIFIED_SOURCES += [
'StorageDBThread.cpp',
'StorageDBUpdater.cpp',
'StorageIPC.cpp',
'StorageNotifierService.cpp',
'StorageObserver.cpp',
'StorageUtils.cpp',
]

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

@ -40,19 +40,21 @@ function* Tests()
is(sessionStorage.length, 0, "Session storage is empty [1]");
is(localStorage.length, 0, "Local storage is empty [1]");
var onStorageChanged = {
observe: function(subject, topic, type) {
if (topic == "dom-storage2-changed") {
ok(expectedTypes.length > 0, "Not more then expected events encountered");
is(type, expectedTypes.shift(), "Expected type of the storage notification");
tests.next();
}
}
function onSessionStorageChanged(e) {
ok(expectedTypes.length > 0, "Not more then expected events encountered");
is("sessionStorage", expectedTypes.shift(), "Expected session type of the storage notification");
tests.next();
}
// Listen for dom-storage2-changed notification
SpecialPowers.Services.obs.addObserver(onStorageChanged,
"dom-storage2-changed");
function onLocalStorageChanged(e) {
ok(expectedTypes.length > 0, "Not more then expected events encountered");
is("localStorage", expectedTypes.shift(), "Expected session type of the storage notification");
tests.next();
}
// Listen for storage notifications
SpecialPowers.addChromeEventListener("MozSessionStorageChanged", onSessionStorageChanged, true);
SpecialPowers.addChromeEventListener("MozLocalStorageChanged", onLocalStorageChanged, true);
// add an empty-value key
localStorage.setItem("empty", "");
@ -104,8 +106,9 @@ function* Tests()
yield undefined;
SimpleTest.executeSoon(function () {
SpecialPowers.Services.obs.removeObserver(onStorageChanged,
"dom-storage2-changed", false);
SpecialPowers.removeChromeEventListener("MozSessionStorageChanged", onSessionStorageChanged, true);
SpecialPowers.removeChromeEventListener("MozLocalStorageChanged", onLocalStorageChanged, true);
is(expectedTypes.length, 0, "received the correct number of events");
sessionStorage.clear();