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 idunknown
push userunknown
push dateunknown
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;
   }