Bug 315727 Request Firefox respond to Windows scroll messages so pages can be scroll captured r=jmathies+smaug, sr=roc
authorMasayuki Nakano <masayuki@d-toybox.com>
Thu, 11 Mar 2010 14:25:29 +0900
changeset 39248 9da3f465942d2e21aa6060fe4152a46abc341798
parent 39247 ed9d9711eb8f6d2a8bd3e6d4298de799fd07b22c
child 39249 2f07047e029e770caf14002d3876249a7fde9ac1
push id12102
push usermasayuki@d-toybox.com
push dateThu, 11 Mar 2010 05:25:55 +0000
treeherdermozilla-central@9da3f465942d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjmathies, roc
bugs315727
milestone1.9.3a3pre
Bug 315727 Request Firefox respond to Windows scroll messages so pages can be scroll captured r=jmathies+smaug, sr=roc
content/events/src/nsEventStateManager.cpp
content/events/src/nsEventStateManager.h
layout/base/nsIPresShell.h
layout/base/nsPresShell.cpp
modules/libpref/src/init/all.js
widget/public/nsGUIEvent.h
widget/src/windows/nsWindow.cpp
widget/tests/TestWinTSF.cpp
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -1345,16 +1345,21 @@ nsEventStateManager::PreHandleEvent(nsPr
   case NS_CONTENT_COMMAND_DELETE:
   case NS_CONTENT_COMMAND_UNDO:
   case NS_CONTENT_COMMAND_REDO:
   case NS_CONTENT_COMMAND_PASTE_TRANSFERABLE:
     {
       DoContentCommandEvent(static_cast<nsContentCommandEvent*>(aEvent));
     }
     break;
+  case NS_CONTENT_COMMAND_SCROLL:
+    {
+      DoContentCommandScrollEvent(static_cast<nsContentCommandEvent*>(aEvent));
+    }
+    break;
   }
   return NS_OK;
 }
 
 static PRInt32
 GetAccessModifierMask(nsISupports* aDocShell)
 {
   nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell));
@@ -4424,8 +4429,55 @@ nsEventStateManager::DoContentCommandEve
           break;
       }
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
   aEvent->mSucceeded = PR_TRUE;
   return NS_OK;
 }
+
+nsresult
+nsEventStateManager::DoContentCommandScrollEvent(nsContentCommandEvent* aEvent)
+{
+  NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
+  nsIPresShell* ps = mPresContext->GetPresShell();
+  NS_ENSURE_TRUE(ps, NS_ERROR_NOT_AVAILABLE);
+  NS_ENSURE_TRUE(aEvent->mScroll.mAmount != 0, NS_ERROR_INVALID_ARG);
+
+  nsIScrollableFrame::ScrollUnit scrollUnit;
+  switch (aEvent->mScroll.mUnit) {
+    case nsContentCommandEvent::eCmdScrollUnit_Line:
+      scrollUnit = nsIScrollableFrame::LINES;
+      break;
+    case nsContentCommandEvent::eCmdScrollUnit_Page:
+      scrollUnit = nsIScrollableFrame::PAGES;
+      break;
+    case nsContentCommandEvent::eCmdScrollUnit_Whole:
+      scrollUnit = nsIScrollableFrame::WHOLE;
+      break;
+    default:
+      return NS_ERROR_INVALID_ARG;
+  }
+
+  aEvent->mSucceeded = PR_TRUE;
+
+  nsIScrollableFrame* sf =
+    ps->GetFrameToScrollAsScrollable(nsIPresShell::eEither);
+  aEvent->mIsEnabled = sf ? CanScrollOn(sf, aEvent->mScroll.mAmount,
+                                        aEvent->mScroll.mIsHorizontal) :
+                            PR_FALSE;
+
+  if (!aEvent->mIsEnabled || aEvent->mOnlyEnabledCheck) {
+    return NS_OK;
+  }
+
+  nsIntPoint pt(0, 0);
+  if (aEvent->mScroll.mIsHorizontal) {
+    pt.x = aEvent->mScroll.mAmount;
+  } else {
+    pt.y = aEvent->mScroll.mAmount;
+  }
+
+  // The caller may want synchronous scrolling.
+  sf->ScrollBy(pt, scrollUnit, nsIScrollableFrame::INSTANT);
+  return NS_OK;
+}
--- a/content/events/src/nsEventStateManager.h
+++ b/content/events/src/nsEventStateManager.h
@@ -331,16 +331,17 @@ protected:
    * Set the fields of aEvent to reflect the mouse position and modifier keys
    * that were set when the user first pressed the mouse button (stored by
    * BeginTrackingDragGesture). aEvent->widget must be
    * mCurrentTarget->GetWindow().
    */
   void FillInEventFromGestureDown(nsMouseEvent* aEvent);
 
   nsresult DoContentCommandEvent(nsContentCommandEvent* aEvent);
