nsSliderFrame.cpp (48212B)
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 // 8 // Eric Vaughan 9 // Netscape Communications 10 // 11 // See documentation in associated header file 12 // 13 14 #include "nsSliderFrame.h" 15 16 #include <algorithm> 17 18 #include "mozilla/Assertions.h" // for MOZ_ASSERT 19 #include "mozilla/ComputedStyle.h" 20 #include "mozilla/DisplayPortUtils.h" 21 #include "mozilla/LookAndFeel.h" 22 #include "mozilla/MouseEvents.h" 23 #include "mozilla/Preferences.h" 24 #include "mozilla/PresShell.h" 25 #include "mozilla/SVGIntegrationUtils.h" 26 #include "mozilla/ScrollContainerFrame.h" 27 #include "mozilla/StaticPrefs_general.h" 28 #include "mozilla/StaticPrefs_layout.h" 29 #include "mozilla/StaticPrefs_slider.h" 30 #include "mozilla/dom/Document.h" 31 #include "mozilla/dom/Event.h" 32 #include "mozilla/layers/APZCCallbackHelper.h" 33 #include "mozilla/layers/AsyncDragMetrics.h" 34 #include "mozilla/layers/InputAPZContext.h" 35 #include "mozilla/layers/WebRenderLayerManager.h" 36 #include "nsCOMPtr.h" 37 #include "nsCSSRendering.h" 38 #include "nsContentUtils.h" 39 #include "nsDeviceContext.h" 40 #include "nsDisplayList.h" 41 #include "nsHTMLParts.h" 42 #include "nsIContent.h" 43 #include "nsIScrollbarMediator.h" 44 #include "nsISupportsImpl.h" 45 #include "nsLayoutUtils.h" 46 #include "nsPresContext.h" 47 #include "nsRefreshDriver.h" // for nsAPostRefreshObserver 48 #include "nsRepeatService.h" 49 #include "nsScrollbarButtonFrame.h" 50 #include "nsScrollbarFrame.h" 51 52 using namespace mozilla; 53 using namespace mozilla::gfx; 54 using mozilla::dom::Document; 55 using mozilla::dom::Event; 56 using mozilla::layers::AsyncDragMetrics; 57 using mozilla::layers::InputAPZContext; 58 using mozilla::layers::ScrollbarData; 59 using mozilla::layers::ScrollDirection; 60 61 bool nsSliderFrame::gMiddlePref = false; 62 63 // Turn this on if you want to debug slider frames. 64 #undef DEBUG_SLIDER 65 66 nsIFrame* NS_NewSliderFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 67 return new (aPresShell) nsSliderFrame(aStyle, aPresShell->GetPresContext()); 68 } 69 70 NS_IMPL_FRAMEARENA_HELPERS(nsSliderFrame) 71 72 NS_QUERYFRAME_HEAD(nsSliderFrame) 73 NS_QUERYFRAME_ENTRY(nsSliderFrame) 74 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 75 76 nsSliderFrame::nsSliderFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) 77 : nsContainerFrame(aStyle, aPresContext, kClassID), 78 mRatio(0.0f), 79 mDragStart(0), 80 mThumbStart(0), 81 mRepeatDirection(0), 82 mScrollingWithAPZ(false), 83 mSuppressionActive(false), 84 mThumbMinLength(0) {} 85 86 // stop timer 87 nsSliderFrame::~nsSliderFrame() { 88 if (mSuppressionActive) { 89 if (auto* presShell = PresShell()) { 90 presShell->SuppressDisplayport(false); 91 } 92 } 93 } 94 95 void nsSliderFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 96 nsIFrame* aPrevInFlow) { 97 nsContainerFrame::Init(aContent, aParent, aPrevInFlow); 98 99 static bool gotPrefs = false; 100 if (!gotPrefs) { 101 gotPrefs = true; 102 103 gMiddlePref = Preferences::GetBool("middlemouse.scrollbarPosition"); 104 } 105 } 106 107 void nsSliderFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID, 108 nsIFrame* aOldFrame) { 109 nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame); 110 if (mFrames.IsEmpty()) { 111 RemoveListener(); 112 } 113 } 114 115 void nsSliderFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, 116 const nsLineList::iterator* aPrevFrameLine, 117 nsFrameList&& aFrameList) { 118 bool wasEmpty = mFrames.IsEmpty(); 119 nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, 120 std::move(aFrameList)); 121 if (wasEmpty) { 122 AddListener(); 123 } 124 } 125 126 void nsSliderFrame::AppendFrames(ChildListID aListID, 127 nsFrameList&& aFrameList) { 128 // If we have no children and on was added then make sure we add the 129 // listener 130 bool wasEmpty = mFrames.IsEmpty(); 131 nsContainerFrame::AppendFrames(aListID, std::move(aFrameList)); 132 if (wasEmpty) { 133 AddListener(); 134 } 135 } 136 137 namespace mozilla { 138 139 // Draw any tick marks that show the position of find in page results. 140 class nsDisplaySliderMarks final : public nsPaintedDisplayItem { 141 public: 142 nsDisplaySliderMarks(nsDisplayListBuilder* aBuilder, nsSliderFrame* aFrame) 143 : nsPaintedDisplayItem(aBuilder, aFrame) { 144 MOZ_COUNT_CTOR(nsDisplaySliderMarks); 145 } 146 147 MOZ_COUNTED_DTOR_FINAL(nsDisplaySliderMarks) 148 149 NS_DISPLAY_DECL_NAME("SliderMarks", TYPE_SLIDER_MARKS) 150 151 void PaintMarks(nsDisplayListBuilder* aDisplayListBuilder, 152 wr::DisplayListBuilder* aBuilder, gfxContext* aCtx); 153 154 nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override { 155 *aSnap = false; 156 return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); 157 } 158 159 bool CreateWebRenderCommands( 160 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, 161 const StackingContextHelper& aSc, 162 layers::RenderRootStateManager* aManager, 163 nsDisplayListBuilder* aDisplayListBuilder) override; 164 165 void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; 166 }; 167 168 // This is shared between the webrender and Paint() paths. For the former, 169 // aBuilder should be assigned and aCtx will be null. For the latter, aBuilder 170 // should be null and aCtx should be the gfxContext for painting. 171 void nsDisplaySliderMarks::PaintMarks(nsDisplayListBuilder* aDisplayListBuilder, 172 wr::DisplayListBuilder* aBuilder, 173 gfxContext* aCtx) { 174 DrawTarget* drawTarget = nullptr; 175 if (aCtx) { 176 drawTarget = aCtx->GetDrawTarget(); 177 } else { 178 MOZ_ASSERT(aBuilder); 179 } 180 181 Document* doc = mFrame->GetContent()->GetUncomposedDoc(); 182 if (!doc) { 183 return; 184 } 185 186 nsGlobalWindowInner* window = 187 nsGlobalWindowInner::Cast(doc->GetInnerWindow()); 188 if (!window) { 189 return; 190 } 191 192 auto* sliderFrame = static_cast<nsSliderFrame*>(mFrame); 193 int32_t maxPos = sliderFrame->Scrollbar()->GetMaxPos(); 194 195 // Use the text highlight color for the tick marks. 196 nscolor highlightColor = 197 LookAndFeel::Color(LookAndFeel::ColorID::TextHighlightBackground, mFrame); 198 DeviceColor fillColor = ToDeviceColor(highlightColor); 199 fillColor.a = 0.3; // make the mark mostly transparent 200 201 int32_t appUnitsPerDevPixel = 202 sliderFrame->PresContext()->AppUnitsPerDevPixel(); 203 nsRect sliderRect = sliderFrame->GetRect(); 204 205 nsPoint refPoint = aDisplayListBuilder->ToReferenceFrame(mFrame); 206 207 // Increase the height of the tick mark rectangle by one pixel. If the 208 // desktop scale is greater than 1, it should be increased more. 209 // The tick marks should be drawn ignoring any page zoom that is applied. 210 float increasePixels = sliderFrame->PresContext() 211 ->DeviceContext() 212 ->GetDesktopToDeviceScale() 213 .scale; 214 const bool isHorizontal = sliderFrame->Scrollbar()->IsHorizontal(); 215 float increasePixelsX = isHorizontal ? increasePixels : 0; 216 float increasePixelsY = isHorizontal ? 0 : increasePixels; 217 nsSize initialSize = 218 isHorizontal ? nsSize(0, sliderRect.height) : nsSize(sliderRect.width, 0); 219 220 nsTArray<uint32_t>& marks = window->GetScrollMarks(); 221 for (uint32_t m = 0; m < marks.Length(); m++) { 222 uint32_t markValue = marks[m]; 223 if (markValue > (uint32_t)maxPos) { 224 markValue = maxPos; 225 } 226 227 // The values in the marks array range up to the window's 228 // scrollMax{X,Y} - scrollMin{X,Y} (the same as the slider's maxpos). 229 // Scale the values to fit within the slider's width or height. 230 nsRect markRect(refPoint, initialSize); 231 if (isHorizontal) { 232 markRect.x += (nscoord)((double)markValue / maxPos * sliderRect.width); 233 } else { 234 markRect.y += (nscoord)((double)markValue / maxPos * sliderRect.height); 235 } 236 237 if (drawTarget) { 238 Rect devPixelRect = 239 NSRectToSnappedRect(markRect, appUnitsPerDevPixel, *drawTarget); 240 devPixelRect.Inflate(increasePixelsX, increasePixelsY); 241 drawTarget->FillRect(devPixelRect, ColorPattern(fillColor)); 242 } else { 243 LayoutDeviceIntRect dRect = LayoutDeviceIntRect::FromAppUnitsToNearest( 244 markRect, appUnitsPerDevPixel); 245 dRect.Inflate(increasePixelsX, increasePixelsY); 246 wr::LayoutRect layoutRect = wr::ToLayoutRect(dRect); 247 aBuilder->PushRect(layoutRect, layoutRect, BackfaceIsHidden(), false, 248 false, wr::ToColorF(fillColor)); 249 } 250 } 251 } 252 253 bool nsDisplaySliderMarks::CreateWebRenderCommands( 254 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, 255 const StackingContextHelper& aSc, layers::RenderRootStateManager* aManager, 256 nsDisplayListBuilder* aDisplayListBuilder) { 257 PaintMarks(aDisplayListBuilder, &aBuilder, nullptr); 258 return true; 259 } 260 261 void nsDisplaySliderMarks::Paint(nsDisplayListBuilder* aBuilder, 262 gfxContext* aCtx) { 263 PaintMarks(aBuilder, nullptr, aCtx); 264 } 265 266 } // namespace mozilla 267 268 void nsSliderFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 269 const nsDisplayListSet& aLists) { 270 if (aBuilder->IsForEventDelivery() && IsDraggingThumb()) { 271 // This is EVIL, we shouldn't be messing with event delivery just to get 272 // thumb mouse drag events to arrive at the slider! 273 aLists.Outlines()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, this); 274 return; 275 } 276 277 DisplayBorderBackgroundOutline(aBuilder, aLists); 278 279 if (nsIFrame* thumb = mFrames.FirstChild()) { 280 BuildDisplayListForThumb(aBuilder, thumb, aLists); 281 } 282 283 // If this is an scrollbar for the root frame, draw any markers. 284 // Markers are not drawn for other scrollbars. 285 // XXX seems like this should be done in nsScrollbarFrame instead perhaps? 286 if (!aBuilder->IsForEventDelivery()) { 287 nsScrollbarFrame* scrollbar = Scrollbar(); 288 if (ScrollContainerFrame* scrollContainerFrame = 289 do_QueryFrame(scrollbar->GetParent())) { 290 if (scrollContainerFrame->IsRootScrollFrameOfDocument()) { 291 nsGlobalWindowInner* window = nsGlobalWindowInner::Cast( 292 PresContext()->Document()->GetInnerWindow()); 293 if (window && 294 window->GetScrollMarksOnHScrollbar() == scrollbar->IsHorizontal() && 295 window->GetScrollMarks().Length() > 0) { 296 aLists.Content()->AppendNewToTop<nsDisplaySliderMarks>(aBuilder, 297 this); 298 } 299 } 300 } 301 } 302 } 303 304 static bool UsesCustomScrollbarMediator(nsIFrame* scrollbarBox) { 305 if (nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox)) { 306 if (nsIScrollbarMediator* mediator = 307 scrollbarFrame->GetScrollbarMediator()) { 308 // Note we can't queryframe from nsIScrollbarMediator to 309 // ScrollContainerFrame directly due to an optimization in the queryframe 310 // implementation for ScrollContainerFrame. 311 nsIFrame* mediatorAsFrame = do_QueryFrame(mediator); 312 ScrollContainerFrame* scrollContainerFrame = 313 do_QueryFrame(mediatorAsFrame); 314 // The scrollbar mediator is not the scroll container frame. 315 // That means this scroll container frame has a custom scrollbar mediator. 316 if (!scrollContainerFrame) { 317 return true; 318 } 319 } 320 } 321 return false; 322 } 323 324 void nsSliderFrame::BuildDisplayListForThumb(nsDisplayListBuilder* aBuilder, 325 nsIFrame* aThumb, 326 const nsDisplayListSet& aLists) { 327 nsRect thumbRect(aThumb->GetRect()); 328 329 nsRect sliderTrack = GetRect(); 330 if (sliderTrack.width < thumbRect.width || 331 sliderTrack.height < thumbRect.height) { 332 return; 333 } 334 335 // If this scrollbar is the scrollbar of an actively scrolled scroll frame, 336 // layerize the scrollbar thumb, wrap it in its own ContainerLayer and 337 // attach scrolling information to it. 338 // We do this here and not in the thumb's BuildDisplayList so that the event 339 // region that gets created for the thumb is included in the nsDisplayOwnLayer 340 // contents. 341 342 const layers::ScrollableLayerGuid::ViewID scrollTargetId = 343 aBuilder->GetCurrentScrollbarTarget(); 344 const bool thumbGetsLayer = 345 scrollTargetId != layers::ScrollableLayerGuid::NULL_SCROLL_ID; 346 347 if (thumbGetsLayer) { 348 const Maybe<ScrollDirection> scrollDirection = 349 aBuilder->GetCurrentScrollbarDirection(); 350 MOZ_ASSERT(scrollDirection.isSome()); 351 const bool isHorizontal = *scrollDirection == ScrollDirection::eHorizontal; 352 const OuterCSSCoord thumbLength = OuterCSSPixel::FromAppUnits( 353 isHorizontal ? thumbRect.width : thumbRect.height); 354 const OuterCSSCoord minThumbLength = 355 OuterCSSPixel::FromAppUnits(mThumbMinLength); 356 357 nsIFrame* scrollbarBox = Scrollbar(); 358 bool isAsyncDraggable = !UsesCustomScrollbarMediator(scrollbarBox); 359 360 nsPoint scrollPortOrigin; 361 if (ScrollContainerFrame* scrollContainerFrame = 362 do_QueryFrame(scrollbarBox->GetParent())) { 363 scrollPortOrigin = scrollContainerFrame->GetScrollPortRect().TopLeft(); 364 } else { 365 isAsyncDraggable = false; 366 } 367 368 // This rect is the range in which the scroll thumb can slide in. 369 sliderTrack = sliderTrack + scrollbarBox->GetPosition() - scrollPortOrigin; 370 const OuterCSSCoord sliderTrackStart = OuterCSSPixel::FromAppUnits( 371 isHorizontal ? sliderTrack.x : sliderTrack.y); 372 const OuterCSSCoord sliderTrackLength = OuterCSSPixel::FromAppUnits( 373 isHorizontal ? sliderTrack.width : sliderTrack.height); 374 const OuterCSSCoord thumbStart = 375 OuterCSSPixel::FromAppUnits(isHorizontal ? thumbRect.x : thumbRect.y); 376 377 const nsRect overflow = aThumb->InkOverflowRectRelativeToParent(); 378 nsSize refSize = aBuilder->RootReferenceFrame()->GetSize(); 379 nsRect dirty = aBuilder->GetVisibleRect().Intersect(thumbRect); 380 dirty = nsLayoutUtils::ComputePartialPrerenderArea( 381 aThumb, aBuilder->GetVisibleRect(), overflow, refSize); 382 383 nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList( 384 aBuilder, this, dirty, dirty); 385 386 // Clip the thumb layer to the slider track. This is necessary to ensure 387 // FrameLayerBuilder is able to merge content before and after the 388 // scrollframe into the same layer (otherwise it thinks the thumb could 389 // potentially move anywhere within the existing clip). 390 DisplayListClipState::AutoSaveRestore thumbClipState(aBuilder); 391 thumbClipState.ClipContainingBlockDescendants( 392 GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this)); 393 394 // Have the thumb's container layer capture the current clip, so 395 // it doesn't apply to the thumb's contents. This allows the contents 396 // to be fully rendered even if they're partially or fully offscreen, 397 // so async scrolling can still bring it into view. 398 DisplayListClipState::AutoSaveRestore thumbContentsClipState(aBuilder); 399 thumbContentsClipState.Clear(); 400 401 nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder); 402 nsDisplayListCollection tempLists(aBuilder); 403 BuildDisplayListForChild(aBuilder, aThumb, tempLists); 404 405 // This is a bit of a hack. Collect up all descendant display items 406 // and merge them into a single Content() list. 407 nsDisplayList masterList(aBuilder); 408 masterList.AppendToTop(tempLists.BorderBackground()); 409 masterList.AppendToTop(tempLists.BlockBorderBackgrounds()); 410 masterList.AppendToTop(tempLists.Floats()); 411 masterList.AppendToTop(tempLists.Content()); 412 masterList.AppendToTop(tempLists.PositionedDescendants()); 413 masterList.AppendToTop(tempLists.Outlines()); 414 415 // Restore the saved clip so it applies to the thumb container layer. 416 thumbContentsClipState.Restore(); 417 418 // Wrap the list to make it its own layer. 419 const ActiveScrolledRoot* ownLayerASR = contASRTracker.GetContainerASR(); 420 aLists.Content()->AppendNewToTopWithIndex<nsDisplayOwnLayer>( 421 aBuilder, this, 422 /* aIndex = */ nsDisplayOwnLayer::OwnLayerForScrollThumb, &masterList, 423 ownLayerASR, nsDisplayItem::ContainerASRType::AncestorOfContained, 424 nsDisplayOwnLayerFlags::None, 425 ScrollbarData::CreateForThumb(*scrollDirection, GetThumbRatio(), 426 thumbStart, thumbLength, minThumbLength, 427 isAsyncDraggable, sliderTrackStart, 428 sliderTrackLength, scrollTargetId), 429 true, false); 430 431 return; 432 } 433 434 BuildDisplayListForChild(aBuilder, aThumb, aLists); 435 } 436 437 void nsSliderFrame::Reflow(nsPresContext* aPresContext, 438 ReflowOutput& aDesiredSize, 439 const ReflowInput& aReflowInput, 440 nsReflowStatus& aStatus) { 441 MarkInReflow(); 442 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 443 NS_ASSERTION(aReflowInput.AvailableWidth() != NS_UNCONSTRAINEDSIZE, 444 "Bogus avail width"); 445 NS_ASSERTION(aReflowInput.AvailableHeight() != NS_UNCONSTRAINEDSIZE, 446 "Bogus avail height"); 447 448 const auto wm = GetWritingMode(); 449 450 // We always take all the space we're given. 451 aDesiredSize.SetSize(wm, aReflowInput.ComputedSize(wm)); 452 aDesiredSize.SetOverflowAreasToDesiredBounds(); 453 454 // Get the thumb, should be our only child. 455 nsIFrame* thumbBox = mFrames.FirstChild(); 456 if (NS_WARN_IF(!thumbBox)) { 457 return; 458 } 459 460 const bool horizontal = Scrollbar()->IsHorizontal(); 461 nsSize availSize = aDesiredSize.PhysicalSize(); 462 ReflowInput thumbRI(aPresContext, aReflowInput, thumbBox, 463 aReflowInput.AvailableSize(wm)); 464 465 // Get the thumb's pref size. 466 nsSize thumbSize = thumbRI.ComputedMinSize(wm).GetPhysicalSize(wm); 467 if (horizontal) { 468 thumbSize.height = availSize.height; 469 } else { 470 thumbSize.width = availSize.width; 471 } 472 473 int32_t curPos = Scrollbar()->GetCurPos(); 474 int32_t maxPos = Scrollbar()->GetMaxPos(); 475 int32_t pageIncrement = Scrollbar()->GetPageIncrement(); 476 477 curPos = std::min(curPos, maxPos); 478 479 // If modifying the logic here, be sure to modify the corresponding 480 // compositor-side calculation in ScrollThumbUtils::ApplyTransformForAxis(). 481 nscoord& availableLength = horizontal ? availSize.width : availSize.height; 482 nscoord& thumbLength = horizontal ? thumbSize.width : thumbSize.height; 483 mThumbMinLength = thumbLength; 484 485 if (pageIncrement + maxPos > 0) { 486 float ratio = float(pageIncrement) / float(maxPos + pageIncrement); 487 thumbLength = 488 std::max(thumbLength, NSToCoordRound(availableLength * ratio)); 489 } 490 491 // Round the thumb's length to device pixels. 492 nsPresContext* presContext = PresContext(); 493 thumbLength = presContext->DevPixelsToAppUnits( 494 presContext->AppUnitsToDevPixels(thumbLength)); 495 496 // mRatio translates the thumb position in app units to the value. 497 mRatio = maxPos ? float(availableLength - thumbLength) / float(maxPos) : 1; 498 499 // set the thumb's coord to be the current pos * the ratio. 500 nsPoint thumbPos; 501 if (horizontal) { 502 thumbPos.x = NSToCoordRound(curPos * mRatio); 503 } else { 504 thumbPos.y = NSToCoordRound(curPos * mRatio); 505 } 506 507 // Same to `snappedThumbLocation` in `nsSliderFrame::CurrentPositionChanged`, 508 // to avoid putting the scroll thumb at subpixel positions which cause 509 // needless invalidations 510 nscoord appUnitsPerPixel = PresContext()->AppUnitsPerDevPixel(); 511 thumbPos = 512 ToAppUnits(thumbPos.ToNearestPixels(appUnitsPerPixel), appUnitsPerPixel); 513 514 const LogicalPoint logicalPos(wm, thumbPos, availSize); 515 // TODO: It seems like a lot of this stuff should really belong in the thumb's 516 // reflow code rather than here, but since we rely on the frame tree structure 517 // heavily this matches the previous code more closely for now. 518 ReflowOutput thumbDesiredSize(wm); 519 const auto flags = ReflowChildFlags::Default; 520 nsReflowStatus status; 521 thumbRI.SetComputedISize(thumbSize.width); 522 thumbRI.SetComputedBSize(thumbSize.height); 523 ReflowChild(thumbBox, aPresContext, thumbDesiredSize, thumbRI, wm, logicalPos, 524 availSize, flags, status); 525 FinishReflowChild(thumbBox, aPresContext, thumbDesiredSize, &thumbRI, wm, 526 logicalPos, availSize, flags); 527 } 528 529 nsresult nsSliderFrame::HandleEvent(nsPresContext* aPresContext, 530 WidgetGUIEvent* aEvent, 531 nsEventStatus* aEventStatus) { 532 NS_ENSURE_ARG_POINTER(aEventStatus); 533 534 if (mAPZDragInitiated && 535 *mAPZDragInitiated == InputAPZContext::GetInputBlockId() && 536 aEvent->mMessage == eMouseDown) { 537 // If we get the mousedown after the APZ notification, then immediately 538 // switch into the state corresponding to an APZ thumb-drag. Note that 539 // we can't just do this in AsyncScrollbarDragInitiated() directly because 540 // the handling for this mousedown event in the presShell will reset the 541 // capturing content which makes isDraggingThumb() return false. We check 542 // the input block here to make sure that we correctly handle any ordering 543 // of {eMouseDown arriving, AsyncScrollbarDragInitiated() being called}. 544 mAPZDragInitiated = Nothing(); 545 DragThumb(true); 546 mScrollingWithAPZ = true; 547 return NS_OK; 548 } 549 550 // If a web page calls event.preventDefault() we still want to 551 // scroll when scroll arrow is clicked. See bug 511075. 552 if (!mContent->IsInNativeAnonymousSubtree() && 553 nsEventStatus_eConsumeNoDefault == *aEventStatus) { 554 return NS_OK; 555 } 556 557 if (mDragInProgress && !IsDraggingThumb()) { 558 StopDrag(); 559 return NS_OK; 560 } 561 562 nsScrollbarFrame* scrollbarBox = Scrollbar(); 563 bool isHorizontal = scrollbarBox->IsHorizontal(); 564 565 if (IsDraggingThumb()) { 566 switch (aEvent->mMessage) { 567 case eTouchMove: 568 case eMouseMove: { 569 if (mScrollingWithAPZ) { 570 break; 571 } 572 nsPoint eventPoint; 573 if (!GetEventPoint(aEvent, eventPoint)) { 574 break; 575 } 576 if (mRepeatDirection) { 577 // On Linux the destination point is determined by the initial click 578 // on the scrollbar track and doesn't change until the mouse button 579 // is released. 580 #ifndef MOZ_WIDGET_GTK 581 // On the other platforms we need to update the destination point now. 582 mDestinationPoint = eventPoint; 583 StopRepeat(); 584 StartRepeat(); 585 #endif 586 break; 587 } 588 589 nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y; 590 591 nsIFrame* thumbFrame = mFrames.FirstChild(); 592 if (!thumbFrame) { 593 return NS_OK; 594 } 595 596 // take our current position and subtract the start location 597 pos -= mDragStart; 598 bool isMouseOutsideThumb = false; 599 const int32_t snapMultiplier = StaticPrefs::slider_snapMultiplier(); 600 if (snapMultiplier) { 601 nsSize thumbSize = thumbFrame->GetSize(); 602 if (isHorizontal) { 603 // horizontal scrollbar - check if mouse is above or below thumb 604 // XXXbz what about looking at the .y of the thumb's rect? Is that 605 // always zero here? 606 if (eventPoint.y < -snapMultiplier * thumbSize.height || 607 eventPoint.y > 608 thumbSize.height + snapMultiplier * thumbSize.height) { 609 isMouseOutsideThumb = true; 610 } 611 } else { 612 // vertical scrollbar - check if mouse is left or right of thumb 613 if (eventPoint.x < -snapMultiplier * thumbSize.width || 614 eventPoint.x > 615 thumbSize.width + snapMultiplier * thumbSize.width) { 616 isMouseOutsideThumb = true; 617 } 618 } 619 } 620 if (aEvent->mClass == eTouchEventClass) { 621 *aEventStatus = nsEventStatus_eConsumeNoDefault; 622 } 623 if (isMouseOutsideThumb) { 624 SetCurrentThumbPosition(mThumbStart); 625 return NS_OK; 626 } 627 628 // set it 629 SetCurrentThumbPosition(pos); 630 } break; 631 632 case eTouchEnd: 633 case eMouseUp: 634 if (ShouldScrollForEvent(aEvent)) { 635 StopDrag(); 636 // we MUST call nsFrame HandleEvent for mouse ups to maintain the 637 // selection state and capture state. 638 return nsIFrame::HandleEvent(aPresContext, aEvent, aEventStatus); 639 } 640 break; 641 642 default: 643 break; 644 } 645 646 // return nsIFrame::HandleEvent(aPresContext, aEvent, aEventStatus); 647 return NS_OK; 648 } 649 650 if (ShouldScrollToClickForEvent(aEvent)) { 651 nsPoint eventPoint; 652 if (!GetEventPoint(aEvent, eventPoint)) { 653 return NS_OK; 654 } 655 nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y; 656 657 // adjust so that the middle of the thumb is placed under the click 658 nsIFrame* thumbFrame = mFrames.FirstChild(); 659 if (!thumbFrame) { 660 return NS_OK; 661 } 662 nsSize thumbSize = thumbFrame->GetSize(); 663 nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height; 664 665 // set it 666 AutoWeakFrame weakFrame(this); 667 SetCurrentThumbPosition(pos - thumbLength / 2); 668 NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); 669 670 DragThumb(true); 671 672 if (aEvent->mClass == eTouchEventClass) { 673 *aEventStatus = nsEventStatus_eConsumeNoDefault; 674 } 675 676 SetupDrag(aEvent, thumbFrame, pos, isHorizontal); 677 } 678 #ifdef MOZ_WIDGET_GTK 679 else if (ShouldScrollForEvent(aEvent) && aEvent->mClass == eMouseEventClass && 680 aEvent->AsMouseEvent()->mButton == MouseButton::eSecondary) { 681 // HandlePress and HandleRelease are usually called via 682 // nsIFrame::HandleEvent, but only for the left mouse button. 683 if (aEvent->mMessage == eMouseDown) { 684 HandlePress(aPresContext, aEvent, aEventStatus); 685 } else if (aEvent->mMessage == eMouseUp) { 686 HandleRelease(aPresContext, aEvent, aEventStatus); 687 } 688 689 return NS_OK; 690 } 691 #endif 692 693 // XXX hack until handle release is actually called in nsframe. 694 // if (aEvent->mMessage == eMouseOut || 695 // aEvent->mMessage == NS_MOUSE_RIGHT_BUTTON_UP || 696 // aEvent->mMessage == NS_MOUSE_LEFT_BUTTON_UP) { 697 // HandleRelease(aPresContext, aEvent, aEventStatus); 698 // } 699 700 if (aEvent->mMessage == eMouseOut && mRepeatDirection) { 701 HandleRelease(aPresContext, aEvent, aEventStatus); 702 } 703 704 return nsIFrame::HandleEvent(aPresContext, aEvent, aEventStatus); 705 } 706 707 // Helper function to collect the "scroll to click" metric. Beware of 708 // caching this, users expect to be able to change the system preference 709 // and see the browser change its behavior immediately. 710 bool nsSliderFrame::GetScrollToClick() { 711 return LookAndFeel::GetInt(LookAndFeel::IntID::ScrollToClick, false); 712 } 713 714 nsScrollbarFrame* nsSliderFrame::Scrollbar() const { 715 MOZ_ASSERT(GetParent()); 716 MOZ_DIAGNOSTIC_ASSERT( 717 static_cast<nsScrollbarFrame*>(do_QueryFrame(GetParent()))); 718 return static_cast<nsScrollbarFrame*>(GetParent()); 719 } 720 721 // called when the current position changed and we need to update the thumb's 722 // location 723 void nsSliderFrame::CurrentPositionChanged() { 724 // get the current position 725 // get our current min and max position from our content node 726 int32_t curPos = Scrollbar()->GetCurPos(); 727 int32_t maxPos = Scrollbar()->GetMaxPos(); 728 729 curPos = std::min(curPos, maxPos); 730 731 // get the thumb's rect 732 nsIFrame* thumbFrame = mFrames.FirstChild(); 733 if (!thumbFrame) { 734 return; 735 } 736 737 const bool horizontal = Scrollbar()->IsHorizontal(); 738 739 // figure out the new rect 740 nsRect thumbRect = thumbFrame->GetRect(); 741 nsRect newThumbRect(thumbRect); 742 if (horizontal) { 743 newThumbRect.x = NSToCoordRound(curPos * mRatio); 744 } else { 745 newThumbRect.y = NSToCoordRound(curPos * mRatio); 746 } 747 748 // avoid putting the scroll thumb at subpixel positions which cause needless 749 // invalidations 750 nscoord appUnitsPerPixel = PresContext()->AppUnitsPerDevPixel(); 751 nsPoint snappedThumbLocation = 752 ToAppUnits(newThumbRect.TopLeft().ToNearestPixels(appUnitsPerPixel), 753 appUnitsPerPixel); 754 if (horizontal) { 755 newThumbRect.x = snappedThumbLocation.x; 756 } else { 757 newThumbRect.y = snappedThumbLocation.y; 758 } 759 760 // set the rect 761 // XXX This out-of-band update of the frame tree is rather fishy! 762 thumbFrame->SetRect(newThumbRect); 763 764 // When the thumb changes position, the mThumbStart value stored in 765 // ScrollbarData for the purpose of telling APZ about the thumb 766 // position painted by the main thread is invalidated. The ScrollbarData 767 // is stored on the nsDisplayOwnLayer item built by *this* frame, so 768 // we need to mark this frame as needing its fisplay item rebuilt. 769 MarkNeedsDisplayItemRebuild(); 770 771 // Request a repaint of the scrollbar 772 nsIScrollbarMediator* mediator = Scrollbar()->GetScrollbarMediator(); 773 if (!mediator || !mediator->ShouldSuppressScrollbarRepaints()) { 774 SchedulePaint(); 775 } 776 } 777 778 // Use this function when you want to set the scroll position via the position 779 // of the scrollbar thumb, e.g. when dragging the slider. This function scrolls 780 // the content in such a way that thumbRect.x/.y becomes aNewThumbPos. 781 void nsSliderFrame::SetCurrentThumbPosition(nscoord aNewPos) { 782 nsScrollbarFrame* sb = Scrollbar(); 783 int32_t newPos = NSToIntRound(aNewPos / mRatio); 784 // get min and max position from our content node 785 int32_t maxpos = sb->GetMaxPos(); 786 787 // get the new position and make sure it is in bounds 788 if (newPos < 0) { 789 newPos = 0; 790 } else if (newPos > maxpos) { 791 newPos = maxpos; 792 } 793 AutoWeakFrame weakFrame(this); 794 795 nsIScrollbarMediator* mediator = sb->GetScrollbarMediator(); 796 if (!mediator) { 797 return; 798 } 799 mediator->ThumbMoved(sb, CSSPixel::ToAppUnits(sb->GetCurPos()), 800 CSSPixel::ToAppUnits(newPos)); 801 if (!weakFrame.IsAlive()) { 802 return; 803 } 804 sb->SetCurPos(newPos); 805 } 806 807 void nsSliderFrame::SetInitialChildList(ChildListID aListID, 808 nsFrameList&& aChildList) { 809 nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList)); 810 if (aListID == FrameChildListID::Principal) { 811 AddListener(); 812 } 813 } 814 815 nsresult nsSliderMediator::HandleEvent(dom::Event* aEvent) { 816 // Only process the event if the thumb is not being dragged. 817 if (mSlider && !mSlider->IsDraggingThumb()) { 818 return mSlider->StartDrag(aEvent); 819 } 820 return NS_OK; 821 } 822 823 static bool ScrollFrameWillBuildScrollInfoLayer(nsIFrame* aScrollFrame) { 824 /* 825 * Note: if changing the conditions in this function, make a corresponding 826 * change to nsDisplayListBuilder::ShouldBuildScrollInfoItemsForHoisting() 827 * in nsDisplayList.cpp. 828 */ 829 nsIFrame* current = aScrollFrame; 830 while (current) { 831 if (SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(current)) { 832 return true; 833 } 834 current = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(current); 835 } 836 return false; 837 } 838 839 ScrollContainerFrame* nsSliderFrame::GetScrollContainerFrame() { 840 return do_QueryFrame(Scrollbar()->GetParent()); 841 } 842 843 void nsSliderFrame::StartAPZDrag(WidgetGUIEvent* aEvent) { 844 if (!aEvent->mFlags.mHandledByAPZ) { 845 return; 846 } 847 848 if (!gfxPlatform::GetPlatform()->SupportsApzDragInput()) { 849 return; 850 } 851 852 if (aEvent->AsMouseEvent() && 853 aEvent->AsMouseEvent()->mButton != MouseButton::ePrimary) { 854 return; 855 } 856 857 nsIFrame* scrollbarBox = Scrollbar(); 858 nsContainerFrame* scrollFrame = scrollbarBox->GetParent(); 859 if (!scrollFrame) { 860 return; 861 } 862 863 nsIContent* scrollableContent = scrollFrame->GetContent(); 864 if (!scrollableContent) { 865 return; 866 } 867 868 // APZ dragging requires the scrollbar to be layerized, which doesn't 869 // happen for scroll info layers. 870 if (ScrollFrameWillBuildScrollInfoLayer(scrollFrame)) { 871 return; 872 } 873 874 // Custom scrollbar mediators are not supported in the APZ codepath. 875 if (UsesCustomScrollbarMediator(scrollbarBox)) { 876 return; 877 } 878 879 bool isHorizontal = Scrollbar()->IsHorizontal(); 880 881 layers::ScrollableLayerGuid::ViewID scrollTargetId; 882 bool hasID = nsLayoutUtils::FindIDFor(scrollableContent, &scrollTargetId); 883 bool hasAPZView = 884 hasID && scrollTargetId != layers::ScrollableLayerGuid::NULL_SCROLL_ID; 885 886 if (!hasAPZView) { 887 return; 888 } 889 890 if (!DisplayPortUtils::HasNonMinimalDisplayPort(scrollableContent)) { 891 return; 892 } 893 894 auto* presShell = PresShell(); 895 uint64_t inputblockId = InputAPZContext::GetInputBlockId(); 896 uint32_t presShellId = presShell->GetPresShellId(); 897 AsyncDragMetrics dragMetrics( 898 scrollTargetId, presShellId, inputblockId, 899 OuterCSSPixel::FromAppUnits(mDragStart), 900 isHorizontal ? ScrollDirection::eHorizontal : ScrollDirection::eVertical); 901 902 // It's important to set this before calling 903 // nsIWidget::StartAsyncScrollbarDrag(), because in some configurations, that 904 // can call AsyncScrollbarDragRejected() synchronously, which clears the flag 905 // (and we want it to stay cleared in that case). 906 mScrollingWithAPZ = true; 907 908 // When we start an APZ drag, we wont get mouse events for the drag. 909 // APZ will consume them all and only notify us of the new scroll position. 910 bool waitForRefresh = InputAPZContext::HavePendingLayerization(); 911 nsIWidget* widget = this->GetNearestWidget(); 912 if (waitForRefresh) { 913 waitForRefresh = false; 914 if (nsPresContext* presContext = presShell->GetPresContext()) { 915 presContext->RegisterManagedPostRefreshObserver( 916 new ManagedPostRefreshObserver( 917 presContext, [widget = RefPtr<nsIWidget>(widget), 918 dragMetrics](bool aWasCanceled) { 919 if (!aWasCanceled) { 920 widget->StartAsyncScrollbarDrag(dragMetrics); 921 } 922 return ManagedPostRefreshObserver::Unregister::Yes; 923 })); 924 waitForRefresh = true; 925 } 926 } 927 if (!waitForRefresh) { 928 widget->StartAsyncScrollbarDrag(dragMetrics); 929 } 930 } 931 932 nsresult nsSliderFrame::StartDrag(Event* aEvent) { 933 #ifdef DEBUG_SLIDER 934 printf("Begin dragging\n"); 935 #endif 936 if (Scrollbar()->IsDisabled()) { 937 return NS_OK; 938 } 939 940 WidgetGUIEvent* event = aEvent->WidgetEventPtr()->AsGUIEvent(); 941 942 if (!ShouldScrollForEvent(event)) { 943 return NS_OK; 944 } 945 946 nsPoint pt; 947 if (!GetEventPoint(event, pt)) { 948 return NS_OK; 949 } 950 bool isHorizontal = Scrollbar()->IsHorizontal(); 951 nscoord pos = isHorizontal ? pt.x : pt.y; 952 953 // If we should scroll-to-click, first place the middle of the slider thumb 954 // under the mouse. 955 nscoord newpos = pos; 956 bool scrollToClick = ShouldScrollToClickForEvent(event); 957 if (scrollToClick) { 958 // adjust so that the middle of the thumb is placed under the click 959 nsIFrame* thumbFrame = mFrames.FirstChild(); 960 if (!thumbFrame) { 961 return NS_OK; 962 } 963 nsSize thumbSize = thumbFrame->GetSize(); 964 nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height; 965 966 newpos -= (thumbLength / 2); 967 } 968 969 DragThumb(true); 970 971 if (scrollToClick) { 972 SetCurrentThumbPosition(newpos); 973 } 974 975 nsIFrame* thumbFrame = mFrames.FirstChild(); 976 if (!thumbFrame) { 977 return NS_OK; 978 } 979 980 SetupDrag(event, thumbFrame, pos, isHorizontal); 981 982 return NS_OK; 983 } 984 985 nsresult nsSliderFrame::StopDrag() { 986 AddListener(); 987 DragThumb(false); 988 989 mScrollingWithAPZ = false; 990 991 UnsuppressDisplayport(); 992 993 if (mRepeatDirection) { 994 StopRepeat(); 995 mRepeatDirection = 0; 996 } 997 return NS_OK; 998 } 999 1000 void nsSliderFrame::DragThumb(bool aGrabMouseEvents) { 1001 if (mDragInProgress != aGrabMouseEvents) { 1002 Scrollbar()->ActivityChanged(aGrabMouseEvents); 1003 } 1004 mDragInProgress = aGrabMouseEvents; 1005 1006 if (aGrabMouseEvents) { 1007 PresShell::SetCapturingContent( 1008 GetContent(), 1009 CaptureFlags::IgnoreAllowedState | CaptureFlags::PreventDragStart); 1010 } else { 1011 PresShell::ReleaseCapturingContent(); 1012 } 1013 } 1014 1015 bool nsSliderFrame::IsDraggingThumb() const { 1016 return PresShell::GetCapturingContent() == GetContent(); 1017 } 1018 1019 void nsSliderFrame::AddListener() { 1020 if (!mMediator) { 1021 mMediator = new nsSliderMediator(this); 1022 } 1023 1024 nsIFrame* thumbFrame = mFrames.FirstChild(); 1025 if (!thumbFrame) { 1026 return; 1027 } 1028 thumbFrame->GetContent()->AddSystemEventListener(u"mousedown"_ns, mMediator, 1029 false, false); 1030 thumbFrame->GetContent()->AddSystemEventListener(u"touchstart"_ns, mMediator, 1031 false, false); 1032 } 1033 1034 void nsSliderFrame::RemoveListener() { 1035 NS_ASSERTION(mMediator, "No listener was ever added!!"); 1036 1037 nsIFrame* thumbFrame = mFrames.FirstChild(); 1038 if (!thumbFrame) { 1039 return; 1040 } 1041 1042 thumbFrame->GetContent()->RemoveSystemEventListener(u"mousedown"_ns, 1043 mMediator, false); 1044 thumbFrame->GetContent()->RemoveSystemEventListener(u"touchstart"_ns, 1045 mMediator, false); 1046 } 1047 1048 bool nsSliderFrame::ShouldScrollForEvent(WidgetGUIEvent* aEvent) { 1049 switch (aEvent->mMessage) { 1050 case eTouchStart: 1051 case eTouchEnd: 1052 return true; 1053 case eMouseDown: 1054 case eMouseUp: { 1055 uint16_t button = aEvent->AsMouseEvent()->mButton; 1056 #ifdef MOZ_WIDGET_GTK 1057 return (button == MouseButton::ePrimary) || 1058 (button == MouseButton::eSecondary && GetScrollToClick()) || 1059 (button == MouseButton::eMiddle && gMiddlePref && 1060 !GetScrollToClick()); 1061 #else 1062 return (button == MouseButton::ePrimary) || 1063 (button == MouseButton::eMiddle && gMiddlePref); 1064 #endif 1065 } 1066 default: 1067 return false; 1068 } 1069 } 1070 1071 bool nsSliderFrame::ShouldScrollToClickForEvent(WidgetGUIEvent* aEvent) { 1072 if (!ShouldScrollForEvent(aEvent)) { 1073 return false; 1074 } 1075 1076 if (aEvent->mMessage != eMouseDown && aEvent->mMessage != eTouchStart) { 1077 return false; 1078 } 1079 1080 #if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK) 1081 // On Mac and Linux, clicking the scrollbar thumb should never scroll to 1082 // click. 1083 if (IsEventOverThumb(aEvent)) { 1084 return false; 1085 } 1086 #endif 1087 1088 if (aEvent->mMessage == eTouchStart) { 1089 return GetScrollToClick(); 1090 } 1091 1092 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); 1093 if (mouseEvent->mButton == MouseButton::ePrimary) { 1094 #ifdef XP_MACOSX 1095 bool invertPref = mouseEvent->IsAlt(); 1096 #else 1097 bool invertPref = mouseEvent->IsShift(); 1098 #endif 1099 return GetScrollToClick() != invertPref; 1100 } 1101 1102 #ifdef MOZ_WIDGET_GTK 1103 if (mouseEvent->mButton == MouseButton::eSecondary) { 1104 return !GetScrollToClick(); 1105 } 1106 #endif 1107 1108 return true; 1109 } 1110 1111 bool nsSliderFrame::IsEventOverThumb(WidgetGUIEvent* aEvent) { 1112 nsIFrame* thumbFrame = mFrames.FirstChild(); 1113 if (!thumbFrame) { 1114 return false; 1115 } 1116 1117 nsPoint eventPoint; 1118 if (!GetEventPoint(aEvent, eventPoint)) { 1119 return false; 1120 } 1121 1122 const nsRect thumbRect = thumbFrame->GetRect(); 1123 const bool isHorizontal = Scrollbar()->IsHorizontal(); 1124 nscoord eventPos = isHorizontal ? eventPoint.x : eventPoint.y; 1125 nscoord thumbStart = isHorizontal ? thumbRect.x : thumbRect.y; 1126 nscoord thumbEnd = isHorizontal ? thumbRect.XMost() : thumbRect.YMost(); 1127 return eventPos >= thumbStart && eventPos < thumbEnd; 1128 } 1129 1130 NS_IMETHODIMP 1131 nsSliderFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent, 1132 nsEventStatus* aEventStatus) { 1133 if (!ShouldScrollForEvent(aEvent) || ShouldScrollToClickForEvent(aEvent)) { 1134 return NS_OK; 1135 } 1136 1137 if (IsEventOverThumb(aEvent)) { 1138 return NS_OK; 1139 } 1140 1141 nsIFrame* thumbFrame = mFrames.FirstChild(); 1142 if (!thumbFrame) { // display:none? 1143 return NS_OK; 1144 } 1145 1146 if (Scrollbar()->IsDisabled()) { 1147 return NS_OK; 1148 } 1149 1150 nsRect thumbRect = thumbFrame->GetRect(); 1151 1152 nscoord change = 1; 1153 nsPoint eventPoint; 1154 if (!GetEventPoint(aEvent, eventPoint)) { 1155 return NS_OK; 1156 } 1157 1158 if (Scrollbar()->IsHorizontal() ? eventPoint.x < thumbRect.x 1159 : eventPoint.y < thumbRect.y) { 1160 change = -1; 1161 } 1162 1163 mRepeatDirection = change; 1164 DragThumb(true); 1165 if (StaticPrefs::layout_scrollbars_click_and_hold_track_continue_to_end()) { 1166 // Set the destination point to the very end of the scrollbar so that 1167 // scrolling doesn't stop halfway through. 1168 if (change > 0) { 1169 mDestinationPoint = nsPoint(GetRect().width, GetRect().height); 1170 } else { 1171 mDestinationPoint = nsPoint(0, 0); 1172 } 1173 } else { 1174 mDestinationPoint = eventPoint; 1175 } 1176 StartRepeat(); 1177 PageScroll(false); 1178 1179 return NS_OK; 1180 } 1181 1182 NS_IMETHODIMP 1183 nsSliderFrame::HandleRelease(nsPresContext* aPresContext, 1184 WidgetGUIEvent* aEvent, 1185 nsEventStatus* aEventStatus) { 1186 StopRepeat(); 1187 1188 nsScrollbarFrame* sb = Scrollbar(); 1189 if (nsIScrollbarMediator* m = sb->GetScrollbarMediator()) { 1190 m->ScrollbarReleased(sb); 1191 } 1192 return NS_OK; 1193 } 1194 1195 void nsSliderFrame::Destroy(DestroyContext& aContext) { 1196 // tell our mediator if we have one we are gone. 1197 if (mMediator) { 1198 mMediator->SetSlider(nullptr); 1199 mMediator = nullptr; 1200 } 1201 StopRepeat(); 1202 1203 // call base class Destroy() 1204 nsContainerFrame::Destroy(aContext); 1205 } 1206 1207 void nsSliderFrame::Notify() { 1208 bool stop = false; 1209 1210 nsIFrame* thumbFrame = mFrames.FirstChild(); 1211 if (!thumbFrame) { 1212 StopRepeat(); 1213 return; 1214 } 1215 nsRect thumbRect = thumbFrame->GetRect(); 1216 1217 const bool isHorizontal = Scrollbar()->IsHorizontal(); 1218 1219 // See if the thumb has moved past our destination point. 1220 // if it has we want to stop. 1221 if (isHorizontal) { 1222 if (mRepeatDirection < 0) { 1223 if (thumbRect.x < mDestinationPoint.x) { 1224 stop = true; 1225 } 1226 } else { 1227 if (thumbRect.x + thumbRect.width > mDestinationPoint.x) { 1228 stop = true; 1229 } 1230 } 1231 } else { 1232 if (mRepeatDirection < 0) { 1233 if (thumbRect.y < mDestinationPoint.y) { 1234 stop = true; 1235 } 1236 } else { 1237 if (thumbRect.y + thumbRect.height > mDestinationPoint.y) { 1238 stop = true; 1239 } 1240 } 1241 } 1242 1243 if (stop) { 1244 StopRepeat(); 1245 } else { 1246 PageScroll(true); 1247 } 1248 } 1249 1250 void nsSliderFrame::PageScroll(bool aClickAndHold) { 1251 int32_t changeDirection = mRepeatDirection; 1252 nsScrollbarFrame* sb = Scrollbar(); 1253 1254 ScrollContainerFrame* sf = GetScrollContainerFrame(); 1255 const ScrollSnapFlags scrollSnapFlags = 1256 ScrollSnapFlags::IntendedDirection | ScrollSnapFlags::IntendedEndPosition; 1257 1258 // If our nsIScrollbarMediator implementation is a ScrollContainerFrame, 1259 // use ScrollTo() to ensure we do not scroll past the intended 1260 // destination. Otherwise, the combination of smooth scrolling and 1261 // ScrollBy() semantics (which adds the delta to the current destination 1262 // if there is a smooth scroll in progress) can lead to scrolling too far 1263 // (bug 1331390). 1264 // Only do this when the page scroll is triggered by the repeat timer 1265 // when the mouse is being held down. For multiple clicks in 1266 // succession, we want to make sure we scroll by a full page for 1267 // each click, so we use ScrollByPage(). 1268 if (aClickAndHold && sf) { 1269 const bool isHorizontal = sb->IsHorizontal(); 1270 1271 nsIFrame* thumbFrame = mFrames.FirstChild(); 1272 if (!thumbFrame) { 1273 return; 1274 } 1275 1276 nsRect thumbRect = thumbFrame->GetRect(); 1277 1278 nscoord maxDistanceAlongTrack; 1279 if (isHorizontal) { 1280 maxDistanceAlongTrack = 1281 mDestinationPoint.x - thumbRect.x - thumbRect.width / 2; 1282 } else { 1283 maxDistanceAlongTrack = 1284 mDestinationPoint.y - thumbRect.y - thumbRect.height / 2; 1285 } 1286 1287 // Convert distance along scrollbar track to amount of scrolled content. 1288 nscoord maxDistanceToScroll = maxDistanceAlongTrack / GetThumbRatio(); 1289 1290 const CSSIntCoord pageLength = Scrollbar()->GetPageIncrement(); 1291 1292 nsPoint pos = sf->GetScrollPosition(); 1293 1294 if (mCurrentClickHoldDestination) { 1295 // We may not have arrived at the destination of the scroll from the 1296 // previous repeat timer tick, some of that scroll may still be pending. 1297 nsPoint pendingScroll = 1298 *mCurrentClickHoldDestination - sf->GetScrollPosition(); 1299 1300 // Scroll by one page relative to the previous destination, so that we 1301 // scroll at a rate of a full page per repeat timer tick. 1302 pos += pendingScroll; 1303 1304 // Make a corresponding adjustment to the maxium distance we can scroll, 1305 // so we successfully avoid overshoot. 1306 maxDistanceToScroll -= (isHorizontal ? pendingScroll.x : pendingScroll.y); 1307 } 1308 1309 nscoord distanceToScroll = 1310 std::min(abs(maxDistanceToScroll), 1311 CSSPixel::ToAppUnits(CSSCoord(pageLength))) * 1312 changeDirection; 1313 1314 if (isHorizontal) { 1315 pos.x += distanceToScroll; 1316 } else { 1317 pos.y += distanceToScroll; 1318 } 1319 1320 mCurrentClickHoldDestination = Some(pos); 1321 sf->ScrollTo(pos, 1322 nsLayoutUtils::IsSmoothScrollingEnabled() && 1323 StaticPrefs::general_smoothScroll_pages() 1324 ? ScrollMode::Smooth 1325 : ScrollMode::Instant, 1326 nullptr, scrollSnapFlags); 1327 1328 return; 1329 } 1330 1331 if (nsIScrollbarMediator* m = sb->GetScrollbarMediator()) { 1332 sb->SetButtonScrollDirectionAndUnit(changeDirection, ScrollUnit::PAGES); 1333 m->ScrollByPage(sb, changeDirection, scrollSnapFlags); 1334 } 1335 } 1336 1337 void nsSliderFrame::SetupDrag(WidgetGUIEvent* aEvent, nsIFrame* aThumbFrame, 1338 nscoord aPos, bool aIsHorizontal) { 1339 if (aIsHorizontal) { 1340 mThumbStart = aThumbFrame->GetPosition().x; 1341 } else { 1342 mThumbStart = aThumbFrame->GetPosition().y; 1343 } 1344 1345 mDragStart = aPos - mThumbStart; 1346 1347 mScrollingWithAPZ = false; 1348 StartAPZDrag(aEvent); // sets mScrollingWithAPZ=true if appropriate 1349 1350 #ifdef DEBUG_SLIDER 1351 printf("Pressed mDragStart=%d\n", mDragStart); 1352 #endif 1353 1354 if (!mScrollingWithAPZ) { 1355 SuppressDisplayport(); 1356 } 1357 } 1358 1359 float nsSliderFrame::GetThumbRatio() const { 1360 // mRatio is in thumb app units per scrolled css pixels. Convert it to a 1361 // ratio of the thumb's CSS pixels per scrolled CSS pixels. (Note the thumb 1362 // is in the scrollframe's parent's space whereas the scrolled CSS pixels 1363 // are in the scrollframe's space). 1364 return mRatio / AppUnitsPerCSSPixel(); 1365 } 1366 1367 void nsSliderFrame::AsyncScrollbarDragInitiated(uint64_t aDragBlockId) { 1368 mAPZDragInitiated = Some(aDragBlockId); 1369 } 1370 1371 void nsSliderFrame::AsyncScrollbarDragRejected() { 1372 mScrollingWithAPZ = false; 1373 // Only suppress the displayport if we're still dragging the thumb. 1374 // Otherwise, no one will unsuppress it. 1375 if (IsDraggingThumb()) { 1376 SuppressDisplayport(); 1377 } 1378 } 1379 1380 void nsSliderFrame::SuppressDisplayport() { 1381 if (!mSuppressionActive) { 1382 PresShell()->SuppressDisplayport(true); 1383 mSuppressionActive = true; 1384 } 1385 } 1386 1387 void nsSliderFrame::UnsuppressDisplayport() { 1388 if (mSuppressionActive) { 1389 PresShell()->SuppressDisplayport(false); 1390 mSuppressionActive = false; 1391 } 1392 } 1393 1394 bool nsSliderFrame::OnlySystemGroupDispatch(EventMessage aMessage) const { 1395 // If we are in a native anonymous subtree, do not dispatch mouse-move or 1396 // pointer-move events targeted at this slider frame to web content. This 1397 // matches the behaviour of other browsers. 1398 return (aMessage == eMouseMove || aMessage == ePointerMove) && 1399 IsDraggingThumb() && GetContent()->IsInNativeAnonymousSubtree(); 1400 } 1401 1402 bool nsSliderFrame::GetEventPoint(WidgetGUIEvent* aEvent, nsPoint& aPoint) { 1403 LayoutDeviceIntPoint refPoint; 1404 if (!GetEventPoint(aEvent, refPoint)) { 1405 return false; 1406 } 1407 aPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, refPoint, 1408 RelativeTo{this}); 1409 return true; 1410 } 1411 1412 bool nsSliderFrame::GetEventPoint(WidgetGUIEvent* aEvent, 1413 LayoutDeviceIntPoint& aPoint) { 1414 NS_ENSURE_TRUE(aEvent, false); 1415 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); 1416 if (touchEvent) { 1417 // return false if there is more than one touch on the page, or if 1418 // we can't find a touch point 1419 if (touchEvent->mTouches.Length() != 1) { 1420 return false; 1421 } 1422 1423 dom::Touch* touch = touchEvent->mTouches.SafeElementAt(0); 1424 if (!touch) { 1425 return false; 1426 } 1427 aPoint = touch->mRefPoint; 1428 } else { 1429 aPoint = aEvent->mRefPoint; 1430 } 1431 return true; 1432 } 1433 1434 NS_IMPL_ISUPPORTS(nsSliderMediator, nsIDOMEventListener)