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