Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsRangeFrame.h"
8 #include "mozilla/EventStates.h"
9 #include "mozilla/TouchEvents.h"
11 #include "nsContentCreatorFunctions.h"
12 #include "nsContentList.h"
13 #include "nsContentUtils.h"
14 #include "nsCSSRendering.h"
15 #include "nsFormControlFrame.h"
16 #include "nsIContent.h"
17 #include "nsIDocument.h"
18 #include "nsNameSpaceManager.h"
19 #include "nsINodeInfo.h"
20 #include "nsIPresShell.h"
21 #include "nsGkAtoms.h"
22 #include "mozilla/dom/HTMLInputElement.h"
23 #include "nsPresContext.h"
24 #include "nsNodeInfoManager.h"
25 #include "nsRenderingContext.h"
26 #include "mozilla/dom/Element.h"
27 #include "nsStyleSet.h"
28 #include "nsThemeConstants.h"
30 #ifdef ACCESSIBILITY
31 #include "nsAccessibilityService.h"
32 #endif
34 #define LONG_SIDE_TO_SHORT_SIDE_RATIO 10
36 using namespace mozilla;
37 using namespace mozilla::dom;
39 nsIFrame*
40 NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
41 {
42 return new (aPresShell) nsRangeFrame(aContext);
43 }
45 nsRangeFrame::nsRangeFrame(nsStyleContext* aContext)
46 : nsContainerFrame(aContext)
47 {
48 }
50 nsRangeFrame::~nsRangeFrame()
51 {
52 }
54 NS_IMPL_FRAMEARENA_HELPERS(nsRangeFrame)
56 NS_QUERYFRAME_HEAD(nsRangeFrame)
57 NS_QUERYFRAME_ENTRY(nsRangeFrame)
58 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
59 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
61 void
62 nsRangeFrame::Init(nsIContent* aContent,
63 nsIFrame* aParent,
64 nsIFrame* aPrevInFlow)
65 {
66 // B2G's AsyncPanZoomController::ReceiveInputEvent handles touch events
67 // without checking whether the out-of-process document that it controls
68 // will handle them, unless it has been told that the document might do so.
69 // This is for perf reasons, otherwise it has to wait for the event to be
70 // round-tripped to the other process and back, delaying panning, etc.
71 // We must call SetHasTouchEventListeners() in order to get APZC to wait
72 // until the event has been round-tripped and check whether it has been
73 // handled, otherwise B2G will end up panning the document when the user
74 // tries to drag our thumb.
75 //
76 nsIPresShell* presShell = PresContext()->GetPresShell();
77 if (presShell) {
78 nsIDocument* document = presShell->GetDocument();
79 if (document) {
80 nsPIDOMWindow* innerWin = document->GetInnerWindow();
81 if (innerWin) {
82 innerWin->SetHasTouchEventListeners();
83 }
84 }
85 }
87 nsStyleSet *styleSet = PresContext()->StyleSet();
89 mOuterFocusStyle =
90 styleSet->ProbePseudoElementStyle(aContent->AsElement(),
91 nsCSSPseudoElements::ePseudo_mozFocusOuter,
92 StyleContext());
94 return nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
95 }
97 void
98 nsRangeFrame::DestroyFrom(nsIFrame* aDestructRoot)
99 {
100 NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
101 "nsRangeFrame should not have continuations; if it does we "
102 "need to call RegUnregAccessKey only for the first.");
103 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
104 nsContentUtils::DestroyAnonymousContent(&mTrackDiv);
105 nsContentUtils::DestroyAnonymousContent(&mProgressDiv);
106 nsContentUtils::DestroyAnonymousContent(&mThumbDiv);
107 nsContainerFrame::DestroyFrom(aDestructRoot);
108 }
110 nsresult
111 nsRangeFrame::MakeAnonymousDiv(Element** aResult,
112 nsCSSPseudoElements::Type aPseudoType,
113 nsTArray<ContentInfo>& aElements)
114 {
115 nsCOMPtr<nsIDocument> doc = mContent->GetDocument();
116 nsRefPtr<Element> resultElement = doc->CreateHTMLElement(nsGkAtoms::div);
118 // Associate the pseudo-element with the anonymous child.
119 nsRefPtr<nsStyleContext> newStyleContext =
120 PresContext()->StyleSet()->ResolvePseudoElementStyle(mContent->AsElement(),
121 aPseudoType,
122 StyleContext(),
123 resultElement);
125 if (!aElements.AppendElement(ContentInfo(resultElement, newStyleContext))) {
126 return NS_ERROR_OUT_OF_MEMORY;
127 }
129 resultElement.forget(aResult);
130 return NS_OK;
131 }
133 nsresult
134 nsRangeFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
135 {
136 nsresult rv;
138 // Create the ::-moz-range-track pseuto-element (a div):
139 rv = MakeAnonymousDiv(getter_AddRefs(mTrackDiv),
140 nsCSSPseudoElements::ePseudo_mozRangeTrack,
141 aElements);
142 NS_ENSURE_SUCCESS(rv, rv);
144 // Create the ::-moz-range-progress pseudo-element (a div):
145 rv = MakeAnonymousDiv(getter_AddRefs(mProgressDiv),
146 nsCSSPseudoElements::ePseudo_mozRangeProgress,
147 aElements);
148 NS_ENSURE_SUCCESS(rv, rv);
150 // Create the ::-moz-range-thumb pseudo-element (a div):
151 rv = MakeAnonymousDiv(getter_AddRefs(mThumbDiv),
152 nsCSSPseudoElements::ePseudo_mozRangeThumb,
153 aElements);
154 return rv;
155 }
157 void
158 nsRangeFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
159 uint32_t aFilter)
160 {
161 aElements.MaybeAppendElement(mTrackDiv);
162 aElements.MaybeAppendElement(mProgressDiv);
163 aElements.MaybeAppendElement(mThumbDiv);
164 }
166 class nsDisplayRangeFocusRing : public nsDisplayItem
167 {
168 public:
169 nsDisplayRangeFocusRing(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
170 : nsDisplayItem(aBuilder, aFrame) {
171 MOZ_COUNT_CTOR(nsDisplayRangeFocusRing);
172 }
173 #ifdef NS_BUILD_REFCNT_LOGGING
174 virtual ~nsDisplayRangeFocusRing() {
175 MOZ_COUNT_DTOR(nsDisplayRangeFocusRing);
176 }
177 #endif
179 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) MOZ_OVERRIDE;
180 virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) MOZ_OVERRIDE;
181 NS_DISPLAY_DECL_NAME("RangeFocusRing", TYPE_OUTLINE)
182 };
184 nsRect
185 nsDisplayRangeFocusRing::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
186 {
187 *aSnap = false;
188 nsRect rect(ToReferenceFrame(), Frame()->GetSize());
190 // We want to paint as if specifying a border for ::-moz-focus-outer
191 // specifies an outline for our frame, so inflate by the border widths:
192 nsStyleContext* styleContext =
193 static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle;
194 MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null");
195 rect.Inflate(styleContext->StyleBorder()->GetComputedBorder());
197 return rect;
198 }
200 void
201 nsDisplayRangeFocusRing::Paint(nsDisplayListBuilder* aBuilder,
202 nsRenderingContext* aCtx)
203 {
204 bool unused;
205 nsStyleContext* styleContext =
206 static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle;
207 MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null");
208 nsCSSRendering::PaintBorder(mFrame->PresContext(), *aCtx, mFrame,
209 mVisibleRect, GetBounds(aBuilder, &unused),
210 styleContext);
211 }
213 void
214 nsRangeFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
215 const nsRect& aDirtyRect,
216 const nsDisplayListSet& aLists)
217 {
218 if (IsThemed()) {
219 DisplayBorderBackgroundOutline(aBuilder, aLists);
220 // Only create items for the thumb. Specifically, we do not want
221 // the track to paint, since *our* background is used to paint
222 // the track, and we don't want the unthemed track painting over
223 // the top of the themed track.
224 // This logic is copied from
225 // nsContainerFrame::BuildDisplayListForNonBlockChildren as
226 // called by BuildDisplayListForInline.
227 nsIFrame* thumb = mThumbDiv->GetPrimaryFrame();
228 if (thumb) {
229 nsDisplayListSet set(aLists, aLists.Content());
230 BuildDisplayListForChild(aBuilder, thumb, aDirtyRect, set, DISPLAY_CHILD_INLINE);
231 }
232 } else {
233 BuildDisplayListForInline(aBuilder, aDirtyRect, aLists);
234 }
236 // Draw a focus outline if appropriate:
238 if (!aBuilder->IsForPainting() ||
239 !IsVisibleForPainting(aBuilder)) {
240 // we don't want the focus ring item for hit-testing or if the item isn't
241 // in the area being [re]painted
242 return;
243 }
245 EventStates eventStates = mContent->AsElement()->State();
246 if (eventStates.HasState(NS_EVENT_STATE_DISABLED) ||
247 !eventStates.HasState(NS_EVENT_STATE_FOCUSRING)) {
248 return; // can't have focus or doesn't match :-moz-focusring
249 }
251 if (!mOuterFocusStyle ||
252 !mOuterFocusStyle->StyleBorder()->HasBorder()) {
253 // no ::-moz-focus-outer specified border (how style specifies a focus ring
254 // for range)
255 return;
256 }
258 const nsStyleDisplay *disp = StyleDisplay();
259 if (IsThemed(disp) &&
260 PresContext()->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) {
261 return; // the native theme displays its own visual indication of focus
262 }
264 aLists.Content()->AppendNewToTop(
265 new (aBuilder) nsDisplayRangeFocusRing(aBuilder, this));
266 }
268 nsresult
269 nsRangeFrame::Reflow(nsPresContext* aPresContext,
270 nsHTMLReflowMetrics& aDesiredSize,
271 const nsHTMLReflowState& aReflowState,
272 nsReflowStatus& aStatus)
273 {
274 DO_GLOBAL_REFLOW_COUNT("nsRangeFrame");
275 DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
277 NS_ASSERTION(mTrackDiv, "::-moz-range-track div must exist!");
278 NS_ASSERTION(mProgressDiv, "::-moz-range-progress div must exist!");
279 NS_ASSERTION(mThumbDiv, "::-moz-range-thumb div must exist!");
280 NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
281 "nsRangeFrame should not have continuations; if it does we "
282 "need to call RegUnregAccessKey only for the first.");
284 if (mState & NS_FRAME_FIRST_REFLOW) {
285 nsFormControlFrame::RegUnRegAccessKey(this, true);
286 }
288 nscoord computedHeight = aReflowState.ComputedHeight();
289 if (computedHeight == NS_AUTOHEIGHT) {
290 computedHeight = 0;
291 }
292 aDesiredSize.Width() = aReflowState.ComputedWidth() +
293 aReflowState.ComputedPhysicalBorderPadding().LeftRight();
294 aDesiredSize.Height() = computedHeight +
295 aReflowState.ComputedPhysicalBorderPadding().TopBottom();
297 nsresult rv =
298 ReflowAnonymousContent(aPresContext, aDesiredSize, aReflowState);
299 NS_ENSURE_SUCCESS(rv, rv);
301 aDesiredSize.SetOverflowAreasToDesiredBounds();
303 nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
304 if (trackFrame) {
305 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, trackFrame);
306 }
308 nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
309 if (rangeProgressFrame) {
310 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rangeProgressFrame);
311 }
313 nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
314 if (thumbFrame) {
315 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, thumbFrame);
316 }
318 FinishAndStoreOverflow(&aDesiredSize);
320 aStatus = NS_FRAME_COMPLETE;
322 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
324 return NS_OK;
325 }
327 nsresult
328 nsRangeFrame::ReflowAnonymousContent(nsPresContext* aPresContext,
329 nsHTMLReflowMetrics& aDesiredSize,
330 const nsHTMLReflowState& aReflowState)
331 {
332 // The width/height of our content box, which is the available width/height
333 // for our anonymous content:
334 nscoord rangeFrameContentBoxWidth = aReflowState.ComputedWidth();
335 nscoord rangeFrameContentBoxHeight = aReflowState.ComputedHeight();
336 if (rangeFrameContentBoxHeight == NS_AUTOHEIGHT) {
337 rangeFrameContentBoxHeight = 0;
338 }
340 nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
342 if (trackFrame) { // display:none?
344 // Position the track:
345 // The idea here is that we allow content authors to style the width,
346 // height, border and padding of the track, but we ignore margin and
347 // positioning properties and do the positioning ourself to keep the center
348 // of the track's border box on the center of the nsRangeFrame's content
349 // box.
351 nsHTMLReflowState trackReflowState(aPresContext, aReflowState, trackFrame,
352 nsSize(aReflowState.ComputedWidth(),
353 NS_UNCONSTRAINEDSIZE));
355 // Find the x/y position of the track frame such that it will be positioned
356 // as described above. These coordinates are with respect to the
357 // nsRangeFrame's border-box.
358 nscoord trackX = rangeFrameContentBoxWidth / 2;
359 nscoord trackY = rangeFrameContentBoxHeight / 2;
361 // Account for the track's border and padding (we ignore its margin):
362 trackX -= trackReflowState.ComputedPhysicalBorderPadding().left +
363 trackReflowState.ComputedWidth() / 2;
364 trackY -= trackReflowState.ComputedPhysicalBorderPadding().top +
365 trackReflowState.ComputedHeight() / 2;
367 // Make relative to our border box instead of our content box:
368 trackX += aReflowState.ComputedPhysicalBorderPadding().left;
369 trackY += aReflowState.ComputedPhysicalBorderPadding().top;
371 nsReflowStatus frameStatus;
372 nsHTMLReflowMetrics trackDesiredSize(aReflowState);
373 nsresult rv = ReflowChild(trackFrame, aPresContext, trackDesiredSize,
374 trackReflowState, trackX, trackY, 0, frameStatus);
375 NS_ENSURE_SUCCESS(rv, rv);
376 MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus),
377 "We gave our child unconstrained height, so it should be complete");
378 rv = FinishReflowChild(trackFrame, aPresContext, trackDesiredSize,
379 &trackReflowState, trackX, trackY, 0);
380 NS_ENSURE_SUCCESS(rv, rv);
381 }
383 nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
385 if (thumbFrame) { // display:none?
386 nsHTMLReflowState thumbReflowState(aPresContext, aReflowState, thumbFrame,
387 nsSize(aReflowState.ComputedWidth(),
388 NS_UNCONSTRAINEDSIZE));
390 // Where we position the thumb depends on its size, so we first reflow
391 // the thumb at {0,0} to obtain its size, then position it afterwards.
393 nsReflowStatus frameStatus;
394 nsHTMLReflowMetrics thumbDesiredSize(aReflowState);
395 nsresult rv = ReflowChild(thumbFrame, aPresContext, thumbDesiredSize,
396 thumbReflowState, 0, 0, 0, frameStatus);
397 NS_ENSURE_SUCCESS(rv, rv);
398 MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus),
399 "We gave our child unconstrained height, so it should be complete");
400 rv = FinishReflowChild(thumbFrame, aPresContext, thumbDesiredSize,
401 &thumbReflowState, 0, 0, 0);
402 NS_ENSURE_SUCCESS(rv, rv);
404 DoUpdateThumbPosition(thumbFrame, nsSize(aDesiredSize.Width(),
405 aDesiredSize.Height()));
406 }
408 nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
410 if (rangeProgressFrame) { // display:none?
411 nsHTMLReflowState progressReflowState(aPresContext, aReflowState,
412 rangeProgressFrame,
413 nsSize(aReflowState.ComputedWidth(),
414 NS_UNCONSTRAINEDSIZE));
416 // We first reflow the range-progress frame at {0,0} to obtain its
417 // unadjusted dimensions, then we adjust it to so that the appropriate edge
418 // ends at the thumb.
420 nsReflowStatus frameStatus;
421 nsHTMLReflowMetrics progressDesiredSize(aReflowState);
422 nsresult rv = ReflowChild(rangeProgressFrame, aPresContext,
423 progressDesiredSize, progressReflowState, 0, 0,
424 0, frameStatus);
425 NS_ENSURE_SUCCESS(rv, rv);
426 MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus),
427 "We gave our child unconstrained height, so it should be complete");
428 rv = FinishReflowChild(rangeProgressFrame, aPresContext,
429 progressDesiredSize, &progressReflowState, 0, 0, 0);
430 NS_ENSURE_SUCCESS(rv, rv);
432 DoUpdateRangeProgressFrame(rangeProgressFrame, nsSize(aDesiredSize.Width(),
433 aDesiredSize.Height()));
434 }
436 return NS_OK;
437 }
439 #ifdef ACCESSIBILITY
440 a11y::AccType
441 nsRangeFrame::AccessibleType()
442 {
443 return a11y::eHTMLRangeType;
444 }
445 #endif
447 double
448 nsRangeFrame::GetValueAsFractionOfRange()
449 {
450 MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast");
451 dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent);
453 MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE);
455 Decimal value = input->GetValueAsDecimal();
456 Decimal minimum = input->GetMinimum();
457 Decimal maximum = input->GetMaximum();
459 MOZ_ASSERT(value.isFinite() && minimum.isFinite() && maximum.isFinite(),
460 "type=range should have a default maximum/minimum");
462 if (maximum <= minimum) {
463 MOZ_ASSERT(value == minimum, "Unsanitized value");
464 return 0.0;
465 }
467 MOZ_ASSERT(value >= minimum && value <= maximum, "Unsanitized value");
469 return ((value - minimum) / (maximum - minimum)).toDouble();
470 }
472 Decimal
473 nsRangeFrame::GetValueAtEventPoint(WidgetGUIEvent* aEvent)
474 {
475 MOZ_ASSERT(aEvent->eventStructType == NS_MOUSE_EVENT ||
476 aEvent->eventStructType == NS_TOUCH_EVENT,
477 "Unexpected event type - aEvent->refPoint may be meaningless");
479 MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast");
480 dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent);
482 MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE);
484 Decimal minimum = input->GetMinimum();
485 Decimal maximum = input->GetMaximum();
486 MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
487 "type=range should have a default maximum/minimum");
488 if (maximum <= minimum) {
489 return minimum;
490 }
491 Decimal range = maximum - minimum;
493 LayoutDeviceIntPoint absPoint;
494 if (aEvent->eventStructType == NS_TOUCH_EVENT) {
495 MOZ_ASSERT(aEvent->AsTouchEvent()->touches.Length() == 1,
496 "Unexpected number of touches");
497 absPoint = LayoutDeviceIntPoint::FromUntyped(
498 aEvent->AsTouchEvent()->touches[0]->mRefPoint);
499 } else {
500 absPoint = aEvent->refPoint;
501 }
502 nsPoint point =
503 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
504 LayoutDeviceIntPoint::ToUntyped(absPoint), this);
506 if (point == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
507 // We don't want to change the current value for this error state.
508 return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal();
509 }
511 nsRect rangeContentRect = GetContentRectRelativeToSelf();
512 nsSize thumbSize;
514 if (IsThemed()) {
515 // We need to get the size of the thumb from the theme.
516 nsPresContext *presContext = PresContext();
517 nsRefPtr<nsRenderingContext> tmpCtx =
518 presContext->PresShell()->CreateReferenceRenderingContext();
519 bool notUsedCanOverride;
520 nsIntSize size;
521 presContext->GetTheme()->
522 GetMinimumWidgetSize(tmpCtx.get(), this, NS_THEME_RANGE_THUMB, &size,
523 ¬UsedCanOverride);
524 thumbSize.width = presContext->DevPixelsToAppUnits(size.width);
525 thumbSize.height = presContext->DevPixelsToAppUnits(size.height);
526 MOZ_ASSERT(thumbSize.width > 0 && thumbSize.height > 0);
527 } else {
528 nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
529 if (thumbFrame) { // diplay:none?
530 thumbSize = thumbFrame->GetSize();
531 }
532 }
534 Decimal fraction;
535 if (IsHorizontal()) {
536 nscoord traversableDistance = rangeContentRect.width - thumbSize.width;
537 if (traversableDistance <= 0) {
538 return minimum;
539 }
540 nscoord posAtStart = rangeContentRect.x + thumbSize.width/2;
541 nscoord posAtEnd = posAtStart + traversableDistance;
542 nscoord posOfPoint = mozilla::clamped(point.x, posAtStart, posAtEnd);
543 fraction = Decimal(posOfPoint - posAtStart) / traversableDistance;
544 if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
545 fraction = Decimal(1) - fraction;
546 }
547 } else {
548 nscoord traversableDistance = rangeContentRect.height - thumbSize.height;
549 if (traversableDistance <= 0) {
550 return minimum;
551 }
552 nscoord posAtStart = rangeContentRect.y + thumbSize.height/2;
553 nscoord posAtEnd = posAtStart + traversableDistance;
554 nscoord posOfPoint = mozilla::clamped(point.y, posAtStart, posAtEnd);
555 // For a vertical range, the top (posAtStart) is the highest value, so we
556 // subtract the fraction from 1.0 to get that polarity correct.
557 fraction = Decimal(1) - Decimal(posOfPoint - posAtStart) / traversableDistance;
558 }
560 MOZ_ASSERT(fraction >= 0 && fraction <= 1);
561 return minimum + fraction * range;
562 }
564 void
565 nsRangeFrame::UpdateForValueChange()
566 {
567 if (NS_SUBTREE_DIRTY(this)) {
568 return; // we're going to be updated when we reflow
569 }
570 nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
571 nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
572 if (!rangeProgressFrame && !thumbFrame) {
573 return; // diplay:none?
574 }
575 if (rangeProgressFrame) {
576 DoUpdateRangeProgressFrame(rangeProgressFrame, GetSize());
577 }
578 if (thumbFrame) {
579 DoUpdateThumbPosition(thumbFrame, GetSize());
580 }
581 if (IsThemed()) {
582 // We don't know the exact dimensions or location of the thumb when native
583 // theming is applied, so we just repaint the entire range.
584 InvalidateFrame();
585 }
587 #ifdef ACCESSIBILITY
588 nsAccessibilityService* accService = nsIPresShell::AccService();
589 if (accService) {
590 accService->RangeValueChanged(PresContext()->PresShell(), mContent);
591 }
592 #endif
594 SchedulePaint();
595 }
597 void
598 nsRangeFrame::DoUpdateThumbPosition(nsIFrame* aThumbFrame,
599 const nsSize& aRangeSize)
600 {
601 MOZ_ASSERT(aThumbFrame);
603 // The idea here is that we want to position the thumb so that the center
604 // of the thumb is on an imaginary line drawn from the middle of one edge
605 // of the range frame's content box to the middle of the opposite edge of
606 // its content box (the opposite edges being the left/right edge if the
607 // range is horizontal, or else the top/bottom edges if the range is
608 // vertical). How far along this line the center of the thumb is placed
609 // depends on the value of the range.
611 nsMargin borderAndPadding = GetUsedBorderAndPadding();
612 nsPoint newPosition(borderAndPadding.left, borderAndPadding.top);
614 nsSize rangeContentBoxSize(aRangeSize);
615 rangeContentBoxSize.width -= borderAndPadding.LeftRight();
616 rangeContentBoxSize.height -= borderAndPadding.TopBottom();
618 nsSize thumbSize = aThumbFrame->GetSize();
619 double fraction = GetValueAsFractionOfRange();
620 MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
622 // We are called under Reflow, so we need to pass IsHorizontal a valid rect.
623 nsSize frameSizeOverride(aRangeSize.width, aRangeSize.height);
624 if (IsHorizontal(&frameSizeOverride)) {
625 if (thumbSize.width < rangeContentBoxSize.width) {
626 nscoord traversableDistance =
627 rangeContentBoxSize.width - thumbSize.width;
628 if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
629 newPosition.x += NSToCoordRound((1.0 - fraction) * traversableDistance);
630 } else {
631 newPosition.x += NSToCoordRound(fraction * traversableDistance);
632 }
633 newPosition.y += (rangeContentBoxSize.height - thumbSize.height)/2;
634 }
635 } else {
636 if (thumbSize.height < rangeContentBoxSize.height) {
637 nscoord traversableDistance =
638 rangeContentBoxSize.height - thumbSize.height;
639 newPosition.x += (rangeContentBoxSize.width - thumbSize.width)/2;
640 newPosition.y += NSToCoordRound((1.0 - fraction) * traversableDistance);
641 }
642 }
643 aThumbFrame->SetPosition(newPosition);
644 }
646 void
647 nsRangeFrame::DoUpdateRangeProgressFrame(nsIFrame* aRangeProgressFrame,
648 const nsSize& aRangeSize)
649 {
650 MOZ_ASSERT(aRangeProgressFrame);
652 // The idea here is that we want to position the ::-moz-range-progress
653 // pseudo-element so that the center line running along its length is on the
654 // corresponding center line of the nsRangeFrame's content box. In the other
655 // dimension, we align the "start" edge of the ::-moz-range-progress
656 // pseudo-element's border-box with the corresponding edge of the
657 // nsRangeFrame's content box, and we size the progress element's border-box
658 // to have a length of GetValueAsFractionOfRange() times the nsRangeFrame's
659 // content-box size.
661 nsMargin borderAndPadding = GetUsedBorderAndPadding();
662 nsSize progSize = aRangeProgressFrame->GetSize();
663 nsRect progRect(borderAndPadding.left, borderAndPadding.top,
664 progSize.width, progSize.height);
666 nsSize rangeContentBoxSize(aRangeSize);
667 rangeContentBoxSize.width -= borderAndPadding.LeftRight();
668 rangeContentBoxSize.height -= borderAndPadding.TopBottom();
670 double fraction = GetValueAsFractionOfRange();
671 MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
673 // We are called under Reflow, so we need to pass IsHorizontal a valid rect.
674 nsSize frameSizeOverride(aRangeSize.width, aRangeSize.height);
675 if (IsHorizontal(&frameSizeOverride)) {
676 nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.width);
677 if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
678 progRect.x += rangeContentBoxSize.width - progLength;
679 }
680 progRect.y += (rangeContentBoxSize.height - progSize.height)/2;
681 progRect.width = progLength;
682 } else {
683 nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.height);
684 progRect.x += (rangeContentBoxSize.width - progSize.width)/2;
685 progRect.y += rangeContentBoxSize.height - progLength;
686 progRect.height = progLength;
687 }
688 aRangeProgressFrame->SetRect(progRect);
689 }
691 nsresult
692 nsRangeFrame::AttributeChanged(int32_t aNameSpaceID,
693 nsIAtom* aAttribute,
694 int32_t aModType)
695 {
696 NS_ASSERTION(mTrackDiv, "The track div must exist!");
697 NS_ASSERTION(mThumbDiv, "The thumb div must exist!");
699 if (aNameSpaceID == kNameSpaceID_None) {
700 if (aAttribute == nsGkAtoms::value ||
701 aAttribute == nsGkAtoms::min ||
702 aAttribute == nsGkAtoms::max ||
703 aAttribute == nsGkAtoms::step) {
704 // We want to update the position of the thumb, except in one special
705 // case: If the value attribute is being set, it is possible that we are
706 // in the middle of a type change away from type=range, under the
707 // SetAttr(..., nsGkAtoms::value, ...) call in HTMLInputElement::
708 // HandleTypeChange. In that case the HTMLInputElement's type will
709 // already have changed, and if we call UpdateForValueChange()
710 // we'll fail the asserts under that call that check the type of our
711 // HTMLInputElement. Given that we're changing away from being a range
712 // and this frame will shortly be destroyed, there's no point in calling
713 // UpdateForValueChange() anyway.
714 MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast");
715 bool typeIsRange = static_cast<dom::HTMLInputElement*>(mContent)->GetType() ==
716 NS_FORM_INPUT_RANGE;
717 // If script changed the <input>'s type before setting these attributes
718 // then we don't need to do anything since we are going to be reframed.
719 if (typeIsRange) {
720 UpdateForValueChange();
721 }
722 } else if (aAttribute == nsGkAtoms::orient) {
723 PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eResize,
724 NS_FRAME_IS_DIRTY);
725 }
726 }
728 return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
729 }
731 nsSize
732 nsRangeFrame::ComputeAutoSize(nsRenderingContext *aRenderingContext,
733 nsSize aCBSize, nscoord aAvailableWidth,
734 nsSize aMargin, nsSize aBorder,
735 nsSize aPadding, bool aShrinkWrap)
736 {
737 nscoord oneEm = NSToCoordRound(StyleFont()->mFont.size *
738 nsLayoutUtils::FontSizeInflationFor(this)); // 1em
740 // frameSizeOverride values just gets us to fall back to being horizontal
741 // (the actual values are irrelevant, as long as width > height):
742 nsSize frameSizeOverride(10,1);
743 bool isHorizontal = IsHorizontal(&frameSizeOverride);
745 nsSize autoSize;
747 // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
748 // given too small a size when we're natively themed. If we're themed, we set
749 // our "thickness" dimension to zero below and rely on that
750 // GetMinimumWidgetSize check to correct that dimension to the natural
751 // thickness of a slider in the current theme.
753 if (isHorizontal) {
754 autoSize.width = LONG_SIDE_TO_SHORT_SIDE_RATIO * oneEm;
755 autoSize.height = IsThemed() ? 0 : oneEm;
756 } else {
757 autoSize.width = IsThemed() ? 0 : oneEm;
758 autoSize.height = LONG_SIDE_TO_SHORT_SIDE_RATIO * oneEm;
759 }
761 return autoSize;
762 }
764 nscoord
765 nsRangeFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
766 {
767 // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
768 // given too small a size when we're natively themed. If we aren't native
769 // themed, we don't mind how small we're sized.
770 return nscoord(0);
771 }
773 nscoord
774 nsRangeFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
775 {
776 // frameSizeOverride values just gets us to fall back to being horizontal:
777 nsSize frameSizeOverride(10,1);
778 bool isHorizontal = IsHorizontal(&frameSizeOverride);
780 if (!isHorizontal && IsThemed()) {
781 // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
782 // given too small a size when we're natively themed. We return zero and
783 // depend on that correction to get our "natuaral" width when we're a
784 // vertical slider.
785 return 0;
786 }
788 nscoord prefWidth = NSToCoordRound(StyleFont()->mFont.size *
789 nsLayoutUtils::FontSizeInflationFor(this)); // 1em
791 if (isHorizontal) {
792 prefWidth *= LONG_SIDE_TO_SHORT_SIDE_RATIO;
793 }
795 return prefWidth;
796 }
798 bool
799 nsRangeFrame::IsHorizontal(const nsSize *aFrameSizeOverride) const
800 {
801 dom::HTMLInputElement* element = static_cast<dom::HTMLInputElement*>(mContent);
802 return !element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
803 nsGkAtoms::vertical, eCaseMatters);
804 }
806 double
807 nsRangeFrame::GetMin() const
808 {
809 return static_cast<dom::HTMLInputElement*>(mContent)->GetMinimum().toDouble();
810 }
812 double
813 nsRangeFrame::GetMax() const
814 {
815 return static_cast<dom::HTMLInputElement*>(mContent)->GetMaximum().toDouble();
816 }
818 double
819 nsRangeFrame::GetValue() const
820 {
821 return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal().toDouble();
822 }
824 nsIAtom*
825 nsRangeFrame::GetType() const
826 {
827 return nsGkAtoms::rangeFrame;
828 }
830 #define STYLES_DISABLING_NATIVE_THEMING \
831 NS_AUTHOR_SPECIFIED_BACKGROUND | \
832 NS_AUTHOR_SPECIFIED_PADDING | \
833 NS_AUTHOR_SPECIFIED_BORDER
835 bool
836 nsRangeFrame::ShouldUseNativeStyle() const
837 {
838 return (StyleDisplay()->mAppearance == NS_THEME_RANGE) &&
839 !PresContext()->HasAuthorSpecifiedRules(const_cast<nsRangeFrame*>(this),
840 (NS_AUTHOR_SPECIFIED_BORDER |
841 NS_AUTHOR_SPECIFIED_BACKGROUND)) &&
842 !PresContext()->HasAuthorSpecifiedRules(mTrackDiv->GetPrimaryFrame(),
843 STYLES_DISABLING_NATIVE_THEMING) &&
844 !PresContext()->HasAuthorSpecifiedRules(mProgressDiv->GetPrimaryFrame(),
845 STYLES_DISABLING_NATIVE_THEMING) &&
846 !PresContext()->HasAuthorSpecifiedRules(mThumbDiv->GetPrimaryFrame(),
847 STYLES_DISABLING_NATIVE_THEMING);
848 }
850 Element*
851 nsRangeFrame::GetPseudoElement(nsCSSPseudoElements::Type aType)
852 {
853 if (aType == nsCSSPseudoElements::ePseudo_mozRangeTrack) {
854 return mTrackDiv;
855 }
857 if (aType == nsCSSPseudoElements::ePseudo_mozRangeThumb) {
858 return mThumbDiv;
859 }
861 if (aType == nsCSSPseudoElements::ePseudo_mozRangeProgress) {
862 return mProgressDiv;
863 }
865 return nsContainerFrame::GetPseudoElement(aType);
866 }
868 nsStyleContext*
869 nsRangeFrame::GetAdditionalStyleContext(int32_t aIndex) const
870 {
871 // We only implement this so that SetAdditionalStyleContext will be
872 // called if style changes that would change the -moz-focus-outer
873 // pseudo-element have occurred.
874 return aIndex == 0 ? mOuterFocusStyle : nullptr;
875 }
877 void
878 nsRangeFrame::SetAdditionalStyleContext(int32_t aIndex,
879 nsStyleContext* aStyleContext)
880 {
881 MOZ_ASSERT(aIndex == 0,
882 "GetAdditionalStyleContext is handling other indexes?");
884 // The -moz-focus-outer pseudo-element's style has changed.
885 mOuterFocusStyle = aStyleContext;
886 }