1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsRFPService.h"
7 
8 #include <algorithm>
9 #include <cfloat>
10 #include <cinttypes>
11 #include <cmath>
12 #include <cstdlib>
13 #include <cstring>
14 #include <ctime>
15 #include <new>
16 #include <type_traits>
17 #include <utility>
18 
19 #include "MainThreadUtils.h"
20 
21 #include "mozilla/ArrayIterator.h"
22 #include "mozilla/Assertions.h"
23 #include "mozilla/Atomics.h"
24 #include "mozilla/Casting.h"
25 #include "mozilla/ClearOnShutdown.h"
26 #include "mozilla/HashFunctions.h"
27 #include "mozilla/HelperMacros.h"
28 #include "mozilla/Likely.h"
29 #include "mozilla/Logging.h"
30 #include "mozilla/MacroForEach.h"
31 #include "mozilla/Preferences.h"
32 #include "mozilla/RefPtr.h"
33 #include "mozilla/Services.h"
34 #include "mozilla/StaticPrefs_javascript.h"
35 #include "mozilla/StaticPrefs_privacy.h"
36 #include "mozilla/StaticPtr.h"
37 #include "mozilla/TextEvents.h"
38 #include "mozilla/dom/Document.h"
39 #include "mozilla/dom/Element.h"
40 #include "mozilla/dom/KeyboardEventBinding.h"
41 #include "mozilla/fallible.h"
42 #include "mozilla/XorShift128PlusRNG.h"
43 
44 #include "nsBaseHashtable.h"
45 #include "nsCOMPtr.h"
46 #include "nsComponentManagerUtils.h"
47 #include "nsCoord.h"
48 #include "nsTHashMap.h"
49 #include "nsDebug.h"
50 #include "nsError.h"
51 #include "nsHashKeys.h"
52 #include "nsJSUtils.h"
53 #include "nsLiteralString.h"
54 #include "nsPrintfCString.h"
55 #include "nsServiceManagerUtils.h"
56 #include "nsString.h"
57 #include "nsStringFlags.h"
58 #include "nsTArray.h"
59 #include "nsTLiteralString.h"
60 #include "nsTPromiseFlatString.h"
61 #include "nsTStringRepr.h"
62 #include "nsXPCOM.h"
63 
64 #include "nsICryptoHash.h"
65 #include "nsIGlobalObject.h"
66 #include "nsIObserverService.h"
67 #include "nsIRandomGenerator.h"
68 #include "nsIXULAppInfo.h"
69 
70 #include "nscore.h"
71 #include "prenv.h"
72 #include "prtime.h"
73 #include "xpcpublic.h"
74 
75 #include "js/Date.h"
76 
77 using namespace mozilla;
78 
79 static mozilla::LazyLogModule gResistFingerprintingLog(
80     "nsResistFingerprinting");
81 
82 #define RESIST_FINGERPRINTING_PREF "privacy.resistFingerprinting"
83 #define RFP_TIMER_PREF "privacy.reduceTimerPrecision"
84 #define RFP_TIMER_UNCONDITIONAL_PREF \
85   "privacy.reduceTimerPrecision.unconditional"
86 #define RFP_TIMER_UNCONDITIONAL_VALUE 20
87 #define RFP_TIMER_VALUE_PREF \
88   "privacy.resistFingerprinting.reduceTimerPrecision.microseconds"
89 #define RFP_JITTER_VALUE_PREF \
90   "privacy.resistFingerprinting.reduceTimerPrecision.jitter"
91 #define PROFILE_INITIALIZED_TOPIC "profile-initial-state"
92 
93 static constexpr uint32_t kVideoFramesPerSec = 30;
94 static constexpr uint32_t kVideoDroppedRatio = 5;
95 
96 #define RFP_DEFAULT_SPOOFING_KEYBOARD_LANG KeyboardLang::EN
97 #define RFP_DEFAULT_SPOOFING_KEYBOARD_REGION KeyboardRegion::US
98 
99 NS_IMPL_ISUPPORTS(nsRFPService, nsIObserver)
100 
101 static StaticRefPtr<nsRFPService> sRFPService;
102 static bool sInitialized = false;
103 nsTHashMap<KeyboardHashKey, const SpoofingKeyboardCode*>*
104     nsRFPService::sSpoofingKeyboardCodes = nullptr;
105 
KeyboardHashKey(const KeyboardLangs aLang,const KeyboardRegions aRegion,const KeyNameIndexType aKeyIdx,const nsAString & aKey)106 KeyboardHashKey::KeyboardHashKey(const KeyboardLangs aLang,
107                                  const KeyboardRegions aRegion,
108                                  const KeyNameIndexType aKeyIdx,
109                                  const nsAString& aKey)
110     : mLang(aLang), mRegion(aRegion), mKeyIdx(aKeyIdx), mKey(aKey) {}
111 
KeyboardHashKey(KeyTypePointer aOther)112 KeyboardHashKey::KeyboardHashKey(KeyTypePointer aOther)
113     : mLang(aOther->mLang),
114       mRegion(aOther->mRegion),
115       mKeyIdx(aOther->mKeyIdx),
116       mKey(aOther->mKey) {}
117 
KeyboardHashKey(KeyboardHashKey && aOther)118 KeyboardHashKey::KeyboardHashKey(KeyboardHashKey&& aOther)
119     : PLDHashEntryHdr(std::move(aOther)),
120       mLang(std::move(aOther.mLang)),
121       mRegion(std::move(aOther.mRegion)),
122       mKeyIdx(std::move(aOther.mKeyIdx)),
123       mKey(std::move(aOther.mKey)) {}
124 
125 KeyboardHashKey::~KeyboardHashKey() = default;
126 
KeyEquals(KeyTypePointer aOther) const127 bool KeyboardHashKey::KeyEquals(KeyTypePointer aOther) const {
128   return mLang == aOther->mLang && mRegion == aOther->mRegion &&
129          mKeyIdx == aOther->mKeyIdx && mKey == aOther->mKey;
130 }
131 
KeyToPointer(KeyType aKey)132 KeyboardHashKey::KeyTypePointer KeyboardHashKey::KeyToPointer(KeyType aKey) {
133   return &aKey;
134 }
135 
HashKey(KeyTypePointer aKey)136 PLDHashNumber KeyboardHashKey::HashKey(KeyTypePointer aKey) {
137   PLDHashNumber hash = mozilla::HashString(aKey->mKey);
138   return mozilla::AddToHash(hash, aKey->mRegion, aKey->mKeyIdx, aKey->mLang);
139 }
140 
141 /* static */
GetOrCreate()142 nsRFPService* nsRFPService::GetOrCreate() {
143   if (!sInitialized) {
144     sRFPService = new nsRFPService();
145     nsresult rv = sRFPService->Init();
146 
147     if (NS_FAILED(rv)) {
148       sRFPService = nullptr;
149       return nullptr;
150     }
151 
152     ClearOnShutdown(&sRFPService);
153     sInitialized = true;
154   }
155 
156   return sRFPService;
157 }
158 
159 /* static */
TimerResolution()160 double nsRFPService::TimerResolution() {
161   double prefValue = StaticPrefs::
162       privacy_resistFingerprinting_reduceTimerPrecision_microseconds();
163   if (StaticPrefs::privacy_resistFingerprinting()) {
164     return std::max(100000.0, prefValue);
165   }
166   return prefValue;
167 }
168 
169 /**
170  * The purpose of this function is to deterministicly generate a random midpoint
171  * between a lower clamped value and an upper clamped value. Assuming a clamping
172  * resolution of 100, here is an example:
173  *
174  * |---------------------------------------|--------------------------|
175  * lower clamped value (e.g. 300)          |           upper clamped value (400)
176  *                              random midpoint (e.g. 360)
177  *
178  * If our actual timestamp (e.g. 325) is below the midpoint, we keep it clamped
179  * downwards. If it were equal to or above the midpoint (e.g. 365) we would
180  * round it upwards to the largest clamped value (in this example: 400).
181  *
182  * The question is: does time go backwards?
183  *
184  * The midpoint is deterministicly random and generated from three components:
185  * a secret seed, a per-timeline (context) 'mix-in', and a clamped time.
186  *
187  * When comparing times across different seed values: time may go backwards.
188  * For a clamped time of 300, one seed may generate a midpoint of 305 and
189  * another 395. So comparing an (actual) timestamp of 325 and 351 could see the
190  * 325 clamped up to 400 and the 351 clamped down to 300. The seed is
191  * per-process, so this case occurs when one can compare timestamps
192  * cross-process. This is uncommon (because we don't have site isolation.) The
193  * circumstances this could occur are BroadcastChannel, Storage Notification,
194  * and in theory (but not yet implemented) SharedWorker. This should be an
195  * exhaustive list (at time of comment writing!).
196  *
197  * Aside from cross-process communication, derived timestamps across different
198  * time origins may go backwards. (Specifically, derived means adding two
199  * timestamps together to get an (approximate) absolute time.)
200  * Assume a page and a worker. If one calls performance.now() in the page and
201  * then triggers a call to performance.now() in the worker, the following
202  * invariant should hold true:
203  *             page.performance.timeOrigin + page.performance.now() <
204  *                      worker.performance.timeOrigin + worker.performance.now()
205  *
206  * We break this invariant.
207  *
208  * The 'Context Mix-in' is a securely generated random seed that is unique for
209  * each timeline that starts over at zero. It is needed to ensure that the
210  * sequence of midpoints (as calculated by the secret seed and clamped time)
211  * does not repeat. In RelativeTimeline.h, we define a 'RelativeTimeline' class
212  * that can be inherited by any object that has a relative timeline. The most
213  * obvious examples are Documents and Workers. An attacker could let time go
214  * forward and observe (roughly) where the random midpoints fall. Then they
215  * create a new object, time starts back over at zero, and they know
216  * (approximately) where the random midpoints are.
217  *
218  * When the timestamp given is a non-relative timestamp (e.g. it is relative to
219  * the unix epoch) it is not possible to replay a sequence of random values.
220  * Thus, providing a zero context pointer is an indicator that the timestamp
221  * given is absolute and does not need any additional randomness.
222  *
223  * @param aClampedTimeUSec [in]  The clamped input time in microseconds.
224  * @param aResolutionUSec  [in]  The current resolution for clamping in
225  *                               microseconds.
226  * @param aMidpointOut     [out] The midpoint, in microseconds, between [0,
227  *                               aResolutionUSec].
228  * @param aContextMixin    [in]  An opaque random value for relative
229  *                               timestamps. 0 for absolute timestamps
230  * @param aSecretSeed      [in]  TESTING ONLY. When provided, the current seed
231  *                               will be replaced with this value.
232  * @return                 A nsresult indicating success of failure. If the
233  *                         function failed, nothing is written to aMidpointOut
234  */
235 
236 /* static */
RandomMidpoint(long long aClampedTimeUSec,long long aResolutionUSec,int64_t aContextMixin,long long * aMidpointOut,uint8_t * aSecretSeed)237 nsresult nsRFPService::RandomMidpoint(long long aClampedTimeUSec,
238                                       long long aResolutionUSec,
239                                       int64_t aContextMixin,
240                                       long long* aMidpointOut,
241                                       uint8_t* aSecretSeed /* = nullptr */) {
242   nsresult rv;
243   const int kSeedSize = 16;
244   static Atomic<uint8_t*> sSecretMidpointSeed;
245 
246   if (MOZ_UNLIKELY(!aMidpointOut)) {
247     return NS_ERROR_INVALID_ARG;
248   }
249 
250   /*
251    * Below, we will use three different values to seed a fairly simple random
252    * number generator. On the first run we initiate the secret seed, which
253    * is mixed in with the time epoch and the context mix in to seed the RNG.
254    *
255    * This isn't the most secure method of generating a random midpoint but is
256    * reasonably performant and should be sufficient for our purposes.
257    */
258 
259   // If we don't have a seed, we need to get one.
260   if (MOZ_UNLIKELY(!sSecretMidpointSeed)) {
261     nsCOMPtr<nsIRandomGenerator> randomGenerator =
262         do_GetService("@mozilla.org/security/random-generator;1", &rv);
263     if (NS_WARN_IF(NS_FAILED(rv))) {
264       return rv;
265     }
266 
267     uint8_t* temp = nullptr;
268     rv = randomGenerator->GenerateRandomBytes(kSeedSize, &temp);
269     if (NS_WARN_IF(NS_FAILED(rv))) {
270       return rv;
271     }
272     if (MOZ_UNLIKELY(!sSecretMidpointSeed.compareExchange(nullptr, temp))) {
273       // Some other thread initted this first, never mind!
274       delete[] temp;
275     }
276   }
277 
278   // sSecretMidpointSeed is now set, and invariant. The contents of the buffer
279   // it points to is also invariant, _unless_ this function is called with a
280   // non-null |aSecretSeed|.
281   uint8_t* seed = sSecretMidpointSeed;
282   MOZ_RELEASE_ASSERT(seed);
283 
284   // If someone has passed in the testing-only parameter, replace our seed with
285   // it. We do _not_ re-allocate the buffer, since that can lead to UAF below.
286   // The math could still be racy if the caller supplies a new secret seed while
287   // some other thread is calling this function, but since this is arcane
288   // test-only functionality that is used in only one test-case presently, we
289   // put the burden of using this particular footgun properly on the test code.
290   if (MOZ_UNLIKELY(aSecretSeed != nullptr)) {
291     memcpy(seed, aSecretSeed, kSeedSize);
292   }
293 
294   // Seed and create our random number generator.
295   non_crypto::XorShift128PlusRNG rng(aContextMixin ^ *(uint64_t*)(seed),
296                                      aClampedTimeUSec ^ *(uint64_t*)(seed + 8));
297 
298   // Retrieve the output midpoint value.
299   if (MOZ_UNLIKELY(aResolutionUSec <= 0)) {  // ??? Bug 1718066
300     return NS_ERROR_FAILURE;
301   }
302   *aMidpointOut = rng.next() % aResolutionUSec;
303 
304   return NS_OK;
305 }
306 
307 /**
308  * Given a precision value, this function will reduce a given input time to the
309  * nearest multiple of that precision.
310  *
311  * It will check if it is appropriate to clamp the input time according to the
312  * values of the given TimerPrecisionType.  Note that if one desires a minimum
313  * precision for Resist Fingerprinting, it is the caller's responsibility to
314  * provide the correct value. This means you should pass TimerResolution(),
315  * which enforces a minimum value on the precision based on preferences.
316  *
317  * It ensures the given precision value is greater than zero, if it is not it
318  * returns the input time.
319  *
320  * While the correct thing to pass is TimerResolution() we expose it as an
321  * argument for testing purposes only.
322  *
323  * @param aTime           [in] The input time to be clamped.
324  * @param aTimeScale      [in] The units the input time is in (Seconds,
325  *                             Milliseconds, or Microseconds).
326  * @param aResolutionUSec [in] The precision (in microseconds) to clamp to.
327  * @param aContextMixin   [in] An opaque random value for relative timestamps.
328  *                             0 for absolute timestamps
329  * @return                 If clamping is appropriate, the clamped value of the
330  *                         input, otherwise the input.
331  */
332 /* static */
ReduceTimePrecisionImpl(double aTime,TimeScale aTimeScale,double aResolutionUSec,int64_t aContextMixin,TimerPrecisionType aType)333 double nsRFPService::ReduceTimePrecisionImpl(double aTime, TimeScale aTimeScale,
334                                              double aResolutionUSec,
335                                              int64_t aContextMixin,
336                                              TimerPrecisionType aType) {
337   if (aType == TimerPrecisionType::DangerouslyNone) {
338     return aTime;
339   }
340 
341   // This boolean will serve as a flag indicating we are clamping the time
342   // unconditionally. We do this when timer reduction preference is off; but we
343   // still want to apply 20us clamping to al timestamps to avoid leaking
344   // nano-second precision.
345   bool unconditionalClamping = false;
346   if (aType == UnconditionalAKAHighRes || aResolutionUSec <= 0) {
347     unconditionalClamping = true;
348     aResolutionUSec = RFP_TIMER_UNCONDITIONAL_VALUE;  // 20 microseconds
349     aContextMixin = 0;  // Just clarifies our logging statement at the end,
350                         // otherwise unused
351   }
352 
353   // Increase the time as needed until it is in microseconds.
354   // Note that a double can hold up to 2**53 with integer precision. This gives
355   // us only until June 5, 2255 in time-since-the-epoch with integer precision.
356   // So we will be losing microseconds precision after that date.
357   // We think this is okay, and we codify it in some tests.
358   double timeScaled = aTime * (1000000 / aTimeScale);
359   // Cut off anything less than a microsecond.
360   long long timeAsInt = timeScaled;
361 
362   // If we have a blank context mixin, this indicates we (should) have an
363   // absolute timestamp. We check the time, and if it less than a unix timestamp
364   // about 10 years in the past, we output to the log and, in debug builds,
365   // assert. This is an error case we want to understand and fix: we must have
366   // given a relative timestamp with a mixin of 0 which is incorrect. Anyone
367   // running a debug build _probably_ has an accurate clock, and if they don't,
368   // they'll hopefully find this message and understand why things are crashing.
369   const long long kFeb282008 = 1204233985000;
370   if (aContextMixin == 0 && timeAsInt < kFeb282008 && !unconditionalClamping &&
371       aType != TimerPrecisionType::RFP) {
372     nsAutoCString type;
373     TypeToText(aType, type);
374     MOZ_LOG(
375         gResistFingerprintingLog, LogLevel::Error,
376         ("About to assert. aTime=%lli<%lli aContextMixin=%" PRId64 " aType=%s",
377          timeAsInt, kFeb282008, aContextMixin, type.get()));
378     MOZ_ASSERT(
379         false,
380         "ReduceTimePrecisionImpl was given a relative time "
381         "with an empty context mix-in (or your clock is 10+ years off.) "
382         "Run this with MOZ_LOG=nsResistFingerprinting:1 to get more details.");
383   }
384 
385   // Cast the resolution (in microseconds) to an int.
386   long long resolutionAsInt = aResolutionUSec;
387   // Perform the clamping.
388   // We do a cast back to double to perform the division with doubles, then
389   // floor the result and the rest occurs with integer precision. This is
390   // because it gives consistency above and below zero. Above zero, performing
391   // the division in integers truncates decimals, taking the result closer to
392   // zero (a floor). Below zero, performing the division in integers truncates
393   // decimals, taking the result closer to zero (a ceil). The impact of this is
394   // that comparing two clamped values that should be related by a constant
395   // (e.g. 10s) that are across the zero barrier will no longer work. We need to
396   // round consistently towards positive infinity or negative infinity (we chose
397   // negative.) This can't be done with a truncation, it must be done with
398   // floor.
399   long long clamped =
400       floor(double(timeAsInt) / resolutionAsInt) * resolutionAsInt;
401 
402   long long midpoint = 0;
403   long long clampedAndJittered = clamped;
404   if (!unconditionalClamping &&
405       StaticPrefs::privacy_resistFingerprinting_reduceTimerPrecision_jitter()) {
406     if (!NS_FAILED(RandomMidpoint(clamped, resolutionAsInt, aContextMixin,
407                                   &midpoint)) &&
408         timeAsInt >= clamped + midpoint) {
409       clampedAndJittered += resolutionAsInt;
410     }
411   }
412 
413   // Cast it back to a double and reduce it to the correct units.
414   double ret = double(clampedAndJittered) / (1000000.0 / aTimeScale);
415 
416   MOZ_LOG(
417       gResistFingerprintingLog, LogLevel::Verbose,
418       ("Given: (%.*f, Scaled: %.*f, Converted: %lli), Rounding %s with (%lli, "
419        "Originally %.*f), "
420        "Intermediate: (%lli), Clamped: (%lli) Jitter: (%i Context: %" PRId64
421        " Midpoint: %lli) "
422        "Final: (%lli Converted: %.*f)",
423        DBL_DIG - 1, aTime, DBL_DIG - 1, timeScaled, timeAsInt,
424        (unconditionalClamping ? "unconditionally" : "normally"),
425        resolutionAsInt, DBL_DIG - 1, aResolutionUSec,
426        (long long)floor(double(timeAsInt) / resolutionAsInt), clamped,
427        StaticPrefs::privacy_resistFingerprinting_reduceTimerPrecision_jitter(),
428        aContextMixin, midpoint, clampedAndJittered, DBL_DIG - 1, ret));
429 
430   return ret;
431 }
432 
433 /* static */
ReduceTimePrecisionAsUSecs(double aTime,int64_t aContextMixin,bool aIsSystemPrincipal,bool aCrossOriginIsolated)434 double nsRFPService::ReduceTimePrecisionAsUSecs(double aTime,
435                                                 int64_t aContextMixin,
436                                                 bool aIsSystemPrincipal,
437                                                 bool aCrossOriginIsolated) {
438   const auto type =
439       GetTimerPrecisionType(aIsSystemPrincipal, aCrossOriginIsolated);
440   return nsRFPService::ReduceTimePrecisionImpl(
441       aTime, MicroSeconds, TimerResolution(), aContextMixin, type);
442 }
443 
444 /* static */
ReduceTimePrecisionAsMSecs(double aTime,int64_t aContextMixin,bool aIsSystemPrincipal,bool aCrossOriginIsolated)445 double nsRFPService::ReduceTimePrecisionAsMSecs(double aTime,
446                                                 int64_t aContextMixin,
447                                                 bool aIsSystemPrincipal,
448                                                 bool aCrossOriginIsolated) {
449   const auto type =
450       GetTimerPrecisionType(aIsSystemPrincipal, aCrossOriginIsolated);
451   return nsRFPService::ReduceTimePrecisionImpl(
452       aTime, MilliSeconds, TimerResolution(), aContextMixin, type);
453 }
454 
455 /* static */
ReduceTimePrecisionAsMSecsRFPOnly(double aTime,int64_t aContextMixin)456 double nsRFPService::ReduceTimePrecisionAsMSecsRFPOnly(double aTime,
457                                                        int64_t aContextMixin) {
458   return nsRFPService::ReduceTimePrecisionImpl(aTime, MilliSeconds,
459                                                TimerResolution(), aContextMixin,
460                                                GetTimerPrecisionTypeRFPOnly());
461 }
462 
463 /* static */
ReduceTimePrecisionAsSecs(double aTime,int64_t aContextMixin,bool aIsSystemPrincipal,bool aCrossOriginIsolated)464 double nsRFPService::ReduceTimePrecisionAsSecs(double aTime,
465                                                int64_t aContextMixin,
466                                                bool aIsSystemPrincipal,
467                                                bool aCrossOriginIsolated) {
468   const auto type =
469       GetTimerPrecisionType(aIsSystemPrincipal, aCrossOriginIsolated);
470   return nsRFPService::ReduceTimePrecisionImpl(
471       aTime, Seconds, TimerResolution(), aContextMixin, type);
472 }
473 
474 /* static */
ReduceTimePrecisionAsSecsRFPOnly(double aTime,int64_t aContextMixin)475 double nsRFPService::ReduceTimePrecisionAsSecsRFPOnly(double aTime,
476                                                       int64_t aContextMixin) {
477   return nsRFPService::ReduceTimePrecisionImpl(aTime, Seconds,
478                                                TimerResolution(), aContextMixin,
479                                                GetTimerPrecisionTypeRFPOnly());
480 }
481 
482 /* static */
ReduceTimePrecisionAsUSecsWrapper(double aTime,JSContext * aCx)483 double nsRFPService::ReduceTimePrecisionAsUSecsWrapper(double aTime,
484                                                        JSContext* aCx) {
485   MOZ_ASSERT(aCx);
486 
487   nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx);
488   MOZ_ASSERT(global);
489   const auto type = GetTimerPrecisionType(/* aIsSystemPrincipal */ false,
490                                           global->CrossOriginIsolated());
491   return nsRFPService::ReduceTimePrecisionImpl(
492       aTime, MicroSeconds, TimerResolution(),
493       0, /* For absolute timestamps (all the JS engine does), supply zero
494             context mixin */
495       type);
496 }
497 
498 /* static */
CalculateTargetVideoResolution(uint32_t aVideoQuality)499 uint32_t nsRFPService::CalculateTargetVideoResolution(uint32_t aVideoQuality) {
500   return aVideoQuality * NSToIntCeil(aVideoQuality * 16 / 9.0);
501 }
502 
503 /* static */
GetSpoofedTotalFrames(double aTime)504 uint32_t nsRFPService::GetSpoofedTotalFrames(double aTime) {
505   double precision = TimerResolution() / 1000 / 1000;
506   double time = floor(aTime / precision) * precision;
507 
508   return NSToIntFloor(time * kVideoFramesPerSec);
509 }
510 
511 /* static */
GetSpoofedDroppedFrames(double aTime,uint32_t aWidth,uint32_t aHeight)512 uint32_t nsRFPService::GetSpoofedDroppedFrames(double aTime, uint32_t aWidth,
513                                                uint32_t aHeight) {
514   uint32_t targetRes = CalculateTargetVideoResolution(
515       StaticPrefs::privacy_resistFingerprinting_target_video_res());
516 
517   // The video resolution is less than or equal to the target resolution, we
518   // report a zero dropped rate for this case.
519   if (targetRes >= aWidth * aHeight) {
520     return 0;
521   }
522 
523   double precision = TimerResolution() / 1000 / 1000;
524   double time = floor(aTime / precision) * precision;
525   // Bound the dropped ratio from 0 to 100.
526   uint32_t boundedDroppedRatio = std::min(kVideoDroppedRatio, 100U);
527 
528   return NSToIntFloor(time * kVideoFramesPerSec *
529                       (boundedDroppedRatio / 100.0));
530 }
531 
532 /* static */
GetSpoofedPresentedFrames(double aTime,uint32_t aWidth,uint32_t aHeight)533 uint32_t nsRFPService::GetSpoofedPresentedFrames(double aTime, uint32_t aWidth,
534                                                  uint32_t aHeight) {
535   uint32_t targetRes = CalculateTargetVideoResolution(
536       StaticPrefs::privacy_resistFingerprinting_target_video_res());
537 
538   // The target resolution is greater than the current resolution. For this
539   // case, there will be no dropped frames, so we report total frames directly.
540   if (targetRes >= aWidth * aHeight) {
541     return GetSpoofedTotalFrames(aTime);
542   }
543 
544   double precision = TimerResolution() / 1000 / 1000;
545   double time = floor(aTime / precision) * precision;
546   // Bound the dropped ratio from 0 to 100.
547   uint32_t boundedDroppedRatio = std::min(kVideoDroppedRatio, 100U);
548 
549   return NSToIntFloor(time * kVideoFramesPerSec *
550                       ((100 - boundedDroppedRatio) / 100.0));
551 }
552 
GetSpoofedVersion()553 static uint32_t GetSpoofedVersion() {
554   // If we can't get the current Firefox version, use a hard-coded ESR version.
555   const uint32_t kKnownEsrVersion = 78;
556 
557   nsresult rv;
558   nsCOMPtr<nsIXULAppInfo> appInfo =
559       do_GetService("@mozilla.org/xre/app-info;1", &rv);
560   NS_ENSURE_SUCCESS(rv, kKnownEsrVersion);
561 
562   nsAutoCString appVersion;
563   rv = appInfo->GetVersion(appVersion);
564   NS_ENSURE_SUCCESS(rv, kKnownEsrVersion);
565 
566   // The browser version will be spoofed as the last ESR version.
567   // By doing so, the anonymity group will cover more versions instead of one
568   // version.
569   uint32_t firefoxVersion = appVersion.ToInteger(&rv);
570   NS_ENSURE_SUCCESS(rv, kKnownEsrVersion);
571 
572   // Some add-on tests set the Firefox version to low numbers like 1 or 42,
573   // which causes the spoofed version calculation's unsigned int subtraction
574   // below to wrap around zero to Firefox versions like 4294967287. This
575   // function should always return an ESR version, so return a good one now.
576   if (firefoxVersion < kKnownEsrVersion) {
577     return kKnownEsrVersion;
578   }
579 
580 #ifdef DEBUG
581   // If we are running in Firefox ESR, determine whether the formula of ESR
582   // version has changed.  Once changed, we must update the formula in this
583   // function.
584   if (!strcmp(MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL), "esr")) {
585     MOZ_ASSERT(((firefoxVersion - kKnownEsrVersion) % 13) == 0,
586                "Please update ESR version formula in nsRFPService.cpp");
587   }
588 #endif  // DEBUG
589 
590   // Starting with Firefox 78, a new ESR version will be released every June.
591   // We can't accurately calculate the next ESR version, but it will be
592   // probably be every ~13 Firefox releases, assuming four-week release
593   // cycles. If this assumption is wrong, we won't need to worry about it
594   // until ESR 104±1 in 2022. :) We have a debug assert above to catch if the
595   // spoofed version doesn't match the actual ESR version then.
596   // We infer the last and closest ESR version based on this rule.
597   uint32_t spoofedVersion =
598       firefoxVersion - ((firefoxVersion - kKnownEsrVersion) % 13);
599 
600   MOZ_ASSERT(spoofedVersion >= kKnownEsrVersion &&
601              spoofedVersion <= firefoxVersion &&
602              (spoofedVersion - kKnownEsrVersion) % 13 == 0);
603 
604   return spoofedVersion;
605 }
606 
607 /* static */
GetSpoofedUserAgent(nsACString & userAgent,bool isForHTTPHeader)608 void nsRFPService::GetSpoofedUserAgent(nsACString& userAgent,
609                                        bool isForHTTPHeader) {
610   // This function generates the spoofed value of User Agent.
611   // We spoof the values of the platform and Firefox version, which could be
612   // used as fingerprinting sources to identify individuals.
613   // Reference of the format of User Agent:
614   // https://developer.mozilla.org/en-US/docs/Web/API/NavigatorID/userAgent
615   // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
616 
617   // These magic numbers are the lengths of the UA string literals below.
618   // Assume three-digit Firefox version numbers so we have room to grow.
619   size_t preallocatedLength =
620       13 +
621       (isForHTTPHeader ? mozilla::ArrayLength(SPOOFED_HTTP_UA_OS)
622                        : mozilla::ArrayLength(SPOOFED_UA_OS)) -
623       1 + 5 + 3 + 10 + mozilla::ArrayLength(LEGACY_UA_GECKO_TRAIL) - 1 + 9 + 3 +
624       2;
625   userAgent.SetCapacity(preallocatedLength);
626 
627   uint32_t spoofedVersion = GetSpoofedVersion();
628 
629   // "Mozilla/5.0 (%s; rv:%d.0) Gecko/%d Firefox/%d.0"
630   userAgent.AssignLiteral("Mozilla/5.0 (");
631 
632   if (isForHTTPHeader) {
633     userAgent.AppendLiteral(SPOOFED_HTTP_UA_OS);
634   } else {
635     userAgent.AppendLiteral(SPOOFED_UA_OS);
636   }
637 
638   userAgent.AppendLiteral("; rv:");
639   userAgent.AppendInt(spoofedVersion);
640   userAgent.AppendLiteral(".0) Gecko/");
641 
642 #if defined(ANDROID)
643   userAgent.AppendInt(spoofedVersion);
644   userAgent.AppendLiteral(".0");
645 #else
646   userAgent.AppendLiteral(LEGACY_UA_GECKO_TRAIL);
647 #endif
648 
649   userAgent.AppendLiteral(" Firefox/");
650   userAgent.AppendInt(spoofedVersion);
651   userAgent.AppendLiteral(".0");
652 
653   MOZ_ASSERT(userAgent.Length() <= preallocatedLength);
654 }
655 
656 static const char* gCallbackPrefs[] = {
657     RESIST_FINGERPRINTING_PREF,   RFP_TIMER_PREF,
658     RFP_TIMER_UNCONDITIONAL_PREF, RFP_TIMER_VALUE_PREF,
659     RFP_JITTER_VALUE_PREF,        nullptr,
660 };
661 
Init()662 nsresult nsRFPService::Init() {
663   MOZ_ASSERT(NS_IsMainThread());
664 
665   nsresult rv;
666 
667   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
668   NS_ENSURE_TRUE(obs, NS_ERROR_NOT_AVAILABLE);
669 
670   rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
671   NS_ENSURE_SUCCESS(rv, rv);
672 
673 #if defined(XP_WIN)
674   rv = obs->AddObserver(this, PROFILE_INITIALIZED_TOPIC, false);
675   NS_ENSURE_SUCCESS(rv, rv);
676 #endif
677 
678   Preferences::RegisterCallbacks(nsRFPService::PrefChanged, gCallbackPrefs,
679                                  this);
680 
681   // We backup the original TZ value here.
682   const char* tzValue = PR_GetEnv("TZ");
683   if (tzValue != nullptr) {
684     mInitialTZValue = nsCString(tzValue);
685   }
686 
687   // Call Update here to cache the values of the prefs and set the timezone.
688   UpdateRFPPref();
689 
690   return rv;
691 }
692 
693 // This function updates only timing-related fingerprinting items
UpdateTimers()694 void nsRFPService::UpdateTimers() {
695   MOZ_ASSERT(NS_IsMainThread());
696 
697   if (StaticPrefs::privacy_resistFingerprinting() ||
698       StaticPrefs::privacy_reduceTimerPrecision()) {
699     JS::SetTimeResolutionUsec(
700         TimerResolution(),
701         StaticPrefs::
702             privacy_resistFingerprinting_reduceTimerPrecision_jitter());
703     JS::SetReduceMicrosecondTimePrecisionCallback(
704         nsRFPService::ReduceTimePrecisionAsUSecsWrapper);
705   } else if (StaticPrefs::privacy_reduceTimerPrecision_unconditional()) {
706     JS::SetTimeResolutionUsec(RFP_TIMER_UNCONDITIONAL_VALUE, false);
707     JS::SetReduceMicrosecondTimePrecisionCallback(
708         nsRFPService::ReduceTimePrecisionAsUSecsWrapper);
709   } else if (sInitialized) {
710     JS::SetTimeResolutionUsec(0, false);
711   }
712 }
713 
714 // This function updates every fingerprinting item necessary except
715 // timing-related
UpdateRFPPref()716 void nsRFPService::UpdateRFPPref() {
717   MOZ_ASSERT(NS_IsMainThread());
718 
719   UpdateTimers();
720 
721   bool privacyResistFingerprinting =
722       StaticPrefs::privacy_resistFingerprinting();
723 
724   // set fdlibm pref
725   JS::SetUseFdlibmForSinCosTan(
726       StaticPrefs::javascript_options_use_fdlibm_for_sin_cos_tan() ||
727       privacyResistFingerprinting);
728 
729   if (privacyResistFingerprinting) {
730     PR_SetEnv("TZ=UTC");
731   } else if (sInitialized) {
732     // We will not touch the TZ value if 'privacy.resistFingerprinting' is false
733     // during the time of initialization.
734     if (!mInitialTZValue.IsEmpty()) {
735       nsAutoCString tzValue = "TZ="_ns + mInitialTZValue;
736       static char* tz = nullptr;
737 
738       // If the tz has been set before, we free it first since it will be
739       // allocated a new value later.
740       if (tz != nullptr) {
741         free(tz);
742       }
743       // PR_SetEnv() needs the input string been leaked intentionally, so
744       // we copy it here.
745       tz = ToNewCString(tzValue, mozilla::fallible);
746       if (tz != nullptr) {
747         PR_SetEnv(tz);
748       }
749     } else {
750 #if defined(XP_WIN)
751       // For Windows, we reset the TZ to an empty string. This will make Windows
752       // to use its system timezone.
753       PR_SetEnv("TZ=");
754 #else
755       // For POSIX like system, we reset the TZ to the /etc/localtime, which is
756       // the system timezone.
757       PR_SetEnv("TZ=:/etc/localtime");
758 #endif
759     }
760   }
761 
762   // If and only if the time zone was changed above, propagate the change to the
763   // <time.h> functions and the JS runtime.
764   if (privacyResistFingerprinting || sInitialized) {
765     // localtime_r (and other functions) may not call tzset, so do this here
766     // after changing TZ to ensure all <time.h> functions use the new time zone.
767 #if defined(XP_WIN)
768     _tzset();
769 #else
770     tzset();
771 #endif
772 
773     nsJSUtils::ResetTimeZone();
774   }
775 }
776 
StartShutdown()777 void nsRFPService::StartShutdown() {
778   MOZ_ASSERT(NS_IsMainThread());
779 
780   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
781 
782   if (obs) {
783     obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
784   }
785   Preferences::UnregisterCallbacks(nsRFPService::PrefChanged, gCallbackPrefs,
786                                    this);
787 }
788 
789 /* static */
MaybeCreateSpoofingKeyCodes(const KeyboardLangs aLang,const KeyboardRegions aRegion)790 void nsRFPService::MaybeCreateSpoofingKeyCodes(const KeyboardLangs aLang,
791                                                const KeyboardRegions aRegion) {
792   if (sSpoofingKeyboardCodes == nullptr) {
793     sSpoofingKeyboardCodes =
794         new nsTHashMap<KeyboardHashKey, const SpoofingKeyboardCode*>();
795   }
796 
797   if (KeyboardLang::EN == aLang) {
798     switch (aRegion) {
799       case KeyboardRegion::US:
800         MaybeCreateSpoofingKeyCodesForEnUS();
801         break;
802     }
803   }
804 }
805 
806 /* static */
MaybeCreateSpoofingKeyCodesForEnUS()807 void nsRFPService::MaybeCreateSpoofingKeyCodesForEnUS() {
808   MOZ_ASSERT(sSpoofingKeyboardCodes);
809 
810   static bool sInitialized = false;
811   const KeyboardLangs lang = KeyboardLang::EN;
812   const KeyboardRegions reg = KeyboardRegion::US;
813 
814   if (sInitialized) {
815     return;
816   }
817 
818   static const SpoofingKeyboardInfo spoofingKeyboardInfoTable[] = {
819 #define KEY(key_, _codeNameIdx, _keyCode, _modifier) \
820   {NS_LITERAL_STRING_FROM_CSTRING(key_),             \
821    KEY_NAME_INDEX_USE_STRING,                        \
822    {CODE_NAME_INDEX_##_codeNameIdx, _keyCode, _modifier}},
823 #define CONTROL(keyNameIdx_, _codeNameIdx, _keyCode) \
824   {u""_ns,                                           \
825    KEY_NAME_INDEX_##keyNameIdx_,                     \
826    {CODE_NAME_INDEX_##_codeNameIdx, _keyCode, MODIFIER_NONE}},
827 #include "KeyCodeConsensus_En_US.h"
828 #undef CONTROL
829 #undef KEY
830   };
831 
832   for (const auto& keyboardInfo : spoofingKeyboardInfoTable) {
833     KeyboardHashKey key(lang, reg, keyboardInfo.mKeyIdx, keyboardInfo.mKey);
834     MOZ_ASSERT(!sSpoofingKeyboardCodes->Contains(key),
835                "Double-defining key code; fix your KeyCodeConsensus file");
836     sSpoofingKeyboardCodes->InsertOrUpdate(key, &keyboardInfo.mSpoofingCode);
837   }
838 
839   sInitialized = true;
840 }
841 
842 /* static */
GetKeyboardLangAndRegion(const nsAString & aLanguage,KeyboardLangs & aLocale,KeyboardRegions & aRegion)843 void nsRFPService::GetKeyboardLangAndRegion(const nsAString& aLanguage,
844                                             KeyboardLangs& aLocale,
845                                             KeyboardRegions& aRegion) {
846   nsAutoString langStr;
847   nsAutoString regionStr;
848   uint32_t partNum = 0;
849 
850   for (const nsAString& part : aLanguage.Split('-')) {
851     if (partNum == 0) {
852       langStr = part;
853     } else {
854       regionStr = part;
855       break;
856     }
857 
858     partNum++;
859   }
860 
861   // We test each language here as well as the region. There are some cases that
862   // only the language is given, we will use the default region code when this
863   // happens. The default region should depend on the given language.
864   if (langStr.EqualsLiteral(RFP_KEYBOARD_LANG_STRING_EN)) {
865     aLocale = KeyboardLang::EN;
866     // Give default values first.
867     aRegion = KeyboardRegion::US;
868 
869     if (regionStr.EqualsLiteral(RFP_KEYBOARD_REGION_STRING_US)) {
870       aRegion = KeyboardRegion::US;
871     }
872   } else {
873     // There is no spoofed keyboard locale for the given language. We use the
874     // default one in this case.
875     aLocale = RFP_DEFAULT_SPOOFING_KEYBOARD_LANG;
876     aRegion = RFP_DEFAULT_SPOOFING_KEYBOARD_REGION;
877   }
878 }
879 
880 /* static */
GetSpoofedKeyCodeInfo(const dom::Document * aDoc,const WidgetKeyboardEvent * aKeyboardEvent,SpoofingKeyboardCode & aOut)881 bool nsRFPService::GetSpoofedKeyCodeInfo(
882     const dom::Document* aDoc, const WidgetKeyboardEvent* aKeyboardEvent,
883     SpoofingKeyboardCode& aOut) {
884   MOZ_ASSERT(aKeyboardEvent);
885 
886   KeyboardLangs keyboardLang = RFP_DEFAULT_SPOOFING_KEYBOARD_LANG;
887   KeyboardRegions keyboardRegion = RFP_DEFAULT_SPOOFING_KEYBOARD_REGION;
888   // If the document is given, we use the content language which is get from the
889   // document. Otherwise, we use the default one.
890   if (aDoc != nullptr) {
891     nsAutoString language;
892     aDoc->GetContentLanguage(language);
893 
894     // If the content-langauge is not given, we try to get langauge from the
895     // HTML lang attribute.
896     if (language.IsEmpty()) {
897       dom::Element* elm = aDoc->GetHtmlElement();
898 
899       if (elm != nullptr) {
900         elm->GetLang(language);
901       }
902     }
903 
904     // If two or more languages are given, per HTML5 spec, we should consider
905     // it as 'unknown'. So we use the default one.
906     if (!language.IsEmpty() && !language.Contains(char16_t(','))) {
907       language.StripWhitespace();
908       GetKeyboardLangAndRegion(language, keyboardLang, keyboardRegion);
909     }
910   }
911 
912   MaybeCreateSpoofingKeyCodes(keyboardLang, keyboardRegion);
913 
914   KeyNameIndex keyIdx = aKeyboardEvent->mKeyNameIndex;
915   nsAutoString keyName;
916 
917   if (keyIdx == KEY_NAME_INDEX_USE_STRING) {
918     keyName = aKeyboardEvent->mKeyValue;
919   }
920 
921   KeyboardHashKey key(keyboardLang, keyboardRegion, keyIdx, keyName);
922   const SpoofingKeyboardCode* keyboardCode = sSpoofingKeyboardCodes->Get(key);
923 
924   if (keyboardCode != nullptr) {
925     aOut = *keyboardCode;
926     return true;
927   }
928 
929   return false;
930 }
931 
932 /* static */
GetSpoofedModifierStates(const dom::Document * aDoc,const WidgetKeyboardEvent * aKeyboardEvent,const Modifiers aModifier,bool & aOut)933 bool nsRFPService::GetSpoofedModifierStates(
934     const dom::Document* aDoc, const WidgetKeyboardEvent* aKeyboardEvent,
935     const Modifiers aModifier, bool& aOut) {
936   MOZ_ASSERT(aKeyboardEvent);
937 
938   // For modifier or control keys, we don't need to hide its modifier states.
939   if (aKeyboardEvent->mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
940     return false;
941   }
942 
943   // We will spoof the modifer state for Alt, Shift, and AltGraph.
944   // We don't spoof the Control key, because it is often used
945   // for command key combinations in web apps.
946   if ((aModifier & (MODIFIER_ALT | MODIFIER_SHIFT | MODIFIER_ALTGRAPH)) != 0) {
947     SpoofingKeyboardCode keyCodeInfo;
948 
949     if (GetSpoofedKeyCodeInfo(aDoc, aKeyboardEvent, keyCodeInfo)) {
950       aOut = ((keyCodeInfo.mModifierStates & aModifier) != 0);
951       return true;
952     }
953   }
954 
955   return false;
956 }
957 
958 /* static */
GetSpoofedCode(const dom::Document * aDoc,const WidgetKeyboardEvent * aKeyboardEvent,nsAString & aOut)959 bool nsRFPService::GetSpoofedCode(const dom::Document* aDoc,
960                                   const WidgetKeyboardEvent* aKeyboardEvent,
961                                   nsAString& aOut) {
962   MOZ_ASSERT(aKeyboardEvent);
963 
964   SpoofingKeyboardCode keyCodeInfo;
965 
966   if (!GetSpoofedKeyCodeInfo(aDoc, aKeyboardEvent, keyCodeInfo)) {
967     return false;
968   }
969 
970   WidgetKeyboardEvent::GetDOMCodeName(keyCodeInfo.mCode, aOut);
971 
972   // We need to change the 'Left' with 'Right' if the location indicates
973   // it's a right key.
974   if (aKeyboardEvent->mLocation ==
975           dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_RIGHT &&
976       StringEndsWith(aOut, u"Left"_ns)) {
977     aOut.ReplaceLiteral(aOut.Length() - 4, 4, u"Right");
978   }
979 
980   return true;
981 }
982 
983 /* static */
GetSpoofedKeyCode(const dom::Document * aDoc,const WidgetKeyboardEvent * aKeyboardEvent,uint32_t & aOut)984 bool nsRFPService::GetSpoofedKeyCode(const dom::Document* aDoc,
985                                      const WidgetKeyboardEvent* aKeyboardEvent,
986                                      uint32_t& aOut) {
987   MOZ_ASSERT(aKeyboardEvent);
988 
989   SpoofingKeyboardCode keyCodeInfo;
990 
991   if (GetSpoofedKeyCodeInfo(aDoc, aKeyboardEvent, keyCodeInfo)) {
992     aOut = keyCodeInfo.mKeyCode;
993     return true;
994   }
995 
996   return false;
997 }
998 
999 /* static */
GetTimerPrecisionType(bool aIsSystemPrincipal,bool aCrossOriginIsolated)1000 TimerPrecisionType nsRFPService::GetTimerPrecisionType(
1001     bool aIsSystemPrincipal, bool aCrossOriginIsolated) {
1002   if (aIsSystemPrincipal) {
1003     return DangerouslyNone;
1004   }
1005 
1006   if (StaticPrefs::privacy_resistFingerprinting()) {
1007     return RFP;
1008   }
1009 
1010   if (StaticPrefs::privacy_reduceTimerPrecision() && aCrossOriginIsolated) {
1011     return UnconditionalAKAHighRes;
1012   }
1013 
1014   if (StaticPrefs::privacy_reduceTimerPrecision()) {
1015     return Normal;
1016   }
1017 
1018   if (StaticPrefs::privacy_reduceTimerPrecision_unconditional()) {
1019     return UnconditionalAKAHighRes;
1020   }
1021 
1022   return DangerouslyNone;
1023 }
1024 
1025 /* static */
GetTimerPrecisionTypeRFPOnly()1026 TimerPrecisionType nsRFPService::GetTimerPrecisionTypeRFPOnly() {
1027   if (StaticPrefs::privacy_resistFingerprinting()) {
1028     return RFP;
1029   }
1030 
1031   if (StaticPrefs::privacy_reduceTimerPrecision_unconditional()) {
1032     return UnconditionalAKAHighRes;
1033   }
1034 
1035   return DangerouslyNone;
1036 }
1037 
1038 /* static */
TypeToText(TimerPrecisionType aType,nsACString & aText)1039 void nsRFPService::TypeToText(TimerPrecisionType aType, nsACString& aText) {
1040   switch (aType) {
1041     case TimerPrecisionType::DangerouslyNone:
1042       aText.AssignLiteral("DangerouslyNone");
1043       return;
1044     case TimerPrecisionType::Normal:
1045       aText.AssignLiteral("Normal");
1046       return;
1047     case TimerPrecisionType::RFP:
1048       aText.AssignLiteral("RFP");
1049       return;
1050     case TimerPrecisionType::UnconditionalAKAHighRes:
1051       aText.AssignLiteral("UnconditionalAKAHighRes");
1052       return;
1053     default:
1054       MOZ_ASSERT(false, "Shouldn't go here");
1055       aText.AssignLiteral("Unknown Enum Value");
1056       return;
1057   }
1058 }
1059 
1060 // static
PrefChanged(const char * aPref,void * aSelf)1061 void nsRFPService::PrefChanged(const char* aPref, void* aSelf) {
1062   static_cast<nsRFPService*>(aSelf)->PrefChanged(aPref);
1063 }
1064 
PrefChanged(const char * aPref)1065 void nsRFPService::PrefChanged(const char* aPref) {
1066   nsDependentCString pref(aPref);
1067 
1068   if (pref.EqualsLiteral(RFP_TIMER_PREF) ||
1069       pref.EqualsLiteral(RFP_TIMER_UNCONDITIONAL_PREF) ||
1070       pref.EqualsLiteral(RFP_TIMER_VALUE_PREF) ||
1071       pref.EqualsLiteral(RFP_JITTER_VALUE_PREF)) {
1072     UpdateTimers();
1073   } else if (pref.EqualsLiteral(RESIST_FINGERPRINTING_PREF)) {
1074     UpdateRFPPref();
1075 
1076 #if defined(XP_WIN)
1077     if (!XRE_IsE10sParentProcess()) {
1078       // Windows does not follow POSIX. Updates to the TZ environment variable
1079       // are not reflected immediately on that platform as they are on UNIX
1080       // systems without this call.
1081       _tzset();
1082     }
1083 #endif
1084   }
1085 }
1086 
1087 NS_IMETHODIMP
Observe(nsISupports * aObject,const char * aTopic,const char16_t * aMessage)1088 nsRFPService::Observe(nsISupports* aObject, const char* aTopic,
1089                       const char16_t* aMessage) {
1090   if (strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic) == 0) {
1091     StartShutdown();
1092   }
1093 #if defined(XP_WIN)
1094   else if (!strcmp(PROFILE_INITIALIZED_TOPIC, aTopic)) {
1095     // If we're e10s, then we don't need to run this, since the child process
1096     // will simply inherit the environment variable from the parent process, in
1097     // which case it's unnecessary to call _tzset().
1098     if (XRE_IsParentProcess() && !XRE_IsE10sParentProcess()) {
1099       // Windows does not follow POSIX. Updates to the TZ environment variable
1100       // are not reflected immediately on that platform as they are on UNIX
1101       // systems without this call.
1102       _tzset();
1103     }
1104 
1105     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1106     NS_ENSURE_TRUE(obs, NS_ERROR_NOT_AVAILABLE);
1107 
1108     nsresult rv = obs->RemoveObserver(this, PROFILE_INITIALIZED_TOPIC);
1109     NS_ENSURE_SUCCESS(rv, rv);
1110   }
1111 #endif
1112 
1113   return NS_OK;
1114 }
1115