Bug 1462891 - Add Timer Rounding to Navigation APIs. r=baku, a=RyanVM
authorTom Ritter <tom@mozilla.com>
Thu, 24 May 2018 15:44:46 -0500
changeset 473605 30b75eae500bb1a8d6c21e0050ca606a9a7da0a6
parent 473604 e049dc25908cf34ecde90b6d47eb2aa1b896c132
child 473606 f97d4447e80247f695362b197e0156afdf58acf2
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, RyanVM
bugs1462891
milestone61.0
Bug 1462891 - Add Timer Rounding to Navigation APIs. r=baku, a=RyanVM
dom/performance/PerformanceNavigationTiming.cpp
dom/performance/tests/mochitest.ini
dom/performance/tests/test_performance_navigation_timing.html
--- a/dom/performance/PerformanceNavigationTiming.cpp
+++ b/dom/performance/PerformanceNavigationTiming.cpp
@@ -16,62 +16,93 @@ NS_IMPL_ADDREF_INHERITED(PerformanceNavi
 NS_IMPL_RELEASE_INHERITED(PerformanceNavigationTiming, PerformanceResourceTiming)
 
 JSObject*
 PerformanceNavigationTiming::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PerformanceNavigationTimingBinding::Wrap(aCx, this, aGivenProto);
 }
 
