1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 //
7 // Eric Vaughan
8 // Netscape Communications
9 //
10 // See documentation in associated header file
11 //
12
13 #include "nsSliderFrame.h"
14
15 #include "gfxPrefs.h"
16 #include "nsStyleContext.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 "nsIPresShell.h"
24 #include "nsCSSRendering.h"
25 #include "nsIDOMEvent.h"
26 #include "nsIDOMMouseEvent.h"
27 #include "nsScrollbarButtonFrame.h"
28 #include "nsISliderListener.h"
29 #include "nsIScrollableFrame.h"
30 #include "nsIScrollbarMediator.h"
31 #include "nsISupportsImpl.h"
32 #include "nsScrollbarFrame.h"
33 #include "nsRepeatService.h"
34 #include "nsBoxLayoutState.h"
35 #include "nsSprocketLayout.h"
36 #include "nsIServiceManager.h"
37 #include "nsContentUtils.h"
38 #include "nsLayoutUtils.h"
39 #include "nsDisplayList.h"
40 #include "mozilla/Assertions.h" // for MOZ_ASSERT
41 #include "mozilla/Preferences.h"
42 #include "mozilla/LookAndFeel.h"
43 #include "mozilla/MouseEvents.h"
44 #include "mozilla/Telemetry.h"
45 #include "mozilla/layers/APZCCallbackHelper.h"
46 #include "mozilla/layers/AsyncDragMetrics.h"
47 #include "mozilla/layers/InputAPZContext.h"
48 #include "mozilla/layers/ScrollInputMethods.h"
49 #include <algorithm>
50
51 using namespace mozilla;
52 using mozilla::layers::APZCCallbackHelper;
53 using mozilla::layers::AsyncDragMetrics;
54 using mozilla::layers::InputAPZContext;
55 using mozilla::layers::ScrollInputMethod;
56
57 bool nsSliderFrame::gMiddlePref = false;
58 int32_t nsSliderFrame::gSnapMultiplier;
59
60 // Turn this on if you want to debug slider frames.
61 #undef DEBUG_SLIDER
62
63 static already_AddRefed<nsIContent>
GetContentOfBox(nsIFrame * aBox)64 GetContentOfBox(nsIFrame *aBox)
65 {
66 nsCOMPtr<nsIContent> content = aBox->GetContent();
67 return content.forget();
68 }
69
70 nsIFrame*
NS_NewSliderFrame(nsIPresShell * aPresShell,nsStyleContext * aContext)71 NS_NewSliderFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
72 {
73 return new (aPresShell) nsSliderFrame(aContext);
74 }
75
76 NS_IMPL_FRAMEARENA_HELPERS(nsSliderFrame)
77
NS_QUERYFRAME_HEAD(nsSliderFrame)78 NS_QUERYFRAME_HEAD(nsSliderFrame)
79 NS_QUERYFRAME_ENTRY(nsSliderFrame)
80 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
81
82 nsSliderFrame::nsSliderFrame(nsStyleContext* aContext):
83 nsBoxFrame(aContext),
84 mCurPos(0),
85 mChange(0),
86 mDragFinished(true),
87 mUserChanged(false),
88 mScrollingWithAPZ(false),
89 mSuppressionActive(false)
90 {
91 }
92
93 // stop timer
~nsSliderFrame()94 nsSliderFrame::~nsSliderFrame()
95 {
96 if (mSuppressionActive) {
97 APZCCallbackHelper::SuppressDisplayport(false, PresContext() ?
98 PresContext()->PresShell() :
99 nullptr);
100 }
101 }
102
103 void
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)104 nsSliderFrame::Init(nsIContent* aContent,
105 nsContainerFrame* aParent,
106 nsIFrame* aPrevInFlow)
107 {
108 nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
109
110 static bool gotPrefs = false;
111 if (!gotPrefs) {
112 gotPrefs = true;
113
114 gMiddlePref = Preferences::GetBool("middlemouse.scrollbarPosition");
115 gSnapMultiplier = Preferences::GetInt("slider.snapMultiplier");
116 }
117
118 mCurPos = GetCurrentPosition(aContent);
119 }
120
121 void
RemoveFrame(ChildListID aListID,nsIFrame * aOldFrame)122 nsSliderFrame::RemoveFrame(ChildListID aListID,
123 nsIFrame* aOldFrame)
124 {
125 nsBoxFrame::RemoveFrame(aListID, aOldFrame);
126 if (mFrames.IsEmpty())
127 RemoveListener();
128 }
129
130 void
InsertFrames(ChildListID aListID,nsIFrame * aPrevFrame,nsFrameList & aFrameList)131 nsSliderFrame::InsertFrames(ChildListID aListID,
132 nsIFrame* aPrevFrame,
133 nsFrameList& aFrameList)
134 {
135 bool wasEmpty = mFrames.IsEmpty();
136 nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
137 if (wasEmpty)
138 AddListener();
139 }
140
141 void
AppendFrames(ChildListID aListID,nsFrameList & aFrameList)142 nsSliderFrame::AppendFrames(ChildListID aListID,
143 nsFrameList& aFrameList)
144 {
145 // if we have no children and on was added then make sure we add the
146 // listener
147 bool wasEmpty = mFrames.IsEmpty();
148 nsBoxFrame::AppendFrames(aListID, aFrameList);
149 if (wasEmpty)
150 AddListener();
151 }
152
153 int32_t
GetCurrentPosition(nsIContent * content)154 nsSliderFrame::GetCurrentPosition(nsIContent* content)
155 {
156 return GetIntegerAttribute(content, nsGkAtoms::curpos, 0);
157 }
158
159 int32_t
GetMinPosition(nsIContent * content)160 nsSliderFrame::GetMinPosition(nsIContent* content)
161 {
162 return GetIntegerAttribute(content, nsGkAtoms::minpos, 0);
163 }
164
165 int32_t
GetMaxPosition(nsIContent * content)166 nsSliderFrame::GetMaxPosition(nsIContent* content)
167 {
168 return GetIntegerAttribute(content, nsGkAtoms::maxpos, 100);
169 }
170
171 int32_t
GetIncrement(nsIContent * content)172 nsSliderFrame::GetIncrement(nsIContent* content)
173 {
174 return GetIntegerAttribute(content, nsGkAtoms::increment, 1);
175 }
176
177
178 int32_t
GetPageIncrement(nsIContent * content)179 nsSliderFrame::GetPageIncrement(nsIContent* content)
180 {
181 return GetIntegerAttribute(content, nsGkAtoms::pageincrement, 10);
182 }
183
184 int32_t
GetIntegerAttribute(nsIContent * content,nsIAtom * atom,int32_t defaultValue)185 nsSliderFrame::GetIntegerAttribute(nsIContent* content, nsIAtom* atom, int32_t defaultValue)
186 {
187 nsAutoString value;
188 content->GetAttr(kNameSpaceID_None, atom, value);
189 if (!value.IsEmpty()) {
190 nsresult error;
191
192 // convert it to an integer
193 defaultValue = value.ToInteger(&error);
194 }
195
196 return defaultValue;
197 }
198
199 class nsValueChangedRunnable : public Runnable
200 {
201 public:
nsValueChangedRunnable(nsISliderListener * aListener,nsIAtom * aWhich,int32_t aValue,bool aUserChanged)202 nsValueChangedRunnable(nsISliderListener* aListener,
203 nsIAtom* aWhich,
204 int32_t aValue,
205 bool aUserChanged)
206 : mListener(aListener), mWhich(aWhich),
207 mValue(aValue), mUserChanged(aUserChanged)
208 {}
209
Run()210 NS_IMETHOD Run() override
211 {
212 return mListener->ValueChanged(nsDependentAtomString(mWhich),
213 mValue, mUserChanged);
214 }
215
216 nsCOMPtr<nsISliderListener> mListener;
217 nsCOMPtr<nsIAtom> mWhich;
218 int32_t mValue;
219 bool mUserChanged;
220 };
221
222 class nsDragStateChangedRunnable : public Runnable
223 {
224 public:
nsDragStateChangedRunnable(nsISliderListener * aListener,bool aDragBeginning)225 nsDragStateChangedRunnable(nsISliderListener* aListener,
226 bool aDragBeginning)
227 : mListener(aListener),
228 mDragBeginning(aDragBeginning)
229 {}
230
Run()231 NS_IMETHOD Run() override
232 {
233 return mListener->DragStateChanged(mDragBeginning);
234 }
235
236 nsCOMPtr<nsISliderListener> mListener;
237 bool mDragBeginning;
238 };
239
240 nsresult
AttributeChanged(int32_t aNameSpaceID,nsIAtom * aAttribute,int32_t aModType)241 nsSliderFrame::AttributeChanged(int32_t aNameSpaceID,
242 nsIAtom* aAttribute,
243 int32_t aModType)
244 {
245 nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
246 aModType);
247 // if the current position changes
248 if (aAttribute == nsGkAtoms::curpos) {
249 CurrentPositionChanged();
250 } else if (aAttribute == nsGkAtoms::minpos ||
251 aAttribute == nsGkAtoms::maxpos) {
252 // bounds check it.
253
254 nsIFrame* scrollbarBox = GetScrollbar();
255 nsCOMPtr<nsIContent> scrollbar;
256 scrollbar = GetContentOfBox(scrollbarBox);
257 int32_t current = GetCurrentPosition(scrollbar);
258 int32_t min = GetMinPosition(scrollbar);
259 int32_t max = GetMaxPosition(scrollbar);
260
261 // inform the parent <scale> that the minimum or maximum changed
262 nsIFrame* parent = GetParent();
263 if (parent) {
264 nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
265 if (sliderListener) {
266 nsContentUtils::AddScriptRunner(
267 new nsValueChangedRunnable(sliderListener, aAttribute,
268 aAttribute == nsGkAtoms::minpos ? min : max, false));
269 }
270 }
271
272 if (current < min || current > max)
273 {
274 int32_t direction = 0;
275 if (current < min || max < min) {
276 current = min;
277 direction = -1;
278 } else if (current > max) {
279 current = max;
280 direction = 1;
281 }
282
283 // set the new position and notify observers
284 nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
285 if (scrollbarFrame) {
286 nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
287 scrollbarFrame->SetIncrementToWhole(direction);
288 if (mediator) {
289 mediator->ScrollByWhole(scrollbarFrame, direction,
290 nsIScrollbarMediator::ENABLE_SNAP);
291 }
292 }
293 // 'this' might be destroyed here
294
295 nsContentUtils::AddScriptRunner(
296 new nsSetAttrRunnable(scrollbar, nsGkAtoms::curpos, current));
297 }
298 }
299
300 if (aAttribute == nsGkAtoms::minpos ||
301 aAttribute == nsGkAtoms::maxpos ||
302 aAttribute == nsGkAtoms::pageincrement ||
303 aAttribute == nsGkAtoms::increment) {
304
305 PresContext()->PresShell()->
306 FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
307 }
308
309 return rv;
310 }
311
312 void
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsRect & aDirtyRect,const nsDisplayListSet & aLists)313 nsSliderFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
314 const nsRect& aDirtyRect,
315 const nsDisplayListSet& aLists)
316 {
317 if (aBuilder->IsForEventDelivery() && isDraggingThumb()) {
318 // This is EVIL, we shouldn't be messing with event delivery just to get
319 // thumb mouse drag events to arrive at the slider!
320 aLists.Outlines()->AppendNewToTop(new (aBuilder)
321 nsDisplayEventReceiver(aBuilder, this));
322 return;
323 }
324
325 nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
326 }
327
328 void
BuildDisplayListForChildren(nsDisplayListBuilder * aBuilder,const nsRect & aDirtyRect,const nsDisplayListSet & aLists)329 nsSliderFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
330 const nsRect& aDirtyRect,
331 const nsDisplayListSet& aLists)
332 {
333 // if we are too small to have a thumb don't paint it.
334 nsIFrame* thumb = nsBox::GetChildXULBox(this);
335
336 if (thumb) {
337 nsRect thumbRect(thumb->GetRect());
338 nsMargin m;
339 thumb->GetXULMargin(m);
340 thumbRect.Inflate(m);
341
342 nsRect crect;
343 GetXULClientRect(crect);
344
345 if (crect.width < thumbRect.width || crect.height < thumbRect.height)
346 return;
347
348 // If this scrollbar is the scrollbar of an actively scrolled scroll frame,
349 // layerize the scrollbar thumb, wrap it in its own ContainerLayer and
350 // attach scrolling information to it.
351 // We do this here and not in the thumb's nsBoxFrame::BuildDisplayList so
352 // that the event region that gets created for the thumb is included in
353 // the nsDisplayOwnLayer contents.
354
355 uint32_t flags = 0;
356 mozilla::layers::FrameMetrics::ViewID scrollTargetId =
357 mozilla::layers::FrameMetrics::NULL_SCROLL_ID;
358 aBuilder->GetScrollbarInfo(&scrollTargetId, &flags);
359 bool thumbGetsLayer = (scrollTargetId != layers::FrameMetrics::NULL_SCROLL_ID);
360 nsLayoutUtils::SetScrollbarThumbLayerization(thumb, thumbGetsLayer);
361
362 if (thumbGetsLayer) {
363 nsDisplayListCollection tempLists;
364 nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, tempLists);
365
366 // This is a bit of a hack. Collect up all descendant display items
367 // and merge them into a single Content() list.
368 nsDisplayList masterList;
369 masterList.AppendToTop(tempLists.BorderBackground());
370 masterList.AppendToTop(tempLists.BlockBorderBackgrounds());
371 masterList.AppendToTop(tempLists.Floats());
372 masterList.AppendToTop(tempLists.Content());
373 masterList.AppendToTop(tempLists.PositionedDescendants());
374 masterList.AppendToTop(tempLists.Outlines());
375
376 // Wrap the list to make it its own layer.
377 aLists.Content()->AppendNewToTop(new (aBuilder)
378 nsDisplayOwnLayer(aBuilder, this, &masterList, flags, scrollTargetId,
379 GetThumbRatio()));
380
381 return;
382 }
383 }
384
385 nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
386 }
387
388 NS_IMETHODIMP
DoXULLayout(nsBoxLayoutState & aState)389 nsSliderFrame::DoXULLayout(nsBoxLayoutState& aState)
390 {
391 // get the thumb should be our only child
392 nsIFrame* thumbBox = nsBox::GetChildXULBox(this);
393
394 if (!thumbBox) {
395 SyncLayout(aState);
396 return NS_OK;
397 }
398
399 EnsureOrient();
400
401 #ifdef DEBUG_LAYOUT
402 if (mState & NS_STATE_DEBUG_WAS_SET) {
403 if (mState & NS_STATE_SET_TO_DEBUG)
404 SetXULDebug(aState, true);
405 else
406 SetXULDebug(aState, false);
407 }
408 #endif
409
410 // get the content area inside our borders
411 nsRect clientRect;
412 GetXULClientRect(clientRect);
413
414 // get the scrollbar
415 nsIFrame* scrollbarBox = GetScrollbar();
416 nsCOMPtr<nsIContent> scrollbar;
417 scrollbar = GetContentOfBox(scrollbarBox);
418
419 // get the thumb's pref size
420 nsSize thumbSize = thumbBox->GetXULPrefSize(aState);
421
422 if (IsXULHorizontal())
423 thumbSize.height = clientRect.height;
424 else
425 thumbSize.width = clientRect.width;
426
427 int32_t curPos = GetCurrentPosition(scrollbar);
428 int32_t minPos = GetMinPosition(scrollbar);
429 int32_t maxPos = GetMaxPosition(scrollbar);
430 int32_t pageIncrement = GetPageIncrement(scrollbar);
431
432 maxPos = std::max(minPos, maxPos);
433 curPos = clamped(curPos, minPos, maxPos);
434
435 nscoord& availableLength = IsXULHorizontal() ? clientRect.width : clientRect.height;
436 nscoord& thumbLength = IsXULHorizontal() ? thumbSize.width : thumbSize.height;
437
438 if ((pageIncrement + maxPos - minPos) > 0 && thumbBox->GetXULFlex() > 0) {
439 float ratio = float(pageIncrement) / float(maxPos - minPos + pageIncrement);
440 thumbLength = std::max(thumbLength, NSToCoordRound(availableLength * ratio));
441 }
442
443 // Round the thumb's length to device pixels.
444 nsPresContext* presContext = PresContext();
445 thumbLength = presContext->DevPixelsToAppUnits(
446 presContext->AppUnitsToDevPixels(thumbLength));
447
448 // mRatio translates the thumb position in app units to the value.
449 mRatio = (minPos != maxPos) ? float(availableLength - thumbLength) / float(maxPos - minPos) : 1;
450
451 // in reverse mode, curpos is reversed such that lower values are to the
452 // right or bottom and increase leftwards or upwards. In this case, use the
453 // offset from the end instead of the beginning.
454 bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
455 nsGkAtoms::reverse, eCaseMatters);
456 nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos);
457
458 // set the thumb's coord to be the current pos * the ratio.
459 nsRect thumbRect(clientRect.x, clientRect.y, thumbSize.width, thumbSize.height);
460 int32_t& thumbPos = (IsXULHorizontal() ? thumbRect.x : thumbRect.y);
461 thumbPos += NSToCoordRound(pos * mRatio);
462
463 nsRect oldThumbRect(thumbBox->GetRect());
464 LayoutChildAt(aState, thumbBox, thumbRect);
465
466 SyncLayout(aState);
467
468 // Redraw only if thumb changed size.
469 if (!oldThumbRect.IsEqualInterior(thumbRect))
470 XULRedraw(aState);
471
472 return NS_OK;
473 }
474
475
476 nsresult
HandleEvent(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)477 nsSliderFrame::HandleEvent(nsPresContext* aPresContext,
478 WidgetGUIEvent* aEvent,
479 nsEventStatus* aEventStatus)
480 {
481 NS_ENSURE_ARG_POINTER(aEventStatus);
482
483 // If a web page calls event.preventDefault() we still want to
484 // scroll when scroll arrow is clicked. See bug 511075.
485 if (!mContent->IsInNativeAnonymousSubtree() &&
486 nsEventStatus_eConsumeNoDefault == *aEventStatus) {
487 return NS_OK;
488 }
489
490 if (!mDragFinished && !isDraggingThumb()) {
491 StopDrag();
492 return NS_OK;
493 }
494
495 nsIFrame* scrollbarBox = GetScrollbar();
496 nsCOMPtr<nsIContent> scrollbar;
497 scrollbar = GetContentOfBox(scrollbarBox);
498 bool isHorizontal = IsXULHorizontal();
499
500 if (isDraggingThumb())
501 {
502 switch (aEvent->mMessage) {
503 case eTouchMove:
504 case eMouseMove: {
505 if (mScrollingWithAPZ) {
506 break;
507 }
508 nsPoint eventPoint;
509 if (!GetEventPoint(aEvent, eventPoint)) {
510 break;
511 }
512 if (mChange) {
513 // On Linux the destination point is determined by the initial click
514 // on the scrollbar track and doesn't change until the mouse button
515 // is released.
516 #ifndef MOZ_WIDGET_GTK
517 // On the other platforms we need to update the destination point now.
518 mDestinationPoint = eventPoint;
519 StopRepeat();
520 StartRepeat();
521 #endif
522 break;
523 }
524
525 nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y;
526
527 nsIFrame* thumbFrame = mFrames.FirstChild();
528 if (!thumbFrame) {
529 return NS_OK;
530 }
531
532 mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
533 (uint32_t) ScrollInputMethod::MainThreadScrollbarDrag);
534
535 // take our current position and subtract the start location
536 pos -= mDragStart;
537 bool isMouseOutsideThumb = false;
538 if (gSnapMultiplier) {
539 nsSize thumbSize = thumbFrame->GetSize();
540 if (isHorizontal) {
541 // horizontal scrollbar - check if mouse is above or below thumb
542 // XXXbz what about looking at the .y of the thumb's rect? Is that
543 // always zero here?
544 if (eventPoint.y < -gSnapMultiplier * thumbSize.height ||
545 eventPoint.y > thumbSize.height +
546 gSnapMultiplier * thumbSize.height)
547 isMouseOutsideThumb = true;
548 }
549 else {
550 // vertical scrollbar - check if mouse is left or right of thumb
551 if (eventPoint.x < -gSnapMultiplier * thumbSize.width ||
552 eventPoint.x > thumbSize.width +
553 gSnapMultiplier * thumbSize.width)
554 isMouseOutsideThumb = true;
555 }
556 }
557 if (aEvent->mClass == eTouchEventClass) {
558 *aEventStatus = nsEventStatus_eConsumeNoDefault;
559 }
560 if (isMouseOutsideThumb)
561 {
562 SetCurrentThumbPosition(scrollbar, mThumbStart, false, false);
563 return NS_OK;
564 }
565
566 // set it
567 SetCurrentThumbPosition(scrollbar, pos, false, true); // with snapping
568 }
569 break;
570
571 case eTouchEnd:
572 case eMouseUp:
573 if (ShouldScrollForEvent(aEvent)) {
574 StopDrag();
575 //we MUST call nsFrame HandleEvent for mouse ups to maintain the selection state and capture state.
576 return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
577 }
578 break;
579
580 default:
581 break;
582 }
583
584 //return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
585 return NS_OK;
586 } else if (ShouldScrollToClickForEvent(aEvent)) {
587 nsPoint eventPoint;
588 if (!GetEventPoint(aEvent, eventPoint)) {
589 return NS_OK;
590 }
591 nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y;
592
593 // adjust so that the middle of the thumb is placed under the click
594 nsIFrame* thumbFrame = mFrames.FirstChild();
595 if (!thumbFrame) {
596 return NS_OK;
597 }
598 nsSize thumbSize = thumbFrame->GetSize();
599 nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height;
600
601 mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
602 (uint32_t) ScrollInputMethod::MainThreadScrollbarTrackClick);
603
604 // set it
605 nsWeakFrame weakFrame(this);
606 // should aMaySnap be true here?
607 SetCurrentThumbPosition(scrollbar, pos - thumbLength/2, false, false);
608 NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
609
610 DragThumb(true);
611
612 #ifdef MOZ_WIDGET_GTK
613 nsCOMPtr<nsIContent> thumb = thumbFrame->GetContent();
614 thumb->SetAttr(kNameSpaceID_None, nsGkAtoms::active, NS_LITERAL_STRING("true"), true);
615 #endif
616
617 if (aEvent->mClass == eTouchEventClass) {
618 *aEventStatus = nsEventStatus_eConsumeNoDefault;
619 }
620
621 if (isHorizontal)
622 mThumbStart = thumbFrame->GetPosition().x;
623 else
624 mThumbStart = thumbFrame->GetPosition().y;
625
626 mDragStart = pos - mThumbStart;
627 }
628 #ifdef MOZ_WIDGET_GTK
629 else if (ShouldScrollForEvent(aEvent) &&
630 aEvent->mClass == eMouseEventClass &&
631 aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) {
632 // HandlePress and HandleRelease are usually called via
633 // nsFrame::HandleEvent, but only for the left mouse button.
634 if (aEvent->mMessage == eMouseDown) {
635 HandlePress(aPresContext, aEvent, aEventStatus);
636 } else if (aEvent->mMessage == eMouseUp) {
637 HandleRelease(aPresContext, aEvent, aEventStatus);
638 }
639
640 return NS_OK;
641 }
642 #endif
643
644 // XXX hack until handle release is actually called in nsframe.
645 // if (aEvent->mMessage == eMouseOut ||
646 // aEvent->mMessage == NS_MOUSE_RIGHT_BUTTON_UP ||
647 // aEvent->mMessage == NS_MOUSE_LEFT_BUTTON_UP) {
648 // HandleRelease(aPresContext, aEvent, aEventStatus);
649 // }
650
651 if (aEvent->mMessage == eMouseOut && mChange)
652 HandleRelease(aPresContext, aEvent, aEventStatus);
653
654 return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
655 }
656
657 // Helper function to collect the "scroll to click" metric. Beware of
658 // caching this, users expect to be able to change the system preference
659 // and see the browser change its behavior immediately.
660 bool
GetScrollToClick()661 nsSliderFrame::GetScrollToClick()
662 {
663 if (GetScrollbar() != this) {
664 return LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollToClick, false);
665 }
666
667 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick,
668 nsGkAtoms::_true, eCaseMatters)) {
669 return true;
670 }
671 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick,
672 nsGkAtoms::_false, eCaseMatters)) {
673 return false;
674 }
675
676 #ifdef XP_MACOSX
677 return true;
678 #else
679 return false;
680 #endif
681 }
682
683 nsIFrame*
GetScrollbar()684 nsSliderFrame::GetScrollbar()
685 {
686 // if we are in a scrollbar then return the scrollbar's content node
687 // if we are not then return ours.
688 nsIFrame* scrollbar;
689 nsScrollbarButtonFrame::GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
690
691 if (scrollbar == nullptr)
692 return this;
693
694 return scrollbar->IsXULBoxFrame() ? scrollbar : this;
695 }
696
697 void
PageUpDown(nscoord change)698 nsSliderFrame::PageUpDown(nscoord change)
699 {
700 // on a page up or down get our page increment. We get this by getting the scrollbar we are in and
701 // asking it for the current position and the page increment. If we are not in a scrollbar we will
702 // get the values from our own node.
703 nsIFrame* scrollbarBox = GetScrollbar();
704 nsCOMPtr<nsIContent> scrollbar;
705 scrollbar = GetContentOfBox(scrollbarBox);
706
707 nscoord pageIncrement = GetPageIncrement(scrollbar);
708 int32_t curpos = GetCurrentPosition(scrollbar);
709 int32_t minpos = GetMinPosition(scrollbar);
710 int32_t maxpos = GetMaxPosition(scrollbar);
711
712 // get the new position and make sure it is in bounds
713 int32_t newpos = curpos + change * pageIncrement;
714 if (newpos < minpos || maxpos < minpos)
715 newpos = minpos;
716 else if (newpos > maxpos)
717 newpos = maxpos;
718
719 SetCurrentPositionInternal(scrollbar, newpos, true);
720 }
721
722 // called when the current position changed and we need to update the thumb's location
723 void
CurrentPositionChanged()724 nsSliderFrame::CurrentPositionChanged()
725 {
726 nsIFrame* scrollbarBox = GetScrollbar();
727 nsCOMPtr<nsIContent> scrollbar;
728 scrollbar = GetContentOfBox(scrollbarBox);
729
730 // get the current position
731 int32_t curPos = GetCurrentPosition(scrollbar);
732
733 // do nothing if the position did not change
734 if (mCurPos == curPos)
735 return;
736
737 // get our current min and max position from our content node
738 int32_t minPos = GetMinPosition(scrollbar);
739 int32_t maxPos = GetMaxPosition(scrollbar);
740
741 maxPos = std::max(minPos, maxPos);
742 curPos = clamped(curPos, minPos, maxPos);
743
744 // get the thumb's rect
745 nsIFrame* thumbFrame = mFrames.FirstChild();
746 if (!thumbFrame)
747 return; // The thumb may stream in asynchronously via XBL.
748
749 nsRect thumbRect = thumbFrame->GetRect();
750
751 nsRect clientRect;
752 GetXULClientRect(clientRect);
753
754 // figure out the new rect
755 nsRect newThumbRect(thumbRect);
756
757 bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
758 nsGkAtoms::reverse, eCaseMatters);
759 nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos);
760
761 if (IsXULHorizontal())
762 newThumbRect.x = clientRect.x + NSToCoordRound(pos * mRatio);
763 else
764 newThumbRect.y = clientRect.y + NSToCoordRound(pos * mRatio);
765
766 // avoid putting the scroll thumb at subpixel positions which cause needless invalidations
767 nscoord appUnitsPerPixel = PresContext()->AppUnitsPerDevPixel();
768 nsPoint snappedThumbLocation = ToAppUnits(
769 newThumbRect.TopLeft().ToNearestPixels(appUnitsPerPixel),
770 appUnitsPerPixel);
771 if (IsXULHorizontal()) {
772 newThumbRect.x = snappedThumbLocation.x;
773 } else {
774 newThumbRect.y = snappedThumbLocation.y;
775 }
776
777 // set the rect
778 thumbFrame->SetRect(newThumbRect);
779
780 // Request a repaint of the scrollbar
781 nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
782 nsIScrollbarMediator* mediator = scrollbarFrame
783 ? scrollbarFrame->GetScrollbarMediator() : nullptr;
784 if (!mediator || !mediator->ShouldSuppressScrollbarRepaints()) {
785 SchedulePaint();
786 }
787
788 mCurPos = curPos;
789
790 // inform the parent <scale> if it exists that the value changed
791 nsIFrame* parent = GetParent();
792 if (parent) {
793 nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
794 if (sliderListener) {
795 nsContentUtils::AddScriptRunner(
796 new nsValueChangedRunnable(sliderListener, nsGkAtoms::curpos, mCurPos, mUserChanged));
797 }
798 }
799 }
800
UpdateAttribute(nsIContent * aScrollbar,nscoord aNewPos,bool aNotify,bool aIsSmooth)801 static void UpdateAttribute(nsIContent* aScrollbar, nscoord aNewPos, bool aNotify, bool aIsSmooth) {
802 nsAutoString str;
803 str.AppendInt(aNewPos);
804
805 if (aIsSmooth) {
806 aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, NS_LITERAL_STRING("true"), false);
807 }
808 aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, str, aNotify);
809 if (aIsSmooth) {
810 aScrollbar->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false);
811 }
812 }
813
814 // Use this function when you want to set the scroll position via the position
815 // of the scrollbar thumb, e.g. when dragging the slider. This function scrolls
816 // the content in such a way that thumbRect.x/.y becomes aNewThumbPos.
817 void
SetCurrentThumbPosition(nsIContent * aScrollbar,nscoord aNewThumbPos,bool aIsSmooth,bool aMaySnap)818 nsSliderFrame::SetCurrentThumbPosition(nsIContent* aScrollbar, nscoord aNewThumbPos,
819 bool aIsSmooth, bool aMaySnap)
820 {
821 nsRect crect;
822 GetXULClientRect(crect);
823 nscoord offset = IsXULHorizontal() ? crect.x : crect.y;
824 int32_t newPos = NSToIntRound((aNewThumbPos - offset) / mRatio);
825
826 if (aMaySnap && mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::snap,
827 nsGkAtoms::_true, eCaseMatters)) {
828 // If snap="true", then the slider may only be set to min + (increment * x).
829 // Otherwise, the slider may be set to any positive integer.
830 int32_t increment = GetIncrement(aScrollbar);
831 newPos = NSToIntRound(newPos / float(increment)) * increment;
832 }
833
834 SetCurrentPosition(aScrollbar, newPos, aIsSmooth);
835 }
836
837 // Use this function when you know the target scroll position of the scrolled content.
838 // aNewPos should be passed to this function as a position as if the minpos is 0.
839 // That is, the minpos will be added to the position by this function. In a reverse
840 // direction slider, the newpos should be the distance from the end.
841 void
SetCurrentPosition(nsIContent * aScrollbar,int32_t aNewPos,bool aIsSmooth)842 nsSliderFrame::SetCurrentPosition(nsIContent* aScrollbar, int32_t aNewPos,
843 bool aIsSmooth)
844 {
845 // get min and max position from our content node
846 int32_t minpos = GetMinPosition(aScrollbar);
847 int32_t maxpos = GetMaxPosition(aScrollbar);
848
849 // in reverse direction sliders, flip the value so that it goes from
850 // right to left, or bottom to top.
851 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
852 nsGkAtoms::reverse, eCaseMatters))
853 aNewPos = maxpos - aNewPos;
854 else
855 aNewPos += minpos;
856
857 // get the new position and make sure it is in bounds
858 if (aNewPos < minpos || maxpos < minpos)
859 aNewPos = minpos;
860 else if (aNewPos > maxpos)
861 aNewPos = maxpos;
862
863 SetCurrentPositionInternal(aScrollbar, aNewPos, aIsSmooth);
864 }
865
866 void
SetCurrentPositionInternal(nsIContent * aScrollbar,int32_t aNewPos,bool aIsSmooth)867 nsSliderFrame::SetCurrentPositionInternal(nsIContent* aScrollbar, int32_t aNewPos,
868 bool aIsSmooth)
869 {
870 nsCOMPtr<nsIContent> scrollbar = aScrollbar;
871 nsIFrame* scrollbarBox = GetScrollbar();
872 nsWeakFrame weakFrame(this);
873
874 mUserChanged = true;
875
876 nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
877 if (scrollbarFrame) {
878 // See if we have a mediator.
879 nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
880 if (mediator) {
881 nscoord oldPos = nsPresContext::CSSPixelsToAppUnits(GetCurrentPosition(scrollbar));
882 nscoord newPos = nsPresContext::CSSPixelsToAppUnits(aNewPos);
883 mediator->ThumbMoved(scrollbarFrame, oldPos, newPos);
884 if (!weakFrame.IsAlive()) {
885 return;
886 }
887 UpdateAttribute(scrollbar, aNewPos, /* aNotify */false, aIsSmooth);
888 CurrentPositionChanged();
889 mUserChanged = false;
890 return;
891 }
892 }
893
894 UpdateAttribute(scrollbar, aNewPos, true, aIsSmooth);
895 if (!weakFrame.IsAlive()) {
896 return;
897 }
898 mUserChanged = false;
899
900 #ifdef DEBUG_SLIDER
901 printf("Current Pos=%d\n",aNewPos);
902 #endif
903
904 }
905
906 nsIAtom*
GetType() const907 nsSliderFrame::GetType() const
908 {
909 return nsGkAtoms::sliderFrame;
910 }
911
912 void
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)913 nsSliderFrame::SetInitialChildList(ChildListID aListID,
914 nsFrameList& aChildList)
915 {
916 nsBoxFrame::SetInitialChildList(aListID, aChildList);
917 if (aListID == kPrincipalList) {
918 AddListener();
919 }
920 }
921
922 nsresult
HandleEvent(nsIDOMEvent * aEvent)923 nsSliderMediator::HandleEvent(nsIDOMEvent* aEvent)
924 {
925 // Only process the event if the thumb is not being dragged.
926 if (mSlider && !mSlider->isDraggingThumb())
927 return mSlider->StartDrag(aEvent);
928
929 return NS_OK;
930 }
931
932 bool
StartAPZDrag()933 nsSliderFrame::StartAPZDrag()
934 {
935 if (!gfxPlatform::GetPlatform()->SupportsApzDragInput()) {
936 return false;
937 }
938
939 nsContainerFrame* cf = GetScrollbar()->GetParent();
940 if (!cf) {
941 return false;
942 }
943
944 nsIContent* scrollableContent = cf->GetContent();
945 if (!scrollableContent) {
946 return false;
947 }
948
949 mozilla::layers::FrameMetrics::ViewID scrollTargetId;
950 bool hasID = nsLayoutUtils::FindIDFor(scrollableContent, &scrollTargetId);
951 bool hasAPZView = hasID && (scrollTargetId != layers::FrameMetrics::NULL_SCROLL_ID);
952
953 if (!hasAPZView) {
954 return false;
955 }
956
957 nsIFrame* scrollbarBox = GetScrollbar();
958 nsCOMPtr<nsIContent> scrollbar = GetContentOfBox(scrollbarBox);
959
960 // This rect is the range in which the scroll thumb can slide in.
961 nsRect sliderTrack = GetRect() - scrollbarBox->GetPosition();
962 CSSIntRect sliderTrackCSS = CSSIntRect::FromAppUnitsRounded(sliderTrack);
963
964 uint64_t inputblockId = InputAPZContext::GetInputBlockId();
965 uint32_t presShellId = PresContext()->PresShell()->GetPresShellId();
966 AsyncDragMetrics dragMetrics(scrollTargetId, presShellId, inputblockId,
967 NSAppUnitsToIntPixels(mDragStart,
968 float(AppUnitsPerCSSPixel())),
969 sliderTrackCSS,
970 IsXULHorizontal() ? AsyncDragMetrics::HORIZONTAL :
971 AsyncDragMetrics::VERTICAL);
972
973 if (!nsLayoutUtils::HasDisplayPort(scrollableContent)) {
974 return false;
975 }
976
977 // When we start an APZ drag, we wont get mouse events for the drag.
978 // APZ will consume them all and only notify us of the new scroll position.
979 this->GetNearestWidget()->StartAsyncScrollbarDrag(dragMetrics);
980 return true;
981 }
982
983 nsresult
StartDrag(nsIDOMEvent * aEvent)984 nsSliderFrame::StartDrag(nsIDOMEvent* aEvent)
985 {
986 #ifdef DEBUG_SLIDER
987 printf("Begin dragging\n");
988 #endif
989 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
990 nsGkAtoms::_true, eCaseMatters))
991 return NS_OK;
992
993 WidgetGUIEvent* event = aEvent->WidgetEventPtr()->AsGUIEvent();
994
995 if (!ShouldScrollForEvent(event)) {
996 return NS_OK;
997 }
998
999 nsPoint pt;
1000 if (!GetEventPoint(event, pt)) {
1001 return NS_OK;
1002 }
1003 bool isHorizontal = IsXULHorizontal();
1004 nscoord pos = isHorizontal ? pt.x : pt.y;
1005
1006 // If we should scroll-to-click, first place the middle of the slider thumb
1007 // under the mouse.
1008 nsCOMPtr<nsIContent> scrollbar;
1009 nscoord newpos = pos;
1010 bool scrollToClick = ShouldScrollToClickForEvent(event);
1011 if (scrollToClick) {
1012 // adjust so that the middle of the thumb is placed under the click
1013 nsIFrame* thumbFrame = mFrames.FirstChild();
1014 if (!thumbFrame) {
1015 return NS_OK;
1016 }
1017 nsSize thumbSize = thumbFrame->GetSize();
1018 nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height;
1019
1020 newpos -= (thumbLength/2);
1021
1022 nsIFrame* scrollbarBox = GetScrollbar();
1023 scrollbar = GetContentOfBox(scrollbarBox);
1024 }
1025
1026 DragThumb(true);
1027
1028 if (scrollToClick) {
1029 // should aMaySnap be true here?
1030 SetCurrentThumbPosition(scrollbar, newpos, false, false);
1031 }
1032
1033 nsIFrame* thumbFrame = mFrames.FirstChild();
1034 if (!thumbFrame) {
1035 return NS_OK;
1036 }
1037
1038 #ifdef MOZ_WIDGET_GTK
1039 nsCOMPtr<nsIContent> thumb = thumbFrame->GetContent();
1040 thumb->SetAttr(kNameSpaceID_None, nsGkAtoms::active, NS_LITERAL_STRING("true"), true);
1041 #endif
1042
1043 if (isHorizontal)
1044 mThumbStart = thumbFrame->GetPosition().x;
1045 else
1046 mThumbStart = thumbFrame->GetPosition().y;
1047
1048 mDragStart = pos - mThumbStart;
1049
1050 mScrollingWithAPZ = StartAPZDrag();
1051
1052 #ifdef DEBUG_SLIDER
1053 printf("Pressed mDragStart=%d\n",mDragStart);
1054 #endif
1055
1056 if (!mScrollingWithAPZ && !mSuppressionActive) {
1057 MOZ_ASSERT(PresContext()->PresShell());
1058 APZCCallbackHelper::SuppressDisplayport(true, PresContext()->PresShell());
1059 mSuppressionActive = true;
1060 }
1061
1062 return NS_OK;
1063 }
1064
1065 nsresult
StopDrag()1066 nsSliderFrame::StopDrag()
1067 {
1068 AddListener();
1069 DragThumb(false);
1070
1071 mScrollingWithAPZ = false;
1072
1073 if (mSuppressionActive) {
1074 MOZ_ASSERT(PresContext()->PresShell());
1075 APZCCallbackHelper::SuppressDisplayport(false, PresContext()->PresShell());
1076 mSuppressionActive = false;
1077 }
1078
1079 #ifdef MOZ_WIDGET_GTK
1080 nsIFrame* thumbFrame = mFrames.FirstChild();
1081 if (thumbFrame) {
1082 nsCOMPtr<nsIContent> thumb = thumbFrame->GetContent();
1083 thumb->UnsetAttr(kNameSpaceID_None, nsGkAtoms::active, true);
1084 }
1085 #endif
1086
1087 if (mChange) {
1088 StopRepeat();
1089 mChange = 0;
1090 }
1091 return NS_OK;
1092 }
1093
1094 void
DragThumb(bool aGrabMouseEvents)1095 nsSliderFrame::DragThumb(bool aGrabMouseEvents)
1096 {
1097 mDragFinished = !aGrabMouseEvents;
1098
1099 // inform the parent <scale> that a drag is beginning or ending
1100 nsIFrame* parent = GetParent();
1101 if (parent) {
1102 nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
1103 if (sliderListener) {
1104 nsContentUtils::AddScriptRunner(
1105 new nsDragStateChangedRunnable(sliderListener, aGrabMouseEvents));
1106 }
1107 }
1108
1109 nsIPresShell::SetCapturingContent(aGrabMouseEvents ? GetContent() : nullptr,
1110 aGrabMouseEvents ? CAPTURE_IGNOREALLOWED : 0);
1111 }
1112
1113 bool
isDraggingThumb()1114 nsSliderFrame::isDraggingThumb()
1115 {
1116 return (nsIPresShell::GetCapturingContent() == GetContent());
1117 }
1118
1119 void
AddListener()1120 nsSliderFrame::AddListener()
1121 {
1122 if (!mMediator) {
1123 mMediator = new nsSliderMediator(this);
1124 }
1125
1126 nsIFrame* thumbFrame = mFrames.FirstChild();
1127 if (!thumbFrame) {
1128 return;
1129 }
1130 thumbFrame->GetContent()->
1131 AddSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator,
1132 false, false);
1133 thumbFrame->GetContent()->
1134 AddSystemEventListener(NS_LITERAL_STRING("touchstart"), mMediator,
1135 false, false);
1136 }
1137
1138 void
RemoveListener()1139 nsSliderFrame::RemoveListener()
1140 {
1141 NS_ASSERTION(mMediator, "No listener was ever added!!");
1142
1143 nsIFrame* thumbFrame = mFrames.FirstChild();
1144 if (!thumbFrame)
1145 return;
1146
1147 thumbFrame->GetContent()->
1148 RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator, false);
1149 }
1150
1151 bool
ShouldScrollForEvent(WidgetGUIEvent * aEvent)1152 nsSliderFrame::ShouldScrollForEvent(WidgetGUIEvent* aEvent)
1153 {
1154 switch (aEvent->mMessage) {
1155 case eTouchStart:
1156 case eTouchEnd:
1157 return true;
1158 case eMouseDown:
1159 case eMouseUp: {
1160 uint16_t button = aEvent->AsMouseEvent()->button;
1161 #ifdef MOZ_WIDGET_GTK
1162 return (button == WidgetMouseEvent::eLeftButton) ||
1163 (button == WidgetMouseEvent::eRightButton && GetScrollToClick()) ||
1164 (button == WidgetMouseEvent::eMiddleButton && gMiddlePref && !GetScrollToClick());
1165 #else
1166 return (button == WidgetMouseEvent::eLeftButton) ||
1167 (button == WidgetMouseEvent::eMiddleButton && gMiddlePref);
1168 #endif
1169 }
1170 default:
1171 return false;
1172 }
1173 }
1174
1175 bool
ShouldScrollToClickForEvent(WidgetGUIEvent * aEvent)1176 nsSliderFrame::ShouldScrollToClickForEvent(WidgetGUIEvent* aEvent)
1177 {
1178 if (!ShouldScrollForEvent(aEvent)) {
1179 return false;
1180 }
1181
1182 if (aEvent->mMessage == eTouchStart) {
1183 return GetScrollToClick();
1184 }
1185
1186 if (aEvent->mMessage != eMouseDown) {
1187 return false;
1188 }
1189
1190 #if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
1191 // On Mac and Linux, clicking the scrollbar thumb should never scroll to click.
1192 if (IsEventOverThumb(aEvent)) {
1193 return false;
1194 }
1195 #endif
1196
1197 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
1198 if (mouseEvent->button == WidgetMouseEvent::eLeftButton) {
1199 #ifdef XP_MACOSX
1200 bool invertPref = mouseEvent->IsAlt();
1201 #else
1202 bool invertPref = mouseEvent->IsShift();
1203 #endif
1204 return GetScrollToClick() != invertPref;
1205 }
1206
1207 #ifdef MOZ_WIDGET_GTK
1208 if (mouseEvent->button == WidgetMouseEvent::eRightButton) {
1209 return !GetScrollToClick();
1210 }
1211 #endif
1212
1213 return true;
1214 }
1215
1216 bool
IsEventOverThumb(WidgetGUIEvent * aEvent)1217 nsSliderFrame::IsEventOverThumb(WidgetGUIEvent* aEvent)
1218 {
1219 nsIFrame* thumbFrame = mFrames.FirstChild();
1220 if (!thumbFrame) {
1221 return false;
1222 }
1223
1224 nsPoint eventPoint;
1225 if (!GetEventPoint(aEvent, eventPoint)) {
1226 return false;
1227 }
1228
1229 nsRect thumbRect = thumbFrame->GetRect();
1230 #if defined(MOZ_WIDGET_GTK)
1231 /* Scrollbar track can have padding, so it's better to check that eventPoint
1232 * is inside of actual thumb, not just its one axis. The part of the scrollbar
1233 * track adjacent to thumb can actually receive events in GTK3 */
1234 return eventPoint.x >= thumbRect.x && eventPoint.x < thumbRect.XMost() &&
1235 eventPoint.y >= thumbRect.y && eventPoint.y < thumbRect.YMost();
1236 #else
1237 bool isHorizontal = IsXULHorizontal();
1238 nscoord eventPos = isHorizontal ? eventPoint.x : eventPoint.y;
1239 nscoord thumbStart = isHorizontal ? thumbRect.x : thumbRect.y;
1240 nscoord thumbEnd = isHorizontal ? thumbRect.XMost() : thumbRect.YMost();
1241
1242 return eventPos >= thumbStart && eventPos < thumbEnd;
1243 #endif
1244 }
1245
1246 NS_IMETHODIMP
HandlePress(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)1247 nsSliderFrame::HandlePress(nsPresContext* aPresContext,
1248 WidgetGUIEvent* aEvent,
1249 nsEventStatus* aEventStatus)
1250 {
1251 if (!ShouldScrollForEvent(aEvent) || ShouldScrollToClickForEvent(aEvent)) {
1252 return NS_OK;
1253 }
1254
1255 if (IsEventOverThumb(aEvent)) {
1256 return NS_OK;
1257 }
1258
1259 nsIFrame* thumbFrame = mFrames.FirstChild();
1260 if (!thumbFrame) // display:none?
1261 return NS_OK;
1262
1263 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
1264 nsGkAtoms::_true, eCaseMatters))
1265 return NS_OK;
1266
1267 nsRect thumbRect = thumbFrame->GetRect();
1268
1269 nscoord change = 1;
1270 nsPoint eventPoint;
1271 if (!GetEventPoint(aEvent, eventPoint)) {
1272 return NS_OK;
1273 }
1274 if (IsXULHorizontal() ? eventPoint.x < thumbRect.x
1275 : eventPoint.y < thumbRect.y)
1276 change = -1;
1277
1278 mChange = change;
1279 DragThumb(true);
1280 // On Linux we want to keep scrolling in the direction indicated by |change|
1281 // until the mouse is released. On the other platforms we want to stop
1282 // scrolling as soon as the scrollbar thumb has reached the current mouse
1283 // position.
1284 #ifdef MOZ_WIDGET_GTK
1285 nsRect clientRect;
1286 GetXULClientRect(clientRect);
1287
1288 // Set the destination point to the very end of the scrollbar so that
1289 // scrolling doesn't stop halfway through.
1290 if (change > 0) {
1291 mDestinationPoint = nsPoint(clientRect.width, clientRect.height);
1292 }
1293 else {
1294 mDestinationPoint = nsPoint(0, 0);
1295 }
1296 #else
1297 mDestinationPoint = eventPoint;
1298 #endif
1299 StartRepeat();
1300 PageScroll(change);
1301
1302 return NS_OK;
1303 }
1304
1305 NS_IMETHODIMP
HandleRelease(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)1306 nsSliderFrame::HandleRelease(nsPresContext* aPresContext,
1307 WidgetGUIEvent* aEvent,
1308 nsEventStatus* aEventStatus)
1309 {
1310 StopRepeat();
1311
1312 nsIFrame* scrollbar = GetScrollbar();
1313 nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
1314 if (sb) {
1315 nsIScrollbarMediator* m = sb->GetScrollbarMediator();
1316 if (m) {
1317 m->ScrollbarReleased(sb);
1318 }
1319 }
1320 return NS_OK;
1321 }
1322
1323 void
DestroyFrom(nsIFrame * aDestructRoot)1324 nsSliderFrame::DestroyFrom(nsIFrame* aDestructRoot)
1325 {
1326 // tell our mediator if we have one we are gone.
1327 if (mMediator) {
1328 mMediator->SetSlider(nullptr);
1329 mMediator = nullptr;
1330 }
1331 StopRepeat();
1332
1333 // call base class Destroy()
1334 nsBoxFrame::DestroyFrom(aDestructRoot);
1335 }
1336
1337 nsSize
GetXULPrefSize(nsBoxLayoutState & aState)1338 nsSliderFrame::GetXULPrefSize(nsBoxLayoutState& aState)
1339 {
1340 EnsureOrient();
1341 return nsBoxFrame::GetXULPrefSize(aState);
1342 }
1343
1344 nsSize
GetXULMinSize(nsBoxLayoutState & aState)1345 nsSliderFrame::GetXULMinSize(nsBoxLayoutState& aState)
1346 {
1347 EnsureOrient();
1348
1349 // our min size is just our borders and padding
1350 return nsBox::GetXULMinSize(aState);
1351 }
1352
1353 nsSize
GetXULMaxSize(nsBoxLayoutState & aState)1354 nsSliderFrame::GetXULMaxSize(nsBoxLayoutState& aState)
1355 {
1356 EnsureOrient();
1357 return nsBoxFrame::GetXULMaxSize(aState);
1358 }
1359
1360 void
EnsureOrient()1361 nsSliderFrame::EnsureOrient()
1362 {
1363 nsIFrame* scrollbarBox = GetScrollbar();
1364
1365 bool isHorizontal = (scrollbarBox->GetStateBits() & NS_STATE_IS_HORIZONTAL) != 0;
1366 if (isHorizontal)
1367 mState |= NS_STATE_IS_HORIZONTAL;
1368 else
1369 mState &= ~NS_STATE_IS_HORIZONTAL;
1370 }
1371
1372
1373 void
Notify(void)1374 nsSliderFrame::Notify(void)
1375 {
1376 bool stop = false;
1377
1378 nsIFrame* thumbFrame = mFrames.FirstChild();
1379 if (!thumbFrame) {
1380 StopRepeat();
1381 return;
1382 }
1383 nsRect thumbRect = thumbFrame->GetRect();
1384
1385 bool isHorizontal = IsXULHorizontal();
1386
1387 // See if the thumb has moved past our destination point.
1388 // if it has we want to stop.
1389 if (isHorizontal) {
1390 if (mChange < 0) {
1391 if (thumbRect.x < mDestinationPoint.x)
1392 stop = true;
1393 } else {
1394 if (thumbRect.x + thumbRect.width > mDestinationPoint.x)
1395 stop = true;
1396 }
1397 } else {
1398 if (mChange < 0) {
1399 if (thumbRect.y < mDestinationPoint.y)
1400 stop = true;
1401 } else {
1402 if (thumbRect.y + thumbRect.height > mDestinationPoint.y)
1403 stop = true;
1404 }
1405 }
1406
1407
1408 if (stop) {
1409 StopRepeat();
1410 } else {
1411 PageScroll(mChange);
1412 }
1413 }
1414
1415 void
PageScroll(nscoord aChange)1416 nsSliderFrame::PageScroll(nscoord aChange)
1417 {
1418 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
1419 nsGkAtoms::reverse, eCaseMatters)) {
1420 aChange = -aChange;
1421 }
1422 nsIFrame* scrollbar = GetScrollbar();
1423 nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
1424 if (sb) {
1425 nsIScrollbarMediator* m = sb->GetScrollbarMediator();
1426 sb->SetIncrementToPage(aChange);
1427 if (m) {
1428 m->ScrollByPage(sb, aChange, nsIScrollbarMediator::ENABLE_SNAP);
1429 return;
1430 }
1431 }
1432 PageUpDown(aChange);
1433 }
1434
1435 float
GetThumbRatio() const1436 nsSliderFrame::GetThumbRatio() const
1437 {
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();
1443 }
1444
1445 NS_IMPL_ISUPPORTS(nsSliderMediator,
1446 nsIDOMEventListener)
1447