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/. */
9 // Netscape Communications
11 // See documentation in associated header file
14 #include "nsSliderFrame.h"
16 #include "mozilla/ComputedStyle.h"
17 #include "nsPresContext.h"
18 #include "nsIContent.h"
20 #include "nsNameSpaceManager.h"
21 #include "nsGkAtoms.h"
22 #include "nsHTMLParts.h"
23 #include "nsCSSRendering.h"
24 #include "nsScrollbarButtonFrame.h"
25 #include "nsIScrollableFrame.h"
26 #include "nsIScrollbarMediator.h"
27 #include "nsISupportsImpl.h"
28 #include "nsScrollbarFrame.h"
29 #include "nsRepeatService.h"
30 #include "nsContentUtils.h"
31 #include "nsLayoutUtils.h"
32 #include "nsDisplayList.h"
33 #include "nsDeviceContext.h"
34 #include "nsRefreshDriver.h" // for nsAPostRefreshObserver
35 #include "mozilla/Assertions.h" // for MOZ_ASSERT
36 #include "mozilla/DisplayPortUtils.h"
37 #include "mozilla/LookAndFeel.h"
38 #include "mozilla/MouseEvents.h"
39 #include "mozilla/Preferences.h"
40 #include "mozilla/PresShell.h"
41 #include "mozilla/StaticPrefs_general.h"
42 #include "mozilla/StaticPrefs_layout.h"
43 #include "mozilla/SVGIntegrationUtils.h"
44 #include "mozilla/Telemetry.h"
45 #include "mozilla/dom/Document.h"
46 #include "mozilla/dom/Event.h"
47 #include "mozilla/layers/APZCCallbackHelper.h"
48 #include "mozilla/layers/AsyncDragMetrics.h"
49 #include "mozilla/layers/InputAPZContext.h"
50 #include "mozilla/layers/WebRenderLayerManager.h"
51 #include "mozilla/StaticPrefs_slider.h"
54 using namespace mozilla
;
55 using namespace mozilla::gfx
;
56 using mozilla::dom::Document
;
57 using mozilla::dom::Event
;
58 using mozilla::layers::AsyncDragMetrics
;
59 using mozilla::layers::InputAPZContext
;
60 using mozilla::layers::ScrollbarData
;
61 using mozilla::layers::ScrollDirection
;
63 bool nsSliderFrame::gMiddlePref
= false;
65 // Turn this on if you want to debug slider frames.
68 nsIFrame
* NS_NewSliderFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
69 return new (aPresShell
) nsSliderFrame(aStyle
, aPresShell
->GetPresContext());
72 NS_IMPL_FRAMEARENA_HELPERS(nsSliderFrame
)
74 NS_QUERYFRAME_HEAD(nsSliderFrame
)
75 NS_QUERYFRAME_ENTRY(nsSliderFrame
)
76 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
78 nsSliderFrame::nsSliderFrame(ComputedStyle
* aStyle
, nsPresContext
* aPresContext
)
79 : nsContainerFrame(aStyle
, aPresContext
, kClassID
),
87 mScrollingWithAPZ(false),
88 mSuppressionActive(false),
92 nsSliderFrame::~nsSliderFrame() {
93 if (mSuppressionActive
) {
94 if (auto* presShell
= PresShell()) {
95 presShell
->SuppressDisplayport(false);
100 void nsSliderFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
101 nsIFrame
* aPrevInFlow
) {
102 nsContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
104 static bool gotPrefs
= false;
108 gMiddlePref
= Preferences::GetBool("middlemouse.scrollbarPosition");
111 mCurPos
= GetCurrentPosition(aContent
);
114 void nsSliderFrame::RemoveFrame(DestroyContext
& aContext
, ChildListID aListID
,
115 nsIFrame
* aOldFrame
) {
116 nsContainerFrame::RemoveFrame(aContext
, aListID
, aOldFrame
);
117 if (mFrames
.IsEmpty()) {
122 void nsSliderFrame::InsertFrames(ChildListID aListID
, nsIFrame
* aPrevFrame
,
123 const nsLineList::iterator
* aPrevFrameLine
,
124 nsFrameList
&& aFrameList
) {
125 bool wasEmpty
= mFrames
.IsEmpty();
126 nsContainerFrame::InsertFrames(aListID
, aPrevFrame
, aPrevFrameLine
,
127 std::move(aFrameList
));
133 void nsSliderFrame::AppendFrames(ChildListID aListID
,
134 nsFrameList
&& aFrameList
) {
135 // If we have no children and on was added then make sure we add the
137 bool wasEmpty
= mFrames
.IsEmpty();
138 nsContainerFrame::AppendFrames(aListID
, std::move(aFrameList
));
144 int32_t nsSliderFrame::GetCurrentPosition(nsIContent
* content
) {
145 return GetIntegerAttribute(content
, nsGkAtoms::curpos
, 0);
148 int32_t nsSliderFrame::GetMinPosition(nsIContent
* content
) {
149 return GetIntegerAttribute(content
, nsGkAtoms::minpos
, 0);
152 int32_t nsSliderFrame::GetMaxPosition(nsIContent
* content
) {
153 return GetIntegerAttribute(content
, nsGkAtoms::maxpos
, 100);
156 int32_t nsSliderFrame::GetIncrement(nsIContent
* content
) {
157 return GetIntegerAttribute(content
, nsGkAtoms::increment
, 1);
160 int32_t nsSliderFrame::GetPageIncrement(nsIContent
* content
) {
161 return GetIntegerAttribute(content
, nsGkAtoms::pageincrement
, 10);
164 int32_t nsSliderFrame::GetIntegerAttribute(nsIContent
* content
, nsAtom
* atom
,
165 int32_t defaultValue
) {
167 if (content
->IsElement()) {
168 content
->AsElement()->GetAttr(atom
, value
);
170 if (!value
.IsEmpty()) {
173 // convert it to an integer
174 defaultValue
= value
.ToInteger(&error
);
180 nsresult
nsSliderFrame::AttributeChanged(int32_t aNameSpaceID
,
181 nsAtom
* aAttribute
, int32_t aModType
) {
183 nsContainerFrame::AttributeChanged(aNameSpaceID
, aAttribute
, aModType
);
184 // if the current position changes
185 if (aAttribute
== nsGkAtoms::curpos
) {
186 CurrentPositionChanged();
187 } else if (aAttribute
== nsGkAtoms::minpos
||
188 aAttribute
== nsGkAtoms::maxpos
) {
191 nsScrollbarFrame
* scrollbarBox
= Scrollbar();
192 nsCOMPtr
<nsIContent
> scrollbar
= scrollbarBox
->GetContent();
193 int32_t current
= GetCurrentPosition(scrollbar
);
194 int32_t min
= GetMinPosition(scrollbar
);
195 int32_t max
= GetMaxPosition(scrollbar
);
197 if (current
< min
|| current
> max
) {
198 int32_t direction
= 0;
199 if (current
< min
|| max
< min
) {
202 } else if (current
> max
) {
207 // set the new position and notify observers
208 nsIScrollbarMediator
* mediator
= scrollbarBox
->GetScrollbarMediator();
209 scrollbarBox
->SetIncrementToWhole(direction
);
211 mediator
->ScrollByWhole(scrollbarBox
, direction
,
212 ScrollSnapFlags::IntendedEndPosition
);
214 // 'this' might be destroyed here
216 nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
217 scrollbar
->AsElement(), nsGkAtoms::curpos
, current
));
221 if (aAttribute
== nsGkAtoms::minpos
|| aAttribute
== nsGkAtoms::maxpos
||
222 aAttribute
== nsGkAtoms::pageincrement
||
223 aAttribute
== nsGkAtoms::increment
) {
224 PresShell()->FrameNeedsReflow(
225 this, IntrinsicDirty::FrameAncestorsAndDescendants
, NS_FRAME_IS_DIRTY
);
233 // Draw any tick marks that show the position of find in page results.
234 class nsDisplaySliderMarks final
: public nsPaintedDisplayItem
{
236 nsDisplaySliderMarks(nsDisplayListBuilder
* aBuilder
, nsSliderFrame
* aFrame
)
237 : nsPaintedDisplayItem(aBuilder
, aFrame
) {
238 MOZ_COUNT_CTOR(nsDisplaySliderMarks
);
240 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplaySliderMarks
)
242 NS_DISPLAY_DECL_NAME("SliderMarks", TYPE_SLIDER_MARKS
)
244 void PaintMarks(nsDisplayListBuilder
* aDisplayListBuilder
,
245 wr::DisplayListBuilder
* aBuilder
, gfxContext
* aCtx
);
247 nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
, bool* aSnap
) const override
{
249 return mFrame
->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
252 bool CreateWebRenderCommands(
253 wr::DisplayListBuilder
& aBuilder
, wr::IpcResourceUpdateQueue
& aResources
,
254 const StackingContextHelper
& aSc
,
255 layers::RenderRootStateManager
* aManager
,
256 nsDisplayListBuilder
* aDisplayListBuilder
) override
;
258 void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) override
;
261 // This is shared between the webrender and Paint() paths. For the former,
262 // aBuilder should be assigned and aCtx will be null. For the latter, aBuilder
263 // should be null and aCtx should be the gfxContext for painting.
264 void nsDisplaySliderMarks::PaintMarks(nsDisplayListBuilder
* aDisplayListBuilder
,
265 wr::DisplayListBuilder
* aBuilder
,
267 DrawTarget
* drawTarget
= nullptr;
269 drawTarget
= aCtx
->GetDrawTarget();
271 MOZ_ASSERT(aBuilder
);
274 Document
* doc
= mFrame
->GetContent()->GetUncomposedDoc();
279 nsGlobalWindowInner
* window
=
280 nsGlobalWindowInner::Cast(doc
->GetInnerWindow());
285 nsSliderFrame
* sliderFrame
= static_cast<nsSliderFrame
*>(mFrame
);
287 nsIFrame
* scrollbarBox
= sliderFrame
->Scrollbar();
288 nsCOMPtr
<nsIContent
> scrollbar
= scrollbarBox
->GetContent();
290 int32_t minPos
= sliderFrame
->GetMinPosition(scrollbar
);
291 int32_t maxPos
= sliderFrame
->GetMaxPosition(scrollbar
);
293 // Use the text highlight color for the tick marks.
294 nscolor highlightColor
=
295 LookAndFeel::Color(LookAndFeel::ColorID::TextHighlightBackground
, mFrame
);
296 DeviceColor fillColor
= ToDeviceColor(highlightColor
);
297 fillColor
.a
= 0.3; // make the mark mostly transparent
299 int32_t appUnitsPerDevPixel
=
300 sliderFrame
->PresContext()->AppUnitsPerDevPixel();
301 nsRect sliderRect
= sliderFrame
->GetRect();
303 nsPoint refPoint
= aDisplayListBuilder
->ToReferenceFrame(mFrame
);
305 // Increase the height of the tick mark rectangle by one pixel. If the
306 // desktop scale is greater than 1, it should be increased more.
307 // The tick marks should be drawn ignoring any page zoom that is applied.
308 float increasePixels
= sliderFrame
->PresContext()
310 ->GetDesktopToDeviceScale()
312 const bool isHorizontal
= sliderFrame
->Scrollbar()->IsHorizontal();
313 float increasePixelsX
= isHorizontal
? increasePixels
: 0;
314 float increasePixelsY
= isHorizontal
? 0 : increasePixels
;
316 isHorizontal
? nsSize(0, sliderRect
.height
) : nsSize(sliderRect
.width
, 0);
318 nsTArray
<uint32_t>& marks
= window
->GetScrollMarks();
319 for (uint32_t m
= 0; m
< marks
.Length(); m
++) {
320 uint32_t markValue
= marks
[m
];
321 if (markValue
> (uint32_t)maxPos
) {
324 if (markValue
< (uint32_t)minPos
) {
328 // The values in the marks array range up to the window's
329 // scrollMax{X,Y} - scrollMin{X,Y} (the same as the slider's maxpos).
330 // Scale the values to fit within the slider's width or height.
331 nsRect
markRect(refPoint
, initialSize
);
334 (nscoord
)((double)markValue
/ (maxPos
- minPos
) * sliderRect
.width
);
337 (nscoord
)((double)markValue
/ (maxPos
- minPos
) * sliderRect
.height
);
342 NSRectToSnappedRect(markRect
, appUnitsPerDevPixel
, *drawTarget
);
343 devPixelRect
.Inflate(increasePixelsX
, increasePixelsY
);
344 drawTarget
->FillRect(devPixelRect
, ColorPattern(fillColor
));
346 LayoutDeviceIntRect dRect
= LayoutDeviceIntRect::FromAppUnitsToNearest(
347 markRect
, appUnitsPerDevPixel
);
348 dRect
.Inflate(increasePixelsX
, increasePixelsY
);
349 wr::LayoutRect layoutRect
= wr::ToLayoutRect(dRect
);
350 aBuilder
->PushRect(layoutRect
, layoutRect
, BackfaceIsHidden(), false,
351 false, wr::ToColorF(fillColor
));
356 bool nsDisplaySliderMarks::CreateWebRenderCommands(
357 wr::DisplayListBuilder
& aBuilder
, wr::IpcResourceUpdateQueue
& aResources
,
358 const StackingContextHelper
& aSc
, layers::RenderRootStateManager
* aManager
,
359 nsDisplayListBuilder
* aDisplayListBuilder
) {
360 PaintMarks(aDisplayListBuilder
, &aBuilder
, nullptr);
364 void nsDisplaySliderMarks::Paint(nsDisplayListBuilder
* aBuilder
,
366 PaintMarks(aBuilder
, nullptr, aCtx
);
369 } // namespace mozilla
371 void nsSliderFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
372 const nsDisplayListSet
& aLists
) {
373 if (aBuilder
->IsForEventDelivery() && isDraggingThumb()) {
374 // This is EVIL, we shouldn't be messing with event delivery just to get
375 // thumb mouse drag events to arrive at the slider!
376 aLists
.Outlines()->AppendNewToTop
<nsDisplayEventReceiver
>(aBuilder
, this);
380 DisplayBorderBackgroundOutline(aBuilder
, aLists
);
382 if (nsIFrame
* thumb
= mFrames
.FirstChild()) {
383 BuildDisplayListForThumb(aBuilder
, thumb
, aLists
);
386 // If this is an scrollbar for the root frame, draw any markers.
387 // Markers are not drawn for other scrollbars.
388 // XXX seems like this should be done in nsScrollbarFrame instead perhaps?
389 if (!aBuilder
->IsForEventDelivery()) {
390 nsScrollbarFrame
* scrollbar
= Scrollbar();
391 if (nsIScrollableFrame
* scrollFrame
=
392 do_QueryFrame(scrollbar
->GetParent())) {
393 if (scrollFrame
->IsRootScrollFrameOfDocument()) {
394 nsGlobalWindowInner
* window
= nsGlobalWindowInner::Cast(
395 PresContext()->Document()->GetInnerWindow());
397 window
->GetScrollMarksOnHScrollbar() == scrollbar
->IsHorizontal() &&
398 window
->GetScrollMarks().Length() > 0) {
399 aLists
.Content()->AppendNewToTop
<nsDisplaySliderMarks
>(aBuilder
,
407 static bool UsesCustomScrollbarMediator(nsIFrame
* scrollbarBox
) {
408 if (nsScrollbarFrame
* scrollbarFrame
= do_QueryFrame(scrollbarBox
)) {
409 if (nsIScrollbarMediator
* mediator
=
410 scrollbarFrame
->GetScrollbarMediator()) {
411 nsIScrollableFrame
* scrollFrame
= do_QueryFrame(mediator
);
412 // The scrollbar mediator is not the scroll frame.
413 // That means this scroll frame has a custom scrollbar mediator.
422 void nsSliderFrame::BuildDisplayListForThumb(nsDisplayListBuilder
* aBuilder
,
424 const nsDisplayListSet
& aLists
) {
425 nsRect
thumbRect(aThumb
->GetRect());
427 nsRect sliderTrack
= GetRect();
428 if (sliderTrack
.width
< thumbRect
.width
||
429 sliderTrack
.height
< thumbRect
.height
) {
433 // If this scrollbar is the scrollbar of an actively scrolled scroll frame,
434 // layerize the scrollbar thumb, wrap it in its own ContainerLayer and
435 // attach scrolling information to it.
436 // We do this here and not in the thumb's BuildDisplayList so that the event
437 // region that gets created for the thumb is included in the nsDisplayOwnLayer
440 const layers::ScrollableLayerGuid::ViewID scrollTargetId
=
441 aBuilder
->GetCurrentScrollbarTarget();
442 const bool thumbGetsLayer
=
443 scrollTargetId
!= layers::ScrollableLayerGuid::NULL_SCROLL_ID
;
445 if (thumbGetsLayer
) {
446 const Maybe
<ScrollDirection
> scrollDirection
=
447 aBuilder
->GetCurrentScrollbarDirection();
448 MOZ_ASSERT(scrollDirection
.isSome());
449 const bool isHorizontal
= *scrollDirection
== ScrollDirection::eHorizontal
;
450 const OuterCSSCoord thumbLength
= OuterCSSPixel::FromAppUnits(
451 isHorizontal
? thumbRect
.width
: thumbRect
.height
);
452 const OuterCSSCoord minThumbLength
=
453 OuterCSSPixel::FromAppUnits(mThumbMinLength
);
455 nsIFrame
* scrollbarBox
= Scrollbar();
456 bool isAsyncDraggable
= !UsesCustomScrollbarMediator(scrollbarBox
);
458 nsPoint scrollPortOrigin
;
459 if (nsIScrollableFrame
* scrollFrame
=
460 do_QueryFrame(scrollbarBox
->GetParent())) {
461 scrollPortOrigin
= scrollFrame
->GetScrollPortRect().TopLeft();
463 isAsyncDraggable
= false;
466 // This rect is the range in which the scroll thumb can slide in.
467 sliderTrack
= sliderTrack
+ scrollbarBox
->GetPosition() - scrollPortOrigin
;
468 const OuterCSSCoord sliderTrackStart
= OuterCSSPixel::FromAppUnits(
469 isHorizontal
? sliderTrack
.x
: sliderTrack
.y
);
470 const OuterCSSCoord sliderTrackLength
= OuterCSSPixel::FromAppUnits(
471 isHorizontal
? sliderTrack
.width
: sliderTrack
.height
);
472 const OuterCSSCoord thumbStart
=
473 OuterCSSPixel::FromAppUnits(isHorizontal
? thumbRect
.x
: thumbRect
.y
);
475 const nsRect overflow
= aThumb
->InkOverflowRectRelativeToParent();
476 nsSize refSize
= aBuilder
->RootReferenceFrame()->GetSize();
477 nsRect dirty
= aBuilder
->GetVisibleRect().Intersect(thumbRect
);
478 dirty
= nsLayoutUtils::ComputePartialPrerenderArea(
479 aThumb
, aBuilder
->GetVisibleRect(), overflow
, refSize
);
481 nsDisplayListBuilder::AutoBuildingDisplayList
buildingDisplayList(
482 aBuilder
, this, dirty
, dirty
);
484 // Clip the thumb layer to the slider track. This is necessary to ensure
485 // FrameLayerBuilder is able to merge content before and after the
486 // scrollframe into the same layer (otherwise it thinks the thumb could
487 // potentially move anywhere within the existing clip).
488 DisplayListClipState::AutoSaveRestore
thumbClipState(aBuilder
);
489 thumbClipState
.ClipContainingBlockDescendants(
490 GetRectRelativeToSelf() + aBuilder
->ToReferenceFrame(this));
492 // Have the thumb's container layer capture the current clip, so
493 // it doesn't apply to the thumb's contents. This allows the contents
494 // to be fully rendered even if they're partially or fully offscreen,
495 // so async scrolling can still bring it into view.
496 DisplayListClipState::AutoSaveRestore
thumbContentsClipState(aBuilder
);
497 thumbContentsClipState
.Clear();
499 nsDisplayListBuilder::AutoContainerASRTracker
contASRTracker(aBuilder
);
500 nsDisplayListCollection
tempLists(aBuilder
);
501 BuildDisplayListForChild(aBuilder
, aThumb
, tempLists
);
503 // This is a bit of a hack. Collect up all descendant display items
504 // and merge them into a single Content() list.
505 nsDisplayList
masterList(aBuilder
);
506 masterList
.AppendToTop(tempLists
.BorderBackground());
507 masterList
.AppendToTop(tempLists
.BlockBorderBackgrounds());
508 masterList
.AppendToTop(tempLists
.Floats());
509 masterList
.AppendToTop(tempLists
.Content());
510 masterList
.AppendToTop(tempLists
.PositionedDescendants());
511 masterList
.AppendToTop(tempLists
.Outlines());
513 // Restore the saved clip so it applies to the thumb container layer.
514 thumbContentsClipState
.Restore();
516 // Wrap the list to make it its own layer.
517 const ActiveScrolledRoot
* ownLayerASR
= contASRTracker
.GetContainerASR();
518 aLists
.Content()->AppendNewToTopWithIndex
<nsDisplayOwnLayer
>(
520 /* aIndex = */ nsDisplayOwnLayer::OwnLayerForScrollThumb
, &masterList
,
521 ownLayerASR
, nsDisplayOwnLayerFlags::None
,
522 ScrollbarData::CreateForThumb(*scrollDirection
, GetThumbRatio(),
523 thumbStart
, thumbLength
, minThumbLength
,
524 isAsyncDraggable
, sliderTrackStart
,
525 sliderTrackLength
, scrollTargetId
),
531 BuildDisplayListForChild(aBuilder
, aThumb
, aLists
);
534 void nsSliderFrame::Reflow(nsPresContext
* aPresContext
,
535 ReflowOutput
& aDesiredSize
,
536 const ReflowInput
& aReflowInput
,
537 nsReflowStatus
& aStatus
) {
539 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
540 NS_ASSERTION(aReflowInput
.AvailableWidth() != NS_UNCONSTRAINEDSIZE
,
541 "Bogus avail width");
542 NS_ASSERTION(aReflowInput
.AvailableHeight() != NS_UNCONSTRAINEDSIZE
,
543 "Bogus avail height");
545 const auto wm
= GetWritingMode();
547 // We always take all the space we're given.
548 aDesiredSize
.SetSize(wm
, aReflowInput
.ComputedSize(wm
));
549 aDesiredSize
.SetOverflowAreasToDesiredBounds();
551 // Get the thumb, should be our only child.
552 nsIFrame
* thumbBox
= mFrames
.FirstChild();
553 if (NS_WARN_IF(!thumbBox
)) {
557 nsScrollbarFrame
* scrollbarBox
= Scrollbar();
558 nsIContent
* scrollbar
= scrollbarBox
->GetContent();
559 const bool horizontal
= scrollbarBox
->IsHorizontal();
560 nsSize availSize
= aDesiredSize
.PhysicalSize();
561 ReflowInput
thumbRI(aPresContext
, aReflowInput
, thumbBox
,
562 aReflowInput
.AvailableSize(wm
));
564 // Get the thumb's pref size.
565 nsSize thumbSize
= thumbRI
.ComputedMinSize(wm
).GetPhysicalSize(wm
);
567 thumbSize
.height
= availSize
.height
;
569 thumbSize
.width
= availSize
.width
;
572 int32_t curPos
= GetCurrentPosition(scrollbar
);
573 int32_t minPos
= GetMinPosition(scrollbar
);
574 int32_t maxPos
= GetMaxPosition(scrollbar
);
575 int32_t pageIncrement
= GetPageIncrement(scrollbar
);
577 maxPos
= std::max(minPos
, maxPos
);
578 curPos
= clamped(curPos
, minPos
, maxPos
);
580 // If modifying the logic here, be sure to modify the corresponding
581 // compositor-side calculation in ScrollThumbUtils::ApplyTransformForAxis().
582 nscoord
& availableLength
= horizontal
? availSize
.width
: availSize
.height
;
583 nscoord
& thumbLength
= horizontal
? thumbSize
.width
: thumbSize
.height
;
584 mThumbMinLength
= thumbLength
;
586 if ((pageIncrement
+ maxPos
- minPos
) > 0) {
587 float ratio
= float(pageIncrement
) / float(maxPos
- minPos
+ pageIncrement
);
589 std::max(thumbLength
, NSToCoordRound(availableLength
* ratio
));
592 // Round the thumb's length to device pixels.
593 nsPresContext
* presContext
= PresContext();
594 thumbLength
= presContext
->DevPixelsToAppUnits(
595 presContext
->AppUnitsToDevPixels(thumbLength
));
597 // mRatio translates the thumb position in app units to the value.
598 mRatio
= (minPos
!= maxPos
)
599 ? float(availableLength
- thumbLength
) / float(maxPos
- minPos
)
602 // in reverse mode, curpos is reversed such that lower values are to the
603 // right or bottom and increase leftwards or upwards. In this case, use the
604 // offset from the end instead of the beginning.
605 bool reverse
= mContent
->AsElement()->AttrValueIs(
606 kNameSpaceID_None
, nsGkAtoms::dir
, nsGkAtoms::reverse
, eCaseMatters
);
607 nscoord pos
= reverse
? (maxPos
- curPos
) : (curPos
- minPos
);
609 // set the thumb's coord to be the current pos * the ratio.
612 thumbPos
.x
= NSToCoordRound(pos
* mRatio
);
614 thumbPos
.y
= NSToCoordRound(pos
* mRatio
);
617 // Same to `snappedThumbLocation` in `nsSliderFrame::CurrentPositionChanged`,
618 // to avoid putting the scroll thumb at subpixel positions which cause
619 // needless invalidations
620 nscoord appUnitsPerPixel
= PresContext()->AppUnitsPerDevPixel();
622 ToAppUnits(thumbPos
.ToNearestPixels(appUnitsPerPixel
), appUnitsPerPixel
);
624 const LogicalPoint
logicalPos(wm
, thumbPos
, availSize
);
625 // TODO: It seems like a lot of this stuff should really belong in the thumb's
626 // reflow code rather than here, but since we rely on the frame tree structure
627 // heavily this matches the previous code more closely for now.
628 ReflowOutput
thumbDesiredSize(wm
);
629 const auto flags
= ReflowChildFlags::Default
;
630 nsReflowStatus status
;
631 thumbRI
.SetComputedISize(thumbSize
.width
);
632 thumbRI
.SetComputedBSize(thumbSize
.height
);
633 ReflowChild(thumbBox
, aPresContext
, thumbDesiredSize
, thumbRI
, wm
, logicalPos
,
634 availSize
, flags
, status
);
635 FinishReflowChild(thumbBox
, aPresContext
, thumbDesiredSize
, &thumbRI
, wm
,
636 logicalPos
, availSize
, flags
);
639 nsresult
nsSliderFrame::HandleEvent(nsPresContext
* aPresContext
,
640 WidgetGUIEvent
* aEvent
,
641 nsEventStatus
* aEventStatus
) {
642 NS_ENSURE_ARG_POINTER(aEventStatus
);
644 if (mAPZDragInitiated
&&
645 *mAPZDragInitiated
== InputAPZContext::GetInputBlockId() &&
646 aEvent
->mMessage
== eMouseDown
) {
647 // If we get the mousedown after the APZ notification, then immediately
648 // switch into the state corresponding to an APZ thumb-drag. Note that
649 // we can't just do this in AsyncScrollbarDragInitiated() directly because
650 // the handling for this mousedown event in the presShell will reset the
651 // capturing content which makes isDraggingThumb() return false. We check
652 // the input block here to make sure that we correctly handle any ordering
653 // of {eMouseDown arriving, AsyncScrollbarDragInitiated() being called}.
654 mAPZDragInitiated
= Nothing();
656 mScrollingWithAPZ
= true;
660 // If a web page calls event.preventDefault() we still want to
661 // scroll when scroll arrow is clicked. See bug 511075.
662 if (!mContent
->IsInNativeAnonymousSubtree() &&
663 nsEventStatus_eConsumeNoDefault
== *aEventStatus
) {
667 if (!mDragFinished
&& !isDraggingThumb()) {
672 nsScrollbarFrame
* scrollbarBox
= Scrollbar();
673 nsCOMPtr
<nsIContent
> scrollbar
= scrollbarBox
->GetContent();
674 bool isHorizontal
= scrollbarBox
->IsHorizontal();
676 if (isDraggingThumb()) {
677 switch (aEvent
->mMessage
) {
680 if (mScrollingWithAPZ
) {
684 if (!GetEventPoint(aEvent
, eventPoint
)) {
687 if (mRepeatDirection
) {
688 // On Linux the destination point is determined by the initial click
689 // on the scrollbar track and doesn't change until the mouse button
691 #ifndef MOZ_WIDGET_GTK
692 // On the other platforms we need to update the destination point now.
693 mDestinationPoint
= eventPoint
;
700 nscoord pos
= isHorizontal
? eventPoint
.x
: eventPoint
.y
;
702 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
707 // take our current position and subtract the start location
709 bool isMouseOutsideThumb
= false;
710 const int32_t snapMultiplier
= StaticPrefs::slider_snapMultiplier();
711 if (snapMultiplier
) {
712 nsSize thumbSize
= thumbFrame
->GetSize();
714 // horizontal scrollbar - check if mouse is above or below thumb
715 // XXXbz what about looking at the .y of the thumb's rect? Is that
717 if (eventPoint
.y
< -snapMultiplier
* thumbSize
.height
||
719 thumbSize
.height
+ snapMultiplier
* thumbSize
.height
) {
720 isMouseOutsideThumb
= true;
723 // vertical scrollbar - check if mouse is left or right of thumb
724 if (eventPoint
.x
< -snapMultiplier
* thumbSize
.width
||
726 thumbSize
.width
+ snapMultiplier
* thumbSize
.width
) {
727 isMouseOutsideThumb
= true;
731 if (aEvent
->mClass
== eTouchEventClass
) {
732 *aEventStatus
= nsEventStatus_eConsumeNoDefault
;
734 if (isMouseOutsideThumb
) {
735 SetCurrentThumbPosition(scrollbar
, mThumbStart
, false, false);
740 SetCurrentThumbPosition(scrollbar
, pos
, false, true); // with snapping
745 if (ShouldScrollForEvent(aEvent
)) {
747 // we MUST call nsFrame HandleEvent for mouse ups to maintain the
748 // selection state and capture state.
749 return nsIFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
757 // return nsIFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
761 if (ShouldScrollToClickForEvent(aEvent
)) {
763 if (!GetEventPoint(aEvent
, eventPoint
)) {
766 nscoord pos
= isHorizontal
? eventPoint
.x
: eventPoint
.y
;
768 // adjust so that the middle of the thumb is placed under the click
769 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
773 nsSize thumbSize
= thumbFrame
->GetSize();
774 nscoord thumbLength
= isHorizontal
? thumbSize
.width
: thumbSize
.height
;
777 AutoWeakFrame
weakFrame(this);
778 // should aMaySnap be true here?
779 SetCurrentThumbPosition(scrollbar
, pos
- thumbLength
/ 2, false, false);
780 NS_ENSURE_TRUE(weakFrame
.IsAlive(), NS_OK
);
784 if (aEvent
->mClass
== eTouchEventClass
) {
785 *aEventStatus
= nsEventStatus_eConsumeNoDefault
;
788 SetupDrag(aEvent
, thumbFrame
, pos
, isHorizontal
);
790 #ifdef MOZ_WIDGET_GTK
791 else if (ShouldScrollForEvent(aEvent
) && aEvent
->mClass
== eMouseEventClass
&&
792 aEvent
->AsMouseEvent()->mButton
== MouseButton::eSecondary
) {
793 // HandlePress and HandleRelease are usually called via
794 // nsIFrame::HandleEvent, but only for the left mouse button.
795 if (aEvent
->mMessage
== eMouseDown
) {
796 HandlePress(aPresContext
, aEvent
, aEventStatus
);
797 } else if (aEvent
->mMessage
== eMouseUp
) {
798 HandleRelease(aPresContext
, aEvent
, aEventStatus
);
805 // XXX hack until handle release is actually called in nsframe.
806 // if (aEvent->mMessage == eMouseOut ||
807 // aEvent->mMessage == NS_MOUSE_RIGHT_BUTTON_UP ||
808 // aEvent->mMessage == NS_MOUSE_LEFT_BUTTON_UP) {
809 // HandleRelease(aPresContext, aEvent, aEventStatus);
812 if (aEvent
->mMessage
== eMouseOut
&& mRepeatDirection
) {
813 HandleRelease(aPresContext
, aEvent
, aEventStatus
);
816 return nsIFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
819 // Helper function to collect the "scroll to click" metric. Beware of
820 // caching this, users expect to be able to change the system preference
821 // and see the browser change its behavior immediately.
822 bool nsSliderFrame::GetScrollToClick() {
823 return LookAndFeel::GetInt(LookAndFeel::IntID::ScrollToClick
, false);
826 nsScrollbarFrame
* nsSliderFrame::Scrollbar() {
827 MOZ_ASSERT(GetParent());
828 MOZ_DIAGNOSTIC_ASSERT(
829 static_cast<nsScrollbarFrame
*>(do_QueryFrame(GetParent())));
830 return static_cast<nsScrollbarFrame
*>(GetParent());
833 void nsSliderFrame::PageUpDown(nscoord change
) {
834 // on a page up or down get our page increment. We get this by getting the
835 // scrollbar we are in and asking it for the current position and the page
836 // increment. If we are not in a scrollbar we will get the values from our own
838 nsIFrame
* scrollbarBox
= Scrollbar();
839 nsCOMPtr
<nsIContent
> scrollbar
= scrollbarBox
->GetContent();
841 nscoord pageIncrement
= GetPageIncrement(scrollbar
);
842 int32_t curpos
= GetCurrentPosition(scrollbar
);
843 int32_t minpos
= GetMinPosition(scrollbar
);
844 int32_t maxpos
= GetMaxPosition(scrollbar
);
846 // get the new position and make sure it is in bounds
847 int32_t newpos
= curpos
+ change
* pageIncrement
;
848 if (newpos
< minpos
|| maxpos
< minpos
)
850 else if (newpos
> maxpos
)
853 SetCurrentPositionInternal(scrollbar
, newpos
, true);
856 // called when the current position changed and we need to update the thumb's
858 void nsSliderFrame::CurrentPositionChanged() {
859 nsScrollbarFrame
* scrollbarBox
= Scrollbar();
860 nsCOMPtr
<nsIContent
> scrollbar
= scrollbarBox
->GetContent();
862 // get the current position
863 int32_t curPos
= GetCurrentPosition(scrollbar
);
865 // do nothing if the position did not change
866 if (mCurPos
== curPos
) {
870 // get our current min and max position from our content node
871 int32_t minPos
= GetMinPosition(scrollbar
);
872 int32_t maxPos
= GetMaxPosition(scrollbar
);
874 maxPos
= std::max(minPos
, maxPos
);
875 curPos
= clamped(curPos
, minPos
, maxPos
);
877 // get the thumb's rect
878 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
883 bool reverse
= mContent
->AsElement()->AttrValueIs(
884 kNameSpaceID_None
, nsGkAtoms::dir
, nsGkAtoms::reverse
, eCaseMatters
);
885 nscoord pos
= reverse
? (maxPos
- curPos
) : (curPos
- minPos
);
886 const bool horizontal
= Scrollbar()->IsHorizontal();
888 // figure out the new rect
889 nsRect thumbRect
= thumbFrame
->GetRect();
890 nsRect
newThumbRect(thumbRect
);
892 newThumbRect
.x
= NSToCoordRound(pos
* mRatio
);
894 newThumbRect
.y
= NSToCoordRound(pos
* mRatio
);
897 // avoid putting the scroll thumb at subpixel positions which cause needless
899 nscoord appUnitsPerPixel
= PresContext()->AppUnitsPerDevPixel();
900 nsPoint snappedThumbLocation
=
901 ToAppUnits(newThumbRect
.TopLeft().ToNearestPixels(appUnitsPerPixel
),
904 newThumbRect
.x
= snappedThumbLocation
.x
;
906 newThumbRect
.y
= snappedThumbLocation
.y
;
910 // XXX This out-of-band update of the frame tree is rather fishy!
911 thumbFrame
->SetRect(newThumbRect
);
913 // When the thumb changes position, the mThumbStart value stored in
914 // ScrollbarData for the purpose of telling APZ about the thumb
915 // position painted by the main thread is invalidated. The ScrollbarData
916 // is stored on the nsDisplayOwnLayer item built by *this* frame, so
917 // we need to mark this frame as needing its fisplay item rebuilt.
918 MarkNeedsDisplayItemRebuild();
920 // Request a repaint of the scrollbar
921 nsIScrollbarMediator
* mediator
= scrollbarBox
->GetScrollbarMediator();
922 if (!mediator
|| !mediator
->ShouldSuppressScrollbarRepaints()) {
929 static void UpdateAttribute(dom::Element
* aScrollbar
, nscoord aNewPos
,
930 bool aNotify
, bool aIsSmooth
) {
932 str
.AppendInt(aNewPos
);
935 aScrollbar
->SetAttr(kNameSpaceID_None
, nsGkAtoms::smooth
, u
"true"_ns
,
938 aScrollbar
->SetAttr(kNameSpaceID_None
, nsGkAtoms::curpos
, str
, aNotify
);
940 aScrollbar
->UnsetAttr(kNameSpaceID_None
, nsGkAtoms::smooth
, false);
944 // Use this function when you want to set the scroll position via the position
945 // of the scrollbar thumb, e.g. when dragging the slider. This function scrolls
946 // the content in such a way that thumbRect.x/.y becomes aNewThumbPos.
947 void nsSliderFrame::SetCurrentThumbPosition(nsIContent
* aScrollbar
,
948 nscoord aNewThumbPos
,
949 bool aIsSmooth
, bool aMaySnap
) {
950 int32_t newPos
= NSToIntRound(aNewThumbPos
/ mRatio
);
952 mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::snap
,
953 nsGkAtoms::_true
, eCaseMatters
)) {
954 // If snap="true", then the slider may only be set to min + (increment * x).
955 // Otherwise, the slider may be set to any positive integer.
956 int32_t increment
= GetIncrement(aScrollbar
);
957 newPos
= NSToIntRound(newPos
/ float(increment
)) * increment
;
960 SetCurrentPosition(aScrollbar
, newPos
, aIsSmooth
);
963 // Use this function when you know the target scroll position of the scrolled
964 // content. aNewPos should be passed to this function as a position as if the
965 // minpos is 0. That is, the minpos will be added to the position by this
966 // function. In a reverse direction slider, the newpos should be the distance
968 void nsSliderFrame::SetCurrentPosition(nsIContent
* aScrollbar
, int32_t aNewPos
,
970 // get min and max position from our content node
971 int32_t minpos
= GetMinPosition(aScrollbar
);
972 int32_t maxpos
= GetMaxPosition(aScrollbar
);
974 // in reverse direction sliders, flip the value so that it goes from
975 // right to left, or bottom to top.
976 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::dir
,
977 nsGkAtoms::reverse
, eCaseMatters
)) {
978 aNewPos
= maxpos
- aNewPos
;
983 // get the new position and make sure it is in bounds
984 if (aNewPos
< minpos
|| maxpos
< minpos
) {
986 } else if (aNewPos
> maxpos
) {
990 SetCurrentPositionInternal(aScrollbar
, aNewPos
, aIsSmooth
);
993 void nsSliderFrame::SetCurrentPositionInternal(nsIContent
* aScrollbar
,
996 nsCOMPtr
<nsIContent
> scrollbar
= aScrollbar
;
997 nsScrollbarFrame
* scrollbarBox
= Scrollbar();
998 AutoWeakFrame
weakFrame(this);
1000 mUserChanged
= true;
1002 // See if we have a mediator.
1003 if (nsIScrollbarMediator
* mediator
= scrollbarBox
->GetScrollbarMediator()) {
1005 nsPresContext::CSSPixelsToAppUnits(GetCurrentPosition(scrollbar
));
1006 nscoord newPos
= nsPresContext::CSSPixelsToAppUnits(aNewPos
);
1007 mediator
->ThumbMoved(scrollbarBox
, oldPos
, newPos
);
1008 if (!weakFrame
.IsAlive()) {
1011 UpdateAttribute(scrollbar
->AsElement(), aNewPos
, /* aNotify */ false,
1013 CurrentPositionChanged();
1014 mUserChanged
= false;
1018 UpdateAttribute(scrollbar
->AsElement(), aNewPos
, true, aIsSmooth
);
1019 if (!weakFrame
.IsAlive()) {
1022 mUserChanged
= false;
1025 printf("Current Pos=%d\n", aNewPos
);
1029 void nsSliderFrame::SetInitialChildList(ChildListID aListID
,
1030 nsFrameList
&& aChildList
) {
1031 nsContainerFrame::SetInitialChildList(aListID
, std::move(aChildList
));
1032 if (aListID
== FrameChildListID::Principal
) {
1037 nsresult
nsSliderMediator::HandleEvent(dom::Event
* aEvent
) {
1038 // Only process the event if the thumb is not being dragged.
1039 if (mSlider
&& !mSlider
->isDraggingThumb()) return mSlider
->StartDrag(aEvent
);
1044 static bool ScrollFrameWillBuildScrollInfoLayer(nsIFrame
* aScrollFrame
) {
1046 * Note: if changing the conditions in this function, make a corresponding
1047 * change to nsDisplayListBuilder::ShouldBuildScrollInfoItemsForHoisting()
1048 * in nsDisplayList.cpp.
1050 nsIFrame
* current
= aScrollFrame
;
1052 if (SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(current
)) {
1055 current
= nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(current
);
1060 nsIScrollableFrame
* nsSliderFrame::GetScrollFrame() {
1061 return do_QueryFrame(Scrollbar()->GetParent());
1064 void nsSliderFrame::StartAPZDrag(WidgetGUIEvent
* aEvent
) {
1065 if (!aEvent
->mFlags
.mHandledByAPZ
) {
1069 if (!gfxPlatform::GetPlatform()->SupportsApzDragInput()) {
1073 if (aEvent
->AsMouseEvent() &&
1074 aEvent
->AsMouseEvent()->mButton
!= MouseButton::ePrimary
) {
1078 nsIFrame
* scrollbarBox
= Scrollbar();
1079 nsContainerFrame
* scrollFrame
= scrollbarBox
->GetParent();
1084 nsIContent
* scrollableContent
= scrollFrame
->GetContent();
1085 if (!scrollableContent
) {
1089 // APZ dragging requires the scrollbar to be layerized, which doesn't
1090 // happen for scroll info layers.
1091 if (ScrollFrameWillBuildScrollInfoLayer(scrollFrame
)) {
1095 // Custom scrollbar mediators are not supported in the APZ codepath.
1096 if (UsesCustomScrollbarMediator(scrollbarBox
)) {
1100 bool isHorizontal
= Scrollbar()->IsHorizontal();
1102 layers::ScrollableLayerGuid::ViewID scrollTargetId
;
1103 bool hasID
= nsLayoutUtils::FindIDFor(scrollableContent
, &scrollTargetId
);
1105 hasID
&& scrollTargetId
!= layers::ScrollableLayerGuid::NULL_SCROLL_ID
;
1111 if (!DisplayPortUtils::HasNonMinimalDisplayPort(scrollableContent
)) {
1115 auto* presShell
= PresShell();
1116 uint64_t inputblockId
= InputAPZContext::GetInputBlockId();
1117 uint32_t presShellId
= presShell
->GetPresShellId();
1118 AsyncDragMetrics
dragMetrics(
1119 scrollTargetId
, presShellId
, inputblockId
,
1120 OuterCSSPixel::FromAppUnits(mDragStart
),
1121 isHorizontal
? ScrollDirection::eHorizontal
: ScrollDirection::eVertical
);
1123 // It's important to set this before calling
1124 // nsIWidget::StartAsyncScrollbarDrag(), because in some configurations, that
1125 // can call AsyncScrollbarDragRejected() synchronously, which clears the flag
1126 // (and we want it to stay cleared in that case).
1127 mScrollingWithAPZ
= true;
1129 // When we start an APZ drag, we wont get mouse events for the drag.
1130 // APZ will consume them all and only notify us of the new scroll position.
1131 bool waitForRefresh
= InputAPZContext::HavePendingLayerization();
1132 nsIWidget
* widget
= this->GetNearestWidget();
1133 if (waitForRefresh
) {
1134 waitForRefresh
= false;
1135 if (nsPresContext
* presContext
= presShell
->GetPresContext()) {
1136 presContext
->RegisterManagedPostRefreshObserver(
1137 new ManagedPostRefreshObserver(
1138 presContext
, [widget
= RefPtr
<nsIWidget
>(widget
),
1139 dragMetrics
](bool aWasCanceled
) {
1140 if (!aWasCanceled
) {
1141 widget
->StartAsyncScrollbarDrag(dragMetrics
);
1143 return ManagedPostRefreshObserver::Unregister::Yes
;
1145 waitForRefresh
= true;
1148 if (!waitForRefresh
) {
1149 widget
->StartAsyncScrollbarDrag(dragMetrics
);
1153 nsresult
nsSliderFrame::StartDrag(Event
* aEvent
) {
1155 printf("Begin dragging\n");
1157 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::disabled
,
1158 nsGkAtoms::_true
, eCaseMatters
))
1161 WidgetGUIEvent
* event
= aEvent
->WidgetEventPtr()->AsGUIEvent();
1163 if (!ShouldScrollForEvent(event
)) {
1168 if (!GetEventPoint(event
, pt
)) {
1171 bool isHorizontal
= Scrollbar()->IsHorizontal();
1172 nscoord pos
= isHorizontal
? pt
.x
: pt
.y
;
1174 // If we should scroll-to-click, first place the middle of the slider thumb
1176 nsCOMPtr
<nsIContent
> scrollbar
;
1177 nscoord newpos
= pos
;
1178 bool scrollToClick
= ShouldScrollToClickForEvent(event
);
1179 if (scrollToClick
) {
1180 // adjust so that the middle of the thumb is placed under the click
1181 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
1185 nsSize thumbSize
= thumbFrame
->GetSize();
1186 nscoord thumbLength
= isHorizontal
? thumbSize
.width
: thumbSize
.height
;
1188 newpos
-= (thumbLength
/ 2);
1190 scrollbar
= Scrollbar()->GetContent();
1195 if (scrollToClick
) {
1196 // should aMaySnap be true here?
1197 SetCurrentThumbPosition(scrollbar
, newpos
, false, false);
1200 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
1205 SetupDrag(event
, thumbFrame
, pos
, isHorizontal
);
1210 nsresult
nsSliderFrame::StopDrag() {
1214 mScrollingWithAPZ
= false;
1216 UnsuppressDisplayport();
1218 if (mRepeatDirection
) {
1220 mRepeatDirection
= 0;
1225 void nsSliderFrame::DragThumb(bool aGrabMouseEvents
) {
1226 mDragFinished
= !aGrabMouseEvents
;
1228 if (aGrabMouseEvents
) {
1229 PresShell::SetCapturingContent(
1231 CaptureFlags::IgnoreAllowedState
| CaptureFlags::PreventDragStart
);
1233 PresShell::ReleaseCapturingContent();
1237 bool nsSliderFrame::isDraggingThumb() const {
1238 return PresShell::GetCapturingContent() == GetContent();
1241 void nsSliderFrame::AddListener() {
1243 mMediator
= new nsSliderMediator(this);
1246 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
1250 thumbFrame
->GetContent()->AddSystemEventListener(u
"mousedown"_ns
, mMediator
,
1252 thumbFrame
->GetContent()->AddSystemEventListener(u
"touchstart"_ns
, mMediator
,
1256 void nsSliderFrame::RemoveListener() {
1257 NS_ASSERTION(mMediator
, "No listener was ever added!!");
1259 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
1260 if (!thumbFrame
) return;
1262 thumbFrame
->GetContent()->RemoveSystemEventListener(u
"mousedown"_ns
,
1264 thumbFrame
->GetContent()->RemoveSystemEventListener(u
"touchstart"_ns
,
1268 bool nsSliderFrame::ShouldScrollForEvent(WidgetGUIEvent
* aEvent
) {
1269 switch (aEvent
->mMessage
) {
1275 uint16_t button
= aEvent
->AsMouseEvent()->mButton
;
1276 #ifdef MOZ_WIDGET_GTK
1277 return (button
== MouseButton::ePrimary
) ||
1278 (button
== MouseButton::eSecondary
&& GetScrollToClick()) ||
1279 (button
== MouseButton::eMiddle
&& gMiddlePref
&&
1280 !GetScrollToClick());
1282 return (button
== MouseButton::ePrimary
) ||
1283 (button
== MouseButton::eMiddle
&& gMiddlePref
);
1291 bool nsSliderFrame::ShouldScrollToClickForEvent(WidgetGUIEvent
* aEvent
) {
1292 if (!ShouldScrollForEvent(aEvent
)) {
1296 if (aEvent
->mMessage
!= eMouseDown
&& aEvent
->mMessage
!= eTouchStart
) {
1300 #if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
1301 // On Mac and Linux, clicking the scrollbar thumb should never scroll to
1303 if (IsEventOverThumb(aEvent
)) {
1308 if (aEvent
->mMessage
== eTouchStart
) {
1309 return GetScrollToClick();
1312 WidgetMouseEvent
* mouseEvent
= aEvent
->AsMouseEvent();
1313 if (mouseEvent
->mButton
== MouseButton::ePrimary
) {
1315 bool invertPref
= mouseEvent
->IsAlt();
1317 bool invertPref
= mouseEvent
->IsShift();
1319 return GetScrollToClick() != invertPref
;
1322 #ifdef MOZ_WIDGET_GTK
1323 if (mouseEvent
->mButton
== MouseButton::eSecondary
) {
1324 return !GetScrollToClick();
1331 bool nsSliderFrame::IsEventOverThumb(WidgetGUIEvent
* aEvent
) {
1332 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
1338 if (!GetEventPoint(aEvent
, eventPoint
)) {
1342 const nsRect thumbRect
= thumbFrame
->GetRect();
1343 const bool isHorizontal
= Scrollbar()->IsHorizontal();
1344 nscoord eventPos
= isHorizontal
? eventPoint
.x
: eventPoint
.y
;
1345 nscoord thumbStart
= isHorizontal
? thumbRect
.x
: thumbRect
.y
;
1346 nscoord thumbEnd
= isHorizontal
? thumbRect
.XMost() : thumbRect
.YMost();
1347 return eventPos
>= thumbStart
&& eventPos
< thumbEnd
;
1351 nsSliderFrame::HandlePress(nsPresContext
* aPresContext
, WidgetGUIEvent
* aEvent
,
1352 nsEventStatus
* aEventStatus
) {
1353 if (!ShouldScrollForEvent(aEvent
) || ShouldScrollToClickForEvent(aEvent
)) {
1357 if (IsEventOverThumb(aEvent
)) {
1361 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
1362 if (!thumbFrame
) // display:none?
1365 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::disabled
,
1366 nsGkAtoms::_true
, eCaseMatters
))
1369 nsRect thumbRect
= thumbFrame
->GetRect();
1373 if (!GetEventPoint(aEvent
, eventPoint
)) {
1377 if (Scrollbar()->IsHorizontal() ? eventPoint
.x
< thumbRect
.x
1378 : eventPoint
.y
< thumbRect
.y
) {
1382 mRepeatDirection
= change
;
1384 if (StaticPrefs::layout_scrollbars_click_and_hold_track_continue_to_end()) {
1385 // Set the destination point to the very end of the scrollbar so that
1386 // scrolling doesn't stop halfway through.
1388 mDestinationPoint
= nsPoint(GetRect().width
, GetRect().height
);
1390 mDestinationPoint
= nsPoint(0, 0);
1393 mDestinationPoint
= eventPoint
;
1402 nsSliderFrame::HandleRelease(nsPresContext
* aPresContext
,
1403 WidgetGUIEvent
* aEvent
,
1404 nsEventStatus
* aEventStatus
) {
1407 nsScrollbarFrame
* sb
= Scrollbar();
1408 if (nsIScrollbarMediator
* m
= sb
->GetScrollbarMediator()) {
1409 m
->ScrollbarReleased(sb
);
1414 void nsSliderFrame::Destroy(DestroyContext
& aContext
) {
1415 // tell our mediator if we have one we are gone.
1417 mMediator
->SetSlider(nullptr);
1418 mMediator
= nullptr;
1422 // call base class Destroy()
1423 nsContainerFrame::Destroy(aContext
);
1426 void nsSliderFrame::Notify() {
1429 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
1434 nsRect thumbRect
= thumbFrame
->GetRect();
1436 const bool isHorizontal
= Scrollbar()->IsHorizontal();
1438 // See if the thumb has moved past our destination point.
1439 // if it has we want to stop.
1441 if (mRepeatDirection
< 0) {
1442 if (thumbRect
.x
< mDestinationPoint
.x
) stop
= true;
1444 if (thumbRect
.x
+ thumbRect
.width
> mDestinationPoint
.x
) stop
= true;
1447 if (mRepeatDirection
< 0) {
1448 if (thumbRect
.y
< mDestinationPoint
.y
) stop
= true;
1450 if (thumbRect
.y
+ thumbRect
.height
> mDestinationPoint
.y
) stop
= true;
1461 void nsSliderFrame::PageScroll(bool aClickAndHold
) {
1462 int32_t changeDirection
= mRepeatDirection
;
1463 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::dir
,
1464 nsGkAtoms::reverse
, eCaseMatters
)) {
1465 changeDirection
= -changeDirection
;
1467 nsScrollbarFrame
* sb
= Scrollbar();
1469 nsIScrollableFrame
* sf
= GetScrollFrame();
1470 const ScrollSnapFlags scrollSnapFlags
=
1471 ScrollSnapFlags::IntendedDirection
| ScrollSnapFlags::IntendedEndPosition
;
1473 // If our nsIScrollbarMediator implementation is an nsIScrollableFrame,
1474 // use ScrollTo() to ensure we do not scroll past the intended
1475 // destination. Otherwise, the combination of smooth scrolling and
1476 // ScrollBy() semantics (which adds the delta to the current destination
1477 // if there is a smooth scroll in progress) can lead to scrolling too far
1479 // Only do this when the page scroll is triggered by the repeat timer
1480 // when the mouse is being held down. For multiple clicks in
1481 // succession, we want to make sure we scroll by a full page for
1482 // each click, so we use ScrollByPage().
1483 if (aClickAndHold
&& sf
) {
1484 const bool isHorizontal
= sb
->IsHorizontal();
1486 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
1491 nsRect thumbRect
= thumbFrame
->GetRect();
1493 nscoord maxDistanceAlongTrack
;
1495 maxDistanceAlongTrack
=
1496 mDestinationPoint
.x
- thumbRect
.x
- thumbRect
.width
/ 2;
1498 maxDistanceAlongTrack
=
1499 mDestinationPoint
.y
- thumbRect
.y
- thumbRect
.height
/ 2;
1502 // Convert distance along scrollbar track to amount of scrolled content.
1503 nscoord maxDistanceToScroll
= maxDistanceAlongTrack
/ GetThumbRatio();
1505 nsIContent
* content
= sb
->GetContent();
1506 const CSSIntCoord pageLength
= GetPageIncrement(content
);
1508 nsPoint pos
= sf
->GetScrollPosition();
1510 if (mCurrentClickHoldDestination
) {
1511 // We may not have arrived at the destination of the scroll from the
1512 // previous repeat timer tick, some of that scroll may still be pending.
1513 nsPoint pendingScroll
=
1514 *mCurrentClickHoldDestination
- sf
->GetScrollPosition();
1516 // Scroll by one page relative to the previous destination, so that we
1517 // scroll at a rate of a full page per repeat timer tick.
1518 pos
+= pendingScroll
;
1520 // Make a corresponding adjustment to the maxium distance we can scroll,
1521 // so we successfully avoid overshoot.
1522 maxDistanceToScroll
-= (isHorizontal
? pendingScroll
.x
: pendingScroll
.y
);
1525 nscoord distanceToScroll
=
1526 std::min(abs(maxDistanceToScroll
),
1527 CSSPixel::ToAppUnits(CSSCoord(pageLength
))) *
1531 pos
.x
+= distanceToScroll
;
1533 pos
.y
+= distanceToScroll
;
1536 mCurrentClickHoldDestination
= Some(pos
);
1538 StaticPrefs::general_smoothScroll() &&
1539 StaticPrefs::general_smoothScroll_pages()
1540 ? ScrollMode::Smooth
1541 : ScrollMode::Instant
,
1542 nullptr, scrollSnapFlags
);
1547 sb
->SetIncrementToPage(changeDirection
);
1548 if (nsIScrollbarMediator
* m
= sb
->GetScrollbarMediator()) {
1549 m
->ScrollByPage(sb
, changeDirection
, scrollSnapFlags
);
1552 PageUpDown(changeDirection
);
1555 void nsSliderFrame::SetupDrag(WidgetGUIEvent
* aEvent
, nsIFrame
* aThumbFrame
,
1556 nscoord aPos
, bool aIsHorizontal
) {
1557 if (aIsHorizontal
) {
1558 mThumbStart
= aThumbFrame
->GetPosition().x
;
1560 mThumbStart
= aThumbFrame
->GetPosition().y
;
1563 mDragStart
= aPos
- mThumbStart
;
1565 mScrollingWithAPZ
= false;
1566 StartAPZDrag(aEvent
); // sets mScrollingWithAPZ=true if appropriate
1569 printf("Pressed mDragStart=%d\n", mDragStart
);
1572 if (!mScrollingWithAPZ
) {
1573 SuppressDisplayport();
1577 float nsSliderFrame::GetThumbRatio() const {
1578 // mRatio is in thumb app units per scrolled css pixels. Convert it to a
1579 // ratio of the thumb's CSS pixels per scrolled CSS pixels. (Note the thumb
1580 // is in the scrollframe's parent's space whereas the scrolled CSS pixels
1581 // are in the scrollframe's space).
1582 return mRatio
/ AppUnitsPerCSSPixel();
1585 void nsSliderFrame::AsyncScrollbarDragInitiated(uint64_t aDragBlockId
) {
1586 mAPZDragInitiated
= Some(aDragBlockId
);
1589 void nsSliderFrame::AsyncScrollbarDragRejected() {
1590 mScrollingWithAPZ
= false;
1591 // Only suppress the displayport if we're still dragging the thumb.
1592 // Otherwise, no one will unsuppress it.
1593 if (isDraggingThumb()) {
1594 SuppressDisplayport();
1598 void nsSliderFrame::SuppressDisplayport() {
1599 if (!mSuppressionActive
) {
1600 PresShell()->SuppressDisplayport(true);
1601 mSuppressionActive
= true;
1605 void nsSliderFrame::UnsuppressDisplayport() {
1606 if (mSuppressionActive
) {
1607 PresShell()->SuppressDisplayport(false);
1608 mSuppressionActive
= false;
1612 bool nsSliderFrame::OnlySystemGroupDispatch(EventMessage aMessage
) const {
1613 // If we are in a native anonymous subtree, do not dispatch mouse-move or
1614 // pointer-move events targeted at this slider frame to web content. This
1615 // matches the behaviour of other browsers.
1616 return (aMessage
== eMouseMove
|| aMessage
== ePointerMove
) &&
1617 isDraggingThumb() && GetContent()->IsInNativeAnonymousSubtree();
1620 bool nsSliderFrame::GetEventPoint(WidgetGUIEvent
* aEvent
, nsPoint
& aPoint
) {
1621 LayoutDeviceIntPoint refPoint
;
1622 if (!GetEventPoint(aEvent
, refPoint
)) {
1625 aPoint
= nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent
, refPoint
,
1630 bool nsSliderFrame::GetEventPoint(WidgetGUIEvent
* aEvent
,
1631 LayoutDeviceIntPoint
& aPoint
) {
1632 NS_ENSURE_TRUE(aEvent
, false);
1633 WidgetTouchEvent
* touchEvent
= aEvent
->AsTouchEvent();
1635 // return false if there is more than one touch on the page, or if
1636 // we can't find a touch point
1637 if (touchEvent
->mTouches
.Length() != 1) {
1641 dom::Touch
* touch
= touchEvent
->mTouches
.SafeElementAt(0);
1645 aPoint
= touch
->mRefPoint
;
1647 aPoint
= aEvent
->mRefPoint
;
1652 NS_IMPL_ISUPPORTS(nsSliderMediator
, nsIDOMEventListener
)