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