Bug 1026803 part 1 - Factor out a common utility class for converting wrapping native times to TimeStamps; r=karlt
authorBrian Birtles <birtles@gmail.com>
Tue, 11 Aug 2015 13:38:18 +0900
changeset 258992 d3c1a726529692b0ea8cf672539683820894c5f7
parent 258991 f8b3d9a3d5771e23cd99f899315831b97c774a92
child 258993 19b4b30fc2ba7ad6acda304deed46db4e6fea5f5
push id29268
push userryanvm@gmail.com
push dateTue, 25 Aug 2015 00:37:23 +0000
treeherdermozilla-central@08015770c9d6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskarlt
bugs1026803
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1026803 part 1 - Factor out a common utility class for converting wrapping native times to TimeStamps; r=karlt This just moves the code from widget/windows/nsWindow.cpp to a template class in widget/xpwidgets/WrappingTimeConverter.h so that we can reuse this code for other platforms (GTK at least).
widget/SystemTimeConverter.h
widget/windows/nsWindow.cpp
widget/windows/nsWindow.h
new file mode 100644
--- /dev/null
+++ b/widget/SystemTimeConverter.h
@@ -0,0 +1,132 @@
+/* -*- 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 SystemTimeConverter_h
+#define SystemTimeConverter_h
+
+#include <limits>
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+
+// Utility class that takes a time value representing an integral number of
+// milliseconds (e.g. a native event time) that wraps within a fixed range (e.g.
+// unsigned 32-bit range) and converts it to a TimeStamp.
+//
+// It does this by using a historical reference time recorded in both time
+// scales (i.e. both as a numerical time value and as a TimeStamp).
+//
+// For performance reasons, this class is careful to minimize calls to the
+// native "current time" function (e.g. gdk_x11_server_get_time) since this can
+// be slow. Furthermore, it uses TimeStamp::NowLowRes instead of TimeStamp::Now
+// except when establishing the reference time.
+template <typename Time>
+class SystemTimeConverter {
+public:
+  SystemTimeConverter()
+    : mReferenceTime(Time(0))
+    , mReferenceTimeStamp() // Initializes to the null timestamp
+    , kTimeRange(std::numeric_limits<Time>::max())
+    , kTimeHalfRange(kTimeRange / 2)
+  { }
+
+  template <typename GetCurrentTimeFunc>
+  mozilla::TimeStamp
+  GetTimeStampFromSystemTime(Time aTime,
+                             GetCurrentTimeFunc aGetCurrentTimeFunc) {
+    // If the reference time is not set, use the current time value to fill
+    // it in.
+    if (mReferenceTimeStamp.IsNull()) {
+      UpdateReferenceTime(aTime, aGetCurrentTimeFunc);
+    }
+    TimeStamp roughlyNow = TimeStamp::NowLoRes();
+
+    // If the time is before the reference time there are two possibilities:
+    //
+    //   a) Time has wrapped
+    //   b) The initial times were not delivered strictly in time order
+    //
+    // I'm not sure if (b) ever happens but even if it doesn't currently occur,
+    // it could come about if we change the way we fetch events, for example.
+    //
+    // We can tell we have (b) if the time is a little bit before the reference
+    // time (i.e. less than half the range) and the timestamp is only a little
+    // bit past the reference timestamp (again less than half the range).
+    // In that case we should adjust the reference time so it is the
+    // earliest time.
+    if (aTime < mReferenceTime &&
+        mReferenceTime - aTime < kTimeHalfRange &&
+        roughlyNow - mReferenceTimeStamp <
+          TimeDuration::FromMilliseconds(kTimeHalfRange)) {
+      UpdateReferenceTime(aTime, aGetCurrentTimeFunc);
+    }
+
+    double timeSinceReference =
+      mReferenceTime <= aTime
+        ? aTime - mReferenceTime
+        : static_cast<double>(kTimeRange) + aTime - mReferenceTime;
+    TimeStamp timestamp =
+      mReferenceTimeStamp + TimeDuration::FromMilliseconds(timeSinceReference);
+
+    // Time may have wrapped several times since we recorded the reference
+    // time so we extend timestamp as needed.
+    double timesWrapped =
+      (roughlyNow - mReferenceTimeStamp).ToMilliseconds() / kTimeRange;
+    int32_t cyclesToAdd = static_cast<int32_t>(timesWrapped); // floor
+
+    // There is some imprecision in the above calculation since we are using
+    // TimeStamp::NowLoRes and mReferenceTime and mReferenceTimeStamp may not
+    // be *exactly* the same moment. It is possible we think that time
+    // has *just* wrapped based on comparing timestamps, but actually the
+    // time is *just about* to wrap; or vice versa.
+    // In the following, we detect this situation and adjust cyclesToAdd as
+    // necessary.
+    double intervalFraction = fmod(timesWrapped, 1.0);
+
+    // If our rough calculation of how many times we've wrapped based on
+    // comparing timestamps says we've just wrapped (specifically, less 10% past
+    // the wrap point), but the time is just before the wrap point (again,
+    // within 10%), then we need to reduce the number of wraps by 1.
+    if (intervalFraction < 0.1 && timeSinceReference > kTimeRange * 0.9) {
+      cyclesToAdd--;
+    // Likewise, if our rough calculation says we've just wrapped but actually
+    // the time is just after the wrap point, we need to add an extra wrap.
+    } else if (intervalFraction > 0.9 &&
+               timeSinceReference < kTimeRange * 0.1) {
+      cyclesToAdd++;
+    }
+
+    if (cyclesToAdd > 0) {
+      timestamp += TimeDuration::FromMilliseconds(kTimeRange * cyclesToAdd);
+    }
+
+    return timestamp;
+  }
+
+private:
+  template <typename GetCurrentTimeFunc>
+  void
+  UpdateReferenceTime(Time aTime, GetCurrentTimeFunc aGetCurrentTimeFunc) {
+    mReferenceTime = aTime;
+    Time currentTime = aGetCurrentTimeFunc();
+    TimeStamp currentTimeStamp = TimeStamp::Now();
+    double timeSinceReference =
+      aTime <= currentTime
+        ? currentTime - aTime
+        : static_cast<double>(kTimeRange) + currentTime - aTime;
+    mReferenceTimeStamp =
+      currentTimeStamp - TimeDuration::FromMilliseconds(timeSinceReference);
+  }
+
+  Time mReferenceTime;
+  TimeStamp mReferenceTimeStamp;
+
+  const Time kTimeRange;
+  const Time kTimeHalfRange;
+};
+
+} // namespace mozilla
+
+#endif /* SystemTimeConverter_h */
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -117,16 +117,17 @@
 #include "nsIWindowMediator.h"
 #include "nsIServiceManager.h"
 #include "nsWindowGfx.h"
 #include "gfxWindowsPlatform.h"
 #include "Layers.h"
 #include "nsPrintfCString.h"
 #include "mozilla/Preferences.h"
 #include "nsISound.h"
