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