Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/events/WheelHandlingHelper.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 "WheelHandlingHelper.h"
8
9
#include <utility>                      // for std::swap
10
11
#include "mozilla/EventDispatcher.h"
12
#include "mozilla/EventStateManager.h"
13
#include "mozilla/MouseEvents.h"
14
#include "mozilla/Preferences.h"
15
#include "mozilla/dom/WheelEventBinding.h"
16
#include "nsCOMPtr.h"
17
#include "nsContentUtils.h"
18
#include "nsIContent.h"
19
#include "nsIContentInlines.h"
20
#include "nsIDocument.h"
21
#include "nsIDocumentInlines.h"         // for nsIDocument and HTMLBodyElement
22
#include "nsIPresShell.h"
23
#include "nsIScrollableFrame.h"
24
#include "nsITextControlElement.h"
25
#include "nsITimer.h"
26
#include "nsPluginFrame.h"
27
#include "nsPresContext.h"
28
#include "prtime.h"
29
#include "Units.h"
30
#include "ScrollAnimationPhysics.h"
31
32
namespace mozilla {
33
34
/******************************************************************/
35
/* mozilla::DeltaValues                                           */
36
/******************************************************************/
37
38
DeltaValues::DeltaValues(WidgetWheelEvent* aEvent)
39
  : deltaX(aEvent->mDeltaX)
40
  , deltaY(aEvent->mDeltaY)
41
0
{
42
0
}
43
44
/******************************************************************/
45
/* mozilla::WheelHandlingUtils                                    */
46
/******************************************************************/
47
48
/* static */ bool
49
WheelHandlingUtils::CanScrollInRange(nscoord aMin, nscoord aValue, nscoord aMax,
50
                                     double aDirection)
51
0
{
52
0
  return aDirection > 0.0 ? aValue < static_cast<double>(aMax) :
53
0
                            static_cast<double>(aMin) < aValue;
54
0
}
55
56
/* static */ bool
57
WheelHandlingUtils::CanScrollOn(nsIFrame* aFrame,
58
                                double aDirectionX, double aDirectionY)
59
0
{
60
0
  nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame);
61
0
  if (scrollableFrame) {
62
0
    return CanScrollOn(scrollableFrame, aDirectionX, aDirectionY);
63
0
  }
64
0
  nsPluginFrame* pluginFrame = do_QueryFrame(aFrame);
65
0
  return pluginFrame && pluginFrame->WantsToHandleWheelEventAsDefaultAction();
66
0
}
67
68
/* static */ bool
69
WheelHandlingUtils::CanScrollOn(nsIScrollableFrame* aScrollFrame,
70
                                double aDirectionX, double aDirectionY)
71
0
{
72
0
  MOZ_ASSERT(aScrollFrame);
73
0
  NS_ASSERTION(aDirectionX || aDirectionY,
74
0
               "One of the delta values must be non-zero at least");
75
0
76
0
  nsPoint scrollPt = aScrollFrame->GetScrollPosition();
77
0
  nsRect scrollRange = aScrollFrame->GetScrollRange();
78
0
  uint32_t directions = aScrollFrame->GetPerceivedScrollingDirections();
79
0
80
0
  return (aDirectionX && (directions & nsIScrollableFrame::HORIZONTAL) &&
81
0
          CanScrollInRange(scrollRange.x, scrollPt.x,
82
0
                           scrollRange.XMost(), aDirectionX)) ||
83
0
         (aDirectionY && (directions & nsIScrollableFrame::VERTICAL) &&
84
0
          CanScrollInRange(scrollRange.y, scrollPt.y,
85
0
                           scrollRange.YMost(), aDirectionY));
86
0
}
87
88
/*static*/ Maybe<layers::ScrollDirection>
89
WheelHandlingUtils::GetDisregardedWheelScrollDirection(const nsIFrame* aFrame)
90
0
{
91
0
  nsIContent* content = aFrame->GetContent();
92
0
  if (!content) {
93
0
    return Nothing();
94
0
  }
95
0
  nsCOMPtr<nsITextControlElement> ctrl =
96
0
    do_QueryInterface(content->IsInAnonymousSubtree()
97
0
                      ? content->GetBindingParent() : content);
98
0
  if (!ctrl || !ctrl->IsSingleLineTextControl()) {
99
0
    return Nothing();
100
0
  }
101
0
  // Disregard scroll in the block-flow direction by mouse wheel on a
102
0
  // single-line text control. For instance, in tranditional Chinese writing
103
0
  // system, a single-line text control cannot be scrolled horizontally with
104
0
  // mouse wheel even if they overflow at the right and left edges; Whereas in
105
0
  // latin-based writing system, a single-line text control cannot be scrolled
106
0
  // vertically with mouse wheel even if they overflow at the top and bottom
107
0
  // edges
108
0
  return Some(aFrame->GetWritingMode().IsVertical()
109
0
              ? layers::ScrollDirection::eHorizontal
110
0
              : layers::ScrollDirection::eVertical);
111
0
}
112
113
/******************************************************************/
114
/* mozilla::WheelTransaction                                      */
115
/******************************************************************/
116
117
AutoWeakFrame WheelTransaction::sTargetFrame(nullptr);
118
uint32_t WheelTransaction::sTime = 0;
119
uint32_t WheelTransaction::sMouseMoved = 0;
120
nsITimer* WheelTransaction::sTimer = nullptr;
121
int32_t WheelTransaction::sScrollSeriesCounter = 0;
122
bool WheelTransaction::sOwnScrollbars = false;
123
124
/* static */ bool
125
WheelTransaction::OutOfTime(uint32_t aBaseTime, uint32_t aThreshold)
126
0
{
127
0
  uint32_t now = PR_IntervalToMilliseconds(PR_IntervalNow());
128
0
  return (now - aBaseTime > aThreshold);
129
0
}
130
131
/* static */ void
132
WheelTransaction::OwnScrollbars(bool aOwn)
133
0
{
134
0
  sOwnScrollbars = aOwn;
135
0
}
136
137
/* static */ void
138
WheelTransaction::BeginTransaction(nsIFrame* aTargetFrame,
139
                                   const WidgetWheelEvent* aEvent)
