summaryrefslogtreecommitdiffstats
path: root/toolkit/components/resistfingerprinting
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/resistfingerprinting')
-rw-r--r--toolkit/components/resistfingerprinting/RFPHelper.sys.mjs2
-rw-r--r--toolkit/components/resistfingerprinting/metrics.yaml384
-rw-r--r--toolkit/components/resistfingerprinting/moz.build12
-rw-r--r--toolkit/components/resistfingerprinting/nsRFPService.cpp38
-rw-r--r--toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp297
-rw-r--r--toolkit/components/resistfingerprinting/nsUserCharacteristics.h28
-rw-r--r--toolkit/components/resistfingerprinting/pings.yaml19
-rw-r--r--toolkit/components/resistfingerprinting/tests/gtest/moz.build1
-rw-r--r--toolkit/components/resistfingerprinting/tests/gtest/test_usercharping.cpp143
9 files changed, 923 insertions, 1 deletions
diff --git a/toolkit/components/resistfingerprinting/RFPHelper.sys.mjs b/toolkit/components/resistfingerprinting/RFPHelper.sys.mjs
index a085aa492a..223c0259b7 100644
--- a/toolkit/components/resistfingerprinting/RFPHelper.sys.mjs
+++ b/toolkit/components/resistfingerprinting/RFPHelper.sys.mjs
@@ -20,7 +20,7 @@ var logConsole;
function log(msg) {
if (!logConsole) {
logConsole = console.createInstance({
- prefix: "RFPHelper.jsm",
+ prefix: "RFPHelper",
maxLogLevelPref: "privacy.resistFingerprinting.jsmloglevel",
});
}
diff --git a/toolkit/components/resistfingerprinting/metrics.yaml b/toolkit/components/resistfingerprinting/metrics.yaml
index 916c8c7871..3c706d20fa 100644
--- a/toolkit/components/resistfingerprinting/metrics.yaml
+++ b/toolkit/components/resistfingerprinting/metrics.yaml
@@ -25,3 +25,387 @@ fingerprinting.protection:
- tschuster@mozilla.com
expires: never
telemetry_mirror: FINGERPRINTING_PROTECTION_CANVAS_NOISE_CALCULATE_TIME_MS
+
+
+characteristics:
+ client_identifier:
+ type: uuid
+ description: >
+ A unique identifier for a user, not the same as the normal Telemetry
+ client_id, but needed so we can deduplicate reports and only take the most
+ recent one per user.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ - deletion-request
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879154
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879154#c8
+ expires: never
+ data_sensitivity:
+ - technical
+
+ submission_schema:
+ type: quantity
+ unit: versions
+ description: >
+ An incrementing constant that represents the current schema/source of the
+ data present in a ping. By referring to this value in a ping, one can know
+ for certain the provenance of other data present in the ping, and what
+ data may or may not be present.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879154
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879154#c8
+ expires: never
+
+ max_touch_points:
+ type: quantity
+ unit: Fingers
+ description: >
+ The number of touch points we will report to the web. On Android, this is
+ based on Android's FEATURE_TOUCHSCREEN* constants - Mozilla caps this at 5
+ as Android stops distinguishing between numbers greater than 5. On
+ Windows this comes from the SM_MAXIMUMTOUCHES System Metric.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879156
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879156#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ video_dynamic_range:
+ type: boolean
+ description: >
+ What LookAndFeel(VideoDynamicRange) reports. Note that CSSVideoDynamicRange
+ has an additional dependency on Color Depth.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ prefers_reduced_transparency:
+ type: boolean
+ description: >
+ What LookAndFeel(PrefersReducedTransparency) reports.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ prefers_reduced_motion:
+ type: boolean
+ description: >
+ What LookAndFeel(PrefersReducedMotion) reports.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ prefers_contrast:
+ type: quantity
+ unit: enum StylePrefersContrast value
+ description: >
+ What Gecko_MediaFeatures_PrefersContrast reports for a ContentDocument
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ inverted_colors:
+ type: boolean
+ description: >
+ What LookAndFeel(InvertedColors) reports.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ color_scheme:
+ type: quantity
+ unit: enum mozilla::ColorScheme value
+ description: >
+ The Color Scheme used for Content, from ContentPrefs() Preference Sheet.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ color_gamut:
+ type: quantity
+ unit: enum dom::ScreenColorGamut value
+ description: >
+ The Color Gamut reported by CSS
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ color_depth:
+ type: quantity
+ unit: bits
+ description: >
+ The Color Depth reported by CSS
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ missing_fonts:
+ type: text
+ description: >
+ If a Font List is available for the user's platform, this
+ string_list contains the fonts that are missing from the user's
+ computer.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1880561
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1880561#c6
+ expires: never
+ data_sensitivity:
+ # Text metrics are _required_ to be web_activity or highly_sensitive, so even though this
+ # is more like 'technical' (per the Data Review), I'm marking highly sensitive.
+ - highly_sensitive
+
+ screen_width:
+ type: quantity
+ unit: pixels
+ description: >
+ Width of the primary screen in pixels.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881749
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881749#c5
+ expires: never
+ data_sensitivity:
+ - technical
+
+ screen_height:
+ type: quantity
+ unit: pixels
+ description: >
+ Height of the primary screen in pixels.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881749
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881749#c5
+ expires: never
+ data_sensitivity:
+ - technical
+
+ processor_count:
+ type: quantity
+ unit: int
+ description: >
+ Number of processors.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881759
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881759#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ timezone:
+ type: string
+ description: >
+ The the current timezone of the system
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881773
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881773#c4
+ expires: never
+ data_sensitivity:
+ - interaction
+
+ target_frame_rate:
+ type: quantity
+ unit: int
+ description: >
+ The target frame rate in frames-per-second.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882054
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882054#c3
+ expires: never
+ data_sensitivity:
+ - technical
+
+ prefs_intl_accept_languages:
+ type: string
+ description: >
+ Value of the intl.accept_languages pref.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882482
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882482#c6
+ expires: never
+ data_sensitivity:
+ - interaction
+
+ prefs_media_eme_enabled:
+ type: boolean
+ description: >
+ Value of the media.eme.enabled pref.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882482
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882482#c6
+ expires: never
+ data_sensitivity:
+ - interaction
+
+ prefs_zoom_text_only:
+ type: boolean
+ description: >
+ Text-only zoom enabled (vs. full-zoom)
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882482
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882482#c6
+ expires: never
+ data_sensitivity:
+ - interaction
diff --git a/toolkit/components/resistfingerprinting/moz.build b/toolkit/components/resistfingerprinting/moz.build
index 6ac25d3b76..9b4e9cc8ed 100644
--- a/toolkit/components/resistfingerprinting/moz.build
+++ b/toolkit/components/resistfingerprinting/moz.build
@@ -13,6 +13,17 @@ UNIFIED_SOURCES += [
"nsRFPService.cpp",
"RelativeTimeline.cpp",
]
+# Because nsUserCharacteristics doesn't `use namespace mozilla` (because we're going to wind
+# up with a million includes on this file and pollution will get confusing), unified build
+# will mask a lot of errors that would cause backouts. This exposes them locally.
+SOURCES += [
+ "nsUserCharacteristics.cpp",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ LOCAL_INCLUDES += [
+ "/xpcom/base",
+ ]
SPHINX_TREES["resistfingerprinting"] = "docs"
@@ -23,6 +34,7 @@ EXPORTS.mozilla += [
"RelativeTimeline.h",
"RFPTargetIPCUtils.h",
]
+EXPORTS.mozilla.gtest += ["nsUserCharacteristics.h"]
EXTRA_JS_MODULES += [
"FingerprintingWebCompatService.sys.mjs",
diff --git a/toolkit/components/resistfingerprinting/nsRFPService.cpp b/toolkit/components/resistfingerprinting/nsRFPService.cpp
index 8579fe2a3b..643cc2cb7a 100644
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -71,6 +71,7 @@
#include "nsTLiteralString.h"
#include "nsTPromiseFlatString.h"
#include "nsTStringRepr.h"
+#include "nsUserCharacteristics.h"
#include "nsXPCOM.h"
#include "nsICookieJarSettings.h"
@@ -100,8 +101,14 @@ static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection");
#define RESIST_FINGERPRINTINGPROTECTION_OVERRIDE_PREF \
"privacy.fingerprintingProtection.overrides"
+#define GLEAN_DATA_SUBMISSION_PREF "datareporting.healthreport.uploadEnabled"
+#define USER_CHARACTERISTICS_UUID_PREF \
+ "toolkit.telemetry.user_characteristics_ping.uuid"
+
#define RFP_TIMER_UNCONDITIONAL_VALUE 20
#define LAST_PB_SESSION_EXITED_TOPIC "last-pb-context-exited"
+#define IDLE_TOPIC "browser-idle-startup-tasks-finished"
+#define GFX_FEATURES "gfx-features-ready"
static constexpr uint32_t kVideoFramesPerSec = 30;
static constexpr uint32_t kVideoDroppedRatio = 5;
@@ -156,6 +163,7 @@ already_AddRefed<nsRFPService> nsRFPService::GetOrCreate() {
static const char* gCallbackPrefs[] = {
RESIST_FINGERPRINTINGPROTECTION_OVERRIDE_PREF,
+ GLEAN_DATA_SUBMISSION_PREF,
nullptr,
};
@@ -176,6 +184,12 @@ nsresult nsRFPService::Init() {
rv = obs->AddObserver(this, OBSERVER_TOPIC_IDLE_DAILY, false);
NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = obs->AddObserver(this, IDLE_TOPIC, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = obs->AddObserver(this, GFX_FEATURES, false);
+ NS_ENSURE_SUCCESS(rv, rv);
}
Preferences::RegisterCallbacks(nsRFPService::PrefChanged, gCallbackPrefs,
@@ -273,6 +287,8 @@ void nsRFPService::StartShutdown() {
if (XRE_IsParentProcess()) {
obs->RemoveObserver(this, LAST_PB_SESSION_EXITED_TOPIC);
obs->RemoveObserver(this, OBSERVER_TOPIC_IDLE_DAILY);
+ obs->RemoveObserver(this, IDLE_TOPIC);
+ obs->RemoveObserver(this, GFX_FEATURES);
}
}
@@ -290,16 +306,30 @@ void nsRFPService::PrefChanged(const char* aPref, void* aSelf) {
}
void nsRFPService::PrefChanged(const char* aPref) {
+ MOZ_LOG(gResistFingerprintingLog, LogLevel::Info,
+ ("Pref Changed: %s", aPref));
nsDependentCString pref(aPref);
if (pref.EqualsLiteral(RESIST_FINGERPRINTINGPROTECTION_OVERRIDE_PREF)) {
UpdateFPPOverrideList();
+ } else if (pref.EqualsLiteral(GLEAN_DATA_SUBMISSION_PREF)) {
+ if (XRE_IsParentProcess() &&
+ !Preferences::GetBool(GLEAN_DATA_SUBMISSION_PREF, false)) {
+ MOZ_LOG(gResistFingerprintingLog, LogLevel::Info, ("Clearing UUID"));
+ // If the user has unset the telemetry pref, wipe out the UUID pref value
+ // (The data will also be erased server-side via the "deletion-request"
+ // ping)
+ Preferences::SetCString(USER_CHARACTERISTICS_UUID_PREF, ""_ns);
+ }
}
}
NS_IMETHODIMP
nsRFPService::Observe(nsISupports* aObject, const char* aTopic,
const char16_t* aMessage) {
+ const int kNumTopicsForUserCharacteristics = 2;
+ static int seenTopicsForUserCharacteristics = 0;
+
if (strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic) == 0) {
StartShutdown();
}
@@ -312,6 +342,14 @@ nsRFPService::Observe(nsISupports* aObject, const char* aTopic,
ClearBrowsingSessionKey(pattern);
}
+ if (!strcmp(IDLE_TOPIC, aTopic) || !strcmp(GFX_FEATURES, aTopic)) {
+ seenTopicsForUserCharacteristics++;
+
+ if (seenTopicsForUserCharacteristics == kNumTopicsForUserCharacteristics) {
+ nsUserCharacteristics::MaybeSubmitPing();
+ }
+ }
+
if (!strcmp(OBSERVER_TOPIC_IDLE_DAILY, aTopic)) {
if (StaticPrefs::
privacy_resistFingerprinting_randomization_daily_reset_enabled()) {
diff --git a/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp b/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp
new file mode 100644
index 0000000000..9bce616f81
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp
@@ -0,0 +1,297 @@
+/* -*- Mode: C++; 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 "nsUserCharacteristics.h"
+
+#include "nsID.h"
+#include "nsIUUIDGenerator.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/glean/GleanPings.h"
+#include "mozilla/glean/GleanMetrics.h"
+
+#include "mozilla/StaticPrefs_media.h"
+
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/PreferenceSheet.h"
+#include "mozilla/RelativeLuminanceUtils.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "mozilla/dom/ScreenBinding.h"
+#include "mozilla/intl/TimeZone.h"
+#include "mozilla/widget/ScreenManager.h"
+
+#include "gfxPlatformFontList.h"
+#include "prsystem.h"
+#if defined(XP_WIN)
+# include "WinUtils.h"
+#elif defined(MOZ_WIDGET_ANDROID)
+# include "mozilla/java/GeckoAppShellWrappers.h"
+#elif defined(XP_MACOSX)
+# include "nsMacUtilsImpl.h"
+#endif
+
+static mozilla::LazyLogModule gUserCharacteristicsLog("UserCharacteristics");
+
+// ==================================================================
+namespace testing {
+extern "C" {
+
+int MaxTouchPoints() {
+#if defined(XP_WIN)
+ return mozilla::widget::WinUtils::GetMaxTouchPoints();
+#elif defined(MOZ_WIDGET_ANDROID)
+ return mozilla::java::GeckoAppShell::GetMaxTouchPoints();
+#else
+ return 0;
+#endif
+}
+
+} // extern "C"
+}; // namespace testing
+
+// ==================================================================
+void PopulateCSSProperties() {
+ mozilla::glean::characteristics::video_dynamic_range.Set(
+ mozilla::LookAndFeel::GetInt(
+ mozilla::LookAndFeel::IntID::VideoDynamicRange));
+ mozilla::glean::characteristics::prefers_reduced_transparency.Set(
+ mozilla::LookAndFeel::GetInt(
+ mozilla::LookAndFeel::IntID::PrefersReducedTransparency));
+ mozilla::glean::characteristics::prefers_reduced_motion.Set(
+ mozilla::LookAndFeel::GetInt(
+ mozilla::LookAndFeel::IntID::PrefersReducedMotion));
+ mozilla::glean::characteristics::inverted_colors.Set(
+ mozilla::LookAndFeel::GetInt(
+ mozilla::LookAndFeel::IntID::InvertedColors));
+ mozilla::glean::characteristics::color_scheme.Set(
+ (int)mozilla::PreferenceSheet::ContentPrefs().mColorScheme);
+
+ mozilla::StylePrefersContrast prefersContrast = [] {
+ // Replicates Gecko_MediaFeatures_PrefersContrast but without a Document
+ if (!mozilla::PreferenceSheet::ContentPrefs().mUseAccessibilityTheme &&
+ mozilla::PreferenceSheet::ContentPrefs().mUseDocumentColors) {
+ return mozilla::StylePrefersContrast::NoPreference;
+ }
+
+ const auto& colors = mozilla::PreferenceSheet::ContentPrefs().ColorsFor(
+ mozilla::ColorScheme::Light);
+ float ratio = mozilla::RelativeLuminanceUtils::ContrastRatio(
+ colors.mDefaultBackground, colors.mDefault);
+ // https://www.w3.org/TR/WCAG21/#contrast-minimum
+ if (ratio < 4.5f) {
+ return mozilla::StylePrefersContrast::Less;
+ }
+ // https://www.w3.org/TR/WCAG21/#contrast-enhanced
+ if (ratio >= 7.0f) {
+ return mozilla::StylePrefersContrast::More;
+ }
+ return mozilla::StylePrefersContrast::Custom;
+ }();
+ mozilla::glean::characteristics::prefers_contrast.Set((int)prefersContrast);
+}
+
+void PopulateScreenProperties() {
+ auto& screenManager = mozilla::widget::ScreenManager::GetSingleton();
+ RefPtr<mozilla::widget::Screen> screen = screenManager.GetPrimaryScreen();
+ MOZ_ASSERT(screen);
+
+ mozilla::dom::ScreenColorGamut colorGamut;
+ screen->GetColorGamut(&colorGamut);
+ mozilla::glean::characteristics::color_gamut.Set((int)colorGamut);
+
+ int32_t colorDepth;
+ screen->GetColorDepth(&colorDepth);
+ mozilla::glean::characteristics::color_depth.Set(colorDepth);
+
+ mozilla::glean::characteristics::color_gamut.Set((int)colorGamut);
+ mozilla::glean::characteristics::color_depth.Set(colorDepth);
+ const mozilla::LayoutDeviceIntRect rect = screen->GetRect();
+ mozilla::glean::characteristics::screen_height.Set(rect.Height());
+ mozilla::glean::characteristics::screen_width.Set(rect.Width());
+}
+
+void PopulateMissingFonts() {
+ nsCString aMissingFonts;
+ gfxPlatformFontList::PlatformFontList()->GetMissingFonts(aMissingFonts);
+
+ mozilla::glean::characteristics::missing_fonts.Set(aMissingFonts);
+}
+
+void PopulatePrefs() {
+ nsAutoCString acceptLang;
+ mozilla::Preferences::GetLocalizedCString("intl.accept_languages",
+ acceptLang);
+ mozilla::glean::characteristics::prefs_intl_accept_languages.Set(acceptLang);
+
+ mozilla::glean::characteristics::prefs_media_eme_enabled.Set(
+ mozilla::StaticPrefs::media_eme_enabled());
+
+ mozilla::glean::characteristics::prefs_zoom_text_only.Set(
+ !mozilla::Preferences::GetBool("browser.zoom.full"));
+}
+
+// ==================================================================
+// The current schema of the data. Anytime you add a metric, or change how a
+// metric is set, this variable should be incremented. It'll be a lot. It's
+// okay. We're going to need it to know (including during development) what is
+// the source of the data we are looking at.
+const int kSubmissionSchema = 0;
+
+/* static */
+void nsUserCharacteristics::MaybeSubmitPing() {
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Debug,
+ ("In MaybeSubmitPing()"));
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ /**
+ * There are two preferences at play here:
+ * - Last Version Sent - preference containing the last version sent by the
+ * user to Mozilla
+ * - Current Version - preference containing the version Mozilla would like
+ * the user to send
+ *
+ * A point of complexity arises in that these two values _may_ be changed
+ * by the user, even though neither is intended to be.
+ *
+ * When Current Version > Last Version Sent, we intend for the user to submit
+ * a new ping, which will include the schema version. Then update Last Version
+ * Sent = Current Version.
+ *
+ */
+ const auto* const kLastVersionPref =
+ "toolkit.telemetry.user_characteristics_ping.last_version_sent";
+ const auto* const kCurrentVersionPref =
+ "toolkit.telemetry.user_characteristics_ping.current_version";
+
+ auto lastSubmissionVersion =
+ mozilla::Preferences::GetInt(kLastVersionPref, 0);
+ auto currentVersion = mozilla::Preferences::GetInt(kCurrentVersionPref, 0);
+
+ MOZ_ASSERT(currentVersion == -1 || lastSubmissionVersion <= currentVersion,
+ "lastSubmissionVersion is somehow greater than currentVersion "
+ "- did you edit prefs improperly?");
+
+ if (lastSubmissionVersion < 0) {
+ // This is a way for users to opt out of this ping specifically.
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Debug,
+ ("Returning, User Opt-out"));
+ return;
+ }
+ if (currentVersion == 0) {
+ // Do nothing. We do not want any pings.
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Debug,
+ ("Returning, currentVersion == 0"));
+ return;
+ }
+ if (currentVersion == -1) {
+ // currentVersion = -1 is a development value to force a ping submission
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Debug,
+ ("Force-Submitting Ping"));
+ if (NS_SUCCEEDED(PopulateData())) {
+ SubmitPing();
+ }
+ return;
+ }
+ if (lastSubmissionVersion > currentVersion) {
+ // This is an unexpected scneario that indicates something is wrong. We
+ // asserted against it (in debug, above) We will try to sanity-correct
+ // ourselves by setting it to the current version.
+ mozilla::Preferences::SetInt(kLastVersionPref, currentVersion);
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Warning,
+ ("Returning, lastSubmissionVersion > currentVersion"));
+ return;
+ }
+ if (lastSubmissionVersion == currentVersion) {
+ // We are okay, we've already submitted the most recent ping
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Warning,
+ ("Returning, lastSubmissionVersion == currentVersion"));
+ return;
+ }
+ if (lastSubmissionVersion < currentVersion) {
+ if (NS_SUCCEEDED(PopulateData())) {
+ if (NS_SUCCEEDED(SubmitPing())) {
+ mozilla::Preferences::SetInt(kLastVersionPref, currentVersion);
+ }
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Should never reach here");
+ }
+}
+
+const auto* const kUUIDPref =
+ "toolkit.telemetry.user_characteristics_ping.uuid";
+
+/* static */
+nsresult nsUserCharacteristics::PopulateData(bool aTesting /* = false */) {
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Warning,
+ ("Populating Data"));
+ MOZ_ASSERT(XRE_IsParentProcess());
+ mozilla::glean::characteristics::submission_schema.Set(kSubmissionSchema);
+
+ nsAutoCString uuidString;
+ nsresult rv = mozilla::Preferences::GetCString(kUUIDPref, uuidString);
+ if (NS_FAILED(rv) || uuidString.Length() == 0) {
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIDToCString id(nsID::GenerateUUID());
+ uuidString = id.get();
+ mozilla::Preferences::SetCString(kUUIDPref, uuidString);
+ }
+ mozilla::glean::characteristics::client_identifier.Set(uuidString);
+
+ mozilla::glean::characteristics::max_touch_points.Set(
+ testing::MaxTouchPoints());
+
+ if (aTesting) {
+ // Many of the later peices of data do not work in a gtest
+ // so just populate something, and return
+ return NS_OK;
+ }
+
+ PopulateMissingFonts();
+ PopulateCSSProperties();
+ PopulateScreenProperties();
+ PopulatePrefs();
+
+ mozilla::glean::characteristics::target_frame_rate.Set(
+ gfxPlatform::TargetFrameRate());
+
+ int32_t processorCount = 0;
+#if defined(XP_MACOSX)
+ if (nsMacUtilsImpl::IsTCSMAvailable()) {
+ // On failure, zero is returned from GetPhysicalCPUCount()
+ // and we fallback to PR_GetNumberOfProcessors below.
+ processorCount = nsMacUtilsImpl::GetPhysicalCPUCount();
+ }
+#endif
+ if (processorCount == 0) {
+ processorCount = PR_GetNumberOfProcessors();
+ }
+ mozilla::glean::characteristics::processor_count.Set(processorCount);
+
+ AutoTArray<char16_t, 128> tzBuffer;
+ auto result = mozilla::intl::TimeZone::GetDefaultTimeZone(tzBuffer);
+ if (result.isOk()) {
+ NS_ConvertUTF16toUTF8 timeZone(
+ nsDependentString(tzBuffer.Elements(), tzBuffer.Length()));
+ mozilla::glean::characteristics::timezone.Set(timeZone);
+ } else {
+ mozilla::glean::characteristics::timezone.Set("<error>"_ns);
+ }
+
+ return NS_OK;
+}
+
+/* static */
+nsresult nsUserCharacteristics::SubmitPing() {
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Warning,
+ ("Submitting Ping"));
+ mozilla::glean_pings::UserCharacteristics.Submit();
+
+ return NS_OK;
+}
diff --git a/toolkit/components/resistfingerprinting/nsUserCharacteristics.h b/toolkit/components/resistfingerprinting/nsUserCharacteristics.h
new file mode 100644
index 0000000000..a52bc9aea7
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/nsUserCharacteristics.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef __nsUserCharacteristics_h__
+#define __nsUserCharacteristics_h__
+
+#include "ErrorList.h"
+
+class nsUserCharacteristics {
+ public:
+ static void MaybeSubmitPing();
+
+ // Public For testing
+ static nsresult PopulateData(bool aTesting = false);
+ static nsresult SubmitPing();
+};
+
+namespace testing {
+extern "C" { // Needed to call these in the gtest
+
+int MaxTouchPoints();
+
+} // extern "C"
+}; // namespace testing
+
+#endif /* __nsUserCharacteristics_h__ */
diff --git a/toolkit/components/resistfingerprinting/pings.yaml b/toolkit/components/resistfingerprinting/pings.yaml
new file mode 100644
index 0000000000..46a4b2da19
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/pings.yaml
@@ -0,0 +1,19 @@
+# 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/.
+
+---
+$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
+
+user-characteristics:
+ description: |
+ A ping representing user hardware and software settings. Note that this
+ ping does not include client_id. More details are available in Bug 1879151
+ include_client_id: false
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879154
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879154#c8
+ notification_emails:
+ - tritter@mozilla.com
diff --git a/toolkit/components/resistfingerprinting/tests/gtest/moz.build b/toolkit/components/resistfingerprinting/tests/gtest/moz.build
index dcc6a7e12e..a677def009 100644
--- a/toolkit/components/resistfingerprinting/tests/gtest/moz.build
+++ b/toolkit/components/resistfingerprinting/tests/gtest/moz.build
@@ -1,5 +1,6 @@
UNIFIED_SOURCES += [
"test_reduceprecision.cpp",
+ "test_usercharping.cpp",
]
FINAL_LIBRARY = "xul-gtest"
diff --git a/toolkit/components/resistfingerprinting/tests/gtest/test_usercharping.cpp b/toolkit/components/resistfingerprinting/tests/gtest/test_usercharping.cpp
new file mode 100644
index 0000000000..dd1cbe7d46
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/tests/gtest/test_usercharping.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * 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 "gtest/gtest.h"
+#include "mozilla/gtest/nsUserCharacteristics.h"
+
+#include "mozilla/glean/GleanPings.h"
+#include "mozilla/glean/GleanMetrics.h"
+
+using namespace mozilla;
+
+const auto* const kUUIDPref =
+ "toolkit.telemetry.user_characteristics_ping.uuid";
+
+TEST(ResistFingerprinting, UserCharacteristics_Simple)
+{
+ mozilla::glean::characteristics::max_touch_points.Set(7);
+
+ bool submitted = false;
+ mozilla::glean_pings::UserCharacteristics.TestBeforeNextSubmit(
+ [&submitted](const nsACString& aReason) {
+ submitted = true;
+
+ ASSERT_EQ(
+ 7, mozilla::glean::characteristics::max_touch_points.TestGetValue()
+ .unwrap()
+ .ref());
+ });
+ mozilla::glean_pings::UserCharacteristics.Submit();
+ ASSERT_TRUE(submitted);
+}
+
+TEST(ResistFingerprinting, UserCharacteristics_Complex)
+{
+ nsUserCharacteristics::PopulateData(true);
+
+ bool submitted = false;
+ mozilla::glean_pings::UserCharacteristics.TestBeforeNextSubmit(
+ [&submitted](const nsACString& aReason) {
+ submitted = true;
+
+ ASSERT_STRNE("", mozilla::glean::characteristics::client_identifier
+ .TestGetValue()
+ .unwrap()
+ .value()
+ .get());
+
+ nsCString fullUuidStr;
+ Preferences::GetCString(kUUIDPref, fullUuidStr);
+
+ // Remove the '{' and '}'
+ nsAutoCString uuidString;
+ uuidString = Substring(fullUuidStr, 1, NSID_LENGTH - 3);
+
+ ASSERT_STREQ(
+ uuidString.get(),
+ mozilla::glean::characteristics::client_identifier.TestGetValue()
+ .unwrap()
+ .value()
+ .get());
+ ASSERT_EQ(
+ testing::MaxTouchPoints(),
+ mozilla::glean::characteristics::max_touch_points.TestGetValue()
+ .unwrap()
+ .ref());
+ });
+ nsUserCharacteristics::SubmitPing();
+ ASSERT_TRUE(submitted);
+}
+
+TEST(ResistFingerprinting, UserCharacteristics_ClearPref)
+{
+ nsCString originalUUID;
+
+ mozilla::glean_pings::UserCharacteristics.TestBeforeNextSubmit(
+ [&originalUUID](const nsACString& aReason) {
+ originalUUID =
+ mozilla::glean::characteristics::client_identifier.TestGetValue()
+ .unwrap()
+ .value()
+ .get();
+ ASSERT_STRNE("", mozilla::glean::characteristics::client_identifier
+ .TestGetValue()
+ .unwrap()
+ .value()
+ .get());
+
+ nsCString fullUuidStr;
+ Preferences::GetCString(kUUIDPref, fullUuidStr);
+
+ // Remove the '{' and '}'
+ nsAutoCString uuidString;
+ uuidString = Substring(fullUuidStr, 1, NSID_LENGTH - 3);
+
+ ASSERT_STREQ(
+ uuidString.get(),
+ mozilla::glean::characteristics::client_identifier.TestGetValue()
+ .unwrap()
+ .value()
+ .get());
+ });
+ nsUserCharacteristics::PopulateData(true);
+ nsUserCharacteristics::SubmitPing();
+
+ auto original_value =
+ Preferences::GetBool("datareporting.healthreport.uploadEnabled");
+ Preferences::SetBool("datareporting.healthreport.uploadEnabled", true);
+ Preferences::SetBool("datareporting.healthreport.uploadEnabled", false);
+
+ mozilla::glean_pings::UserCharacteristics.TestBeforeNextSubmit(
+ [](const nsACString& aReason) {
+ // Assert that the pref is blank
+ nsAutoCString uuidValue;
+ Preferences::GetCString(kUUIDPref, uuidValue);
+ ASSERT_STREQ("", uuidValue.get());
+ });
+ nsUserCharacteristics::SubmitPing();
+
+ Preferences::SetBool("datareporting.healthreport.uploadEnabled", true);
+ mozilla::glean_pings::UserCharacteristics.TestBeforeNextSubmit(
+ [&originalUUID](const nsACString& aReason) {
+ // Assert that the new UUID is different from the old one
+ ASSERT_STRNE(
+ originalUUID.get(),
+ mozilla::glean::characteristics::client_identifier.TestGetValue()
+ .unwrap()
+ .value()
+ .get());
+
+ // Assert that the pref is not blank
+ nsAutoCString uuidValue;
+ Preferences::GetCString(kUUIDPref, uuidValue);
+ ASSERT_STRNE("", uuidValue.get());
+ });
+ nsUserCharacteristics::PopulateData(true);
+ nsUserCharacteristics::SubmitPing();
+
+ Preferences::SetBool("datareporting.healthreport.uploadEnabled",
+ original_value);
+}