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 224153 9eabf947efc3363a1bf79aa03c3053d184510846
parent 224152 3181a38a32c0e82f86a4c47bb8c75ca54bd10408
child 224154 249dae95ea83149bdc1e82df00afbac10ee38c6b
push id583
push userbhearsum@mozilla.com
push dateMon, 24 Nov 2014 19:04:58 +0000
treeherdermozilla-release@c107e74250f4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmats
bugs957445
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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.