Bug 957445: Part 1 - Scrolling moved from nsScrollbarButtonFrame and nsSliderFrame into nsIScrollbarMediator, r=mats
authorMiranda Emery <miranda.j.emery@gmail.com>
Wed, 05 Feb 2014 14:30:34 +1300
changeset 201255 9eabf947efc3363a1bf79aa03c3053d184510846
parent 201254 3181a38a32c0e82f86a4c47bb8c75ca54bd10408
child 201256 249dae95ea83149bdc1e82df00afbac10ee38c6b
push idunknown
push userunknown
push dateunknown
reviewersmats
bugs957445
milestone34.0a1
Bug 957445: Part 1 - Scrolling moved from nsScrollbarButtonFrame and nsSliderFrame into nsIScrollbarMediator, r=mats
layout/xul/nsIScrollbarMediator.h
layout/xul/nsListBoxBodyFrame.cpp
layout/xul/nsListBoxBodyFrame.h
layout/xul/nsScrollbarButtonFrame.cpp
layout/xul/nsScrollbarButtonFrame.h
layout/xul/nsScrollbarFrame.cpp
layout/xul/nsScrollbarFrame.h
layout/xul/nsSliderFrame.cpp
layout/xul/nsSliderFrame.h
layout/xul/tree/nsTreeBodyFrame.cpp
layout/xul/tree/nsTreeBodyFrame.h
--- a/layout/xul/nsIScrollbarMediator.h
+++ b/layout/xul/nsIScrollbarMediator.h
@@ -2,25 +2,50 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsIScrollbarMediator_h___
 #define nsIScrollbarMediator_h___
 
 #include "nsQueryFrame.h"
+#include "nsCoord.h"
 
 class nsScrollbarFrame;
 
 class nsIScrollbarMediator
 {
 public:
   NS_DECL_QUERYFRAME_TARGET(nsIScrollbarMediator)
 
-  // The aScrollbar argument denotes the scrollbar that's firing the notification.
-  NS_IMETHOD PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex) = 0;
-  NS_IMETHOD ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex) = 0;
+  /**
+   * The aScrollbar argument denotes the scrollbar that's firing the notification.
+   * aScrollbar is never null.
+   * aDirection is either -1, 0, or 1.
+   */
 
-  NS_IMETHOD VisibilityChanged(bool aVisible) = 0;
+  /**
+   * One of the following three methods is called when the scrollbar's button is
+   * clicked.
+   * @note These methods might destroy the frame, pres shell, and other objects.
+   */
+  virtual void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection) = 0;
+  virtual void ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection) = 0;
+  virtual void ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection) = 0;
+  /**
+   * RepeatButtonScroll is called when the scrollbar's button is held down. When the
+   * button is first clicked the increment is set; RepeatButtonScroll adds this
+   * increment to the current position.
+   * @note This method might destroy the frame, pres shell, and other objects.
+   */
+  virtual void RepeatButtonScroll(nsScrollbarFrame* aScrollbar) = 0;
+  /**
+   * aOldPos and aNewPos are scroll positions.
+   * @note This method might destroy the frame, pres shell, and other objects.
+   */
+  virtual void ThumbMoved(nsScrollbarFrame* aScrollbar,
+                          nscoord aOldPos,
+                          nscoord aNewPos) = 0;
+  virtual void VisibilityChanged(bool aVisible) = 0;
 };
 
 #endif
 
--- a/layout/xul/nsListBoxBodyFrame.cpp
+++ b/layout/xul/nsListBoxBodyFrame.cpp
@@ -319,103 +319,138 @@ nsListBoxBodyFrame::GetPrefSize(nsBoxLay
     nsMargin scrollbars = scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState);
     pref.width += scrollbars.left + scrollbars.right;
   }
   return pref;
 }
 
 ///////////// nsIScrollbarMediator ///////////////
 
