Bug 1425462 Normalize the JavaScript Engine behavior by adding a callback r?luke draft
authorTom Ritter <tom@mozilla.com>
Thu, 22 Feb 2018 16:05:50 -0600
changeset 762573 ed653c07274c39c0ad6d3d426a74e4b1b7e184ac
parent 761765 98d000cf88c122a868ef1745a00c7597e3d26def
child 762574 aec2b149393efdc215d0c9a0b2f00f6191819fc2
push id101207
push userbmo:tom@mozilla.com
push dateFri, 02 Mar 2018 17:45:38 +0000
reviewersluke
bugs1425462, 1440539
milestone60.0a1
Bug 1425462 Normalize the JavaScript Engine behavior by adding a callback r?luke Time Precision Reduction in the JS Engine was handled by a small bit of duplicated logic. With Time Jittering, and general improvements to the logic due to float fuzziness, we want to unify the logic for the JS Engine and the browser into one location. This patch does that. Note that this will leave the JS Shell without a time jittering implementation. It currently has a time clamping implementation - but I'm not actually sure if the shell is doing anything with it, because it's probably not calling SetTimeResolutionUsec to set it up. In Bug 1440539 we will add a jitter implementation for the shell. (And probably turn time rounding and jittering on for it too.) MozReview-Commit-ID: 2BTIMzE8MjW
js/public/Date.h
js/src/jsdate.cpp
toolkit/components/resistfingerprinting/nsRFPService.cpp
toolkit/components/resistfingerprinting/nsRFPService.h
--- a/js/public/Date.h
+++ b/js/public/Date.h
@@ -166,16 +166,27 @@ DayFromYear(double year);
 
 // Takes an integer number of milliseconds since the epoch and an integer year,
 // returns the number of days in that year. If |time| is nonfinite, returns NaN.
 // Otherwise |time| *must* correspond to a time within the valid year |year|.
 // This should usually be ensured by computing |year| as |JS::DayFromYear(time)|.
 JS_PUBLIC_API(double)
 DayWithinYear(double time, double year);
 
-// Sets the time resolution for fingerprinting protection.
-// If it's set to zero, then no rounding will happen.
+// The callback will be a wrapper function that accepts a single double (the time
+// to clamp and jitter.) Inside the JS Engine, other parameters that may be needed
+// are all constant, so they are handled inside the wrapper function
+using ReduceMicrosecondTimePrecisionCallback = double(*)(double);
+
+// Set a callback into the toolkit/components/resistfingerprinting function that
+// will centralize time resolution and jitter into one place.
+JS_PUBLIC_API(void)
+SetReduceMicrosecondTimePrecisionCallback(ReduceMicrosecondTimePrecisionCallback callback);
+
+// Sets the time resolution for fingerprinting protection, and whether jitter
+// should occur. If resolution is set to zero, then no rounding or jitter will
+// occur. This is used if the callback above is not specified.
 JS_PUBLIC_API(void)
 SetTimeResolutionUsec(uint32_t resolution, bool jitter);
 
 } // namespace JS
 
 #endif /* js_Date_h */
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -60,16 +60,19 @@ using JS::ClippedTime;
 using JS::GenericNaN;
 using JS::TimeClip;
 using JS::ToInteger;
 
 // When this value is non-zero, we'll round the time by this resolution.
 static Atomic<uint32_t, Relaxed> sResolutionUsec;
 // This is not implemented yet, but we will use this to know to jitter the time in the JS shell
 static Atomic<bool, Relaxed> sJitter;
