Backed out changeset 496886cb30a5 (bug 1867152) for bc failures on browser_user_input...
[gecko.git] / layout / generic / ScrollbarActivity.cpp
blobe9865c62f929c55cba08733f5068f227ac95ef8c
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/. */
7 #include "ScrollbarActivity.h"
8 #include "nsIScrollbarMediator.h"
9 #include "nsIContent.h"
10 #include "nsIFrame.h"
11 #include "nsContentUtils.h"
12 #include "nsITimer.h"
13 #include "nsQueryFrame.h"
14 #include "nsIScrollableFrame.h"
15 #include "PresShell.h"
16 #include "nsLayoutUtils.h"
17 #include "nsScrollbarFrame.h"
18 #include "nsRefreshDriver.h"
19 #include "mozilla/dom/Element.h"
20 #include "mozilla/dom/Event.h"
21 #include "mozilla/dom/Document.h"
22 #include "mozilla/LookAndFeel.h"
24 namespace mozilla::layout {
26 using mozilla::dom::Element;
28 NS_IMPL_ISUPPORTS(ScrollbarActivity, nsIDOMEventListener)
30 static bool DisplayOnMouseMove() {
31 return LookAndFeel::GetInt(LookAndFeel::IntID::ScrollbarDisplayOnMouseMove);
34 void ScrollbarActivity::Destroy() {
35 StopListeningForScrollbarEvents();
36 StopListeningForScrollAreaEvents();
37 CancelFadeTimer();
40 void ScrollbarActivity::ActivityOccurred() {
41 ActivityStarted();
42 ActivityStopped();
45 static void SetBooleanAttribute(Element* aElement, nsAtom* aAttribute,
46 bool aValue) {
47 if (aElement) {
48 if (aValue) {
49 aElement->SetAttr(kNameSpaceID_None, aAttribute, u"true"_ns, true);
50 } else {
51 aElement->UnsetAttr(kNameSpaceID_None, aAttribute, true);
56 void ScrollbarActivity::ActivityStarted() {
57 const bool wasActive = IsActive();
58 mNestedActivityCounter++;
59 if (wasActive) {
60 return;
62 CancelFadeTimer();
63 StartListeningForScrollbarEvents();
64 StartListeningForScrollAreaEvents();
65 SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::active, true);
66 SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::active, true);
67 mScrollbarEffectivelyVisible = true;
70 void ScrollbarActivity::ActivityStopped() {
71 if (!IsActive()) {
72 // This can happen if there was a frame reconstruction while the activity
73 // was ongoing. In this case we just do nothing. We should probably handle
74 // this case better.
75 return;
77 mNestedActivityCounter--;
78 if (IsActive()) {
79 return;
81 // Clear sticky scrollbar hover status.
82 HoveredScrollbar(nullptr);
83 StartFadeTimer();
86 NS_IMETHODIMP
87 ScrollbarActivity::HandleEvent(dom::Event* aEvent) {
88 if (!mScrollbarEffectivelyVisible && !DisplayOnMouseMove()) {
89 return NS_OK;
92 nsAutoString type;
93 aEvent->GetType(type);
95 if (type.EqualsLiteral("mousemove")) {
96 // Mouse motions anywhere in the scrollable frame should keep the
97 // scrollbars visible, but we have to be careful as content descendants of
98 // our scrollable content aren't necessarily scrolled by our scroll frame
99 // (if they are out of flow and their containing block is not a descendant
100 // of our scroll frame) and we don't want those to activate us.
101 nsIFrame* scrollFrame = do_QueryFrame(mScrollableFrame);
102 MOZ_ASSERT(scrollFrame);
103 nsIScrollableFrame* scrollableFrame = do_QueryFrame(mScrollableFrame);
104 nsCOMPtr<nsIContent> targetContent =
105 do_QueryInterface(aEvent->GetOriginalTarget());
106 nsIFrame* targetFrame =
107 targetContent ? targetContent->GetPrimaryFrame() : nullptr;
108 if ((scrollableFrame && scrollableFrame->IsRootScrollFrameOfDocument()) ||
109 !targetFrame ||
110 nsLayoutUtils::IsAncestorFrameCrossDocInProcess(
111 scrollFrame, targetFrame,
112 scrollFrame->PresShell()->GetRootFrame())) {
113 ActivityOccurred();
115 return NS_OK;
118 nsCOMPtr<nsIContent> targetContent =
119 do_QueryInterface(aEvent->GetOriginalTarget());
121 HandleEventForScrollbar(type, targetContent, GetHorizontalScrollbar(),
122 &mHScrollbarHovered);
123 HandleEventForScrollbar(type, targetContent, GetVerticalScrollbar(),
124 &mVScrollbarHovered);
126 return NS_OK;
129 void ScrollbarActivity::HandleEventForScrollbar(const nsAString& aType,
130 nsIContent* aTarget,
131 Element* aScrollbar,
132 bool* aStoredHoverState) {
133 if (!aTarget || !aScrollbar ||
134 !aTarget->IsInclusiveDescendantOf(aScrollbar)) {
135 return;
138 if (aType.EqualsLiteral("mousedown")) {
139 ActivityStarted();
140 } else if (aType.EqualsLiteral("mouseup")) {
141 ActivityStopped();
142 } else if (aType.EqualsLiteral("mouseover") ||
143 aType.EqualsLiteral("mouseout")) {
144 bool newHoveredState = aType.EqualsLiteral("mouseover");
145 if (newHoveredState && !*aStoredHoverState) {
146 ActivityStarted();
147 HoveredScrollbar(aScrollbar);
148 } else if (*aStoredHoverState && !newHoveredState) {
149 ActivityStopped();
150 // Don't call HoveredScrollbar(nullptr) here because we want the hover
151 // attribute to stick until the scrollbars are hidden.
153 *aStoredHoverState = newHoveredState;
157 void ScrollbarActivity::StartListeningForScrollbarEvents() {
158 if (mListeningForScrollbarEvents) {
159 return;
162 mHorizontalScrollbar = GetHorizontalScrollbar();
163 mVerticalScrollbar = GetVerticalScrollbar();
165 AddScrollbarEventListeners(mHorizontalScrollbar);
166 AddScrollbarEventListeners(mVerticalScrollbar);
168 mListeningForScrollbarEvents = true;
171 void ScrollbarActivity::StopListeningForScrollbarEvents() {
172 if (!mListeningForScrollbarEvents) return;
174 RemoveScrollbarEventListeners(mHorizontalScrollbar);
175 RemoveScrollbarEventListeners(mVerticalScrollbar);
177 mHorizontalScrollbar = nullptr;
178 mVerticalScrollbar = nullptr;
179 mListeningForScrollbarEvents = false;
182 void ScrollbarActivity::StartListeningForScrollAreaEvents() {
183 if (mListeningForScrollAreaEvents) {
184 return;
186 nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame);
187 scrollArea->GetContent()->AddEventListener(u"mousemove"_ns, this, true);
188 mListeningForScrollAreaEvents = true;
191 void ScrollbarActivity::StopListeningForScrollAreaEvents() {
192 if (!mListeningForScrollAreaEvents) {
193 return;
195 nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame);
196 scrollArea->GetContent()->RemoveEventListener(u"mousemove"_ns, this, true);
197 mListeningForScrollAreaEvents = false;
200 void ScrollbarActivity::AddScrollbarEventListeners(
201 dom::EventTarget* aScrollbar) {
202 if (aScrollbar) {
203 aScrollbar->AddEventListener(u"mousedown"_ns, this, true);
204 aScrollbar->AddEventListener(u"mouseup"_ns, this, true);
205 aScrollbar->AddEventListener(u"mouseover"_ns, this, true);
206 aScrollbar->AddEventListener(u"mouseout"_ns, this, true);
210 void ScrollbarActivity::RemoveScrollbarEventListeners(
211 dom::EventTarget* aScrollbar) {
212 if (aScrollbar) {
213 aScrollbar->RemoveEventListener(u"mousedown"_ns, this, true);
214 aScrollbar->RemoveEventListener(u"mouseup"_ns, this, true);
215 aScrollbar->RemoveEventListener(u"mouseover"_ns, this, true);
216 aScrollbar->RemoveEventListener(u"mouseout"_ns, this, true);
220 void ScrollbarActivity::CancelFadeTimer() {
221 if (mFadeTimer) {
222 mFadeTimer->Cancel();
226 void ScrollbarActivity::StartFadeTimer() {
227 CancelFadeTimer();
228 if (StaticPrefs::layout_testing_overlay_scrollbars_always_visible()) {
229 return;
231 if (!mFadeTimer) {
232 mFadeTimer = NS_NewTimer();
234 mFadeTimer->InitWithNamedFuncCallback(
235 [](nsITimer*, void* aClosure) {
236 RefPtr<ScrollbarActivity> activity =
237 static_cast<ScrollbarActivity*>(aClosure);
238 activity->BeginFade();
240 this, LookAndFeel::GetInt(LookAndFeel::IntID::ScrollbarFadeBeginDelay),
241 nsITimer::TYPE_ONE_SHOT, "ScrollbarActivity::FadeBeginTimerFired");
244 void ScrollbarActivity::BeginFade() {
245 MOZ_ASSERT(!IsActive());
246 mScrollbarEffectivelyVisible = false;
247 SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::active, false);
248 SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::active, false);
251 static void MaybeInvalidateScrollbarForHover(
252 Element* aScrollbarToInvalidate, Element* aScrollbarAboutToGetHover) {
253 if (aScrollbarToInvalidate) {
254 bool hasHover = aScrollbarToInvalidate->HasAttr(nsGkAtoms::hover);
255 bool willHaveHover = aScrollbarAboutToGetHover == aScrollbarToInvalidate;
256 if (hasHover != willHaveHover) {
257 if (nsIFrame* f = aScrollbarToInvalidate->GetPrimaryFrame()) {
258 f->SchedulePaint();
264 void ScrollbarActivity::HoveredScrollbar(Element* aScrollbar) {
265 Element* vertScrollbar = GetVerticalScrollbar();
266 Element* horzScrollbar = GetHorizontalScrollbar();
267 MaybeInvalidateScrollbarForHover(vertScrollbar, aScrollbar);
268 MaybeInvalidateScrollbarForHover(horzScrollbar, aScrollbar);
270 SetBooleanAttribute(horzScrollbar, nsGkAtoms::hover, false);
271 SetBooleanAttribute(vertScrollbar, nsGkAtoms::hover, false);
272 SetBooleanAttribute(aScrollbar, nsGkAtoms::hover, true);
275 Element* ScrollbarActivity::GetScrollbarContent(bool aVertical) {
276 nsIFrame* box = mScrollableFrame->GetScrollbarBox(aVertical);
277 return box ? box->GetContent()->AsElement() : nullptr;
280 } // namespace mozilla::layout