140
0
{
141
0
  NS_ASSERTION(!sTargetFrame, "previous transaction is not finished!");
142
0
  MOZ_ASSERT(aEvent->mMessage == eWheel,
143
0
             "Transaction must be started with a wheel event");
144
0
  ScrollbarsForWheel::OwnWheelTransaction(false);
145
0
  sTargetFrame = aTargetFrame;
146
0
  sScrollSeriesCounter = 0;
147
0
  if (!UpdateTransaction(aEvent)) {
148
0
    NS_ERROR("BeginTransaction is called even cannot scroll the frame");
149
0
    EndTransaction();
150
0
  }
151
0
}
152
153
/* static */ bool
154
WheelTransaction::UpdateTransaction(const WidgetWheelEvent* aEvent)
155
0
{
156
0
  nsIFrame* scrollToFrame = GetTargetFrame();
157
0
  nsIScrollableFrame* scrollableFrame = scrollToFrame->GetScrollTargetFrame();
158
0
  if (scrollableFrame) {
159
0
    scrollToFrame = do_QueryFrame(scrollableFrame);
160
0
  }
161
0
162
0
  if (!WheelHandlingUtils::CanScrollOn(scrollToFrame,
163
0
                                       aEvent->mDeltaX, aEvent->mDeltaY)) {
164
0
    OnFailToScrollTarget();
165
0
    // We should not modify the transaction state when the view will not be
166
0
    // scrolled actually.
167
0
    return false;
168
0
  }
169
0
170
0
  SetTimeout();
171
0
172
0
  if (sScrollSeriesCounter != 0 && OutOfTime(sTime, kScrollSeriesTimeoutMs)) {
173
0
    sScrollSeriesCounter = 0;
174
0
  }
175
0
  sScrollSeriesCounter++;
176
0
177
0
  // We should use current time instead of WidgetEvent.time.
178
0
  // 1. Some events doesn't have the correct creation time.
179
0
  // 2. If the computer runs slowly by other processes eating the CPU resource,
180
0
  //    the event creation time doesn't keep real time.
181
0
  sTime = PR_IntervalToMilliseconds(PR_IntervalNow());
182
0
  sMouseMoved = 0;
183
0
  return true;
184
0
}
185
186
/* static */ void
187
WheelTransaction::MayEndTransaction()
188
0
{
189
0
  if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) {
190
0
    ScrollbarsForWheel::OwnWheelTransaction(true);
191
0
  } else {
192
0
    EndTransaction();
193
0
  }
194
0
}
195
196
/* static */ void
197
WheelTransaction::EndTransaction()
198
0
{
199
0
  if (sTimer) {
200
0
    sTimer->Cancel();
201
0
  }
202
0
  sTargetFrame = nullptr;
203
0
  sScrollSeriesCounter = 0;
204
0
  if (sOwnScrollbars) {
205
0
    sOwnScrollbars = false;
206
0
    ScrollbarsForWheel::OwnWheelTransaction(false);
207
0
    ScrollbarsForWheel::Inactivate();
208
0
  }
209
0
}
210
211
/* static */ bool
212
WheelTransaction::WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent,
213
                                          AutoWeakFrame& aTargetWeakFrame)
214
0
{
215
0
  nsIFrame* lastTargetFrame = GetTargetFrame();
216
0
  if (!lastTargetFrame) {
217
0
    BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent);
218
0
  } else if (lastTargetFrame != aTargetWeakFrame.GetFrame()) {
219
0
    EndTransaction();
220
0
    BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent);
221
0
  } else {
222
0
    UpdateTransaction(aWheelEvent);
223
0
  }
224
0
225
0
  // When the wheel event will not be handled with any frames,
226
0
  // UpdateTransaction() fires MozMouseScrollFailed event which is for
227
0
  // automated testing.  In the event handler, the target frame might be
228
0
  // destroyed.  Then, the caller shouldn't try to handle the default action.
229
0
  if (!aTargetWeakFrame.IsAlive()) {
230
0
    EndTransaction();
231
0
    return false;
232
0
  }
233
0
234
0
  return true;