-NS_IMETHODIMP
-nsListBoxBodyFrame::PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex)
+void
+nsListBoxBodyFrame::ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection)
+{
+  MOZ_ASSERT(aScrollbar != nullptr);
+  UpdateIndex(aDirection);
+  aScrollbar->SetIncrementToPage(aDirection);
+  aScrollbar->MoveToNewPosition();
+}
+
+void
+nsListBoxBodyFrame::ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection)
+{
+  MOZ_ASSERT(aScrollbar != nullptr); 
+  UpdateIndex(aDirection);
+  aScrollbar->SetIncrementToWhole(aDirection);
+  aScrollbar->MoveToNewPosition();
+}
+
+void
+nsListBoxBodyFrame::ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection)
+{
+  MOZ_ASSERT(aScrollbar != nullptr); 
+  UpdateIndex(aDirection);
+  aScrollbar->SetIncrementToLine(aDirection);
+  aScrollbar->MoveToNewPosition();
+}
+
+void
+nsListBoxBodyFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar)
+{
+  int32_t increment = aScrollbar->GetIncrement();
+  if (increment < 0) {
+    UpdateIndex(-1);
+  } else if (increment > 0) {
+    UpdateIndex(1);
+  }
+  aScrollbar->MoveToNewPosition();
+}
+
+void
+nsListBoxBodyFrame::ThumbMoved(nsScrollbarFrame* aScrollbar,
+                               nscoord aOldPos,
+                               nscoord aNewPos)
 { 
   if (mScrolling || mRowHeight == 0)
-    return NS_OK;
+    return;
 
-  nscoord oldTwipIndex, newTwipIndex;
+  nscoord oldTwipIndex;
   oldTwipIndex = mCurrentIndex*mRowHeight;
-  newTwipIndex = nsPresContext::CSSPixelsToAppUnits(aNewIndex);
-  int32_t twipDelta = newTwipIndex > oldTwipIndex ? newTwipIndex - oldTwipIndex : oldTwipIndex - newTwipIndex;
+  int32_t twipDelta = aNewPos > oldTwipIndex ? aNewPos - oldTwipIndex : oldTwipIndex - aNewPos;
 
   int32_t rowDelta = twipDelta / mRowHeight;
   int32_t remainder = twipDelta % mRowHeight;
   if (remainder > (mRowHeight/2))
     rowDelta++;
 
   if (rowDelta == 0)
-    return NS_OK;
+    return;
 
   // update the position to be row based.
 
-  int32_t newIndex = newTwipIndex > oldTwipIndex ? mCurrentIndex + rowDelta : mCurrentIndex - rowDelta;
+  int32_t newIndex = aNewPos > oldTwipIndex ? mCurrentIndex + rowDelta : mCurrentIndex - rowDelta;
   //aNewIndex = newIndex*mRowHeight/mOnePixel;
 
   nsListScrollSmoother* smoother = GetSmoother();
 
   // if we can't scroll the rows in time then start a timer. We will eat
   // events until the user stops moving and the timer stops.
   if (smoother->IsRunning() || rowDelta*mTimePerRow > USER_TIME_THRESHOLD) {
 
      smoother->Stop();
 
-     smoother->mDelta = newTwipIndex > oldTwipIndex ? rowDelta : -rowDelta;
+     smoother->mDelta = aNewPos > oldTwipIndex ? rowDelta : -rowDelta;
 
      smoother->Start();
 
-     return NS_OK;
+     return;
   }
 
   smoother->Stop();
 
   mCurrentIndex = newIndex;
   smoother->mDelta = 0;
   
   if (mCurrentIndex < 0) {
     mCurrentIndex = 0;
-    return NS_OK;
+    return;
   }
-
-  return InternalPositionChanged(newTwipIndex < oldTwipIndex, rowDelta);
+  InternalPositionChanged(aNewPos < oldTwipIndex, rowDelta);
 }
 
-NS_IMETHODIMP
+void
 nsListBoxBodyFrame::VisibilityChanged(bool aVisible)
 {
   if (mRowHeight == 0)
-    return NS_OK;
+    return;
 
   int32_t lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight);
   if (lastPageTopRow < 0)
     lastPageTopRow = 0;
   int32_t delta = mCurrentIndex - lastPageTopRow;
   if (delta > 0) {
     mCurrentIndex = lastPageTopRow;
     InternalPositionChanged(true, delta);
   }
-
-  return NS_OK;
 }
 
-NS_IMETHODIMP
-nsListBoxBodyFrame::ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex)
+void
+nsListBoxBodyFrame::UpdateIndex(int32_t aDirection)
 {
-  if (aOldIndex == aNewIndex)
-    return NS_OK;
-  if (aNewIndex < aOldIndex)
+  if (aDirection == 0)
+    return;
+  if (aDirection < 0)
     mCurrentIndex--;
   else mCurrentIndex++;
   if (mCurrentIndex < 0) {
     mCurrentIndex = 0;
-    return NS_OK;
+    return;
   }
-  InternalPositionChanged(aNewIndex < aOldIndex, 1);
-
-  return NS_OK;
+  InternalPositionChanged(aDirection < 0, 1);
 }
-
+ 
 ///////////// nsIReflowCallback ///////////////
 
 bool
 nsListBoxBodyFrame::ReflowFinished()
 {
   nsAutoScriptBlocker scriptBlocker;
   // now create or destroy any rows as needed
   CreateRows();
--- a/layout/xul/nsListBoxBodyFrame.h
+++ b/layout/xul/nsListBoxBodyFrame.h
@@ -51,19 +51,25 @@ public:
   virtual void Init(nsIContent*       aContent,
                     nsContainerFrame* aParent, 
                     nsIFrame*         aPrevInFlow) MOZ_OVERRIDE;
   virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE;
 
   virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType) MOZ_OVERRIDE;
 
   // nsIScrollbarMediator
