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