235
0
}
236
237
/* static */ void
238
WheelTransaction::OnEvent(WidgetEvent* aEvent)
239
0
{
240
0
  if (!sTargetFrame) {
241
0
    return;
242
0
  }
243
0
244
0
  if (OutOfTime(sTime, GetTimeoutTime())) {
245
0
    // Even if the scroll event which is handled after timeout, but onTimeout
246
0
    // was not fired by timer, then the scroll event will scroll old frame,
247
0
    // therefore, we should call OnTimeout here and ensure to finish the old
248
0
    // transaction.
249
0
    OnTimeout(nullptr, nullptr);
250
0
    return;
251
0
  }
252
0
253
0
  switch (aEvent->mMessage) {
254
0
    case eWheel:
255
0
      if (sMouseMoved != 0 &&
256
0
          OutOfTime(sMouseMoved, GetIgnoreMoveDelayTime())) {
257
0
        // Terminate the current mousewheel transaction if the mouse moved more
258
0
        // than ignoremovedelay milliseconds ago
259
0
        EndTransaction();
260
0
      }
261
0
      return;
262
0
    case eMouseMove:
263
0
    case eDragOver: {
264
0
      WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
265
0
      if (mouseEvent->IsReal()) {
266
0
        // If the cursor is moving to be outside the frame,
267
0
        // terminate the scrollwheel transaction.
268
0
        LayoutDeviceIntPoint pt = GetScreenPoint(mouseEvent);
269
0
        auto r = LayoutDeviceIntRect::FromAppUnitsToNearest(
270
0
          sTargetFrame->GetScreenRectInAppUnits(),
271
0
          sTargetFrame->PresContext()->AppUnitsPerDevPixel());
272
0
        if (!r.Contains(pt)) {
273
0
          EndTransaction();
274
0
          return;
275
0
        }
276
0
277
0
        // If the cursor is moving inside the frame, and it is less than
278
0
        // ignoremovedelay milliseconds since the last scroll operation, ignore
279
0
        // the mouse move; otherwise, record the current mouse move time to be
280
0
        // checked later
281
0
        if (!sMouseMoved && OutOfTime(sTime, GetIgnoreMoveDelayTime())) {
282
0
          sMouseMoved = PR_IntervalToMilliseconds(PR_IntervalNow());
283
0
        }
284
0
      }
285
0
      return;
286
0
    }
287
0
    case eKeyPress:
288
0
    case eKeyUp:
289
0
    case eKeyDown:
290
0
    case eMouseUp:
291
0
    case eMouseDown:
292
0
    case eMouseDoubleClick:
293
0
    case eMouseAuxClick:
294
0
    case eMouseClick:
295
0
    case eContextMenu:
296
0
    case eDrop:
297
0
      EndTransaction();
298
0
      return;
299
0
    default:
300
0
      break;
301
0
  }
302
0
}
303
304
/* static */ void
305
WheelTransaction::Shutdown()
306
0
{
307
0
  NS_IF_RELEASE(sTimer);
308
0
}
309
310
/* static */ void
311
WheelTransaction::OnFailToScrollTarget()
312
0
{
313
0
  MOZ_ASSERT(sTargetFrame, "We don't have mouse scrolling transaction");
314
0
315
0
  if (Prefs::sTestMouseScroll) {
316
0
    // This event is used for automated tests, see bug 442774.
317
0
    nsContentUtils::DispatchTrustedEvent(
318
0
                      sTargetFrame->GetContent()->OwnerDoc(),
319
0
                      sTargetFrame->GetContent(),
320
0
                      NS_LITERAL_STRING("MozMouseScrollFailed"),
321
0
                      CanBubble::eYes, Cancelable::eYes);
322
0
  }
323
0
  // The target frame might be destroyed in the event handler, at that time,
324
0
  // we need to finish the current transaction
325
0
  if (!sTargetFrame) {
326
0
    EndTransaction();
327
0
  }
328
0
}
329
330
/* static */ void
331
WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure)
332
0
{
333
0
  if (!sTargetFrame) {
334
0
    // The transaction target was destroyed already
335
0
    EndTransaction();
336
0
    return;
337
0
  }
338
0
  // Store the sTargetFrame, the variable becomes null in EndTransaction.
339
0
  nsIFrame* frame = sTargetFrame;
340
0
  // We need to finish current transaction before DOM event firing. Because
341
0
  // the next DOM event might create strange situation for us.
342
0
  MayEndTransaction();
343
0
344
0
  if (Prefs::sTestMouseScroll) {
345
0
    // This event is used for automated tests, see bug 442774.
346
0
    nsContentUtils::DispatchTrustedEvent(
347
0
                      frame->GetContent()->OwnerDoc(),
348
0
                      frame->GetContent(),
349
0
                      NS_LITERAL_STRING("MozMouseScrollTransactionTimeout"),
350
0
                      CanBubble::eYes, Cancelable::eYes);
351
0
  }
352
0
}
353
354
/* static */ void
355
WheelTransaction::SetTimeout()
356
0
{
357
0
  if (!sTimer) {
358
0
    sTimer = NS_NewTimer().take();
359
0
    if (!sTimer) {
360
0
      return;
361
0
    }
362
0
  }
363
0
  sTimer->Cancel();
364
0
  DebugOnly<nsresult> rv =
365
0
    sTimer->InitWithNamedFuncCallback(OnTimeout,
366
0
                                      nullptr,
367
0
                                      GetTimeoutTime(),
368
0
                                      nsITimer::TYPE_ONE_SHOT,
369
0
                                      "WheelTransaction::SetTimeout");
370
0
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
371
0
                       "nsITimer::InitWithFuncCallback failed");
