Bug 77992 part 1.1 - Convert Windows native event times to timestamps; r=masayuki
authorBrian Birtles <birtles@gmail.com>
Fri, 06 Jun 2014 14:29:49 +0900
changeset 207316 16f49acfc4e9cb839c5027ede91bfac9d93c795c
parent 207315 55d21a229ee27460df88741273e698022a5c724f
child 207317 2820efae73160c047ac2323fcb32db2e00af8b29
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki
bugs77992
milestone32.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 77992 part 1.1 - Convert Windows native event times to timestamps; r=masayuki
widget/windows/nsWindow.cpp
widget/windows/nsWindow.h
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -56,16 +56,17 @@
 
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TouchEvents.h"
 
 #include "mozilla/ipc/MessageChannel.h"
 #include <algorithm>
+#include <limits>
 
 #include "nsWindow.h"
 
 #include <shellapi.h>
 #include <windows.h>
 #include <wtsapi32.h>
 #include <process.h>
 #include <commctrl.h>
@@ -246,16 +247,19 @@ bool            nsWindow::sAllowD3D9    
 
 TriStateBool nsWindow::sHasBogusPopupsDropShadowOnMultiMonitor = TRI_UNKNOWN;
 
 // Used in OOPP plugin focus processing.
 const wchar_t* kOOPPPluginFocusEventId   = L"OOPP Plugin Focus Widget Event";
 uint32_t        nsWindow::sOOPPPluginFocusEvent   =
                   RegisterWindowMessageW(kOOPPPluginFocusEventId);
 
+DWORD           nsWindow::sFirstEventTime = 0;
+TimeStamp       nsWindow::sFirstEventTimeStamp = TimeStamp();
+
 /**************************************************************
  *
  * SECTION: globals variables
  *
  **************************************************************/
 
 static const char *sScreenManagerContractID       = "@mozilla.org/gfx/screenmanager;1";
 
@@ -296,16 +300,20 @@ 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.
@@ -3666,16 +3674,17 @@ void nsWindow::InitEvent(WidgetGUIEvent&
   }
   else {  
     // use the point override if provided
     event.refPoint.x = aPoint->x;
     event.refPoint.y = aPoint->y;
   }
 
   event.time = ::GetMessageTime();
+  event.timeStamp = GetMessageTimeStamp(event.time);
 }
 
 /**************************************************************
  *
  * SECTION: Moz event dispatch helpers
  *
  * Helpers for dispatching different types of moz events.
  *
@@ -5652,16 +5661,117 @@ nsWindow::ClientMarginHitTestPoint(int32
       mCachedHitTestTime = TimeStamp::Now();
       mCachedHitTestResult = testResult;
     }
   }
 
   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 (timesWrapped > 0) {
+    eventTimeStamp +=
+      TimeDuration::FromMilliseconds(kEventTimeRange * timesWrapped);
+  }
+
+  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);
+}
+
 void nsWindow::PostSleepWakeNotification(const bool aIsSleepMode)
 {
   if (aIsSleepMode == gIsSleepMode)
     return;
 
   gIsSleepMode = aIsSleepMode;
 
   nsCOMPtr<nsIObserverService> observerService =
@@ -6104,16 +6214,18 @@ bool nsWindow::OnTouch(WPARAM wParam, LP
     for (uint32_t i = 0; i < cInputs; i++) {
       uint32_t msg;
 
       if (pInputs[i].dwFlags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE)) {
         // Create a standard touch event to send
         if (!touchEventToSend) {
           touchEventToSend = new WidgetTouchEvent(true, NS_TOUCH_MOVE, this);
           touchEventToSend->time = ::GetMessageTime();
+          touchEventToSend->timeStamp =
+            GetMessageTimeStamp(touchEventToSend->time);
           ModifierKeyState modifierKeyState;
           modifierKeyState.InitInputEvent(*touchEventToSend);
         }
 
         // Pres shell expects this event to be a NS_TOUCH_START if new contact
         // points have been added since the last event sent.
         if (pInputs[i].dwFlags & TOUCHEVENTF_DOWN) {
           touchEventToSend->message = msg = NS_TOUCH_START;
@@ -6122,16 +6234,18 @@ bool nsWindow::OnTouch(WPARAM wParam, LP
         }
       } else if (pInputs[i].dwFlags & TOUCHEVENTF_UP) {
         // Pres shell expects removed contacts points to be delivered in a
         // separate NS_TOUCH_END event containing only the contact points
         // that were removed.
         if (!touchEndEventToSend) {
           touchEndEventToSend = new WidgetTouchEvent(true, NS_TOUCH_END, this);
           touchEndEventToSend->time = ::GetMessageTime();
+          touchEndEventToSend->timeStamp =
+            GetMessageTimeStamp(touchEndEventToSend->time);
           ModifierKeyState modifierKeyState;
           modifierKeyState.InitInputEvent(*touchEndEventToSend);
         }
         msg = NS_TOUCH_END;
       } else {
         // Filter out spurious Windows events we don't understand, like palm
         // contact.
         continue;
@@ -6202,16 +6316,17 @@ bool nsWindow::OnGesture(WPARAM wParam, 
 
     WidgetWheelEvent wheelEvent(true, NS_WHEEL_WHEEL, this);
 
     ModifierKeyState modifierKeyState;
     modifierKeyState.InitInputEvent(wheelEvent);
 
     wheelEvent.button      = 0;
     wheelEvent.time        = ::GetMessageTime();
+    wheelEvent.timeStamp   = GetMessageTimeStamp(wheelEvent.time);
     wheelEvent.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
 
     bool endFeedback = true;
 
     if (mGesture.PanDeltaToPixelScroll(wheelEvent)) {
       DispatchEvent(&wheelEvent, status);
     }
 
@@ -6236,16 +6351,17 @@ bool nsWindow::OnGesture(WPARAM wParam, 
     return false; // fall through to DefWndProc
   }
   
   // Polish up and send off the new event
   ModifierKeyState modifierKeyState;
   modifierKeyState.InitInputEvent(event);
   event.button    = 0;
   event.time      = ::GetMessageTime();
+  event.timeStamp = GetMessageTimeStamp(event.time);
   event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
 
   nsEventStatus status;
   DispatchEvent(&event, status);
   if (status == nsEventStatus_eIgnore) {
     return false; // Ignored, fall through
   }
 
--- a/widget/windows/nsWindow.h
+++ b/widget/windows/nsWindow.h
@@ -370,16 +370,18 @@ protected:
                                               bool *aEventDispatched);
   LRESULT                 ProcessKeyDownMessage(const MSG &aMsg,
                                                 bool *aEventDispatched);
   static bool             EventIsInsideWindow(nsWindow* aWindow);
   // Convert nsEventStatus value to a windows boolean
   static bool             ConvertStatus(nsEventStatus aStatus);
   static void             PostSleepWakeNotification(const bool aIsSleepMode);
   int32_t                 ClientMarginHitTestPoint(int32_t mx, int32_t my);
+  static TimeStamp        GetMessageTimeStamp(LONG aEventTime);
+  static void             UpdateFirstEventTime(DWORD aEventTime);
 
   /**
    * Event handlers
    */
   virtual void            OnDestroy();
   virtual bool            OnResize(nsIntRect &aWindowRect);
   bool                    OnGesture(WPARAM wParam, LPARAM lParam);
   bool                    OnTouch(WPARAM wParam, LPARAM lParam);
@@ -574,16 +576,21 @@ 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();
 };
 
 /**
  * A child window is a window with different style.
  */
 class ChildWindow : public nsWindow {