tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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)