372
0
}
373
374
/* static */ LayoutDeviceIntPoint
375
WheelTransaction::GetScreenPoint(WidgetGUIEvent* aEvent)
376
0
{
377
0
  NS_ASSERTION(aEvent, "aEvent is null");
378
0
  NS_ASSERTION(aEvent->mWidget, "aEvent-mWidget is null");
379
0
  return aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset();
380
0
}
381
382
/* static */ DeltaValues
383
WheelTransaction::AccelerateWheelDelta(WidgetWheelEvent* aEvent,
384
                                       bool aAllowScrollSpeedOverride)
385
0
{
386
0
  DeltaValues result(aEvent);
387
0
388
0
  // Don't accelerate the delta values if the event isn't line scrolling.
389
0
  if (aEvent->mDeltaMode != dom::WheelEvent_Binding::DOM_DELTA_LINE) {
390
0
    return result;
391
0
  }
392
0
393
0
  if (aAllowScrollSpeedOverride) {
394
0
    result = OverrideSystemScrollSpeed(aEvent);
395
0
  }
396
0
397
0
  // Accelerate by the sScrollSeriesCounter
398
0
  int32_t start = GetAccelerationStart();
399
0
  if (start >= 0 && sScrollSeriesCounter >= start) {
400
0
    int32_t factor = GetAccelerationFactor();
401
0
    if (factor > 0) {
402
0
      result.deltaX = ComputeAcceleratedWheelDelta(result.deltaX, factor);
403
0
      result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor);
404
0
    }
405
0
  }
406
0
407
0
  return result;
408
0
}
409
410
/* static */ double
411
WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta, int32_t aFactor)
412
0
{
413
0
  return mozilla::ComputeAcceleratedWheelDelta(aDelta, sScrollSeriesCounter, aFactor);
414
0
}
415
416
/* static */ DeltaValues
417
WheelTransaction::OverrideSystemScrollSpeed(WidgetWheelEvent* aEvent)
418
0
{
419
0
  MOZ_ASSERT(sTargetFrame, "We don't have mouse scrolling transaction");
420
0
  MOZ_ASSERT(aEvent->mDeltaMode == WheelEvent_Binding::DOM_DELTA_LINE);
421
0
422
0
  // If the event doesn't scroll to both X and Y, we don't need to do anything
423
0
  // here.
424
0
  if (!aEvent->mDeltaX && !aEvent->mDeltaY) {
425
0
    return DeltaValues(aEvent);
426
0
  }
427
0
428
0
  return DeltaValues(aEvent->OverriddenDeltaX(),
429
0
                     aEvent->OverriddenDeltaY());
430
0
}
431
432
/******************************************************************/
433
/* mozilla::ScrollbarsForWheel                                    */
434
/******************************************************************/
435
436
const DeltaValues ScrollbarsForWheel::directions[kNumberOfTargets] = {
437
  DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1), DeltaValues(0, +1)
438
};
439
440
AutoWeakFrame ScrollbarsForWheel::sActiveOwner = nullptr;
441
AutoWeakFrame ScrollbarsForWheel::sActivatedScrollTargets[kNumberOfTargets] = {
442
  nullptr, nullptr, nullptr, nullptr
443
};
444
445
bool ScrollbarsForWheel::sHadWheelStart = false;
446
bool ScrollbarsForWheel::sOwnWheelTransaction = false;
447
448
/* static */ void
449
ScrollbarsForWheel::PrepareToScrollText(EventStateManager* aESM,
450
                                        nsIFrame* aTargetFrame,
451
                                        WidgetWheelEvent* aEvent)
452
0
{
453
0
  if (aEvent->mMessage == eWheelOperationStart) {
454
0
    WheelTransaction::OwnScrollbars(false);
455
0
    if (!IsActive()) {
456
0
      TemporarilyActivateAllPossibleScrollTargets(aESM, aTargetFrame, aEvent);
457
0
      sHadWheelStart = true;
458
0
    }
459
0
  } else {
460
0
    DeactivateAllTemporarilyActivatedScrollTargets();
461
0
  }
462
0
}
463
464
/* static */ void
465
ScrollbarsForWheel::SetActiveScrollTarget(nsIScrollableFrame* aScrollTarget)
466
0
{
467
0
  if (!sHadWheelStart) {
468
0
    return;
469
0
  }
470
0
  nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(aScrollTarget);
471
0
  if (!scrollbarMediator) {
472
0
    return;
473
0
  }
474
0
  sHadWheelStart = false;
475
0
  sActiveOwner = do_QueryFrame(aScrollTarget);
476
0
  scrollbarMediator->ScrollbarActivityStarted();
477
0
}
478
479
/* static */ void
480
ScrollbarsForWheel::MayInactivate()
481
0
{
482
0
  if (!sOwnWheelTransaction && WheelTransaction::GetTargetFrame()) {
483
0
    WheelTransaction::OwnScrollbars(true);
484
0
  } else {
485
0
    Inactivate();
486
0
  }
487
0
}
488
489
/* static */ void
490
ScrollbarsForWheel::Inactivate()
491
0
{
492
0
  nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sActiveOwner);
