Files
camoufox/patches/force-default-pointer.patch
T
Jake WriterandClaude Opus 5 a80abb452a feat(patches): report a touchscreen digitizer, not a phone
navigator.maxTouchPoints could already be spoofed, but nothing moved with it,
so a spoofed digitizer contradicted itself in two places a script reads in one
line: (any-pointer: coarse) stayed false, and window.TouchEvent and
window.Touch were absent entirely.

Restore the aID branch in force-default-pointer.patch so the coarse bit joins
the *any-pointer* set, and only when maxTouchPoints > 0. The primary pointer
stays Fine|Hover: a touchscreen laptop still drives its trackpad, and
reporting (pointer: coarse) would claim a phone while the accompanying desktop
UA said otherwise. The host LookAndFeel value is still not consulted -- the
capability set must not vary with the machine the browser runs on.

Expose the touch interfaces by moving TouchEvent::PrefEnabled only, never
LegacyAPIEnabled. dom.w3c_touch_events.legacy_apis.enabled is false everywhere
but Android, so a real Windows touchscreen laptop exposes TouchEvent and Touch
while 'ontouchstart' in window is false. Matching that shape matters more than
exposing the whole touch API: a build that switches touch on wholesale is more
detectable than one that does nothing.

Rename mobile-fingerprint-spoofing.patch to touchscreen-fingerprint-spoofing
.patch, since the rationale is the ordinary Windows touchscreen laptop rather
than a phone, and carry the new TouchEvent.cpp hunk there beside the existing
Navigator.cpp one. The rename moves it after navigator-spoofing.patch in
basename order, so its Navigator.cpp hunk now lands with an offset; verified
to still apply cleanly with no rejects.

Warn at launch whenever navigator.maxTouchPoints is set, separately from the
blanket navigator warning, because the knock-on effects reach past navigator
into the CSS pointer media queries and the TouchEvent interfaces.

tests/patches/touchscreen-digitizer.py checks all 16 signals and asserts that
maxTouchPoints=0 still looks like a machine with no digitizer. It fails on a
binary built without this change (13/16) and passes on one built with it.

The reference values it carries are RECONSTRUCTED, not captured: the recording
from the Dell XPS 15 9510 was not reachable from the build host, so eight
values come from the specification and eight from Gecko's own gating logic.
Each is marked in the table. Check them against the real capture when the
reference machine is available; the capture wins.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2RfR387Mh1JhZ9LZvkptP
2026-09-05 17:30:17 -06:00

58 lines
2.2 KiB
Diff

diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp
--- a/layout/style/nsMediaFeatures.cpp
+++ b/layout/style/nsMediaFeatures.cpp
@@ -408,34 +433,41 @@ static PointerCapabilities GetPointerCapabilities(const Document* aDocument,
// that we don't need to care about ResistFingerprinting.
if (bc->TouchEventsOverride() == dom::TouchEventsOverride::Enabled) {
return PointerCapabilities::Coarse;
}
}
// The default value for Desktop is mouse-type pointer, and for Android
// a coarse pointer.
- const PointerCapabilities kDefaultCapabilities =
#ifdef ANDROID
- PointerCapabilities::Coarse;
+ return PointerCapabilities::Coarse;
#else
+ // Report the desktop default rather than whatever the host toolkit
+ // advertises, so the capability set never varies with the machine the
+ // browser happens to be running on.
+ PointerCapabilities capabilities =
PointerCapabilities::Fine | PointerCapabilities::Hover;
-#endif
- if (aDocument->ShouldResistFingerprinting(
- RFPTarget::CSSPointerCapabilities)) {
- return kDefaultCapabilities;
- }
- int32_t intValue;
- nsresult rv = LookAndFeel::GetInt(aID, &intValue);
- if (NS_FAILED(rv)) {
- return kDefaultCapabilities;
+ // A touchscreen laptop still drives a fine, hovering *primary* pointer --
+ // its trackpad -- and the digitizer only ever joins the `any-pointer` set.
+ // Keeping that split is the whole point: a spoofed maxTouchPoints paired
+ // with `(pointer: coarse)` would claim a phone, while the accompanying
+ // desktop UA said otherwise.
+ if (aID == LookAndFeel::IntID::AllPointerCapabilities) {
+ if (auto maxTouchPoints =
+ MaskConfig::GetUint32("navigator.maxTouchPoints")) {
+ if (maxTouchPoints.value() > 0) {
+ capabilities |= PointerCapabilities::Coarse;
+ }
+ }
}
- return static_cast<PointerCapabilities>(intValue);
+ return capabilities;
+#endif
}
PointerCapabilities Gecko_MediaFeatures_PrimaryPointerCapabilities(
const Document* aDocument) {
return GetPointerCapabilities(aDocument,
LookAndFeel::IntID::PrimaryPointerCapabilities);
}