diff --git a/toolkit/components/cookiebanners/CookieBannerDomainPrefService.cpp b/toolkit/components/cookiebanners/CookieBannerDomainPrefService.cpp new file mode 100644 index 000000000000..29392a113759 --- /dev/null +++ b/toolkit/components/cookiebanners/CookieBannerDomainPrefService.cpp @@ -0,0 +1,222 @@ +/* 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 "CookieBannerDomainPrefService.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Logging.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPtr.h" + +#include "nsIContentPrefService2.h" +#include "nsICookieBannerService.h" +#include "nsServiceManagerUtils.h" +#include "nsVariant.h" + +#define COOKIE_BANNER_CONTENT_PREF_NAME u"cookiebanner"_ns + +namespace mozilla { + +NS_IMPL_ISUPPORTS(CookieBannerDomainPrefService, nsIContentPrefCallback2) + +LazyLogModule gCookieBannerPerSitePrefLog("CookieBannerDomainPref"); + +static StaticRefPtr + sCookieBannerDomainPrefService; + +/* static */ +already_AddRefed +CookieBannerDomainPrefService::GetOrCreate() { + if (!sCookieBannerDomainPrefService) { + sCookieBannerDomainPrefService = new CookieBannerDomainPrefService(); + + ClearOnShutdown(&sCookieBannerDomainPrefService); + } + + return do_AddRef(sCookieBannerDomainPrefService); +} + +void CookieBannerDomainPrefService::Init() { + // Make sure we won't init again. + if (mIsInitialized) { + return; + } + + nsCOMPtr contentPrefService = + do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); + + if (!contentPrefService) { + return; + } + + mIsInitialized = true; + + // Populate the content pref for cookie banner domain preferences. + DebugOnly rv = contentPrefService->GetByName( + COOKIE_BANNER_CONTENT_PREF_NAME, nullptr, this); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Fail to get all content prefs during init."); +} + +Maybe CookieBannerDomainPrefService::GetPref( + const nsACString& aDomain, bool aIsPrivate) { + // For private windows, the domain prefs will only be stored in memory. + if (aIsPrivate) { + return mPrefsPrivate.MaybeGet(aDomain); + } + + // We return nothing if the first reading of the content pref is not completed + // yet. Note that, we won't be able to get the domain pref for early loads. + // But, we think this is acceptable because the cookie banners on the early + // load tabs would have interacted before when the user disabled the banner + // handling. So, there should be consent cookies in place to prevent banner + // showing. In this case, our cookie injection and banner clicking won't do + // anything. + if (!mIsContentPrefLoaded) { + return Nothing(); + } + + return mPrefs.MaybeGet(aDomain); +} + +nsresult CookieBannerDomainPrefService::SetPref( + const nsACString& aDomain, nsICookieBannerService::Modes aMode, + bool aIsPrivate) { + // For private windows, the domain prefs will only be stored in memory. + if (aIsPrivate) { + Unused << mPrefsPrivate.InsertOrUpdate(aDomain, aMode); + return NS_OK; + } + + EnsureInitCompleted(); + + // Update the in-memory domain preference map. + Unused << mPrefs.InsertOrUpdate(aDomain, aMode); + + // Set the preference to the content pref service. + nsCOMPtr contentPrefService = + do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(contentPrefService, NS_ERROR_FAILURE); + + RefPtr variant = new nsVariant(); + nsresult rv = variant->SetAsUint8(aMode); + NS_ENSURE_SUCCESS(rv, rv); + + // Store the domain preference to the content pref service. + rv = contentPrefService->Set(NS_ConvertUTF8toUTF16(aDomain), + COOKIE_BANNER_CONTENT_PREF_NAME, variant, + nullptr, nullptr); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Fail to set cookie banner domain pref."); + + return rv; +} + +nsresult CookieBannerDomainPrefService::RemovePref(const nsACString& aDomain, + bool aIsPrivate) { + // For private windows, we only need to remove in-memory settings. + if (aIsPrivate) { + mPrefsPrivate.Remove(aDomain); + return NS_OK; + } + + EnsureInitCompleted(); + + mPrefs.Remove(aDomain); + + nsCOMPtr contentPrefService = + do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(contentPrefService, NS_ERROR_FAILURE); + + // Remove the domain preference from the content pref service. + nsresult rv = contentPrefService->RemoveByDomainAndName( + NS_ConvertUTF8toUTF16(aDomain), COOKIE_BANNER_CONTENT_PREF_NAME, nullptr, + nullptr); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Fail to remove cookie banner domain pref."); + return rv; +} + +nsresult CookieBannerDomainPrefService::RemoveAll(bool aIsPrivate) { + // For private windows, we only need to remove in-memory settings. + if (aIsPrivate) { + mPrefsPrivate.Clear(); + return NS_OK; + } + + EnsureInitCompleted(); + + mPrefs.Clear(); + + nsCOMPtr contentPrefService = + do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(contentPrefService, NS_ERROR_FAILURE); + + // Remove all the domain preferences. + nsresult rv = contentPrefService->RemoveByName( + COOKIE_BANNER_CONTENT_PREF_NAME, nullptr, nullptr); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Fail to remove all cookie banner domain prefs."); + + return rv; +} + +void CookieBannerDomainPrefService::EnsureInitCompleted() { + if (mIsContentPrefLoaded) { + return; + } + + // Wait until the service is fully initialized. + SpinEventLoopUntil("CookieBannerDomainPrefService::EnsureUpdateComplete"_ns, + [&] { return mIsContentPrefLoaded; }); +} + +NS_IMETHODIMP +CookieBannerDomainPrefService::HandleResult(nsIContentPref* aPref) { + NS_ENSURE_ARG_POINTER(aPref); + + nsAutoString domain; + nsresult rv = aPref->GetDomain(domain); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr value; + rv = aPref->GetValue(getter_AddRefs(value)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!value) { + return NS_OK; + } + + uint8_t data; + rv = value->GetAsUint8(&data); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << mPrefs.InsertOrUpdate(NS_ConvertUTF16toUTF8(domain), + nsICookieBannerService::Modes(data)); + + return NS_OK; +} + +NS_IMETHODIMP +CookieBannerDomainPrefService::HandleCompletion(uint16_t aReason) { + mIsContentPrefLoaded = true; + return NS_OK; +} + +NS_IMETHODIMP +CookieBannerDomainPrefService::HandleError(nsresult error) { + // We don't need to do anything here because HandleCompletion is always + // called. + + if (NS_WARN_IF(NS_FAILED(error))) { + MOZ_LOG(gCookieBannerPerSitePrefLog, LogLevel::Warning, + ("Fail to get content pref during initiation.")); + return NS_OK; + } + + return NS_OK; +} + +} // namespace mozilla diff --git a/toolkit/components/cookiebanners/CookieBannerDomainPrefService.h b/toolkit/components/cookiebanners/CookieBannerDomainPrefService.h new file mode 100644 index 000000000000..99b1a763c29c --- /dev/null +++ b/toolkit/components/cookiebanners/CookieBannerDomainPrefService.h @@ -0,0 +1,71 @@ +/* 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_CookieBannerDomainPrefService_h__ +#define mozilla_CookieBannerDomainPrefService_h__ + +#include "nsIContentPrefService2.h" + +#include "mozilla/Maybe.h" +#include "nsStringFwd.h" +#include "nsTHashMap.h" + +#include "nsICookieBannerService.h" + +namespace mozilla { + +// The service which maintains the per-domain cookie banner preference. It uses +// the content pref to store the per-domain preference for cookie banner +// handling. To support the synchronous access, the service caches the +// preferences in the memory. +class CookieBannerDomainPrefService final : public nsIContentPrefCallback2 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICONTENTPREFCALLBACK2 + + static already_AddRefed GetOrCreate(); + + // Get the preference for the given domain. + Maybe GetPref(const nsACString& aDomain, + bool aIsPrivate); + + // Set the preference for the given domain. + [[nodiscard]] nsresult SetPref(const nsACString& aDomain, + nsICookieBannerService::Modes aMode, + bool aIsPrivate); + + // Remove the preference for the given domain. + [[nodiscard]] nsresult RemovePref(const nsACString& aDomain, bool aIsPrivate); + + // Remove all site preferences. + [[nodiscard]] nsresult RemoveAll(bool aIsPrivate); + + void Init(); + + private: + ~CookieBannerDomainPrefService() = default; + + CookieBannerDomainPrefService() + : mIsInitialized(false), mIsContentPrefLoaded(false) {} + + // Indicates whether the service is initialized. + bool mIsInitialized; + + // Indicates whether the first reading of content pref completed. + bool mIsContentPrefLoaded; + + // Map of the per site preference keyed by domain. + nsTHashMap mPrefs; + + // Map of the per site preference for private windows keyed by domain. + nsTHashMap mPrefsPrivate; + + // A helper function that will wait until the initialization of the content + // pref completed. + void EnsureInitCompleted(); +}; + +} // namespace mozilla + +#endif // mozilla_CookieBannerDomainPrefService_h__ diff --git a/toolkit/components/cookiebanners/moz.build b/toolkit/components/cookiebanners/moz.build index 10612d8092c5..d67ffa6f925a 100644 --- a/toolkit/components/cookiebanners/moz.build +++ b/toolkit/components/cookiebanners/moz.build @@ -36,6 +36,7 @@ EXPORTS.mozilla += [ ] UNIFIED_SOURCES += [ + "CookieBannerDomainPrefService.cpp", "nsClickRule.cpp", "nsCookieBannerRule.cpp", "nsCookieBannerService.cpp",