493
0
  if (scrollbarMediator) {
494
0
    scrollbarMediator->ScrollbarActivityStopped();
495
0
  }
496
0
  sActiveOwner = nullptr;
497
0
  DeactivateAllTemporarilyActivatedScrollTargets();
498
0
  if (sOwnWheelTransaction) {
499
0
    sOwnWheelTransaction = false;
500
0
    WheelTransaction::OwnScrollbars(false);
501
0
    WheelTransaction::EndTransaction();
502
0
  }
503
0
}
504
505
/* static */ bool
506
ScrollbarsForWheel::IsActive()
507
0
{
508
0
  if (sActiveOwner) {
509
0
    return true;
510
0
  }
511
0
  for (size_t i = 0; i < kNumberOfTargets; ++i) {
512
0
    if (sActivatedScrollTargets[i]) {
513
0
      return true;
514
0
    }
515
0
  }
516
0
  return false;
517
0
}
518
519
/* static */ void
520
ScrollbarsForWheel::OwnWheelTransaction(bool aOwn)
521
0
{
522
0
  sOwnWheelTransaction = aOwn;
523
0
}
524
525
/* static */ void
526
ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets(
527
                      EventStateManager* aESM,
528
                      nsIFrame* aTargetFrame,
529
                      WidgetWheelEvent* aEvent)
