Bug 1217238 - Add a pref to reduce precision of time exposed by Javascript. r=bz,h4writer draft
authorJonathan Hao <jhao@mozilla.com>
Thu, 15 Dec 2016 17:13:59 +0800
changeset 450985 bea6abc4e3e09b1390d6a507526eaf979b8481ee
parent 450984 3fab31d49ff59d21102b6e334371cb40875320c9
child 539882 039551b1449d4eac5f2c10116e8c089c0b53cc34
push id39005
push userbmo:jhao@mozilla.com
push dateMon, 19 Dec 2016 08:59:15 +0000
reviewersbz, h4writer
bugs1217238
milestone53.0a1
Bug 1217238 - Add a pref to reduce precision of time exposed by Javascript. r=bz,h4writer
dom/events/Event.cpp
dom/file/File.cpp
dom/file/MultipartBlobImpl.cpp
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/DOMMediaStream.cpp
dom/media/webaudio/AudioContext.cpp
dom/performance/Performance.cpp
dom/security/TimePrecisionReducer.h
dom/security/moz.build
dom/workers/RuntimeService.cpp
js/src/jsapi.h
js/src/jsdate.cpp
js/src/shell/js.cpp
js/xpconnect/src/XPCJSContext.cpp
layout/base/nsRefreshDriver.cpp
layout/style/nsAnimationManager.h
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AccessCheck.h"
 #include "base/basictypes.h"
 #include "ipc/IPCMessageUtils.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/TimePrecisionReducer.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "mozilla/dom/Performance.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