+#include "SystemTimeConverter.h"
 #include "WinTaskbar.h"
 #include "WinUtils.h"
 #include "WidgetUtils.h"
 #include "nsIWidgetListener.h"
 #include "mozilla/dom/Touch.h"
 #include "mozilla/gfx/2D.h"
 #include "nsToolkitCompsCID.h"
 #include "nsIAppStartup.h"
@@ -242,18 +243,32 @@ LONG            nsWindow::sLastMouseDown
 LONG            nsWindow::sLastClickCount         = 0L;
 BYTE            nsWindow::sLastMouseButton        = 0;
 
 // Trim heap on minimize. (initialized, but still true.)
 int             nsWindow::sTrimOnMinimize         = 2;
 
 TriStateBool nsWindow::sHasBogusPopupsDropShadowOnMultiMonitor = TRI_UNKNOWN;
 
-DWORD           nsWindow::sFirstEventTime = 0;
-TimeStamp       nsWindow::sFirstEventTimeStamp = TimeStamp();
+namespace mozilla {
+
+class CurrentWindowsTimeGetter {
+public:
+  DWORD operator() () {
+    return ::GetTickCount();
+  }
+};
+
+} // namespace mozilla
+
+static SystemTimeConverter<DWORD>&
+TimeConverter() {
+  static SystemTimeConverter<DWORD> timeConverterSingleton;
+  return timeConverterSingleton;
+}
 
 /**************************************************************
  *
  * SECTION: globals variables
  *
  **************************************************************/
 
 static const char *sScreenManagerContractID       = "@mozilla.org/gfx/screenmanager;1";