530
0
{
531
0
  for (size_t i = 0; i < kNumberOfTargets; i++) {
532
0
    const DeltaValues *dir = &directions[i];
533
0
    AutoWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
534
0
    MOZ_ASSERT(!*scrollTarget, "scroll target still temporarily activated!");
535
0
    nsIScrollableFrame* target = do_QueryFrame(
536
0
      aESM->ComputeScrollTarget(
537
0
              aTargetFrame, dir->deltaX, dir->deltaY, aEvent,
538
0
              EventStateManager::COMPUTE_DEFAULT_ACTION_TARGET));
539
0
    nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(target);
540
0
    if (scrollbarMediator) {
541
0
      nsIFrame* targetFrame = do_QueryFrame(target);
542
0
      *scrollTarget = targetFrame;
543
0
      scrollbarMediator->ScrollbarActivityStarted();
544
0
    }
545
0
  }
546
0
}
547
548
/* static */ void
549
ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets()
550
0
{
551
0
  for (size_t i = 0; i < kNumberOfTargets; i++) {
552
0
    AutoWeakFrame* scrollTarget = &sActivatedScrollTargets[i];
553
0
    if (*scrollTarget) {
554
0
      nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(*scrollTarget);
555
0
      if (scrollbarMediator) {
556
0
        scrollbarMediator->ScrollbarActivityStopped();
557
0
      }
558
0
      *scrollTarget = nullptr;
559
0
    }
560
0
  }
561
0
}
562
563
/******************************************************************/
564
/* mozilla::WheelTransaction::Prefs                               */
565
/******************************************************************/
566
567
int32_t WheelTransaction::Prefs::sMouseWheelAccelerationStart = -1;
568
int32_t WheelTransaction::Prefs::sMouseWheelAccelerationFactor = -1;
569
uint32_t WheelTransaction::Prefs::sMouseWheelTransactionTimeout = 1500;
570
uint32_t WheelTransaction::Prefs::sMouseWheelTransactionIgnoreMoveDelay = 100;
571
bool WheelTransaction::Prefs::sTestMouseScroll = false;
572
573
/* static */ void
574
WheelTransaction::Prefs::InitializeStatics()
575
0
{
576
0
  static bool sIsInitialized = false;
577
0
  if (!sIsInitialized) {
578
0
    Preferences::AddIntVarCache(&sMouseWheelAccelerationStart,
579
0
                                "mousewheel.acceleration.start", -1);
580
0
    Preferences::AddIntVarCache(&sMouseWheelAccelerationFactor,
581
0
                                "mousewheel.acceleration.factor", -1);
582
0
    Preferences::AddUintVarCache(&sMouseWheelTransactionTimeout,
583
0
                                 "mousewheel.transaction.timeout", 1500);
584
0
    Preferences::AddUintVarCache(&sMouseWheelTransactionIgnoreMoveDelay,
585
0
                                 "mousewheel.transaction.ignoremovedelay", 100);
586
0
    Preferences::AddBoolVarCache(&sTestMouseScroll, "test.mousescroll", false);
587
0
    sIsInitialized = true;
588
0
  }
589
0
}
590
591
/******************************************************************/
592
/* mozilla::WheelDeltaHorizontalizer                              */
593
/******************************************************************/
594
595
void
596
WheelDeltaHorizontalizer::Horizontalize()
597
0
{
598
0
  MOZ_ASSERT(!mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler,
599
0
             "Wheel delta values in one wheel scroll event are being adjusted "
600
0
             "a second time");
601
0
602
0
  // Log the old values.
603
0
  mOldDeltaX = mWheelEvent.mDeltaX;
604
0
  mOldDeltaZ = mWheelEvent.mDeltaZ;
605
0
  mOldOverflowDeltaX = mWheelEvent.mOverflowDeltaX;
606
0
  mOldLineOrPageDeltaX = mWheelEvent.mLineOrPageDeltaX;
607
0
608
0
  // Move deltaY values to deltaX and set both deltaY and deltaZ to 0.
609
0
  mWheelEvent.mDeltaX = mWheelEvent.mDeltaY;
610
0
  mWheelEvent.mDeltaY = 0.0;
611
0
  mWheelEvent.mDeltaZ = 0.0;
612
0
  mWheelEvent.mOverflowDeltaX = mWheelEvent.mOverflowDeltaY;
613
0
  mWheelEvent.mOverflowDeltaY = 0.0;
614
0
  mWheelEvent.mLineOrPageDeltaX = mWheelEvent.mLineOrPageDeltaY;
615
0
  mWheelEvent.mLineOrPageDeltaY = 0;
616
0
617
0
  // Mark it horizontalized in order to restore the delta values when this
618
0
  // instance is being destroyed.
619
0
  mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler = true;
620
0
  mHorizontalized = true;
621
0
}
622
623
void WheelDeltaHorizontalizer::CancelHorizontalization()
624
0
{
625
0
  // Restore the horizontalized delta.
626
0
  if (mHorizontalized &&
627
0
      mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler) {
628
0
    mWheelEvent.mDeltaY = mWheelEvent.mDeltaX;
629
0
    mWheelEvent.mDeltaX = mOldDeltaX;
630
0
    mWheelEvent.mDeltaZ = mOldDeltaZ;
631
0
    mWheelEvent.mOverflowDeltaY = mWheelEvent.mOverflowDeltaX;
632
0
    mWheelEvent.mOverflowDeltaX = mOldOverflowDeltaX;
633
0
    mWheelEvent.mLineOrPageDeltaY = mWheelEvent.mLineOrPageDeltaX;
634
0
    mWheelEvent.mLineOrPageDeltaX = mOldLineOrPageDeltaX;
635
0
    mWheelEvent.mDeltaValuesHorizontalizedForDefaultHandler = false;
636
0
    mHorizontalized = false;
637
0
  }
638
0
}
639
640
WheelDeltaHorizontalizer::~WheelDeltaHorizontalizer()
641
0
{
642
0
  CancelHorizontalization();
643
0
}
644
645
/******************************************************************/
646
/* mozilla::AutoDirWheelDeltaAdjuster                             */
647
/******************************************************************/
648
649
bool
650
AutoDirWheelDeltaAdjuster::ShouldBeAdjusted()
651
0
{
652
0
  // Sometimes, this function can be called more than one time. If we have
653
0
  // already checked if the scroll should be adjusted, there's no need to check
654
0
  // it again.
655
0
  if (mCheckedIfShouldBeAdjusted) {
656
0
    return mShouldBeAdjusted;
657
0
  }
658
0
  mCheckedIfShouldBeAdjusted = true;
659
0
660
0
  // For an auto-dir wheel scroll, if all the following conditions are met, we
661
0
  // should adjust X and Y values:
662
0
  // 1. There is only one non-zero value between DeltaX and DeltaY.
663
0
  // 2. There is only one direction for the target that overflows and is
664
0
  //    scrollable with wheel.
665
0
  // 3. The direction described in Condition 1 is orthogonal to the one
666
0
  // described in Condition 2.
667
0
  if ((mDeltaX && mDeltaY) || (!mDeltaX && !mDeltaY)) {
668
0
    return false;
669
0
  }
670
0
  if (mDeltaX) {
671
0
    if (CanScrollAlongXAxis()) {
672
0
      return false;
673
0
    }
674
0
    if (IsHorizontalContentRightToLeft()) {
675
0
      mShouldBeAdjusted = mDeltaX > 0 ? CanScrollUpwards()
676
0
                                      : CanScrollDownwards();
677
0
    } else {
678
0
      mShouldBeAdjusted = mDeltaX < 0 ? CanScrollUpwards()
679
0
                                      : CanScrollDownwards();
680
0
    }
681
0
    return mShouldBeAdjusted;
682
0
  }
683
0
  MOZ_ASSERT(0 != mDeltaY);
684
0
  if (CanScrollAlongYAxis()) {
685
0
    return false;
686
0
  }
687
0
  if (IsHorizontalContentRightToLeft()) {
688
0
    mShouldBeAdjusted = mDeltaY > 0 ? CanScrollLeftwards()
689
0
                                    : CanScrollRightwards();
690
0
  } else {
691
0
    mShouldBeAdjusted = mDeltaY < 0 ? CanScrollLeftwards()
692
0
                                    : CanScrollRightwards();
693
0
  }
694
0
  return mShouldBeAdjusted;
695
0
}
696
697
void
698
AutoDirWheelDeltaAdjuster::Adjust()
699
0
{
700
0
  if (!ShouldBeAdjusted()) {
701
0
    return;
702
0
  }
703
0
  std::swap(mDeltaX, mDeltaY);
704
0
  if (IsHorizontalContentRightToLeft()) {
705
0
    mDeltaX *= -1;
706
0
    mDeltaY *= -1;
707
0
  }
708
0
  mShouldBeAdjusted = false;
709
0
  OnAdjusted();
710
0
}
711
712
/******************************************************************/
713
/* mozilla::ESMAutoDirWheelDeltaAdjuster                          */
714
/******************************************************************/
715
716
ESMAutoDirWheelDeltaAdjuster::ESMAutoDirWheelDeltaAdjuster(
717
                                WidgetWheelEvent& aEvent,
718
                                nsIFrame& aScrollFrame,
719
                                bool aHonoursRoot)
