summaryrefslogtreecommitdiffstats
path: root/toolkit/components/cookiebanners/CookieBannerDomainPrefService.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--toolkit/components/cookiebanners/CookieBannerDomainPrefService.cpp484
1 files changed, 484 insertions, 0 deletions
diff --git a/toolkit/components/cookiebanners/CookieBannerDomainPrefService.cpp b/toolkit/components/cookiebanners/CookieBannerDomainPrefService.cpp
new file mode 100644
index 0000000000..4b4912f1a1
--- /dev/null
+++ b/toolkit/components/cookiebanners/CookieBannerDomainPrefService.cpp
@@ -0,0 +1,484 @@
+/* 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/RefPtr.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticPtr.h"
+
+#include "nsIContentPrefService2.h"
+#include "nsICookieBannerService.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsVariant.h"
+
+#define COOKIE_BANNER_CONTENT_PREF_NAME u"cookiebanner"_ns
+#define COOKIE_BANNER_CONTENT_PREF_NAME_PRIVATE u"cookiebannerprivate"_ns
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(CookieBannerDomainPrefService, nsIAsyncShutdownBlocker,
+ nsIObserver)
+
+NS_IMPL_ISUPPORTS(CookieBannerDomainPrefService::DomainPrefData, nsISupports)
+
+LazyLogModule gCookieBannerPerSitePrefLog("CookieBannerDomainPref");
+
+static StaticRefPtr<CookieBannerDomainPrefService>
+ sCookieBannerDomainPrefService;
+
+namespace {
+
+// A helper function to get the profile-before-change shutdown barrier.
+
+nsCOMPtr<nsIAsyncShutdownClient> GetShutdownBarrier() {
+ nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
+ NS_ENSURE_TRUE(svc, nullptr);
+
+ nsCOMPtr<nsIAsyncShutdownClient> barrier;
+ nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(barrier));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return barrier;
+}
+
+} // anonymous namespace
+
+/* static */
+already_AddRefed<CookieBannerDomainPrefService>
+CookieBannerDomainPrefService::GetOrCreate() {
+ if (!sCookieBannerDomainPrefService) {
+ sCookieBannerDomainPrefService = new CookieBannerDomainPrefService();
+
+ RunOnShutdown([] {
+ MOZ_LOG(gCookieBannerPerSitePrefLog, LogLevel::Debug, ("RunOnShutdown."));
+
+ sCookieBannerDomainPrefService->Shutdown();
+
+ sCookieBannerDomainPrefService = nullptr;
+ });
+ }
+
+ return do_AddRef(sCookieBannerDomainPrefService);
+}
+
+void CookieBannerDomainPrefService::Init() {
+ // Make sure we won't init again.
+ if (mIsInitialized) {
+ return;
+ }
+
+ nsCOMPtr<nsIContentPrefService2> contentPrefService =
+ do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+
+ if (!contentPrefService) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+
+ if (!obs) {
+ return;
+ }
+
+ // Register the observer to watch private browsing session ends. We will clean
+ // the private domain prefs when this happens.
+ nsresult rv = obs->AddObserver(this, "last-pb-context-exited", false);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Fail to add observer for 'last-pb-context-exited'.");
+
+ auto initCallback = MakeRefPtr<InitialLoadContentPrefCallback>(this, false);
+
+ // Populate the content pref for cookie banner domain preferences.
+ rv = contentPrefService->GetByName(COOKIE_BANNER_CONTENT_PREF_NAME, nullptr,
+ initCallback);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Fail to get all content prefs during init.");
+
+ auto initPrivateCallback =
+ MakeRefPtr<InitialLoadContentPrefCallback>(this, true);
+
+ // Populate the content pref for the private browsing.
+ rv = contentPrefService->GetByName(COOKIE_BANNER_CONTENT_PREF_NAME_PRIVATE,
+ nullptr, initPrivateCallback);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Fail to get all content prefs during init.");
+
+ rv = AddShutdownBlocker();
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ mIsInitialized = true;
+}
+
+void CookieBannerDomainPrefService::Shutdown() {
+ // Bail out early if the service never gets initialized.
+ if (!mIsInitialized) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+
+ if (!obs) {
+ return;
+ }
+
+ DebugOnly<nsresult> rv = obs->RemoveObserver(this, "last-pb-context-exited");
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Fail to remove observer for 'last-pb-context-exited'.");
+}
+
+Maybe<nsICookieBannerService::Modes> CookieBannerDomainPrefService::GetPref(
+ const nsACString& aDomain, bool aIsPrivate) {
+ bool isContentPrefLoaded =
+ aIsPrivate ? mIsPrivateContentPrefLoaded : mIsContentPrefLoaded;
+
+ // 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 (!isContentPrefLoaded) {
+ return Nothing();
+ }
+
+ Maybe<RefPtr<DomainPrefData>> data =
+ aIsPrivate ? mPrefsPrivate.MaybeGet(aDomain) : mPrefs.MaybeGet(aDomain);
+
+ if (!data) {
+ return Nothing();
+ }
+
+ return Some(data.ref()->mMode);
+}
+
+nsresult CookieBannerDomainPrefService::SetPref(
+ const nsACString& aDomain, nsICookieBannerService::Modes aMode,
+ bool aIsPrivate, bool aPersistInPrivateBrowsing) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // Don't do anything if we are shutting down.
+ if (NS_WARN_IF(mIsShuttingDown)) {
+ MOZ_LOG(gCookieBannerPerSitePrefLog, LogLevel::Warning,
+ ("Attempt to set a domain pref while shutting down."));
+ return NS_OK;
+ }
+
+ EnsureInitCompleted(aIsPrivate);
+
+ // Create the domain pref data. The data is always persistent for normal
+ // windows. For private windows, the data is only persistent if requested.
+ auto domainPrefData = MakeRefPtr<DomainPrefData>(
+ aMode, aIsPrivate ? aPersistInPrivateBrowsing : true);
+ bool wasPersistentInPrivate = false;
+
+ // Update the in-memory domain preference map.
+ if (aIsPrivate) {
+ Maybe<RefPtr<DomainPrefData>> data = mPrefsPrivate.MaybeGet(aDomain);
+
+ wasPersistentInPrivate = data ? data.ref()->mIsPersistent : false;
+ Unused << mPrefsPrivate.InsertOrUpdate(aDomain, domainPrefData);
+ } else {
+ Unused << mPrefs.InsertOrUpdate(aDomain, domainPrefData);
+ }
+
+ // For private windows, the domain prefs will only be stored in memory.
+ // Unless, this function is instructed to persist setting for private
+ // browsing. To make the disk state consistent with the memory state, we need
+ // to clear the domain pref in the disk when we no longer need to persist the
+ // domain pref for the domain in PBM.
+ if (!aPersistInPrivateBrowsing && aIsPrivate) {
+ // Clear the domain pref in disk if it was persistent.
+ if (wasPersistentInPrivate) {
+ return RemoveContentPrefForDomain(aDomain, true);
+ }
+ return NS_OK;
+ }
+
+ // Set the preference to the content pref service.
+ nsCOMPtr<nsIContentPrefService2> contentPrefService =
+ do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(contentPrefService, NS_ERROR_FAILURE);
+
+ RefPtr<nsVariant> variant = new nsVariant();
+ nsresult rv = variant->SetAsUint8(aMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto callback = MakeRefPtr<WriteContentPrefCallback>(this);
+ mWritingCount++;
+
+ // Store the domain preference to the content pref service.
+ rv = contentPrefService->Set(NS_ConvertUTF8toUTF16(aDomain),
+ aIsPrivate
+ ? COOKIE_BANNER_CONTENT_PREF_NAME_PRIVATE
+ : COOKIE_BANNER_CONTENT_PREF_NAME,
+ variant, nullptr, callback);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Fail to set cookie banner domain pref.");
+
+ return rv;
+}
+
+nsresult CookieBannerDomainPrefService::RemovePref(const nsACString& aDomain,
+ bool aIsPrivate) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // Don't do anything if we are shutting down.
+ if (NS_WARN_IF(mIsShuttingDown)) {
+ MOZ_LOG(gCookieBannerPerSitePrefLog, LogLevel::Warning,
+ ("Attempt to remove a domain pref while shutting down."));
+ return NS_OK;
+ }
+
+ EnsureInitCompleted(aIsPrivate);
+
+ // Clear in-memory domain pref.
+ if (aIsPrivate) {
+ mPrefsPrivate.Remove(aDomain);
+ } else {
+ mPrefs.Remove(aDomain);
+ }
+
+ return RemoveContentPrefForDomain(aDomain, aIsPrivate);
+}
+
+nsresult CookieBannerDomainPrefService::RemoveAll(bool aIsPrivate) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // Don't do anything if we are shutting down.
+ if (NS_WARN_IF(mIsShuttingDown)) {
+ MOZ_LOG(gCookieBannerPerSitePrefLog, LogLevel::Warning,
+ ("Attempt to remove all domain prefs while shutting down."));
+ return NS_OK;
+ }
+
+ EnsureInitCompleted(aIsPrivate);
+
+ // Clear in-memory domain pref.
+ if (aIsPrivate) {
+ mPrefsPrivate.Clear();
+ } else {
+ mPrefs.Clear();
+ }
+
+ nsCOMPtr<nsIContentPrefService2> contentPrefService =
+ do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(contentPrefService, NS_ERROR_FAILURE);
+
+ auto callback = MakeRefPtr<WriteContentPrefCallback>(this);
+ mWritingCount++;
+
+ // Remove all the domain preferences.
+ nsresult rv = contentPrefService->RemoveByName(
+ aIsPrivate ? COOKIE_BANNER_CONTENT_PREF_NAME_PRIVATE
+ : COOKIE_BANNER_CONTENT_PREF_NAME,
+ nullptr, callback);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Fail to remove all cookie banner domain prefs.");
+
+ return rv;
+}
+
+void CookieBannerDomainPrefService::EnsureInitCompleted(bool aIsPrivate) {
+ bool& isContentPrefLoaded =
+ aIsPrivate ? mIsPrivateContentPrefLoaded : mIsContentPrefLoaded;
+ if (isContentPrefLoaded) {
+ return;
+ }
+
+ // Wait until the service is fully initialized.
+ SpinEventLoopUntil("CookieBannerDomainPrefService::EnsureUpdateComplete"_ns,
+ [&] { return isContentPrefLoaded; });
+}
+
+nsresult CookieBannerDomainPrefService::AddShutdownBlocker() {
+ MOZ_ASSERT(!mIsShuttingDown);
+
+ nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
+ NS_ENSURE_TRUE(barrier, NS_ERROR_FAILURE);
+
+ return GetShutdownBarrier()->AddBlocker(
+ this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
+ u"CookieBannerDomainPrefService: shutdown"_ns);
+}
+
+nsresult CookieBannerDomainPrefService::RemoveShutdownBlocker() {
+ MOZ_ASSERT(mIsShuttingDown);
+
+ nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
+ NS_ENSURE_TRUE(barrier, NS_ERROR_FAILURE);
+
+ return GetShutdownBarrier()->RemoveBlocker(this);
+}
+
+nsresult CookieBannerDomainPrefService::RemoveContentPrefForDomain(
+ const nsACString& aDomain, bool aIsPrivate) {
+ nsCOMPtr<nsIContentPrefService2> contentPrefService =
+ do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(contentPrefService, NS_ERROR_FAILURE);
+
+ auto callback = MakeRefPtr<WriteContentPrefCallback>(this);
+ mWritingCount++;
+
+ // Remove the domain preference from the content pref service.
+ nsresult rv = contentPrefService->RemoveByDomainAndName(
+ NS_ConvertUTF8toUTF16(aDomain),
+ aIsPrivate ? COOKIE_BANNER_CONTENT_PREF_NAME_PRIVATE
+ : COOKIE_BANNER_CONTENT_PREF_NAME,
+ nullptr, callback);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Fail to remove cookie banner domain pref.");
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(CookieBannerDomainPrefService::BaseContentPrefCallback,
+ nsIContentPrefCallback2)
+
+NS_IMETHODIMP
+CookieBannerDomainPrefService::InitialLoadContentPrefCallback::HandleResult(
+ nsIContentPref* aPref) {
+ NS_ENSURE_ARG_POINTER(aPref);
+ MOZ_ASSERT(mService);
+
+ nsAutoString domain;
+ nsresult rv = aPref->GetDomain(domain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIVariant> 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);
+
+ // Create the domain pref data and indicate it's persistent.
+ auto domainPrefData =
+ MakeRefPtr<DomainPrefData>(nsICookieBannerService::Modes(data), true);
+
+ if (mIsPrivate) {
+ Unused << mService->mPrefsPrivate.InsertOrUpdate(
+ NS_ConvertUTF16toUTF8(domain), domainPrefData);
+ } else {
+ Unused << mService->mPrefs.InsertOrUpdate(NS_ConvertUTF16toUTF8(domain),
+ domainPrefData);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieBannerDomainPrefService::InitialLoadContentPrefCallback::HandleCompletion(
+ uint16_t aReason) {
+ MOZ_ASSERT(mService);
+
+ if (mIsPrivate) {
+ mService->mIsPrivateContentPrefLoaded = true;
+ } else {
+ mService->mIsContentPrefLoaded = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieBannerDomainPrefService::InitialLoadContentPrefCallback::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;
+}
+
+NS_IMETHODIMP
+CookieBannerDomainPrefService::WriteContentPrefCallback::HandleResult(
+ nsIContentPref* aPref) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieBannerDomainPrefService::WriteContentPrefCallback::HandleCompletion(
+ uint16_t aReason) {
+ MOZ_ASSERT(mService);
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mService->mWritingCount > 0);
+
+ mService->mWritingCount--;
+
+ // Remove the shutdown blocker after we complete writing to content pref.
+ if (mService->mIsShuttingDown && mService->mWritingCount == 0) {
+ mService->RemoveShutdownBlocker();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieBannerDomainPrefService::WriteContentPrefCallback::HandleError(
+ nsresult error) {
+ if (NS_WARN_IF(NS_FAILED(error))) {
+ MOZ_LOG(gCookieBannerPerSitePrefLog, LogLevel::Warning,
+ ("Fail to write content pref."));
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieBannerDomainPrefService::Observe(nsISupports* /*aSubject*/,
+ const char* aTopic,
+ const char16_t* /*aData*/) {
+ if (strcmp(aTopic, "last-pb-context-exited") != 0) {
+ MOZ_ASSERT_UNREACHABLE("unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Clear the private browsing domain prefs that are not persistent when we
+ // observe the private browsing session has ended.
+ mPrefsPrivate.RemoveIf([](const auto& iter) {
+ const RefPtr<DomainPrefData>& data = iter.Data();
+ return !data->mIsPersistent;
+ });
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieBannerDomainPrefService::GetName(nsAString& aName) {
+ aName.AssignLiteral(
+ "CookieBannerDomainPrefService: write content pref before "
+ "profile-before-change.");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieBannerDomainPrefService::BlockShutdown(nsIAsyncShutdownClient*) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mIsShuttingDown = true;
+
+ // If we are not writing the content pref, we can remove the shutdown blocker
+ // directly.
+ if (mWritingCount == 0) {
+ RemoveShutdownBlocker();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieBannerDomainPrefService::GetState(nsIPropertyBag**) { return NS_OK; }
+
+} // namespace mozilla