Coverage Report

Created: 2018-09-25 14:53

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