/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) |