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 "ContentClassifierPrefMirror.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Preferences.h"
#include "mozilla/Span.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "MainThreadUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
namespace mozilla {
StaticAutoPtr<ContentClassifierPrefMirror>
ContentClassifierPrefMirror::sInstance;
namespace {
constexpr char kMirrorModePref[] =
"privacy.trackingprotection.content.mirror.mode";
enum class MirrorMode : uint32_t {
Off = 0,
On = 1,
Handover = 2,
};
MirrorMode CurrentMode() {
switch (Preferences::GetUint(kMirrorModePref, uint32_t(MirrorMode::Off))) {
case 1:
return MirrorMode::On;
case 2:
return MirrorMode::Handover;
default:
return MirrorMode::Off;
}
}
// A hidden pref that marks the mirror as owning the mirrored content prefs,
// so that it can release them again once the mirror is disabled.
constexpr char kOwnsPrefsPref[] =
"privacy.trackingprotection.content.mirror.owns_prefs";
// Content classifier prefs the mirror owns while enabled.
constexpr char kProtectionEnabledPref[] =
"privacy.trackingprotection.content.protection.enabled";
constexpr char kProtectionEnginesPref[] =
"privacy.trackingprotection.content.protection.engines";
constexpr char kProtectionEnginesPBMPref[] =
"privacy.trackingprotection.content.protection.engines.pbmode";
constexpr char kAnnotationEnabledPref[] =
"privacy.trackingprotection.content.annotation.enabled";
constexpr char kAnnotationEnginesPref[] =
"privacy.trackingprotection.content.annotation.engines";
constexpr char kAnnotationEnginesPBMPref[] =
"privacy.trackingprotection.content.annotation.engines.pbmode";
constexpr const char* kMirroredPrefs[] = {
kProtectionEnabledPref, kProtectionEnginesPref, kProtectionEnginesPBMPref,
kAnnotationEnabledPref, kAnnotationEnginesPref, kAnnotationEnginesPBMPref,
};
constexpr char kMajorExceptionsEngine[] = "major-exceptions";
constexpr char kMinorExceptionsEngine[] = "minor-exceptions";
// Maps a content classifier engine name (see ContentClassifierService.cpp,
// kFeatures) onto the ETP prefs that gate it in normal and private-browsing
// contexts. An engine joins the mirrored list when its gating pref is true.
struct EngineMapping {
const char* mEngine;
const char* mNormalPref;
const char* mPBMPref;
};
// The order here is the order the engines land in the mirrored pref, which is
// the order ContentClassifierService evaluates them in, and MaybeCancelChannel
// blocks on the first matched engine. So it must mirror the blocking-feature
// order of UrlClassifierFeatureFactory::GetCancelingFeaturesFromChannel, or a
// URL on more than one list gets cancelled by a different feature (and reports
// a different error code and state bit) than under the classic classifier.
constexpr EngineMapping kProtectionMappings[] = {
{"email-trackers", "privacy.trackingprotection.emailtracking.enabled",
"privacy.trackingprotection.emailtracking.pbmode.enabled"},
{"cryptominers", "privacy.trackingprotection.cryptomining.enabled",
"privacy.trackingprotection.cryptomining.enabled"},
{"fingerprinters", "privacy.trackingprotection.fingerprinting.enabled",
"privacy.trackingprotection.fingerprinting.enabled"},
{"social-trackers", "privacy.trackingprotection.socialtracking.enabled",
"privacy.trackingprotection.socialtracking.enabled"},
{"harmful-addon", "privacy.trackingprotection.harmfuladdon.enabled",
"privacy.trackingprotection.harmfuladdon.enabled"},
{"trackers", "privacy.trackingprotection.enabled",
"privacy.trackingprotection.pbmode.enabled"},
};
// Mirrors UrlClassifierFeatureFactory::GetNonCancelingFeaturesFromChannel.
// Annotation engines are evaluated independently and every match is annotated,
// so order is not load-bearing the way it is for kProtectionMappings, but we
// keep the two in sync to avoid surprises.
constexpr EngineMapping kAnnotationMappings[] = {
{"cryptominers", "privacy.trackingprotection.annotate_channels",
"privacy.trackingprotection.annotate_channels"},
{"fingerprinters", "privacy.trackingprotection.annotate_channels",
"privacy.trackingprotection.annotate_channels"},
{"social-trackers", "privacy.trackingprotection.annotate_channels",
"privacy.trackingprotection.annotate_channels"},
// trackers and trackers-content are one feature (TrackingAnnotation) in
// url-classifier, split in two here.
{"trackers", "privacy.trackingprotection.annotate_channels",
"privacy.trackingprotection.annotate_channels"},
// Content trackers are annotated only when the strict list is in use.
{"trackers-content", "privacy.annotate_channels.strict_list.enabled",
"privacy.annotate_channels.strict_list.pbmode.enabled"},
};
// ETP source prefs the mirror observes; a change to any of these recomputes
// the mirrored content prefs. Must cover every gating pref referenced above.
constexpr const char* kWatchedPrefs[] = {
"privacy.trackingprotection.enabled",
"privacy.trackingprotection.pbmode.enabled",
"privacy.trackingprotection.annotate_channels",
"privacy.annotate_channels.strict_list.enabled",
"privacy.annotate_channels.strict_list.pbmode.enabled",
"privacy.trackingprotection.fingerprinting.enabled",
"privacy.trackingprotection.cryptomining.enabled",
"privacy.trackingprotection.socialtracking.enabled",
"privacy.trackingprotection.emailtracking.enabled",
"privacy.trackingprotection.emailtracking.pbmode.enabled",
"privacy.trackingprotection.harmfuladdon.enabled",
"privacy.trackingprotection.allow_list.baseline.enabled",
"privacy.trackingprotection.allow_list.convenience.enabled",
};
// Build a comma-separated engine list, selecting each mapping's normal or
// PBM gating pref according to aPrivateBrowsing.
void BuildEngineList(Span<const EngineMapping> aMappings, bool aPrivateBrowsing,
nsACString& aOut) {
aOut.Truncate();
for (const auto& mapping : aMappings) {
const char* gatingPref =
aPrivateBrowsing ? mapping.mPBMPref : mapping.mNormalPref;
if (Preferences::GetBool(gatingPref, false)) {
if (!aOut.IsEmpty()) {
aOut.Append(',');
}
aOut.Append(nsDependentCString(mapping.mEngine));
}
}
}
// Append the WebCompat exception engines to a protection engine list built by
// BuildEngineList.
void AppendExceptionEngines(nsACString& aProtectionEngines) {
// Only add exception engines if the list already holds a blocking engine.
if (aProtectionEngines.IsEmpty()) {
return;
}
bool baseline =
StaticPrefs::privacy_trackingprotection_allow_list_baseline_enabled();
bool convenience =
StaticPrefs::privacy_trackingprotection_allow_list_convenience_enabled();
if (baseline) {
aProtectionEngines.Append(',');
aProtectionEngines.Append(nsDependentCString(kMajorExceptionsEngine));
}
if (baseline && convenience) {
aProtectionEngines.Append(',');
aProtectionEngines.Append(nsDependentCString(kMinorExceptionsEngine));
}
}
} // namespace
// static
void ContentClassifierPrefMirror::Init() {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
static bool sRegistered = false;
if (sRegistered) {
return;
}
sRegistered = true;
// Tear the singleton down at shutdown
RunOnShutdown([] { Shutdown(); });
Preferences::RegisterCallbackAndCall(
&ContentClassifierPrefMirror::OnMirrorPrefChange,
nsDependentCString(kMirrorModePref));
}
ContentClassifierPrefMirror::ContentClassifierPrefMirror() {
for (const char* pref : kWatchedPrefs) {
Preferences::RegisterCallback(&ContentClassifierPrefMirror::OnPrefChange,
nsDependentCString(pref));
}
}
ContentClassifierPrefMirror::~ContentClassifierPrefMirror() {
for (const char* pref : kWatchedPrefs) {
Preferences::UnregisterCallback(&ContentClassifierPrefMirror::OnPrefChange,
nsDependentCString(pref));
}
}
// static
void ContentClassifierPrefMirror::OnMirrorPrefChange(const char* aPref,
void* aData) {
MOZ_ASSERT(NS_IsMainThread());
switch (CurrentMode()) {
case MirrorMode::On:
if (!sInstance) {
sInstance = new ContentClassifierPrefMirror();
}
sInstance->ScheduleSync();
return;
case MirrorMode::Handover: {
// Stop mirroring, but leave the content pref values.
Shutdown();
DebugOnly<nsresult> rv = Preferences::ClearUser(kOwnsPrefsPref);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"Failed to clear the ContentClassifierMirror owning pref");
return;
}
case MirrorMode::Off:
// Stop mirroring, and clear the content pref values if they were mirrored
// before.
Shutdown();
ReleaseMirroredPrefs();
return;
}
}
// static
void ContentClassifierPrefMirror::Shutdown() {
MOZ_ASSERT(NS_IsMainThread());
sInstance = nullptr;
}
// static
void ContentClassifierPrefMirror::ReleaseMirroredPrefs() {
MOZ_ASSERT(NS_IsMainThread());
// No-op if the mirror doesn't own the mirrored prefs.
if (!Preferences::GetBool(kOwnsPrefsPref, false)) {
return;
}
DebugOnly<nsresult> rv;
for (const char* pref : kMirroredPrefs) {
rv = Preferences::ClearUser(pref);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"Failed to clear a ContentClassifierMirror mirrored pref");
}
rv = Preferences::ClearUser(kOwnsPrefsPref);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"Failed to clear the ContentClassifierMirror owning pref");
}
// static
void ContentClassifierPrefMirror::OnPrefChange(const char* aPref, void* aData) {
MOZ_ASSERT(NS_IsMainThread());
if (sInstance) {
sInstance->ScheduleSync();
}
}
void ContentClassifierPrefMirror::ScheduleSync() {
MOZ_ASSERT(NS_IsMainThread());
if (mSyncScheduled) {
return;
}
mSyncScheduled = true;
NS_DispatchToMainThread(
NS_NewRunnableFunction("ContentClassifierPrefMirror::Sync", [] {
if (sInstance) {
sInstance->mSyncScheduled = false;
sInstance->Sync();
}
}));
}
void ContentClassifierPrefMirror::Sync() {
MOZ_ASSERT(NS_IsMainThread());
if (CurrentMode() != MirrorMode::On) {
// OnMirrorPrefChange owns the release and handover paths.
return;
}
// Compute the ContentClassifier engine lists from the ETP source prefs for
// the normal and private prefs.
nsAutoCString protectionEngines;
nsAutoCString protectionEnginesPBM;
nsAutoCString annotationEngines;
nsAutoCString annotationEnginesPBM;
BuildEngineList(Span(kProtectionMappings), false, protectionEngines);
BuildEngineList(Span(kProtectionMappings), true, protectionEnginesPBM);
BuildEngineList(Span(kAnnotationMappings), false, annotationEngines);
BuildEngineList(Span(kAnnotationMappings), true, annotationEnginesPBM);
AppendExceptionEngines(protectionEngines);
AppendExceptionEngines(protectionEnginesPBM);
Preferences::SetCString(kProtectionEnginesPref, protectionEngines);
Preferences::SetCString(kProtectionEnginesPBMPref, protectionEnginesPBM);
Preferences::SetCString(kAnnotationEnginesPref, annotationEngines);
Preferences::SetCString(kAnnotationEnginesPBMPref, annotationEnginesPBM);
Preferences::SetBool(
kProtectionEnabledPref,
!protectionEngines.IsEmpty() || !protectionEnginesPBM.IsEmpty());
Preferences::SetBool(
kAnnotationEnabledPref,
!annotationEngines.IsEmpty() || !annotationEnginesPBM.IsEmpty());
// Claim the prefs above so that a later disable knows to give them back,
// including after a restart.
Preferences::SetBool(kOwnsPrefsPref, true);
}
} // namespace mozilla