layout/generic/ScrollbarActivity.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:0f2e8f24c34f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "ScrollbarActivity.h"
7 #include "nsIScrollbarOwner.h"
8 #include "nsIContent.h"
9 #include "nsIDOMEvent.h"
10 #include "nsIDOMElementCSSInlineStyle.h"
11 #include "nsIDOMCSSStyleDeclaration.h"
12 #include "nsIFrame.h"
13 #include "nsContentUtils.h"
14 #include "nsAString.h"
15 #include "nsQueryFrame.h"
16 #include "nsComponentManagerUtils.h"
17 #include "mozilla/LookAndFeel.h"
18 #include "mozilla/Preferences.h"
19
20 namespace mozilla {
21 namespace layout {
22
23 NS_IMPL_ISUPPORTS(ScrollbarActivity, nsIDOMEventListener)
24
25 static bool
26 GetForceAlwaysVisiblePref()
27 {
28 static bool sForceAlwaysVisible;
29 static bool sForceAlwaysVisiblePrefCached = false;
30 if (!sForceAlwaysVisiblePrefCached) {
31 Preferences::AddBoolVarCache(&sForceAlwaysVisible,
32 "layout.testing.overlay-scrollbars.always-visible");
33 sForceAlwaysVisiblePrefCached = true;
34 }
35 return sForceAlwaysVisible;
36 }
37
38 void
39 ScrollbarActivity::QueryLookAndFeelVals()
40 {
41 // Fade animation constants
42 mScrollbarFadeBeginDelay =
43 LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollbarFadeBeginDelay);
44 mScrollbarFadeDuration =
45 LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollbarFadeDuration);
46 // Controls whether we keep the mouse move listener so we can display the
47 // scrollbars whenever the user moves the mouse within the scroll area.
48 mDisplayOnMouseMove =
49 LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollbarDisplayOnMouseMove);
50 }
51
52 void
53 ScrollbarActivity::Destroy()
54 {
55 StopListeningForScrollbarEvents();
56 StopListeningForScrollAreaEvents();
57 UnregisterFromRefreshDriver();
58 CancelFadeBeginTimer();
59 }
60
61 void
62 ScrollbarActivity::ActivityOccurred()
63 {
64 ActivityStarted();
65 ActivityStopped();
66 }
67
68 void
69 ScrollbarActivity::ActivityStarted()
70 {
71 mNestedActivityCounter++;
72 CancelFadeBeginTimer();
73 if (!SetIsFading(false)) {
74 return;
75 }
76 UnregisterFromRefreshDriver();
77 StartListeningForScrollbarEvents();
78 StartListeningForScrollAreaEvents();
79 SetIsActive(true);
80
81 NS_ASSERTION(mIsActive, "need to be active during activity");
82 NS_ASSERTION(!mIsFading, "must not be fading during activity");
83 }
84
85 void
86 ScrollbarActivity::ActivityStopped()
87 {
88 NS_ASSERTION(IsActivityOngoing(), "activity stopped while none was going on");
89 NS_ASSERTION(mIsActive, "need to be active during activity");
90 NS_ASSERTION(!mIsFading, "must not be fading during ongoing activity");
91
92 mNestedActivityCounter--;
93
94 if (!IsActivityOngoing()) {
95 StartFadeBeginTimer();
96
97 NS_ASSERTION(mIsActive, "need to be active right after activity");
98 NS_ASSERTION(!mIsFading, "must not be fading right after activity");
99 }
100 }
101
102 NS_IMETHODIMP
103 ScrollbarActivity::HandleEvent(nsIDOMEvent* aEvent)
104 {
105 if (!mDisplayOnMouseMove && !mIsActive)
106 return NS_OK;
107
108 nsAutoString type;
109 aEvent->GetType(type);
110
111 if (type.EqualsLiteral("mousemove")) {
112 // Mouse motions anywhere in the scrollable frame should keep the
113 // scrollbars visible.
114 ActivityOccurred();
115 return NS_OK;
116 }
117
118 nsCOMPtr<nsIDOMEventTarget> target;
119 aEvent->GetOriginalTarget(getter_AddRefs(target));
120 nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
121
122 HandleEventForScrollbar(type, targetContent, GetHorizontalScrollbar(),
123 &mHScrollbarHovered);
124 HandleEventForScrollbar(type, targetContent, GetVerticalScrollbar(),
125 &mVScrollbarHovered);
126
127 return NS_OK;
128 }
129
130 void
131 ScrollbarActivity::WillRefresh(TimeStamp aTime)
132 {
133 NS_ASSERTION(mIsActive, "should only fade while scrollbars are visible");
134 NS_ASSERTION(!IsActivityOngoing(), "why weren't we unregistered from the refresh driver when scrollbar activity started?");
135 NS_ASSERTION(mIsFading, "should only animate fading during fade");
136
137 if (!UpdateOpacity(aTime)) {
138 return;
139 }
140
141 if (!IsStillFading(aTime)) {
142 EndFade();
143 }
144 }
145
146 bool
147 ScrollbarActivity::IsStillFading(TimeStamp aTime)
148 {
149 return !mFadeBeginTime.IsNull() && (aTime - mFadeBeginTime < FadeDuration());
150 }
151
152 void
153 ScrollbarActivity::HandleEventForScrollbar(const nsAString& aType,
154 nsIContent* aTarget,
155 nsIContent* aScrollbar,
156 bool* aStoredHoverState)
157 {
158 if (!aTarget || !aScrollbar ||
159 !nsContentUtils::ContentIsDescendantOf(aTarget, aScrollbar))
160 return;
161
162 if (aType.EqualsLiteral("mousedown")) {
163 ActivityStarted();
164 } else if (aType.EqualsLiteral("mouseup")) {
165 ActivityStopped();
166 } else if (aType.EqualsLiteral("mouseover") ||
167 aType.EqualsLiteral("mouseout")) {
168 bool newHoveredState = aType.EqualsLiteral("mouseover");
169 if (newHoveredState && !*aStoredHoverState) {
170 ActivityStarted();
171 HoveredScrollbar(aScrollbar);
172 } else if (*aStoredHoverState && !newHoveredState) {
173 ActivityStopped();
174 // Don't call HoveredScrollbar(nullptr) here because we want the hover
175 // attribute to stick until the scrollbars are hidden.
176 }
177 *aStoredHoverState = newHoveredState;
178 }
179 }
180
181 void
182 ScrollbarActivity::StartListeningForScrollbarEvents()
183 {
184 if (mListeningForScrollbarEvents)
185 return;
186
187 mHorizontalScrollbar = do_QueryInterface(GetHorizontalScrollbar());
188 mVerticalScrollbar = do_QueryInterface(GetVerticalScrollbar());
189
190 AddScrollbarEventListeners(mHorizontalScrollbar);
191 AddScrollbarEventListeners(mVerticalScrollbar);
192
193 mListeningForScrollbarEvents = true;
194 }
195
196 void
197 ScrollbarActivity::StopListeningForScrollbarEvents()
198 {
199 if (!mListeningForScrollbarEvents)
200 return;
201
202 RemoveScrollbarEventListeners(mHorizontalScrollbar);
203 RemoveScrollbarEventListeners(mVerticalScrollbar);
204
205 mHorizontalScrollbar = nullptr;
206 mVerticalScrollbar = nullptr;
207 mListeningForScrollbarEvents = false;
208 }
209
210 void
211 ScrollbarActivity::StartListeningForScrollAreaEvents()
212 {
213 if (mListeningForScrollAreaEvents)
214 return;
215
216 nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame);
217 nsCOMPtr<nsIDOMEventTarget> scrollAreaTarget
218 = do_QueryInterface(scrollArea->GetContent());
219 if (scrollAreaTarget) {
220 scrollAreaTarget->AddEventListener(NS_LITERAL_STRING("mousemove"), this,
221 true);
222 }
223 mListeningForScrollAreaEvents = true;
224 }
225
226 void
227 ScrollbarActivity::StopListeningForScrollAreaEvents()
228 {
229 if (!mListeningForScrollAreaEvents)
230 return;
231
232 nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame);
233 nsCOMPtr<nsIDOMEventTarget> scrollAreaTarget = do_QueryInterface(scrollArea->GetContent());
234 if (scrollAreaTarget) {
235 scrollAreaTarget->RemoveEventListener(NS_LITERAL_STRING("mousemove"), this, true);
236 }
237 mListeningForScrollAreaEvents = false;
238 }
239
240 void
241 ScrollbarActivity::AddScrollbarEventListeners(nsIDOMEventTarget* aScrollbar)
242 {
243 if (aScrollbar) {
244 aScrollbar->AddEventListener(NS_LITERAL_STRING("mousedown"), this, true);
245 aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseup"), this, true);
246 aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseover"), this, true);
247 aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseout"), this, true);
248 }
249 }
250
251 void
252 ScrollbarActivity::RemoveScrollbarEventListeners(nsIDOMEventTarget* aScrollbar)
253 {
254 if (aScrollbar) {
255 aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, true);
256 aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseup"), this, true);
257 aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseover"), this, true);
258 aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseout"), this, true);
259 }
260 }
261
262 void
263 ScrollbarActivity::BeginFade()
264 {
265 NS_ASSERTION(mIsActive, "can't begin fade when we're already inactive");
266 NS_ASSERTION(!IsActivityOngoing(), "why wasn't the fade begin timer cancelled when scrollbar activity started?");
267 NS_ASSERTION(!mIsFading, "shouldn't be fading just yet");
268
269 CancelFadeBeginTimer();
270 mFadeBeginTime = TimeStamp::Now();
271 if (!SetIsFading(true)) {
272 return;
273 }
274 RegisterWithRefreshDriver();
275
276 NS_ASSERTION(mIsActive, "only fade while scrollbars are visible");
277 NS_ASSERTION(mIsFading, "should be fading now");
278 }
279
280 void
281 ScrollbarActivity::EndFade()
282 {
283 NS_ASSERTION(mIsActive, "still need to be active at this point");
284 NS_ASSERTION(!IsActivityOngoing(), "why wasn't the fade end timer cancelled when scrollbar activity started?");
285
286 if (!SetIsFading(false)) {
287 return;
288 }
289 SetIsActive(false);
290 UnregisterFromRefreshDriver();
291 StopListeningForScrollbarEvents();
292 if (!mDisplayOnMouseMove) {
293 StopListeningForScrollAreaEvents();
294 }
295
296 NS_ASSERTION(!mIsActive, "should have gone inactive after fade end");
297 NS_ASSERTION(!mIsFading, "shouldn't be fading anymore");
298 }
299
300 void
301 ScrollbarActivity::RegisterWithRefreshDriver()
302 {
303 nsRefreshDriver* refreshDriver = GetRefreshDriver();
304 if (refreshDriver) {
305 refreshDriver->AddRefreshObserver(this, Flush_Style);
306 }
307 }
308
309 void
310 ScrollbarActivity::UnregisterFromRefreshDriver()
311 {
312 nsRefreshDriver* refreshDriver = GetRefreshDriver();
313 if (refreshDriver) {
314 refreshDriver->RemoveRefreshObserver(this, Flush_Style);
315 }
316 }
317
318 static void
319 SetBooleanAttribute(nsIContent* aContent, nsIAtom* aAttribute, bool aValue)
320 {
321 if (aContent) {
322 if (aValue) {
323 aContent->SetAttr(kNameSpaceID_None, aAttribute,
324 NS_LITERAL_STRING("true"), true);
325 } else {
326 aContent->UnsetAttr(kNameSpaceID_None, aAttribute, true);
327 }
328 }
329 }
330
331 void
332 ScrollbarActivity::SetIsActive(bool aNewActive)
333 {
334 if (mIsActive == aNewActive)
335 return;
336
337 mIsActive = aNewActive;
338 if (!mIsActive) {
339 // Clear sticky scrollbar hover status.
340 HoveredScrollbar(nullptr);
341 }
342
343 SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::active, mIsActive);
344 SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::active, mIsActive);
345 }
346
347 static void
348 SetOpacityOnElement(nsIContent* aContent, double aOpacity)
349 {
350 nsCOMPtr<nsIDOMElementCSSInlineStyle> inlineStyleContent =
351 do_QueryInterface(aContent);
352 if (inlineStyleContent) {
353 nsCOMPtr<nsIDOMCSSStyleDeclaration> decl;
354 inlineStyleContent->GetStyle(getter_AddRefs(decl));
355 if (decl) {
356 nsAutoString str;
357 str.AppendFloat(aOpacity);
358 decl->SetProperty(NS_LITERAL_STRING("opacity"), str, EmptyString());
359 }
360 }
361 }
362
363 bool
364 ScrollbarActivity::UpdateOpacity(TimeStamp aTime)
365 {
366 double progress = (aTime - mFadeBeginTime) / FadeDuration();
367 double opacity = 1.0 - std::max(0.0, std::min(1.0, progress));
368
369 // 'this' may be getting destroyed during SetOpacityOnElement calls.
370 nsWeakFrame weakFrame((do_QueryFrame(mScrollableFrame)));
371 SetOpacityOnElement(GetHorizontalScrollbar(), opacity);
372 if (!weakFrame.IsAlive()) {
373 return false;
374 }
375 SetOpacityOnElement(GetVerticalScrollbar(), opacity);
376 if (!weakFrame.IsAlive()) {
377 return false;
378 }
379 return true;
380 }
381
382 static void
383 UnsetOpacityOnElement(nsIContent* aContent)
384 {
385 nsCOMPtr<nsIDOMElementCSSInlineStyle> inlineStyleContent =
386 do_QueryInterface(aContent);
387 if (inlineStyleContent) {
388 nsCOMPtr<nsIDOMCSSStyleDeclaration> decl;
389 inlineStyleContent->GetStyle(getter_AddRefs(decl));
390 if (decl) {
391 nsAutoString dummy;
392 decl->RemoveProperty(NS_LITERAL_STRING("opacity"), dummy);
393 }
394 }
395 }
396
397 bool
398 ScrollbarActivity::SetIsFading(bool aNewFading)
399 {
400 if (mIsFading == aNewFading)
401 return true;
402
403 mIsFading = aNewFading;
404 if (!mIsFading) {
405 mFadeBeginTime = TimeStamp();
406 // 'this' may be getting destroyed during UnsetOpacityOnElement calls.
407 nsWeakFrame weakFrame((do_QueryFrame(mScrollableFrame)));
408 UnsetOpacityOnElement(GetHorizontalScrollbar());
409 if (!weakFrame.IsAlive()) {
410 return false;
411 }
412 UnsetOpacityOnElement(GetVerticalScrollbar());
413 if (!weakFrame.IsAlive()) {
414 return false;
415 }
416 }
417 return true;
418 }
419
420 void
421 ScrollbarActivity::StartFadeBeginTimer()
422 {
423 if (GetForceAlwaysVisiblePref()) {
424 return;
425 }
426 if (!mFadeBeginTimer) {
427 mFadeBeginTimer = do_CreateInstance("@mozilla.org/timer;1");
428 }
429 mFadeBeginTimer->InitWithFuncCallback(FadeBeginTimerFired, this,
430 mScrollbarFadeBeginDelay,
431 nsITimer::TYPE_ONE_SHOT);
432 }
433
434 void
435 ScrollbarActivity::CancelFadeBeginTimer()
436 {
437 if (mFadeBeginTimer) {
438 mFadeBeginTimer->Cancel();
439 }
440 }
441
442 void
443 ScrollbarActivity::HoveredScrollbar(nsIContent* aScrollbar)
444 {
445 SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::hover, false);
446 SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::hover, false);
447 SetBooleanAttribute(aScrollbar, nsGkAtoms::hover, true);
448 }
449
450 nsRefreshDriver*
451 ScrollbarActivity::GetRefreshDriver()
452 {
453 nsIFrame* scrollableFrame = do_QueryFrame(mScrollableFrame);
454 return scrollableFrame->PresContext()->RefreshDriver();
455 }
456
457 nsIContent*
458 ScrollbarActivity::GetScrollbarContent(bool aVertical)
459 {
460 nsIFrame* box = mScrollableFrame->GetScrollbarBox(aVertical);
461 return box ? box->GetContent() : nullptr;
462 }
463
464 } // namespace layout
465 } // namespace mozilla

mercurial