|
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 "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" |
|
29 |
|
30 #ifdef ACCESSIBILITY |
|
31 #include "nsAccessibilityService.h" |
|
32 #endif |
|
33 |
|
34 #define LONG_SIDE_TO_SHORT_SIDE_RATIO 10 |
|
35 |
|
36 using namespace mozilla; |
|
37 using namespace mozilla::dom; |
|
38 |
|
39 nsIFrame* |
|
40 NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) |
|
41 { |
|
42 return new (aPresShell) nsRangeFrame(aContext); |
|
43 } |
|
44 |
|
45 nsRangeFrame::nsRangeFrame(nsStyleContext* aContext) |
|
46 : nsContainerFrame(aContext) |
|
47 { |
|
48 } |
|
49 |
|
50 nsRangeFrame::~nsRangeFrame() |
|
51 { |
|
52 } |
|
53 |
|
54 NS_IMPL_FRAMEARENA_HELPERS(nsRangeFrame) |
|
55 |
|
56 NS_QUERYFRAME_HEAD(nsRangeFrame) |
|
57 NS_QUERYFRAME_ENTRY(nsRangeFrame) |
|
58 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) |
|
59 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) |
|
60 |
|
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 } |
|
86 |
|
87 nsStyleSet *styleSet = PresContext()->StyleSet(); |
|
88 |
|
89 mOuterFocusStyle = |
|
90 styleSet->ProbePseudoElementStyle(aContent->AsElement(), |
|
91 nsCSSPseudoElements::ePseudo_mozFocusOuter, |
|
92 StyleContext()); |
|
93 |
|
94 return nsContainerFrame::Init(aContent, aParent, aPrevInFlow); |
|
95 } |
|
96 |
|
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 } |
|
109 |
|
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); |
|
117 |
|
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); |
|
124 |
|
125 if (!aElements.AppendElement(ContentInfo(resultElement, newStyleContext))) { |
|
126 return NS_ERROR_OUT_OF_MEMORY; |
|
127 } |
|
128 |
|
129 resultElement.forget(aResult); |
|
130 return NS_OK; |
|
131 } |
|
132 |
|
133 nsresult |
|
134 nsRangeFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements) |
|
135 { |
|
136 nsresult rv; |
|
137 |
|
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); |
|
143 |
|
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); |
|
149 |
|
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 } |
|
156 |
|
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 } |
|
165 |
|
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 |
|
178 |
|
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 }; |
|
183 |
|
184 nsRect |
|
185 nsDisplayRangeFocusRing::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) |
|
186 { |
|
187 *aSnap = false; |
|
188 nsRect rect(ToReferenceFrame(), Frame()->GetSize()); |
|
189 |
|
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()); |
|
196 |
|
197 return rect; |
|
198 } |
|
199 |
|
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 } |
|
212 |
|
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 } |
|
235 |
|
236 // Draw a focus outline if appropriate: |
|
237 |
|
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 } |
|
244 |
|
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 } |
|
250 |
|
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 } |
|
257 |
|
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 } |
|
263 |
|
264 aLists.Content()->AppendNewToTop( |
|
265 new (aBuilder) nsDisplayRangeFocusRing(aBuilder, this)); |
|
266 } |
|
267 |
|
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); |
|
276 |
|
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."); |
|
283 |
|
284 if (mState & NS_FRAME_FIRST_REFLOW) { |
|
285 nsFormControlFrame::RegUnRegAccessKey(this, true); |
|
286 } |
|
287 |
|
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(); |
|
296 |
|
297 nsresult rv = |
|
298 ReflowAnonymousContent(aPresContext, aDesiredSize, aReflowState); |
|
299 NS_ENSURE_SUCCESS(rv, rv); |
|
300 |
|
301 aDesiredSize.SetOverflowAreasToDesiredBounds(); |
|
302 |
|
303 nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame(); |
|
304 if (trackFrame) { |
|
305 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, trackFrame); |
|
306 } |
|
307 |
|
308 nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame(); |
|
309 if (rangeProgressFrame) { |
|
310 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rangeProgressFrame); |
|
311 } |
|
312 |
|
313 nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame(); |
|
314 if (thumbFrame) { |
|
315 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, thumbFrame); |
|
316 } |
|
317 |
|
318 FinishAndStoreOverflow(&aDesiredSize); |
|
319 |
|
320 aStatus = NS_FRAME_COMPLETE; |
|
321 |
|
322 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); |
|
323 |
|
324 return NS_OK; |
|
325 } |
|
326 |
|
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 } |
|
339 |
|
340 nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame(); |
|
341 |
|
342 if (trackFrame) { // display:none? |
|
343 |
|
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. |
|
350 |
|
351 nsHTMLReflowState trackReflowState(aPresContext, aReflowState, trackFrame, |
|
352 nsSize(aReflowState.ComputedWidth(), |
|
353 NS_UNCONSTRAINEDSIZE)); |
|
354 |
|
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; |
|
360 |
|
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; |
|
366 |
|
367 // Make relative to our border box instead of our content box: |
|
368 trackX += aReflowState.ComputedPhysicalBorderPadding().left; |
|
369 trackY += aReflowState.ComputedPhysicalBorderPadding().top; |
|
370 |
|
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 } |
|
382 |
|
383 nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame(); |
|
384 |
|
385 if (thumbFrame) { // display:none? |
|
386 nsHTMLReflowState thumbReflowState(aPresContext, aReflowState, thumbFrame, |
|
387 nsSize(aReflowState.ComputedWidth(), |
|
388 NS_UNCONSTRAINEDSIZE)); |
|
389 |
|
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. |
|
392 |
|
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); |
|
403 |
|
404 DoUpdateThumbPosition(thumbFrame, nsSize(aDesiredSize.Width(), |
|
405 aDesiredSize.Height())); |
|
406 } |
|
407 |
|
408 nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame(); |
|
409 |
|
410 if (rangeProgressFrame) { // display:none? |
|
411 nsHTMLReflowState progressReflowState(aPresContext, aReflowState, |
|
412 rangeProgressFrame, |
|
413 nsSize(aReflowState.ComputedWidth(), |
|
414 NS_UNCONSTRAINEDSIZE)); |
|
415 |
|
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. |
|
419 |
|
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); |
|
431 |
|
432 DoUpdateRangeProgressFrame(rangeProgressFrame, nsSize(aDesiredSize.Width(), |
|
433 aDesiredSize.Height())); |
|
434 } |
|
435 |
|
436 return NS_OK; |
|
437 } |
|
438 |
|
439 #ifdef ACCESSIBILITY |
|
440 a11y::AccType |
|
441 nsRangeFrame::AccessibleType() |
|
442 { |
|
443 return a11y::eHTMLRangeType; |
|
444 } |
|
445 #endif |
|
446 |
|
447 double |
|
448 nsRangeFrame::GetValueAsFractionOfRange() |
|
449 { |
|
450 MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast"); |
|
451 dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent); |
|
452 |
|
453 MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE); |
|
454 |
|
455 Decimal value = input->GetValueAsDecimal(); |
|
456 Decimal minimum = input->GetMinimum(); |
|
457 Decimal maximum = input->GetMaximum(); |
|
458 |
|
459 MOZ_ASSERT(value.isFinite() && minimum.isFinite() && maximum.isFinite(), |
|
460 "type=range should have a default maximum/minimum"); |
|
461 |
|
462 if (maximum <= minimum) { |
|
463 MOZ_ASSERT(value == minimum, "Unsanitized value"); |
|
464 return 0.0; |
|
465 } |
|
466 |
|
467 MOZ_ASSERT(value >= minimum && value <= maximum, "Unsanitized value"); |
|
468 |
|
469 return ((value - minimum) / (maximum - minimum)).toDouble(); |
|
470 } |
|
471 |
|
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"); |
|
478 |
|
479 MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast"); |
|
480 dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent); |
|
481 |
|
482 MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE); |
|
483 |
|
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; |
|
492 |
|
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); |
|
505 |
|
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 } |
|
510 |
|
511 nsRect rangeContentRect = GetContentRectRelativeToSelf(); |
|
512 nsSize thumbSize; |
|
513 |
|
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 } |
|
533 |
|
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 } |
|
559 |
|
560 MOZ_ASSERT(fraction >= 0 && fraction <= 1); |
|
561 return minimum + fraction * range; |
|
562 } |
|
563 |
|
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 } |
|
586 |
|
587 #ifdef ACCESSIBILITY |
|
588 nsAccessibilityService* accService = nsIPresShell::AccService(); |
|
589 if (accService) { |
|
590 accService->RangeValueChanged(PresContext()->PresShell(), mContent); |
|
591 } |
|
592 #endif |
|
593 |
|
594 SchedulePaint(); |
|
595 } |
|
596 |
|
597 void |
|
598 nsRangeFrame::DoUpdateThumbPosition(nsIFrame* aThumbFrame, |
|
599 const nsSize& aRangeSize) |
|
600 { |
|
601 MOZ_ASSERT(aThumbFrame); |
|
602 |
|
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. |
|
610 |
|
611 nsMargin borderAndPadding = GetUsedBorderAndPadding(); |
|
612 nsPoint newPosition(borderAndPadding.left, borderAndPadding.top); |
|
613 |
|
614 nsSize rangeContentBoxSize(aRangeSize); |
|
615 rangeContentBoxSize.width -= borderAndPadding.LeftRight(); |
|
616 rangeContentBoxSize.height -= borderAndPadding.TopBottom(); |
|
617 |
|
618 nsSize thumbSize = aThumbFrame->GetSize(); |
|
619 double fraction = GetValueAsFractionOfRange(); |
|
620 MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0); |
|
621 |
|
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 } |
|
645 |
|
646 void |
|
647 nsRangeFrame::DoUpdateRangeProgressFrame(nsIFrame* aRangeProgressFrame, |
|
648 const nsSize& aRangeSize) |
|
649 { |
|
650 MOZ_ASSERT(aRangeProgressFrame); |
|
651 |
|
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. |
|
660 |
|
661 nsMargin borderAndPadding = GetUsedBorderAndPadding(); |
|
662 nsSize progSize = aRangeProgressFrame->GetSize(); |
|
663 nsRect progRect(borderAndPadding.left, borderAndPadding.top, |
|
664 progSize.width, progSize.height); |
|
665 |
|
666 nsSize rangeContentBoxSize(aRangeSize); |
|
667 rangeContentBoxSize.width -= borderAndPadding.LeftRight(); |
|
668 rangeContentBoxSize.height -= borderAndPadding.TopBottom(); |
|
669 |
|
670 double fraction = GetValueAsFractionOfRange(); |
|
671 MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0); |
|
672 |
|
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 } |
|
690 |
|
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!"); |
|
698 |
|
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 } |
|
727 |
|
728 return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); |
|
729 } |
|
730 |
|
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 |
|
739 |
|
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); |
|
744 |
|
745 nsSize autoSize; |
|
746 |
|
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. |
|
752 |
|
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 } |
|
760 |
|
761 return autoSize; |
|
762 } |
|
763 |
|
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 } |
|
772 |
|
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); |
|
779 |
|
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 } |
|
787 |
|
788 nscoord prefWidth = NSToCoordRound(StyleFont()->mFont.size * |
|
789 nsLayoutUtils::FontSizeInflationFor(this)); // 1em |
|
790 |
|
791 if (isHorizontal) { |
|
792 prefWidth *= LONG_SIDE_TO_SHORT_SIDE_RATIO; |
|
793 } |
|
794 |
|
795 return prefWidth; |
|
796 } |
|
797 |
|
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 } |
|
805 |
|
806 double |
|
807 nsRangeFrame::GetMin() const |
|
808 { |
|
809 return static_cast<dom::HTMLInputElement*>(mContent)->GetMinimum().toDouble(); |
|
810 } |
|
811 |
|
812 double |
|
813 nsRangeFrame::GetMax() const |
|
814 { |
|
815 return static_cast<dom::HTMLInputElement*>(mContent)->GetMaximum().toDouble(); |
|
816 } |
|
817 |
|
818 double |
|
819 nsRangeFrame::GetValue() const |
|
820 { |
|
821 return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal().toDouble(); |
|
822 } |
|
823 |
|
824 nsIAtom* |
|
825 nsRangeFrame::GetType() const |
|
826 { |
|
827 return nsGkAtoms::rangeFrame; |
|
828 } |
|
829 |
|
830 #define STYLES_DISABLING_NATIVE_THEMING \ |
|
831 NS_AUTHOR_SPECIFIED_BACKGROUND | \ |
|
832 NS_AUTHOR_SPECIFIED_PADDING | \ |
|
833 NS_AUTHOR_SPECIFIED_BORDER |
|
834 |
|
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 } |
|
849 |
|
850 Element* |
|
851 nsRangeFrame::GetPseudoElement(nsCSSPseudoElements::Type aType) |
|
852 { |
|
853 if (aType == nsCSSPseudoElements::ePseudo_mozRangeTrack) { |
|
854 return mTrackDiv; |
|
855 } |
|
856 |
|
857 if (aType == nsCSSPseudoElements::ePseudo_mozRangeThumb) { |
|
858 return mThumbDiv; |
|
859 } |
|
860 |
|
861 if (aType == nsCSSPseudoElements::ePseudo_mozRangeProgress) { |
|
862 return mProgressDiv; |
|
863 } |
|
864 |
|
865 return nsContainerFrame::GetPseudoElement(aType); |
|
866 } |
|
867 |
|
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 } |
|
876 |
|
877 void |
|
878 nsRangeFrame::SetAdditionalStyleContext(int32_t aIndex, |
|
879 nsStyleContext* aStyleContext) |
|
880 { |
|
881 MOZ_ASSERT(aIndex == 0, |
|
882 "GetAdditionalStyleContext is handling other indexes?"); |
|
883 |
|
884 // The -moz-focus-outer pseudo-element's style has changed. |
|
885 mOuterFocusStyle = aStyleContext; |
|
886 } |