Source code

Revision control

Copy as Markdown

Other Tools

/* 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