Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "nsRangeFrame.h"
7 :
8 : #include "mozilla/EventStates.h"
9 : #include "mozilla/TouchEvents.h"
10 :
11 : #include "gfxContext.h"
12 : #include "nsContentCreatorFunctions.h"
13 : #include "nsContentList.h"
14 : #include "nsContentUtils.h"
15 : #include "nsCSSPseudoElements.h"
16 : #include "nsCSSRendering.h"
17 : #include "nsFormControlFrame.h"
18 : #include "nsIContent.h"
19 : #include "nsIDocument.h"
20 : #include "nsNameSpaceManager.h"
21 : #include "nsIPresShell.h"
22 : #include "nsGkAtoms.h"
23 : #include "mozilla/dom/HTMLInputElement.h"
24 : #include "nsPresContext.h"
25 : #include "nsNodeInfoManager.h"
26 : #include "mozilla/dom/Element.h"
27 : #include "mozilla/StyleSetHandle.h"
28 : #include "mozilla/StyleSetHandleInlines.h"
29 : #include "nsThemeConstants.h"
30 :
31 : #ifdef ACCESSIBILITY
32 : #include "nsAccessibilityService.h"
33 : #endif
34 :
35 : #define LONG_SIDE_TO_SHORT_SIDE_RATIO 10
36 :
37 : using namespace mozilla;
38 : using namespace mozilla::dom;
39 : using namespace mozilla::image;
40 :
41 0 : NS_IMPL_ISUPPORTS(nsRangeFrame::DummyTouchListener, nsIDOMEventListener)
42 :
43 : nsIFrame*
44 0 : NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
45 : {
46 0 : return new (aPresShell) nsRangeFrame(aContext);
47 : }
48 :
49 0 : nsRangeFrame::nsRangeFrame(nsStyleContext* aContext)
50 0 : : nsContainerFrame(aContext, kClassID)
51 : {
52 0 : }
53 :
54 0 : nsRangeFrame::~nsRangeFrame()
55 : {
56 : #ifdef DEBUG
57 0 : if (mOuterFocusStyle) {
58 0 : mOuterFocusStyle->FrameRelease();
59 : }
60 : #endif
61 0 : }
62 :
63 0 : NS_IMPL_FRAMEARENA_HELPERS(nsRangeFrame)
64 :
65 0 : NS_QUERYFRAME_HEAD(nsRangeFrame)
66 0 : NS_QUERYFRAME_ENTRY(nsRangeFrame)
67 0 : NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
68 0 : NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
69 :
70 : void
71 0 : nsRangeFrame::Init(nsIContent* aContent,
72 : nsContainerFrame* aParent,
73 : nsIFrame* aPrevInFlow)
74 : {
75 : // With APZ enabled, touch events may be handled directly by the APZC code
76 : // if the APZ knows that there is no content interested in the touch event.
77 : // The range input element *is* interested in touch events, but doesn't use
78 : // the usual mechanism (i.e. registering an event listener) to handle touch
79 : // input. Instead, we do it here so that the APZ finds out about it, and
80 : // makes sure to wait for content to run handlers before handling the touch
81 : // input itself.
82 0 : if (!mDummyTouchListener) {
83 0 : mDummyTouchListener = new DummyTouchListener();
84 : }
85 0 : aContent->AddEventListener(NS_LITERAL_STRING("touchstart"), mDummyTouchListener, false);
86 :
87 0 : StyleSetHandle styleSet = PresContext()->StyleSet();
88 :
89 : mOuterFocusStyle =
90 0 : styleSet->ProbePseudoElementStyle(aContent->AsElement(),
91 : CSSPseudoElementType::mozFocusOuter,
92 0 : StyleContext());
93 :
94 0 : return nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
95 : }
96 :
97 : void
98 0 : nsRangeFrame::DestroyFrom(nsIFrame* aDestructRoot)
99 : {
100 0 : NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
101 : "nsRangeFrame should not have continuations; if it does we "
102 : "need to call RegUnregAccessKey only for the first.");
103 :
104 0 : mContent->RemoveEventListener(NS_LITERAL_STRING("touchstart"), mDummyTouchListener, false);
105 :
106 0 : nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
107 0 : nsContentUtils::DestroyAnonymousContent(&mTrackDiv);
108 0 : nsContentUtils::DestroyAnonymousContent(&mProgressDiv);
109 0 : nsContentUtils::DestroyAnonymousContent(&mThumbDiv);
110 0 : nsContainerFrame::DestroyFrom(aDestructRoot);
111 0 : }
112 :
113 : nsresult
114 0 : nsRangeFrame::MakeAnonymousDiv(Element** aResult,
115 : CSSPseudoElementType aPseudoType,
116 : nsTArray<ContentInfo>& aElements)
117 : {
118 0 : nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
119 0 : RefPtr<Element> resultElement = doc->CreateHTMLElement(nsGkAtoms::div);
120 :
121 : // Associate the pseudo-element with the anonymous child.
122 0 : resultElement->SetPseudoElementType(aPseudoType);
123 :
124 0 : if (!aElements.AppendElement(resultElement)) {
125 0 : return NS_ERROR_OUT_OF_MEMORY;
126 : }
127 :
128 0 : resultElement.forget(aResult);
129 0 : return NS_OK;
130 : }
131 :
132 : nsresult
133 0 : nsRangeFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
134 : {
135 : nsresult rv;
136 :
137 : // Create the ::-moz-range-track pseuto-element (a div):
138 0 : rv = MakeAnonymousDiv(getter_AddRefs(mTrackDiv),
139 : CSSPseudoElementType::mozRangeTrack,
140 0 : aElements);
141 0 : NS_ENSURE_SUCCESS(rv, rv);
142 :
143 : // Create the ::-moz-range-progress pseudo-element (a div):
144 0 : rv = MakeAnonymousDiv(getter_AddRefs(mProgressDiv),
145 : CSSPseudoElementType::mozRangeProgress,
146 0 : aElements);
147 0 : NS_ENSURE_SUCCESS(rv, rv);
148 :
149 : // Create the ::-moz-range-thumb pseudo-element (a div):
150 0 : rv = MakeAnonymousDiv(getter_AddRefs(mThumbDiv),
151 : CSSPseudoElementType::mozRangeThumb,
152 0 : aElements);
153 0 : return rv;
154 : }
155 :
156 : void
157 0 : nsRangeFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
158 : uint32_t aFilter)
159 : {
160 0 : if (mTrackDiv) {
161 0 : aElements.AppendElement(mTrackDiv);
162 : }
163 :
164 0 : if (mProgressDiv) {
165 0 : aElements.AppendElement(mProgressDiv);
166 : }
167 :
168 0 : if (mThumbDiv) {
169 0 : aElements.AppendElement(mThumbDiv);
170 : }
171 0 : }
172 :
173 : class nsDisplayRangeFocusRing : public nsDisplayItem
174 : {
175 : public:
176 0 : nsDisplayRangeFocusRing(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
177 0 : : nsDisplayItem(aBuilder, aFrame) {
178 0 : MOZ_COUNT_CTOR(nsDisplayRangeFocusRing);
179 0 : }
180 : #ifdef NS_BUILD_REFCNT_LOGGING
181 0 : virtual ~nsDisplayRangeFocusRing() {
182 0 : MOZ_COUNT_DTOR(nsDisplayRangeFocusRing);
183 0 : }
184 : #endif
185 :
186 : nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
187 : void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
188 : const nsDisplayItemGeometry* aGeometry,
189 : nsRegion *aInvalidRegion) override;
190 : virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
191 : virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
192 0 : NS_DISPLAY_DECL_NAME("RangeFocusRing", TYPE_RANGE_FOCUS_RING)
193 : };
194 :
195 : nsDisplayItemGeometry*
196 0 : nsDisplayRangeFocusRing::AllocateGeometry(nsDisplayListBuilder* aBuilder)
197 : {
198 0 : return new nsDisplayItemGenericImageGeometry(this, aBuilder);
199 : }
200 :
201 : void
202 0 : nsDisplayRangeFocusRing::ComputeInvalidationRegion(
203 : nsDisplayListBuilder* aBuilder,
204 : const nsDisplayItemGeometry* aGeometry,
205 : nsRegion* aInvalidRegion)
206 : {
207 : auto geometry =
208 0 : static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
209 :
210 0 : if (aBuilder->ShouldSyncDecodeImages() &&
211 0 : geometry->ShouldInvalidateToSyncDecodeImages()) {
212 : bool snap;
213 0 : aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
214 : }
215 :
216 0 : nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
217 0 : }
218 :
219 : nsRect
220 0 : nsDisplayRangeFocusRing::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
221 : {
222 0 : *aSnap = false;
223 0 : nsRect rect(ToReferenceFrame(), Frame()->GetSize());
224 :
225 : // We want to paint as if specifying a border for ::-moz-focus-outer
226 : // specifies an outline for our frame, so inflate by the border widths:
227 : nsStyleContext* styleContext =
228 0 : static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle;
229 0 : MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null");
230 0 : rect.Inflate(styleContext->StyleBorder()->GetComputedBorder());
231 :
232 0 : return rect;
233 : }
234 :
235 : void
236 0 : nsDisplayRangeFocusRing::Paint(nsDisplayListBuilder* aBuilder,
237 : gfxContext* aCtx)
238 : {
239 : bool unused;
240 : nsStyleContext* styleContext =
241 0 : static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle;
242 0 : MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null");
243 :
244 0 : PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
245 0 : ? PaintBorderFlags::SYNC_DECODE_IMAGES
246 0 : : PaintBorderFlags();
247 :
248 : DrawResult result =
249 0 : nsCSSRendering::PaintBorder(mFrame->PresContext(), *aCtx, mFrame,
250 0 : mVisibleRect, GetBounds(aBuilder, &unused),
251 0 : styleContext, flags);
252 :
253 0 : nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
254 0 : }
255 :
256 : void
257 0 : nsRangeFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
258 : const nsRect& aDirtyRect,
259 : const nsDisplayListSet& aLists)
260 : {
261 0 : const nsStyleDisplay* disp = StyleDisplay();
262 0 : if (IsThemed(disp)) {
263 0 : DisplayBorderBackgroundOutline(aBuilder, aLists);
264 : // Only create items for the thumb. Specifically, we do not want
265 : // the track to paint, since *our* background is used to paint
266 : // the track, and we don't want the unthemed track painting over
267 : // the top of the themed track.
268 : // This logic is copied from
269 : // nsContainerFrame::BuildDisplayListForNonBlockChildren as
270 : // called by BuildDisplayListForInline.
271 0 : nsIFrame* thumb = mThumbDiv->GetPrimaryFrame();
272 0 : if (thumb) {
273 0 : nsDisplayListSet set(aLists, aLists.Content());
274 0 : BuildDisplayListForChild(aBuilder, thumb, aDirtyRect, set, DISPLAY_CHILD_INLINE);
275 : }
276 : } else {
277 0 : BuildDisplayListForInline(aBuilder, aDirtyRect, aLists);
278 : }
279 :
280 : // Draw a focus outline if appropriate:
281 :
282 0 : if (!aBuilder->IsForPainting() ||
283 0 : !IsVisibleForPainting(aBuilder)) {
284 : // we don't want the focus ring item for hit-testing or if the item isn't
285 : // in the area being [re]painted
286 0 : return;
287 : }
288 :
289 0 : EventStates eventStates = mContent->AsElement()->State();
290 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED) ||
291 0 : !eventStates.HasState(NS_EVENT_STATE_FOCUSRING)) {
292 0 : return; // can't have focus or doesn't match :-moz-focusring
293 : }
294 :
295 0 : if (!mOuterFocusStyle ||
296 0 : !mOuterFocusStyle->StyleBorder()->HasBorder()) {
297 : // no ::-moz-focus-outer specified border (how style specifies a focus ring
298 : // for range)
299 0 : return;
300 : }
301 :
302 0 : if (IsThemed(disp) &&
303 0 : PresContext()->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) {
304 0 : return; // the native theme displays its own visual indication of focus
305 : }
306 :
307 0 : aLists.Content()->AppendNewToTop(
308 0 : new (aBuilder) nsDisplayRangeFocusRing(aBuilder, this));
309 : }
310 :
311 : void
312 0 : nsRangeFrame::Reflow(nsPresContext* aPresContext,
313 : ReflowOutput& aDesiredSize,
314 : const ReflowInput& aReflowInput,
315 : nsReflowStatus& aStatus)
316 : {
317 0 : MarkInReflow();
318 0 : DO_GLOBAL_REFLOW_COUNT("nsRangeFrame");
319 0 : DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
320 :
321 0 : NS_ASSERTION(mTrackDiv, "::-moz-range-track div must exist!");
322 0 : NS_ASSERTION(mProgressDiv, "::-moz-range-progress div must exist!");
323 0 : NS_ASSERTION(mThumbDiv, "::-moz-range-thumb div must exist!");
324 0 : NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
325 : "nsRangeFrame should not have continuations; if it does we "
326 : "need to call RegUnregAccessKey only for the first.");
327 :
328 0 : if (mState & NS_FRAME_FIRST_REFLOW) {
329 0 : nsFormControlFrame::RegUnRegAccessKey(this, true);
330 : }
331 :
332 0 : WritingMode wm = aReflowInput.GetWritingMode();
333 0 : nscoord computedBSize = aReflowInput.ComputedBSize();
334 0 : if (computedBSize == NS_AUTOHEIGHT) {
335 0 : computedBSize = 0;
336 : }
337 : LogicalSize
338 : finalSize(wm,
339 0 : aReflowInput.ComputedISize() +
340 0 : aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm),
341 : computedBSize +
342 0 : aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm));
343 0 : aDesiredSize.SetSize(wm, finalSize);
344 :
345 0 : ReflowAnonymousContent(aPresContext, aDesiredSize, aReflowInput);
346 :
347 0 : aDesiredSize.SetOverflowAreasToDesiredBounds();
348 :
349 0 : nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
350 0 : if (trackFrame) {
351 0 : ConsiderChildOverflow(aDesiredSize.mOverflowAreas, trackFrame);
352 : }
353 :
354 0 : nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
355 0 : if (rangeProgressFrame) {
356 0 : ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rangeProgressFrame);
357 : }
358 :
359 0 : nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
360 0 : if (thumbFrame) {
361 0 : ConsiderChildOverflow(aDesiredSize.mOverflowAreas, thumbFrame);
362 : }
363 :
364 0 : FinishAndStoreOverflow(&aDesiredSize);
365 :
366 0 : aStatus.Reset();
367 :
368 0 : NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
369 0 : }
370 :
371 : void
372 0 : nsRangeFrame::ReflowAnonymousContent(nsPresContext* aPresContext,
373 : ReflowOutput& aDesiredSize,
374 : const ReflowInput& aReflowInput)
375 : {
376 : // The width/height of our content box, which is the available width/height
377 : // for our anonymous content:
378 0 : nscoord rangeFrameContentBoxWidth = aReflowInput.ComputedWidth();
379 0 : nscoord rangeFrameContentBoxHeight = aReflowInput.ComputedHeight();
380 0 : if (rangeFrameContentBoxHeight == NS_AUTOHEIGHT) {
381 0 : rangeFrameContentBoxHeight = 0;
382 : }
383 :
384 0 : nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
385 :
386 0 : if (trackFrame) { // display:none?
387 :
388 : // Position the track:
389 : // The idea here is that we allow content authors to style the width,
390 : // height, border and padding of the track, but we ignore margin and
391 : // positioning properties and do the positioning ourself to keep the center
392 : // of the track's border box on the center of the nsRangeFrame's content
393 : // box.
394 :
395 0 : WritingMode wm = trackFrame->GetWritingMode();
396 0 : LogicalSize availSize = aReflowInput.ComputedSize(wm);
397 0 : availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
398 : ReflowInput trackReflowInput(aPresContext, aReflowInput,
399 0 : trackFrame, availSize);
400 :
401 : // Find the x/y position of the track frame such that it will be positioned
402 : // as described above. These coordinates are with respect to the
403 : // nsRangeFrame's border-box.
404 0 : nscoord trackX = rangeFrameContentBoxWidth / 2;
405 0 : nscoord trackY = rangeFrameContentBoxHeight / 2;
406 :
407 : // Account for the track's border and padding (we ignore its margin):
408 0 : trackX -= trackReflowInput.ComputedPhysicalBorderPadding().left +
409 0 : trackReflowInput.ComputedWidth() / 2;
410 0 : trackY -= trackReflowInput.ComputedPhysicalBorderPadding().top +
411 0 : trackReflowInput.ComputedHeight() / 2;
412 :
413 : // Make relative to our border box instead of our content box:
414 0 : trackX += aReflowInput.ComputedPhysicalBorderPadding().left;
415 0 : trackY += aReflowInput.ComputedPhysicalBorderPadding().top;
416 :
417 0 : nsReflowStatus frameStatus;
418 0 : ReflowOutput trackDesiredSize(aReflowInput);
419 0 : ReflowChild(trackFrame, aPresContext, trackDesiredSize,
420 0 : trackReflowInput, trackX, trackY, 0, frameStatus);
421 0 : MOZ_ASSERT(frameStatus.IsFullyComplete(),
422 : "We gave our child unconstrained height, so it should be complete");
423 : FinishReflowChild(trackFrame, aPresContext, trackDesiredSize,
424 0 : &trackReflowInput, trackX, trackY, 0);
425 : }
426 :
427 0 : nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
428 :
429 0 : if (thumbFrame) { // display:none?
430 0 : WritingMode wm = thumbFrame->GetWritingMode();
431 0 : LogicalSize availSize = aReflowInput.ComputedSize(wm);
432 0 : availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
433 : ReflowInput thumbReflowInput(aPresContext, aReflowInput,
434 0 : thumbFrame, availSize);
435 :
436 : // Where we position the thumb depends on its size, so we first reflow
437 : // the thumb at {0,0} to obtain its size, then position it afterwards.
438 :
439 0 : nsReflowStatus frameStatus;
440 0 : ReflowOutput thumbDesiredSize(aReflowInput);
441 0 : ReflowChild(thumbFrame, aPresContext, thumbDesiredSize,
442 0 : thumbReflowInput, 0, 0, 0, frameStatus);
443 0 : MOZ_ASSERT(frameStatus.IsFullyComplete(),
444 : "We gave our child unconstrained height, so it should be complete");
445 : FinishReflowChild(thumbFrame, aPresContext, thumbDesiredSize,
446 0 : &thumbReflowInput, 0, 0, 0);
447 0 : DoUpdateThumbPosition(thumbFrame, nsSize(aDesiredSize.Width(),
448 0 : aDesiredSize.Height()));
449 : }
450 :
451 0 : nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
452 :
453 0 : if (rangeProgressFrame) { // display:none?
454 0 : WritingMode wm = rangeProgressFrame->GetWritingMode();
455 0 : LogicalSize availSize = aReflowInput.ComputedSize(wm);
456 0 : availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
457 : ReflowInput progressReflowInput(aPresContext, aReflowInput,
458 0 : rangeProgressFrame, availSize);
459 :
460 : // We first reflow the range-progress frame at {0,0} to obtain its
461 : // unadjusted dimensions, then we adjust it to so that the appropriate edge
462 : // ends at the thumb.
463 :
464 0 : nsReflowStatus frameStatus;
465 0 : ReflowOutput progressDesiredSize(aReflowInput);
466 0 : ReflowChild(rangeProgressFrame, aPresContext,
467 : progressDesiredSize, progressReflowInput, 0, 0,
468 0 : 0, frameStatus);
469 0 : MOZ_ASSERT(frameStatus.IsFullyComplete(),
470 : "We gave our child unconstrained height, so it should be complete");
471 : FinishReflowChild(rangeProgressFrame, aPresContext,
472 0 : progressDesiredSize, &progressReflowInput, 0, 0, 0);
473 0 : DoUpdateRangeProgressFrame(rangeProgressFrame, nsSize(aDesiredSize.Width(),
474 0 : aDesiredSize.Height()));
475 : }
476 0 : }
477 :
478 : #ifdef ACCESSIBILITY
479 : a11y::AccType
480 0 : nsRangeFrame::AccessibleType()
481 : {
482 0 : return a11y::eHTMLRangeType;
483 : }
484 : #endif
485 :
486 : double
487 0 : nsRangeFrame::GetValueAsFractionOfRange()
488 : {
489 0 : MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
490 0 : dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent);
491 :
492 0 : MOZ_ASSERT(input->ControlType() == NS_FORM_INPUT_RANGE);
493 :
494 0 : Decimal value = input->GetValueAsDecimal();
495 0 : Decimal minimum = input->GetMinimum();
496 0 : Decimal maximum = input->GetMaximum();
497 :
498 0 : MOZ_ASSERT(value.isFinite() && minimum.isFinite() && maximum.isFinite(),
499 : "type=range should have a default maximum/minimum");
500 :
501 0 : if (maximum <= minimum) {
502 0 : MOZ_ASSERT(value == minimum, "Unsanitized value");
503 0 : return 0.0;
504 : }
505 :
506 0 : MOZ_ASSERT(value >= minimum && value <= maximum, "Unsanitized value");
507 :
508 0 : return ((value - minimum) / (maximum - minimum)).toDouble();
509 : }
510 :
511 : Decimal
512 0 : nsRangeFrame::GetValueAtEventPoint(WidgetGUIEvent* aEvent)
513 : {
514 0 : MOZ_ASSERT(aEvent->mClass == eMouseEventClass ||
515 : aEvent->mClass == eTouchEventClass,
516 : "Unexpected event type - aEvent->mRefPoint may be meaningless");
517 :
518 0 : MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
519 0 : dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent);
520 :
521 0 : MOZ_ASSERT(input->ControlType() == NS_FORM_INPUT_RANGE);
522 :
523 0 : Decimal minimum = input->GetMinimum();
524 0 : Decimal maximum = input->GetMaximum();
525 0 : MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
526 : "type=range should have a default maximum/minimum");
527 0 : if (maximum <= minimum) {
528 0 : return minimum;
529 : }
530 0 : Decimal range = maximum - minimum;
531 :
532 0 : LayoutDeviceIntPoint absPoint;
533 0 : if (aEvent->mClass == eTouchEventClass) {
534 0 : MOZ_ASSERT(aEvent->AsTouchEvent()->mTouches.Length() == 1,
535 : "Unexpected number of mTouches");
536 0 : absPoint = aEvent->AsTouchEvent()->mTouches[0]->mRefPoint;
537 : } else {
538 0 : absPoint = aEvent->mRefPoint;
539 : }
540 : nsPoint point =
541 0 : nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, absPoint, this);
542 :
543 0 : if (point == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
544 : // We don't want to change the current value for this error state.
545 0 : return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal();
546 : }
547 :
548 0 : nsRect rangeContentRect = GetContentRectRelativeToSelf();
549 0 : nsSize thumbSize;
550 :
551 0 : if (IsThemed()) {
552 : // We need to get the size of the thumb from the theme.
553 0 : nsPresContext *presContext = PresContext();
554 : bool notUsedCanOverride;
555 0 : LayoutDeviceIntSize size;
556 0 : presContext->GetTheme()->
557 0 : GetMinimumWidgetSize(presContext, this, NS_THEME_RANGE_THUMB, &size,
558 0 : ¬UsedCanOverride);
559 0 : thumbSize.width = presContext->DevPixelsToAppUnits(size.width);
560 0 : thumbSize.height = presContext->DevPixelsToAppUnits(size.height);
561 : // For GTK, GetMinimumWidgetSize returns zero for the thumb dimension
562 : // perpendicular to the orientation of the slider. That's okay since we
563 : // only care about the dimension in the direction of the slider when using
564 : // |thumbSize| below, but it means this assertion need to check
565 : // IsHorizontal().
566 0 : MOZ_ASSERT((IsHorizontal() && thumbSize.width > 0) ||
567 : (!IsHorizontal() && thumbSize.height > 0),
568 : "The thumb is expected to take up some slider space");
569 : } else {
570 0 : nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
571 0 : if (thumbFrame) { // diplay:none?
572 0 : thumbSize = thumbFrame->GetSize();
573 : }
574 : }
575 :
576 0 : Decimal fraction;
577 0 : if (IsHorizontal()) {
578 0 : nscoord traversableDistance = rangeContentRect.width - thumbSize.width;
579 0 : if (traversableDistance <= 0) {
580 0 : return minimum;
581 : }
582 0 : nscoord posAtStart = rangeContentRect.x + thumbSize.width/2;
583 0 : nscoord posAtEnd = posAtStart + traversableDistance;
584 0 : nscoord posOfPoint = mozilla::clamped(point.x, posAtStart, posAtEnd);
585 0 : fraction = Decimal(posOfPoint - posAtStart) / Decimal(traversableDistance);
586 0 : if (IsRightToLeft()) {
587 0 : fraction = Decimal(1) - fraction;
588 : }
589 : } else {
590 0 : nscoord traversableDistance = rangeContentRect.height - thumbSize.height;
591 0 : if (traversableDistance <= 0) {
592 0 : return minimum;
593 : }
594 0 : nscoord posAtStart = rangeContentRect.y + thumbSize.height/2;
595 0 : nscoord posAtEnd = posAtStart + traversableDistance;
596 0 : nscoord posOfPoint = mozilla::clamped(point.y, posAtStart, posAtEnd);
597 : // For a vertical range, the top (posAtStart) is the highest value, so we
598 : // subtract the fraction from 1.0 to get that polarity correct.
599 0 : fraction = Decimal(1) - Decimal(posOfPoint - posAtStart) / Decimal(traversableDistance);
600 : }
601 :
602 0 : MOZ_ASSERT(fraction >= Decimal(0) && fraction <= Decimal(1));
603 0 : return minimum + fraction * range;
604 : }
605 :
606 : void
607 0 : nsRangeFrame::UpdateForValueChange()
608 : {
609 0 : if (NS_SUBTREE_DIRTY(this)) {
610 0 : return; // we're going to be updated when we reflow
611 : }
612 0 : nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
613 0 : nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
614 0 : if (!rangeProgressFrame && !thumbFrame) {
615 0 : return; // diplay:none?
616 : }
617 0 : if (rangeProgressFrame) {
618 0 : DoUpdateRangeProgressFrame(rangeProgressFrame, GetSize());
619 : }
620 0 : if (thumbFrame) {
621 0 : DoUpdateThumbPosition(thumbFrame, GetSize());
622 : }
623 0 : if (IsThemed()) {
624 : // We don't know the exact dimensions or location of the thumb when native
625 : // theming is applied, so we just repaint the entire range.
626 0 : InvalidateFrame();
627 : }
628 :
629 : #ifdef ACCESSIBILITY
630 0 : nsAccessibilityService* accService = nsIPresShell::AccService();
631 0 : if (accService) {
632 0 : accService->RangeValueChanged(PresContext()->PresShell(), mContent);
633 : }
634 : #endif
635 :
636 0 : SchedulePaint();
637 : }
638 :
639 : void
640 0 : nsRangeFrame::DoUpdateThumbPosition(nsIFrame* aThumbFrame,
641 : const nsSize& aRangeSize)
642 : {
643 0 : MOZ_ASSERT(aThumbFrame);
644 :
645 : // The idea here is that we want to position the thumb so that the center
646 : // of the thumb is on an imaginary line drawn from the middle of one edge
647 : // of the range frame's content box to the middle of the opposite edge of
648 : // its content box (the opposite edges being the left/right edge if the
649 : // range is horizontal, or else the top/bottom edges if the range is
650 : // vertical). How far along this line the center of the thumb is placed
651 : // depends on the value of the range.
652 :
653 0 : nsMargin borderAndPadding = GetUsedBorderAndPadding();
654 0 : nsPoint newPosition(borderAndPadding.left, borderAndPadding.top);
655 :
656 0 : nsSize rangeContentBoxSize(aRangeSize);
657 0 : rangeContentBoxSize.width -= borderAndPadding.LeftRight();
658 0 : rangeContentBoxSize.height -= borderAndPadding.TopBottom();
659 :
660 0 : nsSize thumbSize = aThumbFrame->GetSize();
661 0 : double fraction = GetValueAsFractionOfRange();
662 0 : MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
663 :
664 0 : if (IsHorizontal()) {
665 0 : if (thumbSize.width < rangeContentBoxSize.width) {
666 : nscoord traversableDistance =
667 0 : rangeContentBoxSize.width - thumbSize.width;
668 0 : if (IsRightToLeft()) {
669 0 : newPosition.x += NSToCoordRound((1.0 - fraction) * traversableDistance);
670 : } else {
671 0 : newPosition.x += NSToCoordRound(fraction * traversableDistance);
672 : }
673 0 : newPosition.y += (rangeContentBoxSize.height - thumbSize.height)/2;
674 : }
675 : } else {
676 0 : if (thumbSize.height < rangeContentBoxSize.height) {
677 : nscoord traversableDistance =
678 0 : rangeContentBoxSize.height - thumbSize.height;
679 0 : newPosition.x += (rangeContentBoxSize.width - thumbSize.width)/2;
680 0 : newPosition.y += NSToCoordRound((1.0 - fraction) * traversableDistance);
681 : }
682 : }
683 0 : aThumbFrame->SetPosition(newPosition);
684 0 : }
685 :
686 : void
687 0 : nsRangeFrame::DoUpdateRangeProgressFrame(nsIFrame* aRangeProgressFrame,
688 : const nsSize& aRangeSize)
689 : {
690 0 : MOZ_ASSERT(aRangeProgressFrame);
691 :
692 : // The idea here is that we want to position the ::-moz-range-progress
693 : // pseudo-element so that the center line running along its length is on the
694 : // corresponding center line of the nsRangeFrame's content box. In the other
695 : // dimension, we align the "start" edge of the ::-moz-range-progress
696 : // pseudo-element's border-box with the corresponding edge of the
697 : // nsRangeFrame's content box, and we size the progress element's border-box
698 : // to have a length of GetValueAsFractionOfRange() times the nsRangeFrame's
699 : // content-box size.
700 :
701 0 : nsMargin borderAndPadding = GetUsedBorderAndPadding();
702 0 : nsSize progSize = aRangeProgressFrame->GetSize();
703 : nsRect progRect(borderAndPadding.left, borderAndPadding.top,
704 0 : progSize.width, progSize.height);
705 :
706 0 : nsSize rangeContentBoxSize(aRangeSize);
707 0 : rangeContentBoxSize.width -= borderAndPadding.LeftRight();
708 0 : rangeContentBoxSize.height -= borderAndPadding.TopBottom();
709 :
710 0 : double fraction = GetValueAsFractionOfRange();
711 0 : MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
712 :
713 0 : if (IsHorizontal()) {
714 0 : nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.width);
715 0 : if (IsRightToLeft()) {
716 0 : progRect.x += rangeContentBoxSize.width - progLength;
717 : }
718 0 : progRect.y += (rangeContentBoxSize.height - progSize.height)/2;
719 0 : progRect.width = progLength;
720 : } else {
721 0 : nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.height);
722 0 : progRect.x += (rangeContentBoxSize.width - progSize.width)/2;
723 0 : progRect.y += rangeContentBoxSize.height - progLength;
724 0 : progRect.height = progLength;
725 : }
726 0 : aRangeProgressFrame->SetRect(progRect);
727 0 : }
728 :
729 : nsresult
730 0 : nsRangeFrame::AttributeChanged(int32_t aNameSpaceID,
731 : nsIAtom* aAttribute,
732 : int32_t aModType)
733 : {
734 0 : NS_ASSERTION(mTrackDiv, "The track div must exist!");
735 0 : NS_ASSERTION(mThumbDiv, "The thumb div must exist!");
736 :
737 0 : if (aNameSpaceID == kNameSpaceID_None) {
738 0 : if (aAttribute == nsGkAtoms::value ||
739 0 : aAttribute == nsGkAtoms::min ||
740 0 : aAttribute == nsGkAtoms::max ||
741 0 : aAttribute == nsGkAtoms::step) {
742 : // We want to update the position of the thumb, except in one special
743 : // case: If the value attribute is being set, it is possible that we are
744 : // in the middle of a type change away from type=range, under the
745 : // SetAttr(..., nsGkAtoms::value, ...) call in HTMLInputElement::
746 : // HandleTypeChange. In that case the HTMLInputElement's type will
747 : // already have changed, and if we call UpdateForValueChange()
748 : // we'll fail the asserts under that call that check the type of our
749 : // HTMLInputElement. Given that we're changing away from being a range
750 : // and this frame will shortly be destroyed, there's no point in calling
751 : // UpdateForValueChange() anyway.
752 0 : MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
753 : bool typeIsRange =
754 0 : static_cast<dom::HTMLInputElement*>(mContent)->ControlType() ==
755 0 : NS_FORM_INPUT_RANGE;
756 : // If script changed the <input>'s type before setting these attributes
757 : // then we don't need to do anything since we are going to be reframed.
758 0 : if (typeIsRange) {
759 0 : UpdateForValueChange();
760 0 : }
761 0 : } else if (aAttribute == nsGkAtoms::orient) {
762 0 : PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eResize,
763 0 : NS_FRAME_IS_DIRTY);
764 : }
765 : }
766 :
767 0 : return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
768 : }
769 :
770 : LogicalSize
771 0 : nsRangeFrame::ComputeAutoSize(gfxContext* aRenderingContext,
772 : WritingMode aWM,
773 : const LogicalSize& aCBSize,
774 : nscoord aAvailableISize,
775 : const LogicalSize& aMargin,
776 : const LogicalSize& aBorder,
777 : const LogicalSize& aPadding,
778 : ComputeSizeFlags aFlags)
779 : {
780 0 : nscoord oneEm = NSToCoordRound(StyleFont()->mFont.size *
781 0 : nsLayoutUtils::FontSizeInflationFor(this)); // 1em
782 :
783 0 : bool isInlineOriented = IsInlineOriented();
784 :
785 0 : const WritingMode wm = GetWritingMode();
786 0 : LogicalSize autoSize(wm);
787 :
788 : // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
789 : // given too small a size when we're natively themed. If we're themed, we set
790 : // our "thickness" dimension to zero below and rely on that
791 : // GetMinimumWidgetSize check to correct that dimension to the natural
792 : // thickness of a slider in the current theme.
793 :
794 0 : if (isInlineOriented) {
795 0 : autoSize.ISize(wm) = LONG_SIDE_TO_SHORT_SIDE_RATIO * oneEm;
796 0 : autoSize.BSize(wm) = IsThemed() ? 0 : oneEm;
797 : } else {
798 0 : autoSize.ISize(wm) = IsThemed() ? 0 : oneEm;
799 0 : autoSize.BSize(wm) = LONG_SIDE_TO_SHORT_SIDE_RATIO * oneEm;
800 : }
801 :
802 0 : return autoSize.ConvertTo(aWM, wm);
803 : }
804 :
805 : nscoord
806 0 : nsRangeFrame::GetMinISize(gfxContext *aRenderingContext)
807 : {
808 : // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
809 : // given too small a size when we're natively themed. If we aren't native
810 : // themed, we don't mind how small we're sized.
811 0 : return nscoord(0);
812 : }
813 :
814 : nscoord
815 0 : nsRangeFrame::GetPrefISize(gfxContext *aRenderingContext)
816 : {
817 0 : bool isInline = IsInlineOriented();
818 :
819 0 : if (!isInline && IsThemed()) {
820 : // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
821 : // given too small a size when we're natively themed. We return zero and
822 : // depend on that correction to get our "natural" width when we're a
823 : // vertical slider.
824 0 : return 0;
825 : }
826 :
827 0 : nscoord prefISize = NSToCoordRound(StyleFont()->mFont.size *
828 0 : nsLayoutUtils::FontSizeInflationFor(this)); // 1em
829 :
830 0 : if (isInline) {
831 0 : prefISize *= LONG_SIDE_TO_SHORT_SIDE_RATIO;
832 : }
833 :
834 0 : return prefISize;
835 : }
836 :
837 : bool
838 0 : nsRangeFrame::IsHorizontal() const
839 : {
840 : dom::HTMLInputElement* element =
841 0 : static_cast<dom::HTMLInputElement*>(mContent);
842 0 : return element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
843 0 : nsGkAtoms::horizontal, eCaseMatters) ||
844 0 : (!element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
845 0 : nsGkAtoms::vertical, eCaseMatters) &&
846 0 : GetWritingMode().IsVertical() ==
847 0 : element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
848 0 : nsGkAtoms::block, eCaseMatters));
849 : }
850 :
851 : double
852 0 : nsRangeFrame::GetMin() const
853 : {
854 0 : return static_cast<dom::HTMLInputElement*>(mContent)->GetMinimum().toDouble();
855 : }
856 :
857 : double
858 0 : nsRangeFrame::GetMax() const
859 : {
860 0 : return static_cast<dom::HTMLInputElement*>(mContent)->GetMaximum().toDouble();
861 : }
862 :
863 : double
864 0 : nsRangeFrame::GetValue() const
865 : {
866 0 : return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal().toDouble();
867 : }
868 :
869 : #define STYLES_DISABLING_NATIVE_THEMING \
870 : NS_AUTHOR_SPECIFIED_BACKGROUND | \
871 : NS_AUTHOR_SPECIFIED_PADDING | \
872 : NS_AUTHOR_SPECIFIED_BORDER
873 :
874 : bool
875 0 : nsRangeFrame::ShouldUseNativeStyle() const
876 : {
877 0 : nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
878 0 : nsIFrame* progressFrame = mProgressDiv->GetPrimaryFrame();
879 0 : nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
880 :
881 0 : return (StyleDisplay()->mAppearance == NS_THEME_RANGE) &&
882 0 : !PresContext()->HasAuthorSpecifiedRules(this,
883 : (NS_AUTHOR_SPECIFIED_BORDER |
884 0 : NS_AUTHOR_SPECIFIED_BACKGROUND)) &&
885 0 : trackFrame &&
886 0 : !PresContext()->HasAuthorSpecifiedRules(trackFrame,
887 0 : STYLES_DISABLING_NATIVE_THEMING) &&
888 0 : progressFrame &&
889 0 : !PresContext()->HasAuthorSpecifiedRules(progressFrame,
890 0 : STYLES_DISABLING_NATIVE_THEMING) &&
891 0 : thumbFrame &&
892 0 : !PresContext()->HasAuthorSpecifiedRules(thumbFrame,
893 0 : STYLES_DISABLING_NATIVE_THEMING);
894 : }
895 :
896 : Element*
897 0 : nsRangeFrame::GetPseudoElement(CSSPseudoElementType aType)
898 : {
899 0 : if (aType == CSSPseudoElementType::mozRangeTrack) {
900 0 : return mTrackDiv;
901 : }
902 :
903 0 : if (aType == CSSPseudoElementType::mozRangeThumb) {
904 0 : return mThumbDiv;
905 : }
906 :
907 0 : if (aType == CSSPseudoElementType::mozRangeProgress) {
908 0 : return mProgressDiv;
909 : }
910 :
911 0 : return nsContainerFrame::GetPseudoElement(aType);
912 : }
913 :
914 : nsStyleContext*
915 0 : nsRangeFrame::GetAdditionalStyleContext(int32_t aIndex) const
916 : {
917 : // We only implement this so that SetAdditionalStyleContext will be
918 : // called if style changes that would change the -moz-focus-outer
919 : // pseudo-element have occurred.
920 0 : if (aIndex != 0) {
921 0 : return nullptr;
922 : }
923 0 : return mOuterFocusStyle;
924 : }
925 :
926 : void
927 0 : nsRangeFrame::SetAdditionalStyleContext(int32_t aIndex,
928 : nsStyleContext* aStyleContext)
929 : {
930 0 : MOZ_ASSERT(aIndex == 0,
931 : "GetAdditionalStyleContext is handling other indexes?");
932 :
933 : #ifdef DEBUG
934 0 : if (mOuterFocusStyle) {
935 0 : mOuterFocusStyle->FrameRelease();
936 : }
937 : #endif
938 :
939 : // The -moz-focus-outer pseudo-element's style has changed.
940 0 : mOuterFocusStyle = aStyleContext;
941 :
942 : #ifdef DEBUG
943 0 : if (mOuterFocusStyle) {
944 0 : mOuterFocusStyle->FrameAddRef();
945 : }
946 : #endif
947 0 : }
|