-  NS_IMETHOD PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex) MOZ_OVERRIDE;
-  NS_IMETHOD ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex) MOZ_OVERRIDE;
-  NS_IMETHOD VisibilityChanged(bool aVisible) MOZ_OVERRIDE;
+  virtual void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection) MOZ_OVERRIDE;
+  virtual void ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection) MOZ_OVERRIDE;
+  virtual void ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection) MOZ_OVERRIDE;
+  virtual void RepeatButtonScroll(nsScrollbarFrame* aScrollbar) MOZ_OVERRIDE;
+  virtual void ThumbMoved(nsScrollbarFrame* aScrollbar,
+                          int32_t aOldPos,
+                          int32_t aNewPos) MOZ_OVERRIDE;
+  virtual void VisibilityChanged(bool aVisible) MOZ_OVERRIDE;
+
 
   // nsIReflowCallback
   virtual bool ReflowFinished() MOZ_OVERRIDE;
   virtual void ReflowCallbackCanceled() MOZ_OVERRIDE;
 
   NS_IMETHOD DoLayout(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE;
   virtual void MarkIntrinsicISizesDirty() MOZ_OVERRIDE;
 
@@ -84,16 +90,17 @@ public:
   nsresult InternalPositionChanged(bool aUp, int32_t aDelta);
   // Process pending position changed events, then do the position change.
   // This can wipe out the frametree.
   nsresult DoInternalPositionChangedSync(bool aUp, int32_t aDelta);
   // Actually do the internal position change.  This can wipe out the frametree
   nsresult DoInternalPositionChanged(bool aUp, int32_t aDelta);
   nsListScrollSmoother* GetSmoother();
   void VerticalScroll(int32_t aDelta);
+  void UpdateIndex(int32_t aDirection);
 
   // frames
   nsIFrame* GetFirstFrame();
   nsIFrame* GetLastFrame();
 
   // lazy row creation and destruction
   void CreateRows();
   void DestroyRows(int32_t& aRowsToLose);
--- a/layout/xul/nsScrollbarButtonFrame.cpp
+++ b/layout/xul/nsScrollbarButtonFrame.cpp
@@ -73,17 +73,16 @@ nsScrollbarButtonFrame::HandleEvent(nsPr
       mCursorOnThis = frameRect.Contains(cursor);
       break;
     }
   }
 
   return nsButtonBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
 }
 
-
 bool
 nsScrollbarButtonFrame::HandleButtonPress(nsPresContext* aPresContext,
                                           WidgetGUIEvent* aEvent,
                                           nsEventStatus* aEventStatus)
 {
   // Get the desired action for the scrollbar button.
   LookAndFeel::IntID tmpAction;
   uint16_t button = aEvent->AsMouseEvent()->button;
@@ -104,72 +103,83 @@ nsScrollbarButtonFrame::HandleButtonPres
   }
 
   // get the scrollbar control
   nsIFrame* scrollbar;
   GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
 
   if (scrollbar == nullptr)
     return false;
-
-  // get the scrollbars content node
-  nsIContent* content = scrollbar->GetContent();
-
+ 
   static nsIContent::AttrValuesArray strings[] = { &nsGkAtoms::increment,
                                                    &nsGkAtoms::decrement,
                                                    nullptr };
   int32_t index = mContent->FindAttrValueIn(kNameSpaceID_None,
                                             nsGkAtoms::type,
                                             strings, eCaseMatters);
   int32_t direction;
-  if (index == 0) 
+  if (index == 0)
     direction = 1;
   else if (index == 1)
     direction = -1;
   else
     return false;
 
-  // Whether or not to repeat the click action.
-  bool repeat = true;
-  // Use smooth scrolling by default.
-  bool smoothScroll = true;
-  switch (pressedButtonAction) {
+  bool repeat = pressedButtonAction != 2;
+  // set this attribute so we can style it later
+  nsWeakFrame weakFrame(this);
+  mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::active, NS_LITERAL_STRING("true"), true);
+
+  nsIPresShell::SetCapturingContent(mContent, CAPTURE_IGNOREALLOWED);
+
+  if (!weakFrame.IsAlive()) {
+    return false;
+  }
+
+  nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
+  if (sb) {
+    nsIScrollbarMediator* m = sb->GetScrollbarMediator();
+    switch (pressedButtonAction) {
     case 0:
-      mIncrement = direction * nsSliderFrame::GetIncrement(content);
+      sb->SetIncrementToLine(direction);
+      if (m) {
+        m->ScrollByLine(sb, direction);
+      }
       break;
     case 1:
-      mIncrement = direction * nsSliderFrame::GetPageIncrement(content);
+      sb->SetIncrementToPage(direction);
+      if (m) {
+        m->ScrollByPage(sb, direction);
+      }
       break;
     case 2:
-      if (direction == -1)
-        mIncrement = -nsSliderFrame::GetCurrentPosition(content);
-      else
-        mIncrement = nsSliderFrame::GetMaxPosition(content) - 
-                     nsSliderFrame::GetCurrentPosition(content);
-      // Don't repeat or use smooth scrolling if scrolling to beginning or end
-      // of a page.
-      repeat = smoothScroll = false;
+      sb->SetIncrementToWhole(direction);
+      if (m) {
+        m->ScrollByWhole(sb, direction);
+      }
       break;
     case 3:
     default:
       // We were told to ignore this click, or someone assigned a non-standard
       // value to the button's action.
       return false;
+    }
+    if (!weakFrame.IsAlive()) {
+      return false;
+    }
+    if (!m) {
+      sb->MoveToNewPosition();
+      if (!weakFrame.IsAlive()) {
+        return false;
+      }
+    }
   }
