Bug 636564 - Implement lion style scrollbars on Mac OSX 10.7+; r=roc
authorStephen Pohl <spohl.mozilla.bugs@gmail.com>
Thu, 02 May 2013 10:58:00 -0400
changeset 141580 0c513ba7413713ceecd38e6930234cda641b37af
parent 141579 0727b84804805a770776a89f581ac12818c9124d
child 141581 42ade8c2d8b95ae12e1c5bf0b074d44fe653d8a4
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs636564
milestone23.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 636564 - Implement lion style scrollbars on Mac OSX 10.7+; r=roc
content/base/src/nsGkAtomList.h
dom/tests/mochitest/general/test_offsets.xul
layout/generic/ScrollbarActivity.cpp
layout/generic/ScrollbarActivity.h
layout/generic/moz.build
layout/generic/nsFrameIdList.h
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
layout/generic/nsIScrollableFrame.h
layout/generic/nsIScrollbarOwner.h
layout/reftests/bugs/reftest.list
layout/style/nsCSSRuleProcessor.cpp
layout/style/nsMediaFeatures.cpp
layout/style/test/test_media_queries.html
layout/xul/tree/nsTreeBodyFrame.cpp
layout/xul/tree/nsTreeBodyFrame.h
toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js
toolkit/content/tests/chrome/test_hiddenpaging.xul
toolkit/content/tests/chrome/test_menulist_keynav.xul
toolkit/content/tests/chrome/test_scrollbar.xul
toolkit/content/xul.css
toolkit/themes/osx/global/nativescrollbars.css
toolkit/themes/osx/global/resizer.css
widget/LookAndFeel.h
widget/cocoa/nsChildView.mm
widget/cocoa/nsLookAndFeel.h
widget/cocoa/nsLookAndFeel.mm
widget/cocoa/nsNativeThemeCocoa.mm
widget/tests/test_bug485118.xul
widget/xpwidgets/nsXPLookAndFeel.cpp
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1976,16 +1976,17 @@ GK_ATOM(CriticalDisplayPort, "_critical_
 // Names for system metrics
 GK_ATOM(scrollbar_start_backward, "scrollbar-start-backward")
 GK_ATOM(scrollbar_start_forward, "scrollbar-start-forward")
 GK_ATOM(scrollbar_end_backward, "scrollbar-end-backward")
 GK_ATOM(scrollbar_end_forward, "scrollbar-end-forward")
 GK_ATOM(scrollbar_thumb_proportional, "scrollbar-thumb-proportional")
 GK_ATOM(images_in_menus, "images-in-menus")
 GK_ATOM(images_in_buttons, "images-in-buttons")
+GK_ATOM(overlay_scrollbars, "overlay-scrollbars")
 GK_ATOM(windows_default_theme, "windows-default-theme")
 GK_ATOM(mac_graphite_theme, "mac-graphite-theme")
 GK_ATOM(mac_lion_theme, "mac-lion-theme")
 GK_ATOM(windows_compositor, "windows-compositor")
 GK_ATOM(windows_glass, "windows-glass")
 GK_ATOM(touch_enabled, "touch-enabled")
 GK_ATOM(maemo_classic, "maemo-classic")
 GK_ATOM(menubar_drag, "menubar-drag")
@@ -2004,16 +2005,17 @@ GK_ATOM(windows_theme_generic, "windows-
 // And the same again, as media query keywords.
 GK_ATOM(_moz_scrollbar_start_backward, "-moz-scrollbar-start-backward")
 GK_ATOM(_moz_scrollbar_start_forward, "-moz-scrollbar-start-forward")
 GK_ATOM(_moz_scrollbar_end_backward, "-moz-scrollbar-end-backward")
 GK_ATOM(_moz_scrollbar_end_forward, "-moz-scrollbar-end-forward")
 GK_ATOM(_moz_scrollbar_thumb_proportional, "-moz-scrollbar-thumb-proportional")
 GK_ATOM(_moz_images_in_menus, "-moz-images-in-menus")
 GK_ATOM(_moz_images_in_buttons, "-moz-images-in-buttons")
+GK_ATOM(_moz_overlay_scrollbars, "-moz-overlay-scrollbars")
 GK_ATOM(_moz_windows_default_theme, "-moz-windows-default-theme")
 GK_ATOM(_moz_mac_graphite_theme, "-moz-mac-graphite-theme")
 GK_ATOM(_moz_mac_lion_theme, "-moz-mac-lion-theme")
 GK_ATOM(_moz_windows_compositor, "-moz-windows-compositor")
 GK_ATOM(_moz_windows_classic, "-moz-windows-classic")
 GK_ATOM(_moz_windows_glass, "-moz-windows-glass")
 GK_ATOM(_moz_windows_theme, "-moz-windows-theme")
 GK_ATOM(_moz_touch_enabled, "-moz-touch-enabled")
--- a/dom/tests/mochitest/general/test_offsets.xul
+++ b/dom/tests/mochitest/general/test_offsets.xul
@@ -22,17 +22,17 @@
           maxwidth="66" maxheight="56">
       <label value="One" style="margin: 0"/>
       <label id="scrollchild" value="Two"/>
       <label value="Three"/>
       <label id="lastline" value="This fourth label is much longer than the others"
              style="margin: 0; padding: 0; border: 0;"/>
     </vbox>
     <vbox id="scrollbox-test">
-       <scrollbar orient="vertical" style="margin: 0; border: 0; padding: 0;"/>
+       <scrollbar orient="vertical" style="border: 0; padding: 0;"/>
     </vbox>
   </hbox>
 </vbox>
 
 <svg:svg id="svgbase" width="45" height="20" xmlns:svg="http://www.w3.org/2000/svg">
   <svg:rect id="svgrect" x="3" y="5" width="45" height="20" fill="red"/>
 </svg:svg>
 
--- a/layout/generic/ScrollbarActivity.cpp
+++ b/layout/generic/ScrollbarActivity.cpp
@@ -1,43 +1,268 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 #include "ScrollbarActivity.h"
-#include "nsContentUtils.h"
+#include "nsIScrollbarOwner.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMNSEvent.h"
+#include "nsIDOMElementCSSInlineStyle.h"
+#include "nsIDOMCSSStyleDeclaration.h"
 #include "nsIFrame.h"
-#include "nsIScrollableFrame.h"
+#include "nsContentUtils.h"
+#include "nsAString.h"
+#include "nsQueryFrame.h"
 #include "nsComponentManagerUtils.h"
 
 namespace mozilla {
+namespace layout {
 
-ScrollbarActivity::~ScrollbarActivity()
+NS_IMPL_ISUPPORTS1(ScrollbarActivity, nsIDOMEventListener)
+
+void
+ScrollbarActivity::Destroy()
 {
-  CancelActivityFinishedTimer();
+  StopListeningForEvents();
+  UnregisterFromRefreshDriver();
+  CancelFadeBeginTimer();
 }
 
 void
 ScrollbarActivity::ActivityOccurred()
 {
-  CancelActivityFinishedTimer();
-  StartActivityFinishedTimer();
+  ActivityStarted();
+  ActivityStopped();
+}
+
+void
+ScrollbarActivity::ActivityStarted()
+{
+  mNestedActivityCounter++;
+  CancelFadeBeginTimer();
+  SetIsFading(false);
+  UnregisterFromRefreshDriver();
+  StartListeningForEvents();
+  SetIsActive(true);
+
+  NS_ASSERTION(mIsActive, "need to be active during activity");
+  NS_ASSERTION(!mIsFading, "must not be fading during activity");
+  NS_ASSERTION(!mFadeBeginTimer, "fade begin timer shouldn't be running");
+}
+
+void
+ScrollbarActivity::ActivityStopped()
+{
+  NS_ASSERTION(IsActivityOngoing(), "activity stopped while none was going on");
+  NS_ASSERTION(mIsActive, "need to be active during activity");
+  NS_ASSERTION(!mIsFading, "must not be fading during ongoing activity");
+  NS_ASSERTION(!mFadeBeginTimer, "must not be waiting for fade during ongoing activity");
+
+  mNestedActivityCounter--;
+
+  if (!IsActivityOngoing()) {
+    StartFadeBeginTimer();
+
+    NS_ASSERTION(mIsActive, "need to be active right after activity");
+    NS_ASSERTION(!mIsFading, "must not be fading right after activity");
+    NS_ASSERTION(mFadeBeginTimer, "fade begin timer should be running");
+  }
+}
 
-  SetIsActive(true);
-  NS_ASSERTION(mIsActive, "need to be active during activity");
+NS_IMETHODIMP
+ScrollbarActivity::HandleEvent(nsIDOMEvent* aEvent)
+{
+  if (!mIsActive)
+    return NS_OK;
+
+  nsAutoString type;
+  aEvent->GetType(type);
+
+  if (type.EqualsLiteral("mousemove")) {
+    // Mouse motions anywhere in the scrollable frame should keep the
+    // scrollbars visible.
+    ActivityOccurred();
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIDOMEventTarget> target;
+  aEvent->GetOriginalTarget(getter_AddRefs(target));
+  nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
+
+  HandleEventForScrollbar(type, targetContent, GetHorizontalScrollbar(),
+                          &mHScrollbarHovered);
+  HandleEventForScrollbar(type, targetContent, GetVerticalScrollbar(),
+                          &mVScrollbarHovered);
+
+  return NS_OK;
+}
+
+void
+ScrollbarActivity::WillRefresh(TimeStamp aTime)
+{
+  NS_ASSERTION(mIsActive, "should only fade while scrollbars are visible");
+  NS_ASSERTION(!IsActivityOngoing(), "why weren't we unregistered from the refresh driver when scrollbar activity started?");
+  NS_ASSERTION(mIsFading, "should only animate fading during fade");
+
+  UpdateOpacity(aTime);
+
+  if (!IsStillFading(aTime)) {
+    EndFade();
+  }
+}
+
+bool
+ScrollbarActivity::IsStillFading(TimeStamp aTime)
+{
+  return !mFadeBeginTime.IsNull() && (aTime - mFadeBeginTime < FadeDuration());
 }
 
 void
-ScrollbarActivity::ActivityFinished()
+ScrollbarActivity::HandleEventForScrollbar(const nsAString& aType,
+                                           nsIContent* aTarget,
+                                           nsIContent* aScrollbar,
+                                           bool* aStoredHoverState)
 {
-  SetIsActive(false);
-  NS_ASSERTION(!mIsActive, "need to be unactive once activity is finished");
+  if (!aTarget || !aScrollbar ||
+      !nsContentUtils::ContentIsDescendantOf(aTarget, aScrollbar))
+    return;
+
+  if (aType.EqualsLiteral("mousedown")) {
+    ActivityStarted();
+  } else if (aType.EqualsLiteral("mouseup")) {
+    ActivityStopped();
+  } else if (aType.EqualsLiteral("mouseover") ||
+             aType.EqualsLiteral("mouseout")) {
+    bool newHoveredState = aType.EqualsLiteral("mouseover");
+    if (newHoveredState && !*aStoredHoverState) {
+      ActivityStarted();
+      HoveredScrollbar(aScrollbar);
+    } else if (*aStoredHoverState && !newHoveredState) {
+      ActivityStopped();
+      // Don't call HoveredScrollbar(nullptr) here because we want the hover
+      // attribute to stick until the scrollbars are hidden.
+    }
+    *aStoredHoverState = newHoveredState;
+  }
+}
+
+void
+ScrollbarActivity::StartListeningForEvents()
+{
+  if (mListeningForEvents)
+    return;
+
+  nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame);
+  nsCOMPtr<nsIDOMEventTarget> scrollAreaTarget = do_QueryInterface(
+                                                   scrollArea->GetContent());
+  mHorizontalScrollbar = do_QueryInterface(GetHorizontalScrollbar());
+  mVerticalScrollbar = do_QueryInterface(GetVerticalScrollbar());
+
+  if (scrollAreaTarget) {
+    scrollAreaTarget->AddEventListener(NS_LITERAL_STRING("mousemove"), this,
+                                       true);
+  }
+  StartListeningForEventsOnScrollbar(mHorizontalScrollbar);
+  StartListeningForEventsOnScrollbar(mVerticalScrollbar);
+  mListeningForEvents = true;
+}
+
+void
+ScrollbarActivity::StartListeningForEventsOnScrollbar(nsIDOMEventTarget* aScrollbar)
+{
+  if (aScrollbar) {
+    aScrollbar->AddEventListener(NS_LITERAL_STRING("mousedown"), this, true);
+    aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseup"), this, true);
+    aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseover"), this, true);
+    aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseout"), this, true);
+  }
 }
 
+void
+ScrollbarActivity::StopListeningForEvents()
+{
+  if (!mListeningForEvents)
+    return;
+
+  nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame);
+  nsCOMPtr<nsIDOMEventTarget> scrollAreaTarget = do_QueryInterface(scrollArea->GetContent());
+
+  if (scrollAreaTarget) {
+    scrollAreaTarget->RemoveEventListener(NS_LITERAL_STRING("mousemove"), this, true);
+  }
+  StopListeningForEventsOnScrollbar(mHorizontalScrollbar);
+  StopListeningForEventsOnScrollbar(mVerticalScrollbar);
+
+  mHorizontalScrollbar = nullptr;
+  mVerticalScrollbar = nullptr;
+  mListeningForEvents = false;
+}
+
+void
+ScrollbarActivity::StopListeningForEventsOnScrollbar(nsIDOMEventTarget* aScrollbar)
+{
+  if (aScrollbar) {
+    aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, true);
+    aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseup"), this, true);
+    aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseover"), this, true);
+    aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseout"), this, true);
+  }
+}
+
+void
+ScrollbarActivity::BeginFade()
+{
+  NS_ASSERTION(mIsActive, "can't begin fade when we're already inactive");
+  NS_ASSERTION(!IsActivityOngoing(), "why wasn't the fade begin timer cancelled when scrollbar activity started?");
+  NS_ASSERTION(!mIsFading, "shouldn't be fading just yet");
+
+  CancelFadeBeginTimer();
+  mFadeBeginTime = TimeStamp::Now();
+  SetIsFading(true);
+  RegisterWithRefreshDriver();
+
+  NS_ASSERTION(mIsActive, "only fade while scrollbars are visible");
+  NS_ASSERTION(mIsFading, "should be fading now");
+}
+
+void
+ScrollbarActivity::EndFade()
+{
+  NS_ASSERTION(mIsActive, "still need to be active at this point");
+  NS_ASSERTION(!IsActivityOngoing(), "why wasn't the fade end timer cancelled when scrollbar activity started?");
+
+  SetIsFading(false);
+  SetIsActive(false);
+  UnregisterFromRefreshDriver();
+  StopListeningForEvents();
+
+  NS_ASSERTION(!mIsActive, "should have gone inactive after fade end");
+  NS_ASSERTION(!mIsFading, "shouldn't be fading anymore");
+  NS_ASSERTION(!mFadeBeginTimer, "fade begin timer shouldn't be running");
+}
+
+void
+ScrollbarActivity::RegisterWithRefreshDriver()
+{
+  nsRefreshDriver* refreshDriver = GetRefreshDriver();
+  if (refreshDriver) {
+    refreshDriver->AddRefreshObserver(this, Flush_Style);
+  }
+}
+
+void
+ScrollbarActivity::UnregisterFromRefreshDriver()
+{
+  nsRefreshDriver* refreshDriver = GetRefreshDriver();
+  if (refreshDriver) {
+    refreshDriver->RemoveRefreshObserver(this, Flush_Style);
+  }
+}
 
 static void
 SetBooleanAttribute(nsIContent* aContent, nsIAtom* aAttribute, bool aValue)
 {
   if (aContent) {
     if (aValue) {
       aContent->SetAttr(kNameSpaceID_None, aAttribute,
                         NS_LITERAL_STRING("true"), true);
@@ -47,43 +272,119 @@ SetBooleanAttribute(nsIContent* aContent
   }
 }
 
 void
 ScrollbarActivity::SetIsActive(bool aNewActive)
 {
   if (mIsActive == aNewActive)
     return;
+
   mIsActive = aNewActive;
+  if (!mIsActive) {
+    // Clear sticky scrollbar hover status.
+    HoveredScrollbar(nullptr);
+  }
 
   SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::active, mIsActive);
   SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::active, mIsActive);
 }
 
+static void
+SetOpacityOnElement(nsIContent* aContent, double aOpacity)
+{
+  nsCOMPtr<nsIDOMElementCSSInlineStyle> inlineStyleContent =
+    do_QueryInterface(aContent);
+  if (inlineStyleContent) {
+    nsCOMPtr<nsIDOMCSSStyleDeclaration> decl;
+    inlineStyleContent->GetStyle(getter_AddRefs(decl));
+    if (decl) {
+      nsAutoString str;
+      str.AppendFloat(aOpacity);
+      decl->SetProperty(NS_LITERAL_STRING("opacity"), str, EmptyString());
+    }
+  }
+}
+
 void
-ScrollbarActivity::StartActivityFinishedTimer()
+ScrollbarActivity::UpdateOpacity(TimeStamp aTime)
 {
-  NS_ASSERTION(!mActivityFinishedTimer, "timer already alive!");
-  mActivityFinishedTimer = do_CreateInstance("@mozilla.org/timer;1");
-  mActivityFinishedTimer->InitWithFuncCallback(ActivityFinishedTimerFired, this,
-                                            kScrollbarActivityFinishedDelay,
-                                            nsITimer::TYPE_ONE_SHOT);
+  double progress = (aTime - mFadeBeginTime) / FadeDuration();
+  double opacity = 1.0 - std::max(0.0, std::min(1.0, progress));
+  SetOpacityOnElement(GetHorizontalScrollbar(), opacity);
+  SetOpacityOnElement(GetVerticalScrollbar(), opacity);
+}
+
+static void
+UnsetOpacityOnElement(nsIContent* aContent)
+{
+  nsCOMPtr<nsIDOMElementCSSInlineStyle> inlineStyleContent =
+    do_QueryInterface(aContent);
+  if (inlineStyleContent) {
+    nsCOMPtr<nsIDOMCSSStyleDeclaration> decl;
+    inlineStyleContent->GetStyle(getter_AddRefs(decl));
+    if (decl) {
+      nsAutoString dummy;
+      decl->RemoveProperty(NS_LITERAL_STRING("opacity"), dummy);
+    }
+  }
 }
 
 void
-ScrollbarActivity::CancelActivityFinishedTimer()
+ScrollbarActivity::SetIsFading(bool aNewFading)
+{
+  if (mIsFading == aNewFading)
+    return;
+
+  mIsFading = aNewFading;
+  if (!mIsFading) {
+    mFadeBeginTime = TimeStamp();
+    UnsetOpacityOnElement(GetHorizontalScrollbar());
+    UnsetOpacityOnElement(GetVerticalScrollbar());
+  }
+}
+
+void
+ScrollbarActivity::StartFadeBeginTimer()
 {
-  if (mActivityFinishedTimer) {
-    mActivityFinishedTimer->Cancel();
-    mActivityFinishedTimer = nullptr;
+  NS_ASSERTION(!mFadeBeginTimer, "timer already alive!");
+  mFadeBeginTimer = do_CreateInstance("@mozilla.org/timer;1");
+  mFadeBeginTimer->InitWithFuncCallback(FadeBeginTimerFired, this,
+                                        kScrollbarFadeBeginDelay,
+                                        nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+ScrollbarActivity::CancelFadeBeginTimer()
+{
+  if (mFadeBeginTimer) {
+    mFadeBeginTimer->Cancel();
+    mFadeBeginTimer = nullptr;
   }
 }
 
+void
+ScrollbarActivity::HoveredScrollbar(nsIContent* aScrollbar)
+{
+  SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::hover, false);
+  SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::hover, false);
+  SetBooleanAttribute(aScrollbar, nsGkAtoms::hover, true);
+}
+
+nsRefreshDriver*
+ScrollbarActivity::GetRefreshDriver()
+{
+  nsIFrame* box = mScrollableFrame->GetScrollbarBox(false);
+  if (!box) {
+    box = mScrollableFrame->GetScrollbarBox(true);
+  }
+  return box ? box->PresContext()->RefreshDriver() : nullptr;
+}
+
 nsIContent*
 ScrollbarActivity::GetScrollbarContent(bool aVertical)
 {
   nsIFrame* box = mScrollableFrame->GetScrollbarBox(aVertical);
   return box ? box->GetContent() : nullptr;
-
-  return nullptr;
 }
 
+} // namespace layout
 } // namespace mozilla
--- a/layout/generic/ScrollbarActivity.h
+++ b/layout/generic/ScrollbarActivity.h
@@ -2,87 +2,143 @@
 /* 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 ScrollbarActivity_h___
 #define ScrollbarActivity_h___
 
 #include "nsCOMPtr.h"
+#include "nsIDOMEventListener.h"
 #include "mozilla/TimeStamp.h"
+#include "nsRefreshDriver.h"
 
 class nsIContent;
+class nsIScrollbarOwner;
 class nsITimer;
 class nsIAtom;
-class nsIScrollableFrame;
 
 namespace mozilla {
+namespace layout {
 
 /**
  * ScrollbarActivity
  *
- * This class manages scrollbar active state. When some activity occured
- * the 'active' attribute of the both the horizontal scrollbar and vertical
- * scrollbar is set.
- * After a small amount of time of inactivity this attribute is unset from
- * both scrollbars.
- * Some css specific rules can affect the scrollbar, like showing/hiding it
- * with a fade transition.
+ * This class manages scrollbar behavior that imitates the native Mac OS X
+ * Lion overlay scrollbar behavior: Scrollbars are only shown while "scrollbar
+ * activity" occurs, and they're hidden with a fade animation after a short
+ * delay.
  *
- * Initial scrollbar activity needs to be reported by the scrollbar frame that
+ * Scrollbar activity has these states:
+ *  - inactive:
+ *      Scrollbars are hidden.
+ *  - ongoing activity:
+ *      Scrollbars are visible and being operated on in some way, for example
+ *      because they're hovered or pressed.
+ *  - active, but waiting for fade out
+ *      Scrollbars are still completely visible but are about to fade away.
+ *  - fading out
+ *      Scrollbars are subject to a fade-out animation.
+ *
+ * Initial scrollbar activity needs to be reported by the scrollbar holder that
  * owns the ScrollbarActivity instance. This needs to happen via a call to
  * ActivityOccurred(), for example when the current scroll position or the size
  * of the scroll area changes.
  *
- * ScrollbarActivity then wait until a timeout has expired or a new call to
- * ActivityOccured() has been made. When the timeout expires ActivityFinished()
- * is call and reset the active state.
+ * As soon as scrollbars are visible, the ScrollbarActivity class manages the
+ * rest of the activity behavior: It ensures that mouse motions inside the
+ * scroll area keep the scrollbars visible, and that scrollbars don't fade away
+ * while they're being hovered / dragged. It also sets a sticky hover attribute
+ * on the most recently hovered scrollbar.
+ *
+ * ScrollbarActivity falls into hibernation after the scrollbars have faded
+ * out. It only starts acting after the next call to ActivityOccurred() /
+ * ActivityStarted().
  */
 
-class ScrollbarActivity {
+class ScrollbarActivity : public nsIDOMEventListener,
+                          public nsARefreshObserver {
 public:
-  ScrollbarActivity(nsIScrollableFrame* aScrollableFrame)
-   : mIsActive(false)
-   , mScrollableFrame(aScrollableFrame)
+  ScrollbarActivity(nsIScrollbarOwner* aScrollableFrame)
+   : mScrollableFrame(aScrollableFrame)
+   , mNestedActivityCounter(0)
+   , mIsActive(false)
+   , mIsFading(false)
+   , mListeningForEvents(false)
+   , mHScrollbarHovered(false)
+   , mVScrollbarHovered(false)
   {}
 
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMEVENTLISTENER
+
+  virtual ~ScrollbarActivity() {}
+
+  void Destroy();
+
   void ActivityOccurred();
-  void ActivityFinished();
-  ~ScrollbarActivity();
+  void ActivityStarted();
+  void ActivityStopped();
+
+  virtual void WillRefresh(TimeStamp aTime);
+
+  static void FadeBeginTimerFired(nsITimer* aTimer, void* aSelf) {
+    reinterpret_cast<ScrollbarActivity*>(aSelf)->BeginFade();
+  }
+
+  static const uint32_t kScrollbarFadeBeginDelay = 450; // milliseconds
+  static const uint32_t kScrollbarFadeDuration = 200; // milliseconds
 
 protected:
-  /*
-   * mIsActive is true once any type of activity occurent on the scrollable
-   * frame and until kScrollbarActivityFinishedDelay has expired.
-   * This does not reflect the value of the 'active' attributes on scrollbars.
-   */
-  bool mIsActive;
+
+  bool IsActivityOngoing()
+  { return mNestedActivityCounter > 0; }
+  bool IsStillFading(TimeStamp aTime);
 
-  /*
-   * Hold a reference to the scrollable frame in order to retrieve the
-   * horizontal and vertical scrollbar boxes where to set the 'active'
-   * attribute.
-   */
-  nsIScrollableFrame* mScrollableFrame;
-
-  nsCOMPtr<nsITimer> mActivityFinishedTimer;
+  void HandleEventForScrollbar(const nsAString& aType,
+                               nsIContent* aTarget,
+                               nsIContent* aScrollbar,
+                               bool* aStoredHoverState);
 
   void SetIsActive(bool aNewActive);
+  void SetIsFading(bool aNewFading);
 
-  enum { kScrollbarActivityFinishedDelay = 450 }; // milliseconds
-  static void ActivityFinishedTimerFired(nsITimer* aTimer, void* aSelf) {
-    reinterpret_cast<ScrollbarActivity*>(aSelf)->ActivityFinished();
-  }
-  void StartActivityFinishedTimer();
-  void CancelActivityFinishedTimer();
+  void BeginFade();
+  void EndFade();
+
+  void StartFadeBeginTimer();
+  void CancelFadeBeginTimer();
+  void StartListeningForEvents();
+  void StartListeningForEventsOnScrollbar(nsIDOMEventTarget* aScrollbar);
+  void StopListeningForEvents();
+  void StopListeningForEventsOnScrollbar(nsIDOMEventTarget* aScrollbar);
+  void RegisterWithRefreshDriver();
+  void UnregisterFromRefreshDriver();
+
+  void UpdateOpacity(TimeStamp aTime);
+  void HoveredScrollbar(nsIContent* aScrollbar);
 
+  nsRefreshDriver* GetRefreshDriver();
   nsIContent* GetScrollbarContent(bool aVertical);
-  nsIContent* GetHorizontalScrollbar() {
-    return GetScrollbarContent(false);
+  nsIContent* GetHorizontalScrollbar() { return GetScrollbarContent(false); }
+  nsIContent* GetVerticalScrollbar() { return GetScrollbarContent(true); }
+
+  static const TimeDuration FadeDuration() {
+    return TimeDuration::FromMilliseconds(kScrollbarFadeDuration);
   }
-  nsIContent* GetVerticalScrollbar() {
-    return GetScrollbarContent(true);
-  }
+
+  nsIScrollbarOwner* mScrollableFrame;
+  TimeStamp mFadeBeginTime;
+  nsCOMPtr<nsITimer> mFadeBeginTimer;
+  nsCOMPtr<nsIDOMEventTarget> mHorizontalScrollbar; // null while inactive
+  nsCOMPtr<nsIDOMEventTarget> mVerticalScrollbar;   // null while inactive
+  int mNestedActivityCounter;
+  bool mIsActive;
+  bool mIsFading;
+  bool mListeningForEvents;
+  bool mHScrollbarHovered;
+  bool mVScrollbarHovered;
 };
 
+} // namespace layout
 } // namespace mozilla
 
 #endif /* ScrollbarActivity_h___ */
--- a/layout/generic/moz.build
+++ b/layout/generic/moz.build
@@ -20,16 +20,17 @@ EXPORTS += [
     'nsHTMLReflowState.h',
     'nsIAnonymousContentCreator.h',
     'nsIFrame.h',
     'nsIFrameUtil.h',
     'nsILineIterator.h',
     'nsIObjectFrame.h',
     'nsIPageSequenceFrame.h',
     'nsIScrollableFrame.h',
+    'nsIScrollbarOwner.h',
     'nsIStatefulFrame.h',
     'nsObjectFrame.h',
     'nsQueryFrame.h',
     'nsSubDocumentFrame.h',
 ]
 
 EXPORTS.mozilla += [
     'Selection.h',
--- a/layout/generic/nsFrameIdList.h
+++ b/layout/generic/nsFrameIdList.h
@@ -50,16 +50,17 @@ FRAME_ID(nsIPageSequenceFrame)
 FRAME_ID(nsIPercentHeightObserver)
 FRAME_ID(nsIRootBox)
 FRAME_ID(nsISVGChildFrame)
 FRAME_ID(nsISVGGlyphFragmentLeaf)
 FRAME_ID(nsISVGGlyphFragmentNode)
 FRAME_ID(nsISVGSVGFrame)
 FRAME_ID(nsIScrollableFrame)
 FRAME_ID(nsIScrollbarMediator)
+FRAME_ID(nsIScrollbarOwner)
 FRAME_ID(nsISelectControlFrame)
 FRAME_ID(nsIStatefulFrame)
 FRAME_ID(nsITableCellLayout)
 FRAME_ID(nsITableLayout)
 FRAME_ID(nsITextControlFrame)
 FRAME_ID(nsITreeBoxObject)
 FRAME_ID(nsImageBoxFrame)
 FRAME_ID(nsImageControlFrame)
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -55,16 +55,17 @@
 #include "nsRefreshDriver.h"
 #include "nsContentList.h"
 #include <algorithm>
 #include <cstdlib> // for std::abs(int/long)
 #include <cmath> // for std::abs(float/double)
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::layout;
 
 //----------------------------------------------------------------------
 
 //----------nsHTMLScrollFrame-------------------------------------------
 
 nsIFrame*
 NS_NewHTMLScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, bool aIsRoot)
 {
@@ -220,21 +221,33 @@ GetScrollbarMetrics(nsBoxLayoutState& aS
 {
   NS_ASSERTION(aState.GetRenderingContext(),
                "Must have rendering context in layout state for size "
                "computations");
 
   if (aMin) {
     *aMin = aBox->GetMinSize(aState);
     nsBox::AddMargin(aBox, *aMin);
+    if (aMin->width < 0) {
+      aMin->width = 0;
+    }
+    if (aMin->height < 0) {
+      aMin->height = 0;
+    }
   }
 
   if (aPref) {
     *aPref = aBox->GetPrefSize(aState);
     nsBox::AddMargin(aBox, *aPref);
+    if (aPref->width < 0) {
+      aPref->width = 0;
+    }
+    if (aPref->height < 0) {
+      aPref->height = 0;
+    }
   }
 }
 
 /**
  * Assuming that we know the metrics for our wrapped frame and
  * whether the horizontal and/or vertical scrollbars are present,
  * compute the resulting layout and return true if the layout is
  * consistent. If the layout is consistent then we fill in the
@@ -863,16 +876,17 @@ nsHTMLScrollFrame::AccessibleType()
   }
 
   return a11y::eHyperTextType;
 }
 #endif
 
 NS_QUERYFRAME_HEAD(nsHTMLScrollFrame)
   NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+  NS_QUERYFRAME_ENTRY(nsIScrollbarOwner)
   NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
   NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
 
 //----------nsXULScrollFrame-------------------------------------------
 
 nsIFrame*
 NS_NewXULScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, bool aIsRoot)
@@ -1110,16 +1124,17 @@ nsXULScrollFrame::DoLayout(nsBoxLayoutSt
   aState.SetLayoutFlags(flags);
 
   nsBox::DoLayout(aState);
   return rv;
 }
 
 NS_QUERYFRAME_HEAD(nsXULScrollFrame)
   NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+  NS_QUERYFRAME_ENTRY(nsIScrollbarOwner)
   NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
   NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
 
 //-------------------- Inner ----------------------
 
 #define SMOOTH_SCROLL_PREF_NAME "general.smoothScroll"
 
@@ -1462,17 +1477,17 @@ nsGfxScrollFrameInner::nsGfxScrollFrameI
   , mMayHaveDirtyFixedChildren(false)
   , mUpdateScrollbarAttributes(false)
   , mCollapsedResizer(false)
   , mShouldBuildLayer(false)
   , mHasBeenScrolled(false)
 {
   mScrollingActive = IsAlwaysActive();
 
-  if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars) != 0) {
+  if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
     mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(aOuter));
   }
 }
 
 nsGfxScrollFrameInner::~nsGfxScrollFrameInner()
 {
   if (mActivityExpirationState.IsTracked()) {
     gScrollFrameActivityTracker->RemoveObject(this);
@@ -2816,16 +2831,17 @@ nsGfxScrollFrameInner::AppendAnonymousCo
   aElements.MaybeAppendElement(mScrollCornerContent);
   aElements.MaybeAppendElement(mResizerContent);
 }
 
 void
 nsGfxScrollFrameInner::Destroy()
 {
   if (mScrollbarActivity) {
+    mScrollbarActivity->Destroy();
     mScrollbarActivity = nullptr;
   }
 
   // Unbind any content created in CreateAnonymousContent from the tree
   nsContentUtils::DestroyAnonymousContent(&mHScrollbarContent);
   nsContentUtils::DestroyAnonymousContent(&mVScrollbarContent);
   nsContentUtils::DestroyAnonymousContent(&mScrollCornerContent);
   nsContentUtils::DestroyAnonymousContent(&mResizerContent);
@@ -3597,16 +3613,36 @@ nsGfxScrollFrameInner::AdjustScrollbarRe
     return;
 
   if (aVertical)
     aRect.height = std::max(0, resizerRect.y - aRect.y);
   else
     aRect.width = std::max(0, resizerRect.x - aRect.x);
 }
 
+static void
+AdjustOverlappingScrollbars(nsRect& aVRect, nsRect& aHRect)
+{
+  if (aVRect.IsEmpty() || aHRect.IsEmpty())
+    return;
+
+  const nsRect oldVRect = aVRect;
+  const nsRect oldHRect = aHRect;
+  if (oldVRect.Contains(oldHRect.BottomRight() - nsPoint(1, 1))) {
+    aHRect.width = std::max(0, oldVRect.x - oldHRect.x);
+  } else if (oldVRect.Contains(oldHRect.BottomLeft() - nsPoint(0, 1))) {
+    nscoord overlap = std::min(oldHRect.width, oldVRect.XMost() - oldHRect.x);
+    aHRect.x += overlap;
+    aHRect.width -= overlap;
+  }
+  if (oldHRect.Contains(oldVRect.BottomRight() - nsPoint(1, 1))) {
+    aVRect.height = std::max(0, oldHRect.y - oldVRect.y);
+  }
+}
+
 void
 nsGfxScrollFrameInner::LayoutScrollbars(nsBoxLayoutState& aState,
                                         const nsRect& aContentArea,
                                         const nsRect& aOldScrollArea)
 {
   NS_ASSERTION(!mSupppressScrollbarUpdate,
                "This should have been suppressed");
 
@@ -3639,68 +3675,75 @@ nsGfxScrollFrameInner::LayoutScrollbars(
     }
 
     if (mScrollCornerBox) {
       nsBoxFrame::LayoutChildAt(aState, mScrollCornerBox, r);
     }
 
     if (hasResizer) {
       // if a resizer is present, get its size. Assume a default size of 15 pixels.
-      nsSize resizerSize;
       nscoord defaultSize = nsPresContext::CSSPixelsToAppUnits(15);
-      resizerSize.width = mVScrollbarBox ?
+      nsSize resizerMinSize = mResizerBox->GetMinSize(aState);
+
+      nscoord vScrollbarWidth = mVScrollbarBox ?
         mVScrollbarBox->GetPrefSize(aState).width : defaultSize;
-      if (resizerSize.width > r.width) {
-        r.width = resizerSize.width;
-        if (aContentArea.x == mScrollPort.x && !scrollbarOnLeft)
-          r.x = aContentArea.XMost() - r.width;
+      r.width = std::max(std::max(r.width, vScrollbarWidth), resizerMinSize.width);
+      if (aContentArea.x == mScrollPort.x && !scrollbarOnLeft) {
+        r.x = aContentArea.XMost() - r.width;
       }
 
-      resizerSize.height = mHScrollbarBox ?
+      nscoord hScrollbarHeight = mHScrollbarBox ?
         mHScrollbarBox->GetPrefSize(aState).height : defaultSize;
-      if (resizerSize.height > r.height) {
-        r.height = resizerSize.height;
-        if (aContentArea.y == mScrollPort.y)
-          r.y = aContentArea.YMost() - r.height;
+      r.height = std::max(std::max(r.height, hScrollbarHeight), resizerMinSize.height);
+      if (aContentArea.y == mScrollPort.y) {
+        r.y = aContentArea.YMost() - r.height;
       }
 
       nsBoxFrame::LayoutChildAt(aState, mResizerBox, r);
     }
     else if (mResizerBox) {
       // otherwise lay out the resizer with an empty rectangle
       nsBoxFrame::LayoutChildAt(aState, mResizerBox, nsRect());
     }
   }
 
   nsPresContext* presContext = mScrolledFrame->PresContext();
+  nsRect vRect;
   if (mVScrollbarBox) {
     NS_PRECONDITION(mVScrollbarBox->IsBoxFrame(), "Must be a box frame!");
-    nsRect vRect(mScrollPort);
+    vRect = mScrollPort;
     vRect.width = aContentArea.width - mScrollPort.width;
     vRect.x = scrollbarOnLeft ? aContentArea.x : mScrollPort.XMost();
     if (mHasVerticalScrollbar) {
       nsMargin margin;
       mVScrollbarBox->GetMargin(margin);
       vRect.Deflate(margin);
     }
     AdjustScrollbarRectForResizer(mOuter, presContext, vRect, hasResizer, true);
-    nsBoxFrame::LayoutChildAt(aState, mVScrollbarBox, vRect);
   }
 
+  nsRect hRect;
   if (mHScrollbarBox) {
     NS_PRECONDITION(mHScrollbarBox->IsBoxFrame(), "Must be a box frame!");
-    nsRect hRect(mScrollPort);
+    hRect = mScrollPort;
     hRect.height = aContentArea.height - mScrollPort.height;
     hRect.y = true ? mScrollPort.YMost() : aContentArea.y;
     if (mHasHorizontalScrollbar) {
       nsMargin margin;
       mHScrollbarBox->GetMargin(margin);
       hRect.Deflate(margin);
     }
     AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, false);
+  }
+
+  AdjustOverlappingScrollbars(vRect, hRect);
+  if (mVScrollbarBox) {
+    nsBoxFrame::LayoutChildAt(aState, mVScrollbarBox, vRect);
+  }
+  if (mHScrollbarBox) {
     nsBoxFrame::LayoutChildAt(aState, mHScrollbarBox, hRect);
   }
 
   // may need to update fixed position children of the viewport,
   // if the client area changed size because of an incremental
   // reflow of a descendant.  (If the outer frame is dirty, the fixed
   // children will be re-laid out anyway)
   if (aOldScrollArea.Size() != mScrollPort.Size() &&
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -29,28 +29,31 @@ class nsIPresShell;
 class nsIContent;
 class nsIAtom;
 class nsIDocument;
 class nsIScrollFrameInternal;
 class nsPresState;
 struct ScrollReflowState;
 
 namespace mozilla {
+namespace layout {
 class ScrollbarActivity;
 }
+}
 
 // When set, the next scroll operation on the scrollframe will invalidate its
 // entire contents. Useful for text-overflow.
 // This bit is cleared after each time the scrollframe is scrolled. Whoever
 // needs to set it should set it again on each paint.
 #define NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL NS_FRAME_STATE_BIT(20)
 
 class nsGfxScrollFrameInner : public nsIReflowCallback {
 public:
   typedef mozilla::gfx::Point Point;
+  typedef mozilla::layout::ScrollbarActivity ScrollbarActivity;
 
   class AsyncScroll;
 
   nsGfxScrollFrameInner(nsContainerFrame* aOuter, bool aIsRoot);
   ~nsGfxScrollFrameInner();
 
   typedef nsIScrollableFrame::ScrollbarStyles ScrollbarStyles;
   ScrollbarStyles GetScrollbarStylesFromFrame() const;
@@ -276,17 +279,17 @@ public:
   nsRevocableEventPtr<ScrolledAreaEvent> mScrolledAreaEvent;
   nsIFrame* mHScrollbarBox;
   nsIFrame* mVScrollbarBox;
   nsIFrame* mScrolledFrame;
   nsIFrame* mScrollCornerBox;
   nsIFrame* mResizerBox;
   nsContainerFrame* mOuter;
   nsRefPtr<AsyncScroll> mAsyncScroll;
-  nsAutoPtr<mozilla::ScrollbarActivity> mScrollbarActivity;
+  nsCOMPtr<ScrollbarActivity> mScrollbarActivity;
   nsTArray<nsIScrollPositionListener*> mListeners;
   nsRect mScrollPort;
   // Where we're currently scrolling to, if we're scrolling asynchronously.
   // If we're not in the middle of an asynchronous scroll then this is
   // just the current scroll position. ScrollBy will choose its
   // destination based on this value.
   nsPoint mDestination;
   nsPoint mScrollPosAtLastPaint;
@@ -452,16 +455,21 @@ public:
     return pt;
   }
 
   // nsIAnonymousContentCreator
   virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) MOZ_OVERRIDE;
   virtual void AppendAnonymousContentTo(nsBaseContentList& aElements,
                                         uint32_t aFilter) MOZ_OVERRIDE;
 
+  // nsIScrollbarOwner
+  virtual nsIFrame* GetScrollbarBox(bool aVertical) MOZ_OVERRIDE {
+    return mInner.GetScrollbarBox(aVertical);
+  }
+
   // nsIScrollableFrame
   virtual nsIFrame* GetScrolledFrame() const {
     return mInner.GetScrolledFrame();
   }
   virtual nsGfxScrollFrameInner::ScrollbarStyles GetScrollbarStyles() const {
     return mInner.GetScrollbarStylesFromFrame();
   }
   virtual uint32_t GetScrollbarVisibility() const MOZ_OVERRIDE {
@@ -520,19 +528,16 @@ public:
     mInner.ScrollToRestoredPosition();
   }
   virtual void AddScrollPositionListener(nsIScrollPositionListener* aListener) MOZ_OVERRIDE {
     mInner.AddScrollPositionListener(aListener);
   }
   virtual void RemoveScrollPositionListener(nsIScrollPositionListener* aListener) MOZ_OVERRIDE {
     mInner.RemoveScrollPositionListener(aListener);
   }
-  virtual nsIFrame* GetScrollbarBox(bool aVertical) MOZ_OVERRIDE {
-    return mInner.GetScrollbarBox(aVertical);
-  }
   virtual void CurPosAttributeChanged(nsIContent* aChild) MOZ_OVERRIDE {
     mInner.CurPosAttributeChanged(aChild);
   }
   NS_IMETHOD PostScrolledAreaEventForCurrentArea() MOZ_OVERRIDE {
     mInner.PostScrolledAreaEvent();
     return NS_OK;
   }
   virtual bool IsScrollingActive() MOZ_OVERRIDE {
@@ -708,16 +713,21 @@ public:
   bool AddHorizontalScrollbar (nsBoxLayoutState& aState, bool aOnBottom);
   bool AddVerticalScrollbar   (nsBoxLayoutState& aState, bool aOnRight);
   void RemoveHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom);
   void RemoveVerticalScrollbar  (nsBoxLayoutState& aState, bool aOnRight);
 
   static void AdjustReflowStateForPrintPreview(nsBoxLayoutState& aState, bool& aSetBack);
   static void AdjustReflowStateBack(nsBoxLayoutState& aState, bool aSetBack);
 
+  // nsIScrollbarOwner
+  virtual nsIFrame* GetScrollbarBox(bool aVertical) MOZ_OVERRIDE {
+    return mInner.GetScrollbarBox(aVertical);
+  }
+
   // nsIScrollableFrame
   virtual nsIFrame* GetScrolledFrame() const {
     return mInner.GetScrolledFrame();
   }
   virtual nsGfxScrollFrameInner::ScrollbarStyles GetScrollbarStyles() const {
     return mInner.GetScrollbarStylesFromFrame();
   }
   virtual uint32_t GetScrollbarVisibility() const MOZ_OVERRIDE {
@@ -776,19 +786,16 @@ public:
     mInner.ScrollToRestoredPosition();
   }
   virtual void AddScrollPositionListener(nsIScrollPositionListener* aListener) MOZ_OVERRIDE {
     mInner.AddScrollPositionListener(aListener);
   }
   virtual void RemoveScrollPositionListener(nsIScrollPositionListener* aListener) MOZ_OVERRIDE {
     mInner.RemoveScrollPositionListener(aListener);
   }
-  virtual nsIFrame* GetScrollbarBox(bool aVertical) MOZ_OVERRIDE {
-    return mInner.GetScrollbarBox(aVertical);
-  }
   virtual void CurPosAttributeChanged(nsIContent* aChild) MOZ_OVERRIDE {
     mInner.CurPosAttributeChanged(aChild);
   }
   NS_IMETHOD PostScrolledAreaEventForCurrentArea() MOZ_OVERRIDE {
     mInner.PostScrolledAreaEvent();
     return NS_OK;
   }
   virtual bool IsScrollingActive() MOZ_OVERRIDE {
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -9,30 +9,31 @@
 
 #ifndef nsIScrollFrame_h___
 #define nsIScrollFrame_h___
 
 #include "nsISupports.h"
 #include "nsCoord.h"
 #include "nsPresContext.h"
 #include "mozilla/gfx/Point.h"
+#include "nsIScrollbarOwner.h"
 
 #define NS_DEFAULT_VERTICAL_SCROLL_DISTANCE   3
 #define NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE 5
 
 class nsBoxLayoutState;
 class nsIScrollPositionListener;
 class nsIFrame;
 
 /**
  * Interface for frames that are scrollable. This interface exposes
  * APIs for examining scroll state, observing changes to scroll state,
  * and triggering scrolling.
  */
-class nsIScrollableFrame : public nsQueryFrame {
+class nsIScrollableFrame : public nsIScrollbarOwner {
 public:
   typedef mozilla::gfx::Point Point;
 
   NS_DECL_QUERYFRAME_TARGET(nsIScrollableFrame)
 
   /**
    * Get the frame for the content that we are scrolling within
    * this scrollable frame.
@@ -198,24 +199,16 @@ public:
    */
   virtual void AddScrollPositionListener(nsIScrollPositionListener* aListener) = 0;
   /**
    * Remove a scroll position listener.
    */
   virtual void RemoveScrollPositionListener(nsIScrollPositionListener* aListener) = 0;
 
   /**
-   * Obtain the XUL box for the horizontal or vertical scrollbar, or null
-   * if there is no such box. Avoid using this, but may be useful for
-   * setting up a scrollbar mediator if you want to redirect scrollbar
-   * input.
-   */
-  virtual nsIFrame* GetScrollbarBox(bool aVertical) = 0;
-
-  /**
    * Internal method used by scrollbars to notify their scrolling
    * container of changes.
    */
   virtual void CurPosAttributeChanged(nsIContent* aChild) = 0;
 
   /**
    * Allows the docshell to request that the scroll frame post an event
    * after being restored from history.
new file mode 100644
--- /dev/null
+++ b/layout/generic/nsIScrollbarOwner.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsIScrollbarOwner_h___
+#define nsIScrollbarOwner_h___
+
+#include "nsQueryFrame.h"
+
+class nsIDOMEventTarget;
+class nsIFrame;
+
+/**
+ * An interface that represents a frame which manages scrollbars.
+ */
+class nsIScrollbarOwner : public nsQueryFrame {
+public:
+  NS_DECL_QUERYFRAME_TARGET(nsIScrollbarOwner)
+
+  /**
+   * Obtain the frame for the horizontal or vertical scrollbar, or null
+   * if there is no such box.
+   */
+  virtual nsIFrame* GetScrollbarBox(bool aVertical) = 0;
+};
+
+#endif
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -163,28 +163,28 @@ skip-if(B2G) == 192767-21.xul 192767-31.
 skip-if(B2G) == 192767-22.xul 192767-32.xul
 skip-if(B2G) == 192767-23.xul 192767-33.xul
 skip-if(B2G) == 192767-24.xul 192767-34.xul
 skip-if(B2G) == 192767-25.xul 192767-35.xul
 skip-if(B2G) == 192767-26.xul 192767-36.xul
 skip-if(B2G) == 192767-27.xul 192767-37.xul
 != 192767-01.xul 192767-21.xul
 != 192767-02.xul 192767-22.xul
-fails-if(Android) != 192767-03.xul 192767-23.xul
+skip-if(B2G) fails-if(Android) != 192767-03.xul 192767-23.xul
 != 192767-04.xul 192767-24.xul
 != 192767-05.xul 192767-25.xul
-fails-if(Android) != 192767-06.xul 192767-26.xul
-fails-if(Android) != 192767-07.xul 192767-27.xul
+skip-if(B2G) fails-if(Android) != 192767-06.xul 192767-26.xul
+skip-if(B2G) fails-if(Android) != 192767-07.xul 192767-27.xul
 != 192767-11.xul 192767-31.xul
 != 192767-12.xul 192767-32.xul
-fails-if(Android) != 192767-13.xul 192767-33.xul
+skip-if(B2G) fails-if(Android) != 192767-13.xul 192767-33.xul
 != 192767-14.xul 192767-34.xul
 != 192767-15.xul 192767-35.xul
-fails-if(Android) != 192767-16.xul 192767-36.xul
-fails-if(Android) != 192767-17.xul 192767-37.xul
+skip-if(B2G) fails-if(Android) != 192767-16.xul 192767-36.xul
+skip-if(B2G) fails-if(Android) != 192767-17.xul 192767-37.xul
 != 200774-1.html about:blank
 == 201215-1.html 201215-1-ref.html
 == 201293-1a.html 201293-1-ref.html
 == 201293-1b.html 201293-1-ref.html
 == 201293-1c.html 201293-1-ref.html
 == 201293-1d.html 201293-1-ref.html
 == 203727.html 203727-ref.html
 == 206516-1.html 206516-1-ref.html
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -1129,16 +1129,22 @@ InitSystemMetrics()
 
   metricResult =
     LookAndFeel::GetInt(LookAndFeel::eIntID_ImagesInButtons);
   if (metricResult) {
     sSystemMetrics->AppendElement(nsGkAtoms::images_in_buttons);
   }
 
   metricResult =
+    LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars);
+  if (metricResult) {
+    sSystemMetrics->AppendElement(nsGkAtoms::overlay_scrollbars);
+  }
+
+  metricResult =
     LookAndFeel::GetInt(LookAndFeel::eIntID_MenuBarDrag);
   if (metricResult) {
     sSystemMetrics->AppendElement(nsGkAtoms::menubar_drag);
   }
 
   nsresult rv =
     LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsDefaultTheme, &metricResult);
   if (NS_SUCCEEDED(rv) && metricResult) {
--- a/layout/style/nsMediaFeatures.cpp
+++ b/layout/style/nsMediaFeatures.cpp
@@ -504,16 +504,23 @@ nsMediaFeatures::features[] = {
     {
         &nsGkAtoms::_moz_images_in_buttons,
         nsMediaFeature::eMinMaxNotAllowed,
         nsMediaFeature::eBoolInteger,
         { &nsGkAtoms::images_in_buttons },
         GetSystemMetric
     },
     {
+        &nsGkAtoms::_moz_overlay_scrollbars,
+        nsMediaFeature::eMinMaxNotAllowed,
+        nsMediaFeature::eBoolInteger,
+        { &nsGkAtoms::overlay_scrollbars },
+        GetSystemMetric
+    },
+    {
         &nsGkAtoms::_moz_windows_default_theme,
         nsMediaFeature::eMinMaxNotAllowed,
         nsMediaFeature::eBoolInteger,
         { &nsGkAtoms::windows_default_theme },
         GetSystemMetric
     },
     {
         &nsGkAtoms::_moz_mac_graphite_theme,
--- a/layout/style/test/test_media_queries.html
+++ b/layout/style/test/test_media_queries.html
@@ -556,16 +556,17 @@ function run() {
   // System metrics
   expression_should_be_parseable("-moz-scrollbar-start-backward");
   expression_should_be_parseable("-moz-scrollbar-start-forward");
   expression_should_be_parseable("-moz-scrollbar-end-backward");
   expression_should_be_parseable("-moz-scrollbar-end-forward");
   expression_should_be_parseable("-moz-scrollbar-thumb-proportional");
   expression_should_be_parseable("-moz-images-in-menus");
   expression_should_be_parseable("-moz-images-in-buttons");
+  expression_should_be_parseable("-moz-overlay-scrollbars");
   expression_should_be_parseable("-moz-windows-default-theme");
   expression_should_be_parseable("-moz-mac-graphite-theme");
   expression_should_be_parseable("-moz-mac-lion-theme");
   expression_should_be_parseable("-moz-windows-compositor");
   expression_should_be_parseable("-moz-windows-classic");
   expression_should_be_parseable("-moz-windows-glass");
   expression_should_be_parseable("-moz-touch-enabled");
   expression_should_be_parseable("-moz-maemo-classic");
@@ -573,16 +574,17 @@ function run() {
 
   expression_should_be_parseable("-moz-scrollbar-start-backward: 0");
   expression_should_be_parseable("-moz-scrollbar-start-forward: 0");
   expression_should_be_parseable("-moz-scrollbar-end-backward: 0");
   expression_should_be_parseable("-moz-scrollbar-end-forward: 0");
   expression_should_be_parseable("-moz-scrollbar-thumb-proportional: 0");
   expression_should_be_parseable("-moz-images-in-menus: 0");
   expression_should_be_parseable("-moz-images-in-buttons: 0");
+  expression_should_be_parseable("-moz-overlay-scrollbars: 0");
   expression_should_be_parseable("-moz-windows-default-theme: 0");
   expression_should_be_parseable("-moz-mac-graphite-theme: 0");
   expression_should_be_parseable("-moz-mac-lion-theme: 0");
   expression_should_be_parseable("-moz-windows-compositor: 0");
   expression_should_be_parseable("-moz-windows-classic: 0");
   expression_should_be_parseable("-moz-windows-glass: 0");
   expression_should_be_parseable("-moz-touch-enabled: 0");
   expression_should_be_parseable("-moz-maemo-classic: 0");
@@ -590,16 +592,17 @@ function run() {
 
   expression_should_be_parseable("-moz-scrollbar-start-backward: 1");
   expression_should_be_parseable("-moz-scrollbar-start-forward: 1");
   expression_should_be_parseable("-moz-scrollbar-end-backward: 1");
   expression_should_be_parseable("-moz-scrollbar-end-forward: 1");
   expression_should_be_parseable("-moz-scrollbar-thumb-proportional: 1");
   expression_should_be_parseable("-moz-images-in-menus: 1");
   expression_should_be_parseable("-moz-images-in-buttons: 1");
+  expression_should_be_parseable("-moz-overlay-scrollbars: 1");
   expression_should_be_parseable("-moz-windows-default-theme: 1");
   expression_should_be_parseable("-moz-mac-graphite-theme: 1");
   expression_should_be_parseable("-moz-mac-lion-theme: 1");
   expression_should_be_parseable("-moz-windows-compositor: 1");
   expression_should_be_parseable("-moz-windows-classic: 1");
   expression_should_be_parseable("-moz-windows-glass: 1");
   expression_should_be_parseable("-moz-touch-enabled: 1");
   expression_should_be_parseable("-moz-maemo-classic: 1");
@@ -607,16 +610,17 @@ function run() {
 
   expression_should_not_be_parseable("-moz-scrollbar-start-backward: -1");
   expression_should_not_be_parseable("-moz-scrollbar-start-forward: -1");
   expression_should_not_be_parseable("-moz-scrollbar-end-backward: -1");
   expression_should_not_be_parseable("-moz-scrollbar-end-forward: -1");
   expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: -1");
   expression_should_not_be_parseable("-moz-images-in-menus: -1");
   expression_should_not_be_parseable("-moz-images-in-buttons: -1");
+  expression_should_not_be_parseable("-moz-overlay-scrollbars: -1");
   expression_should_not_be_parseable("-moz-windows-default-theme: -1");
   expression_should_not_be_parseable("-moz-mac-graphite-theme: -1");
   expression_should_not_be_parseable("-moz-mac-lion-theme: -1");
   expression_should_not_be_parseable("-moz-windows-compositor: -1");
   expression_should_not_be_parseable("-moz-windows-classic: -1");
   expression_should_not_be_parseable("-moz-windows-glass: -1");
   expression_should_not_be_parseable("-moz-touch-enabled: -1");
   expression_should_not_be_parseable("-moz-maemo-classic: -1");
@@ -624,16 +628,17 @@ function run() {
 
   expression_should_not_be_parseable("-moz-scrollbar-start-backward: true");
   expression_should_not_be_parseable("-moz-scrollbar-start-forward: true");
   expression_should_not_be_parseable("-moz-scrollbar-end-backward: true");
   expression_should_not_be_parseable("-moz-scrollbar-end-forward: true");
   expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: true");
   expression_should_not_be_parseable("-moz-images-in-menus: true");
   expression_should_not_be_parseable("-moz-images-in-buttons: true");
+  expression_should_not_be_parseable("-moz-overlay-scrollbars: true");
   expression_should_not_be_parseable("-moz-windows-default-theme: true");
   expression_should_not_be_parseable("-moz-mac-graphite-theme: true");
   expression_should_not_be_parseable("-moz-mac-lion-theme: true");
   expression_should_not_be_parseable("-moz-windows-compositor: true");
   expression_should_not_be_parseable("-moz-windows-classic: true");
   expression_should_not_be_parseable("-moz-windows-glass: true");
   expression_should_not_be_parseable("-moz-touch-enabled: true");
   expression_should_not_be_parseable("-moz-maemo-classic: true");
--- a/layout/xul/tree/nsTreeBodyFrame.cpp
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -55,25 +55,27 @@
 #include "nsLayoutUtils.h"
 #include "nsIScrollableFrame.h"
 #include "nsEventDispatcher.h"
 #include "nsDisplayList.h"
 #include "nsTreeBoxObject.h"
 #include "nsRenderingContext.h"
 #include "nsIScriptableRegion.h"
 #include <algorithm>
+#include "ScrollbarActivity.h"
 
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
 #ifdef IBMBIDI
 #include "nsBidiUtils.h"
 #endif
 
 using namespace mozilla;
+using namespace mozilla::layout;
 
 // Enumeration function that cancels all the image requests in our cache
 static PLDHashOperator
 CancelImageRequest(const nsAString& aKey,
                    nsTreeImageCacheEntry aEntry, void* aData)
 {
 
   // If our imgIRequest object was registered with the refresh driver,
@@ -97,16 +99,17 @@ NS_NewTreeBodyFrame(nsIPresShell* aPresS
 {
   return new (aPresShell) nsTreeBodyFrame(aPresShell, aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsTreeBodyFrame)
 
 NS_QUERYFRAME_HEAD(nsTreeBodyFrame)
   NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
+  NS_QUERYFRAME_ENTRY(nsIScrollbarOwner)
   NS_QUERYFRAME_ENTRY(nsTreeBodyFrame)
 NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame)
 
 // Constructor
 nsTreeBodyFrame::nsTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 :nsLeafBoxFrame(aPresShell, aContext),
  mSlots(nullptr),
  mTopRowIndex(0),
@@ -164,16 +167,21 @@ nsTreeBodyFrame::Init(nsIContent*     aC
 
   mIndentation = GetIndentation();
   mRowHeight = GetRowHeight();
 
   mCreatedListeners.Init();
 
   mImageCache.Init(16);
   EnsureBoxObject();
+
+  if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
+    mScrollbarActivity = new ScrollbarActivity(
+                           static_cast<nsIScrollbarOwner*>(this));
+  }
 }
 
 nsSize
 nsTreeBodyFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState)
 {
   EnsureView();
 
   nsIContent* baseElement = GetBaseElement();
@@ -261,16 +269,21 @@ nsTreeBodyFrame::CalcMaxRowWidth()
 
   mStringWidth += rowMargin.left + rowMargin.right;
   return mStringWidth;
 }
 
 void
 nsTreeBodyFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
+  if (mScrollbarActivity) {
+    mScrollbarActivity->Destroy();
+    mScrollbarActivity = nullptr;
+  }
+
   mScrollEvent.Revoke();
   // Make sure we cancel any posted callbacks. 
   if (mReflowCallbackPosted) {
     PresContext()->PresShell()->CancelReflowCallback(this);
     mReflowCallbackPosted = false;
   }
 
   if (mColumns)
@@ -832,34 +845,36 @@ nsTreeBodyFrame::ScrollParts nsTreeBodyF
   return result;
 }
 
 void
 nsTreeBodyFrame::UpdateScrollbars(const ScrollParts& aParts)
 {
   nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
 
+  nsWeakFrame weakFrame(this);
+
   if (aParts.mVScrollbar) {
-    nsWeakFrame self(this);
     nsAutoString curPos;
     curPos.AppendInt(mTopRowIndex*rowHeightAsPixels);
     aParts.mVScrollbarContent->
       SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curPos, true);
-    if (!self.IsAlive()) {
-      return;
-    }
   }
 
   if (aParts.mHScrollbar) {
     nsAutoString curPos;
     curPos.AppendInt(mHorzPosition);
     aParts.mHScrollbarContent->
       SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curPos, true);
     // 'this' might be deleted here
   }
+
+  if (weakFrame.IsAlive() && mScrollbarActivity) {
+    mScrollbarActivity->ActivityOccurred();
+  }
 }
 
 void
 nsTreeBodyFrame::CheckOverflow(const ScrollParts& aParts)
 {
   bool verticalOverflowChanged = false;
   bool horizontalOverflowChanged = false;
 
@@ -952,16 +967,20 @@ nsTreeBodyFrame::InvalidateScrollbars(co
       SetAttr(kNameSpaceID_None, nsGkAtoms::pageincrement, pageStr, true);
     ENSURE_TRUE(weakFrame.IsAlive());
   
     pageStr.Truncate();
     pageStr.AppendInt(nsPresContext::CSSPixelsToAppUnits(16));
     aParts.mHScrollbarContent->
       SetAttr(kNameSpaceID_None, nsGkAtoms::increment, pageStr, true);
   }
+
+  if (mScrollbarActivity) {
+    mScrollbarActivity->ActivityOccurred();
+  }
 }
 
 // Takes client x/y in pixels, converts them to appunits, and converts into
 // values relative to this nsTreeBodyFrame frame.
 nsPoint
 nsTreeBodyFrame::AdjustClientCoordsToBoxCoordSpace(int32_t aX, int32_t aY)
 {
   nsPoint point(nsPresContext::CSSPixelsToAppUnits(aX),
--- a/layout/xul/tree/nsTreeBodyFrame.h
+++ b/layout/xul/tree/nsTreeBodyFrame.h
@@ -19,20 +19,27 @@
 #include "nsTreeColumns.h"
 #include "nsAutoPtr.h"
 #include "nsDataHashtable.h"
 #include "imgIRequest.h"
 #include "imgINotificationObserver.h"
 #include "nsScrollbarFrame.h"
 #include "nsThreadUtils.h"
 #include "mozilla/LookAndFeel.h"
+#include "nsIScrollbarOwner.h"
 
 class nsOverflowChecker;
 class nsTreeImageListener;
 
+namespace mozilla {
+namespace layout {
+class ScrollbarActivity;
+}
+}
+
 // An entry in the tree's image cache
 struct nsTreeImageCacheEntry
 {
   nsTreeImageCacheEntry() {}
   nsTreeImageCacheEntry(imgIRequest *aRequest, imgINotificationObserver *aListener)
     : request(aRequest), listener(aListener) {}
 
   nsCOMPtr<imgIRequest> request;
@@ -40,18 +47,21 @@ struct nsTreeImageCacheEntry
 };
 
 // The actual frame that paints the cells and rows.
 class nsTreeBodyFrame MOZ_FINAL
   : public nsLeafBoxFrame
   , public nsICSSPseudoComparator
   , public nsIScrollbarMediator
   , public nsIReflowCallback
+  , public nsIScrollbarOwner
 {
 public:
+  typedef mozilla::layout::ScrollbarActivity ScrollbarActivity;
+
   nsTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
   ~nsTreeBodyFrame();
 
   NS_DECL_QUERYFRAME_TARGET(nsTreeBodyFrame)
   NS_DECL_QUERYFRAME
   NS_DECL_FRAMEARENA_HELPERS
 
   // Callback handler methods for refresh driver based animations.
@@ -121,16 +131,22 @@ public:
   // nsICSSPseudoComparator
   virtual bool PseudoMatches(nsCSSSelector* aSelector) MOZ_OVERRIDE;
 
   // nsIScrollbarMediator
   NS_IMETHOD PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex);
   NS_IMETHOD ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex) MOZ_OVERRIDE;
   NS_IMETHOD VisibilityChanged(bool aVisible) MOZ_OVERRIDE { Invalidate(); return NS_OK; }
 
+  // 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.
   virtual void Init(nsIContent*     aContent,
                     nsIFrame*       aParent,
                     nsIFrame*       aPrevInFlow) MOZ_OVERRIDE;
   virtual void DestroyFrom(nsIFrame* aDestructRoot);
 
   NS_IMETHOD GetCursor(const nsPoint& aPoint,
                        nsIFrame::Cursor& aCursor);
@@ -520,16 +536,18 @@ protected: // Data Members
       // An array used to keep track of all spring loaded folders.
       nsTArray<int32_t>        mArray;
   };
 
   Slots* mSlots;
 
   nsRevocableEventPtr<ScrollEvent> mScrollEvent;
 
+  nsCOMPtr<ScrollbarActivity> mScrollbarActivity;
+
   // The cached box object parent.
   nsCOMPtr<nsITreeBoxObject> mTreeBoxObject;
 
   // Cached column information.
   nsRefPtr<nsTreeColumns> mColumns;
 
   // The current view for this tree widget.  We get all of our row and cell data
   // from the view.
--- a/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js
+++ b/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js
@@ -75,17 +75,17 @@ function test()
     <div id="c" style="width: 100px; height: 100px; overflow-x: auto; overflow-y: hidden;"><div style="width: 200px; height: 200px;"></div></div>\
     <div id="d" style="width: 100px; height: 100px; overflow-y: auto; overflow-x: hidden;"><div style="width: 200px; height: 200px;"></div></div>\
     <select id="e" style="width: 100px; height: 100px;" multiple="multiple"><option>aaaaaaaaaaaaaaaaaaaaaaaa</option><option>a</option><option>a</option>\
     <option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option>\
     <option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option></select>\
     <select id="f" style="width: 100px; height: 100px;"><option>a</option><option>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</option><option>a</option>\
     <option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option>\
     <option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option></select>\
-    <div id="g" style="width: 99px; height: 99px; padding: 10px; border: 10px solid black; margin: 10px; overflow: auto;"><div style="width: 100px; height: 100px; margin: 10px;"></div></div>\
+    <div id="g" style="width: 99px; height: 99px; border: 10px solid black; margin: 10px; overflow: auto;"><div style="width: 100px; height: 100px;"></div></div>\
     <div id="h" style="width: 100px; height: 100px; overflow: -moz-hidden-unscrollable;"><div style="width: 200px; height: 200px;"></div></div>\
     <iframe id="iframe" style="display: none;"></iframe>\
     </body>';
   gBrowser.selectedBrowser.addEventListener("pageshow", onLoad, false);
   gBrowser.loadURI(dataUri);
 
   function onLoad() {
     gBrowser.selectedBrowser.removeEventListener("pageshow", onLoad, false);
--- a/toolkit/content/tests/chrome/test_hiddenpaging.xul
+++ b/toolkit/content/tests/chrome/test_hiddenpaging.xul
@@ -93,21 +93,23 @@ function testRichlistbox()
   is(listbox.getIndexOfFirstVisibleRow(), 0, id + ": Third page up should not have scrolled at all");
 }
 
 function testListbox()
 {
   var id = "listbox";
   var listbox = document.getElementById(id);
 
-  // Check that a scrollbar is visible by comparing the width of the listitem
-  // with the width of the listbox. This is a simple way to do this without
-  // checking the anonymous content.
-  ok(listbox.firstChild.getBoundingClientRect().width < listbox.getBoundingClientRect().width - 10,
-     id + ": Scrollbar visible");
+  if (!window.matchMedia("(-moz-overlay-scrollbars)").matches) {
+   // Check that a scrollbar is visible by comparing the width of the listitem
+   // with the width of the listbox. This is a simple way to do this without
+   // checking the anonymous content.
+   ok(listbox.firstChild.getBoundingClientRect().width < listbox.getBoundingClientRect().width - 10,
+      id + ": Scrollbar visible");
+ }
 
   var rowHeight = listbox.firstChild.getBoundingClientRect().height;
 
   listbox.focus();
   listbox.selectedIndex = 0;
   sendKey("PAGE_DOWN");
   is(listbox.selectedItem.id, id + "_item8", id + ": Page down should go to the item one visible page away");
   is(listbox.getIndexOfFirstVisibleRow(), 7, id + ": Page down should have scrolled down a visible page");
--- a/toolkit/content/tests/chrome/test_menulist_keynav.xul
+++ b/toolkit/content/tests/chrome/test_menulist_keynav.xul
@@ -83,17 +83,18 @@ function differentPressed()
   gShowPopup = true;
 
   for (let i = 0; i < 65; i++) {
     list.appendItem("Item" + i, "item" + i);
   }
   list.open = true;
   is(list.getBoundingClientRect().width, list.firstChild.getBoundingClientRect().width,
      "menu and popup width match");
-  ok(list.getBoundingClientRect().width > list.getItemAtIndex(0).getBoundingClientRect().width + 2,
+  var minScrollbarWidth = window.matchMedia("(-moz-overlay-scrollbars)").matches ? 0 : 3;
+  ok(list.getBoundingClientRect().width >= list.getItemAtIndex(0).getBoundingClientRect().width + minScrollbarWidth,
      "menuitem width accounts for scrollbar");
   list.open = false;
 
   list.menupopup.maxHeight = 100;
   list.open = true;
 
   var rowdiff = list.getItemAtIndex(1).getBoundingClientRect().top -
                 list.getItemAtIndex(0).getBoundingClientRect().top;
--- a/toolkit/content/tests/chrome/test_scrollbar.xul
+++ b/toolkit/content/tests/chrome/test_scrollbar.xul
@@ -13,17 +13,18 @@
   <body xmlns="http://www.w3.org/1999/xhtml"/>
   
   <hbox>
       <scrollbar orient="horizontal"
                  id="scroller"
                  curpos="0"
                  maxpos="600"
                  pageincrement="400"
-                 width="500"/>
+                 width="500"
+                 style="margin:0"/>
   </hbox>
   
   <!-- test code goes here -->
   <script type="application/javascript"><![CDATA[
 
 /** Test for Scrollbar **/
 var scrollbarTester = {
   scrollbar: null,
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -920,17 +920,17 @@ grippy {
   min-width: 0% ! important;
   min-height: 0% ! important;
   -moz-box-ordinal-group: 2147483646;
 }
 
 /********** scrollbar **********/
 
 /* Scrollbars are never flipped even if BiDI kicks in. */
-scrollbar {
+scrollbar[orient="horizontal"] {
   direction: ltr;
 }
 
 thumb {
   -moz-binding: url(chrome://global/content/bindings/scrollbar.xml#thumb);
   display: -moz-box !important;
 }
 
--- a/toolkit/themes/osx/global/nativescrollbars.css
+++ b/toolkit/themes/osx/global/nativescrollbars.css
@@ -12,16 +12,36 @@ scrollbar {
   background-color: white;
 }
 
 html|select[size]:not([size="0"]):not([size="1"]) > scrollbar,
 html|select[multiple] > scrollbar {
   -moz-appearance: scrollbar-small;
 }
 
+@media all and (-moz-overlay-scrollbars) {
+  scrollbar {
+    position: relative;
+    z-index: 2147483647;
+  }
+
+  scrollbar:not([active="true"]),
+  scrollbar[disabled="true"] {
+    visibility: hidden;
+  }
+
+  scrollbar[orient="vertical"] {
+    -moz-margin-start: -16px;
+  }
+
+  scrollbar[orient="horizontal"] {
+    margin-top: -16px;
+  }
+}
+
 /* ..... track ..... */
 
 slider {
   -moz-appearance: scrollbartrack-horizontal;
 }
 
 slider[orient="vertical"] {
   -moz-appearance: scrollbartrack-vertical;
--- a/toolkit/themes/osx/global/resizer.css
+++ b/toolkit/themes/osx/global/resizer.css
@@ -4,17 +4,19 @@
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 resizer {
   -moz-appearance: resizer;
   background: url("chrome://global/skin/icons/resizer.png") no-repeat;
   background-size: 100% 100%;
   cursor: se-resize;
+  min-width: 15px;
   width: 15px;
+  min-height: 15px;
   height: 15px;
 }
 @media (min-resolution: 2dppx) {
   resizer {
     background-image: url("chrome://global/skin/icons/resizer@2x.png");
     background-size: 100% 100%;
   }
 }
--- a/widget/LookAndFeel.h
+++ b/widget/LookAndFeel.h
@@ -181,16 +181,18 @@ public:
     // show the caret when text is selected?
     eIntID_ShowCaretDuringSelection,
     // select textfields when focused via tab/accesskey?
     eIntID_SelectTextfieldsOnKeyFocus,
     // delay before submenus open
     eIntID_SubmenuDelay,
     // can popups overlap menu/task bar?
     eIntID_MenusCanOverlapOSBar,
+    // should overlay scrollbars be used?
+    eIntID_UseOverlayScrollbars,
     // show/hide scrollbars based on activity
     eIntID_ShowHideScrollbars,
     // skip navigating to disabled menu item?
     eIntID_SkipNavigatingDisabledMenuItem,
     // begin a drag if the mouse is moved further than the threshold while the
     // button is down
     eIntID_DragThresholdX,
     eIntID_DragThresholdY,
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -2244,16 +2244,21 @@ NSEvent* gLastDragMouseDownEvent = nil;
   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(systemMetricsChanged)
                                                name:NSControlTintDidChangeNotification
                                              object:nil];
   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(systemMetricsChanged)
                                                name:NSSystemColorsDidChangeNotification
                                              object:nil];
+  // TODO: replace the string with the constant once we build with the 10.7 SDK
+  [[NSNotificationCenter defaultCenter] addObserver:self
+                                           selector:@selector(systemMetricsChanged)
+                                               name:@"NSPreferredScrollerStyleDidChangeNotification"
+                                             object:nil];
   [[NSDistributedNotificationCenter defaultCenter] addObserver:self
                                                       selector:@selector(systemMetricsChanged)
                                                           name:@"AppleAquaScrollBarVariantChanged"
                                                         object:nil
                                             suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately]; 
   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(_surfaceNeedsUpdate:)
                                                name:NSViewGlobalFrameDidChangeNotification
--- a/widget/cocoa/nsLookAndFeel.h
+++ b/widget/cocoa/nsLookAndFeel.h
@@ -19,16 +19,18 @@ public:
                            gfxFontStyle& aFontStyle,
                            float aDevPixPerCSSPixel);
   virtual PRUnichar GetPasswordCharacterImpl()
   {
     // unicode value for the bullet character, used for password textfields.
     return 0x2022;
   }
 
+  static bool UseOverlayScrollbars();
+
 protected:
 
   // Apple hasn't defined a constant for scollbars with two arrows on each end, so we'll use this one.
   static const int kThemeScrollBarArrowsBoth = 2;
   static const int kThemeScrollBarArrowsUpperLeft = 3;
 };
 
 #endif // nsLookAndFeel_h_
--- a/widget/cocoa/nsLookAndFeel.mm
+++ b/widget/cocoa/nsLookAndFeel.mm
@@ -10,16 +10,26 @@
 #include "nsStyleConsts.h"
 #include "gfxFont.h"
 
 #import <Cocoa/Cocoa.h>
 
 // This must be included last:
 #include "nsObjCExceptions.h"
 
+enum {
+  mozNSScrollerStyleLegacy       = 0,
+  mozNSScrollerStyleOverlay      = 1
+};
+typedef NSInteger mozNSScrollerStyle;
+
+@interface NSScroller(AvailableSinceLion)
++ (mozNSScrollerStyle)preferredScrollerStyle;
+@end
+
 nsLookAndFeel::nsLookAndFeel() : nsXPLookAndFeel()
 {
 }
 
 nsLookAndFeel::~nsLookAndFeel()
 {
 }
 
@@ -336,16 +346,19 @@ nsLookAndFeel::GetIntImpl(IntID aID, int
         } else {
           aResult = eScrollArrowStyle_BothAtBottom; // The default is BothAtBottom.
         }
       }
       break;
     case eIntID_ScrollSliderStyle:
       aResult = eScrollThumbStyle_Proportional;
       break;
+    case eIntID_UseOverlayScrollbars:
+      aResult = UseOverlayScrollbars();
+      break;
     case eIntID_TreeOpenDelay:
       aResult = 1000;
       break;
     case eIntID_TreeCloseDelay:
       aResult = 1000;
       break;
     case eIntID_TreeLazyScrollDelay:
       aResult = 150;
@@ -448,16 +461,22 @@ nsLookAndFeel::GetFloatImpl(FloatID aID,
     default:
       aResult = -1.0;
       res = NS_ERROR_FAILURE;
   }
 
   return res;
 }
 
+bool nsLookAndFeel::UseOverlayScrollbars()
+{
+  return [NSScroller respondsToSelector:@selector(preferredScrollerStyle)] &&
+         [NSScroller preferredScrollerStyle] == mozNSScrollerStyleOverlay;
+}
+
 // copied from gfxQuartzFontCache.mm, maybe should go in a Cocoa utils
 // file somewhere
 static void GetStringForNSString(const NSString *aSrc, nsAString& aDest)
 {
     aDest.SetLength([aSrc length]);
     [aSrc getCharacters:aDest.BeginWriting()];
 }
 
--- a/widget/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -21,16 +21,17 @@
 #include "nsPresContext.h"
 #include "nsGkAtoms.h"
 #include "nsCocoaFeatures.h"
 #include "nsCocoaWindow.h"
 #include "nsNativeThemeColors.h"
 #include "nsIScrollableFrame.h"
 #include "nsIDOMHTMLMeterElement.h"
 #include "mozilla/dom/Element.h"
+#include "nsLookAndFeel.h"
 
 #include "gfxContext.h"
 #include "gfxQuartzSurface.h"
 #include "gfxQuartzNativeDrawing.h"
 #include <algorithm>
 
 #define DRAW_IN_FRAME_DEBUG 0
 #define SCROLLBARS_VISUAL_DEBUG 0
@@ -2279,44 +2280,83 @@ nsNativeThemeCocoa::DrawWidgetBackground
         isVertical ||
         rangeFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
       DrawScale(cgContext, macRect, eventState, isVertical, reverseDir,
                 value, min, max, aFrame);
       break;
     }
 
     case NS_THEME_SCROLLBAR_SMALL:
-    case NS_THEME_SCROLLBAR: {
-      DrawScrollbar(cgContext, macRect, aFrame);
-    }
+    case NS_THEME_SCROLLBAR:
+      if (!nsLookAndFeel::UseOverlayScrollbars()) {
+        DrawScrollbar(cgContext, macRect, aFrame);
+      }
       break;
     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
-#if SCROLLBARS_VISUAL_DEBUG
-      CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 0, 0.6);
-      CGContextFillRect(cgContext, macRect);
-    break;
-#endif
+      if (nsLookAndFeel::UseOverlayScrollbars()) {
+        BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL);
+        BOOL isRolledOver = CheckBooleanAttr(GetParentScrollbarFrame(aFrame),
+                                             nsGkAtoms::hover);
+        if (!isRolledOver) {
+          if (isHorizontal) {
+            macRect.origin.y += 4;
+            macRect.size.height -= 4;
+          } else {
+            macRect.origin.x += 4;
+            macRect.size.width -= 4;
+          }
+        }
+        const BOOL isOnTopOfBrightBackground = YES; // TODO: detect this properly
+        CUIDraw([NSWindow coreUIRenderer], macRect, cgContext,
+                (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
+                  @"kCUIWidgetOverlayScrollBar", @"widget",
+                  @"regular", @"size",
+                  (isRolledOver ? @"rollover" : @""), @"state",
+                  (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
+                  (isOnTopOfBrightBackground ? @"" : @"kCUIVariantWhite"), @"kCUIVariantKey",
+                  [NSNumber numberWithBool:YES], @"indiconly",
+                  [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
+                  [NSNumber numberWithBool:YES], @"is.flipped",
+                  nil],
+                nil);
+      }
+      break;
     case NS_THEME_SCROLLBAR_BUTTON_UP:
     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
 #if SCROLLBARS_VISUAL_DEBUG
       CGContextSetRGBFillColor(cgContext, 1.0, 0, 0, 0.6);
       CGContextFillRect(cgContext, macRect);
+#endif
     break;
-#endif
     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
 #if SCROLLBARS_VISUAL_DEBUG
       CGContextSetRGBFillColor(cgContext, 0, 1.0, 0, 0.6);
       CGContextFillRect(cgContext, macRect);
-    break;      
 #endif
+    break;
     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
-      // do nothing, drawn by scrollbar
+      if (nsLookAndFeel::UseOverlayScrollbars() &&
+          CheckBooleanAttr(GetParentScrollbarFrame(aFrame), nsGkAtoms::hover)) {
+        BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL);
+        const BOOL isOnTopOfBrightBackground = YES; // TODO: detect this properly
+        CUIDraw([NSWindow coreUIRenderer], macRect, cgContext,
+                (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
+                  @"kCUIWidgetOverlayScrollBar", @"widget",
+                  @"regular", @"size",
+                  (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
+                  (isOnTopOfBrightBackground ? @"" : @"kCUIVariantWhite"), @"kCUIVariantKey",
+                  [NSNumber numberWithBool:YES], @"noindicator",
+                  [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
+                  [NSNumber numberWithBool:YES], @"is.flipped",
+                  nil],
+                nil);
+      }
       break;
 
     case NS_THEME_TEXTFIELD_MULTILINE: {
       // we have to draw this by hand because there is no HITheme value for it
       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
       
       CGContextFillRect(cgContext, macRect);
 
@@ -2478,37 +2518,46 @@ nsNativeThemeCocoa::GetWidgetBorder(nsDe
       ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
       aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
       break;
     }
 
     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
     {
+      bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL);
+
       // On Lion and later, scrollbars have no arrows.
       if (!nsCocoaFeatures::OnLionOrLater()) {
         // There's only an endcap to worry about when both arrows are on the bottom
         NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
         if (!buttonPlacement || [buttonPlacement isEqualToString:@"DoubleMax"]) {
-          bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL);
-
           nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
           if (!scrollbarFrame) return NS_ERROR_FAILURE;
           bool isSmall = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
 
           // There isn't a metric for this, so just hardcode a best guess at the value.
           // This value is even less exact due to the fact that the endcap is partially concave.
           int32_t endcapSize = isSmall ? 5 : 6;
 
           if (isHorizontal)
             aResult->SizeTo(0, 0, 0, endcapSize);
           else
             aResult->SizeTo(endcapSize, 0, 0, 0);
         }
       }
+
+      if (nsLookAndFeel::UseOverlayScrollbars()) {
+        if (isHorizontal) {
+          aResult->SizeTo(1, 2, 1, 1);
+        } else {
+          aResult->SizeTo(2, 1, 1, 1);
+        }
+      }
+
       break;
     }
 
     case NS_THEME_STATUSBAR:
       aResult->SizeTo(1, 0, 0, 0);
       break;
   }
 
@@ -2585,17 +2634,17 @@ nsNativeThemeCocoa::GetWidgetOverflow(ns
       aOverflowRect->Inflate(m);
       return true;
     }
   }
 
   return false;
 }
 
-static const int32_t kRegularScrollbarThumbMinSize = 22;
+static const int32_t kRegularScrollbarThumbMinSize = 26;
 static const int32_t kSmallScrollbarThumbMinSize = 19;
 
 NS_IMETHODIMP
 nsNativeThemeCocoa::GetMinimumWidgetSize(nsRenderingContext* aContext,
                                          nsIFrame* aFrame,
                                          uint8_t aWidgetType,
                                          nsIntSize* aResult,
                                          bool* aIsOverridable)
@@ -2724,25 +2773,16 @@ nsNativeThemeCocoa::GetMinimumWidgetSize
     case NS_THEME_SCALE_VERTICAL:
     {
       SInt32 scaleWidth = 0;
       ::GetThemeMetric(kThemeMetricVSliderWidth, &scaleWidth);
       aResult->SizeTo(scaleWidth, scaleWidth);
       *aIsOverridable = false;
       break;
     }
-      
-    case NS_THEME_SCROLLBAR_SMALL:
-    {
-      SInt32 scrollbarWidth = 0;
-      ::GetThemeMetric(kThemeMetricSmallScrollBarWidth, &scrollbarWidth);
-      aResult->SizeTo(scrollbarWidth, scrollbarWidth);
-      *aIsOverridable = false;
-      break;
-    }
 
     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
     {
       // Find our parent scrollbar frame in order to find out whether we're in
       // a small or a large scrollbar.
       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
       if (!scrollbarFrame)
@@ -2751,34 +2791,41 @@ nsNativeThemeCocoa::GetMinimumWidgetSize
       bool isSmall = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
       bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL);
       int32_t& minSize = isHorizontal ? aResult->width : aResult->height;
       minSize = isSmall ? kSmallScrollbarThumbMinSize : kRegularScrollbarThumbMinSize;
       break;
     }
 
     case NS_THEME_SCROLLBAR:
+    case NS_THEME_SCROLLBAR_SMALL:
     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
     {
+      *aIsOverridable = false;
+
+      if (nsLookAndFeel::UseOverlayScrollbars()) {
+        aResult->SizeTo(16, 16);
+        break;
+      }
+
       // yeah, i know i'm cheating a little here, but i figure that it
       // really doesn't matter if the scrollbar is vertical or horizontal
       // and the width metric is a really good metric for every piece
       // of the scrollbar.
 
       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
       if (!scrollbarFrame) return NS_ERROR_FAILURE;
 
       int32_t themeMetric = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
                             kThemeMetricSmallScrollBarWidth :
                             kThemeMetricScrollBarWidth;
       SInt32 scrollbarWidth = 0;
       ::GetThemeMetric(themeMetric, &scrollbarWidth);
       aResult->SizeTo(scrollbarWidth, scrollbarWidth);
-      *aIsOverridable = false;
       break;
     }
 
     case NS_THEME_SCROLLBAR_BUTTON_UP:
     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
     {
@@ -2830,18 +2877,16 @@ NS_IMETHODIMP
 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, 
                                      nsIAtom* aAttribute, bool* aShouldRepaint)
 {
   // Some widget types just never change state.
   switch (aWidgetType) {
     case NS_THEME_TOOLBOX:
     case NS_THEME_TOOLBAR:
     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
-    case NS_THEME_SCROLLBAR_TRACK_VERTICAL: 
-    case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
     case NS_THEME_STATUSBAR:
     case NS_THEME_STATUSBAR_PANEL:
     case NS_THEME_STATUSBAR_RESIZER_PANEL:
     case NS_THEME_TOOLTIP:
     case NS_THEME_TAB_PANELS:
     case NS_THEME_TAB_PANEL:
     case NS_THEME_DIALOG:
     case NS_THEME_MENUPOPUP:
@@ -2868,17 +2913,18 @@ nsNativeThemeCocoa::WidgetStateChanged(n
     *aShouldRepaint = false;
     if (aAttribute == nsGkAtoms::disabled ||
         aAttribute == nsGkAtoms::checked ||
         aAttribute == nsGkAtoms::selected ||
         aAttribute == nsGkAtoms::menuactive ||
         aAttribute == nsGkAtoms::sortDirection ||
         aAttribute == nsGkAtoms::focused ||
         aAttribute == nsGkAtoms::_default ||
-        aAttribute == nsGkAtoms::open)
+        aAttribute == nsGkAtoms::open ||
+        aAttribute == nsGkAtoms::hover)
       *aShouldRepaint = true;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNativeThemeCocoa::ThemeChanged()
@@ -2980,21 +3026,22 @@ nsNativeThemeCocoa::ThemeSupportsWidget(
     case NS_THEME_RESIZER:
     {
       nsIFrame* parentFrame = aFrame->GetParent();
       if (!parentFrame || parentFrame->GetType() != nsGkAtoms::scrollFrame)
         return true;
 
       // Note that IsWidgetStyled is not called for resizers on Mac. This is
       // because for scrollable containers, the native resizer looks better
-      // when scrollbars are present even when the style is overriden, and the
-      // custom transparent resizer looks better when scrollbars are not
-      // present.
+      // when (non-overlay) scrollbars are present even when the style is
+      // overriden, and the custom transparent resizer looks better when
+      // scrollbars are not present.
       nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
-      return (scrollFrame && scrollFrame->GetScrollbarVisibility());
+      return (!nsLookAndFeel::UseOverlayScrollbars() &&
+              scrollFrame && scrollFrame->GetScrollbarVisibility());
       break;
     }
   }
 
   return false;
 }
 
 bool
@@ -3039,16 +3086,18 @@ nsNativeThemeCocoa::GetWidgetTransparenc
 {
   switch (aWidgetType) {
   case NS_THEME_MENUPOPUP:
   case NS_THEME_TOOLTIP:
     return eTransparent;
 
   case NS_THEME_SCROLLBAR_SMALL:
   case NS_THEME_SCROLLBAR:
+    return nsLookAndFeel::UseOverlayScrollbars() ? eTransparent : eOpaque;
+
   case NS_THEME_STATUSBAR:
     // Knowing that scrollbars and statusbars are opaque improves
     // performance, because we create layers for them.
     return eOpaque;
 
   default:
     return eUnknownTransparency;
   }
--- a/widget/tests/test_bug485118.xul
+++ b/widget/tests/test_bug485118.xul
@@ -53,17 +53,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 SimpleTest.waitForExplicitFinish();
 
 function runTest() {
   ["horizontal", "vertical"].forEach(function (orient) {
     ["", "Small"].forEach(function (size) {
       var elem = document.getElementById(orient + size);
       var thumbRect = document.getAnonymousElementByAttribute(elem, 'sbattr', 'scrollbar-thumb').getBoundingClientRect();
       var sizeToCheck = orient == "horizontal" ? "width" : "height";
-      var expectedSize = size == "Small" ? 19 : 22;
+      var expectedSize = size == "Small" ? 19 : 26;
       is(thumbRect[sizeToCheck], expectedSize, size + " scrollbar has wrong minimum " + sizeToCheck);
     });
   });
   SimpleTest.finish();
 }
 window.addEventListener("load", runTest, false);
 
 ]]>
--- a/widget/xpwidgets/nsXPLookAndFeel.cpp
+++ b/widget/xpwidgets/nsXPLookAndFeel.cpp
@@ -43,16 +43,19 @@ nsLookAndFeelIntPref nsXPLookAndFeel::sI
     eIntID_DragThresholdY,
     false, 0 },
   { "ui.useAccessibilityTheme",
     eIntID_UseAccessibilityTheme,
     false, 0 },
   { "ui.menusCanOverlapOSBar",
     eIntID_MenusCanOverlapOSBar,
     false, 0 },
+  { "ui.useOverlayScrollbars",
+    eIntID_UseOverlayScrollbars,
+    false, 0 },
   { "ui.showHideScrollbars",
     eIntID_ShowHideScrollbars,
     false, 0 },
   { "ui.skipNavigatingDisabledMenuItem",
     eIntID_SkipNavigatingDisabledMenuItem,
     false, 0 },
   { "ui.treeOpenDelay",
     eIntID_TreeOpenDelay,