@@ -1064,17 +1065,18 @@ Event::DefaultPrevented(CallerType aCall
   return mEvent->DefaultPreventedByContent() ||
          aCallerType == CallerType::System;
 }
 
 double
 Event::TimeStamp() const
 {
   if (!sReturnHighResTimeStamp) {
-    return static_cast<double>(mEvent->mTime);
+    // Round to 100ms if javascript.options.privacy.reduce_time_precision is on.
+    return TimePrecisionReducer::ReduceAsMSecsIfPrefIsOn(mEvent->mTime);
   }
 
   if (mEvent->mTimeStamp.IsNull()) {
     return 0.0;
   }
 
   if (mIsMainThreadEvent) {
     if (NS_WARN_IF(!mOwner)) {
@@ -1086,17 +1088,19 @@ Event::TimeStamp() const
       return 0.0;
     }
 
     Performance* perf = win->GetPerformance();
     if (NS_WARN_IF(!perf)) {
       return 0.0;
     }
 
-    return perf->GetDOMTiming()->TimeStampToDOMHighRes(mEvent->mTimeStamp);
+    // Round to 100ms if javascript.options.privacy.reduce_time_precision is on.
+    return TimePrecisionReducer::ReduceAsMSecsIfPrefIsOn(
+        perf->GetDOMTiming()->TimeStampToDOMHighRes(mEvent->mTimeStamp));
   }
 
   // For dedicated workers, we should make times relative to the navigation
   // start of the document that created the worker, which is the same as the
   // timebase for performance.now().
   workers::WorkerPrivate* workerPrivate =
     workers::GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(workerPrivate);
--- a/dom/file/File.cpp
+++ b/dom/file/File.cpp
@@ -31,16 +31,17 @@
 #include "nsPrintfCString.h"
 #include "mozilla/SHA1.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/FileBinding.h"
+#include "mozilla/dom/TimePrecisionReducer.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerRunnable.h"
 #include "nsThreadUtils.h"
 #include "nsStreamUtils.h"
 #include "SlicedInputStream.h"
 
 namespace mozilla {
 namespace dom {
@@ -730,17 +731,18 @@ BlobImplBase::GetType(nsAString& aType)
   aType = mContentType;
 }
 
 int64_t
 BlobImplBase::GetLastModified(ErrorResult& aRv)
 {
   MOZ_ASSERT(mIsFile, "Should only be called on files");
   if (IsDateUnknown()) {
-    mLastModificationDate = PR_Now();
+    // Round to 100ms if javascript.options.privacy.reduce_time_precision is on.
+    mLastModificationDate = TimePrecisionReducer::ReduceAsUSecsIfPrefIsOn(PR_Now());
   }
 
   return mLastModificationDate / PR_USEC_PER_MSEC;
 }
 
 void
 BlobImplBase::SetLastModified(int64_t aLastModified)
 {
--- a/dom/file/MultipartBlobImpl.cpp
+++ b/dom/file/MultipartBlobImpl.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MultipartBlobImpl.h"
 #include "jsfriendapi.h"
 #include "mozilla/dom/BlobSet.h"
 #include "mozilla/dom/FileBinding.h"
+#include "mozilla/dom/TimePrecisionReducer.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "nsDOMClassInfoID.h"
 #include "nsIMultiplexInputStream.h"
 #include "nsStringStream.h"
 #include "nsTArray.h"
 #include "nsJSUtils.h"
 #include "nsContentUtils.h"
 #include "nsIScriptError.h"
@@ -265,18 +266,21 @@ MultipartBlobImpl::SetLengthAndModifiedD
 
   mLength = totalLength;
 
   if (mIsFile) {
     // We cannot use PR_Now() because bug 493756 and, for this reason:
     //   var x = new Date(); var f = new File(...);
     //   x.getTime() < f.dateModified.getTime()
     // could fail.
+    //
+    // Round to 100ms if javascript.options.privacy.reduce_time_precision is on.
     mLastModificationDate =
-      lastModifiedSet ? lastModified * PR_USEC_PER_MSEC : JS_Now();
+      lastModifiedSet ? lastModified * PR_USEC_PER_MSEC
+                      : TimePrecisionReducer::ReduceAsUSecsIfPrefIsOn(JS_Now());
   }
 }
 
 void
 MultipartBlobImpl::GetMozFullPathInternal(nsAString& aFilename,
                                           ErrorResult& aRv) const
 {
   if (!mIsFromNsIFile || mBlobImpls.Length() == 0) {
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/HTMLMediaElementBinding.h"
 #include "mozilla/dom/HTMLSourceElement.h"
 #include "mozilla/dom/ElementInlines.h"
 #include "mozilla/dom/Promise.h"
+#include "mozilla/dom/TimePrecisionReducer.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/dom/MediaEncryptedEvent.h"
 #include "mozilla/EMEUtils.h"
 
 #include "base/basictypes.h"
 #include "nsIDOMHTMLMediaElement.h"
@@ -2403,32 +2404,38 @@ HTMLMediaElement::Seeking() const
 
 NS_IMETHODIMP HTMLMediaElement::GetSeeking(bool* aSeeking)
 {
   *aSeeking = Seeking();
   return NS_OK;
 }
 
 double
-HTMLMediaElement::CurrentTime() const
+HTMLMediaElement::CurrentTimeImpl() const
 {
   if (MediaStream* stream = GetSrcMediaStream()) {
     if (mSrcStreamPausedCurrentTime >= 0) {
       return mSrcStreamPausedCurrentTime;
     }
     return stream->StreamTimeToSeconds(stream->GetCurrentTime());
   }
 
   if (mDefaultPlaybackStartPosition == 0.0 && mDecoder) {
     return mDecoder->GetCurrentTime();
   }
 
   return mDefaultPlaybackStartPosition;
 }
 
+double HTMLMediaElement::CurrentTime() const
+{
+  // Round to 100ms if javascript.options.privacy.reduce_time_precision is on.
+  return TimePrecisionReducer::ReduceAsSecsIfPrefIsOn(CurrentTimeImpl());
+}
+
 NS_IMETHODIMP HTMLMediaElement::GetCurrentTime(double* aCurrentTime)
 {
   *aCurrentTime = CurrentTime();
   return NS_OK;
 }
 
 void
 HTMLMediaElement::FastSeek(double aTime, ErrorResult& aRv)
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1698,14 +1698,17 @@ private:
   nsTArray<RefPtr<Promise>> mPendingPlayPromises;
 
   // A list of already-dispatched but not yet run
   // nsResolveOrRejectPendingPlayPromisesRunners.
   // Runners whose Run() method is called remove themselves from this list.
   // We keep track of these because the load algorithm resolves/rejects all
   // already-dispatched pending play promises.
   nsTArray<nsResolveOrRejectPendingPlayPromisesRunner*> mPendingPlayPromisesRunners;
+
+  // The unrounded current time.
+  double CurrentTimeImpl() const;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLMediaElement_h
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -7,16 +7,17 @@
 #include "nsContentUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIScriptError.h"
 #include "nsIUUIDGenerator.h"
 #include "nsPIDOMWindow.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamTrackEvent.h"
 #include "mozilla/dom/LocalMediaStreamBinding.h"
+#include "mozilla/dom/TimePrecisionReducer.h"
 #include "mozilla/dom/AudioNode.h"
 #include "AudioChannelAgent.h"
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/VideoTrackList.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/media/MediaUtils.h"
@@ -539,18 +540,19 @@ DOMMediaStream::Constructor(const Global
 }
 
 double
 DOMMediaStream::CurrentTime()
 {
   if (!mPlaybackStream) {
     return 0.0;
   }
-  return mPlaybackStream->
-    StreamTimeToSeconds(mPlaybackStream->GetCurrentTime() - mLogicalStreamStartTime);
+  // Round to 100ms if javascript.options.privacy.reduce_time_precision is on.
+  return TimePrecisionReducer::ReduceAsSecsIfPrefIsOn(mPlaybackStream->
+    StreamTimeToSeconds(mPlaybackStream->GetCurrentTime() - mLogicalStreamStartTime));
 }
 
 void
 DOMMediaStream::GetId(nsAString& aID) const
 {
   aID = mID;
 }
 
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/ErrorResult.h"
 #include "mozilla/OwningNonNull.h"
 
 #include "mozilla/dom/AnalyserNode.h"
 #include "mozilla/dom/AudioContextBinding.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/OfflineAudioContextBinding.h"
 #include "mozilla/dom/Promise.h"
+#include "mozilla/dom/TimePrecisionReducer.h"
 
 #include "AudioBuffer.h"
 #include "AudioBufferSourceNode.h"
 #include "AudioChannelService.h"
 #include "AudioDestinationNode.h"
 #include "AudioListener.h"
 #include "AudioStream.h"
 #include "BiquadFilterNode.h"
@@ -728,17 +729,19 @@ AudioContext::DestinationStream() const
   }
   return nullptr;
 }
 
 double
 AudioContext::CurrentTime() const
 {
   MediaStream* stream = Destination()->Stream();
-  return stream->StreamTimeToSeconds(stream->GetCurrentTime());
+  // Round to 100ms if javascript.options.privacy.reduce_time_precision is on.
+  return TimePrecisionReducer::ReduceAsSecsIfPrefIsOn(
+      stream->StreamTimeToSeconds(stream->GetCurrentTime()));
 }
 
 void
 AudioContext::Shutdown()
 {
   mIsShutDown = true;
 
   if (!mIsOffline) {
--- a/dom/performance/Performance.cpp
+++ b/dom/performance/Performance.cpp
@@ -15,16 +15,17 @@
 #include "PerformanceResourceTiming.h"
 #include "PerformanceService.h"
 #include "PerformanceWorker.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/PerformanceBinding.h"
 #include "mozilla/dom/PerformanceEntryEvent.h"
 #include "mozilla/dom/PerformanceNavigationBinding.h"
 #include "mozilla/dom/PerformanceObserverBinding.h"
+#include "mozilla/dom/TimePrecisionReducer.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Preferences.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 
 #ifdef MOZ_WIDGET_GONK
 #define PERFLOG(msg, ...)  __android_log_print(ANDROID_LOG_INFO, "PerformanceTiming", msg, ##__VA_ARGS__)
 #else
@@ -236,18 +237,20 @@ Performance::ClearResourceTimings()
 }
 
 DOMHighResTimeStamp
 Performance::RoundTime(double aTime) const
 {
   // Round down to the nearest 5us, because if the timer is too accurate people
   // can do nasty timing attacks with it.  See similar code in the worker
   // Performance implementation.
-  const double maxResolutionMs = 0.005;
-  return floor(aTime / maxResolutionMs) * maxResolutionMs;
+  double maxResolutionMs = 0.005;
+  // Round to 100ms if javascript.options.privacy.reduce_time_precision is on.
+  return TimePrecisionReducer::ReduceAsMSecsIfPrefIsOn(
+      floor(aTime / maxResolutionMs) * maxResolutionMs);
 }
 
 
 void
 Performance::Mark(const nsAString& aName, ErrorResult& aRv)
 {
   // Don't add the entry if the buffer is full. XXX should be removed by bug 1159003.
   if (mUserEntries.Length() >= mResourceTimingBufferSize) {
new file mode 100644
--- /dev/null
+++ b/dom/security/TimePrecisionReducer.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TimePrecisionReducer_h
+#define mozilla_dom_TimePrecisionReducer_h
+
+#include "mozilla/Preferences.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsThreadUtils.h"
+
+// Reduce time precision to 100ms if javascript.options.privacy.reduce_time_precision
+// is on.
+class TimePrecisionReducer final : public mozilla::Runnable
+{
+public:
+  static double ReduceAsSecsIfPrefIsOn(double aTime)
+  {
+    if (!IsPrefOn()) {
+      return aTime;
+    }
+    return floor(aTime * 10) / 10;
+  }
+
+  static double ReduceAsMSecsIfPrefIsOn(double aTime)
+  {
+    if (!IsPrefOn()) {
+      return aTime;
+    }
+    return floor(aTime / 100) * 100;
+  }
+
+  static double ReduceAsUSecsIfPrefIsOn(double aTime)
+  {
+    if (!IsPrefOn()) {
+      return aTime;
+    }
+    return floor(aTime / 100000) * 100000;
+  }
+
+private:
+  bool mIsPrefOn;
+  static bool IsPrefOn()
+  {
+    nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+    MOZ_ASSERT(mainThread);
+    RefPtr<TimePrecisionReducer> runnable = new TimePrecisionReducer();
+    mozilla::SyncRunnable::DispatchToThread(mainThread, runnable);
+    return runnable->mIsPrefOn;
+  }
+
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    mIsPrefOn = mozilla::Preferences::GetBool(
+        "javascript.options.privacy.reduce_time_precision");
+    return NS_OK;
+  }
+
+  TimePrecisionReducer() : mIsPrefOn(false) {}
+};
+
+#endif /* mozilla_dom_TimePrecisionReducer_h */
--- a/dom/security/moz.build
+++ b/dom/security/moz.build
@@ -11,16 +11,17 @@ EXPORTS.mozilla.dom += [
     'nsContentSecurityManager.h',
     'nsCSPContext.h',
     'nsCSPService.h',
     'nsCSPUtils.h',
     'nsMixedContentBlocker.h',
     'SRICheck.h',
     'SRILogHelper.h',
     'SRIMetadata.h',
+    'TimePrecisionReducer.h',
 ]
 
 EXPORTS += [
     'nsContentSecurityManager.h',
     'nsMixedContentBlocker.h',
 ]
 
 UNIFIED_SOURCES += [
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -301,16 +301,17 @@ LoadContextOptions(const char* aPrefName
   JS::ContextOptions contextOptions;
   contextOptions.setAsmJS(GetWorkerPref<bool>(NS_LITERAL_CSTRING("asmjs")))
                 .setWasm(GetWorkerPref<bool>(NS_LITERAL_CSTRING("wasm")))
                 .setThrowOnAsmJSValidationFailure(GetWorkerPref<bool>(
                       NS_LITERAL_CSTRING("throw_on_asmjs_validation_failure")))
                 .setBaseline(GetWorkerPref<bool>(NS_LITERAL_CSTRING("baselinejit")))
                 .setIon(GetWorkerPref<bool>(NS_LITERAL_CSTRING("ion")))
                 .setNativeRegExp(GetWorkerPref<bool>(NS_LITERAL_CSTRING("native_regexp")))
+                .setReduceTimePrecision(GetWorkerPref<bool>(NS_LITERAL_CSTRING("privacy.reduce_time_precision")))
                 .setAsyncStack(GetWorkerPref<bool>(NS_LITERAL_CSTRING("asyncstack")))
                 .setWerror(GetWorkerPref<bool>(NS_LITERAL_CSTRING("werror")))
                 .setExtraWarnings(GetWorkerPref<bool>(NS_LITERAL_CSTRING("strict")));
 
   RuntimeService::SetDefaultContextOptions(contextOptions);
 
   if (rts) {
     rts->UpdateAllWorkerContextOptions();
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1088,16 +1088,17 @@ class JS_PUBLIC_API(ContextOptions) {
     ContextOptions()
       : baseline_(true),
         ion_(true),
         asmJS_(true),
         wasm_(false),
         wasmAlwaysBaseline_(false),
         throwOnAsmJSValidationFailure_(false),
         nativeRegExp_(true),
+        reduceTimePrecision_(false),
         unboxedArrays_(false),
         asyncStack_(true),
         throwOnDebuggeeWouldRun_(true),
         dumpStackOnDebuggeeWouldRun_(false),
         werror_(false),
         strictMode_(false),
         extraWarnings_(false),
 #ifdef NIGHTLY_BUILD
@@ -1169,16 +1170,22 @@ class JS_PUBLIC_API(ContextOptions) {
     }
 
     bool nativeRegExp() const { return nativeRegExp_; }
     ContextOptions& setNativeRegExp(bool flag) {
         nativeRegExp_ = flag;
         return *this;
     }
 
+    bool reduceTimePrecision() const { return reduceTimePrecision_; }
+    ContextOptions& setReduceTimePrecision(bool flag) {
+        reduceTimePrecision_ = flag;
+        return *this;
+    }
+
     bool unboxedArrays() const { return unboxedArrays_; }
     ContextOptions& setUnboxedArrays(bool flag) {
         unboxedArrays_ = flag;
         return *this;
     }
 
     bool asyncStack() const { return asyncStack_; }
     ContextOptions& setAsyncStack(bool flag) {
@@ -1237,16 +1244,17 @@ class JS_PUBLIC_API(ContextOptions) {
   private:
     bool baseline_ : 1;
     bool ion_ : 1;
     bool asmJS_ : 1;
     bool wasm_ : 1;
     bool wasmAlwaysBaseline_ : 1;
     bool throwOnAsmJSValidationFailure_ : 1;
     bool nativeRegExp_ : 1;
+    bool reduceTimePrecision_ : 1;
     bool unboxedArrays_ : 1;
     bool asyncStack_ : 1;
     bool throwOnDebuggeeWouldRun_ : 1;
     bool dumpStackOnDebuggeeWouldRun_ : 1;
     bool werror_ : 1;
     bool strictMode_ : 1;
     bool extraWarnings_ : 1;
     bool forEachStatement_: 1;
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -1225,26 +1225,32 @@ date_parse(JSContext* cx, unsigned argc,
         return true;
     }
 
     args.rval().set(TimeValue(result));
     return true;
 }
 
 static ClippedTime
-NowAsMillis()
+NowAsMillis(JSContext* cx)
 {
-    return TimeClip(static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_MSEC);
+    double now = static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_MSEC;
+    if (cx->options().reduceTimePrecision()) {
+      // Truncate all date objects to 100ms precision
+      return TimeClip(floor(now / 100.0) * 100.0);
+    } else {
+      return TimeClip(now);
+    }
 }
 
 bool
 js::date_now(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    args.rval().set(TimeValue(NowAsMillis()));
+    args.rval().set(TimeValue(NowAsMillis(cx)));
     return true;
 }
 
 void
 DateObject::setUTCTime(ClippedTime t)
 {
     for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++)
         setReservedSlot(ind, UndefinedValue());
@@ -3068,17 +3074,17 @@ ToDateString(JSContext* cx, const CallAr
     return FormatDate(cx, t.toDouble(), FormatSpec::DateTime, args.rval());
 }
 
 static bool
 DateNoArguments(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(args.length() == 0);
 
-    ClippedTime now = NowAsMillis();
+    ClippedTime now = NowAsMillis(cx);
 
     if (args.isConstructing())
         return NewDateObject(cx, args, now);
 
     return ToDateString(cx, args, now);
 }
 
 static bool
@@ -3119,17 +3125,17 @@ DateOneArgument(JSContext* cx, const Cal
             if (!ToNumber(cx, args[0], &d))
                 return false;
             t = TimeClip(d);
         }
 
         return NewDateObject(cx, args, t);
     }
 
-    return ToDateString(cx, args, NowAsMillis());
+    return ToDateString(cx, args, NowAsMillis(cx));
 }
 
 static bool
 DateMultipleArguments(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(args.length() >= 2);
 
     // Step 3.
@@ -3199,17 +3205,17 @@ DateMultipleArguments(JSContext* cx, con
 
         // Step 3p.
         double finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli));
 
         // Steps 3q-t.
         return NewDateObject(cx, args, TimeClip(UTC(finalDate)));
     }
 
-    return ToDateString(cx, args, NowAsMillis());
+    return ToDateString(cx, args, NowAsMillis(cx));
 }
 
 bool
 js::DateConstructor(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() == 0)
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -313,16 +313,17 @@ struct MOZ_STACK_CLASS EnvironmentPrepar
 static bool enableCodeCoverage = false;
 static bool enableDisassemblyDumps = false;
 static bool offthreadCompilation = false;
 static bool enableBaseline = false;
 static bool enableIon = false;
 static bool enableAsmJS = false;
 static bool enableWasm = false;
 static bool enableNativeRegExp = false;
+static bool enableReduceTimePrecision = false;
 static bool enableUnboxedArrays = false;
 static bool enableSharedMemory = SHARED_MEMORY_DEFAULT;
 static bool enableWasmAlwaysBaseline = false;
 #ifdef JS_GC_ZEAL
 static uint32_t gZealBits = 0;
 static uint32_t gZealFrequency = 0;
 #endif
 static bool printTiming = false;
@@ -7216,25 +7217,27 @@ ProcessArgs(JSContext* cx, OptionParser*
 static bool
 SetContextOptions(JSContext* cx, const OptionParser& op)
 {
     enableBaseline = !op.getBoolOption("no-baseline");
     enableIon = !op.getBoolOption("no-ion");
     enableAsmJS = !op.getBoolOption("no-asmjs");
     enableWasm = !op.getBoolOption("no-wasm");
     enableNativeRegExp = !op.getBoolOption("no-native-regexp");
+    enableReduceTimePrecision = op.getBoolOption("reduce-time-precision");
     enableUnboxedArrays = op.getBoolOption("unboxed-arrays");
     enableWasmAlwaysBaseline = op.getBoolOption("wasm-always-baseline");
 
     JS::ContextOptionsRef(cx).setBaseline(enableBaseline)
                              .setIon(enableIon)
                              .setAsmJS(enableAsmJS)
                              .setWasm(enableWasm)
                              .setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
                              .setNativeRegExp(enableNativeRegExp)
+                             .setReduceTimePrecision(enableReduceTimePrecision)
                              .setUnboxedArrays(enableUnboxedArrays);
 
     if (op.getBoolOption("wasm-check-bce"))
         jit::JitOptions.wasmAlwaysCheckBounds = true;
 
     if (op.getBoolOption("no-unboxed-objects"))
         jit::JitOptions.disableUnboxedObjects = true;
 
@@ -7506,16 +7509,17 @@ SetWorkerContextOptions(JSContext* cx)
 {
     // Copy option values from the main thread.
     JS::ContextOptionsRef(cx).setBaseline(enableBaseline)
                              .setIon(enableIon)
                              .setAsmJS(enableAsmJS)
                              .setWasm(enableWasm)
                              .setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
                              .setNativeRegExp(enableNativeRegExp)
+                             .setReduceTimePrecision(enableReduceTimePrecision)
                              .setUnboxedArrays(enableUnboxedArrays);
     cx->setOffthreadIonCompilationEnabled(offthreadCompilation);
     cx->profilingScripts = enableCodeCoverage || enableDisassemblyDumps;
 
 #ifdef JS_GC_ZEAL
     if (gZealBits && gZealFrequency) {
 #define ZEAL_MODE(_, value)                        \
         if (gZealBits & (1 << value))              \
@@ -7685,16 +7689,17 @@ main(int argc, char** argv, char** envp)
                                          "shell's global")
         || !op.addIntOption('\0', "thread-count", "COUNT", "Use COUNT auxiliary threads "
                             "(default: # of cores - 1)", -1)
         || !op.addBoolOption('\0', "ion", "Enable IonMonkey (default)")
         || !op.addBoolOption('\0', "no-ion", "Disable IonMonkey")
         || !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation")
         || !op.addBoolOption('\0', "no-wasm", "Disable WebAssembly compilation")
         || !op.addBoolOption('\0', "no-native-regexp", "Disable native regexp compilation")
+        || !op.addBoolOption('\0', "reduce-time-precision", "Reduce time precision to 100ms")
         || !op.addBoolOption('\0', "no-unboxed-objects", "Disable creating unboxed plain objects")
         || !op.addBoolOption('\0', "unboxed-arrays", "Allow creating unboxed arrays")
         || !op.addBoolOption('\0', "wasm-always-baseline", "Enable wasm baseline compiler when possible")
         || !op.addBoolOption('\0', "wasm-check-bce", "Always generate wasm bounds check, even redundant ones.")
 #ifdef ENABLE_SHARED_ARRAY_BUFFER
         || !op.addStringOption('\0', "shared-memory", "on/off",
                                "SharedArrayBuffer and Atomics "
 #  if SHARED_MEMORY_DEFAULT
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -1436,16 +1436,18 @@ ReloadPrefsCallback(const char* pref, vo
     bool useIon = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion") && !safeMode;
     bool useAsmJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "asmjs") && !safeMode;
     bool useWasm = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm") && !safeMode;
     bool useWasmBaseline = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_baselinejit") && !safeMode;
     bool throwOnAsmJSValidationFailure = Preferences::GetBool(JS_OPTIONS_DOT_STR
                                                               "throw_on_asmjs_validation_failure");
     bool useNativeRegExp = Preferences::GetBool(JS_OPTIONS_DOT_STR "native_regexp") && !safeMode;
 
+    bool reduceTimePrecision = Preferences::GetBool(JS_OPTIONS_DOT_STR "privacy.reduce_time_precision");
+
     bool parallelParsing = Preferences::GetBool(JS_OPTIONS_DOT_STR "parallel_parsing");
     bool offthreadIonCompilation = Preferences::GetBool(JS_OPTIONS_DOT_STR
                                                        "ion.offthread_compilation");
     bool useBaselineEager = Preferences::GetBool(JS_OPTIONS_DOT_STR
                                                  "baselinejit.unsafe_eager_compilation");
     bool useIonEager = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion.unsafe_eager_compilation");
 
     sDiscardSystemSource = Preferences::GetBool(JS_OPTIONS_DOT_STR "discardSystemSource");
@@ -1480,16 +1482,17 @@ ReloadPrefsCallback(const char* pref, vo
 
     JS::ContextOptionsRef(cx).setBaseline(useBaseline)
                              .setIon(useIon)
                              .setAsmJS(useAsmJS)
                              .setWasm(useWasm)
                              .setWasmAlwaysBaseline(useWasmBaseline)
                              .setThrowOnAsmJSValidationFailure(throwOnAsmJSValidationFailure)
                              .setNativeRegExp(useNativeRegExp)
+                             .setReduceTimePrecision(reduceTimePrecision)
                              .setAsyncStack(useAsyncStack)
                              .setThrowOnDebuggeeWouldRun(throwOnDebuggeeWouldRun)
                              .setDumpStackOnDebuggeeWouldRun(dumpStackOnDebuggeeWouldRun)
                              .setWerror(werror)
                              .setExtraWarnings(extraWarnings);
 
     JS_SetParallelParsingEnabled(cx, parallelParsing);
     JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -40,16 +40,17 @@
 #include "jsapi.h"
 #include "nsContentUtils.h"
 #include "mozilla/PendingAnimationTracker.h"
 #include "mozilla/Preferences.h"
 #include "nsViewManager.h"
 #include "GeckoProfiler.h"
 #include "nsNPAPIPluginInstance.h"
 #include "mozilla/dom/Performance.h"
+#include "mozilla/dom/TimePrecisionReducer.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "mozilla/RestyleManager.h"
 #include "mozilla/RestyleManagerHandle.h"
 #include "mozilla/RestyleManagerHandleInlines.h"
 #include "Layers.h"
 #include "imgIContainer.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "nsDocShell.h"
@@ -1693,17 +1694,20 @@ nsRefreshDriver::RunFrameRequestCallback
       // XXXbz Bug 863140: GetInnerWindow can return the outer
       // window in some cases.
       nsPIDOMWindowInner* innerWindow =
         docCallbacks.mDocument->GetInnerWindow();
       DOMHighResTimeStamp timeStamp = 0;
       if (innerWindow && innerWindow->IsInnerWindow()) {
         mozilla::dom::Performance* perf = innerWindow->GetPerformance();
         if (perf) {
-          timeStamp = perf->GetDOMTiming()->TimeStampToDOMHighRes(aNowTime);
+          // Round to 100ms if javascript.options.privacy.reduce_time_precision
+          // is on.
+          timeStamp = TimePrecisionReducer::ReduceAsMSecsIfPrefIsOn(
+              perf->GetDOMTiming()->TimeStampToDOMHighRes(aNowTime));
         }
         // else window is partially torn down already
       }
       for (auto& callback : docCallbacks.mCallbacks) {
         callback->Call(timeStamp);
       }
     }
     profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_END);
--- a/layout/style/nsAnimationManager.h
+++ b/layout/style/nsAnimationManager.h
@@ -5,16 +5,17 @@
 #ifndef nsAnimationManager_h_
 #define nsAnimationManager_h_
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/EventForwards.h"
 #include "AnimationCommon.h"
 #include "mozilla/dom/Animation.h"
+#include "mozilla/dom/TimePrecisionReducer.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/TimeStamp.h"
 
 class nsIGlobalObject;
 class nsStyleContext;
 
 namespace mozilla {
 namespace css {
@@ -42,17 +43,18 @@ struct AnimationEventInfo {
                      dom::Animation* aAnimation)
     : mElement(aElement)
     , mAnimation(aAnimation)
     , mEvent(true, aMessage)
     , mTimeStamp(aTimeStamp)
   {
     // XXX Looks like nobody initialize WidgetEvent::time
     mEvent.mAnimationName = aAnimationName;
-    mEvent.mElapsedTime = aElapsedTime.ToSeconds();
+    mEvent.mElapsedTime =
+      TimePrecisionReducer::ReduceAsSecsIfPrefIsOn(aElapsedTime.ToSeconds());
     mEvent.mPseudoElement =
       AnimationCollection<dom::CSSAnimation>::PseudoTypeAsString(aPseudoType);
   }
 
   // InternalAnimationEvent doesn't support copy-construction, so we need
   // to ourselves in order to work with nsTArray
   AnimationEventInfo(const AnimationEventInfo& aOther)
     : mElement(aOther.mElement)