Coverage Report

Created: 2018-09-25 14:53

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