+// The callback we will use for the Gecko implementation of Timer Clamping/Jittering
+static Atomic<JS::ReduceMicrosecondTimePrecisionCallback, Relaxed> sReduceMicrosecondTimePrecisionCallback;
+
 
 /*
  * The JS 'Date' object is patterned after the Java 'Date' object.
  * Here is a script:
  *
  *    today = new Date();
  *
  *    print(today.toLocaleString());
@@ -402,16 +405,22 @@ JS::DayFromYear(double year)
 
 JS_PUBLIC_API(double)
 JS::DayWithinYear(double time, double year)
 {
     return ::DayWithinYear(time, year);
 }
 
 JS_PUBLIC_API(void)
+JS::SetReduceMicrosecondTimePrecisionCallback(JS::ReduceMicrosecondTimePrecisionCallback callback)
+{
+    sReduceMicrosecondTimePrecisionCallback = callback;
+}
+
+JS_PUBLIC_API(void)
 JS::SetTimeResolutionUsec(uint32_t resolution, bool jitter)
 {
     sResolutionUsec = resolution;
     sJitter = jitter;
 }
 
 /*
  * Find a year for which any given date will fall on the same weekday.
@@ -1294,19 +1303,21 @@ date_parse(JSContext* cx, unsigned argc,
     args.rval().set(TimeValue(result));
     return true;
 }
 
 static ClippedTime
 NowAsMillis()
 {
     double now = PRMJ_Now();
-    if (sResolutionUsec) {
+    if (sReduceMicrosecondTimePrecisionCallback)
+        now = sReduceMicrosecondTimePrecisionCallback(now);
+    else if (sResolutionUsec)
         now = floor(now / sResolutionUsec) * sResolutionUsec;
-    }
+
     return TimeClip(now / PRMJ_USEC_PER_MSEC);
 }
 
 bool
 js::date_now(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().set(TimeValue(NowAsMillis()));
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -473,16 +473,23 @@ nsRFPService::ReduceTimePrecisionImpl(
 double
 nsRFPService::ReduceTimePrecisionAsUSecs(double aTime, TimerPrecisionType aType /* = TimerPrecisionType::All */)
 {
   return nsRFPService::ReduceTimePrecisionImpl(aTime, MicroSeconds, TimerResolution(), aType);
 }
 
 /* static */
 double
+nsRFPService::ReduceTimePrecisionAsUSecsWrapper(double aTime)
+{
+  return nsRFPService::ReduceTimePrecisionImpl(aTime, MicroSeconds, TimerResolution(), TimerPrecisionType::All);
+}
+
+/* static */
+double
 nsRFPService::ReduceTimePrecisionAsMSecs(double aTime, TimerPrecisionType aType /* = TimerPrecisionType::All */)
 {
   return nsRFPService::ReduceTimePrecisionImpl(aTime, MilliSeconds, TimerResolution(), aType);
 }
 
 /* static */
 double
 nsRFPService::ReduceTimePrecisionAsSecs(double aTime, TimerPrecisionType aType /* = TimerPrecisionType::All */)
@@ -666,16 +673,17 @@ nsRFPService::Init()
 
 // This function updates only timing-related fingerprinting items
 void
 nsRFPService::UpdateTimers() {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (sPrivacyResistFingerprinting || sPrivacyTimerPrecisionReduction) {
     JS::SetTimeResolutionUsec(TimerResolution(), sJitter);
+    JS::SetReduceMicrosecondTimePrecisionCallback(nsRFPService::ReduceTimePrecisionAsUSecsWrapper);
   } else if (sInitialized) {
     JS::SetTimeResolutionUsec(0, false);
   }
 }
 
 
 // This function updates every fingerprinting item necessary except timing-related
 void
--- a/toolkit/components/resistfingerprinting/nsRFPService.h
+++ b/toolkit/components/resistfingerprinting/nsRFPService.h
@@ -175,16 +175,20 @@ public:
     TimerPrecisionType aType = TimerPrecisionType::All);
   static double ReduceTimePrecisionAsMSecs(
     double aTime,
     TimerPrecisionType aType = TimerPrecisionType::All);
   static double ReduceTimePrecisionAsSecs(
     double aTime,
     TimerPrecisionType aType = TimerPrecisionType::All);
 
+  // Used by the JS Engine, as it doesn't know about the TimerPrecisionType enum
+  static double ReduceTimePrecisionAsUSecsWrapper(
+    double aTime);
+
   // Public only for testing purposes
   static double ReduceTimePrecisionImpl(
     double aTime,
     TimeScale aTimeScale,
     double aResolutionUSec,
     TimerPrecisionType aType);
   static nsresult RandomMidpoint(long long aClampedTimeUSec,
                                  long long aResolutionUSec,