Bug 1591736 - Fix AddonManagerWebAPI::IsAPIEnabled in out-of-process iframes r=mixedpuppy
[gecko.git] / layout / xul / nsSliderFrame.cpp
blob7403ce74c0979a9c4ad84a3477edf397cd434a0d
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/. */
7 //
8 // Eric Vaughan
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"
19 #include "nsCOMPtr.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 "nsBoxLayoutState.h"
31 #include "nsSprocketLayout.h"
32 #include "nsIServiceManager.h"
33 #include "nsContentUtils.h"
34 #include "nsLayoutUtils.h"
35 #include "nsDisplayList.h"
36 #include "nsRefreshDriver.h" // for nsAPostRefreshObserver
37 #include "nsSVGIntegrationUtils.h"
38 #include "mozilla/Assertions.h" // for MOZ_ASSERT
39 #include "mozilla/LookAndFeel.h"
40 #include "mozilla/MouseEvents.h"
41 #include "mozilla/Preferences.h"
42 #include "mozilla/PresShell.h"
43 #include "mozilla/Telemetry.h"
44 #include "mozilla/dom/Event.h"
45 #include "mozilla/layers/APZCCallbackHelper.h"
46 #include "mozilla/layers/AsyncDragMetrics.h"
47 #include "mozilla/layers/InputAPZContext.h"
48 #include <algorithm>
50 using namespace mozilla;
51 using mozilla::dom::Event;
52 using mozilla::layers::APZCCallbackHelper;
53 using mozilla::layers::AsyncDragMetrics;
54 using mozilla::layers::InputAPZContext;
55 using mozilla::layers::ScrollbarData;
56 using mozilla::layers::ScrollDirection;
58 bool nsSliderFrame::gMiddlePref = false;
59 int32_t nsSliderFrame::gSnapMultiplier;
61 // Turn this on if you want to debug slider frames.
62 #undef DEBUG_SLIDER
64 static already_AddRefed<nsIContent> GetContentOfBox(nsIFrame* aBox) {
65 nsCOMPtr<nsIContent> content = aBox->GetContent();
66 return content.forget();
69 nsIFrame* NS_NewSliderFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
70 return new (aPresShell) nsSliderFrame(aStyle, aPresShell->GetPresContext());
73 NS_IMPL_FRAMEARENA_HELPERS(nsSliderFrame)
75 NS_QUERYFRAME_HEAD(nsSliderFrame)
76 NS_QUERYFRAME_ENTRY(nsSliderFrame)
77 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
79 nsSliderFrame::nsSliderFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
80 : nsBoxFrame(aStyle, aPresContext, kClassID),
81 mRatio(0.0f),
82 mDragStart(0),
83 mThumbStart(0),
84 mCurPos(0),
85 mChange(0),
86 mDragFinished(true),
87 mUserChanged(false),
88 mScrollingWithAPZ(false),
89 mSuppressionActive(false) {}
91 // stop timer
92 nsSliderFrame::~nsSliderFrame() {
93 if (mSuppressionActive) {
94 if (mozilla::PresShell* presShell = PresShell()) {
95 presShell->SuppressDisplayport(false);
100 void nsSliderFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
101 nsIFrame* aPrevInFlow) {
102 nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
104 static bool gotPrefs = false;
105 if (!gotPrefs) {
106 gotPrefs = true;
108 gMiddlePref = Preferences::GetBool("middlemouse.scrollbarPosition");
109 gSnapMultiplier = Preferences::GetInt("slider.snapMultiplier");
112 mCurPos = GetCurrentPosition(aContent);
115 void nsSliderFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
116 nsBoxFrame::RemoveFrame(aListID, aOldFrame);
117 if (mFrames.IsEmpty()) RemoveListener();
120 void nsSliderFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
121 const nsLineList::iterator* aPrevFrameLine,
122 nsFrameList& aFrameList) {
123 bool wasEmpty = mFrames.IsEmpty();
124 nsBoxFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, aFrameList);
125 if (wasEmpty) AddListener();
128 void nsSliderFrame::AppendFrames(ChildListID aListID, nsFrameList& aFrameList) {
129 // if we have no children and on was added then make sure we add the
130 // listener
131 bool wasEmpty = mFrames.IsEmpty();
132 nsBoxFrame::AppendFrames(aListID, aFrameList);
133 if (wasEmpty) AddListener();
136 int32_t nsSliderFrame::GetCurrentPosition(nsIContent* content) {
137 return GetIntegerAttribute(content, nsGkAtoms::curpos, 0);
140 int32_t nsSliderFrame::GetMinPosition(nsIContent* content) {
141 return GetIntegerAttribute(content, nsGkAtoms::minpos, 0);
144 int32_t nsSliderFrame::GetMaxPosition(nsIContent* content) {
145 return GetIntegerAttribute(content, nsGkAtoms::maxpos, 100);
148 int32_t nsSliderFrame::GetIncrement(nsIContent* content) {
149 return GetIntegerAttribute(content, nsGkAtoms::increment, 1);
152 int32_t nsSliderFrame::GetPageIncrement(nsIContent* content) {
153 return GetIntegerAttribute(content, nsGkAtoms::pageincrement, 10);
156 int32_t nsSliderFrame::GetIntegerAttribute(nsIContent* content, nsAtom* atom,
157 int32_t defaultValue) {
158 nsAutoString value;
159 if (content->IsElement()) {
160 content->AsElement()->GetAttr(kNameSpaceID_None, atom, value);
162 if (!value.IsEmpty()) {
163 nsresult error;
165 // convert it to an integer
166 defaultValue = value.ToInteger(&error);
169 return defaultValue;
172 nsresult nsSliderFrame::AttributeChanged(int32_t aNameSpaceID,
173 nsAtom* aAttribute, int32_t aModType) {
174 nsresult rv =
175 nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
176 // if the current position changes
177 if (aAttribute == nsGkAtoms::curpos) {
178 CurrentPositionChanged();
179 } else if (aAttribute == nsGkAtoms::minpos ||
180 aAttribute == nsGkAtoms::maxpos) {
181 // bounds check it.
183 nsIFrame* scrollbarBox = GetScrollbar();
184 nsCOMPtr<nsIContent> scrollbar = GetContentOfBox(scrollbarBox);
185 int32_t current = GetCurrentPosition(scrollbar);
186 int32_t min = GetMinPosition(scrollbar);
187 int32_t max = GetMaxPosition(scrollbar);
189 if (current < min || current > max) {
190 int32_t direction = 0;
191 if (current < min || max < min) {
192 current = min;
193 direction = -1;
194 } else if (current > max) {
195 current = max;
196 direction = 1;
199 // set the new position and notify observers
200 nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
201 if (scrollbarFrame) {
202 nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
203 scrollbarFrame->SetIncrementToWhole(direction);
204 if (mediator) {
205 mediator->ScrollByWhole(scrollbarFrame, direction,
206 nsIScrollbarMediator::ENABLE_SNAP);
209 // 'this' might be destroyed here
211 nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
212 scrollbar->AsElement(), nsGkAtoms::curpos, current));
216 if (aAttribute == nsGkAtoms::minpos || aAttribute == nsGkAtoms::maxpos ||
217 aAttribute == nsGkAtoms::pageincrement ||
218 aAttribute == nsGkAtoms::increment) {
219 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
220 NS_FRAME_IS_DIRTY);
223 return rv;
226 void nsSliderFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
227 const nsDisplayListSet& aLists) {
228 if (aBuilder->IsForEventDelivery() && isDraggingThumb()) {
229 // This is EVIL, we shouldn't be messing with event delivery just to get
230 // thumb mouse drag events to arrive at the slider!
231 aLists.Outlines()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, this);
232 return;
235 nsBoxFrame::BuildDisplayList(aBuilder, aLists);
238 static bool UsesCustomScrollbarMediator(nsIFrame* scrollbarBox) {
239 if (nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox)) {
240 if (nsIScrollbarMediator* mediator =
241 scrollbarFrame->GetScrollbarMediator()) {
242 nsIScrollableFrame* scrollFrame = do_QueryFrame(mediator);
243 // The scrollbar mediator is not the scroll frame.
244 // That means this scroll frame has a custom scrollbar mediator.
245 if (!scrollFrame) {
246 return true;
250 return false;
253 void nsSliderFrame::BuildDisplayListForChildren(
254 nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) {
255 // if we are too small to have a thumb don't paint it.
256 nsIFrame* thumb = nsBox::GetChildXULBox(this);
258 if (thumb) {
259 nsRect thumbRect(thumb->GetRect());
260 nsMargin m;
261 thumb->GetXULMargin(m);
262 thumbRect.Inflate(m);
264 nsRect sliderTrack;
265 GetXULClientRect(sliderTrack);
267 if (sliderTrack.width < thumbRect.width ||
268 sliderTrack.height < thumbRect.height)
269 return;
271 // If this scrollbar is the scrollbar of an actively scrolled scroll frame,
272 // layerize the scrollbar thumb, wrap it in its own ContainerLayer and
273 // attach scrolling information to it.
274 // We do this here and not in the thumb's nsBoxFrame::BuildDisplayList so
275 // that the event region that gets created for the thumb is included in
276 // the nsDisplayOwnLayer contents.
278 const mozilla::layers::ScrollableLayerGuid::ViewID scrollTargetId =
279 aBuilder->GetCurrentScrollbarTarget();
280 const bool thumbGetsLayer =
281 (scrollTargetId != layers::ScrollableLayerGuid::NULL_SCROLL_ID);
283 if (thumbGetsLayer) {
284 const Maybe<ScrollDirection> scrollDirection =
285 aBuilder->GetCurrentScrollbarDirection();
286 MOZ_ASSERT(scrollDirection.isSome());
287 const bool isHorizontal =
288 *scrollDirection == ScrollDirection::eHorizontal;
289 const float appUnitsPerCss = float(AppUnitsPerCSSPixel());
290 const CSSCoord thumbLength = NSAppUnitsToFloatPixels(
291 isHorizontal ? thumbRect.width : thumbRect.height, appUnitsPerCss);
293 nsIFrame* scrollbarBox = GetScrollbar();
294 bool isAsyncDraggable = !UsesCustomScrollbarMediator(scrollbarBox);
296 nsPoint scrollPortOrigin;
297 if (nsIScrollableFrame* scrollFrame =
298 do_QueryFrame(scrollbarBox->GetParent())) {
299 scrollPortOrigin = scrollFrame->GetScrollPortRect().TopLeft();
300 } else {
301 isAsyncDraggable = false;
304 // This rect is the range in which the scroll thumb can slide in.
305 sliderTrack = sliderTrack + GetRect().TopLeft() +
306 scrollbarBox->GetPosition() - scrollPortOrigin;
307 const CSSCoord sliderTrackStart = NSAppUnitsToFloatPixels(
308 isHorizontal ? sliderTrack.x : sliderTrack.y, appUnitsPerCss);
309 const CSSCoord sliderTrackLength = NSAppUnitsToFloatPixels(
310 isHorizontal ? sliderTrack.width : sliderTrack.height,
311 appUnitsPerCss);
312 const CSSCoord thumbStart = NSAppUnitsToFloatPixels(
313 isHorizontal ? thumbRect.x : thumbRect.y, appUnitsPerCss);
315 const nsRect overflow = thumb->GetVisualOverflowRectRelativeToParent();
316 nsSize refSize = aBuilder->RootReferenceFrame()->GetSize();
317 const gfxSize scale = nsLayoutUtils::GetTransformToAncestorScale(thumb);
318 if (scale.width != 0 && scale.height != 0) {
319 refSize.width /= scale.width;
320 refSize.height /= scale.height;
322 nsRect dirty = aBuilder->GetVisibleRect().Intersect(thumbRect);
323 dirty = nsLayoutUtils::ComputePartialPrerenderArea(
324 aBuilder->GetVisibleRect(), overflow, refSize);
326 nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
327 aBuilder, this, dirty, dirty);
329 // Clip the thumb layer to the slider track. This is necessary to ensure
330 // FrameLayerBuilder is able to merge content before and after the
331 // scrollframe into the same layer (otherwise it thinks the thumb could
332 // potentially move anywhere within the existing clip).
333 DisplayListClipState::AutoSaveRestore thumbClipState(aBuilder);
334 thumbClipState.ClipContainingBlockDescendants(
335 GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this));
337 // Have the thumb's container layer capture the current clip, so
338 // it doesn't apply to the thumb's contents. This allows the contents
339 // to be fully rendered even if they're partially or fully offscreen,
340 // so async scrolling can still bring it into view.
341 DisplayListClipState::AutoSaveRestore thumbContentsClipState(aBuilder);
342 thumbContentsClipState.Clear();
344 nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
345 nsDisplayListCollection tempLists(aBuilder);
346 nsBoxFrame::BuildDisplayListForChildren(aBuilder, tempLists);
348 // This is a bit of a hack. Collect up all descendant display items
349 // and merge them into a single Content() list.
350 nsDisplayList masterList;
351 masterList.AppendToTop(tempLists.BorderBackground());
352 masterList.AppendToTop(tempLists.BlockBorderBackgrounds());
353 masterList.AppendToTop(tempLists.Floats());
354 masterList.AppendToTop(tempLists.Content());
355 masterList.AppendToTop(tempLists.PositionedDescendants());
356 masterList.AppendToTop(tempLists.Outlines());
358 // Restore the saved clip so it applies to the thumb container layer.
359 thumbContentsClipState.Restore();
361 // Wrap the list to make it its own layer.
362 const ActiveScrolledRoot* ownLayerASR = contASRTracker.GetContainerASR();
363 aLists.Content()->AppendNewToTop<nsDisplayOwnLayer>(
364 aBuilder, this, &masterList, ownLayerASR,
365 nsDisplayOwnLayerFlags::None,
366 ScrollbarData::CreateForThumb(*scrollDirection, GetThumbRatio(),
367 thumbStart, thumbLength,
368 isAsyncDraggable, sliderTrackStart,
369 sliderTrackLength, scrollTargetId),
370 true, false, nsDisplayOwnLayer::OwnLayerForScrollThumb);
372 return;
376 nsBoxFrame::BuildDisplayListForChildren(aBuilder, aLists);
379 NS_IMETHODIMP
380 nsSliderFrame::DoXULLayout(nsBoxLayoutState& aState) {
381 // get the thumb should be our only child
382 nsIFrame* thumbBox = nsBox::GetChildXULBox(this);
384 if (!thumbBox) {
385 SyncLayout(aState);
386 return NS_OK;
389 EnsureOrient();
391 // get the content area inside our borders
392 nsRect clientRect;
393 GetXULClientRect(clientRect);
395 // get the scrollbar
396 nsIFrame* scrollbarBox = GetScrollbar();
397 nsCOMPtr<nsIContent> scrollbar = GetContentOfBox(scrollbarBox);
399 // get the thumb's pref size
400 nsSize thumbSize = thumbBox->GetXULPrefSize(aState);
402 if (IsXULHorizontal())
403 thumbSize.height = clientRect.height;
404 else
405 thumbSize.width = clientRect.width;
407 int32_t curPos = GetCurrentPosition(scrollbar);
408 int32_t minPos = GetMinPosition(scrollbar);
409 int32_t maxPos = GetMaxPosition(scrollbar);
410 int32_t pageIncrement = GetPageIncrement(scrollbar);
412 maxPos = std::max(minPos, maxPos);
413 curPos = clamped(curPos, minPos, maxPos);
415 nscoord& availableLength =
416 IsXULHorizontal() ? clientRect.width : clientRect.height;
417 nscoord& thumbLength = IsXULHorizontal() ? thumbSize.width : thumbSize.height;
419 if ((pageIncrement + maxPos - minPos) > 0 && thumbBox->GetXULFlex() > 0) {
420 float ratio = float(pageIncrement) / float(maxPos - minPos + pageIncrement);
421 thumbLength =
422 std::max(thumbLength, NSToCoordRound(availableLength * ratio));
425 // Round the thumb's length to device pixels.
426 nsPresContext* presContext = PresContext();
427 thumbLength = presContext->DevPixelsToAppUnits(
428 presContext->AppUnitsToDevPixels(thumbLength));
430 // mRatio translates the thumb position in app units to the value.
431 mRatio = (minPos != maxPos)
432 ? float(availableLength - thumbLength) / float(maxPos - minPos)
433 : 1;
435 // in reverse mode, curpos is reversed such that lower values are to the
436 // right or bottom and increase leftwards or upwards. In this case, use the
437 // offset from the end instead of the beginning.
438 bool reverse = mContent->AsElement()->AttrValueIs(
439 kNameSpaceID_None, nsGkAtoms::dir, nsGkAtoms::reverse, eCaseMatters);
440 nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos);
442 // set the thumb's coord to be the current pos * the ratio.
443 nsRect thumbRect(clientRect.x, clientRect.y, thumbSize.width,
444 thumbSize.height);
445 int32_t& thumbPos = (IsXULHorizontal() ? thumbRect.x : thumbRect.y);
446 thumbPos += NSToCoordRound(pos * mRatio);
448 nsRect oldThumbRect(thumbBox->GetRect());
449 LayoutChildAt(aState, thumbBox, thumbRect);
451 SyncLayout(aState);
453 // Redraw only if thumb changed size.
454 if (!oldThumbRect.IsEqualInterior(thumbRect)) XULRedraw(aState);
456 return NS_OK;
459 nsresult nsSliderFrame::HandleEvent(nsPresContext* aPresContext,
460 WidgetGUIEvent* aEvent,
461 nsEventStatus* aEventStatus) {
462 NS_ENSURE_ARG_POINTER(aEventStatus);
464 if (mAPZDragInitiated &&
465 *mAPZDragInitiated == InputAPZContext::GetInputBlockId() &&
466 aEvent->mMessage == eMouseDown) {
467 // If we get the mousedown after the APZ notification, then immediately
468 // switch into the state corresponding to an APZ thumb-drag. Note that
469 // we can't just do this in AsyncScrollbarDragInitiated() directly because
470 // the handling for this mousedown event in the presShell will reset the
471 // capturing content which makes isDraggingThumb() return false. We check
472 // the input block here to make sure that we correctly handle any ordering
473 // of {eMouseDown arriving, AsyncScrollbarDragInitiated() being called}.
474 mAPZDragInitiated = Nothing();
475 DragThumb(true);
476 mScrollingWithAPZ = true;
477 return NS_OK;
480 // If a web page calls event.preventDefault() we still want to
481 // scroll when scroll arrow is clicked. See bug 511075.
482 if (!mContent->IsInNativeAnonymousSubtree() &&
483 nsEventStatus_eConsumeNoDefault == *aEventStatus) {
484 return NS_OK;
487 if (!mDragFinished && !isDraggingThumb()) {
488 StopDrag();
489 return NS_OK;
492 nsIFrame* scrollbarBox = GetScrollbar();
493 nsCOMPtr<nsIContent> scrollbar;
494 scrollbar = GetContentOfBox(scrollbarBox);
495 bool isHorizontal = IsXULHorizontal();
497 if (isDraggingThumb()) {
498 switch (aEvent->mMessage) {
499 case eTouchMove:
500 case eMouseMove: {
501 if (mScrollingWithAPZ) {
502 break;
504 nsPoint eventPoint;
505 if (!GetEventPoint(aEvent, eventPoint)) {
506 break;
508 if (mChange) {
509 // On Linux the destination point is determined by the initial click
510 // on the scrollbar track and doesn't change until the mouse button
511 // is released.
512 #ifndef MOZ_WIDGET_GTK
513 // On the other platforms we need to update the destination point now.
514 mDestinationPoint = eventPoint;
515 StopRepeat();
516 StartRepeat();
517 #endif
518 break;
521 nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y;
523 nsIFrame* thumbFrame = mFrames.FirstChild();
524 if (!thumbFrame) {
525 return NS_OK;
528 // take our current position and subtract the start location
529 pos -= mDragStart;
530 bool isMouseOutsideThumb = false;
531 if (gSnapMultiplier) {
532 nsSize thumbSize = thumbFrame->GetSize();
533 if (isHorizontal) {
534 // horizontal scrollbar - check if mouse is above or below thumb
535 // XXXbz what about looking at the .y of the thumb's rect? Is that
536 // always zero here?
537 if (eventPoint.y < -gSnapMultiplier * thumbSize.height ||
538 eventPoint.y >
539 thumbSize.height + gSnapMultiplier * thumbSize.height)
540 isMouseOutsideThumb = true;
541 } else {
542 // vertical scrollbar - check if mouse is left or right of thumb
543 if (eventPoint.x < -gSnapMultiplier * thumbSize.width ||
544 eventPoint.x >
545 thumbSize.width + gSnapMultiplier * thumbSize.width)
546 isMouseOutsideThumb = true;
549 if (aEvent->mClass == eTouchEventClass) {
550 *aEventStatus = nsEventStatus_eConsumeNoDefault;
552 if (isMouseOutsideThumb) {
553 SetCurrentThumbPosition(scrollbar, mThumbStart, false, false);
554 return NS_OK;
557 // set it
558 SetCurrentThumbPosition(scrollbar, pos, false, true); // with snapping
559 } break;
561 case eTouchEnd:
562 case eMouseUp:
563 if (ShouldScrollForEvent(aEvent)) {
564 StopDrag();
565 // we MUST call nsFrame HandleEvent for mouse ups to maintain the
566 // selection state and capture state.
567 return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
569 break;
571 default:
572 break;
575 // return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
576 return NS_OK;
577 } else if (ShouldScrollToClickForEvent(aEvent)) {
578 nsPoint eventPoint;
579 if (!GetEventPoint(aEvent, eventPoint)) {
580 return NS_OK;
582 nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y;
584 // adjust so that the middle of the thumb is placed under the click
585 nsIFrame* thumbFrame = mFrames.FirstChild();
586 if (!thumbFrame) {
587 return NS_OK;
589 nsSize thumbSize = thumbFrame->GetSize();
590 nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height;
592 // set it
593 AutoWeakFrame weakFrame(this);
594 // should aMaySnap be true here?
595 SetCurrentThumbPosition(scrollbar, pos - thumbLength / 2, false, false);
596 NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
598 DragThumb(true);
600 #ifdef MOZ_WIDGET_GTK
601 RefPtr<Element> thumb = thumbFrame->GetContent()->AsElement();
602 thumb->SetAttr(kNameSpaceID_None, nsGkAtoms::active,
603 NS_LITERAL_STRING("true"), true);
604 #endif
606 if (aEvent->mClass == eTouchEventClass) {
607 *aEventStatus = nsEventStatus_eConsumeNoDefault;
610 if (isHorizontal)
611 mThumbStart = thumbFrame->GetPosition().x;
612 else
613 mThumbStart = thumbFrame->GetPosition().y;
615 mDragStart = pos - mThumbStart;
617 #ifdef MOZ_WIDGET_GTK
618 else if (ShouldScrollForEvent(aEvent) && aEvent->mClass == eMouseEventClass &&
619 aEvent->AsMouseEvent()->mButton == MouseButton::eRight) {
620 // HandlePress and HandleRelease are usually called via
621 // nsFrame::HandleEvent, but only for the left mouse button.
622 if (aEvent->mMessage == eMouseDown) {
623 HandlePress(aPresContext, aEvent, aEventStatus);
624 } else if (aEvent->mMessage == eMouseUp) {
625 HandleRelease(aPresContext, aEvent, aEventStatus);
628 return NS_OK;
630 #endif
632 // XXX hack until handle release is actually called in nsframe.
633 // if (aEvent->mMessage == eMouseOut ||
634 // aEvent->mMessage == NS_MOUSE_RIGHT_BUTTON_UP ||
635 // aEvent->mMessage == NS_MOUSE_LEFT_BUTTON_UP) {
636 // HandleRelease(aPresContext, aEvent, aEventStatus);
637 // }
639 if (aEvent->mMessage == eMouseOut && mChange)
640 HandleRelease(aPresContext, aEvent, aEventStatus);
642 return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
645 // Helper function to collect the "scroll to click" metric. Beware of
646 // caching this, users expect to be able to change the system preference
647 // and see the browser change its behavior immediately.
648 bool nsSliderFrame::GetScrollToClick() {
649 if (GetScrollbar() != this) {
650 return LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollToClick, false);
653 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
654 nsGkAtoms::movetoclick,
655 nsGkAtoms::_true, eCaseMatters)) {
656 return true;
658 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
659 nsGkAtoms::movetoclick,
660 nsGkAtoms::_false, eCaseMatters)) {
661 return false;
664 #ifdef XP_MACOSX
665 return true;
666 #else
667 return false;
668 #endif
671 nsIFrame* nsSliderFrame::GetScrollbar() {
672 // if we are in a scrollbar then return the scrollbar's content node
673 // if we are not then return ours.
674 nsIFrame* scrollbar;
675 nsScrollbarButtonFrame::GetParentWithTag(nsGkAtoms::scrollbar, this,
676 scrollbar);
678 if (scrollbar == nullptr) return this;
680 return scrollbar->IsXULBoxFrame() ? scrollbar : this;
683 void nsSliderFrame::PageUpDown(nscoord change) {
684 // on a page up or down get our page increment. We get this by getting the
685 // scrollbar we are in and asking it for the current position and the page
686 // increment. If we are not in a scrollbar we will get the values from our own
687 // node.
688 nsIFrame* scrollbarBox = GetScrollbar();
689 nsCOMPtr<nsIContent> scrollbar;
690 scrollbar = GetContentOfBox(scrollbarBox);
692 nscoord pageIncrement = GetPageIncrement(scrollbar);
693 int32_t curpos = GetCurrentPosition(scrollbar);
694 int32_t minpos = GetMinPosition(scrollbar);
695 int32_t maxpos = GetMaxPosition(scrollbar);
697 // get the new position and make sure it is in bounds
698 int32_t newpos = curpos + change * pageIncrement;
699 if (newpos < minpos || maxpos < minpos)
700 newpos = minpos;
701 else if (newpos > maxpos)
702 newpos = maxpos;
704 SetCurrentPositionInternal(scrollbar, newpos, true);
707 // called when the current position changed and we need to update the thumb's
708 // location
709 void nsSliderFrame::CurrentPositionChanged() {
710 nsIFrame* scrollbarBox = GetScrollbar();
711 nsCOMPtr<nsIContent> scrollbar = GetContentOfBox(scrollbarBox);
713 // get the current position
714 int32_t curPos = GetCurrentPosition(scrollbar);
716 // do nothing if the position did not change
717 if (mCurPos == curPos) return;
719 // get our current min and max position from our content node
720 int32_t minPos = GetMinPosition(scrollbar);
721 int32_t maxPos = GetMaxPosition(scrollbar);
723 maxPos = std::max(minPos, maxPos);
724 curPos = clamped(curPos, minPos, maxPos);
726 // get the thumb's rect
727 nsIFrame* thumbFrame = mFrames.FirstChild();
728 if (!thumbFrame) return; // The thumb may stream in asynchronously via XBL.
730 nsRect thumbRect = thumbFrame->GetRect();
732 nsRect clientRect;
733 GetXULClientRect(clientRect);
735 // figure out the new rect
736 nsRect newThumbRect(thumbRect);
738 bool reverse = mContent->AsElement()->AttrValueIs(
739 kNameSpaceID_None, nsGkAtoms::dir, nsGkAtoms::reverse, eCaseMatters);
740 nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos);
742 if (IsXULHorizontal())
743 newThumbRect.x = clientRect.x + NSToCoordRound(pos * mRatio);
744 else
745 newThumbRect.y = clientRect.y + NSToCoordRound(pos * mRatio);
747 // avoid putting the scroll thumb at subpixel positions which cause needless
748 // invalidations
749 nscoord appUnitsPerPixel = PresContext()->AppUnitsPerDevPixel();
750 nsPoint snappedThumbLocation =
751 ToAppUnits(newThumbRect.TopLeft().ToNearestPixels(appUnitsPerPixel),
752 appUnitsPerPixel);
753 if (IsXULHorizontal()) {
754 newThumbRect.x = snappedThumbLocation.x;
755 } else {
756 newThumbRect.y = snappedThumbLocation.y;
759 // set the rect
760 thumbFrame->SetRect(newThumbRect);
762 // Request a repaint of the scrollbar
763 nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
764 nsIScrollbarMediator* mediator =
765 scrollbarFrame ? scrollbarFrame->GetScrollbarMediator() : nullptr;
766 if (!mediator || !mediator->ShouldSuppressScrollbarRepaints()) {
767 SchedulePaint();
770 mCurPos = curPos;
773 static void UpdateAttribute(Element* aScrollbar, nscoord aNewPos, bool aNotify,
774 bool aIsSmooth) {
775 nsAutoString str;
776 str.AppendInt(aNewPos);
778 if (aIsSmooth) {
779 aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth,
780 NS_LITERAL_STRING("true"), false);
782 aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, str, aNotify);
783 if (aIsSmooth) {
784 aScrollbar->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false);
788 // Use this function when you want to set the scroll position via the position
789 // of the scrollbar thumb, e.g. when dragging the slider. This function scrolls
790 // the content in such a way that thumbRect.x/.y becomes aNewThumbPos.
791 void nsSliderFrame::SetCurrentThumbPosition(nsIContent* aScrollbar,
792 nscoord aNewThumbPos,
793 bool aIsSmooth, bool aMaySnap) {
794 nsRect crect;
795 GetXULClientRect(crect);
796 nscoord offset = IsXULHorizontal() ? crect.x : crect.y;
797 int32_t newPos = NSToIntRound((aNewThumbPos - offset) / mRatio);
799 if (aMaySnap &&
800 mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::snap,
801 nsGkAtoms::_true, eCaseMatters)) {
802 // If snap="true", then the slider may only be set to min + (increment * x).
803 // Otherwise, the slider may be set to any positive integer.
804 int32_t increment = GetIncrement(aScrollbar);
805 newPos = NSToIntRound(newPos / float(increment)) * increment;
808 SetCurrentPosition(aScrollbar, newPos, aIsSmooth);
811 // Use this function when you know the target scroll position of the scrolled
812 // content. aNewPos should be passed to this function as a position as if the
813 // minpos is 0. That is, the minpos will be added to the position by this
814 // function. In a reverse direction slider, the newpos should be the distance
815 // from the end.
816 void nsSliderFrame::SetCurrentPosition(nsIContent* aScrollbar, int32_t aNewPos,
817 bool aIsSmooth) {
818 // get min and max position from our content node
819 int32_t minpos = GetMinPosition(aScrollbar);
820 int32_t maxpos = GetMaxPosition(aScrollbar);
822 // in reverse direction sliders, flip the value so that it goes from
823 // right to left, or bottom to top.
824 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
825 nsGkAtoms::reverse, eCaseMatters))
826 aNewPos = maxpos - aNewPos;
827 else
828 aNewPos += minpos;
830 // get the new position and make sure it is in bounds
831 if (aNewPos < minpos || maxpos < minpos)
832 aNewPos = minpos;
833 else if (aNewPos > maxpos)
834 aNewPos = maxpos;
836 SetCurrentPositionInternal(aScrollbar, aNewPos, aIsSmooth);
839 void nsSliderFrame::SetCurrentPositionInternal(nsIContent* aScrollbar,
840 int32_t aNewPos,
841 bool aIsSmooth) {
842 nsCOMPtr<nsIContent> scrollbar = aScrollbar;
843 nsIFrame* scrollbarBox = GetScrollbar();
844 AutoWeakFrame weakFrame(this);
846 mUserChanged = true;
848 nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
849 if (scrollbarFrame) {
850 // See if we have a mediator.
851 nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
852 if (mediator) {
853 nscoord oldPos =
854 nsPresContext::CSSPixelsToAppUnits(GetCurrentPosition(scrollbar));
855 nscoord newPos = nsPresContext::CSSPixelsToAppUnits(aNewPos);
856 mediator->ThumbMoved(scrollbarFrame, oldPos, newPos);
857 if (!weakFrame.IsAlive()) {
858 return;
860 UpdateAttribute(scrollbar->AsElement(), aNewPos, /* aNotify */ false,
861 aIsSmooth);
862 CurrentPositionChanged();
863 mUserChanged = false;
864 return;
868 UpdateAttribute(scrollbar->AsElement(), aNewPos, true, aIsSmooth);
869 if (!weakFrame.IsAlive()) {
870 return;
872 mUserChanged = false;
874 #ifdef DEBUG_SLIDER
875 printf("Current Pos=%d\n", aNewPos);
876 #endif
879 void nsSliderFrame::SetInitialChildList(ChildListID aListID,
880 nsFrameList& aChildList) {
881 nsBoxFrame::SetInitialChildList(aListID, aChildList);
882 if (aListID == kPrincipalList) {
883 AddListener();
887 nsresult nsSliderMediator::HandleEvent(dom::Event* aEvent) {
888 // Only process the event if the thumb is not being dragged.
889 if (mSlider && !mSlider->isDraggingThumb()) return mSlider->StartDrag(aEvent);
891 return NS_OK;
894 class AsyncScrollbarDragStarter final : public nsAPostRefreshObserver {
895 public:
896 AsyncScrollbarDragStarter(mozilla::PresShell* aPresShell, nsIWidget* aWidget,
897 const AsyncDragMetrics& aDragMetrics)
898 : mPresShell(aPresShell), mWidget(aWidget), mDragMetrics(aDragMetrics) {}
899 virtual ~AsyncScrollbarDragStarter() {}
901 void DidRefresh() override {
902 if (!mPresShell) {
903 MOZ_ASSERT_UNREACHABLE(
904 "Post-refresh observer fired again after failed attempt at "
905 "unregistering it");
906 return;
909 mWidget->StartAsyncScrollbarDrag(mDragMetrics);
911 if (!mPresShell->RemovePostRefreshObserver(this)) {
912 MOZ_ASSERT_UNREACHABLE(
913 "Unable to unregister post-refresh observer! Leaking it instead of "
914 "leaving garbage registered");
915 // Graceful handling, just in case...
916 mPresShell = nullptr;
917 mWidget = nullptr;
918 return;
921 delete this;
924 private:
925 RefPtr<mozilla::PresShell> mPresShell;
926 RefPtr<nsIWidget> mWidget;
927 AsyncDragMetrics mDragMetrics;
930 static bool ScrollFrameWillBuildScrollInfoLayer(nsIFrame* aScrollFrame) {
932 * Note: if changing the conditions in this function, make a corresponding
933 * change to nsDisplayListBuilder::ShouldBuildScrollInfoItemsForHoisting()
934 * in nsDisplayList.cpp.
936 nsIFrame* current = aScrollFrame;
937 while (current) {
938 if (nsSVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(
939 current)) {
940 return true;
942 current = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(current);
944 return false;
947 nsIScrollableFrame* nsSliderFrame::GetScrollFrame() {
948 nsIFrame* scrollbarBox = GetScrollbar();
949 if (!scrollbarBox) {
950 return nullptr;
953 nsContainerFrame* scrollFrame = scrollbarBox->GetParent();
954 if (!scrollFrame) {
955 return nullptr;
958 nsIScrollableFrame* scrollFrameAsScrollable = do_QueryFrame(scrollFrame);
959 return scrollFrameAsScrollable;
962 void nsSliderFrame::StartAPZDrag(WidgetGUIEvent* aEvent) {
963 if (!aEvent->mFlags.mHandledByAPZ) {
964 return;
967 if (!gfxPlatform::GetPlatform()->SupportsApzDragInput()) {
968 return;
971 nsIFrame* scrollbarBox = GetScrollbar();
972 nsContainerFrame* scrollFrame = scrollbarBox->GetParent();
973 if (!scrollFrame) {
974 return;
977 nsIContent* scrollableContent = scrollFrame->GetContent();
978 if (!scrollableContent) {
979 return;
982 // APZ dragging requires the scrollbar to be layerized, which doesn't
983 // happen for scroll info layers.
984 if (ScrollFrameWillBuildScrollInfoLayer(scrollFrame)) {
985 return;
988 // Custom scrollbar mediators are not supported in the APZ codepath.
989 if (UsesCustomScrollbarMediator(scrollbarBox)) {
990 return;
993 bool isHorizontal = IsXULHorizontal();
995 mozilla::layers::ScrollableLayerGuid::ViewID scrollTargetId;
996 bool hasID = nsLayoutUtils::FindIDFor(scrollableContent, &scrollTargetId);
997 bool hasAPZView =
998 hasID && (scrollTargetId != layers::ScrollableLayerGuid::NULL_SCROLL_ID);
1000 if (!hasAPZView) {
1001 return;
1004 if (!nsLayoutUtils::HasDisplayPort(scrollableContent)) {
1005 return;
1008 mozilla::PresShell* presShell = PresShell();
1009 uint64_t inputblockId = InputAPZContext::GetInputBlockId();
1010 uint32_t presShellId = presShell->GetPresShellId();
1011 AsyncDragMetrics dragMetrics(
1012 scrollTargetId, presShellId, inputblockId,
1013 NSAppUnitsToFloatPixels(mDragStart, float(AppUnitsPerCSSPixel())),
1014 isHorizontal ? ScrollDirection::eHorizontal : ScrollDirection::eVertical);
1016 // It's important to set this before calling
1017 // nsIWidget::StartAsyncScrollbarDrag(), because in some configurations, that
1018 // can call AsyncScrollbarDragRejected() synchronously, which clears the flag
1019 // (and we want it to stay cleared in that case).
1020 mScrollingWithAPZ = true;
1022 // When we start an APZ drag, we wont get mouse events for the drag.
1023 // APZ will consume them all and only notify us of the new scroll position.
1024 bool waitForRefresh = InputAPZContext::HavePendingLayerization();
1025 nsIWidget* widget = this->GetNearestWidget();
1026 if (waitForRefresh) {
1027 waitForRefresh = presShell->AddPostRefreshObserver(
1028 new AsyncScrollbarDragStarter(presShell, widget, dragMetrics));
1030 if (!waitForRefresh) {
1031 widget->StartAsyncScrollbarDrag(dragMetrics);
1035 nsresult nsSliderFrame::StartDrag(Event* aEvent) {
1036 #ifdef DEBUG_SLIDER
1037 printf("Begin dragging\n");
1038 #endif
1039 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
1040 nsGkAtoms::_true, eCaseMatters))
1041 return NS_OK;
1043 WidgetGUIEvent* event = aEvent->WidgetEventPtr()->AsGUIEvent();
1045 if (!ShouldScrollForEvent(event)) {
1046 return NS_OK;
1049 nsPoint pt;
1050 if (!GetEventPoint(event, pt)) {
1051 return NS_OK;
1053 bool isHorizontal = IsXULHorizontal();
1054 nscoord pos = isHorizontal ? pt.x : pt.y;
1056 // If we should scroll-to-click, first place the middle of the slider thumb
1057 // under the mouse.
1058 nsCOMPtr<nsIContent> scrollbar;
1059 nscoord newpos = pos;
1060 bool scrollToClick = ShouldScrollToClickForEvent(event);
1061 if (scrollToClick) {
1062 // adjust so that the middle of the thumb is placed under the click
1063 nsIFrame* thumbFrame = mFrames.FirstChild();
1064 if (!thumbFrame) {
1065 return NS_OK;
1067 nsSize thumbSize = thumbFrame->GetSize();
1068 nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height;
1070 newpos -= (thumbLength / 2);
1072 nsIFrame* scrollbarBox = GetScrollbar();
1073 scrollbar = GetContentOfBox(scrollbarBox);
1076 DragThumb(true);
1078 if (scrollToClick) {
1079 // should aMaySnap be true here?
1080 SetCurrentThumbPosition(scrollbar, newpos, false, false);
1083 nsIFrame* thumbFrame = mFrames.FirstChild();
1084 if (!thumbFrame) {
1085 return NS_OK;
1088 #ifdef MOZ_WIDGET_GTK
1089 RefPtr<Element> thumb = thumbFrame->GetContent()->AsElement();
1090 thumb->SetAttr(kNameSpaceID_None, nsGkAtoms::active,
1091 NS_LITERAL_STRING("true"), true);
1092 #endif
1094 if (isHorizontal)
1095 mThumbStart = thumbFrame->GetPosition().x;
1096 else
1097 mThumbStart = thumbFrame->GetPosition().y;
1099 mDragStart = pos - mThumbStart;
1101 mScrollingWithAPZ = false;
1102 StartAPZDrag(event); // sets mScrollingWithAPZ=true if appropriate
1104 #ifdef DEBUG_SLIDER
1105 printf("Pressed mDragStart=%d\n", mDragStart);
1106 #endif
1108 if (!mScrollingWithAPZ) {
1109 SuppressDisplayport();
1112 return NS_OK;
1115 nsresult nsSliderFrame::StopDrag() {
1116 AddListener();
1117 DragThumb(false);
1119 mScrollingWithAPZ = false;
1121 UnsuppressDisplayport();
1123 #ifdef MOZ_WIDGET_GTK
1124 nsIFrame* thumbFrame = mFrames.FirstChild();
1125 if (thumbFrame) {
1126 RefPtr<Element> thumb = thumbFrame->GetContent()->AsElement();
1127 thumb->UnsetAttr(kNameSpaceID_None, nsGkAtoms::active, true);
1129 #endif
1131 if (mChange) {
1132 StopRepeat();
1133 mChange = 0;
1135 return NS_OK;
1138 void nsSliderFrame::DragThumb(bool aGrabMouseEvents) {
1139 mDragFinished = !aGrabMouseEvents;
1141 if (aGrabMouseEvents) {
1142 PresShell::SetCapturingContent(GetContent(),
1143 CaptureFlags::IgnoreAllowedState);
1144 } else {
1145 PresShell::ReleaseCapturingContent();
1149 bool nsSliderFrame::isDraggingThumb() const {
1150 return PresShell::GetCapturingContent() == GetContent();
1153 void nsSliderFrame::AddListener() {
1154 if (!mMediator) {
1155 mMediator = new nsSliderMediator(this);
1158 nsIFrame* thumbFrame = mFrames.FirstChild();
1159 if (!thumbFrame) {
1160 return;
1162 thumbFrame->GetContent()->AddSystemEventListener(
1163 NS_LITERAL_STRING("mousedown"), mMediator, false, false);
1164 thumbFrame->GetContent()->AddSystemEventListener(
1165 NS_LITERAL_STRING("touchstart"), mMediator, false, false);
1168 void nsSliderFrame::RemoveListener() {
1169 NS_ASSERTION(mMediator, "No listener was ever added!!");
1171 nsIFrame* thumbFrame = mFrames.FirstChild();
1172 if (!thumbFrame) return;
1174 thumbFrame->GetContent()->RemoveSystemEventListener(
1175 NS_LITERAL_STRING("mousedown"), mMediator, false);
1178 bool nsSliderFrame::ShouldScrollForEvent(WidgetGUIEvent* aEvent) {
1179 switch (aEvent->mMessage) {
1180 case eTouchStart:
1181 case eTouchEnd:
1182 return true;
1183 case eMouseDown:
1184 case eMouseUp: {
1185 uint16_t button = aEvent->AsMouseEvent()->mButton;
1186 #ifdef MOZ_WIDGET_GTK
1187 return (button == MouseButton::eLeft) ||
1188 (button == MouseButton::eRight && GetScrollToClick()) ||
1189 (button == MouseButton::eMiddle && gMiddlePref &&
1190 !GetScrollToClick());
1191 #else
1192 return (button == MouseButton::eLeft) ||
1193 (button == MouseButton::eMiddle && gMiddlePref);
1194 #endif
1196 default:
1197 return false;
1201 bool nsSliderFrame::ShouldScrollToClickForEvent(WidgetGUIEvent* aEvent) {
1202 if (!ShouldScrollForEvent(aEvent)) {
1203 return false;
1206 if (aEvent->mMessage != eMouseDown && aEvent->mMessage != eTouchStart) {
1207 return false;
1210 #if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
1211 // On Mac and Linux, clicking the scrollbar thumb should never scroll to
1212 // click.
1213 if (IsEventOverThumb(aEvent)) {
1214 return false;
1216 #endif
1218 if (aEvent->mMessage == eTouchStart) {
1219 return GetScrollToClick();
1222 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
1223 if (mouseEvent->mButton == MouseButton::eLeft) {
1224 #ifdef XP_MACOSX
1225 bool invertPref = mouseEvent->IsAlt();
1226 #else
1227 bool invertPref = mouseEvent->IsShift();
1228 #endif
1229 return GetScrollToClick() != invertPref;
1232 #ifdef MOZ_WIDGET_GTK
1233 if (mouseEvent->mButton == MouseButton::eRight) {
1234 return !GetScrollToClick();
1236 #endif
1238 return true;
1241 bool nsSliderFrame::IsEventOverThumb(WidgetGUIEvent* aEvent) {
1242 nsIFrame* thumbFrame = mFrames.FirstChild();
1243 if (!thumbFrame) {
1244 return false;
1247 nsPoint eventPoint;
1248 if (!GetEventPoint(aEvent, eventPoint)) {
1249 return false;
1252 nsRect thumbRect = thumbFrame->GetRect();
1253 #if defined(MOZ_WIDGET_GTK)
1254 /* Scrollbar track can have padding, so it's better to check that eventPoint
1255 * is inside of actual thumb, not just its one axis. The part of the scrollbar
1256 * track adjacent to thumb can actually receive events in GTK3 */
1257 return eventPoint.x >= thumbRect.x && eventPoint.x < thumbRect.XMost() &&
1258 eventPoint.y >= thumbRect.y && eventPoint.y < thumbRect.YMost();
1259 #else
1260 bool isHorizontal = IsXULHorizontal();
1261 nscoord eventPos = isHorizontal ? eventPoint.x : eventPoint.y;
1262 nscoord thumbStart = isHorizontal ? thumbRect.x : thumbRect.y;
1263 nscoord thumbEnd = isHorizontal ? thumbRect.XMost() : thumbRect.YMost();
1265 return eventPos >= thumbStart && eventPos < thumbEnd;
1266 #endif
1269 NS_IMETHODIMP
1270 nsSliderFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
1271 nsEventStatus* aEventStatus) {
1272 if (!ShouldScrollForEvent(aEvent) || ShouldScrollToClickForEvent(aEvent)) {
1273 return NS_OK;
1276 if (IsEventOverThumb(aEvent)) {
1277 return NS_OK;
1280 nsIFrame* thumbFrame = mFrames.FirstChild();
1281 if (!thumbFrame) // display:none?
1282 return NS_OK;
1284 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
1285 nsGkAtoms::_true, eCaseMatters))
1286 return NS_OK;
1288 nsRect thumbRect = thumbFrame->GetRect();
1290 nscoord change = 1;
1291 nsPoint eventPoint;
1292 if (!GetEventPoint(aEvent, eventPoint)) {
1293 return NS_OK;
1296 if (IsXULHorizontal() ? eventPoint.x < thumbRect.x
1297 : eventPoint.y < thumbRect.y)
1298 change = -1;
1300 mChange = change;
1301 DragThumb(true);
1302 // On Linux we want to keep scrolling in the direction indicated by |change|
1303 // until the mouse is released. On the other platforms we want to stop
1304 // scrolling as soon as the scrollbar thumb has reached the current mouse
1305 // position.
1306 #ifdef MOZ_WIDGET_GTK
1307 nsRect clientRect;
1308 GetXULClientRect(clientRect);
1310 // Set the destination point to the very end of the scrollbar so that
1311 // scrolling doesn't stop halfway through.
1312 if (change > 0) {
1313 mDestinationPoint = nsPoint(clientRect.width, clientRect.height);
1314 } else {
1315 mDestinationPoint = nsPoint(0, 0);
1317 #else
1318 mDestinationPoint = eventPoint;
1319 #endif
1320 StartRepeat();
1321 PageScroll(change);
1323 return NS_OK;
1326 NS_IMETHODIMP
1327 nsSliderFrame::HandleRelease(nsPresContext* aPresContext,
1328 WidgetGUIEvent* aEvent,
1329 nsEventStatus* aEventStatus) {
1330 StopRepeat();
1332 nsIFrame* scrollbar = GetScrollbar();
1333 nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
1334 if (sb) {
1335 nsIScrollbarMediator* m = sb->GetScrollbarMediator();
1336 if (m) {
1337 m->ScrollbarReleased(sb);
1340 return NS_OK;
1343 void nsSliderFrame::DestroyFrom(nsIFrame* aDestructRoot,
1344 PostDestroyData& aPostDestroyData) {
1345 // tell our mediator if we have one we are gone.
1346 if (mMediator) {
1347 mMediator->SetSlider(nullptr);
1348 mMediator = nullptr;
1350 StopRepeat();
1352 // call base class Destroy()
1353 nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
1356 nsSize nsSliderFrame::GetXULPrefSize(nsBoxLayoutState& aState) {
1357 EnsureOrient();
1358 return nsBoxFrame::GetXULPrefSize(aState);
1361 nsSize nsSliderFrame::GetXULMinSize(nsBoxLayoutState& aState) {
1362 EnsureOrient();
1364 // our min size is just our borders and padding
1365 return nsBox::GetXULMinSize(aState);
1368 nsSize nsSliderFrame::GetXULMaxSize(nsBoxLayoutState& aState) {
1369 EnsureOrient();
1370 return nsBoxFrame::GetXULMaxSize(aState);
1373 void nsSliderFrame::EnsureOrient() {
1374 nsIFrame* scrollbarBox = GetScrollbar();
1376 bool isHorizontal =
1377 (scrollbarBox->GetStateBits() & NS_STATE_IS_HORIZONTAL) != 0;
1378 if (isHorizontal)
1379 AddStateBits(NS_STATE_IS_HORIZONTAL);
1380 else
1381 RemoveStateBits(NS_STATE_IS_HORIZONTAL);
1384 void nsSliderFrame::Notify(void) {
1385 bool stop = false;
1387 nsIFrame* thumbFrame = mFrames.FirstChild();
1388 if (!thumbFrame) {
1389 StopRepeat();
1390 return;
1392 nsRect thumbRect = thumbFrame->GetRect();
1394 bool isHorizontal = IsXULHorizontal();
1396 // See if the thumb has moved past our destination point.
1397 // if it has we want to stop.
1398 if (isHorizontal) {
1399 if (mChange < 0) {
1400 if (thumbRect.x < mDestinationPoint.x) stop = true;
1401 } else {
1402 if (thumbRect.x + thumbRect.width > mDestinationPoint.x) stop = true;
1404 } else {
1405 if (mChange < 0) {
1406 if (thumbRect.y < mDestinationPoint.y) stop = true;
1407 } else {
1408 if (thumbRect.y + thumbRect.height > mDestinationPoint.y) stop = true;
1412 if (stop) {
1413 StopRepeat();
1414 } else {
1415 PageScroll(mChange);
1419 void nsSliderFrame::PageScroll(nscoord aChange) {
1420 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
1421 nsGkAtoms::reverse, eCaseMatters)) {
1422 aChange = -aChange;
1424 nsIFrame* scrollbar = GetScrollbar();
1425 nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
1426 if (sb) {
1427 nsIScrollbarMediator* m = sb->GetScrollbarMediator();
1428 sb->SetIncrementToPage(aChange);
1429 if (m) {
1430 m->ScrollByPage(sb, aChange, nsIScrollbarMediator::ENABLE_SNAP);
1431 return;
1434 PageUpDown(aChange);
1437 float nsSliderFrame::GetThumbRatio() const {
1438 // mRatio is in thumb app units per scrolled css pixels. Convert it to a
1439 // ratio of the thumb's CSS pixels per scrolled CSS pixels. (Note the thumb
1440 // is in the scrollframe's parent's space whereas the scrolled CSS pixels
1441 // are in the scrollframe's space).
1442 return mRatio / mozilla::AppUnitsPerCSSPixel();
1445 void nsSliderFrame::AsyncScrollbarDragInitiated(uint64_t aDragBlockId) {
1446 mAPZDragInitiated = Some(aDragBlockId);
1449 void nsSliderFrame::AsyncScrollbarDragRejected() {
1450 mScrollingWithAPZ = false;
1451 // Only suppress the displayport if we're still dragging the thumb.
1452 // Otherwise, no one will unsuppress it.
1453 if (isDraggingThumb()) {
1454 SuppressDisplayport();
1458 void nsSliderFrame::SuppressDisplayport() {
1459 if (!mSuppressionActive) {
1460 PresShell()->SuppressDisplayport(true);
1461 mSuppressionActive = true;
1465 void nsSliderFrame::UnsuppressDisplayport() {
1466 if (mSuppressionActive) {
1467 PresShell()->SuppressDisplayport(false);
1468 mSuppressionActive = false;
1472 bool nsSliderFrame::OnlySystemGroupDispatch(EventMessage aMessage) const {
1473 // If we are in a native anonymous subtree, do not dispatch mouse-move events
1474 // targeted at this slider frame to web content. This matches the behaviour
1475 // of other browsers.
1476 return aMessage == eMouseMove && isDraggingThumb() &&
1477 GetContent()->IsInNativeAnonymousSubtree();
1480 NS_IMPL_ISUPPORTS(nsSliderMediator, nsIDOMEventListener)