720
  : AutoDirWheelDeltaAdjuster(aEvent.mDeltaX, aEvent.mDeltaY)
721
  , mLineOrPageDeltaX(aEvent.mLineOrPageDeltaX)
722
  , mLineOrPageDeltaY(aEvent.mLineOrPageDeltaY)
723
  , mOverflowDeltaX(aEvent.mOverflowDeltaX)
724
  , mOverflowDeltaY(aEvent.mOverflowDeltaY)
725
0
{
726
0
  mScrollTargetFrame = aScrollFrame.GetScrollTargetFrame();
727
0
  MOZ_ASSERT(mScrollTargetFrame);
728
0
729
0
  nsIFrame* honouredFrame = nullptr;
730
0
  if (aHonoursRoot) {
731
0
    // If we are going to honour root, first try to get the frame for <body> as
732
0
    // the honoured root, because <body> is in preference to <html> if the
733
0
    // current document is an HTML document.
734
0
    nsIDocument* document = aScrollFrame.PresShell()->GetDocument();
735
0
    if (document) {
736
0
      Element* bodyElement = document->GetBodyElement();
737
0
      if (bodyElement) {
738
0
        honouredFrame = bodyElement->GetPrimaryFrame();
739
0
      }
740
0
    }
741
0
742
0
    if (!honouredFrame) {
743
0
      // If there is no <body> frame, fall back to the real root frame.
744
0
      honouredFrame = aScrollFrame.PresShell()->GetRootScrollFrame();
745
0
    }
746
0
747
0
    if (!honouredFrame) {
748
0
      // If there is no root scroll frame, fall back to the current scrolling
749
0
      // frame.
750
0
      honouredFrame = &aScrollFrame;
751
0
    }
752
0
  } else {
753
0
    honouredFrame = &aScrollFrame;
754
0
  }
755
0
756
0
  WritingMode writingMode = honouredFrame->GetWritingMode();
757
0
  WritingMode::BlockDir blockDir = writingMode.GetBlockDir();
758
0
  WritingMode::InlineDir inlineDir = writingMode.GetInlineDir();
759
0
  // Get whether the honoured frame's content in the horizontal direction starts
760
0
  // from right to left(E.g. it's true either if "writing-mode: vertical-rl", or
761
0
  // if "writing-mode: horizontal-tb; direction: rtl;" in CSS).
762
0
  mIsHorizontalContentRightToLeft =
763
0
    (blockDir == WritingMode::BlockDir::eBlockRL ||
764
0
     (blockDir == WritingMode::BlockDir::eBlockTB &&
765
0
      inlineDir == WritingMode::InlineDir::eInlineRTL));
766
0
}
767
768
void
769
ESMAutoDirWheelDeltaAdjuster::OnAdjusted()
770
0
{
771
0
  // Adjust() only adjusted basic deltaX and deltaY, which are not enough for
772
0
  // ESM, we should continue to adjust line-or-page and overflow values.
773
0
  if (mDeltaX) {
774
0
    // A vertical scroll was adjusted to be horizontal.
775
0
    MOZ_ASSERT(0 == mDeltaY);
776
0
777
0
    mLineOrPageDeltaX = mLineOrPageDeltaY;
778
0
    mLineOrPageDeltaY = 0;
779
0
    mOverflowDeltaX = mOverflowDeltaY;
780
0
    mOverflowDeltaY = 0;
781
0
  } else {
782
0
    // A horizontal scroll was adjusted to be vertical.
783
0
    MOZ_ASSERT(0 != mDeltaY);
784
0
785
0
    mLineOrPageDeltaY = mLineOrPageDeltaX;
786
0
    mLineOrPageDeltaX = 0;
787
0
    mOverflowDeltaY = mOverflowDeltaX;
788
0
    mOverflowDeltaX = 0;
789
0
  }
790
0
  if (mIsHorizontalContentRightToLeft) {
791
0
    // If in RTL writing mode, reverse the side the scroll will go towards.
792
0
    mLineOrPageDeltaX *= -1;
793
0
    mLineOrPageDeltaY *= -1;
794
0
    mOverflowDeltaX *= -1;
795
0
    mOverflowDeltaY *= -1;
796
0
  }
797
0
}
798
799
bool
800
ESMAutoDirWheelDeltaAdjuster::CanScrollAlongXAxis() const
801
0
{
802
0
  return mScrollTargetFrame->GetPerceivedScrollingDirections() &
803
0
           nsIScrollableFrame::HORIZONTAL;
804
0
}
805
806
bool
807
ESMAutoDirWheelDeltaAdjuster::CanScrollAlongYAxis() const
808
0
{
809
0
  return mScrollTargetFrame->GetPerceivedScrollingDirections() &
810
0
           nsIScrollableFrame::VERTICAL;
811
0
}
812
813
bool
814
ESMAutoDirWheelDeltaAdjuster::CanScrollUpwards() const
815
0
{
816
0
  nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
817
0
  nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
818
0
  return static_cast<double>(scrollRange.y) < scrollPt.y;
819
0
}
820
821
bool
822
ESMAutoDirWheelDeltaAdjuster::CanScrollDownwards() const
823
0
{
824
0
  nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
825
0
  nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
826
0
  return static_cast<double>(scrollRange.YMost()) > scrollPt.y;
827
0
}
828
829
bool
830
ESMAutoDirWheelDeltaAdjuster::CanScrollLeftwards() const
831
0
{
832
0
  nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
833
0
  nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
834
0
  return static_cast<double>(scrollRange.x) < scrollPt.x;
835
0
}
836
837
bool
838
ESMAutoDirWheelDeltaAdjuster::CanScrollRightwards() const
839
0
{
840
0
  nsPoint scrollPt = mScrollTargetFrame->GetScrollPosition();
841
0
  nsRect scrollRange = mScrollTargetFrame->GetScrollRange();
842
0
  return static_cast<double>(scrollRange.XMost()) > scrollPt.x;
843
0
}
844
845
bool
846
ESMAutoDirWheelDeltaAdjuster::IsHorizontalContentRightToLeft() const
847
0
{
848
0
  return mIsHorizontalContentRightToLeft;
849
0
}
850
851
/******************************************************************/
852
/* mozilla::ESMAutoDirWheelDeltaRestorer                          */
853
/******************************************************************/
854
855
/*explicit*/
856
ESMAutoDirWheelDeltaRestorer::ESMAutoDirWheelDeltaRestorer(
857
                                WidgetWheelEvent& aEvent)
