ScrollbarActivity.cpp (5315B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ScrollbarActivity.h" 8 9 #include "PresShell.h" 10 #include "mozilla/LookAndFeel.h" 11 #include "mozilla/ScrollContainerFrame.h" 12 #include "mozilla/dom/Document.h" 13 #include "mozilla/dom/Element.h" 14 #include "mozilla/dom/Event.h" 15 #include "nsContentUtils.h" 16 #include "nsIContent.h" 17 #include "nsIFrame.h" 18 #include "nsIScrollbarMediator.h" 19 #include "nsITimer.h" 20 #include "nsLayoutUtils.h" 21 #include "nsQueryFrame.h" 22 #include "nsRefreshDriver.h" 23 #include "nsScrollbarFrame.h" 24 25 namespace mozilla::layout { 26 27 using mozilla::dom::Element; 28 29 NS_IMPL_ISUPPORTS(ScrollbarActivity, nsIDOMEventListener) 30 31 static bool DisplayOnMouseMove() { 32 return LookAndFeel::GetInt(LookAndFeel::IntID::ScrollbarDisplayOnMouseMove); 33 } 34 35 void ScrollbarActivity::Destroy() { 36 StopListeningForScrollAreaEvents(); 37 CancelFadeTimer(); 38 } 39 40 void ScrollbarActivity::ActivityOccurred() { 41 ActivityStarted(); 42 ActivityStopped(); 43 } 44 45 static void SetScrollbarActive(Element* aScrollbar, bool aIsActive) { 46 if (!aScrollbar) { 47 return; 48 } 49 if (aIsActive) { 50 if (nsScrollbarFrame* sf = do_QueryFrame(aScrollbar->GetPrimaryFrame())) { 51 sf->WillBecomeActive(); 52 } 53 } 54 aScrollbar->SetBoolAttr(nsGkAtoms::active, aIsActive); 55 } 56 57 void ScrollbarActivity::ActivityStarted() { 58 const bool wasActive = IsActive(); 59 mNestedActivityCounter++; 60 if (wasActive) { 61 return; 62 } 63 CancelFadeTimer(); 64 if (mScrollbarEffectivelyVisible) { 65 return; 66 } 67 StartListeningForScrollAreaEvents(); 68 SetScrollbarActive(GetHorizontalScrollbar(), true); 69 SetScrollbarActive(GetVerticalScrollbar(), true); 70 mScrollbarEffectivelyVisible = true; 71 } 72 73 void ScrollbarActivity::ActivityStopped() { 74 if (!IsActive()) { 75 // This can happen if there was a frame reconstruction while the activity 76 // was ongoing. In this case we just do nothing. We should probably handle 77 // this case better. 78 return; 79 } 80 mNestedActivityCounter--; 81 if (IsActive()) { 82 return; 83 } 84 StartFadeTimer(); 85 } 86 87 NS_IMETHODIMP 88 ScrollbarActivity::HandleEvent(dom::Event* aEvent) { 89 if (!mScrollbarEffectivelyVisible && !DisplayOnMouseMove()) { 90 return NS_OK; 91 } 92 93 nsAutoString type; 94 aEvent->GetType(type); 95 96 auto* targetContent = 97 nsIContent::FromEventTargetOrNull(aEvent->GetOriginalTarget()); 98 if (type.EqualsLiteral("mousemove")) { 99 // Mouse motions anywhere in the scrollable frame should keep the 100 // scrollbars visible, but we have to be careful as content descendants of 101 // our scrollable content aren't necessarily scrolled by our scroll frame 102 // (if they are out of flow and their containing block is not a descendant 103 // of our scroll frame) and we don't want those to activate us. 104 nsIFrame* scrollFrame = do_QueryFrame(mScrollableFrame); 105 MOZ_ASSERT(scrollFrame); 106 ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(scrollFrame); 107 nsIFrame* targetFrame = 108 targetContent ? targetContent->GetPrimaryFrame() : nullptr; 109 if ((scrollContainerFrame && 110 scrollContainerFrame->IsRootScrollFrameOfDocument()) || 111 !targetFrame || 112 nsLayoutUtils::IsAncestorFrameCrossDocInProcess( 113 scrollFrame, targetFrame, 114 scrollFrame->PresShell()->GetRootFrame())) { 115 ActivityOccurred(); 116 } 117 return NS_OK; 118 } 119 120 return NS_OK; 121 } 122 123 void ScrollbarActivity::StartListeningForScrollAreaEvents() { 124 if (mListeningForScrollAreaEvents) { 125 return; 126 } 127 nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame); 128 scrollArea->GetContent()->AddEventListener(u"mousemove"_ns, this, true); 129 mListeningForScrollAreaEvents = true; 130 } 131 132 void ScrollbarActivity::StopListeningForScrollAreaEvents() { 133 if (!mListeningForScrollAreaEvents) { 134 return; 135 } 136 nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame); 137 scrollArea->GetContent()->RemoveEventListener(u"mousemove"_ns, this, true); 138 mListeningForScrollAreaEvents = false; 139 } 140 141 void ScrollbarActivity::CancelFadeTimer() { 142 if (mFadeTimer) { 143 mFadeTimer->Cancel(); 144 } 145 } 146 147 void ScrollbarActivity::StartFadeTimer() { 148 CancelFadeTimer(); 149 if (StaticPrefs::layout_testing_overlay_scrollbars_always_visible()) { 150 return; 151 } 152 if (!mFadeTimer) { 153 mFadeTimer = NS_NewTimer(); 154 } 155 mFadeTimer->InitWithNamedFuncCallback( 156 [](nsITimer*, void* aClosure) { 157 RefPtr<ScrollbarActivity> activity = 158 static_cast<ScrollbarActivity*>(aClosure); 159 activity->BeginFade(); 160 }, 161 this, LookAndFeel::GetInt(LookAndFeel::IntID::ScrollbarFadeBeginDelay), 162 nsITimer::TYPE_ONE_SHOT, "ScrollbarActivity::FadeBeginTimerFired"_ns); 163 } 164 165 void ScrollbarActivity::BeginFade() { 166 MOZ_ASSERT(!IsActive()); 167 mScrollbarEffectivelyVisible = false; 168 SetScrollbarActive(GetHorizontalScrollbar(), false); 169 SetScrollbarActive(GetVerticalScrollbar(), false); 170 } 171 172 Element* ScrollbarActivity::GetScrollbarContent(bool aVertical) { 173 nsIFrame* box = mScrollableFrame->GetScrollbarBox(aVertical); 174 return box ? box->GetContent()->AsElement() : nullptr; 175 } 176 177 } // namespace mozilla::layout