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"
11 #include "nsContentUtils.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();
40 void ScrollbarActivity::ActivityOccurred() {
45 static void SetBooleanAttribute(Element
* aElement
, nsAtom
* aAttribute
,
49 aElement
->SetAttr(kNameSpaceID_None
, aAttribute
, u
"true"_ns
, true);
51 aElement
->UnsetAttr(kNameSpaceID_None
, aAttribute
, true);
56 void ScrollbarActivity::ActivityStarted() {
57 const bool wasActive
= IsActive();
58 mNestedActivityCounter
++;
63 StartListeningForScrollbarEvents();
64 StartListeningForScrollAreaEvents();
65 SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::active
, true);
66 SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::active
, true);
67 mScrollbarEffectivelyVisible
= true;
70 void ScrollbarActivity::ActivityStopped() {
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
77 mNestedActivityCounter
--;
81 // Clear sticky scrollbar hover status.
82 HoveredScrollbar(nullptr);
87 ScrollbarActivity::HandleEvent(dom::Event
* aEvent
) {
88 if (!mScrollbarEffectivelyVisible
&& !DisplayOnMouseMove()) {
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()) ||
110 nsLayoutUtils::IsAncestorFrameCrossDocInProcess(
111 scrollFrame
, targetFrame
,
112 scrollFrame
->PresShell()->GetRootFrame())) {
118 nsCOMPtr
<nsIContent
> targetContent
=
119 do_QueryInterface(aEvent
->GetOriginalTarget());
121 HandleEventForScrollbar(type
, targetContent
, GetHorizontalScrollbar(),
122 &mHScrollbarHovered
);
123 HandleEventForScrollbar(type
, targetContent
, GetVerticalScrollbar(),
124 &mVScrollbarHovered
);
129 void ScrollbarActivity::HandleEventForScrollbar(const nsAString
& aType
,
132 bool* aStoredHoverState
) {
133 if (!aTarget
|| !aScrollbar
||
134 !aTarget
->IsInclusiveDescendantOf(aScrollbar
)) {
138 if (aType
.EqualsLiteral("mousedown")) {
140 } else if (aType
.EqualsLiteral("mouseup")) {
142 } else if (aType
.EqualsLiteral("mouseover") ||
143 aType
.EqualsLiteral("mouseout")) {
144 bool newHoveredState
= aType
.EqualsLiteral("mouseover");
145 if (newHoveredState
&& !*aStoredHoverState
) {
147 HoveredScrollbar(aScrollbar
);
148 } else if (*aStoredHoverState
&& !newHoveredState
) {
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
) {
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
) {
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
) {
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
) {
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
) {
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() {
222 mFadeTimer
->Cancel();
226 void ScrollbarActivity::StartFadeTimer() {
228 if (StaticPrefs::layout_testing_overlay_scrollbars_always_visible()) {
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()) {
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