Coverage Report

Created: 2018-09-25 14:53

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