-  // set this attribute so we can style it later
-  nsWeakFrame weakFrame(this);
-  mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::active, NS_LITERAL_STRING("true"), true);
-
-  nsIPresShell::SetCapturingContent(mContent, CAPTURE_IGNOREALLOWED);
-
-  if (weakFrame.IsAlive()) {
-    DoButtonAction(smoothScroll);
+  if (repeat) {
+    StartRepeat();
   }
-  if (repeat)
-    StartRepeat();
   return true;
 }
 
 NS_IMETHODIMP 
 nsScrollbarButtonFrame::HandleRelease(nsPresContext* aPresContext,
                                       WidgetGUIEvent* aEvent,
                                       nsEventStatus* aEventStatus)
 {
@@ -177,83 +187,42 @@ nsScrollbarButtonFrame::HandleRelease(ns
   // we're not active anymore
   mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::active, true);
   StopRepeat();
   return NS_OK;
 }
 
 void nsScrollbarButtonFrame::Notify()
 {
-  // Since this is only going to get called if we're scrolling a page length
-  // or a line increment, we will always use smooth scrolling.
   if (mCursorOnThis ||
       LookAndFeel::GetInt(
         LookAndFeel::eIntID_ScrollbarButtonAutoRepeatBehavior, 0)) {
-    DoButtonAction(true);
+    // get the scrollbar control
+    nsIFrame* scrollbar;
+    GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
+    nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
+    if (sb) {
+      nsIScrollbarMediator* m = sb->GetScrollbarMediator();
+      if (m) {
+        m->RepeatButtonScroll(sb);
+      } else {
+        sb->MoveToNewPosition();
+      }
+    }
   }
 }
 
 void
 nsScrollbarButtonFrame::MouseClicked(nsPresContext* aPresContext,
                                      WidgetGUIEvent* aEvent) 
 {
   nsButtonBoxFrame::MouseClicked(aPresContext, aEvent);
   //MouseClicked();
 }
 
-void
-nsScrollbarButtonFrame::DoButtonAction(bool aSmoothScroll) 
-{
-  // get the scrollbar control
-  nsIFrame* scrollbar;
-  GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
-
-  if (scrollbar == nullptr)
-    return;
-
-  // get the scrollbars content node
-  nsCOMPtr<nsIContent> content = scrollbar->GetContent();
-
-  // get the current pos
-  int32_t curpos = nsSliderFrame::GetCurrentPosition(content);
-  int32_t oldpos = curpos;
-
-  // get the max pos
-  int32_t maxpos = nsSliderFrame::GetMaxPosition(content);
-
-  // increment the given amount
-  if (mIncrement)
-    curpos += mIncrement;
-
-  // make sure the current position is between the current and max positions
-  if (curpos < 0)
-    curpos = 0;
-  else if (curpos > maxpos)
-    curpos = maxpos;
-
-  nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
-  if (sb) {
-    nsIScrollbarMediator* m = sb->GetScrollbarMediator();
-    if (m) {
-      m->ScrollbarButtonPressed(sb, oldpos, curpos);
-      return;
-    }
-  }
-
-  // set the current position of the slider.
-  nsAutoString curposStr;
-  curposStr.AppendInt(curpos);
-
-  if (aSmoothScroll)
-    content->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, NS_LITERAL_STRING("true"), false);
-  content->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curposStr, true);
-  if (aSmoothScroll)
-    content->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false);
-}
-
 nsresult
 nsScrollbarButtonFrame::GetChildWithTag(nsPresContext* aPresContext,
                                         nsIAtom* atom, nsIFrame* start,
                                         nsIFrame*& result)
 {
   // recursively search our children
   nsIFrame* childFrame = start->GetFirstPrincipalChild();
   while (nullptr != childFrame) 
--- a/layout/xul/nsScrollbarButtonFrame.h
+++ b/layout/xul/nsScrollbarButtonFrame.h
@@ -62,26 +62,24 @@ public:
 
   NS_IMETHOD HandleRelease(nsPresContext* aPresContext,
                            mozilla::WidgetGUIEvent* aEvent,
                            nsEventStatus* aEventStatus) MOZ_OVERRIDE;
 
 protected:
   virtual void MouseClicked(nsPresContext* aPresContext,
                             mozilla::WidgetGUIEvent* aEvent) MOZ_OVERRIDE;
-  void DoButtonAction(bool aSmoothScroll);
 
   void StartRepeat() {
     nsRepeatService::GetInstance()->Start(Notify, this);
   }
   void StopRepeat() {
     nsRepeatService::GetInstance()->Stop(Notify, this);
   }
   void Notify();
   static void Notify(void* aData) {
     static_cast<nsScrollbarButtonFrame*>(aData)->Notify();
   }
   
-  int32_t mIncrement;  
   bool mCursorOnThis;
 };
 
 #endif
--- a/layout/xul/nsScrollbarFrame.cpp
+++ b/layout/xul/nsScrollbarFrame.cpp
@@ -6,24 +6,26 @@
 //
 // Eric Vaughan
 // Netscape Communications
 //
 // See documentation in associated header file
 //
 
 #include "nsScrollbarFrame.h"
