Bug 462809 - Interpretation of scroll events on Windows and OS X, r=vladimir, ui-r=beltzner
authorMargaret Leibovic <mleibovic@mozilla.com>
Tue, 04 Aug 2009 14:28:17 -0700
changeset 31122 d8143262d94df2eef902dcfe83e96dd8310859c9
parent 31121 4784832ab52b927bf3b757db41a10bc7885cad08
child 31123 fe96f2059e54d4277e5ea33f0135e51966da48cc
push id8382
push userposhannessy@mozilla.com
push dateTue, 04 Aug 2009 21:29:00 +0000
treeherdermozilla-central@d8143262d94d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvladimir, beltzner
bugs462809
milestone1.9.2a1pre
Bug 462809 - Interpretation of scroll events on Windows and OS X, r=vladimir, ui-r=beltzner
modules/libpref/src/init/all.js
widget/src/windows/nsWindow.cpp
widget/src/windows/nsWindow.h
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -958,19 +958,26 @@ pref("clipboard.autocopy", false);
 // mouse wheel scroll transaction period of time (in milliseconds)
 pref("mousewheel.transaction.timeout", 1500);
 // mouse wheel scroll transaction is held even if the mouse cursor is moved.
 pref("mousewheel.transaction.ignoremovedelay", 100);
 
 // Macbook touchpad two finger pixel scrolling
 pref("mousewheel.enable_pixel_scrolling", true);
 
+// prefs for improved windows scrolling model
+// number of mousewheel clicks when acceleration starts
+// acceleration can be turned off if pref is set to -1
+pref("mousewheel.acceleration.start", 3);
+// factor to be muliplied for constant acceleration
+pref("mousewheel.acceleration.factor", 10);
+
 // 0=lines, 1=pages, 2=history , 3=text size
 pref("mousewheel.withnokey.action",0);
-pref("mousewheel.withnokey.numlines",1);	
+pref("mousewheel.withnokey.numlines",6);
 pref("mousewheel.withnokey.sysnumlines",true);
 pref("mousewheel.withcontrolkey.action",0);
 pref("mousewheel.withcontrolkey.numlines",1);
 pref("mousewheel.withcontrolkey.sysnumlines",true);
 // mousewheel.withshiftkey, see the Mac note below.
 pref("mousewheel.withshiftkey.action",0);
 pref("mousewheel.withshiftkey.numlines",1);
 pref("mousewheel.withshiftkey.sysnumlines",true);
--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -115,17 +115,19 @@
 #include "prprf.h"
 #include "prmem.h"
 
 #include "nsIAppShell.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIDOMNSUIEvent.h"
 #include "nsITheme.h"
 #include "nsIPrefBranch.h"
+#include "nsIPrefBranch2.h"
 #include "nsIPrefService.h"
+#include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsIScreenManager.h"
 #include "imgIContainer.h"
 #include "nsIFile.h"
 #include "nsIRollupListener.h"
 #include "nsIMenuRollup.h"
 #include "nsIRegion.h"
 #include "nsIServiceManager.h"
@@ -185,16 +187,115 @@
 #include "nsSplashScreen.h"
 #endif // defined(MOZ_SPLASHSCREEN)
 
 // Windowless plugin support
 #include "nsplugindefs.h"
 
 #include "nsWindowDefs.h"
 