+  nsresult DoContentCommandScrollEvent(nsContentCommandEvent* aEvent);
 
   PRInt32     mLockCursor;
 
   nsWeakFrame mCurrentTarget;
   nsCOMPtr<nsIContent> mCurrentTargetContent;
   nsWeakFrame mLastMouseOverFrame;
   nsCOMPtr<nsIContent> mLastMouseOverElement;
   nsWeakFrame mLastDragOverFrame;
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -122,18 +122,18 @@ typedef struct CapturingContentInfo {
   nsIContent* mContent;
 
   CapturingContentInfo() :
     mAllowed(PR_FALSE), mRetargetToElement(PR_FALSE), mPreventDrag(PR_FALSE),
     mContent(nsnull) { }
 } CapturingContentInfo;
 
 #define NS_IPRESSHELL_IID     \
-  { 0x20b82adf, 0x1f5c, 0x44f7, \
-    { 0x9b, 0x74, 0xc0, 0xa3, 0x14, 0xd8, 0xcf, 0x91 } }
+  { 0xe5e070ce, 0xbc17, 0x4b5f, \
+    { 0xb2, 0x21, 0xbf, 0xc3, 0xe1, 0x68, 0xbe, 0x9b } }
 
 // Constants for ScrollContentIntoView() function
 #define NS_PRESSHELL_SCROLL_TOP      0
 #define NS_PRESSHELL_SCROLL_BOTTOM   100
 #define NS_PRESSHELL_SCROLL_LEFT     0
 #define NS_PRESSHELL_SCROLL_RIGHT    100
 #define NS_PRESSHELL_SCROLL_CENTER   50
 #define NS_PRESSHELL_SCROLL_ANYWHERE -1
@@ -357,16 +357,26 @@ public:
   nsIScrollableFrame* GetRootScrollFrameAsScrollable() const;
 
   /*
    * The same as GetRootScrollFrame, but returns an nsIScrollableFrame.
    * Can be called by code not linked into gklayout.
    */
   virtual nsIScrollableFrame* GetRootScrollFrameAsScrollableExternal() const;
 