858
  : mEvent(aEvent)
859
  , mOldDeltaX(aEvent.mDeltaX)
860
  , mOldDeltaY(aEvent.mDeltaY)
861
  , mOldLineOrPageDeltaX(aEvent.mLineOrPageDeltaX)
862
  , mOldLineOrPageDeltaY(aEvent.mLineOrPageDeltaY)
863
  , mOldOverflowDeltaX(aEvent.mOverflowDeltaX)
864
  , mOldOverflowDeltaY(aEvent.mOverflowDeltaY)
865
0
{
866
0
}
867
868
ESMAutoDirWheelDeltaRestorer::~ESMAutoDirWheelDeltaRestorer()
869
0
{
870
0
  if (mOldDeltaX == mEvent.mDeltaX || mOldDeltaY == mEvent.mDeltaY) {
871
0
    // The delta of the event wasn't adjusted during the lifetime of this
872
0
    // |ESMAutoDirWheelDeltaRestorer| instance. No need to restore it.
873
0
    return;
874
0
  }
875
0
876
0
  bool forRTL = false;
877
0
878
0
  // First, restore the basic deltaX and deltaY.
879
0
  std::swap(mEvent.mDeltaX, mEvent.mDeltaY);
880
0
  if (mOldDeltaX != mEvent.mDeltaX || mOldDeltaY != mEvent.mDeltaY) {
881
0
    // If X and Y still don't equal to their original values after being
882
0
    // swapped, then it must be because they were adjusted for RTL.
883
0
    forRTL = true;
884
0
    mEvent.mDeltaX *= -1;
885
0
    mEvent.mDeltaY *= -1;
886
0
    MOZ_ASSERT(mOldDeltaX == mEvent.mDeltaX && mOldDeltaY == mEvent.mDeltaY);
887
0
  }
888
0
889
0
  if (mEvent.mDeltaX) {
890
0
    // A horizontal scroll was adjusted to be vertical during the lifetime of
891
0
    // this instance.
892
0
    MOZ_ASSERT(0 == mEvent.mDeltaY);
893
0
894
0
    // Restore the line-or-page and overflow values to be horizontal.
895
0
    mEvent.mOverflowDeltaX = mEvent.mOverflowDeltaY;
896
0
    mEvent.mLineOrPageDeltaX = mEvent.mLineOrPageDeltaY;
897
0
    if (forRTL) {
898
0
      mEvent.mOverflowDeltaX *= -1;
899
0
      mEvent.mLineOrPageDeltaX *= -1;
900
0
    }
901
0
    mEvent.mOverflowDeltaY = mOldOverflowDeltaY;
902
0
    mEvent.mLineOrPageDeltaY = mOldLineOrPageDeltaY;
903
0
  } else {
904
0
    // A vertical scroll was adjusted to be horizontal during the lifetime of
905
0
    // this instance.
906
0
    MOZ_ASSERT(0 != mEvent.mDeltaY);
907
0
908
0
    // Restore the line-or-page and overflow values to be vertical.
909
0
    mEvent.mOverflowDeltaY = mEvent.mOverflowDeltaX;
910
0
    mEvent.mLineOrPageDeltaY = mEvent.mLineOrPageDeltaX;
911
0
    if (forRTL) {
912
0
      mEvent.mOverflowDeltaY *= -1;
913
0
      mEvent.mLineOrPageDeltaY *= -1;
914
0
    }
915
0
    mEvent.mOverflowDeltaX = mOldOverflowDeltaX;
916
0
    mEvent.mLineOrPageDeltaX = mOldLineOrPageDeltaX;
917
0
  }
918
0
}
919
920
} // namespace mozilla