+#define REDUCE_TIME_PRECISION                               \
+  if (mPerformance->IsSystemPrincipal()) {                  \
+    return rawValue;                                        \
+  }                                                         \
+  return nsRFPService::ReduceTimePrecisionAsMSecs(rawValue, \
+    mPerformance->GetRandomTimelineSeed())
+
 DOMHighResTimeStamp
 PerformanceNavigationTiming::UnloadEventStart() const
 {
-  return mPerformance->GetDOMTiming()->GetUnloadEventStartHighRes();
+  DOMHighResTimeStamp rawValue =
+    mPerformance->GetDOMTiming()->GetUnloadEventStartHighRes();
+
+  REDUCE_TIME_PRECISION;
 }
 
 DOMHighResTimeStamp
 PerformanceNavigationTiming::UnloadEventEnd() const
 {
-  return mPerformance->GetDOMTiming()->GetUnloadEventEndHighRes();
+  DOMHighResTimeStamp rawValue =
+    mPerformance->GetDOMTiming()->GetUnloadEventEndHighRes();
+
+  REDUCE_TIME_PRECISION;
 }
 
 DOMHighResTimeStamp
 PerformanceNavigationTiming::DomInteractive() const
 {
-  return mPerformance->GetDOMTiming()->GetDomInteractiveHighRes();
+  DOMHighResTimeStamp rawValue =
+    mPerformance->GetDOMTiming()->GetDomInteractiveHighRes();
+
+  REDUCE_TIME_PRECISION;
 }
 
 DOMHighResTimeStamp
 PerformanceNavigationTiming::DomContentLoadedEventStart() const
 {
-  return mPerformance->GetDOMTiming()->GetDomContentLoadedEventStartHighRes();
+  DOMHighResTimeStamp rawValue =
+    mPerformance->GetDOMTiming()->GetDomContentLoadedEventStartHighRes();
+
+  REDUCE_TIME_PRECISION;
 }
 
 DOMHighResTimeStamp
 PerformanceNavigationTiming::DomContentLoadedEventEnd() const
 {
-  return mPerformance->GetDOMTiming()->GetDomContentLoadedEventEndHighRes();
+  DOMHighResTimeStamp rawValue =
+    mPerformance->GetDOMTiming()->GetDomContentLoadedEventEndHighRes();
+
+  REDUCE_TIME_PRECISION;
 }
 
 DOMHighResTimeStamp
 PerformanceNavigationTiming::DomComplete() const
 {
-  return mPerformance->GetDOMTiming()->GetDomCompleteHighRes();
+  DOMHighResTimeStamp rawValue =
+    mPerformance->GetDOMTiming()->GetDomCompleteHighRes();
+
+  REDUCE_TIME_PRECISION;
 }
 
 DOMHighResTimeStamp
 PerformanceNavigationTiming::LoadEventStart() const
 {
-  return mPerformance->GetDOMTiming()->GetLoadEventStartHighRes();
+  DOMHighResTimeStamp rawValue =
+    mPerformance->GetDOMTiming()->GetLoadEventStartHighRes();
+
+  REDUCE_TIME_PRECISION;
 }
 
 DOMHighResTimeStamp
 PerformanceNavigationTiming::LoadEventEnd() const
 {
-  return mPerformance->GetDOMTiming()->GetLoadEventEndHighRes();
+  DOMHighResTimeStamp rawValue =
+    mPerformance->GetDOMTiming()->GetLoadEventEndHighRes();
+
+  REDUCE_TIME_PRECISION;
 }
 
 NavigationType
 PerformanceNavigationTiming::Type() const
 {
   switch(mPerformance->GetDOMTiming()->GetType()) {
     case nsDOMNavigationTiming::TYPE_NAVIGATE:
       return NavigationType::Navigate;
--- a/dom/performance/tests/mochitest.ini
+++ b/dom/performance/tests/mochitest.ini
@@ -8,16 +8,17 @@ support-files =
   sharedworker_performance_user_timing.js
   test_worker_performance_entries.js
   test_worker_performance_entries.sjs
   empty.js
   serverTiming.sjs
 
 [test_performance_observer.html]
 [test_performance_user_timing.html]
+[test_performance_navigation_timing.html]
 [test_worker_user_timing.html]
 [test_worker_observer.html]
 [test_sharedWorker_performance_user_timing.html]
 [test_worker_performance_now.html]
 [test_timeOrigin.html]
 [test_worker_performance_entries.html]
 [test_performance_server_timing.html]
 scheme = https
new file mode 100644
--- /dev/null
+++ b/dom/performance/tests/test_performance_navigation_timing.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+    <!--
+    https://bugzilla.mozilla.org/show_bug.cgi?id=1462891
+    -->
+    <head>
+        <title>Test for Bug 1462891</title>
+        <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    </head>
+    <body>
+        <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1462891">Mozilla Bug 1462891 - Navigation Timing API</a>
+        <div id="content">
+        </div>
+        <pre id="test">
+            <script class="testbody" type="text/javascript">
+             var index = 0;
+             let isRounded = (x, shouldRound, expectedPrecision) => {
+                if (!shouldRound)
+                   return true;
+
+                let rounded = (Math.floor(x / expectedPrecision) * expectedPrecision);
+                // First we do the perfectly normal check that should work just fine
+                if (rounded === x || x === 0)
+                return true;
+
+                // When we're diving by non-whole numbers, we may not get perfect
+                // multiplication/division because of floating points.
+                // When dealing with ms since epoch, a double's precision is on the order
+                // of 1/5 of a microsecond, so we use a value a little higher than that as
+                // our epsilon.
+                // To be clear, this error is introduced in our re-calculation of 'rounded'
+                // above in JavaScript.
+                if (Math.abs(rounded - x + expectedPrecision) < .0005) {
+                 return true;
+                } else if (Math.abs(rounded - x) < .0005) {
+                 return true;
+                }
+
+                 // Then we handle the case where you're sub-millisecond and the timer is not
+                 // We check that the timer is not sub-millisecond by assuming it is not if it
+                 // returns an even number of milliseconds
+                 if (expectedPrecision < 1 && Math.round(x) == x) {
+                   if (Math.round(rounded) == x) {
+                     return true;
+                   }
+                 }
+
+                 ok(false, "Looming Test Failure, Additional Debugging Info: Expected Precision: " + expectedPrecision + " Measured Value: " + x +
+                   " Rounded Vaue: " + rounded + " Fuzzy1: " + Math.abs(rounded - x + expectedPrecision) +
+                   " Fuzzy 2: " + Math.abs(rounded - x));
+
+                 return false;
+            };
+
+            var metrics = [
+                "unloadEventStart",
+                "unloadEventEnd",
+                "domInteractive",
+                "domContentLoadedEventStart",
+                "domContentLoadedEventEnd",
+                "domComplete",
+                "loadEventStart",
+                "loadEventEnd"
+            ];
+
+            async function runTests(resistFingerprinting, reduceTimerPrecision, expectedPrecision) {
+                await SpecialPowers.pushPrefEnv({
+                    "set": [["privacy.resistFingerprinting", resistFingerprinting],
+                            ["privacy.reduceTimerPrecision", reduceTimerPrecision],
+                            ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+                ]});
+                var entries = performance.getEntriesByType("navigation");
+                is(entries.length, resistFingerprinting ? 0 : 1, "Checking PerformanceNavigationEntry count");
+
+                for (let i=0; i<entries.length; i++) {
+                    for (let j=0; j<metrics.length; j++) {
+                        ok(isRounded(entries[i][metrics[j]], reduceTimerPrecision, expectedPrecision),
+                            "Testing " + metrics[j] + " with value " + entries[i][metrics[j]] +
+                            " with resistFingerprinting " + resistFingerprinting + " reduceTimerPrecision " +
+                            reduceTimerPrecision + " precision " + expectedPrecision);
+                    }
+                }
+            }
+
+            async function startTests() {
+                await runTests(false, false, 2);
+                await runTests(true, false, 2);
+                await runTests(true, true, 2);
+                await runTests(false, true, 1000);
+                await runTests(false, true, 133);
+                await runTests(false, true, 13);
+                await runTests(false, true, 2);
+                await runTests(false, true, 1);
+
+                SimpleTest.finish();
+            }
+
+            SimpleTest.waitForExplicitFinish();
+            addLoadEvent(startTests);
+            </script>
+        </pre>
+    </body>
+</html>