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