+#include "nsSliderFrame.h"
 #include "nsScrollbarButtonFrame.h"
 #include "nsGkAtoms.h"
 #include "nsIScrollableFrame.h"
 #include "nsIScrollbarMediator.h"
 #include "mozilla/LookAndFeel.h"
 #include "nsThemeConstants.h"
 #include "nsRenderingContext.h"
 #include "nsIContent.h"
+#include "nsIDOMMutationEvent.h"
 
 using namespace mozilla;
 
 //
 // NS_NewScrollbarFrame
 //
 // Creates a new scrollbar frame and returns it
 //
@@ -181,8 +183,108 @@ nsScrollbarFrame::GetMargin(nsMargin& aM
         }
       }
       return NS_OK;
     }
   }
 
   return nsBox::GetMargin(aMargin);
 }
+
+void
+nsScrollbarFrame::SetIncrementToLine(int32_t aDirection)
+{
+  // get the scrollbar's content node
+  nsIContent* content = GetContent();
+  mSmoothScroll = true;
+  mIncrement = aDirection * nsSliderFrame::GetIncrement(content);
+}
+
+void
+nsScrollbarFrame::SetIncrementToPage(int32_t aDirection)
+{
+  // get the scrollbar's content node
+  nsIContent* content = GetContent();
+  mSmoothScroll = true;
+  mIncrement = aDirection * nsSliderFrame::GetPageIncrement(content);
+}
+
+void
+nsScrollbarFrame::SetIncrementToWhole(int32_t aDirection)
+{
+  // get the scrollbar's content node
+  nsIContent* content = GetContent();
+  if (aDirection == -1)
+    mIncrement = -nsSliderFrame::GetCurrentPosition(content);
+  else
+    mIncrement = nsSliderFrame::GetMaxPosition(content) -
+                 nsSliderFrame::GetCurrentPosition(content);
+  // Don't repeat or use smooth scrolling if scrolling to beginning or end
+  // of a page.
+  mSmoothScroll = false;
+}
+
+int32_t
+nsScrollbarFrame::MoveToNewPosition()
+{
+  // get the scrollbar's content node
+  nsCOMPtr<nsIContent> content = GetContent();
+
+  // get the current pos
+  int32_t curpos = nsSliderFrame::GetCurrentPosition(content);
+
+  // get the max pos
+  int32_t maxpos = nsSliderFrame::GetMaxPosition(content);
+
+  // increment the given amount
+  if (mIncrement) {
+    curpos += mIncrement;
+  }
+
+  // make sure the current position is between the current and max positions
+  if (curpos < 0) {
+    curpos = 0;
+  } else if (curpos > maxpos) {
+    curpos = maxpos;
+  }
+
+  // set the current position of the slider.
+  nsAutoString curposStr;
+  curposStr.AppendInt(curpos);
+
+  nsWeakFrame weakFrame(this);
+  if (mSmoothScroll) {
+    content->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, NS_LITERAL_STRING("true"), false);
+  }
+  content->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curposStr, false);
+  // notify the nsScrollbarFrame of the change
+  AttributeChanged(kNameSpaceID_None, nsGkAtoms::curpos, nsIDOMMutationEvent::MODIFICATION);
+  if (!weakFrame.IsAlive()) {
+    return curpos;
+  }
+  // notify all nsSliderFrames of the change
+  nsIFrame::ChildListIterator childLists(this);
+  for (; !childLists.IsDone(); childLists.Next()) {
+    nsFrameList::Enumerator childFrames(childLists.CurrentList());
+    for (; !childFrames.AtEnd(); childFrames.Next()) {
+      nsIFrame* f = childFrames.get();
+      nsSliderFrame* sliderFrame = do_QueryFrame(f);
+      if (sliderFrame) {
+        sliderFrame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::curpos, nsIDOMMutationEvent::MODIFICATION);
+        if (!weakFrame.IsAlive()) {
+          return curpos;
+        }
+      }
+    }
+  }
+  // See if we have appearance information for a theme.
+  const nsStyleDisplay* disp = StyleDisplay();
+  nsPresContext* presContext = PresContext();
+  if (disp->mAppearance) {
+    nsITheme *theme = presContext->GetTheme();
+    if (theme && theme->ThemeSupportsWidget(presContext, this, disp->mAppearance)) {
+      bool repaint;
+      theme->WidgetStateChanged(this, disp->mAppearance, nsGkAtoms::curpos, &repaint);
+    }
+  }
+  content->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false);
+  return curpos;
+}
--- a/layout/xul/nsScrollbarFrame.h
+++ b/layout/xul/nsScrollbarFrame.h
@@ -78,13 +78,32 @@ public:
    * frame. This means that when the scroll code decides to hide a
    * scrollframe by setting its height or width to zero, that will
    * hide the children too.
    */
   virtual bool DoesClipChildren() MOZ_OVERRIDE { return true; }
 
   virtual nsresult GetMargin(nsMargin& aMargin) MOZ_OVERRIDE;
 