@@ -293,20 +308,16 @@ static bool gIsPointerEventsEnabled = fa
 #define MAX_ACCELERATED_DIMENSION 8192
 
 // On window open (as well as after), Windows has an unfortunate habit of
 // sending rather a lot of WM_NCHITTEST messages. Because we have to do point
 // to DOM target conversions for these, we cache responses for a given
 // coordinate this many milliseconds:
 #define HITTEST_CACHE_LIFETIME_MS 50
 
-// Times attached to messages wrap when they overflow
-static const DWORD kEventTimeRange = std::numeric_limits<DWORD>::max();
-static const DWORD kEventTimeHalfRange = kEventTimeRange / 2;
-
 
 /**************************************************************
  **************************************************************
  **
  ** BLOCK: nsIWidget impl.
  **
  ** nsIWidget interface implementation, broken down into
  ** sections.
@@ -5852,112 +5863,21 @@ nsWindow::ClientMarginHitTestPoint(int32
   }
 
   return testResult;
 }
 
 TimeStamp
 nsWindow::GetMessageTimeStamp(LONG aEventTime)
 {
-  // Conversion from native event time to timestamp is complicated by the fact
-  // that native event time wraps. Also, for consistency's sake we avoid calling
-  // ::GetTickCount() each time and for performance reasons we only use the
-  // TimeStamp::NowLoRes except when recording the first event's time.
-
-  // We currently require the event time to be passed in. This is an interim
-  // measure to avoid calling GetMessageTime twice for each event--once when
-  // storing the time directly and once when converting to a timestamp.
-  //
-  // Once we no longer store both a time and a timestamp on each event we can
-  // perform the call to GetMessageTime here instead.
   DWORD eventTime = static_cast<DWORD>(aEventTime);
 
-  // Record first event time
-  if (sFirstEventTimeStamp.IsNull()) {
-    nsWindow::UpdateFirstEventTime(eventTime);
-  }
-  TimeStamp roughlyNow = TimeStamp::NowLoRes();
-
-  // If the event time is before the first event time there are two
-  // possibilities:
-  //
-  //   a) Event time has wrapped
-  //   b) The initial events were not delivered strictly in time order
-  //
-  // I'm not sure if (b) ever happens but even if it doesn't currently occur,
-  // it could come about if we change the way we fetch events.
-  //
-  // We can tell we have (b) if the event time is a little bit before the first
-  // event (i.e. less than half the range) and the timestamp is only a little
-  // bit past the first event timestamp (again less than half the range).
-  // In that case we should adjust the first event time so it really is the
-  // first event time.
-  if (eventTime < sFirstEventTime &&
-      sFirstEventTime - eventTime < kEventTimeHalfRange &&
-      roughlyNow - sFirstEventTimeStamp <
-        TimeDuration::FromMilliseconds(kEventTimeHalfRange)) {
-    UpdateFirstEventTime(eventTime);
-  }
-
-  double timeSinceFirstEvent =
-    sFirstEventTime <= eventTime
-      ? eventTime - sFirstEventTime
-      : static_cast<double>(kEventTimeRange) + eventTime - sFirstEventTime;
-  TimeStamp eventTimeStamp =
-    sFirstEventTimeStamp + TimeDuration::FromMilliseconds(timeSinceFirstEvent);
-
-  // Event time may have wrapped several times since we recorded the first
-  // event time (it wraps every ~50 days) so we extend eventTimeStamp as
-  // needed.
-  double timesWrapped =
-    (roughlyNow - sFirstEventTimeStamp).ToMilliseconds() / kEventTimeRange;
-  int32_t cyclesToAdd = static_cast<int32_t>(timesWrapped); // floor
-
-  // There is some imprecision in the above calculation since we are using
-  // TimeStamp::NowLoRes and sFirstEventTime and sFirstEventTimeStamp may not
-  // be *exactly* the same moment. It is possible we think that time
-  // has *just* wrapped based on comparing timestamps, but actually the
-  // event time is *just about* to wrap; or vice versa.
-  // In the following, we detect this situation and adjust cyclesToAdd as
-  // necessary.
-  double intervalFraction = fmod(timesWrapped, 1.0);
-
-  // If our rough calculation of how many times we've wrapped based on comparing
-  // timestamps says we've just wrapped (specifically, less 10% past the wrap
-  // point), but the event time is just before the wrap point (again, within
-  // 10%), then we need to reduce the number of wraps by 1.
-  if (intervalFraction < 0.1 && timeSinceFirstEvent > kEventTimeRange * 0.9) {
-    cyclesToAdd--;
-  // Likewise, if our rough calculation says we've just wrapped but actually the
-  // event time is just after the wrap point, we need to add an extra wrap.
-  } else if (intervalFraction > 0.9 &&
-             timeSinceFirstEvent < kEventTimeRange * 0.1) {
-    cyclesToAdd++;
-  }
-
-  if (cyclesToAdd > 0) {
-    eventTimeStamp +=
-      TimeDuration::FromMilliseconds(kEventTimeRange * cyclesToAdd);
-  }
-
-  return eventTimeStamp;
-}
-
-void
-nsWindow::UpdateFirstEventTime(DWORD aEventTime)
-{
-  sFirstEventTime = aEventTime;
-  DWORD currentTime = ::GetTickCount();
-  TimeStamp currentTimeStamp = TimeStamp::Now();
-  double timeSinceFirstEvent =
-    aEventTime <= currentTime
-      ? currentTime - aEventTime
-      : static_cast<double>(kEventTimeRange) + currentTime - aEventTime;
-  sFirstEventTimeStamp =
-    currentTimeStamp - TimeDuration::FromMilliseconds(timeSinceFirstEvent);
+  CurrentWindowsTimeGetter getCurrentTime;
+  return TimeConverter().GetTimeStampFromSystemTime(aEventTime,
+                                                    getCurrentTime);
 }
 
 void nsWindow::PostSleepWakeNotification(const bool aIsSleepMode)
 {
   if (aIsSleepMode == gIsSleepMode)
     return;
 
   gIsSleepMode = aIsSleepMode;
--- a/widget/windows/nsWindow.h
+++ b/widget/windows/nsWindow.h
@@ -588,21 +588,16 @@ protected:
   //  painting too rapidly in response to frequent input events.
   TimeStamp mLastPaintEndTime;
 
   // Caching for hit test results
   POINT mCachedHitTestPoint;
   TimeStamp mCachedHitTestTime;
   int32_t mCachedHitTestResult;
 
-  // For converting native event times to timestamps we record the time of the
-  // first received event in each time scale.
-  static DWORD     sFirstEventTime;
-  static TimeStamp sFirstEventTimeStamp;
-
   static bool sNeedsToInitMouseWheelSettings;
   static void InitMouseWheelScrollData();
 
   CRITICAL_SECTION mPresentLock;
 };
 
 /**
  * A child window is a window with different style.