+  /*
+   * Gets nearest scrollable frame from current focused content or DOM
+   * selection if there is no focused content. The frame is scrollable with
+   * overflow:scroll or overflow:auto in some direction when aDirection is
+   * eEither.  Otherwise, this returns a nearest frame that is scrollable in
+   * the specified direction.
+   */
+  enum ScrollDirection { eHorizontal, eVertical, eEither };
+  nsIScrollableFrame* GetFrameToScrollAsScrollable(ScrollDirection aDirection);
+
   /**
    * Returns the page sequence frame associated with the frame hierarchy.
    * Returns NULL if not a paginated view.
    */
   NS_IMETHOD GetPageSequenceFrame(nsIPageSequenceFrame** aResult) const = 0;
 
   /**
    * Gets the real primary frame associated with the content object.
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -959,18 +959,16 @@ protected:
 
   // Helper for ScrollContentIntoView
   void DoScrollContentIntoView(nsIContent* aContent,
                                PRIntn      aVPercent,
                                PRIntn      aHPercent);
 
   friend class nsPresShellEventCB;
 
-  nsIScrollableFrame* GetFrameToScroll(nsLayoutUtils::Direction aDirection);
-
   PRBool mCaretEnabled;
 #ifdef NS_DEBUG
   nsresult CloneStyleSet(nsStyleSet* aSet, nsStyleSet** aResult);
   PRBool VerifyIncrementalReflow();
   PRBool mInVerifyReflow;
   void ShowEventTargetDebug();
 #endif
 
@@ -2924,44 +2922,47 @@ PresShell::IntraLineMove(PRBool aForward
   return mSelection->IntraLineMove(aForward, aExtend);  
 }
 
 
 
 NS_IMETHODIMP 
 PresShell::PageMove(PRBool aForward, PRBool aExtend)
 {
-  nsIScrollableFrame *scrollableFrame = GetFrameToScroll(nsLayoutUtils::eVertical);
+  nsIScrollableFrame *scrollableFrame =
+    GetFrameToScrollAsScrollable(nsIPresShell::eVertical);
   if (!scrollableFrame)
     return NS_OK;
 
   mSelection->CommonPageMove(aForward, aExtend, scrollableFrame);
   // After ScrollSelectionIntoView(), the pending notifications might be
   // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
   return ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, nsISelectionController::SELECTION_FOCUS_REGION, PR_TRUE);
 }
 
 
 
 NS_IMETHODIMP 
 PresShell::ScrollPage(PRBool aForward)
 {
-  nsIScrollableFrame* scrollFrame = GetFrameToScroll(nsLayoutUtils::eVertical);
+  nsIScrollableFrame* scrollFrame =
+    GetFrameToScrollAsScrollable(nsIPresShell::eVertical);
   if (scrollFrame) {
     scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
                           nsIScrollableFrame::PAGES,
                           nsIScrollableFrame::SMOOTH);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresShell::ScrollLine(PRBool aForward)
 {
-  nsIScrollableFrame* scrollFrame = GetFrameToScroll(nsLayoutUtils::eVertical);
+  nsIScrollableFrame* scrollFrame =
+    GetFrameToScrollAsScrollable(nsIPresShell::eVertical);
   if (scrollFrame) {
     PRInt32 lineCount = 1;
 #ifdef MOZ_WIDGET_COCOA
     // Emulate the Mac IE behavior of scrolling a minimum of 2 lines
     // rather than 1.  This vastly improves scrolling speed.
     lineCount = 2;
 #endif
     scrollFrame->ScrollBy(nsIntPoint(0, aForward ? lineCount : -lineCount),
@@ -2980,17 +2981,18 @@ PresShell::ScrollLine(PRBool aForward)
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresShell::ScrollHorizontal(PRBool aLeft)
 {
-  nsIScrollableFrame* scrollFrame = GetFrameToScroll(nsLayoutUtils::eHorizontal);
+  nsIScrollableFrame* scrollFrame =
+    GetFrameToScrollAsScrollable(nsIPresShell::eHorizontal);
   if (scrollFrame) {
     scrollFrame->ScrollBy(nsIntPoint(aLeft ? -1 : 1, 0),
                           nsIScrollableFrame::LINES,
                           nsIScrollableFrame::SMOOTH);
 //NEW FOR LINES    
     // force the update to happen now, otherwise multiple scrolls can
     // occur before the update is processed. (bug #7354)
 
@@ -3002,17 +3004,18 @@ PresShell::ScrollHorizontal(PRBool aLeft
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresShell::CompleteScroll(PRBool aForward)
 {
-  nsIScrollableFrame* scrollFrame = GetFrameToScroll(nsLayoutUtils::eVertical);
+  nsIScrollableFrame* scrollFrame =
+    GetFrameToScrollAsScrollable(nsIPresShell::eVertical);
   if (scrollFrame) {
     scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
                           nsIScrollableFrame::WHOLE,
                           nsIScrollableFrame::INSTANT);
   }
   return NS_OK;
 }
 
@@ -3396,17 +3399,18 @@ PresShell::FrameNeedsToContinueReflow(ns
                "Frame passed in is not the descendant of mCurrentReflowRoot");
   NS_ASSERTION(aFrame->GetStateBits() & NS_FRAME_IN_REFLOW,
                "Frame passed in not in reflow?");
 
   mFramesToDirty.PutEntry(aFrame);
 }
 
 nsIScrollableFrame*
-PresShell::GetFrameToScroll(nsLayoutUtils::Direction aDirection)
+nsIPresShell::GetFrameToScrollAsScrollable(
+                nsIPresShell::ScrollDirection aDirection)
 {
   nsIScrollableFrame* scrollFrame = nsnull;
 
   nsCOMPtr<nsIContent> focusedContent;
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (fm && mDocument) {
     nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(mDocument->GetWindow());
 
@@ -3425,18 +3429,25 @@ PresShell::GetFrameToScroll(nsLayoutUtil
   }
   if (focusedContent) {
     nsIFrame* startFrame = focusedContent->GetPrimaryFrame();
     if (startFrame) {
       scrollFrame = startFrame->GetScrollTargetFrame();
       if (scrollFrame) {
         startFrame = scrollFrame->GetScrolledFrame();
       }
-      scrollFrame =
-        nsLayoutUtils::GetNearestScrollableFrameForDirection(startFrame, aDirection);
+      if (aDirection == nsIPresShell::eEither) {
+        scrollFrame =
+          nsLayoutUtils::GetNearestScrollableFrame(startFrame);
+      } else {
+        scrollFrame =
+          nsLayoutUtils::GetNearestScrollableFrameForDirection(startFrame,
+            aDirection == eVertical ? nsLayoutUtils::eVertical :
+                                      nsLayoutUtils::eHorizontal);
+      }
     }
   }
   if (!scrollFrame) {
     scrollFrame = GetRootScrollFrameAsScrollable();
   }
   return scrollFrame;
 }
 
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -1664,16 +1664,20 @@ pref("intl.tsf.on_layout_change_interval
 pref("ui.panel.default_level_parent", true);
 #else
 // See bug 448927, on topmost panel, some IMEs are not usable on Windows.
 pref("ui.panel.default_level_parent", false);
 #endif
 
 pref("mousewheel.system_scroll_override_on_root_content.enabled", true);
 
+// If your mouse drive sends WM_*SCROLL messages when you turn your mouse wheel,
+// set this to true.  Then, gecko processes them as mouse wheel messages.
+pref("mousewheel.emulate_at_wm_scroll", false);
+
 // Bug 514927
 // Enables or disabled the TrackPoint hack, -1 is autodetect, 0 is off,
 // and 1 is on.  Set this to 1 if TrackPoint scrolling is not working.
 pref("ui.trackpoint_hack.enabled", -1);
 # WINNT
 #endif
 
 #ifdef XP_MACOSX
--- a/widget/public/nsGUIEvent.h
+++ b/widget/public/nsGUIEvent.h
@@ -436,16 +436,23 @@ class nsHashKey;
 #define NS_CONTENT_COMMAND_EVENT_START  3800
 #define NS_CONTENT_COMMAND_CUT          (NS_CONTENT_COMMAND_EVENT_START)
 #define NS_CONTENT_COMMAND_COPY         (NS_CONTENT_COMMAND_EVENT_START+1)
 #define NS_CONTENT_COMMAND_PASTE        (NS_CONTENT_COMMAND_EVENT_START+2)
 #define NS_CONTENT_COMMAND_DELETE       (NS_CONTENT_COMMAND_EVENT_START+3)
 #define NS_CONTENT_COMMAND_UNDO         (NS_CONTENT_COMMAND_EVENT_START+4)
 #define NS_CONTENT_COMMAND_REDO         (NS_CONTENT_COMMAND_EVENT_START+5)
 #define NS_CONTENT_COMMAND_PASTE_TRANSFERABLE (NS_CONTENT_COMMAND_EVENT_START+6)
+// NS_CONTENT_COMMAND_SCROLL scrolls the nearest scrollable element to the
+// currently focused content or latest DOM selection. This would normally be
+// the same element scrolled by keyboard scroll commands, except that this event
+// will scroll an element scrollable in either direction.  I.e., if the nearest
+// scrollable ancestor element can only be scrolled vertically, and horizontal
+// scrolling is requested using this event, no scrolling will occur.
+#define NS_CONTENT_COMMAND_SCROLL       (NS_CONTENT_COMMAND_EVENT_START+7)
 
 // Event to gesture notification
 #define NS_GESTURENOTIFY_EVENT_START 3900
 
 #define NS_ORIENTATION_EVENT         4000
 
 #define NS_SCROLLAREA_EVENT_START    4100
 #define NS_SCROLLEDAREACHANGED       (NS_SCROLLAREA_EVENT_START)
@@ -1226,17 +1233,38 @@ public:
   nsContentCommandEvent(PRBool aIsTrusted, PRUint32 aMsg, nsIWidget *aWidget,
                         PRBool aOnlyEnabledCheck = PR_FALSE) :
     nsGUIEvent(aIsTrusted, aMsg, aWidget, NS_CONTENT_COMMAND_EVENT),
     mOnlyEnabledCheck(PRPackedBool(aOnlyEnabledCheck)),
     mSucceeded(PR_FALSE), mIsEnabled(PR_FALSE)
   {
   }
 
+  // NS_CONTENT_COMMAND_PASTE_TRANSFERABLE
   nsCOMPtr<nsITransferable> mTransferable;                 // [in]
+
+  // NS_CONTENT_COMMAND_SCROLL
+  // for mScroll.mUnit
+  enum {
+    eCmdScrollUnit_Line,
+    eCmdScrollUnit_Page,
+    eCmdScrollUnit_Whole
+  };
+
+  struct ScrollInfo {
+    ScrollInfo() :
+      mAmount(0), mUnit(eCmdScrollUnit_Line), mIsHorizontal(PR_FALSE)
+    {
+    }
+
+    PRInt32      mAmount;                                  // [in]
+    PRUint8      mUnit;                                    // [in]
+    PRPackedBool mIsHorizontal;                            // [in]
+  } mScroll;
+
   PRPackedBool mOnlyEnabledCheck;                          // [in]
 
   PRPackedBool mSucceeded;                                 // [out]
   PRPackedBool mIsEnabled;                                 // [out]
 };
 
 /**
  * MenuItem event
--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -4300,26 +4300,18 @@ PRBool nsWindow::ProcessMessage(UINT msg
           break;
       }
       // default = PR_FALSE - tell the driver that the event was not handled
     }
     break;
 
     case WM_HSCROLL:
     case WM_VSCROLL:
-      // check for the incoming nsWindow handle to be null in which case
-      // we assume the message is coming from a horizontal scrollbar inside
-      // a listbox and we don't bother processing it (well, we don't have to)
-      if (lParam) {
-        nsWindow* scrollbar = GetNSWindowPtr((HWND)lParam);
-
-        if (scrollbar) {
-          result = scrollbar->OnScroll(msg, wParam, lParam);
-        }
-      }
+      *aRetValue = 0;
+      result = OnScroll(msg, wParam, lParam);
       break;
 
     case WM_CTLCOLORLISTBOX:
     case WM_CTLCOLOREDIT:
     case WM_CTLCOLORBTN:
     //case WM_CTLCOLORSCROLLBAR: //XXX causes the scrollbar to be drawn incorrectly
     case WM_CTLCOLORSTATIC:
       if (lParam) {
@@ -6282,18 +6274,31 @@ PRBool nsWindow::HandleScrollingPlugins(
       printf("WARNING: couldn't get child window for SCROLL event\n");
   #endif
   }
   return PR_TRUE;  // caller should handle this
 }
 
 PRBool nsWindow::OnScroll(UINT aMsg, WPARAM aWParam, LPARAM aLParam)
 {
-  if (aLParam)
-  {
+  static PRInt8 sMouseWheelEmulation = -1;
+  if (sMouseWheelEmulation < 0) {
+    nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+    NS_ENSURE_TRUE(prefs, PR_FALSE);
+    nsCOMPtr<nsIPrefBranch> prefBranch;
+    prefs->GetBranch(0, getter_AddRefs(prefBranch));
+    NS_ENSURE_TRUE(prefBranch, PR_FALSE);
+    PRBool emulate;
+    nsresult rv =
+      prefBranch->GetBoolPref("mousewheel.emulate_at_wm_scroll", &emulate);
+    NS_ENSURE_SUCCESS(rv, PR_FALSE);
+    sMouseWheelEmulation = PRInt8(emulate);
+  }
+
+  if (aLParam || sMouseWheelEmulation) {
     // Scroll message generated by Thinkpad Trackpoint Driver or similar
     // Treat as a mousewheel message and scroll appropriately
     PRBool quit, result;
     LRESULT retVal;
 
     if (!HandleScrollingPlugins(aMsg, aWParam, aLParam, result, &retVal, quit))
       return quit;  // Return if it's not our message or has been dispatched
 
@@ -6322,19 +6327,53 @@ PRBool nsWindow::OnScroll(UINT aMsg, WPA
     scrollevent.isAlt     = IS_VK_DOWN(NS_VK_ALT);
     InitEvent(scrollevent);
     if (nsnull != mEventCallback)
     {
       DispatchWindowEvent(&scrollevent);
     }
     return PR_TRUE;
   }
+
   // Scroll message generated by external application
-  // XXX Handle by scrolling the window in the desired manner (Bug 315727)
-  return PR_FALSE;
+  nsContentCommandEvent command(PR_TRUE, NS_CONTENT_COMMAND_SCROLL, this);
+
+  command.mScroll.mIsHorizontal = (aMsg == WM_HSCROLL);
+
+  switch (LOWORD(aWParam))
+  {
+    case SB_LINEUP:   // SB_LINELEFT
+      command.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Line;
+      command.mScroll.mAmount = -1;
+      break;
+    case SB_LINEDOWN: // SB_LINERIGHT
+      command.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Line;
+      command.mScroll.mAmount = 1;
+      break;
+    case SB_PAGEUP:   // SB_PAGELEFT
+      command.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Page;
+      command.mScroll.mAmount = -1;
+      break;
+    case SB_PAGEDOWN: // SB_PAGERIGHT
+      command.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Page;
+      command.mScroll.mAmount = 1;
+      break;
+    case SB_TOP:      // SB_LEFT
+      command.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Whole;
+      command.mScroll.mAmount = -1;
+      break;
+    case SB_BOTTOM:   // SB_RIGHT
+      command.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Whole;
+      command.mScroll.mAmount = 1;
+      break;
+    default:
+      return PR_FALSE;
+  }
+  DispatchWindowEvent(&command);
+  return PR_TRUE;
 }
 
 // Return the brush used to paint the background of this control
 HBRUSH nsWindow::OnControlColor()
 {
   return mBrush;
 }
 
--- a/widget/tests/TestWinTSF.cpp
+++ b/widget/tests/TestWinTSF.cpp
@@ -91,16 +91,17 @@ template<class T> class nsReadingIterato
 #include "nsIWebProgress.h"
 #include "nsIWebProgressListener.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMHTMLBodyElement.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsIDOMHTMLInputElement.h"
 #include "nsIDOMHTMLTextAreaElement.h"
+#include "nsIDOMNSElement.h"
 #include "nsISelectionController.h"
 #include "nsIViewManager.h"
 #include "nsTArray.h"
 #include "nsGUIEvent.h"
 
 #ifndef MOZILLA_INTERNAL_API
 #undef nsString_h___
 #undef nsAString_h___
@@ -139,16 +140,17 @@ protected:
   PRBool TestClustering(void);
   PRBool TestSelection(void);
   PRBool TestText(void);
   PRBool TestExtents(void);
   PRBool TestComposition(void);
   PRBool TestNotification(void);
   PRBool TestContentEvents(void);
   PRBool TestEditMessages(void);
+  PRBool TestScrollMessages(void);
 
   PRBool TestSelectionInternal(char* aTestName,
                                         LONG aStart,
                                         LONG aEnd,
                                         TsActiveSelEnd aSelEnd);
   PRBool TestCompositionSelectionAndText(char* aTestName,
                                          LONG aExpectedSelStart,
                                          LONG aExpectedSelEnd,
@@ -1659,16 +1661,18 @@ TestApp::OnStateChange(nsIWebProgress *a
   NS_ASSERTION(aStateFlags & nsIWebProgressListener::STATE_IS_WINDOW &&
               aStateFlags & nsIWebProgressListener::STATE_STOP, "wrong state");
   if (NS_SUCCEEDED(Init())) {
     printf("Testing content events...\n");
     if (TestContentEvents())
       passed("TestContentEvents");
     if (RunTest(&TestApp::TestEditMessages))
       passed("TestEditMessages");
+    if (RunTest(&TestApp::TestScrollMessages))
+      passed("TestScrollMessages");
 
     if (RunTest(&TestApp::TestFocus, PR_FALSE))
       passed("TestFocus");
 
     mCurrentNode = mInput;
     mInput->Focus();
     if (mMgr->GetFocusedStore()) {
       if (RunTest(&TestApp::TestClustering))
@@ -3033,16 +3037,274 @@ TestApp::TestEditMessages(void)
     printf("Current Str: \"%s\"\n", NS_ConvertUTF16toUTF8(str).get());
     return PR_FALSE;
   }
 
   return PR_TRUE;
 }
 
 PRBool
+TestApp::TestScrollMessages(void)
+{
+  NS_NAMED_LITERAL_STRING(kLine, "ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss\n");
+  mTestString.Truncate();
+  for (PRUint32 i = 0; i < 30; i++) {
+    mTestString.Append(kLine);
+  }
+
+  mTextArea->SetAttribute(NS_LITERAL_STRING("style"),
+    NS_LITERAL_STRING("width:3em;height:3em;word-wrap:normal;"));
+  mTextArea->SetValue(mTestString);
+  mTextArea->Focus();
+
+  nsCOMPtr<nsIWidget> widget;
+  if (!GetWidget(getter_AddRefs(widget))) {
+    fail("TestScrollMessages: get nsIWidget");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+  nsCOMPtr<nsIDOMNSElement> textAreaNS(do_QueryInterface(mTextArea));
+  if (!textAreaNS) {
+    fail("TestScrollMessages: get nsIDOMNSElement");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+#define DO_CHECK(aFailureCondition, aDescription) \
+  if (aFailureCondition) { \
+    nsCAutoString str(aDescription); \
+    str.Append(": "); \
+    str.Append(#aFailureCondition); \
+    fail(str.get()); \
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); \
+    return PR_FALSE; \
+  }
+
+  HWND wnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW);
+
+  textAreaNS->SetScrollTop(0);
+  textAreaNS->SetScrollLeft(0);
+
+  if (::SendMessage(wnd, WM_VSCROLL, SB_LINEDOWN, 0) != 0) {
+    fail("TestScrollMessages: SendMessage WM_VSCROLL #1");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+  PRInt32 x, y, prevX, prevY;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x != 0, "TestScrollMessages: SendMessage WM_VSCROLL #1");
+  DO_CHECK(y == 0, "TestScrollMessages: SendMessage WM_VSCROLL #1");
+
+  if (::SendMessage(wnd, WM_HSCROLL, SB_LINERIGHT, 0) != 0) {
+    fail("TestScrollMessages: SendMessage WM_HSCROLL #1");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  const PRInt32 kLineWidth  = x;
+  const PRInt32 kLineHeight = y;
+
+  DO_CHECK(x == 0,     "TestScrollMessages: SendMessage WM_HSCROLL #1");
+  DO_CHECK(y != prevY, "TestScrollMessages: SendMessage WM_HSCROLL #1");
+
+  if (::SendMessage(wnd, WM_VSCROLL, SB_LINEUP, 0) != 0) {
+    fail("TestScrollMessages: SendMessage WM_VSCROLL #2");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x != prevX, "TestScrollMessages: SendMessage WM_VSCROLL #2");
+  DO_CHECK(y != 0,     "TestScrollMessages: SendMessage WM_VSCROLL #2");
+
+  if (::SendMessage(wnd, WM_HSCROLL, SB_LINELEFT, 0) != 0) {
+    fail("TestScrollMessages: SendMessage WM_HSCROLL #2");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x != 0, "TestScrollMessages: SendMessage WM_HSCROLL #2");
+  DO_CHECK(y != 0, "TestScrollMessages: SendMessage WM_HSCROLL #2");
+
+  if (::SendMessage(wnd, WM_VSCROLL, SB_PAGEDOWN, 0) != 0) {
+    fail("TestScrollMessages: SendMessage WM_VSCROLL #3");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x != 0,           "TestScrollMessages: SendMessage WM_VSCROLL #3");
+  DO_CHECK(y <= kLineHeight, "TestScrollMessages: SendMessage WM_VSCROLL #3");
+
+  if (::SendMessage(wnd, WM_HSCROLL, SB_PAGERIGHT, 0) != 0) {
+    fail("TestScrollMessages: SendMessage WM_HSCROLL #3");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x <= kLineWidth, "TestScrollMessages: SendMessage WM_HSCROLL #3");
+  DO_CHECK(y != prevY,      "TestScrollMessages: SendMessage WM_HSCROLL #3");
+
+  const PRInt32 kPageWidth  = x;
+  const PRInt32 kPageHeight = y;
+
+  ::SendMessage(wnd, WM_VSCROLL, SB_LINEDOWN, 0);
+  ::SendMessage(wnd, WM_VSCROLL, SB_LINEUP, 0);
+  ::SendMessage(wnd, WM_HSCROLL, SB_LINERIGHT, 0);
+  ::SendMessage(wnd, WM_HSCROLL, SB_LINELEFT, 0);
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x != prevX, "TestScrollMessages: SB_LINELEFT scrolled wrong amount");
+  DO_CHECK(y != prevY, "TestScrollMessages: SB_LINEUP scrolled wrong amount");
+
+  if (::SendMessage(wnd, WM_VSCROLL, SB_PAGEUP, 0) != 0) {
+    fail("TestScrollMessages: SendMessage WM_VSCROLL #4");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x != prevX, "TestScrollMessages: SendMessage WM_VSCROLL #4");
+  DO_CHECK(y != 0,     "TestScrollMessages: SendMessage WM_VSCROLL #4");
+
+  if (::SendMessage(wnd, WM_HSCROLL, SB_PAGELEFT, 0) != 0) {
+    fail("TestScrollMessages: SendMessage WM_HSCROLL #4");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x != 0, "TestScrollMessages: SendMessage WM_HSCROLL #4");
+  DO_CHECK(y != 0, "TestScrollMessages: SendMessage WM_HSCROLL #4");
+
+  if (::SendMessage(wnd, WM_VSCROLL, SB_BOTTOM, 0) != 0) {
+    fail("TestScrollMessages: SendMessage WM_VSCROLL #5");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x != 0,           "TestScrollMessages: SendMessage WM_VSCROLL #5");
+  DO_CHECK(y <= kPageHeight, "TestScrollMessages: SendMessage WM_VSCROLL #5");
+
+  if (::SendMessage(wnd, WM_HSCROLL, SB_RIGHT, 0) != 0) {
+    fail("TestScrollMessages: SendMessage WM_HSCROLL #6");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x <= kPageWidth, "TestScrollMessages: SendMessage WM_HSCROLL #5");
+  DO_CHECK(y != prevY,      "TestScrollMessages: SendMessage WM_HSCROLL #5");
+
+  ::SendMessage(wnd, WM_VSCROLL, SB_LINEDOWN, 0);
+  ::SendMessage(wnd, WM_HSCROLL, SB_LINERIGHT, 0);
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x != prevX, "SB_RIGHT didn't scroll to right most");
+  DO_CHECK(y != prevY, "SB_BOTTOM didn't scroll to bottom most");
+
+  ::SendMessage(wnd, WM_VSCROLL, SB_PAGEUP, 0);
+  ::SendMessage(wnd, WM_VSCROLL, SB_PAGEDOWN, 0);
+  ::SendMessage(wnd, WM_HSCROLL, SB_PAGELEFT, 0);
+  ::SendMessage(wnd, WM_HSCROLL, SB_PAGERIGHT, 0);
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x != prevX, "TestScrollMessages: SB_PAGELEFT scrolled wrong amount");
+  DO_CHECK(y != prevY, "TestScrollMessages: SB_PAGEUP scrolled wrong amount");
+
+  if (::SendMessage(wnd, WM_VSCROLL, SB_TOP, 0) != 0) {
+    fail("TestScrollMessages: SendMessage WM_VSCROLL #6");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x != prevX, "TestScrollMessages: SendMessage WM_VSCROLL #6");
+  DO_CHECK(y != 0,     "TestScrollMessages: SendMessage WM_VSCROLL #6");
+
+  if (::SendMessage(wnd, WM_HSCROLL, SB_LEFT, 0) != 0) {
+    fail("TestScrollMessages: SendMessage WM_HSCROLL #4");
+    mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+    return PR_FALSE;
+  }
+
+  prevX = x;
+  prevY = y;
+  textAreaNS->GetScrollTop(&y);
+  textAreaNS->GetScrollLeft(&x);
+
+  DO_CHECK(x != 0, "TestScrollMessages: SendMessage WM_HSCROLL #6");
+  DO_CHECK(y != 0, "TestScrollMessages: SendMessage WM_HSCROLL #6");
+#undef DO_CHECK
+
+  mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString());
+  return PR_TRUE;
+}
+
+PRBool
 TestApp::GetWidget(nsIWidget** aWidget)
 {
   nsCOMPtr<nsIDocShell> docShell;
   nsresult rv = mWindow->GetDocShell(getter_AddRefs(docShell));
   if (NS_FAILED(rv) || !docShell) {
     return PR_FALSE;
   }