diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 6a1ce7a08425..13b66ecd1ca0 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -4939,6 +4939,33 @@ IsLegacyIterator(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool +SetTimeResolution(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject callee(cx, &args.callee()); + + if (!args.requireAtLeast(cx, "setTimeResolution", 2)) + return false; + + if (!args[0].isInt32()) { + ReportUsageErrorASCII(cx, callee, "First argument must be an Int32."); + return false; + } + int32_t resolution = args[0].toInt32(); + + if (!args[1].isBoolean()) { + ReportUsageErrorASCII(cx, callee, "Second argument must be a Boolean"); + return false; + } + bool jitter = args[1].toBoolean(); + + JS::SetTimeResolutionUsec(resolution, jitter); + + args.rval().setUndefined(); + return true; +} + static bool EnableExpressionClosures(JSContext* cx, unsigned argc, Value* vp) { @@ -5705,6 +5732,11 @@ gc::ZealModeHelpText), "getTimeZone()", " Get the current time zone.\n"), + JS_FN_HELP("setTimeResolution", SetTimeResolution, 2, 0, +"setTimeResolution(resolution, jitter)", +" Enables time clamping and jittering. Specify a time resolution in\n" +" microseconds and whether or not to jitter\n"), + JS_FN_HELP("enableExpressionClosures", EnableExpressionClosures, 0, 0, "enableExpressionClosures()", " Enables the deprecated, non-standard expression closures.\n"), diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp old mode 100644 new mode 100755 index 3ecbf4ce762b..fbfcb9693205 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -1310,8 +1310,37 @@ NowAsMillis() double now = PRMJ_Now(); if (sReduceMicrosecondTimePrecisionCallback) now = sReduceMicrosecondTimePrecisionCallback(now); - else if (sResolutionUsec) - now = floor(now / sResolutionUsec) * sResolutionUsec; + else if (sResolutionUsec) { + double clamped = floor(now / sResolutionUsec) * sResolutionUsec; + + if (sJitter) { + // Calculate a random midpoint for jittering. In the browser, we are adversarial: + // Web Content may try to calculate the midpoint themselves and use that to bypass + // it's security. In the JS Shell, we are not adversarial, we want to jitter the + // time to recreate the operating environment, but we do not concern ourselves + // with trying to prevent an attacker from calculating the midpoint themselves. + // So we use a very simple, very fast CRC with a hardcoded seed. + + uint64_t midpoint = *((uint64_t*)&clamped); + midpoint ^= 0x0F00DD1E2BAD2DED; // XOR in a 'secret' + // MurmurHash3 internal component from + // https://searchfox.org/mozilla-central/rev/61d400da1c692453c2dc2c1cf37b616ce13dea5b/dom/canvas/MurmurHash3.cpp#85 + midpoint ^= midpoint >> 33; + midpoint *= uint64_t{0xFF51AFD7ED558CCD}; + midpoint ^= midpoint >> 33; + midpoint *= uint64_t{0xC4CEB9FE1A85EC53}; + midpoint ^= midpoint >> 33; + midpoint %= sResolutionUsec; + + if (now > clamped + midpoint) { // We're jittering up to the next step + now = clamped + sResolutionUsec; + } else { // We're staying at the clamped value + now = clamped; + } + } else { //No jitter, only clamping + now = clamped; + } + } return TimeClip(now / PRMJ_USEC_PER_MSEC); }