michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ScrollbarActivity.h" michael@0: #include "nsIScrollbarOwner.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDOMEvent.h" michael@0: #include "nsIDOMElementCSSInlineStyle.h" michael@0: #include "nsIDOMCSSStyleDeclaration.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsAString.h" michael@0: #include "nsQueryFrame.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "mozilla/LookAndFeel.h" michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: namespace mozilla { michael@0: namespace layout { michael@0: michael@0: NS_IMPL_ISUPPORTS(ScrollbarActivity, nsIDOMEventListener) michael@0: michael@0: static bool michael@0: GetForceAlwaysVisiblePref() michael@0: { michael@0: static bool sForceAlwaysVisible; michael@0: static bool sForceAlwaysVisiblePrefCached = false; michael@0: if (!sForceAlwaysVisiblePrefCached) { michael@0: Preferences::AddBoolVarCache(&sForceAlwaysVisible, michael@0: "layout.testing.overlay-scrollbars.always-visible"); michael@0: sForceAlwaysVisiblePrefCached = true; michael@0: } michael@0: return sForceAlwaysVisible; michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::QueryLookAndFeelVals() michael@0: { michael@0: // Fade animation constants michael@0: mScrollbarFadeBeginDelay = michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollbarFadeBeginDelay); michael@0: mScrollbarFadeDuration = michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollbarFadeDuration); michael@0: // Controls whether we keep the mouse move listener so we can display the michael@0: // scrollbars whenever the user moves the mouse within the scroll area. michael@0: mDisplayOnMouseMove = michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollbarDisplayOnMouseMove); michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::Destroy() michael@0: { michael@0: StopListeningForScrollbarEvents(); michael@0: StopListeningForScrollAreaEvents(); michael@0: UnregisterFromRefreshDriver(); michael@0: CancelFadeBeginTimer(); michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::ActivityOccurred() michael@0: { michael@0: ActivityStarted(); michael@0: ActivityStopped(); michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::ActivityStarted() michael@0: { michael@0: mNestedActivityCounter++; michael@0: CancelFadeBeginTimer(); michael@0: if (!SetIsFading(false)) { michael@0: return; michael@0: } michael@0: UnregisterFromRefreshDriver(); michael@0: StartListeningForScrollbarEvents(); michael@0: StartListeningForScrollAreaEvents(); michael@0: SetIsActive(true); michael@0: michael@0: NS_ASSERTION(mIsActive, "need to be active during activity"); michael@0: NS_ASSERTION(!mIsFading, "must not be fading during activity"); michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::ActivityStopped() michael@0: { michael@0: NS_ASSERTION(IsActivityOngoing(), "activity stopped while none was going on"); michael@0: NS_ASSERTION(mIsActive, "need to be active during activity"); michael@0: NS_ASSERTION(!mIsFading, "must not be fading during ongoing activity"); michael@0: michael@0: mNestedActivityCounter--; michael@0: michael@0: if (!IsActivityOngoing()) { michael@0: StartFadeBeginTimer(); michael@0: michael@0: NS_ASSERTION(mIsActive, "need to be active right after activity"); michael@0: NS_ASSERTION(!mIsFading, "must not be fading right after activity"); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ScrollbarActivity::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: if (!mDisplayOnMouseMove && !mIsActive) michael@0: return NS_OK; michael@0: michael@0: nsAutoString type; michael@0: aEvent->GetType(type); michael@0: michael@0: if (type.EqualsLiteral("mousemove")) { michael@0: // Mouse motions anywhere in the scrollable frame should keep the michael@0: // scrollbars visible. michael@0: ActivityOccurred(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr target; michael@0: aEvent->GetOriginalTarget(getter_AddRefs(target)); michael@0: nsCOMPtr targetContent = do_QueryInterface(target); michael@0: michael@0: HandleEventForScrollbar(type, targetContent, GetHorizontalScrollbar(), michael@0: &mHScrollbarHovered); michael@0: HandleEventForScrollbar(type, targetContent, GetVerticalScrollbar(), michael@0: &mVScrollbarHovered); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::WillRefresh(TimeStamp aTime) michael@0: { michael@0: NS_ASSERTION(mIsActive, "should only fade while scrollbars are visible"); michael@0: NS_ASSERTION(!IsActivityOngoing(), "why weren't we unregistered from the refresh driver when scrollbar activity started?"); michael@0: NS_ASSERTION(mIsFading, "should only animate fading during fade"); michael@0: michael@0: if (!UpdateOpacity(aTime)) { michael@0: return; michael@0: } michael@0: michael@0: if (!IsStillFading(aTime)) { michael@0: EndFade(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: ScrollbarActivity::IsStillFading(TimeStamp aTime) michael@0: { michael@0: return !mFadeBeginTime.IsNull() && (aTime - mFadeBeginTime < FadeDuration()); michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::HandleEventForScrollbar(const nsAString& aType, michael@0: nsIContent* aTarget, michael@0: nsIContent* aScrollbar, michael@0: bool* aStoredHoverState) michael@0: { michael@0: if (!aTarget || !aScrollbar || michael@0: !nsContentUtils::ContentIsDescendantOf(aTarget, aScrollbar)) michael@0: return; michael@0: michael@0: if (aType.EqualsLiteral("mousedown")) { michael@0: ActivityStarted(); michael@0: } else if (aType.EqualsLiteral("mouseup")) { michael@0: ActivityStopped(); michael@0: } else if (aType.EqualsLiteral("mouseover") || michael@0: aType.EqualsLiteral("mouseout")) { michael@0: bool newHoveredState = aType.EqualsLiteral("mouseover"); michael@0: if (newHoveredState && !*aStoredHoverState) { michael@0: ActivityStarted(); michael@0: HoveredScrollbar(aScrollbar); michael@0: } else if (*aStoredHoverState && !newHoveredState) { michael@0: ActivityStopped(); michael@0: // Don't call HoveredScrollbar(nullptr) here because we want the hover michael@0: // attribute to stick until the scrollbars are hidden. michael@0: } michael@0: *aStoredHoverState = newHoveredState; michael@0: } michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::StartListeningForScrollbarEvents() michael@0: { michael@0: if (mListeningForScrollbarEvents) michael@0: return; michael@0: michael@0: mHorizontalScrollbar = do_QueryInterface(GetHorizontalScrollbar()); michael@0: mVerticalScrollbar = do_QueryInterface(GetVerticalScrollbar()); michael@0: michael@0: AddScrollbarEventListeners(mHorizontalScrollbar); michael@0: AddScrollbarEventListeners(mVerticalScrollbar); michael@0: michael@0: mListeningForScrollbarEvents = true; michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::StopListeningForScrollbarEvents() michael@0: { michael@0: if (!mListeningForScrollbarEvents) michael@0: return; michael@0: michael@0: RemoveScrollbarEventListeners(mHorizontalScrollbar); michael@0: RemoveScrollbarEventListeners(mVerticalScrollbar); michael@0: michael@0: mHorizontalScrollbar = nullptr; michael@0: mVerticalScrollbar = nullptr; michael@0: mListeningForScrollbarEvents = false; michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::StartListeningForScrollAreaEvents() michael@0: { michael@0: if (mListeningForScrollAreaEvents) michael@0: return; michael@0: michael@0: nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame); michael@0: nsCOMPtr scrollAreaTarget michael@0: = do_QueryInterface(scrollArea->GetContent()); michael@0: if (scrollAreaTarget) { michael@0: scrollAreaTarget->AddEventListener(NS_LITERAL_STRING("mousemove"), this, michael@0: true); michael@0: } michael@0: mListeningForScrollAreaEvents = true; michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::StopListeningForScrollAreaEvents() michael@0: { michael@0: if (!mListeningForScrollAreaEvents) michael@0: return; michael@0: michael@0: nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame); michael@0: nsCOMPtr scrollAreaTarget = do_QueryInterface(scrollArea->GetContent()); michael@0: if (scrollAreaTarget) { michael@0: scrollAreaTarget->RemoveEventListener(NS_LITERAL_STRING("mousemove"), this, true); michael@0: } michael@0: mListeningForScrollAreaEvents = false; michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::AddScrollbarEventListeners(nsIDOMEventTarget* aScrollbar) michael@0: { michael@0: if (aScrollbar) { michael@0: aScrollbar->AddEventListener(NS_LITERAL_STRING("mousedown"), this, true); michael@0: aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseup"), this, true); michael@0: aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseover"), this, true); michael@0: aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseout"), this, true); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::RemoveScrollbarEventListeners(nsIDOMEventTarget* aScrollbar) michael@0: { michael@0: if (aScrollbar) { michael@0: aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, true); michael@0: aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseup"), this, true); michael@0: aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseover"), this, true); michael@0: aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseout"), this, true); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::BeginFade() michael@0: { michael@0: NS_ASSERTION(mIsActive, "can't begin fade when we're already inactive"); michael@0: NS_ASSERTION(!IsActivityOngoing(), "why wasn't the fade begin timer cancelled when scrollbar activity started?"); michael@0: NS_ASSERTION(!mIsFading, "shouldn't be fading just yet"); michael@0: michael@0: CancelFadeBeginTimer(); michael@0: mFadeBeginTime = TimeStamp::Now(); michael@0: if (!SetIsFading(true)) { michael@0: return; michael@0: } michael@0: RegisterWithRefreshDriver(); michael@0: michael@0: NS_ASSERTION(mIsActive, "only fade while scrollbars are visible"); michael@0: NS_ASSERTION(mIsFading, "should be fading now"); michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::EndFade() michael@0: { michael@0: NS_ASSERTION(mIsActive, "still need to be active at this point"); michael@0: NS_ASSERTION(!IsActivityOngoing(), "why wasn't the fade end timer cancelled when scrollbar activity started?"); michael@0: michael@0: if (!SetIsFading(false)) { michael@0: return; michael@0: } michael@0: SetIsActive(false); michael@0: UnregisterFromRefreshDriver(); michael@0: StopListeningForScrollbarEvents(); michael@0: if (!mDisplayOnMouseMove) { michael@0: StopListeningForScrollAreaEvents(); michael@0: } michael@0: michael@0: NS_ASSERTION(!mIsActive, "should have gone inactive after fade end"); michael@0: NS_ASSERTION(!mIsFading, "shouldn't be fading anymore"); michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::RegisterWithRefreshDriver() michael@0: { michael@0: nsRefreshDriver* refreshDriver = GetRefreshDriver(); michael@0: if (refreshDriver) { michael@0: refreshDriver->AddRefreshObserver(this, Flush_Style); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::UnregisterFromRefreshDriver() michael@0: { michael@0: nsRefreshDriver* refreshDriver = GetRefreshDriver(); michael@0: if (refreshDriver) { michael@0: refreshDriver->RemoveRefreshObserver(this, Flush_Style); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: SetBooleanAttribute(nsIContent* aContent, nsIAtom* aAttribute, bool aValue) michael@0: { michael@0: if (aContent) { michael@0: if (aValue) { michael@0: aContent->SetAttr(kNameSpaceID_None, aAttribute, michael@0: NS_LITERAL_STRING("true"), true); michael@0: } else { michael@0: aContent->UnsetAttr(kNameSpaceID_None, aAttribute, true); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::SetIsActive(bool aNewActive) michael@0: { michael@0: if (mIsActive == aNewActive) michael@0: return; michael@0: michael@0: mIsActive = aNewActive; michael@0: if (!mIsActive) { michael@0: // Clear sticky scrollbar hover status. michael@0: HoveredScrollbar(nullptr); michael@0: } michael@0: michael@0: SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::active, mIsActive); michael@0: SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::active, mIsActive); michael@0: } michael@0: michael@0: static void michael@0: SetOpacityOnElement(nsIContent* aContent, double aOpacity) michael@0: { michael@0: nsCOMPtr inlineStyleContent = michael@0: do_QueryInterface(aContent); michael@0: if (inlineStyleContent) { michael@0: nsCOMPtr decl; michael@0: inlineStyleContent->GetStyle(getter_AddRefs(decl)); michael@0: if (decl) { michael@0: nsAutoString str; michael@0: str.AppendFloat(aOpacity); michael@0: decl->SetProperty(NS_LITERAL_STRING("opacity"), str, EmptyString()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: ScrollbarActivity::UpdateOpacity(TimeStamp aTime) michael@0: { michael@0: double progress = (aTime - mFadeBeginTime) / FadeDuration(); michael@0: double opacity = 1.0 - std::max(0.0, std::min(1.0, progress)); michael@0: michael@0: // 'this' may be getting destroyed during SetOpacityOnElement calls. michael@0: nsWeakFrame weakFrame((do_QueryFrame(mScrollableFrame))); michael@0: SetOpacityOnElement(GetHorizontalScrollbar(), opacity); michael@0: if (!weakFrame.IsAlive()) { michael@0: return false; michael@0: } michael@0: SetOpacityOnElement(GetVerticalScrollbar(), opacity); michael@0: if (!weakFrame.IsAlive()) { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: UnsetOpacityOnElement(nsIContent* aContent) michael@0: { michael@0: nsCOMPtr inlineStyleContent = michael@0: do_QueryInterface(aContent); michael@0: if (inlineStyleContent) { michael@0: nsCOMPtr decl; michael@0: inlineStyleContent->GetStyle(getter_AddRefs(decl)); michael@0: if (decl) { michael@0: nsAutoString dummy; michael@0: decl->RemoveProperty(NS_LITERAL_STRING("opacity"), dummy); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: ScrollbarActivity::SetIsFading(bool aNewFading) michael@0: { michael@0: if (mIsFading == aNewFading) michael@0: return true; michael@0: michael@0: mIsFading = aNewFading; michael@0: if (!mIsFading) { michael@0: mFadeBeginTime = TimeStamp(); michael@0: // 'this' may be getting destroyed during UnsetOpacityOnElement calls. michael@0: nsWeakFrame weakFrame((do_QueryFrame(mScrollableFrame))); michael@0: UnsetOpacityOnElement(GetHorizontalScrollbar()); michael@0: if (!weakFrame.IsAlive()) { michael@0: return false; michael@0: } michael@0: UnsetOpacityOnElement(GetVerticalScrollbar()); michael@0: if (!weakFrame.IsAlive()) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::StartFadeBeginTimer() michael@0: { michael@0: if (GetForceAlwaysVisiblePref()) { michael@0: return; michael@0: } michael@0: if (!mFadeBeginTimer) { michael@0: mFadeBeginTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: } michael@0: mFadeBeginTimer->InitWithFuncCallback(FadeBeginTimerFired, this, michael@0: mScrollbarFadeBeginDelay, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::CancelFadeBeginTimer() michael@0: { michael@0: if (mFadeBeginTimer) { michael@0: mFadeBeginTimer->Cancel(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ScrollbarActivity::HoveredScrollbar(nsIContent* aScrollbar) michael@0: { michael@0: SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::hover, false); michael@0: SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::hover, false); michael@0: SetBooleanAttribute(aScrollbar, nsGkAtoms::hover, true); michael@0: } michael@0: michael@0: nsRefreshDriver* michael@0: ScrollbarActivity::GetRefreshDriver() michael@0: { michael@0: nsIFrame* scrollableFrame = do_QueryFrame(mScrollableFrame); michael@0: return scrollableFrame->PresContext()->RefreshDriver(); michael@0: } michael@0: michael@0: nsIContent* michael@0: ScrollbarActivity::GetScrollbarContent(bool aVertical) michael@0: { michael@0: nsIFrame* box = mScrollableFrame->GetScrollbarBox(aVertical); michael@0: return box ? box->GetContent() : nullptr; michael@0: } michael@0: michael@0: } // namespace layout michael@0: } // namespace mozilla