+  /**
+   * The following three methods set the value of mIncrement when a
+   * scrollbar button is pressed.
+   */
+  void SetIncrementToLine(int32_t aDirection);
+  void SetIncrementToPage(int32_t aDirection);
+  void SetIncrementToWhole(int32_t aDirection);
+  /**
+   * MoveToNewPosition() adds mIncrement to the current position and
+   * updates the curpos attribute.
+   * @note This method might destroy the frame, pres shell, and other objects.
+   */
+  int32_t MoveToNewPosition();
+  int32_t GetIncrement() { return mIncrement; }
+
+protected:
+  int32_t mIncrement; // Amount to scroll, in CSSPixels
+  bool mSmoothScroll;
+
 private:
   nsCOMPtr<nsIContent> mScrollbarMediator;
 }; // class nsScrollbarFrame
 
 #endif
--- a/layout/xul/nsSliderFrame.cpp
+++ b/layout/xul/nsSliderFrame.cpp
@@ -55,16 +55,20 @@ GetContentOfBox(nsIFrame *aBox)
 nsIFrame*
 NS_NewSliderFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsSliderFrame(aPresShell, aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsSliderFrame)
 
+NS_QUERYFRAME_HEAD(nsSliderFrame)
+  NS_QUERYFRAME_ENTRY(nsSliderFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
+
 nsSliderFrame::nsSliderFrame(nsIPresShell* aPresShell, nsStyleContext* aContext):
   nsBoxFrame(aPresShell, aContext),
   mCurPos(0),
   mChange(0),
   mDragFinished(true),
   mUserChanged(false)
 {
 }
@@ -240,29 +244,35 @@ nsSliderFrame::AttributeChanged(int32_t 
           nsContentUtils::AddScriptRunner(
             new nsValueChangedRunnable(sliderListener, aAttribute,
                                        aAttribute == nsGkAtoms::minpos ? min : max, false));
         }
       }
 
       if (current < min || current > max)
       {
-        if (current < min || max < min)
-            current = min;
-        else if (current > max)
-            current = max;
+        int32_t direction = 0;
+        if (current < min || max < min) {
+          current = min;
+          direction = -1;
+        } else if (current > max) {
+          current = max;
+          direction = 1;
+        }
 
         // set the new position and notify observers
         nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
         if (scrollbarFrame) {
           nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
+          scrollbarFrame->SetIncrementToWhole(direction);
           if (mediator) {
-            mediator->PositionChanged(scrollbarFrame, GetCurrentPosition(scrollbar), current);
+            mediator->ScrollByWhole(scrollbarFrame, direction);
           }
         }
+        // 'this' might be destroyed here
 
         nsContentUtils::AddScriptRunner(
           new nsSetAttrRunnable(scrollbar, nsGkAtoms::curpos, current));
       }
   }
 
   if (aAttribute == nsGkAtoms::minpos ||
       aAttribute == nsGkAtoms::maxpos ||
@@ -614,20 +624,16 @@ nsSliderFrame::PageUpDown(nscoord change
 {
   // on a page up or down get our page increment. We get this by getting the scrollbar we are in and
   // asking it for the current position and the page increment. If we are not in a scrollbar we will
   // get the values from our own node.
   nsIFrame* scrollbarBox = GetScrollbar();
   nsCOMPtr<nsIContent> scrollbar;
   scrollbar = GetContentOfBox(scrollbarBox);
 
-  if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
-                            nsGkAtoms::reverse, eCaseMatters))
-    change = -change;
-
   nscoord pageIncrement = GetPageIncrement(scrollbar);
   int32_t curpos = GetCurrentPosition(scrollbar);
   int32_t minpos = GetMinPosition(scrollbar);
   int32_t maxpos = GetMaxPosition(scrollbar);
 
   // get the new position and make sure it is in bounds
   int32_t newpos = curpos + change * pageIncrement;
   if (newpos < minpos || maxpos < minpos)
@@ -771,39 +777,42 @@ nsSliderFrame::SetCurrentPosition(nsICon
 }
 
 void
 nsSliderFrame::SetCurrentPositionInternal(nsIContent* aScrollbar, int32_t aNewPos,
                                           bool aIsSmooth)
 {
   nsCOMPtr<nsIContent> scrollbar = aScrollbar;
   nsIFrame* scrollbarBox = GetScrollbar();
+  nsWeakFrame weakFrame(this);
 
   mUserChanged = true;
 
   nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
   if (scrollbarFrame) {
     // See if we have a mediator.
     nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
     if (mediator) {
-      nsRefPtr<nsPresContext> context = PresContext();
       nsCOMPtr<nsIContent> content = GetContent();
-      mediator->PositionChanged(scrollbarFrame, GetCurrentPosition(scrollbar), aNewPos);
-      // 'mediator' might be dangling now...
-      UpdateAttribute(scrollbar, aNewPos, false, aIsSmooth);
-      nsIFrame* frame = content->GetPrimaryFrame();
-      if (frame && frame->GetType() == nsGkAtoms::sliderFrame) {
-        static_cast<nsSliderFrame*>(frame)->CurrentPositionChanged();
+      nscoord oldPos = nsPresContext::CSSPixelsToAppUnits(GetCurrentPosition(scrollbar));
+      nscoord newPos = nsPresContext::CSSPixelsToAppUnits(aNewPos);
+      mediator->ThumbMoved(scrollbarFrame, oldPos, newPos);
+      if (!weakFrame.IsAlive()) {
+        return;
       }
+      CurrentPositionChanged();
       mUserChanged = false;
       return;
     }
   }
 
   UpdateAttribute(scrollbar, aNewPos, true, aIsSmooth);
+  if (!weakFrame.IsAlive()) {
+    return;
+  }
   mUserChanged = false;
 
 #ifdef DEBUG_SLIDER
   printf("Current Pos=%d\n",aNewPos);
 #endif
 
 }
 
@@ -1119,17 +1128,18 @@ nsSliderFrame::HandlePress(nsPresContext
   }
   else {
     mDestinationPoint = nsPoint(0, 0);
   }
 #else
   mDestinationPoint = eventPoint;
 #endif
   StartRepeat();
-  PageUpDown(change);
+  PageScroll(change);
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSliderFrame::HandleRelease(nsPresContext* aPresContext,
                              WidgetGUIEvent* aEvent,
                              nsEventStatus* aEventStatus)
 {
@@ -1183,17 +1193,18 @@ nsSliderFrame::EnsureOrient()
   bool isHorizontal = (scrollbarBox->GetStateBits() & NS_STATE_IS_HORIZONTAL) != 0;
   if (isHorizontal)
       mState |= NS_STATE_IS_HORIZONTAL;
   else
       mState &= ~NS_STATE_IS_HORIZONTAL;
 }
 
 
-void nsSliderFrame::Notify(void)
+void
+nsSliderFrame::Notify(void)
 {
     bool stop = false;
 
     nsIFrame* thumbFrame = mFrames.FirstChild();
     if (!thumbFrame) {
       StopRepeat();
       return;
     }
@@ -1220,14 +1231,34 @@ void nsSliderFrame::Notify(void)
                 stop = true;
         }
     }
 
 
     if (stop) {
       StopRepeat();
     } else {
-      PageUpDown(mChange);
+      PageScroll(mChange);
     }
 }
 
