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 130645 0c513ba7413713ceecd38e6930234cda641b37af
parent 130644 0727b84804805a770776a89f581ac12818c9124d
child 130646 42ade8c2d8b95ae12e1c5bf0b074d44fe653d8a4
push id1579
push userphilringnalda@gmail.com
push dateSat, 04 May 2013 04:38:04 +0000
treeherderfx-team@a56432a42a41 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs636564
milestone23.0a1
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,