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