diff --git a/dom/interfaces/storage/moz.build b/dom/interfaces/storage/moz.build index b7a3e84ce1b0..eafe9327d6ed 100644 --- a/dom/interfaces/storage/moz.build +++ b/dom/interfaces/storage/moz.build @@ -10,6 +10,7 @@ with Files("**"): XPIDL_SOURCES += [ 'nsIDOMStorage.idl', 'nsIDOMStorageManager.idl', + 'nsIStorageActivityService.idl', ] XPIDL_MODULE = 'dom_storage' diff --git a/dom/interfaces/storage/nsIStorageActivityService.idl b/dom/interfaces/storage/nsIStorageActivityService.idl new file mode 100644 index 000000000000..9f1010e0c1a7 --- /dev/null +++ b/dom/interfaces/storage/nsIStorageActivityService.idl @@ -0,0 +1,25 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "domstubs.idl" + +/** + * nsIStorageActivityService is a service that can be used to know which + * origins have been active in a time range. This information can be used to + * implement "Clear Recent History" or similar features. + * + * If you are implementing a new Storage component, you should use + * QuotaManager. But if you don't do it, remember to call + * StorageActivityService methods in order to inform this service about + * 'writing' operations executed by origins. + */ +[scriptable, builtinclass, uuid(fd1310ba-d1be-4327-988e-92b39fcff6f4)] +interface nsIStorageActivityService : nsISupports +{ +}; + +%{ C++ +#define STORAGE_ACTIVITY_SERVICE_CONTRACTID "@mozilla.org/storage/activity-service;1" +%} diff --git a/dom/storage/StorageActivityService.cpp b/dom/storage/StorageActivityService.cpp new file mode 100644 index 000000000000..28157e35ab92 --- /dev/null +++ b/dom/storage/StorageActivityService.cpp @@ -0,0 +1,209 @@ +/* -*- 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 "StorageActivityService.h" + +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/StaticPtr.h" +#include "nsXPCOM.h" + +// This const is used to know when origin activities should be purged because +// too old. This value should be in sync with what the UI needs. +#define TIME_MAX_SECS 86400 /* 24 hours */ + +namespace mozilla { +namespace dom { + +static StaticRefPtr gStorageActivityService; +static bool gStorageActivityShutdown = false; + +/* static */ void +StorageActivityService::SendActivity(nsIPrincipal* aPrincipal) +{ + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr service = GetOrCreate(); + if (NS_WARN_IF(!service)) { + return; + } + + service->SendActivityInternal(aPrincipal); +} + +/* static */ void +StorageActivityService::SendActivity(const mozilla::ipc::PrincipalInfo& aPrincipalInfo) +{ + RefPtr r = NS_NewRunnableFunction( + "StorageActivityService::SendActivity", + [aPrincipalInfo] () { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr principal = + mozilla::ipc::PrincipalInfoToPrincipal(aPrincipalInfo); + + StorageActivityService::SendActivity(principal); + }); + + SystemGroup::Dispatch(TaskCategory::Other, r.forget()); +} + +/* static */ already_AddRefed +StorageActivityService::GetOrCreate() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!gStorageActivityService && !gStorageActivityShutdown) { + RefPtr service = new StorageActivityService(); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return nullptr; + } + + nsresult rv = obs->AddObserver(service, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + gStorageActivityService = service; + } + + RefPtr service = gStorageActivityService; + return service.forget(); +} + +StorageActivityService::StorageActivityService() +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +StorageActivityService::~StorageActivityService() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mTimer); +} + +void +StorageActivityService::SendActivityInternal(nsIPrincipal* aPrincipal) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPrincipal); + + if (!XRE_IsParentProcess()) { + SendActivityToParent(aPrincipal); + return; + } + + nsAutoCString origin; + nsresult rv = aPrincipal->GetOrigin(origin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + mActivities.Put(origin, TimeStamp::NowLoRes()); + + MaybeStartTimer(); +} + +void +StorageActivityService::SendActivityToParent(nsIPrincipal* aPrincipal) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!XRE_IsParentProcess()); + + PBackgroundChild* actor = BackgroundChild::GetOrCreateForCurrentThread(); + if (NS_WARN_IF(!actor)) { + return; + } + + mozilla::ipc::PrincipalInfo principalInfo; + nsresult rv = + mozilla::ipc::PrincipalToPrincipalInfo(aPrincipal, &principalInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + actor->SendStorageActivity(principalInfo); +} + +NS_IMETHODIMP +StorageActivityService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)); + + MaybeStopTimer(); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + + gStorageActivityShutdown = true; + gStorageActivityService = nullptr; + return NS_OK; +} + +void +StorageActivityService::MaybeStartTimer() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mTimer) { + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + mTimer->InitWithCallback(this, + 1000 * 5 * 60 /* any 5 minutes */, + nsITimer::TYPE_REPEATING_SLACK); + } +} + +void +StorageActivityService::MaybeStopTimer() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } +} + +NS_IMETHODIMP +StorageActivityService::Notify(nsITimer* aTimer) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mTimer == aTimer); + + TimeStamp now = TimeStamp::NowLoRes(); + + for (auto iter = mActivities.Iter(); !iter.Done(); iter.Next()) { + if ((now - iter.UserData()).ToSeconds() > TIME_MAX_SECS) { + iter.Remove(); + } + } + + // If no activities, let's stop the timer. + if (mActivities.Count() == 0) { + MaybeStopTimer(); + } + + return NS_OK; +} + +NS_INTERFACE_MAP_BEGIN(StorageActivityService) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStorageActivityService) + NS_INTERFACE_MAP_ENTRY(nsIStorageActivityService) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(StorageActivityService) +NS_IMPL_RELEASE(StorageActivityService) + +} // dom namespace +} // mozilla namespace diff --git a/dom/storage/StorageActivityService.h b/dom/storage/StorageActivityService.h new file mode 100644 index 000000000000..1079d4e33840 --- /dev/null +++ b/dom/storage/StorageActivityService.h @@ -0,0 +1,71 @@ +/* -*- 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_StorageActivityService_h +#define mozilla_dom_StorageActivityService_h + +#include "nsDataHashtable.h" +#include "nsIStorageActivityService.h" +#include "nsITimer.h" +#include "nsWeakReference.h" + +namespace mozilla { + +namespace ipc { +class PrincipalInfo; +} // ipc + +namespace dom { + +class StorageActivityService final : public nsIStorageActivityService + , public nsIObserver + , public nsITimerCallback + , public nsSupportsWeakReference +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISTORAGEACTIVITYSERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + + // Main-thread only. + static void + SendActivity(nsIPrincipal* aPrincipal); + + // Thread-safe. + static void + SendActivity(const mozilla::ipc::PrincipalInfo& aPrincipalInfo); + + // Used by XPCOM. Don't use it, use SendActivity() instead. + static already_AddRefed + GetOrCreate(); + +private: + StorageActivityService(); + ~StorageActivityService(); + + void + SendActivityInternal(nsIPrincipal* aPrincipal); + + void + SendActivityToParent(nsIPrincipal* aPrincipal); + + void + MaybeStartTimer(); + + void + MaybeStopTimer(); + + // Activities grouped by origin (+OriginAttributes). + nsDataHashtable mActivities; + + nsCOMPtr mTimer; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_StorageActivityService_h diff --git a/dom/storage/moz.build b/dom/storage/moz.build index a11d25042565..8f0e11b57e9a 100644 --- a/dom/storage/moz.build +++ b/dom/storage/moz.build @@ -12,6 +12,7 @@ EXPORTS.mozilla.dom += [ 'LocalStorageManager.h', 'SessionStorageManager.h', 'Storage.h', + 'StorageActivityService.h', 'StorageIPC.h', 'StorageNotifierService.h', 'StorageUtils.h', @@ -25,6 +26,7 @@ UNIFIED_SOURCES += [ 'SessionStorageCache.cpp', 'SessionStorageManager.cpp', 'Storage.cpp', + 'StorageActivityService.cpp', 'StorageDBThread.cpp', 'StorageDBUpdater.cpp', 'StorageIPC.cpp', diff --git a/ipc/glue/BackgroundParentImpl.cpp b/ipc/glue/BackgroundParentImpl.cpp index aaa27617eebc..7917f01639f7 100644 --- a/ipc/glue/BackgroundParentImpl.cpp +++ b/ipc/glue/BackgroundParentImpl.cpp @@ -24,6 +24,7 @@ #include "mozilla/dom/PGamepadTestChannelParent.h" #include "mozilla/dom/MessagePortParent.h" #include "mozilla/dom/ServiceWorkerRegistrar.h" +#include "mozilla/dom/StorageActivityService.h" #include "mozilla/dom/asmjscache/AsmJSCache.h" #include "mozilla/dom/cache/ActorUtils.h" #include "mozilla/dom/indexedDB/ActorsParent.h" @@ -987,6 +988,13 @@ BackgroundParentImpl::RecvPClientManagerConstructor(mozilla::dom::PClientManager return IPC_OK(); } +IPCResult +BackgroundParentImpl::RecvStorageActivity(const PrincipalInfo& aPrincipalInfo) +{ + dom::StorageActivityService::SendActivity(aPrincipalInfo); + return IPC_OK(); +} + } // namespace ipc } // namespace mozilla diff --git a/ipc/glue/BackgroundParentImpl.h b/ipc/glue/BackgroundParentImpl.h index 0511c69dc938..706b7f6a902f 100644 --- a/ipc/glue/BackgroundParentImpl.h +++ b/ipc/glue/BackgroundParentImpl.h @@ -271,6 +271,9 @@ protected: virtual mozilla::ipc::IPCResult RecvPClientManagerConstructor(PClientManagerParent* aActor) override; + + virtual mozilla::ipc::IPCResult + RecvStorageActivity(const PrincipalInfo& aPrincipalInfo) override; }; } // namespace ipc diff --git a/ipc/glue/PBackground.ipdl b/ipc/glue/PBackground.ipdl index 0e29c883cb4d..9fb08a18a198 100644 --- a/ipc/glue/PBackground.ipdl +++ b/ipc/glue/PBackground.ipdl @@ -140,6 +140,10 @@ parent: async PClientManager(); + // This method is used to propagate storage activities from the child actor + // to the parent actor. See StorageActivityService. + async StorageActivity(PrincipalInfo principalInfo); + child: async PCache(); async PCacheStreamControl(); diff --git a/layout/build/nsLayoutCID.h b/layout/build/nsLayoutCID.h index 5d4800da0404..2d6b30444269 100644 --- a/layout/build/nsLayoutCID.h +++ b/layout/build/nsLayoutCID.h @@ -79,6 +79,10 @@ #define SERVICEWORKERMANAGER_CID \ { 0xc74bde32, 0xbcc7, 0x4840, { 0x84, 0x30, 0xc7, 0x33, 0x35, 0x1b, 0x21, 0x2a } } +// {69da374a-fda3-4a93-9fbc-d9304f66a7fe} +#define STORAGEACTIVITYSERVICE_CID \ +{ 0x69da374a, 0xfda3, 0x4a93, { 0x9f, 0xbc, 0xd9, 0x30, 0x4f, 0x66, 0xa7, 0xfe } } + #define NOTIFICATIONTELEMETRYSERVICE_CID \ { 0x5995b782, 0x6a0e, 0x4066, { 0xaa, 0xc5, 0x27, 0x6f, 0x0a, 0x9a, 0xd8, 0xcf } } diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp index 9e85155fdad9..adbd088a566b 100644 --- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -83,6 +83,7 @@ #include "mozilla/dom/network/UDPSocketChild.h" #include "mozilla/dom/quota/QuotaManagerService.h" #include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/dom/StorageActivityService.h" #include "mozilla/dom/workers/ServiceWorkerManager.h" #include "mozilla/dom/workers/WorkerDebuggerManager.h" #include "mozilla/dom/Notification.h" @@ -252,6 +253,8 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(ServiceWorkerManager, ServiceWorkerManager::GetInstance) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(WorkerDebuggerManager, WorkerDebuggerManager::GetInstance) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(StorageActivityService, + StorageActivityService::GetOrCreate) #ifdef MOZ_WEBSPEECH NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsSynthVoiceRegistry, @@ -625,6 +628,7 @@ NS_DEFINE_NAMED_CID(NS_TEXTEDITOR_CID); NS_DEFINE_NAMED_CID(DOMREQUEST_SERVICE_CID); NS_DEFINE_NAMED_CID(QUOTAMANAGER_SERVICE_CID); NS_DEFINE_NAMED_CID(SERVICEWORKERMANAGER_CID); +NS_DEFINE_NAMED_CID(STORAGEACTIVITYSERVICE_CID); NS_DEFINE_NAMED_CID(NOTIFICATIONTELEMETRYSERVICE_CID); NS_DEFINE_NAMED_CID(PUSHNOTIFIER_CID); NS_DEFINE_NAMED_CID(WORKERDEBUGGERMANAGER_CID); @@ -882,6 +886,7 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = { { &kDOMREQUEST_SERVICE_CID, false, nullptr, DOMRequestServiceConstructor }, { &kQUOTAMANAGER_SERVICE_CID, false, nullptr, QuotaManagerServiceConstructor }, { &kSERVICEWORKERMANAGER_CID, false, nullptr, ServiceWorkerManagerConstructor }, + { &kSTORAGEACTIVITYSERVICE_CID, false, nullptr, StorageActivityServiceConstructor }, { &kNOTIFICATIONTELEMETRYSERVICE_CID, false, nullptr, NotificationTelemetryServiceConstructor }, { &kPUSHNOTIFIER_CID, false, nullptr, PushNotifierConstructor }, { &kWORKERDEBUGGERMANAGER_CID, true, nullptr, WorkerDebuggerManagerConstructor }, @@ -1007,6 +1012,7 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = { { DOMREQUEST_SERVICE_CONTRACTID, &kDOMREQUEST_SERVICE_CID }, { QUOTAMANAGER_SERVICE_CONTRACTID, &kQUOTAMANAGER_SERVICE_CID }, { SERVICEWORKERMANAGER_CONTRACTID, &kSERVICEWORKERMANAGER_CID }, + { STORAGE_ACTIVITY_SERVICE_CONTRACTID, &kSTORAGEACTIVITYSERVICE_CID }, { NOTIFICATIONTELEMETRYSERVICE_CONTRACTID, &kNOTIFICATIONTELEMETRYSERVICE_CID }, { PUSHNOTIFIER_CONTRACTID, &kPUSHNOTIFIER_CID }, { WORKERDEBUGGERMANAGER_CONTRACTID, &kWORKERDEBUGGERMANAGER_CID },