+// For scroll wheel calculations
+#include "nsITimer.h"
+
+/**************************************************************
+ *
+ * nsScrollPrefObserver Class for scroll acceleration prefs
+ *
+ **************************************************************/
+
+class nsScrollPrefObserver : public nsIObserver
+{
+public:
+  nsScrollPrefObserver();
+  int GetScrollAccelerationStart();
+  int GetScrollAccelerationFactor();
+  int GetScrollNumLines();
+  void RemoveObservers();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+private:
+  nsCOMPtr<nsIPrefBranch2> mPrefBranch;
+  int mScrollAccelerationStart;
+  int mScrollAccelerationFactor;
+  int mScrollNumLines;
+};
+
+NS_IMPL_ISUPPORTS1(nsScrollPrefObserver, nsScrollPrefObserver)
+
+nsScrollPrefObserver::nsScrollPrefObserver()
+{
+  nsresult rv;
+  mPrefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+
+  rv = mPrefBranch->GetIntPref("mousewheel.acceleration.start",
+                               &mScrollAccelerationStart);
+  NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), 
+                    "Failed to get pref: mousewheel.acceleration.start");
+  rv = mPrefBranch->AddObserver("mousewheel.acceleration.start", 
+                                this, PR_FALSE);
+  NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), 
+                    "Failed to add pref observer: mousewheel.acceleration.start");
+                    
+  rv = mPrefBranch->GetIntPref("mousewheel.acceleration.factor",
+                               &mScrollAccelerationFactor);
+  NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), 
+                    "Failed to get pref: mousewheel.acceleration.factor");
+  rv = mPrefBranch->AddObserver("mousewheel.acceleration.factor", 
+                                this, PR_FALSE);
+  NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), 
+                    "Failed to add pref observer: mousewheel.acceleration.factor");
+                    
+  rv = mPrefBranch->GetIntPref("mousewheel.withnokey.numlines",
+                               &mScrollNumLines);
+  NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), 
+                    "Failed to get pref: mousewheel.withnokey.numlines");
+  rv = mPrefBranch->AddObserver("mousewheel.withnokey.numlines", 
+                                this, PR_FALSE);
+  NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), 
+                    "Failed to add pref observer: mousewheel.withnokey.numlines");
+}
+
+int nsScrollPrefObserver::GetScrollAccelerationStart()
+{
+  return mScrollAccelerationStart;
+}
+
+int nsScrollPrefObserver::GetScrollAccelerationFactor()
+{
+  return mScrollAccelerationFactor;
+}
+
+int nsScrollPrefObserver::GetScrollNumLines()
+{
+  return mScrollNumLines;
+}
+
+void nsScrollPrefObserver::RemoveObservers()
+{
+  mPrefBranch->RemoveObserver("mousewheel.acceleration.start", this);
+  mPrefBranch->RemoveObserver("mousewheel.acceleration.factor", this);
+  mPrefBranch->RemoveObserver("mousewheel.withnokey.numlines", this);
+}
+
+NS_IMETHODIMP nsScrollPrefObserver::Observe(nsISupports *aSubject,
+                                            const char *aTopic,
+                                            const PRUnichar *aData)
+{
+  mPrefBranch->GetIntPref("mousewheel.acceleration.start",
+                          &mScrollAccelerationStart);
+  mPrefBranch->GetIntPref("mousewheel.acceleration.factor",
+                          &mScrollAccelerationFactor);
+  mPrefBranch->GetIntPref("mousewheel.withnokey.numlines",
+                          &mScrollNumLines);
+
+  return NS_OK;
+}
+
 /**************************************************************
  **************************************************************
  **
  ** BLOCK: Variables
  **
  ** nsWindow Class static initializations and global variables. 
  **
  **************************************************************
@@ -286,16 +387,19 @@ PRUint32        gLastInputEventTime     
 // in NativeWindowTheme.
 PRBool          gDisableNativeTheme               = PR_FALSE;
 
 // Global used in Show window enumerations.
 static PRBool   gWindowsVisible                   = PR_FALSE;
 
 static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
 
+// Global scroll pref observer for scroll acceleration prefs
+static nsScrollPrefObserver* gScrollPrefObserver  = nsnull;
+
 /**************************************************************
  **************************************************************
  **
  ** BLOCK: nsIWidget impl.
  **
  ** nsIWidget interface implementation, broken down into
  ** sections.
  **
@@ -346,25 +450,31 @@ nsWindow::nsWindow() : nsBaseWidget()
   mTransparentSurface   = nsnull;
   mMemoryDC             = nsnull;
   mTransparencyMode     = eTransparencyOpaque;
 #endif
   mBackground           = ::GetSysColor(COLOR_BTNFACE);
   mBrush                = ::CreateSolidBrush(NSRGB_2_COLOREF(mBackground));
   mForeground           = ::GetSysColor(COLOR_WINDOWTEXT);
 
+  // To be used for scroll acceleration
+  mScrollSeriesCounter = 0;
+
   // Global initialization
   if (!sInstanceCount) {
 #if !defined(WINCE)
   gKbdLayout.LoadLayout(::GetKeyboardLayout(0));
 #endif
 
   // Init IME handler
   nsIMM32Handler::Initialize();
 
+  // Init scroll pref observer for scroll acceleration
+  NS_IF_ADDREF(gScrollPrefObserver = new nsScrollPrefObserver());
+
 #ifdef NS_ENABLE_TSF
   nsTextStore::Initialize();
 #endif
 
 #if !defined(WINCE)
   if (SUCCEEDED(::OleInitialize(NULL)))
     sIsOleInitialized = TRUE;
   NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
@@ -405,16 +515,19 @@ nsWindow::~nsWindow()
     if (sIsOleInitialized) {
       ::OleFlushClipboard();
       ::OleUninitialize();
       sIsOleInitialized = FALSE;
     }
     // delete any of the IME structures that we allocated
     nsIMM32Handler::Terminate();
 #endif // !defined(WINCE)
+
+    gScrollPrefObserver->RemoveObservers();
+    NS_RELEASE(gScrollPrefObserver);
   }
 
 #if !defined(WINCE)
   NS_IF_RELEASE(mNativeDragTarget);
 #endif // !defined(WINCE)
 }
 
 NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget)
@@ -4818,27 +4931,34 @@ PRBool nsWindow::OnMouseWheel(UINT msg, 
   // We should cancel the surplus delta if the current window is not
   // same as previous.
   if (currentWindow != mWnd) {
     currentVDelta = 0;
     currentHDelta = 0;
     currentWindow = mWnd;
   }
 
+  // Keep track of whether or not the scroll notification is part of a series
+  // in order to calculate appropriate acceleration effect
+  UpdateMouseWheelSeriesCounter();
+
   nsMouseScrollEvent scrollEvent(PR_TRUE, NS_MOUSE_SCROLL, this);
   scrollEvent.delta = 0;
   if (isVertical) {
     scrollEvent.scrollFlags = nsMouseScrollEvent::kIsVertical;
     if (ulScrollLines == WHEEL_PAGESCROLL) {
       scrollEvent.scrollFlags |= nsMouseScrollEvent::kIsFullPage;
       scrollEvent.delta = (((short) HIWORD (wParam)) > 0) ? -1 : 1;
     } else {
       currentVDelta -= (short) HIWORD (wParam);
       if (PR_ABS(currentVDelta) >= iDeltaPerLine) {
-        scrollEvent.delta = currentVDelta / iDeltaPerLine;
+        // Compute delta to create acceleration effect
+        scrollEvent.delta = ComputeMouseWheelDelta(currentVDelta, 
+                                                   iDeltaPerLine, 
+                                                   ulScrollLines);
         currentVDelta %= iDeltaPerLine;
       }
     }
   } else {
     scrollEvent.scrollFlags = nsMouseScrollEvent::kIsHorizontal;
     if (ulScrollChars == WHEEL_PAGESCROLL) {
       scrollEvent.scrollFlags |= nsMouseScrollEvent::kIsFullPage;
       scrollEvent.delta = (((short) HIWORD (wParam)) > 0) ? 1 : -1;
@@ -4865,16 +4985,74 @@ PRBool nsWindow::OnMouseWheel(UINT msg, 
   // Note that we should return zero if we process WM_MOUSEWHEEL.
   // But if we process WM_MOUSEHWHEEL, we should return non-zero.
 
   if (result)
     *aRetValue = isVertical ? 0 : TRUE;
   
   return PR_FALSE; // break;
 } 
+
+// Reset scrollSeriesCounter when timer finishes (scroll series has ended)
+void nsWindow::OnMouseWheelTimeout(nsITimer* aTimer, void* aClosure) 
+{
+  nsWindow* window = (nsWindow*) aClosure;
+  window->mScrollSeriesCounter = 0;
+}
+
+// Increment scrollSeriesCount and reset timer to keep count going
+void nsWindow::UpdateMouseWheelSeriesCounter() 
+{
+  mScrollSeriesCounter++;
+
+  int scrollSeriesTimeout = 80;
+  static nsITimer* scrollTimer;
+  if (!scrollTimer) {
+    nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    if (!timer)
+      return;
+    timer.swap(scrollTimer);
+  }
+
+  scrollTimer->Cancel();
+  nsresult rv = 
+    scrollTimer->InitWithFuncCallback(OnMouseWheelTimeout, this,
+                                      scrollSeriesTimeout,
+                                      nsITimer::TYPE_ONE_SHOT);
+  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "nsITimer::InitWithFuncCallback failed");
+}
+
+// If the scroll notification is part of a series of notifications we should
+// increase scollEvent.delta to create an acceleration effect
+int nsWindow::ComputeMouseWheelDelta(int currentVDelta,
+                                     int iDeltaPerLine,
+                                     ULONG ulScrollLines)
+{
+  // scrollAccelerationStart: click number at which acceleration starts
+  int scrollAccelerationStart = gScrollPrefObserver->GetScrollAccelerationStart();
+  // scrollNumlines: number of lines per scroll before acceleration
+  int scrollNumLines = gScrollPrefObserver->GetScrollNumLines();
+  // scrollAccelerationFactor: factor muliplied for constant acceleration
+  int scrollAccelerationFactor = gScrollPrefObserver->GetScrollAccelerationFactor();
+
+  // compute delta that obeys numlines pref
+  int ulScrollLinesInt = static_cast<int>(ulScrollLines);
+  // currentVDelta is a multiple of (iDeltaPerLine * ulScrollLinesInt)
+  int delta = scrollNumLines * currentVDelta / (iDeltaPerLine * ulScrollLinesInt);
+
+  // mScrollSeriesCounter: the index of the scroll notification in a series
+  if (mScrollSeriesCounter < scrollAccelerationStart ||
+      scrollAccelerationStart < 0 ||
+      scrollAccelerationFactor < 0)
+    return delta;
+  else
+    return int(0.5 + delta * mScrollSeriesCounter *
+           (double) scrollAccelerationFactor / 10);
+}
+
 #endif // !defined(WINCE_WINDOWS_MOBILE)
 
 static PRBool
 StringCaseInsensitiveEquals(const PRUnichar* aChars1, const PRUint32 aNumChars1,
                             const PRUnichar* aChars2, const PRUint32 aNumChars2)
 {
   if (aNumChars1 != aNumChars2)
     return PR_FALSE;
--- a/widget/src/windows/nsWindow.h
+++ b/widget/src/windows/nsWindow.h
@@ -320,16 +320,21 @@ protected:
   virtual PRBool          OnPaint(HDC aDC = nsnull);
 #if defined(CAIRO_HAS_DDRAW_SURFACE)
   PRBool                  OnPaintImageDDraw16();
 #endif // defined(CAIRO_HAS_DDRAW_SURFACE)
 #if !defined(WINCE_WINDOWS_MOBILE)
   PRBool                  OnMouseWheel(UINT msg, WPARAM wParam, LPARAM lParam, 
                                        PRBool& result, PRBool& getWheelInfo,
                                        LRESULT *aRetValue);
+  static void             OnMouseWheelTimeout(nsITimer* aTimer, void* aClosure);
+  void                    UpdateMouseWheelSeriesCounter();
+  int                     ComputeMouseWheelDelta(int currentVDelta,
+                                                 int iDeltaPerLine,
+                                                 ULONG ulScrollLines);
 #endif // !defined(WINCE_WINDOWS_MOBILE)
 #if !defined(WINCE)
   void                    OnWindowPosChanging(LPWINDOWPOS& info);
 #endif // !defined(WINCE)
 
   /**
    * Methods for derived classes 
    */
@@ -425,16 +430,17 @@ protected:
   PRInt32               mMenuCmdId;
   DWORD_PTR             mOldStyle;
   DWORD_PTR             mOldExStyle;
   HIMC                  mOldIMC;
   PRUint32              mIMEEnabled;
   nsNativeDragTarget*   mNativeDragTarget;
   HKL                   mLastKeyboardLayout;
   nsPopupType           mPopupType;
+  int                   mScrollSeriesCounter;
 
   static PRUint32       sInstanceCount;
   static TriStateBool   sCanQuit;
   static nsWindow*      sCurrentWindow;
   static BOOL           sIsRegistered;
   static BOOL           sIsPopupClassRegistered;
   static BOOL           sIsOleInitialized;
   static HCURSOR        sHCursor;