+void
+nsSliderFrame::PageScroll(nscoord aChange)
+{
+  if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
+                            nsGkAtoms::reverse, eCaseMatters)) {
+    aChange = -aChange;
+  }
+  nsIFrame* scrollbar = GetScrollbar();
+  nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
+  if (sb) {
+    nsIScrollbarMediator* m = sb->GetScrollbarMediator();
+    sb->SetIncrementToPage(aChange);
+    if (m) {
+      m->ScrollByPage(sb, aChange);
+      return;
+    }
+  }
+  PageUpDown(aChange);
+}
+
 NS_IMPL_ISUPPORTS(nsSliderMediator,
                   nsIDOMEventListener)
--- a/layout/xul/nsSliderFrame.h
+++ b/layout/xul/nsSliderFrame.h
@@ -37,16 +37,18 @@ public:
 protected:
   virtual ~nsSliderMediator() {}
 };
 
 class nsSliderFrame : public nsBoxFrame
 {
 public:
   NS_DECL_FRAMEARENA_HELPERS
+  NS_DECL_QUERYFRAME_TARGET(nsSliderFrame)
+  NS_DECL_QUERYFRAME
 
   friend class nsSliderMediator;
 
   nsSliderFrame(nsIPresShell* aShell, nsStyleContext* aContext);
   virtual ~nsSliderFrame();
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const MOZ_OVERRIDE {
@@ -156,16 +158,17 @@ private:
   }
   void StopRepeat() {
     nsRepeatService::GetInstance()->Stop(Notify, this);
   }
   void Notify();
   static void Notify(void* aData) {
     (static_cast<nsSliderFrame*>(aData))->Notify();
   }
+  void PageScroll(nscoord aChange);
  
   nsPoint mDestinationPoint;
   nsRefPtr<nsSliderMediator> mMediator;
 
   float mRatio;
 
   nscoord mDragStart;
   nscoord mThumbStart;
--- a/layout/xul/tree/nsTreeBodyFrame.cpp
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -4150,58 +4150,95 @@ nsTreeBodyFrame::ScrollHorzInternal(cons
   if (!weakFrame.IsAlive()) {
     return NS_ERROR_FAILURE;
   }
   // And fire off an event about it all
   PostScrollEvent();
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsTreeBodyFrame::ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex)
+void
+nsTreeBodyFrame::ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection)
+{
+  MOZ_ASSERT(aScrollbar != nullptr);
+  ScrollByPages(aDirection);
+}
+
+void
+nsTreeBodyFrame::ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection)
+{
+  MOZ_ASSERT(aScrollbar != nullptr); 
+  int32_t newIndex = aDirection < 0 ? 0 : mTopRowIndex;
+  ScrollToRow(newIndex);
+}
+
+void
+nsTreeBodyFrame::ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection)
+{
+  MOZ_ASSERT(aScrollbar != nullptr);
+  ScrollByLines(aDirection);
+}
+
+void
+nsTreeBodyFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar)
 {
   ScrollParts parts = GetScrollParts();
-
-  if (aScrollbar == parts.mVScrollbar) {
-    if (aNewIndex > aOldIndex)
-      ScrollToRowInternal(parts, mTopRowIndex+1);
-    else if (aNewIndex < aOldIndex)
-      ScrollToRowInternal(parts, mTopRowIndex-1);
+  int32_t increment = aScrollbar->GetIncrement();
+  int32_t direction = 0;
+  if (increment < 0) {
+    direction = -1;
+  } else if (increment > 0) {
+    direction = 1;
+  }
+  bool isHorizontal = aScrollbar->IsHorizontal();
+
+  nsWeakFrame weakFrame(this);
+  if (isHorizontal) {
+    int32_t curpos = aScrollbar->MoveToNewPosition();
+    if (weakFrame.IsAlive()) {
+      ScrollHorzInternal(parts, curpos);
+    }
   } else {
-    nsresult rv = ScrollHorzInternal(parts, aNewIndex);
-    if (NS_FAILED(rv)) return rv;
+    ScrollToRowInternal(parts, mTopRowIndex+direction);
+  }
+
+  if (weakFrame.IsAlive() && mScrollbarActivity) {
+    mScrollbarActivity->ActivityOccurred();
   }
-
-  UpdateScrollbars(parts);
-
-  return NS_OK;
+  if (weakFrame.IsAlive()) {
+    UpdateScrollbars(parts);
+  }
 }
-  
-NS_IMETHODIMP
-nsTreeBodyFrame::PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex)
+
+void
+nsTreeBodyFrame::ThumbMoved(nsScrollbarFrame* aScrollbar,
+                            nscoord aOldPos,
+                            nscoord aNewPos)
 {
   ScrollParts parts = GetScrollParts();
   
-  if (aOldIndex == aNewIndex)
-    return NS_OK;
+  if (aOldPos == aNewPos)
+    return;
+
+  nsWeakFrame weakFrame(this);
 
   // Vertical Scrollbar 
   if (parts.mVScrollbar == aScrollbar) {
     nscoord rh = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
-
-    nscoord newrow = aNewIndex/rh;
+    nscoord newIndex = nsPresContext::AppUnitsToIntCSSPixels(aNewPos);
+    nscoord newrow = newIndex/rh;
     ScrollInternal(parts, newrow);
   // Horizontal Scrollbar
   } else if (parts.mHScrollbar == aScrollbar) {
-    nsresult rv = ScrollHorzInternal(parts, aNewIndex);
-    if (NS_FAILED(rv)) return rv;
+    int32_t newIndex = nsPresContext::AppUnitsToIntCSSPixels(aNewPos);
+    ScrollHorzInternal(parts, newIndex);
   }
-
-  UpdateScrollbars(parts);
-  return NS_OK;
+  if (weakFrame.IsAlive()) {
+    UpdateScrollbars(parts);
+  }
 }
 
 // The style cache.
 nsStyleContext*
 nsTreeBodyFrame::GetPseudoStyleContext(nsIAtom* aPseudoElement)
 {
   return mStyleCache.GetStyleContext(this, PresContext(), mContent,
                                      mStyleContext, aPseudoElement,
--- a/layout/xul/tree/nsTreeBodyFrame.h
+++ b/layout/xul/tree/nsTreeBodyFrame.h
@@ -129,19 +129,24 @@ public:
   // nsIReflowCallback
   virtual bool ReflowFinished() MOZ_OVERRIDE;
   virtual void ReflowCallbackCanceled() MOZ_OVERRIDE;
 
   // nsICSSPseudoComparator
   virtual bool PseudoMatches(nsCSSSelector* aSelector) MOZ_OVERRIDE;
 
   // nsIScrollbarMediator
-  NS_IMETHOD PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex) MOZ_OVERRIDE;
-  NS_IMETHOD ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex) MOZ_OVERRIDE;
-  NS_IMETHOD VisibilityChanged(bool aVisible) MOZ_OVERRIDE { Invalidate(); return NS_OK; }
+  virtual void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection) MOZ_OVERRIDE;
+  virtual void ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection) MOZ_OVERRIDE;
+  virtual void ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection) MOZ_OVERRIDE;
+  virtual void RepeatButtonScroll(nsScrollbarFrame* aScrollbar) MOZ_OVERRIDE;
+  virtual void ThumbMoved(nsScrollbarFrame* aScrollbar,
+                          nscoord aOldPos,
+                          nscoord aNewPos) MOZ_OVERRIDE;
+  virtual void VisibilityChanged(bool aVisible) MOZ_OVERRIDE { Invalidate(); }
 
   // nsIScrollbarOwner
   virtual nsIFrame* GetScrollbarBox(bool aVertical) MOZ_OVERRIDE {
     ScrollParts parts = GetScrollParts();
     return aVertical ? parts.mVScrollbar : parts.mHScrollbar;
   }
 
   // Overridden from nsIFrame to cache our pres context.