Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
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/. */
6 //
7 // Eric Vaughan
8 // Netscape Communications
9 //
10 // See documentation in associated header file
11 //
13 #include "nsSliderFrame.h"
14 #include "nsStyleContext.h"
15 #include "nsPresContext.h"
16 #include "nsIContent.h"
17 #include "nsCOMPtr.h"
18 #include "nsNameSpaceManager.h"
19 #include "nsGkAtoms.h"
20 #include "nsHTMLParts.h"
21 #include "nsIPresShell.h"
22 #include "nsCSSRendering.h"
23 #include "nsIDOMMouseEvent.h"
24 #include "nsScrollbarButtonFrame.h"
25 #include "nsISliderListener.h"
26 #include "nsIScrollbarMediator.h"
27 #include "nsScrollbarFrame.h"
28 #include "nsRepeatService.h"
29 #include "nsBoxLayoutState.h"
30 #include "nsSprocketLayout.h"
31 #include "nsIServiceManager.h"
32 #include "nsContentUtils.h"
33 #include "nsLayoutUtils.h"
34 #include "nsDisplayList.h"
35 #include "mozilla/Preferences.h"
36 #include "mozilla/LookAndFeel.h"
37 #include "mozilla/MouseEvents.h"
38 #include <algorithm>
40 using namespace mozilla;
42 bool nsSliderFrame::gMiddlePref = false;
43 int32_t nsSliderFrame::gSnapMultiplier;
45 // Turn this on if you want to debug slider frames.
46 #undef DEBUG_SLIDER
48 static already_AddRefed<nsIContent>
49 GetContentOfBox(nsIFrame *aBox)
50 {
51 nsCOMPtr<nsIContent> content = aBox->GetContent();
52 return content.forget();
53 }
55 nsIFrame*
56 NS_NewSliderFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
57 {
58 return new (aPresShell) nsSliderFrame(aPresShell, aContext);
59 }
61 NS_IMPL_FRAMEARENA_HELPERS(nsSliderFrame)
63 nsSliderFrame::nsSliderFrame(nsIPresShell* aPresShell, nsStyleContext* aContext):
64 nsBoxFrame(aPresShell, aContext),
65 mCurPos(0),
66 mChange(0),
67 mUserChanged(false)
68 {
69 }
71 // stop timer
72 nsSliderFrame::~nsSliderFrame()
73 {
74 }
76 void
77 nsSliderFrame::Init(nsIContent* aContent,
78 nsIFrame* aParent,
79 nsIFrame* aPrevInFlow)
80 {
81 nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
83 static bool gotPrefs = false;
84 if (!gotPrefs) {
85 gotPrefs = true;
87 gMiddlePref = Preferences::GetBool("middlemouse.scrollbarPosition");
88 gSnapMultiplier = Preferences::GetInt("slider.snapMultiplier");
89 }
91 mCurPos = GetCurrentPosition(aContent);
92 }
94 nsresult
95 nsSliderFrame::RemoveFrame(ChildListID aListID,
96 nsIFrame* aOldFrame)
97 {
98 nsresult rv = nsBoxFrame::RemoveFrame(aListID, aOldFrame);
99 if (mFrames.IsEmpty())
100 RemoveListener();
102 return rv;
103 }
105 nsresult
106 nsSliderFrame::InsertFrames(ChildListID aListID,
107 nsIFrame* aPrevFrame,
108 nsFrameList& aFrameList)
109 {
110 bool wasEmpty = mFrames.IsEmpty();
111 nsresult rv = nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
112 if (wasEmpty)
113 AddListener();
115 return rv;
116 }
118 nsresult
119 nsSliderFrame::AppendFrames(ChildListID aListID,
120 nsFrameList& aFrameList)
121 {
122 // if we have no children and on was added then make sure we add the
123 // listener
124 bool wasEmpty = mFrames.IsEmpty();
125 nsresult rv = nsBoxFrame::AppendFrames(aListID, aFrameList);
126 if (wasEmpty)
127 AddListener();
129 return rv;
130 }
132 int32_t
133 nsSliderFrame::GetCurrentPosition(nsIContent* content)
134 {
135 return GetIntegerAttribute(content, nsGkAtoms::curpos, 0);
136 }
138 int32_t
139 nsSliderFrame::GetMinPosition(nsIContent* content)
140 {
141 return GetIntegerAttribute(content, nsGkAtoms::minpos, 0);
142 }
144 int32_t
145 nsSliderFrame::GetMaxPosition(nsIContent* content)
146 {
147 return GetIntegerAttribute(content, nsGkAtoms::maxpos, 100);
148 }
150 int32_t
151 nsSliderFrame::GetIncrement(nsIContent* content)
152 {
153 return GetIntegerAttribute(content, nsGkAtoms::increment, 1);
154 }
157 int32_t
158 nsSliderFrame::GetPageIncrement(nsIContent* content)
159 {
160 return GetIntegerAttribute(content, nsGkAtoms::pageincrement, 10);
161 }
163 int32_t
164 nsSliderFrame::GetIntegerAttribute(nsIContent* content, nsIAtom* atom, int32_t defaultValue)
165 {
166 nsAutoString value;
167 content->GetAttr(kNameSpaceID_None, atom, value);
168 if (!value.IsEmpty()) {
169 nsresult error;
171 // convert it to an integer
172 defaultValue = value.ToInteger(&error);
173 }
175 return defaultValue;
176 }
178 class nsValueChangedRunnable : public nsRunnable
179 {
180 public:
181 nsValueChangedRunnable(nsISliderListener* aListener,
182 nsIAtom* aWhich,
183 int32_t aValue,
184 bool aUserChanged)
185 : mListener(aListener), mWhich(aWhich),
186 mValue(aValue), mUserChanged(aUserChanged)
187 {}
189 NS_IMETHODIMP Run()
190 {
191 return mListener->ValueChanged(nsDependentAtomString(mWhich),
192 mValue, mUserChanged);
193 }
195 nsCOMPtr<nsISliderListener> mListener;
196 nsCOMPtr<nsIAtom> mWhich;
197 int32_t mValue;
198 bool mUserChanged;
199 };
201 class nsDragStateChangedRunnable : public nsRunnable
202 {
203 public:
204 nsDragStateChangedRunnable(nsISliderListener* aListener,
205 bool aDragBeginning)
206 : mListener(aListener),
207 mDragBeginning(aDragBeginning)
208 {}
210 NS_IMETHODIMP Run()
211 {
212 return mListener->DragStateChanged(mDragBeginning);
213 }
215 nsCOMPtr<nsISliderListener> mListener;
216 bool mDragBeginning;
217 };
219 nsresult
220 nsSliderFrame::AttributeChanged(int32_t aNameSpaceID,
221 nsIAtom* aAttribute,
222 int32_t aModType)
223 {
224 nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
225 aModType);
226 // if the current position changes
227 if (aAttribute == nsGkAtoms::curpos) {
228 CurrentPositionChanged();
229 } else if (aAttribute == nsGkAtoms::minpos ||
230 aAttribute == nsGkAtoms::maxpos) {
231 // bounds check it.
233 nsIFrame* scrollbarBox = GetScrollbar();
234 nsCOMPtr<nsIContent> scrollbar;
235 scrollbar = GetContentOfBox(scrollbarBox);
236 int32_t current = GetCurrentPosition(scrollbar);
237 int32_t min = GetMinPosition(scrollbar);
238 int32_t max = GetMaxPosition(scrollbar);
240 // inform the parent <scale> that the minimum or maximum changed
241 nsIFrame* parent = GetParent();
242 if (parent) {
243 nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
244 if (sliderListener) {
245 nsContentUtils::AddScriptRunner(
246 new nsValueChangedRunnable(sliderListener, aAttribute,
247 aAttribute == nsGkAtoms::minpos ? min : max, false));
248 }
249 }
251 if (current < min || current > max)
252 {
253 if (current < min || max < min)
254 current = min;
255 else if (current > max)
256 current = max;
258 // set the new position and notify observers
259 nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
260 if (scrollbarFrame) {
261 nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
262 if (mediator) {
263 mediator->PositionChanged(scrollbarFrame, GetCurrentPosition(scrollbar), current);
264 }
265 }
267 nsContentUtils::AddScriptRunner(
268 new nsSetAttrRunnable(scrollbar, nsGkAtoms::curpos, current));
269 }
270 }
272 if (aAttribute == nsGkAtoms::minpos ||
273 aAttribute == nsGkAtoms::maxpos ||
274 aAttribute == nsGkAtoms::pageincrement ||
275 aAttribute == nsGkAtoms::increment) {
277 PresContext()->PresShell()->
278 FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
279 }
281 return rv;
282 }
284 void
285 nsSliderFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
286 const nsRect& aDirtyRect,
287 const nsDisplayListSet& aLists)
288 {
289 if (aBuilder->IsForEventDelivery() && isDraggingThumb()) {
290 // This is EVIL, we shouldn't be messing with event delivery just to get
291 // thumb mouse drag events to arrive at the slider!
292 aLists.Outlines()->AppendNewToTop(new (aBuilder)
293 nsDisplayEventReceiver(aBuilder, this));
294 return;
295 }
297 nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
298 }
300 void
301 nsSliderFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
302 const nsRect& aDirtyRect,
303 const nsDisplayListSet& aLists)
304 {
305 // if we are too small to have a thumb don't paint it.
306 nsIFrame* thumb = GetChildBox();
308 if (thumb) {
309 nsRect thumbRect(thumb->GetRect());
310 nsMargin m;
311 thumb->GetMargin(m);
312 thumbRect.Inflate(m);
314 nsRect crect;
315 GetClientRect(crect);
317 if (crect.width < thumbRect.width || crect.height < thumbRect.height)
318 return;
319 }
321 nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
322 }
324 NS_IMETHODIMP
325 nsSliderFrame::DoLayout(nsBoxLayoutState& aState)
326 {
327 // get the thumb should be our only child
328 nsIFrame* thumbBox = GetChildBox();
330 if (!thumbBox) {
331 SyncLayout(aState);
332 return NS_OK;
333 }
335 EnsureOrient();
337 #ifdef DEBUG_LAYOUT
338 if (mState & NS_STATE_DEBUG_WAS_SET) {
339 if (mState & NS_STATE_SET_TO_DEBUG)
340 SetDebug(aState, true);
341 else
342 SetDebug(aState, false);
343 }
344 #endif
346 // get the content area inside our borders
347 nsRect clientRect;
348 GetClientRect(clientRect);
350 // get the scrollbar
351 nsIFrame* scrollbarBox = GetScrollbar();
352 nsCOMPtr<nsIContent> scrollbar;
353 scrollbar = GetContentOfBox(scrollbarBox);
355 // get the thumb's pref size
356 nsSize thumbSize = thumbBox->GetPrefSize(aState);
358 if (IsHorizontal())
359 thumbSize.height = clientRect.height;
360 else
361 thumbSize.width = clientRect.width;
363 int32_t curPos = GetCurrentPosition(scrollbar);
364 int32_t minPos = GetMinPosition(scrollbar);
365 int32_t maxPos = GetMaxPosition(scrollbar);
366 int32_t pageIncrement = GetPageIncrement(scrollbar);
368 maxPos = std::max(minPos, maxPos);
369 curPos = clamped(curPos, minPos, maxPos);
371 nscoord& availableLength = IsHorizontal() ? clientRect.width : clientRect.height;
372 nscoord& thumbLength = IsHorizontal() ? thumbSize.width : thumbSize.height;
374 if ((pageIncrement + maxPos - minPos) > 0 && thumbBox->GetFlex(aState) > 0) {
375 float ratio = float(pageIncrement) / float(maxPos - minPos + pageIncrement);
376 thumbLength = std::max(thumbLength, NSToCoordRound(availableLength * ratio));
377 }
379 // Round the thumb's length to device pixels.
380 nsPresContext* presContext = PresContext();
381 thumbLength = presContext->DevPixelsToAppUnits(
382 presContext->AppUnitsToDevPixels(thumbLength));
384 // mRatio translates the thumb position in app units to the value.
385 mRatio = (minPos != maxPos) ? float(availableLength - thumbLength) / float(maxPos - minPos) : 1;
387 // in reverse mode, curpos is reversed such that lower values are to the
388 // right or bottom and increase leftwards or upwards. In this case, use the
389 // offset from the end instead of the beginning.
390 bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
391 nsGkAtoms::reverse, eCaseMatters);
392 nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos);
394 // set the thumb's coord to be the current pos * the ratio.
395 nsRect thumbRect(clientRect.x, clientRect.y, thumbSize.width, thumbSize.height);
396 int32_t& thumbPos = (IsHorizontal() ? thumbRect.x : thumbRect.y);
397 thumbPos += NSToCoordRound(pos * mRatio);
399 nsRect oldThumbRect(thumbBox->GetRect());
400 LayoutChildAt(aState, thumbBox, thumbRect);
402 SyncLayout(aState);
404 // Redraw only if thumb changed size.
405 if (!oldThumbRect.IsEqualInterior(thumbRect))
406 Redraw(aState);
408 return NS_OK;
409 }
412 nsresult
413 nsSliderFrame::HandleEvent(nsPresContext* aPresContext,
414 WidgetGUIEvent* aEvent,
415 nsEventStatus* aEventStatus)
416 {
417 NS_ENSURE_ARG_POINTER(aEventStatus);
419 // If a web page calls event.preventDefault() we still want to
420 // scroll when scroll arrow is clicked. See bug 511075.
421 if (!mContent->IsInNativeAnonymousSubtree() &&
422 nsEventStatus_eConsumeNoDefault == *aEventStatus) {
423 return NS_OK;
424 }
426 nsIFrame* scrollbarBox = GetScrollbar();
427 nsCOMPtr<nsIContent> scrollbar;
428 scrollbar = GetContentOfBox(scrollbarBox);
429 bool isHorizontal = IsHorizontal();
431 if (isDraggingThumb())
432 {
433 switch (aEvent->message) {
434 case NS_TOUCH_MOVE:
435 case NS_MOUSE_MOVE: {
436 nsPoint eventPoint;
437 if (!GetEventPoint(aEvent, eventPoint)) {
438 break;
439 }
440 if (mChange) {
441 // We're in the process of moving the thumb to the mouse,
442 // but the mouse just moved. Make sure to update our
443 // destination point.
444 mDestinationPoint = eventPoint;
445 StopRepeat();
446 StartRepeat();
447 break;
448 }
450 nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y;
452 nsIFrame* thumbFrame = mFrames.FirstChild();
453 if (!thumbFrame) {
454 return NS_OK;
455 }
457 // take our current position and subtract the start location
458 pos -= mDragStart;
459 bool isMouseOutsideThumb = false;
460 if (gSnapMultiplier) {
461 nsSize thumbSize = thumbFrame->GetSize();
462 if (isHorizontal) {
463 // horizontal scrollbar - check if mouse is above or below thumb
464 // XXXbz what about looking at the .y of the thumb's rect? Is that
465 // always zero here?
466 if (eventPoint.y < -gSnapMultiplier * thumbSize.height ||
467 eventPoint.y > thumbSize.height +
468 gSnapMultiplier * thumbSize.height)
469 isMouseOutsideThumb = true;
470 }
471 else {
472 // vertical scrollbar - check if mouse is left or right of thumb
473 if (eventPoint.x < -gSnapMultiplier * thumbSize.width ||
474 eventPoint.x > thumbSize.width +
475 gSnapMultiplier * thumbSize.width)
476 isMouseOutsideThumb = true;
477 }
478 }
479 if (aEvent->eventStructType == NS_TOUCH_EVENT) {
480 *aEventStatus = nsEventStatus_eConsumeNoDefault;
481 }
482 if (isMouseOutsideThumb)
483 {
484 SetCurrentThumbPosition(scrollbar, mThumbStart, false, false);
485 return NS_OK;
486 }
488 // set it
489 SetCurrentThumbPosition(scrollbar, pos, false, true); // with snapping
490 }
491 break;
493 case NS_TOUCH_END:
494 case NS_MOUSE_BUTTON_UP:
495 if (ShouldScrollForEvent(aEvent)) {
496 // stop capturing
497 AddListener();
498 DragThumb(false);
499 if (mChange) {
500 StopRepeat();
501 mChange = 0;
502 }
503 //we MUST call nsFrame HandleEvent for mouse ups to maintain the selection state and capture state.
504 return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
505 }
506 }
508 //return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
509 return NS_OK;
510 } else if (ShouldScrollToClickForEvent(aEvent)) {
511 nsPoint eventPoint;
512 if (!GetEventPoint(aEvent, eventPoint)) {
513 return NS_OK;
514 }
515 nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y;
517 // adjust so that the middle of the thumb is placed under the click
518 nsIFrame* thumbFrame = mFrames.FirstChild();
519 if (!thumbFrame) {
520 return NS_OK;
521 }
522 nsSize thumbSize = thumbFrame->GetSize();
523 nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height;
525 // set it
526 nsWeakFrame weakFrame(this);
527 // should aMaySnap be true here?
528 SetCurrentThumbPosition(scrollbar, pos - thumbLength/2, false, false);
529 NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
531 DragThumb(true);
532 if (aEvent->eventStructType == NS_TOUCH_EVENT) {
533 *aEventStatus = nsEventStatus_eConsumeNoDefault;
534 }
536 if (isHorizontal)
537 mThumbStart = thumbFrame->GetPosition().x;
538 else
539 mThumbStart = thumbFrame->GetPosition().y;
541 mDragStart = pos - mThumbStart;
542 }
544 // XXX hack until handle release is actually called in nsframe.
545 // if (aEvent->message == NS_MOUSE_EXIT_SYNTH || aEvent->message == NS_MOUSE_RIGHT_BUTTON_UP || aEvent->message == NS_MOUSE_LEFT_BUTTON_UP)
546 // HandleRelease(aPresContext, aEvent, aEventStatus);
548 if (aEvent->message == NS_MOUSE_EXIT_SYNTH && mChange)
549 HandleRelease(aPresContext, aEvent, aEventStatus);
551 return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
552 }
554 // Helper function to collect the "scroll to click" metric. Beware of
555 // caching this, users expect to be able to change the system preference
556 // and see the browser change its behavior immediately.
557 bool
558 nsSliderFrame::GetScrollToClick()
559 {
560 if (GetScrollbar() != this) {
561 return LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollToClick, false);
562 }
564 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick,
565 nsGkAtoms::_true, eCaseMatters)) {
566 return true;
567 }
568 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick,
569 nsGkAtoms::_false, eCaseMatters)) {
570 return false;
571 }
573 #ifdef XP_MACOSX
574 return true;
575 #else
576 return false;
577 #endif
578 }
580 nsIFrame*
581 nsSliderFrame::GetScrollbar()
582 {
583 // if we are in a scrollbar then return the scrollbar's content node
584 // if we are not then return ours.
585 nsIFrame* scrollbar;
586 nsScrollbarButtonFrame::GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
588 if (scrollbar == nullptr)
589 return this;
591 return scrollbar->IsBoxFrame() ? scrollbar : this;
592 }
594 void
595 nsSliderFrame::PageUpDown(nscoord change)
596 {
597 // on a page up or down get our page increment. We get this by getting the scrollbar we are in and
598 // asking it for the current position and the page increment. If we are not in a scrollbar we will
599 // get the values from our own node.
600 nsIFrame* scrollbarBox = GetScrollbar();
601 nsCOMPtr<nsIContent> scrollbar;
602 scrollbar = GetContentOfBox(scrollbarBox);
604 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
605 nsGkAtoms::reverse, eCaseMatters))
606 change = -change;
608 nscoord pageIncrement = GetPageIncrement(scrollbar);
609 int32_t curpos = GetCurrentPosition(scrollbar);
610 int32_t minpos = GetMinPosition(scrollbar);
611 int32_t maxpos = GetMaxPosition(scrollbar);
613 // get the new position and make sure it is in bounds
614 int32_t newpos = curpos + change * pageIncrement;
615 if (newpos < minpos || maxpos < minpos)
616 newpos = minpos;
617 else if (newpos > maxpos)
618 newpos = maxpos;
620 SetCurrentPositionInternal(scrollbar, newpos, true);
621 }
623 // called when the current position changed and we need to update the thumb's location
624 void
625 nsSliderFrame::CurrentPositionChanged()
626 {
627 nsIFrame* scrollbarBox = GetScrollbar();
628 nsCOMPtr<nsIContent> scrollbar;
629 scrollbar = GetContentOfBox(scrollbarBox);
631 // get the current position
632 int32_t curPos = GetCurrentPosition(scrollbar);
634 // do nothing if the position did not change
635 if (mCurPos == curPos)
636 return;
638 // get our current min and max position from our content node
639 int32_t minPos = GetMinPosition(scrollbar);
640 int32_t maxPos = GetMaxPosition(scrollbar);
642 maxPos = std::max(minPos, maxPos);
643 curPos = clamped(curPos, minPos, maxPos);
645 // get the thumb's rect
646 nsIFrame* thumbFrame = mFrames.FirstChild();
647 if (!thumbFrame)
648 return; // The thumb may stream in asynchronously via XBL.
650 nsRect thumbRect = thumbFrame->GetRect();
652 nsRect clientRect;
653 GetClientRect(clientRect);
655 // figure out the new rect
656 nsRect newThumbRect(thumbRect);
658 bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
659 nsGkAtoms::reverse, eCaseMatters);
660 nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos);
662 if (IsHorizontal())
663 newThumbRect.x = clientRect.x + NSToCoordRound(pos * mRatio);
664 else
665 newThumbRect.y = clientRect.y + NSToCoordRound(pos * mRatio);
667 #ifdef MOZ_WIDGET_GONK
668 // avoid putting the scroll thumb at subpixel positions which cause needless invalidations
669 nscoord appUnitsPerPixel = PresContext()->AppUnitsPerDevPixel();
670 newThumbRect = newThumbRect.ToNearestPixels(appUnitsPerPixel).ToAppUnits(appUnitsPerPixel);
671 #endif
672 // set the rect
673 thumbFrame->SetRect(newThumbRect);
675 // Request a repaint of the scrollbar
676 SchedulePaint();
678 mCurPos = curPos;
680 // inform the parent <scale> if it exists that the value changed
681 nsIFrame* parent = GetParent();
682 if (parent) {
683 nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
684 if (sliderListener) {
685 nsContentUtils::AddScriptRunner(
686 new nsValueChangedRunnable(sliderListener, nsGkAtoms::curpos, mCurPos, mUserChanged));
687 }
688 }
689 }
691 static void UpdateAttribute(nsIContent* aScrollbar, nscoord aNewPos, bool aNotify, bool aIsSmooth) {
692 nsAutoString str;
693 str.AppendInt(aNewPos);
695 if (aIsSmooth) {
696 aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, NS_LITERAL_STRING("true"), false);
697 }
698 aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, str, aNotify);
699 if (aIsSmooth) {
700 aScrollbar->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false);
701 }
702 }
704 // Use this function when you want to set the scroll position via the position
705 // of the scrollbar thumb, e.g. when dragging the slider. This function scrolls
706 // the content in such a way that thumbRect.x/.y becomes aNewThumbPos.
707 void
708 nsSliderFrame::SetCurrentThumbPosition(nsIContent* aScrollbar, nscoord aNewThumbPos,
709 bool aIsSmooth, bool aMaySnap)
710 {
711 nsRect crect;
712 GetClientRect(crect);
713 nscoord offset = IsHorizontal() ? crect.x : crect.y;
714 int32_t newPos = NSToIntRound((aNewThumbPos - offset) / mRatio);
716 if (aMaySnap && mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::snap,
717 nsGkAtoms::_true, eCaseMatters)) {
718 // If snap="true", then the slider may only be set to min + (increment * x).
719 // Otherwise, the slider may be set to any positive integer.
720 int32_t increment = GetIncrement(aScrollbar);
721 newPos = NSToIntRound(newPos / float(increment)) * increment;
722 }
724 SetCurrentPosition(aScrollbar, newPos, aIsSmooth);
725 }
727 // Use this function when you know the target scroll position of the scrolled content.
728 // aNewPos should be passed to this function as a position as if the minpos is 0.
729 // That is, the minpos will be added to the position by this function. In a reverse
730 // direction slider, the newpos should be the distance from the end.
731 void
732 nsSliderFrame::SetCurrentPosition(nsIContent* aScrollbar, int32_t aNewPos,
733 bool aIsSmooth)
734 {
735 // get min and max position from our content node
736 int32_t minpos = GetMinPosition(aScrollbar);
737 int32_t maxpos = GetMaxPosition(aScrollbar);
739 // in reverse direction sliders, flip the value so that it goes from
740 // right to left, or bottom to top.
741 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
742 nsGkAtoms::reverse, eCaseMatters))
743 aNewPos = maxpos - aNewPos;
744 else
745 aNewPos += minpos;
747 // get the new position and make sure it is in bounds
748 if (aNewPos < minpos || maxpos < minpos)
749 aNewPos = minpos;
750 else if (aNewPos > maxpos)
751 aNewPos = maxpos;
753 SetCurrentPositionInternal(aScrollbar, aNewPos, aIsSmooth);
754 }
756 void
757 nsSliderFrame::SetCurrentPositionInternal(nsIContent* aScrollbar, int32_t aNewPos,
758 bool aIsSmooth)
759 {
760 nsCOMPtr<nsIContent> scrollbar = aScrollbar;
761 nsIFrame* scrollbarBox = GetScrollbar();
763 mUserChanged = true;
765 nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
766 if (scrollbarFrame) {
767 // See if we have a mediator.
768 nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
769 if (mediator) {
770 nsRefPtr<nsPresContext> context = PresContext();
771 nsCOMPtr<nsIContent> content = GetContent();
772 mediator->PositionChanged(scrollbarFrame, GetCurrentPosition(scrollbar), aNewPos);
773 // 'mediator' might be dangling now...
774 UpdateAttribute(scrollbar, aNewPos, false, aIsSmooth);
775 nsIFrame* frame = content->GetPrimaryFrame();
776 if (frame && frame->GetType() == nsGkAtoms::sliderFrame) {
777 static_cast<nsSliderFrame*>(frame)->CurrentPositionChanged();
778 }
779 mUserChanged = false;
780 return;
781 }
782 }
784 UpdateAttribute(scrollbar, aNewPos, true, aIsSmooth);
785 mUserChanged = false;
787 #ifdef DEBUG_SLIDER
788 printf("Current Pos=%d\n",aNewPos);
789 #endif
791 }
793 nsIAtom*
794 nsSliderFrame::GetType() const
795 {
796 return nsGkAtoms::sliderFrame;
797 }
799 nsresult
800 nsSliderFrame::SetInitialChildList(ChildListID aListID,
801 nsFrameList& aChildList)
802 {
803 nsresult r = nsBoxFrame::SetInitialChildList(aListID, aChildList);
805 AddListener();
807 return r;
808 }
810 nsresult
811 nsSliderMediator::HandleEvent(nsIDOMEvent* aEvent)
812 {
813 // Only process the event if the thumb is not being dragged.
814 if (mSlider && !mSlider->isDraggingThumb())
815 return mSlider->StartDrag(aEvent);
817 return NS_OK;
818 }
820 nsresult
821 nsSliderFrame::StartDrag(nsIDOMEvent* aEvent)
822 {
823 #ifdef DEBUG_SLIDER
824 printf("Begin dragging\n");
825 #endif
826 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
827 nsGkAtoms::_true, eCaseMatters))
828 return NS_OK;
830 WidgetGUIEvent* event = aEvent->GetInternalNSEvent()->AsGUIEvent();
832 if (!ShouldScrollForEvent(event)) {
833 return NS_OK;
834 }
836 nsPoint pt;
837 if (!GetEventPoint(event, pt)) {
838 return NS_OK;
839 }
840 bool isHorizontal = IsHorizontal();
841 nscoord pos = isHorizontal ? pt.x : pt.y;
843 // If we should scroll-to-click, first place the middle of the slider thumb
844 // under the mouse.
845 nsCOMPtr<nsIContent> scrollbar;
846 nscoord newpos = pos;
847 bool scrollToClick = ShouldScrollToClickForEvent(event);
848 if (scrollToClick) {
849 // adjust so that the middle of the thumb is placed under the click
850 nsIFrame* thumbFrame = mFrames.FirstChild();
851 if (!thumbFrame) {
852 return NS_OK;
853 }
854 nsSize thumbSize = thumbFrame->GetSize();
855 nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height;
857 newpos -= (thumbLength/2);
859 nsIFrame* scrollbarBox = GetScrollbar();
860 scrollbar = GetContentOfBox(scrollbarBox);
861 }
863 DragThumb(true);
865 if (scrollToClick) {
866 // should aMaySnap be true here?
867 SetCurrentThumbPosition(scrollbar, newpos, false, false);
868 }
870 nsIFrame* thumbFrame = mFrames.FirstChild();
871 if (!thumbFrame) {
872 return NS_OK;
873 }
875 if (isHorizontal)
876 mThumbStart = thumbFrame->GetPosition().x;
877 else
878 mThumbStart = thumbFrame->GetPosition().y;
880 mDragStart = pos - mThumbStart;
882 #ifdef DEBUG_SLIDER
883 printf("Pressed mDragStart=%d\n",mDragStart);
884 #endif
886 return NS_OK;
887 }
889 void
890 nsSliderFrame::DragThumb(bool aGrabMouseEvents)
891 {
892 // inform the parent <scale> that a drag is beginning or ending
893 nsIFrame* parent = GetParent();
894 if (parent) {
895 nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
896 if (sliderListener) {
897 nsContentUtils::AddScriptRunner(
898 new nsDragStateChangedRunnable(sliderListener, aGrabMouseEvents));
899 }
900 }
902 nsIPresShell::SetCapturingContent(aGrabMouseEvents ? GetContent() : nullptr,
903 aGrabMouseEvents ? CAPTURE_IGNOREALLOWED : 0);
904 }
906 bool
907 nsSliderFrame::isDraggingThumb()
908 {
909 return (nsIPresShell::GetCapturingContent() == GetContent());
910 }
912 void
913 nsSliderFrame::AddListener()
914 {
915 if (!mMediator) {
916 mMediator = new nsSliderMediator(this);
917 }
919 nsIFrame* thumbFrame = mFrames.FirstChild();
920 if (!thumbFrame) {
921 return;
922 }
923 thumbFrame->GetContent()->
924 AddSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator,
925 false, false);
926 thumbFrame->GetContent()->
927 AddSystemEventListener(NS_LITERAL_STRING("touchstart"), mMediator,
928 false, false);
929 }
931 void
932 nsSliderFrame::RemoveListener()
933 {
934 NS_ASSERTION(mMediator, "No listener was ever added!!");
936 nsIFrame* thumbFrame = mFrames.FirstChild();
937 if (!thumbFrame)
938 return;
940 thumbFrame->GetContent()->
941 RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator, false);
942 }
944 bool
945 nsSliderFrame::ShouldScrollForEvent(WidgetGUIEvent* aEvent)
946 {
947 switch (aEvent->message) {
948 case NS_TOUCH_START:
949 case NS_TOUCH_END:
950 return true;
951 case NS_MOUSE_BUTTON_DOWN:
952 case NS_MOUSE_BUTTON_UP: {
953 uint16_t button = aEvent->AsMouseEvent()->button;
954 return (button == WidgetMouseEvent::eLeftButton) ||
955 (button == WidgetMouseEvent::eMiddleButton && gMiddlePref);
956 }
957 default:
958 return false;
959 }
960 }
962 bool
963 nsSliderFrame::ShouldScrollToClickForEvent(WidgetGUIEvent* aEvent)
964 {
965 if (!ShouldScrollForEvent(aEvent)) {
966 return false;
967 }
969 if (aEvent->message == NS_TOUCH_START) {
970 return GetScrollToClick();
971 }
973 if (aEvent->message != NS_MOUSE_BUTTON_DOWN) {
974 return false;
975 }
977 #ifdef XP_MACOSX
978 // On Mac, clicking the scrollbar thumb should never scroll to click.
979 if (IsEventOverThumb(aEvent)) {
980 return false;
981 }
982 #endif
984 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
985 if (mouseEvent->button == WidgetMouseEvent::eLeftButton) {
986 #ifdef XP_MACOSX
987 bool invertPref = mouseEvent->IsAlt();
988 #else
989 bool invertPref = mouseEvent->IsShift();
990 #endif
991 return GetScrollToClick() != invertPref;
992 }
994 return true;
995 }
997 bool
998 nsSliderFrame::IsEventOverThumb(WidgetGUIEvent* aEvent)
999 {
1000 nsIFrame* thumbFrame = mFrames.FirstChild();
1001 if (!thumbFrame) {
1002 return false;
1003 }
1005 nsPoint eventPoint;
1006 if (!GetEventPoint(aEvent, eventPoint)) {
1007 return false;
1008 }
1010 bool isHorizontal = IsHorizontal();
1011 nsRect thumbRect = thumbFrame->GetRect();
1012 nscoord eventPos = isHorizontal ? eventPoint.x : eventPoint.y;
1013 nscoord thumbStart = isHorizontal ? thumbRect.x : thumbRect.y;
1014 nscoord thumbEnd = isHorizontal ? thumbRect.XMost() : thumbRect.YMost();
1016 return eventPos >= thumbStart && eventPos < thumbEnd;
1017 }
1019 NS_IMETHODIMP
1020 nsSliderFrame::HandlePress(nsPresContext* aPresContext,
1021 WidgetGUIEvent* aEvent,
1022 nsEventStatus* aEventStatus)
1023 {
1024 if (!ShouldScrollForEvent(aEvent) || ShouldScrollToClickForEvent(aEvent)) {
1025 return NS_OK;
1026 }
1028 if (IsEventOverThumb(aEvent)) {
1029 return NS_OK;
1030 }
1032 nsIFrame* thumbFrame = mFrames.FirstChild();
1033 if (!thumbFrame) // display:none?
1034 return NS_OK;
1036 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
1037 nsGkAtoms::_true, eCaseMatters))
1038 return NS_OK;
1040 nsRect thumbRect = thumbFrame->GetRect();
1042 nscoord change = 1;
1043 nsPoint eventPoint;
1044 if (!GetEventPoint(aEvent, eventPoint)) {
1045 return NS_OK;
1046 }
1047 if (IsHorizontal() ? eventPoint.x < thumbRect.x
1048 : eventPoint.y < thumbRect.y)
1049 change = -1;
1051 mChange = change;
1052 DragThumb(true);
1053 mDestinationPoint = eventPoint;
1054 StartRepeat();
1055 PageUpDown(change);
1056 return NS_OK;
1057 }
1059 NS_IMETHODIMP
1060 nsSliderFrame::HandleRelease(nsPresContext* aPresContext,
1061 WidgetGUIEvent* aEvent,
1062 nsEventStatus* aEventStatus)
1063 {
1064 StopRepeat();
1066 return NS_OK;
1067 }
1069 void
1070 nsSliderFrame::DestroyFrom(nsIFrame* aDestructRoot)
1071 {
1072 // tell our mediator if we have one we are gone.
1073 if (mMediator) {
1074 mMediator->SetSlider(nullptr);
1075 mMediator = nullptr;
1076 }
1077 StopRepeat();
1079 // call base class Destroy()
1080 nsBoxFrame::DestroyFrom(aDestructRoot);
1081 }
1083 nsSize
1084 nsSliderFrame::GetPrefSize(nsBoxLayoutState& aState)
1085 {
1086 EnsureOrient();
1087 return nsBoxFrame::GetPrefSize(aState);
1088 }
1090 nsSize
1091 nsSliderFrame::GetMinSize(nsBoxLayoutState& aState)
1092 {
1093 EnsureOrient();
1095 // our min size is just our borders and padding
1096 return nsBox::GetMinSize(aState);
1097 }
1099 nsSize
1100 nsSliderFrame::GetMaxSize(nsBoxLayoutState& aState)
1101 {
1102 EnsureOrient();
1103 return nsBoxFrame::GetMaxSize(aState);
1104 }
1106 void
1107 nsSliderFrame::EnsureOrient()
1108 {
1109 nsIFrame* scrollbarBox = GetScrollbar();
1111 bool isHorizontal = (scrollbarBox->GetStateBits() & NS_STATE_IS_HORIZONTAL) != 0;
1112 if (isHorizontal)
1113 mState |= NS_STATE_IS_HORIZONTAL;
1114 else
1115 mState &= ~NS_STATE_IS_HORIZONTAL;
1116 }
1119 void nsSliderFrame::Notify(void)
1120 {
1121 bool stop = false;
1123 nsIFrame* thumbFrame = mFrames.FirstChild();
1124 if (!thumbFrame) {
1125 StopRepeat();
1126 return;
1127 }
1128 nsRect thumbRect = thumbFrame->GetRect();
1130 bool isHorizontal = IsHorizontal();
1132 // See if the thumb has moved past our destination point.
1133 // if it has we want to stop.
1134 if (isHorizontal) {
1135 if (mChange < 0) {
1136 if (thumbRect.x < mDestinationPoint.x)
1137 stop = true;
1138 } else {
1139 if (thumbRect.x + thumbRect.width > mDestinationPoint.x)
1140 stop = true;
1141 }
1142 } else {
1143 if (mChange < 0) {
1144 if (thumbRect.y < mDestinationPoint.y)
1145 stop = true;
1146 } else {
1147 if (thumbRect.y + thumbRect.height > mDestinationPoint.y)
1148 stop = true;
1149 }
1150 }
1153 if (stop) {
1154 StopRepeat();
1155 } else {
1156 PageUpDown(mChange);
1157 }
1158 }
1160 NS_IMPL_ISUPPORTS(nsSliderMediator,
1161 nsIDOMEventListener)