Bug 778810 - Show/Hide scrollbars depending on activity. r=roc
authorVivien Nicolas <21@vingtetun.org>
Fri, 03 Aug 2012 12:35:14 +0200
changeset 101299 c449b548784e7b445ac4849bab25698307e9d2d7
parent 101298 f687cf5c1bb522969eb48a3f1b924724e2bc91a2
child 101300 2d8bd0de9362422bfd80ff514eaee36d1f603073
push id12947
push uservnicolas@mozilla.com
push dateFri, 03 Aug 2012 10:36:12 +0000
treeherdermozilla-inbound@c449b548784e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs778810
milestone17.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 778810 - Show/Hide scrollbars depending on activity. r=roc
b2g/app/b2g.js
layout/generic/Makefile.in
layout/generic/ScrollbarActivity.cpp
layout/generic/ScrollbarActivity.h
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
widget/LookAndFeel.h
widget/xpwidgets/nsXPLookAndFeel.cpp
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -476,8 +476,11 @@ pref("dom.disable_window_showModalDialog
 // Turns on gralloc-based direct texturing for Gonk
 pref("gfx.gralloc.enabled", false);
 
 // XXXX REMOVE FOR PRODUCTION. Turns on GC and CC logging 
 pref("javascript.options.mem.log", true);
 
 // Increase mark slice time from 10ms to 30ms
 pref("javascript.options.mem.gc_incremental_slice_ms", 30);
+
+// Show/Hide scrollbars when active/inactive
+pref("ui.showHideScrollbars", 1);
--- a/layout/generic/Makefile.in
+++ b/layout/generic/Makefile.in
@@ -27,16 +27,17 @@ EXPORTS		= \
 		nsHTMLReflowState.h \
 		nsIAnonymousContentCreator.h \
 		nsIFrame.h \
 		nsIFrameUtil.h \
 		nsILineIterator.h \
 		nsIObjectFrame.h \
 		nsIPageSequenceFrame.h \
 		nsIScrollableFrame.h \
+		ScrollbarActivity.h \
 		nsIStatefulFrame.h \
 		nsFrameSelection.h \
 		nsSubDocumentFrame.h \
 		Selection.h \
 		nsObjectFrame.h \
 		$(NULL)
 
 EXPORTS_NAMESPACES = mozilla/layout mozilla
@@ -77,16 +78,17 @@ CPPSRCS		= \
 		nsIntervalSet.cpp \
 		nsLeafFrame.cpp \
 		nsLineBox.cpp \
 		nsLineLayout.cpp \
 		nsObjectFrame.cpp \
 		nsPageContentFrame.cpp \
 		nsPageFrame.cpp \
 		nsPlaceholderFrame.cpp \
+		ScrollbarActivity.cpp \
 		nsSelection.cpp \
 		nsSimplePageSequence.cpp \
 		nsSplittableFrame.cpp \
 		nsSubDocumentFrame.cpp \
 		nsTextFrameThebes.cpp \
 		nsTextFrameUtils.cpp \
 		TextOverflow.cpp \
 		nsTextRunTransformations.cpp \
new file mode 100644
--- /dev/null
+++ b/layout/generic/ScrollbarActivity.cpp
@@ -0,0 +1,88 @@
+/* -*- 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 "nsIFrame.h"
+#include "nsIScrollableFrame.h"
+
+namespace mozilla {
+
+ScrollbarActivity::~ScrollbarActivity()
+{
+  CancelActivityFinishedTimer();
+}
+
+void
+ScrollbarActivity::ActivityOccurred()
+{
+  CancelActivityFinishedTimer();
+  StartActivityFinishedTimer();
+
+  SetIsActive(true);
+  NS_ASSERTION(mIsActive, "need to be active during activity");
+}
+
+void
+ScrollbarActivity::ActivityFinished()
+{
+  SetIsActive(false);
+  NS_ASSERTION(!mIsActive, "need to be unactive once activity is finished");
+}
+
+
+static void
+SetBooleanAttribute(nsIContent* aContent, nsIAtom* aAttribute, bool aValue)
+{
+  if (aContent) {
+    if (aValue) {
+      aContent->SetAttr(kNameSpaceID_None, aAttribute,
+                        NS_LITERAL_STRING("true"), true);
+    } else {
+      aContent->UnsetAttr(kNameSpaceID_None, aAttribute, true);
+    }
+  }
+}
+
+void
+ScrollbarActivity::SetIsActive(bool aNewActive)
+{
+  if (mIsActive == aNewActive)
+    return;
+  mIsActive = aNewActive;
+
+  SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::active, mIsActive);
+  SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::active, mIsActive);
+}
+
+void
+ScrollbarActivity::StartActivityFinishedTimer()
+{
+  NS_ASSERTION(!mActivityFinishedTimer, "timer already alive!");
+  mActivityFinishedTimer = do_CreateInstance("@mozilla.org/timer;1");
+  mActivityFinishedTimer->InitWithFuncCallback(ActivityFinishedTimerFired, this,
+                                            kScrollbarActivityFinishedDelay,
+                                            nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+ScrollbarActivity::CancelActivityFinishedTimer()
+{
+  if (mActivityFinishedTimer) {
+    mActivityFinishedTimer->Cancel();
+    mActivityFinishedTimer = nsnull;
+  }
+}
+
+nsIContent*
+ScrollbarActivity::GetScrollbarContent(bool aVertical)
+{
+  nsIFrame* box = mScrollableFrame->GetScrollbarBox(aVertical);
+  return box ? box->GetContent() : nsnull;
+
+  return nsnull;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/generic/ScrollbarActivity.h
@@ -0,0 +1,88 @@
+/* -*- 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 ScrollbarActivity_h___
+#define ScrollbarActivity_h___
+
+#include "nsCOMPtr.h"
+#include "mozilla/TimeStamp.h"
+
+class nsIContent;
+class nsITimer;
+class nsIAtom;
+class nsIScrollableFrame;
+
+namespace mozilla {
+
+/**
+ * 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.
+ *
+ * Initial scrollbar activity needs to be reported by the scrollbar frame 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.
+ */
+
+class ScrollbarActivity {
+public:
+  ScrollbarActivity(nsIScrollableFrame* aScrollableFrame)
+   : mIsActive(false)
+   , mScrollableFrame(aScrollableFrame)
+  {}
+
+  void ActivityOccurred();
+  void ActivityFinished();
+  ~ScrollbarActivity();
+
+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;
+
+  /*
+   * 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 SetIsActive(bool aNewActive);
+
+  enum { kScrollbarActivityFinishedDelay = 450 }; // milliseconds
+  static void ActivityFinishedTimerFired(nsITimer* aTimer, void* aSelf) {
+    reinterpret_cast<ScrollbarActivity*>(aSelf)->ActivityFinished();
+  }
+  void StartActivityFinishedTimer();
+  void CancelActivityFinishedTimer();
+
+  nsIContent* GetScrollbarContent(bool aVertical);
+  nsIContent* GetHorizontalScrollbar() {
+    return GetScrollbarContent(false);
+  }
+  nsIContent* GetVerticalScrollbar() {
+    return GetScrollbarContent(true);
+  }
+};
+
+} // namespace mozilla
+
+#endif /* ScrollbarActivity_h___ */
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -48,16 +48,17 @@
 #include "mozilla/dom/Element.h"
 #include "mozilla/StandardInteger.h"
 #include "mozilla/Util.h"
 #include "FrameLayerBuilder.h"
 #include "nsSMILKeySpline.h"
 #include "nsSubDocumentFrame.h"
 #include "nsSVGOuterSVGFrame.h"
 #include "mozilla/Attributes.h"
+#include "ScrollbarActivity.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 //----------------------------------------------------------------------
 
 //----------nsHTMLScrollFrame-------------------------------------------
 
@@ -1604,16 +1605,20 @@ nsGfxScrollFrameInner::nsGfxScrollFrameI
   , mVerticalOverflow(false)
   , mPostedReflowCallback(false)
   , mMayHaveDirtyFixedChildren(false)
   , mUpdateScrollbarAttributes(false)
   , mCollapsedResizer(false)
   , mShouldBuildLayer(false)
 {
   mScrollingActive = IsAlwaysActive();
+
+  if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars) != 0) {
+    mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(aOuter));
+  }
 }
 
 nsGfxScrollFrameInner::~nsGfxScrollFrameInner()
 {
   if (mActivityExpirationState.IsTracked()) {
     gScrollFrameActivityTracker->RemoveObject(this);
   }
   if (gScrollFrameActivityTracker &&
@@ -2844,16 +2849,20 @@ nsGfxScrollFrameInner::AppendAnonymousCo
   aElements.MaybeAppendElement(mVScrollbarContent);
   aElements.MaybeAppendElement(mScrollCornerContent);
   aElements.MaybeAppendElement(mResizerContent);
 }
 
 void
 nsGfxScrollFrameInner::Destroy()
 {
+  if (mScrollbarActivity) {
+    mScrollbarActivity = nsnull;
+  }
+
   // Unbind any content created in CreateAnonymousContent from the tree
   nsContentUtils::DestroyAnonymousContent(&mHScrollbarContent);
   nsContentUtils::DestroyAnonymousContent(&mVScrollbarContent);
   nsContentUtils::DestroyAnonymousContent(&mScrollCornerContent);
   nsContentUtils::DestroyAnonymousContent(&mResizerContent);
 
   if (mPostedReflowCallback) {
     mOuter->PresContext()->PresShell()->CancelReflowCallback(this);
@@ -2887,16 +2896,20 @@ nsGfxScrollFrameInner::UpdateScrollbarPo
 
 void nsGfxScrollFrameInner::CurPosAttributeChanged(nsIContent* aContent)
 {
   NS_ASSERTION(aContent, "aContent must not be null");
   NS_ASSERTION((mHScrollbarBox && mHScrollbarBox->GetContent() == aContent) ||
                (mVScrollbarBox && mVScrollbarBox->GetContent() == aContent),
                "unexpected child");
 
+  if (mScrollbarActivity) {
+    mScrollbarActivity->ActivityOccurred();
+  }
+
   // Attribute changes on the scrollbars happen in one of three ways:
   // 1) The scrollbar changed the attribute in response to some user event
   // 2) We changed the attribute in response to a ScrollPositionDidChange
   // callback from the scrolling view
   // 3) We changed the attribute to adjust the scrollbars for the start
   // of a smooth scroll operation
   //
   // In cases 2 and 3 we do not need to scroll because we're just
@@ -3812,16 +3825,20 @@ nsGfxScrollFrameInner::SetCoordAttribute
 
   nsAutoString newValue;
   newValue.AppendInt(aSize);
 
   if (aContent->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters))
     return;
 
   aContent->SetAttr(kNameSpaceID_None, aAtom, newValue, true);
+
+  if (mScrollbarActivity) {
+    mScrollbarActivity->ActivityOccurred();
+  }
 }
 
 static void
 ReduceRadii(nscoord aXBorder, nscoord aYBorder,
             nscoord& aXRadius, nscoord& aYRadius)
 {
   // In order to ensure that the inside edge of the border has no
   // curvature, we need at least one of its radii to be zero.
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -27,16 +27,20 @@ class nsPresContext;
 class nsIPresShell;
 class nsIContent;
 class nsIAtom;
 class nsIDocument;
 class nsIScrollFrameInternal;
 class nsPresState;
 struct ScrollReflowState;
 
+namespace mozilla {
+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:
@@ -107,23 +111,22 @@ public:
   public:
     NS_DECL_NSIRUNNABLE
     ScrolledAreaEvent(nsGfxScrollFrameInner *inner) : mInner(inner) {}
     void Revoke() { mInner = nullptr; }
   private:
     nsGfxScrollFrameInner *mInner;
   };
 
-  static void FinishReflowForScrollbar(nsIContent* aContent, nscoord aMinXY,
-                                       nscoord aMaxXY, nscoord aCurPosXY,
-                                       nscoord aPageIncrement,
-                                       nscoord aIncrement);
+  void FinishReflowForScrollbar(nsIContent* aContent, nscoord aMinXY,
+                                nscoord aMaxXY, nscoord aCurPosXY,
+                                nscoord aPageIncrement,
+                                nscoord aIncrement);
   static void SetScrollbarEnabled(nsIContent* aContent, nscoord aMaxPos);
-  static void SetCoordAttribute(nsIContent* aContent, nsIAtom* aAtom,
-                                nscoord aSize);
+  void SetCoordAttribute(nsIContent* aContent, nsIAtom* aAtom, nscoord aSize);
   nscoord GetCoordAttribute(nsIBox* aFrame, nsIAtom* aAtom, nscoord aDefaultValue,
                             nscoord* aRangeStart, nscoord* aRangeLength);
 
   // Update scrollbar curpos attributes to reflect current scroll position
   void UpdateScrollbarPosition();
 
   nsRect GetScrollPortRect() const { return mScrollPort; }
   nsPoint GetScrollPosition() const {
@@ -264,16 +267,17 @@ public:
   nsRevocableEventPtr<ScrolledAreaEvent> mScrolledAreaEvent;
   nsIBox* mHScrollbarBox;
   nsIBox* mVScrollbarBox;
   nsIFrame* mScrolledFrame;
   nsIBox* mScrollCornerBox;
   nsIBox* mResizerBox;
   nsContainerFrame* mOuter;
   nsRefPtr<AsyncScroll> mAsyncScroll;
+  nsAutoPtr<mozilla::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;
--- 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,
+    // 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,
     // Accessibility theme being used?
     eIntID_UseAccessibilityTheme,
--- 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.showHideScrollbars",
+    eIntID_ShowHideScrollbars,
+    false, 0 },
   { "ui.skipNavigatingDisabledMenuItem",
     eIntID_SkipNavigatingDisabledMenuItem,
     false, 0 },
   { "ui.treeOpenDelay",
     eIntID_TreeOpenDelay,
     false, 0 },
   { "ui.treeCloseDelay",
     eIntID_TreeCloseDelay,