widget/windows/nsWinGesture.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 /*
     7  * nsWinGesture - Touch input handling for tablet displays.
     8  */
    10 #include "nscore.h"
    11 #include "nsWinGesture.h"
    12 #include "nsUXThemeData.h"
    13 #include "nsIDOMSimpleGestureEvent.h"
    14 #include "nsIDOMWheelEvent.h"
    15 #include "mozilla/Constants.h"
    16 #include "mozilla/MouseEvents.h"
    17 #include "mozilla/Preferences.h"
    18 #include "mozilla/TouchEvents.h"
    20 using namespace mozilla;
    21 using namespace mozilla::widget;
    23 #ifdef PR_LOGGING
    24 extern PRLogModuleInfo* gWindowsLog;
    25 #endif
    27 const wchar_t nsWinGesture::kGestureLibraryName[] =  L"user32.dll";
    28 HMODULE nsWinGesture::sLibraryHandle = nullptr;
    29 nsWinGesture::GetGestureInfoPtr nsWinGesture::getGestureInfo = nullptr;
    30 nsWinGesture::CloseGestureInfoHandlePtr nsWinGesture::closeGestureInfoHandle = nullptr;
    31 nsWinGesture::GetGestureExtraArgsPtr nsWinGesture::getGestureExtraArgs = nullptr;
    32 nsWinGesture::SetGestureConfigPtr nsWinGesture::setGestureConfig = nullptr;
    33 nsWinGesture::GetGestureConfigPtr nsWinGesture::getGestureConfig = nullptr;
    34 nsWinGesture::BeginPanningFeedbackPtr nsWinGesture::beginPanningFeedback = nullptr;
    35 nsWinGesture::EndPanningFeedbackPtr nsWinGesture::endPanningFeedback = nullptr;
    36 nsWinGesture::UpdatePanningFeedbackPtr nsWinGesture::updatePanningFeedback = nullptr;
    38 nsWinGesture::RegisterTouchWindowPtr nsWinGesture::registerTouchWindow = nullptr;
    39 nsWinGesture::UnregisterTouchWindowPtr nsWinGesture::unregisterTouchWindow = nullptr;
    40 nsWinGesture::GetTouchInputInfoPtr nsWinGesture::getTouchInputInfo = nullptr;
    41 nsWinGesture::CloseTouchInputHandlePtr nsWinGesture::closeTouchInputHandle = nullptr;
    43 static bool gEnableSingleFingerPanEvents = false;
    45 nsWinGesture::nsWinGesture() :
    46   mPanActive(false),
    47   mFeedbackActive(false),
    48   mXAxisFeedback(false),
    49   mYAxisFeedback(false),
    50   mPanInertiaActive(false)
    51 {
    52   (void)InitLibrary();
    53   mPixelScrollOverflow = 0;
    54 }
    56 /* Load and shutdown */
    58 bool nsWinGesture::InitLibrary()
    59 {
    60   if (getGestureInfo) {
    61     return true;
    62   } else if (sLibraryHandle) {
    63     return false;
    64   }
    66   sLibraryHandle = ::LoadLibraryW(kGestureLibraryName);
    67   HMODULE hTheme = nsUXThemeData::GetThemeDLL();
    69   // gesture interfaces
    70   if (sLibraryHandle) {
    71     getGestureInfo = (GetGestureInfoPtr)GetProcAddress(sLibraryHandle, "GetGestureInfo");
    72     closeGestureInfoHandle = (CloseGestureInfoHandlePtr)GetProcAddress(sLibraryHandle, "CloseGestureInfoHandle");
    73     getGestureExtraArgs = (GetGestureExtraArgsPtr)GetProcAddress(sLibraryHandle, "GetGestureExtraArgs");
    74     setGestureConfig = (SetGestureConfigPtr)GetProcAddress(sLibraryHandle, "SetGestureConfig");
    75     getGestureConfig = (GetGestureConfigPtr)GetProcAddress(sLibraryHandle, "GetGestureConfig");
    76     registerTouchWindow = (RegisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "RegisterTouchWindow");
    77     unregisterTouchWindow = (UnregisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "UnregisterTouchWindow");
    78     getTouchInputInfo = (GetTouchInputInfoPtr)GetProcAddress(sLibraryHandle, "GetTouchInputInfo");
    79     closeTouchInputHandle = (CloseTouchInputHandlePtr)GetProcAddress(sLibraryHandle, "CloseTouchInputHandle");
    80   }
    82   if (!getGestureInfo || !closeGestureInfoHandle || !getGestureExtraArgs ||
    83     !setGestureConfig || !getGestureConfig) {
    84     getGestureInfo         = nullptr;
    85     closeGestureInfoHandle = nullptr;
    86     getGestureExtraArgs    = nullptr;
    87     setGestureConfig       = nullptr;
    88     getGestureConfig       = nullptr;
    89     return false;
    90   }
    92   if (!registerTouchWindow || !unregisterTouchWindow || !getTouchInputInfo || !closeTouchInputHandle) {
    93     registerTouchWindow   = nullptr;
    94     unregisterTouchWindow = nullptr;
    95     getTouchInputInfo     = nullptr;
    96     closeTouchInputHandle = nullptr;
    97   }
    99   // panning feedback interfaces
   100   if (hTheme) {
   101     beginPanningFeedback = (BeginPanningFeedbackPtr)GetProcAddress(hTheme, "BeginPanningFeedback");
   102     endPanningFeedback = (EndPanningFeedbackPtr)GetProcAddress(hTheme, "EndPanningFeedback");
   103     updatePanningFeedback = (UpdatePanningFeedbackPtr)GetProcAddress(hTheme, "UpdatePanningFeedback");
   104   }
   106   if (!beginPanningFeedback || !endPanningFeedback || !updatePanningFeedback) {
   107     beginPanningFeedback   = nullptr;
   108     endPanningFeedback     = nullptr;
   109     updatePanningFeedback  = nullptr;
   110   }
   112   // Check to see if we want single finger gesture input. Only do this once
   113   // for the app so we don't have to look it up on every window create.
   114   gEnableSingleFingerPanEvents =
   115     Preferences::GetBool("gestures.enable_single_finger_input", false);
   117   return true;
   118 }
   120 #define GCOUNT 5
   122 bool nsWinGesture::SetWinGestureSupport(HWND hWnd,
   123                      WidgetGestureNotifyEvent::ePanDirection aDirection)
   124 {
   125   if (!getGestureInfo)
   126     return false;
   128   GESTURECONFIG config[GCOUNT];
   130   memset(&config, 0, sizeof(config));
   132   config[0].dwID = GID_ZOOM;
   133   config[0].dwWant = GC_ZOOM;
   134   config[0].dwBlock = 0;
   136   config[1].dwID = GID_ROTATE;
   137   config[1].dwWant = GC_ROTATE;
   138   config[1].dwBlock = 0;
   140   config[2].dwID = GID_PAN;
   141   config[2].dwWant  = GC_PAN|GC_PAN_WITH_INERTIA|
   142                       GC_PAN_WITH_GUTTER;
   143   config[2].dwBlock = GC_PAN_WITH_SINGLE_FINGER_VERTICALLY|
   144                       GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
   146   if (gEnableSingleFingerPanEvents) {
   148     if (aDirection == WidgetGestureNotifyEvent::ePanVertical ||
   149         aDirection == WidgetGestureNotifyEvent::ePanBoth)
   150     {
   151       config[2].dwWant  |= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
   152       config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
   153     }
   155     if (aDirection == WidgetGestureNotifyEvent::ePanHorizontal ||
   156         aDirection == WidgetGestureNotifyEvent::ePanBoth)
   157     {
   158       config[2].dwWant  |= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
   159       config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
   160     }
   162   }
   164   config[3].dwWant = GC_TWOFINGERTAP;
   165   config[3].dwID = GID_TWOFINGERTAP;
   166   config[3].dwBlock = 0;
   168   config[4].dwWant = GC_PRESSANDTAP;
   169   config[4].dwID = GID_PRESSANDTAP;
   170   config[4].dwBlock = 0;
   172   return SetGestureConfig(hWnd, GCOUNT, (PGESTURECONFIG)&config);
   173 }
   175 /* Helpers */
   177 bool nsWinGesture::IsAvailable()
   178 {
   179   return getGestureInfo != nullptr;
   180 }
   182 bool nsWinGesture::RegisterTouchWindow(HWND hWnd)
   183 {
   184   if (!registerTouchWindow)
   185     return false;
   187   return registerTouchWindow(hWnd, TWF_WANTPALM);
   188 }
   190 bool nsWinGesture::UnregisterTouchWindow(HWND hWnd)
   191 {
   192   if (!unregisterTouchWindow)
   193     return false;
   195   return unregisterTouchWindow(hWnd);
   196 }
   198 bool nsWinGesture::GetTouchInputInfo(HTOUCHINPUT hTouchInput, uint32_t cInputs, PTOUCHINPUT pInputs)
   199 {
   200   if (!getTouchInputInfo)
   201     return false;
   203   return getTouchInputInfo(hTouchInput, cInputs, pInputs, sizeof(TOUCHINPUT));
   204 }
   206 bool nsWinGesture::CloseTouchInputHandle(HTOUCHINPUT hTouchInput)
   207 {
   208   if (!closeTouchInputHandle)
   209     return false;
   211   return closeTouchInputHandle(hTouchInput);
   212 }
   214 bool nsWinGesture::GetGestureInfo(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo)
   215 {
   216   if (!getGestureInfo || !hGestureInfo || !pGestureInfo)
   217     return false;
   219   ZeroMemory(pGestureInfo, sizeof(GESTUREINFO));
   220   pGestureInfo->cbSize = sizeof(GESTUREINFO);
   222   return getGestureInfo(hGestureInfo, pGestureInfo);
   223 }
   225 bool nsWinGesture::CloseGestureInfoHandle(HGESTUREINFO hGestureInfo)
   226 {
   227   if (!getGestureInfo || !hGestureInfo)
   228     return false;
   230   return closeGestureInfoHandle(hGestureInfo);
   231 }
   233 bool nsWinGesture::GetGestureExtraArgs(HGESTUREINFO hGestureInfo, UINT cbExtraArgs, PBYTE pExtraArgs)
   234 {
   235   if (!getGestureInfo || !hGestureInfo || !pExtraArgs)
   236     return false;
   238   return getGestureExtraArgs(hGestureInfo, cbExtraArgs, pExtraArgs);
   239 }
   241 bool nsWinGesture::SetGestureConfig(HWND hWnd, UINT cIDs, PGESTURECONFIG pGestureConfig)
   242 {
   243   if (!getGestureInfo || !pGestureConfig)
   244     return false;
   246   return setGestureConfig(hWnd, 0, cIDs, pGestureConfig, sizeof(GESTURECONFIG));
   247 }
   249 bool nsWinGesture::GetGestureConfig(HWND hWnd, DWORD dwFlags, PUINT pcIDs, PGESTURECONFIG pGestureConfig)
   250 {
   251   if (!getGestureInfo || !pGestureConfig)
   252     return false;
   254   return getGestureConfig(hWnd, 0, dwFlags, pcIDs, pGestureConfig, sizeof(GESTURECONFIG));
   255 }
   257 bool nsWinGesture::BeginPanningFeedback(HWND hWnd)
   258 {
   259   if (!beginPanningFeedback)
   260     return false;
   262   return beginPanningFeedback(hWnd);
   263 }
   265 bool nsWinGesture::EndPanningFeedback(HWND hWnd)
   266 {
   267   if (!beginPanningFeedback)
   268     return false;
   270   return endPanningFeedback(hWnd, TRUE);
   271 }
   273 bool nsWinGesture::UpdatePanningFeedback(HWND hWnd, LONG offsetX, LONG offsetY, BOOL fInInertia)
   274 {
   275   if (!beginPanningFeedback)
   276     return false;
   278   return updatePanningFeedback(hWnd, offsetX, offsetY, fInInertia);
   279 }
   281 bool nsWinGesture::IsPanEvent(LPARAM lParam)
   282 {
   283   GESTUREINFO gi;
   285   ZeroMemory(&gi,sizeof(GESTUREINFO));
   286   gi.cbSize = sizeof(GESTUREINFO);
   288   BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
   289   if (!result)
   290     return false;
   292   if (gi.dwID == GID_PAN)
   293     return true;
   295   return false;
   296 }
   298 /* Gesture event processing */
   300 bool
   301 nsWinGesture::ProcessGestureMessage(HWND hWnd, WPARAM wParam, LPARAM lParam,
   302                                     WidgetSimpleGestureEvent& evt)
   303 {
   304   GESTUREINFO gi;
   306   ZeroMemory(&gi,sizeof(GESTUREINFO));
   307   gi.cbSize = sizeof(GESTUREINFO);
   309   BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
   310   if (!result)
   311     return false;
   313   // The coordinates of this event
   314   nsPointWin coord;
   315   coord = gi.ptsLocation;
   316   coord.ScreenToClient(hWnd);
   318   evt.refPoint.x = coord.x;
   319   evt.refPoint.y = coord.y;
   321   // Multiple gesture can occur at the same time so gesture state
   322   // info can't be shared.
   323   switch(gi.dwID)
   324   {
   325     case GID_BEGIN:
   326     case GID_END:
   327       // These should always fall through to DefWndProc
   328       return false;
   329       break;
   331     case GID_ZOOM:
   332     {
   333       if (gi.dwFlags & GF_BEGIN) {
   334         // Send a zoom start event
   336         // The low 32 bits are the distance in pixels.
   337         mZoomIntermediate = (float)gi.ullArguments;
   339         evt.message = NS_SIMPLE_GESTURE_MAGNIFY_START;
   340         evt.delta = 0.0;
   341       }
   342       else if (gi.dwFlags & GF_END) {
   343         // Send a zoom end event, the delta is the change
   344         // in touch points.
   345         evt.message = NS_SIMPLE_GESTURE_MAGNIFY;
   346         // (positive for a "zoom in")
   347         evt.delta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments);
   348         mZoomIntermediate = (float)gi.ullArguments;
   349       }
   350       else {
   351         // Send a zoom intermediate event, the delta is the change
   352         // in touch points.
   353         evt.message = NS_SIMPLE_GESTURE_MAGNIFY_UPDATE;
   354         // (positive for a "zoom in")
   355         evt.delta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments);
   356         mZoomIntermediate = (float)gi.ullArguments;
   357       }
   358     }
   359     break;
   361     case GID_ROTATE:
   362     {
   363       // Send a rotate start event
   364       double radians = 0.0;
   366       // On GF_BEGIN, ullArguments contains the absolute rotation at the
   367       // start of the gesture. In later events it contains the offset from
   368       // the start angle.
   369       if (gi.ullArguments != 0)
   370         radians = GID_ROTATE_ANGLE_FROM_ARGUMENT(gi.ullArguments);
   372       double degrees = -1 * radians * (180/M_PI);
   374       if (gi.dwFlags & GF_BEGIN) {
   375           // At some point we should pass the initial angle in
   376           // along with delta. It's useful.
   377           degrees = mRotateIntermediate = 0.0;
   378       }
   380       evt.direction = 0;
   381       evt.delta = degrees - mRotateIntermediate;
   382       mRotateIntermediate = degrees;
   384       if (evt.delta > 0)
   385         evt.direction = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE;
   386       else if (evt.delta < 0)
   387         evt.direction = nsIDOMSimpleGestureEvent::ROTATION_CLOCKWISE;
   389       if (gi.dwFlags & GF_BEGIN)
   390         evt.message = NS_SIMPLE_GESTURE_ROTATE_START;
   391       else if (gi.dwFlags & GF_END)
   392         evt.message = NS_SIMPLE_GESTURE_ROTATE;
   393       else
   394         evt.message = NS_SIMPLE_GESTURE_ROTATE_UPDATE;
   395     }
   396     break;
   398     case GID_TWOFINGERTAP:
   399     {
   400       // Normally maps to "restore" from whatever you may have recently changed. A simple
   401       // double click.
   402       evt.message = NS_SIMPLE_GESTURE_TAP;
   403       evt.clickCount = 1;
   404     }
   405     break;
   407     case GID_PRESSANDTAP:
   408     {
   409       // Two finger right click. Defaults to right click if it falls through.
   410       evt.message = NS_SIMPLE_GESTURE_PRESSTAP;
   411       evt.clickCount = 1;
   412     }
   413     break;
   414   }
   416   return true;
   417 }
   419 bool
   420 nsWinGesture::ProcessPanMessage(HWND hWnd, WPARAM wParam, LPARAM lParam)
   421 {
   422   GESTUREINFO gi;
   424   ZeroMemory(&gi,sizeof(GESTUREINFO));
   425   gi.cbSize = sizeof(GESTUREINFO);
   427   BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
   428   if (!result)
   429     return false;
   431   // The coordinates of this event
   432   nsPointWin coord;
   433   coord = mPanRefPoint = gi.ptsLocation;
   434   // We want screen coordinates in our local offsets as client coordinates will change
   435   // when feedback is taking place. Gui events though require client coordinates. 
   436   mPanRefPoint.ScreenToClient(hWnd);
   438   switch(gi.dwID)
   439   {
   440     case GID_BEGIN:
   441     case GID_END:
   442       // These should always fall through to DefWndProc
   443       return false;
   444       break;
   446     // Setup pixel scroll events for both axis
   447     case GID_PAN:
   448     {
   449       if (gi.dwFlags & GF_BEGIN) {
   450         mPanIntermediate = coord;
   451         mPixelScrollDelta = 0;
   452         mPanActive = true;
   453         mPanInertiaActive = false;
   454       }
   455       else {
   457 #ifdef DBG_jimm
   458         int32_t deltaX = mPanIntermediate.x - coord.x;
   459         int32_t deltaY = mPanIntermediate.y - coord.y;
   460         PR_LOG(gWindowsLog, PR_LOG_ALWAYS, 
   461                ("coordX=%d coordY=%d deltaX=%d deltaY=%d x:%d y:%d\n", coord.x,
   462                 coord.y, deltaX, deltaY, mXAxisFeedback, mYAxisFeedback));
   463 #endif
   465         mPixelScrollDelta.x = mPanIntermediate.x - coord.x;
   466         mPixelScrollDelta.y = mPanIntermediate.y - coord.y;
   467         mPanIntermediate = coord;
   469         if (gi.dwFlags & GF_INERTIA)
   470           mPanInertiaActive = true;
   472         if (gi.dwFlags & GF_END) {
   473           mPanActive = false;
   474           mPanInertiaActive = false;
   475           PanFeedbackFinalize(hWnd, true);
   476         }
   477       }
   478     }
   479     break;
   480   }
   481   return true;
   482 }
   484 inline bool TestTransition(int32_t a, int32_t b)
   485 {
   486   // If a is zero, overflow is zero, implying the cursor has moved back to the start position.
   487   // If b is zero, cached overscroll is zero, implying feedback just begun. 
   488   if (a == 0 || b == 0) return true;
   489   // Test for different signs.
   490   return (a < 0) == (b < 0);
   491 }
   493 void
   494 nsWinGesture::UpdatePanFeedbackX(HWND hWnd, int32_t scrollOverflow, bool& endFeedback)
   495 {
   496   // If scroll overflow was returned indicating we panned past the bounds of
   497   // the scrollable view port, start feeback.
   498   if (scrollOverflow != 0) {
   499     if (!mFeedbackActive) {
   500       BeginPanningFeedback(hWnd);
   501       mFeedbackActive = true;
   502     }      
   503     endFeedback = false;
   504     mXAxisFeedback = true;
   505     return;
   506   }
   508   if (mXAxisFeedback) {
   509     int32_t newOverflow = mPixelScrollOverflow.x - mPixelScrollDelta.x;
   511     // Detect a reverse transition past the starting drag point. This tells us the user
   512     // has panned all the way back so we can stop providing feedback for this axis.
   513     if (!TestTransition(newOverflow, mPixelScrollOverflow.x) || newOverflow == 0)
   514       return;
   516     // Cache the total over scroll in pixels.
   517     mPixelScrollOverflow.x = newOverflow;
   518     endFeedback = false;
   519   }
   520 }
   522 void
   523 nsWinGesture::UpdatePanFeedbackY(HWND hWnd, int32_t scrollOverflow, bool& endFeedback)
   524 {
   525   // If scroll overflow was returned indicating we panned past the bounds of
   526   // the scrollable view port, start feeback.
   527   if (scrollOverflow != 0) {
   528     if (!mFeedbackActive) {
   529       BeginPanningFeedback(hWnd);
   530       mFeedbackActive = true;
   531     }
   532     endFeedback = false;
   533     mYAxisFeedback = true;
   534     return;
   535   }
   537   if (mYAxisFeedback) {
   538     int32_t newOverflow = mPixelScrollOverflow.y - mPixelScrollDelta.y;
   540     // Detect a reverse transition past the starting drag point. This tells us the user
   541     // has panned all the way back so we can stop providing feedback for this axis.
   542     if (!TestTransition(newOverflow, mPixelScrollOverflow.y) || newOverflow == 0)
   543       return;
   545     // Cache the total over scroll in pixels.
   546     mPixelScrollOverflow.y = newOverflow;
   547     endFeedback = false;
   548   }
   549 }
   551 void
   552 nsWinGesture::PanFeedbackFinalize(HWND hWnd, bool endFeedback)
   553 {
   554   if (!mFeedbackActive)
   555     return;
   557   if (endFeedback) {
   558     mFeedbackActive = false;
   559     mXAxisFeedback = false;
   560     mYAxisFeedback = false;
   561     mPixelScrollOverflow = 0;
   562     EndPanningFeedback(hWnd);
   563     return;
   564   }
   566   UpdatePanningFeedback(hWnd, mPixelScrollOverflow.x, mPixelScrollOverflow.y, mPanInertiaActive);
   567 }
   569 bool
   570 nsWinGesture::PanDeltaToPixelScroll(WidgetWheelEvent& aWheelEvent)
   571 {
   572   aWheelEvent.deltaX = aWheelEvent.deltaY = aWheelEvent.deltaZ = 0.0;
   573   aWheelEvent.lineOrPageDeltaX = aWheelEvent.lineOrPageDeltaY = 0;
   575   aWheelEvent.refPoint.x = mPanRefPoint.x;
   576   aWheelEvent.refPoint.y = mPanRefPoint.y;
   577   aWheelEvent.deltaMode = nsIDOMWheelEvent::DOM_DELTA_PIXEL;
   578   aWheelEvent.scrollType = WidgetWheelEvent::SCROLL_SYNCHRONOUSLY;
   579   aWheelEvent.isPixelOnlyDevice = true;
   581   aWheelEvent.overflowDeltaX = 0.0;
   582   aWheelEvent.overflowDeltaY = 0.0;
   584   // Don't scroll the view if we are currently at a bounds, or, if we are
   585   // panning back from a max feedback position. This keeps the original drag point
   586   // constant.
   587   if (!mXAxisFeedback) {
   588     aWheelEvent.deltaX = mPixelScrollDelta.x;
   589   }
   590   if (!mYAxisFeedback) {
   591     aWheelEvent.deltaY = mPixelScrollDelta.y;
   592   }
   594   return (aWheelEvent.deltaX != 0 || aWheelEvent.deltaY != 0);
   595 }

mercurial