1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/forms/nsRangeFrame.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,886 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsRangeFrame.h" 1.10 + 1.11 +#include "mozilla/EventStates.h" 1.12 +#include "mozilla/TouchEvents.h" 1.13 + 1.14 +#include "nsContentCreatorFunctions.h" 1.15 +#include "nsContentList.h" 1.16 +#include "nsContentUtils.h" 1.17 +#include "nsCSSRendering.h" 1.18 +#include "nsFormControlFrame.h" 1.19 +#include "nsIContent.h" 1.20 +#include "nsIDocument.h" 1.21 +#include "nsNameSpaceManager.h" 1.22 +#include "nsINodeInfo.h" 1.23 +#include "nsIPresShell.h" 1.24 +#include "nsGkAtoms.h" 1.25 +#include "mozilla/dom/HTMLInputElement.h" 1.26 +#include "nsPresContext.h" 1.27 +#include "nsNodeInfoManager.h" 1.28 +#include "nsRenderingContext.h" 1.29 +#include "mozilla/dom/Element.h" 1.30 +#include "nsStyleSet.h" 1.31 +#include "nsThemeConstants.h" 1.32 + 1.33 +#ifdef ACCESSIBILITY 1.34 +#include "nsAccessibilityService.h" 1.35 +#endif 1.36 + 1.37 +#define LONG_SIDE_TO_SHORT_SIDE_RATIO 10 1.38 + 1.39 +using namespace mozilla; 1.40 +using namespace mozilla::dom; 1.41 + 1.42 +nsIFrame* 1.43 +NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) 1.44 +{ 1.45 + return new (aPresShell) nsRangeFrame(aContext); 1.46 +} 1.47 + 1.48 +nsRangeFrame::nsRangeFrame(nsStyleContext* aContext) 1.49 + : nsContainerFrame(aContext) 1.50 +{ 1.51 +} 1.52 + 1.53 +nsRangeFrame::~nsRangeFrame() 1.54 +{ 1.55 +} 1.56 + 1.57 +NS_IMPL_FRAMEARENA_HELPERS(nsRangeFrame) 1.58 + 1.59 +NS_QUERYFRAME_HEAD(nsRangeFrame) 1.60 + NS_QUERYFRAME_ENTRY(nsRangeFrame) 1.61 + NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) 1.62 +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 1.63 + 1.64 +void 1.65 +nsRangeFrame::Init(nsIContent* aContent, 1.66 + nsIFrame* aParent, 1.67 + nsIFrame* aPrevInFlow) 1.68 +{ 1.69 + // B2G's AsyncPanZoomController::ReceiveInputEvent handles touch events 1.70 + // without checking whether the out-of-process document that it controls 1.71 + // will handle them, unless it has been told that the document might do so. 1.72 + // This is for perf reasons, otherwise it has to wait for the event to be 1.73 + // round-tripped to the other process and back, delaying panning, etc. 1.74 + // We must call SetHasTouchEventListeners() in order to get APZC to wait 1.75 + // until the event has been round-tripped and check whether it has been 1.76 + // handled, otherwise B2G will end up panning the document when the user 1.77 + // tries to drag our thumb. 1.78 + // 1.79 + nsIPresShell* presShell = PresContext()->GetPresShell(); 1.80 + if (presShell) { 1.81 + nsIDocument* document = presShell->GetDocument(); 1.82 + if (document) { 1.83 + nsPIDOMWindow* innerWin = document->GetInnerWindow(); 1.84 + if (innerWin) { 1.85 + innerWin->SetHasTouchEventListeners(); 1.86 + } 1.87 + } 1.88 + } 1.89 + 1.90 + nsStyleSet *styleSet = PresContext()->StyleSet(); 1.91 + 1.92 + mOuterFocusStyle = 1.93 + styleSet->ProbePseudoElementStyle(aContent->AsElement(), 1.94 + nsCSSPseudoElements::ePseudo_mozFocusOuter, 1.95 + StyleContext()); 1.96 + 1.97 + return nsContainerFrame::Init(aContent, aParent, aPrevInFlow); 1.98 +} 1.99 + 1.100 +void 1.101 +nsRangeFrame::DestroyFrom(nsIFrame* aDestructRoot) 1.102 +{ 1.103 + NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(), 1.104 + "nsRangeFrame should not have continuations; if it does we " 1.105 + "need to call RegUnregAccessKey only for the first."); 1.106 + nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false); 1.107 + nsContentUtils::DestroyAnonymousContent(&mTrackDiv); 1.108 + nsContentUtils::DestroyAnonymousContent(&mProgressDiv); 1.109 + nsContentUtils::DestroyAnonymousContent(&mThumbDiv); 1.110 + nsContainerFrame::DestroyFrom(aDestructRoot); 1.111 +} 1.112 + 1.113 +nsresult 1.114 +nsRangeFrame::MakeAnonymousDiv(Element** aResult, 1.115 + nsCSSPseudoElements::Type aPseudoType, 1.116 + nsTArray<ContentInfo>& aElements) 1.117 +{ 1.118 + nsCOMPtr<nsIDocument> doc = mContent->GetDocument(); 1.119 + nsRefPtr<Element> resultElement = doc->CreateHTMLElement(nsGkAtoms::div); 1.120 + 1.121 + // Associate the pseudo-element with the anonymous child. 1.122 + nsRefPtr<nsStyleContext> newStyleContext = 1.123 + PresContext()->StyleSet()->ResolvePseudoElementStyle(mContent->AsElement(), 1.124 + aPseudoType, 1.125 + StyleContext(), 1.126 + resultElement); 1.127 + 1.128 + if (!aElements.AppendElement(ContentInfo(resultElement, newStyleContext))) { 1.129 + return NS_ERROR_OUT_OF_MEMORY; 1.130 + } 1.131 + 1.132 + resultElement.forget(aResult); 1.133 + return NS_OK; 1.134 +} 1.135 + 1.136 +nsresult 1.137 +nsRangeFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements) 1.138 +{ 1.139 + nsresult rv; 1.140 + 1.141 + // Create the ::-moz-range-track pseuto-element (a div): 1.142 + rv = MakeAnonymousDiv(getter_AddRefs(mTrackDiv), 1.143 + nsCSSPseudoElements::ePseudo_mozRangeTrack, 1.144 + aElements); 1.145 + NS_ENSURE_SUCCESS(rv, rv); 1.146 + 1.147 + // Create the ::-moz-range-progress pseudo-element (a div): 1.148 + rv = MakeAnonymousDiv(getter_AddRefs(mProgressDiv), 1.149 + nsCSSPseudoElements::ePseudo_mozRangeProgress, 1.150 + aElements); 1.151 + NS_ENSURE_SUCCESS(rv, rv); 1.152 + 1.153 + // Create the ::-moz-range-thumb pseudo-element (a div): 1.154 + rv = MakeAnonymousDiv(getter_AddRefs(mThumbDiv), 1.155 + nsCSSPseudoElements::ePseudo_mozRangeThumb, 1.156 + aElements); 1.157 + return rv; 1.158 +} 1.159 + 1.160 +void 1.161 +nsRangeFrame::AppendAnonymousContentTo(nsBaseContentList& aElements, 1.162 + uint32_t aFilter) 1.163 +{ 1.164 + aElements.MaybeAppendElement(mTrackDiv); 1.165 + aElements.MaybeAppendElement(mProgressDiv); 1.166 + aElements.MaybeAppendElement(mThumbDiv); 1.167 +} 1.168 + 1.169 +class nsDisplayRangeFocusRing : public nsDisplayItem 1.170 +{ 1.171 +public: 1.172 + nsDisplayRangeFocusRing(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) 1.173 + : nsDisplayItem(aBuilder, aFrame) { 1.174 + MOZ_COUNT_CTOR(nsDisplayRangeFocusRing); 1.175 + } 1.176 +#ifdef NS_BUILD_REFCNT_LOGGING 1.177 + virtual ~nsDisplayRangeFocusRing() { 1.178 + MOZ_COUNT_DTOR(nsDisplayRangeFocusRing); 1.179 + } 1.180 +#endif 1.181 + 1.182 + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) MOZ_OVERRIDE; 1.183 + virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) MOZ_OVERRIDE; 1.184 + NS_DISPLAY_DECL_NAME("RangeFocusRing", TYPE_OUTLINE) 1.185 +}; 1.186 + 1.187 +nsRect 1.188 +nsDisplayRangeFocusRing::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) 1.189 +{ 1.190 + *aSnap = false; 1.191 + nsRect rect(ToReferenceFrame(), Frame()->GetSize()); 1.192 + 1.193 + // We want to paint as if specifying a border for ::-moz-focus-outer 1.194 + // specifies an outline for our frame, so inflate by the border widths: 1.195 + nsStyleContext* styleContext = 1.196 + static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle; 1.197 + MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null"); 1.198 + rect.Inflate(styleContext->StyleBorder()->GetComputedBorder()); 1.199 + 1.200 + return rect; 1.201 +} 1.202 + 1.203 +void 1.204 +nsDisplayRangeFocusRing::Paint(nsDisplayListBuilder* aBuilder, 1.205 + nsRenderingContext* aCtx) 1.206 +{ 1.207 + bool unused; 1.208 + nsStyleContext* styleContext = 1.209 + static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle; 1.210 + MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null"); 1.211 + nsCSSRendering::PaintBorder(mFrame->PresContext(), *aCtx, mFrame, 1.212 + mVisibleRect, GetBounds(aBuilder, &unused), 1.213 + styleContext); 1.214 +} 1.215 + 1.216 +void 1.217 +nsRangeFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 1.218 + const nsRect& aDirtyRect, 1.219 + const nsDisplayListSet& aLists) 1.220 +{ 1.221 + if (IsThemed()) { 1.222 + DisplayBorderBackgroundOutline(aBuilder, aLists); 1.223 + // Only create items for the thumb. Specifically, we do not want 1.224 + // the track to paint, since *our* background is used to paint 1.225 + // the track, and we don't want the unthemed track painting over 1.226 + // the top of the themed track. 1.227 + // This logic is copied from 1.228 + // nsContainerFrame::BuildDisplayListForNonBlockChildren as 1.229 + // called by BuildDisplayListForInline. 1.230 + nsIFrame* thumb = mThumbDiv->GetPrimaryFrame(); 1.231 + if (thumb) { 1.232 + nsDisplayListSet set(aLists, aLists.Content()); 1.233 + BuildDisplayListForChild(aBuilder, thumb, aDirtyRect, set, DISPLAY_CHILD_INLINE); 1.234 + } 1.235 + } else { 1.236 + BuildDisplayListForInline(aBuilder, aDirtyRect, aLists); 1.237 + } 1.238 + 1.239 + // Draw a focus outline if appropriate: 1.240 + 1.241 + if (!aBuilder->IsForPainting() || 1.242 + !IsVisibleForPainting(aBuilder)) { 1.243 + // we don't want the focus ring item for hit-testing or if the item isn't 1.244 + // in the area being [re]painted 1.245 + return; 1.246 + } 1.247 + 1.248 + EventStates eventStates = mContent->AsElement()->State(); 1.249 + if (eventStates.HasState(NS_EVENT_STATE_DISABLED) || 1.250 + !eventStates.HasState(NS_EVENT_STATE_FOCUSRING)) { 1.251 + return; // can't have focus or doesn't match :-moz-focusring 1.252 + } 1.253 + 1.254 + if (!mOuterFocusStyle || 1.255 + !mOuterFocusStyle->StyleBorder()->HasBorder()) { 1.256 + // no ::-moz-focus-outer specified border (how style specifies a focus ring 1.257 + // for range) 1.258 + return; 1.259 + } 1.260 + 1.261 + const nsStyleDisplay *disp = StyleDisplay(); 1.262 + if (IsThemed(disp) && 1.263 + PresContext()->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) { 1.264 + return; // the native theme displays its own visual indication of focus 1.265 + } 1.266 + 1.267 + aLists.Content()->AppendNewToTop( 1.268 + new (aBuilder) nsDisplayRangeFocusRing(aBuilder, this)); 1.269 +} 1.270 + 1.271 +nsresult 1.272 +nsRangeFrame::Reflow(nsPresContext* aPresContext, 1.273 + nsHTMLReflowMetrics& aDesiredSize, 1.274 + const nsHTMLReflowState& aReflowState, 1.275 + nsReflowStatus& aStatus) 1.276 +{ 1.277 + DO_GLOBAL_REFLOW_COUNT("nsRangeFrame"); 1.278 + DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); 1.279 + 1.280 + NS_ASSERTION(mTrackDiv, "::-moz-range-track div must exist!"); 1.281 + NS_ASSERTION(mProgressDiv, "::-moz-range-progress div must exist!"); 1.282 + NS_ASSERTION(mThumbDiv, "::-moz-range-thumb div must exist!"); 1.283 + NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(), 1.284 + "nsRangeFrame should not have continuations; if it does we " 1.285 + "need to call RegUnregAccessKey only for the first."); 1.286 + 1.287 + if (mState & NS_FRAME_FIRST_REFLOW) { 1.288 + nsFormControlFrame::RegUnRegAccessKey(this, true); 1.289 + } 1.290 + 1.291 + nscoord computedHeight = aReflowState.ComputedHeight(); 1.292 + if (computedHeight == NS_AUTOHEIGHT) { 1.293 + computedHeight = 0; 1.294 + } 1.295 + aDesiredSize.Width() = aReflowState.ComputedWidth() + 1.296 + aReflowState.ComputedPhysicalBorderPadding().LeftRight(); 1.297 + aDesiredSize.Height() = computedHeight + 1.298 + aReflowState.ComputedPhysicalBorderPadding().TopBottom(); 1.299 + 1.300 + nsresult rv = 1.301 + ReflowAnonymousContent(aPresContext, aDesiredSize, aReflowState); 1.302 + NS_ENSURE_SUCCESS(rv, rv); 1.303 + 1.304 + aDesiredSize.SetOverflowAreasToDesiredBounds(); 1.305 + 1.306 + nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame(); 1.307 + if (trackFrame) { 1.308 + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, trackFrame); 1.309 + } 1.310 + 1.311 + nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame(); 1.312 + if (rangeProgressFrame) { 1.313 + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rangeProgressFrame); 1.314 + } 1.315 + 1.316 + nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame(); 1.317 + if (thumbFrame) { 1.318 + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, thumbFrame); 1.319 + } 1.320 + 1.321 + FinishAndStoreOverflow(&aDesiredSize); 1.322 + 1.323 + aStatus = NS_FRAME_COMPLETE; 1.324 + 1.325 + NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); 1.326 + 1.327 + return NS_OK; 1.328 +} 1.329 + 1.330 +nsresult 1.331 +nsRangeFrame::ReflowAnonymousContent(nsPresContext* aPresContext, 1.332 + nsHTMLReflowMetrics& aDesiredSize, 1.333 + const nsHTMLReflowState& aReflowState) 1.334 +{ 1.335 + // The width/height of our content box, which is the available width/height 1.336 + // for our anonymous content: 1.337 + nscoord rangeFrameContentBoxWidth = aReflowState.ComputedWidth(); 1.338 + nscoord rangeFrameContentBoxHeight = aReflowState.ComputedHeight(); 1.339 + if (rangeFrameContentBoxHeight == NS_AUTOHEIGHT) { 1.340 + rangeFrameContentBoxHeight = 0; 1.341 + } 1.342 + 1.343 + nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame(); 1.344 + 1.345 + if (trackFrame) { // display:none? 1.346 + 1.347 + // Position the track: 1.348 + // The idea here is that we allow content authors to style the width, 1.349 + // height, border and padding of the track, but we ignore margin and 1.350 + // positioning properties and do the positioning ourself to keep the center 1.351 + // of the track's border box on the center of the nsRangeFrame's content 1.352 + // box. 1.353 + 1.354 + nsHTMLReflowState trackReflowState(aPresContext, aReflowState, trackFrame, 1.355 + nsSize(aReflowState.ComputedWidth(), 1.356 + NS_UNCONSTRAINEDSIZE)); 1.357 + 1.358 + // Find the x/y position of the track frame such that it will be positioned 1.359 + // as described above. These coordinates are with respect to the 1.360 + // nsRangeFrame's border-box. 1.361 + nscoord trackX = rangeFrameContentBoxWidth / 2; 1.362 + nscoord trackY = rangeFrameContentBoxHeight / 2; 1.363 + 1.364 + // Account for the track's border and padding (we ignore its margin): 1.365 + trackX -= trackReflowState.ComputedPhysicalBorderPadding().left + 1.366 + trackReflowState.ComputedWidth() / 2; 1.367 + trackY -= trackReflowState.ComputedPhysicalBorderPadding().top + 1.368 + trackReflowState.ComputedHeight() / 2; 1.369 + 1.370 + // Make relative to our border box instead of our content box: 1.371 + trackX += aReflowState.ComputedPhysicalBorderPadding().left; 1.372 + trackY += aReflowState.ComputedPhysicalBorderPadding().top; 1.373 + 1.374 + nsReflowStatus frameStatus; 1.375 + nsHTMLReflowMetrics trackDesiredSize(aReflowState); 1.376 + nsresult rv = ReflowChild(trackFrame, aPresContext, trackDesiredSize, 1.377 + trackReflowState, trackX, trackY, 0, frameStatus); 1.378 + NS_ENSURE_SUCCESS(rv, rv); 1.379 + MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus), 1.380 + "We gave our child unconstrained height, so it should be complete"); 1.381 + rv = FinishReflowChild(trackFrame, aPresContext, trackDesiredSize, 1.382 + &trackReflowState, trackX, trackY, 0); 1.383 + NS_ENSURE_SUCCESS(rv, rv); 1.384 + } 1.385 + 1.386 + nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame(); 1.387 + 1.388 + if (thumbFrame) { // display:none? 1.389 + nsHTMLReflowState thumbReflowState(aPresContext, aReflowState, thumbFrame, 1.390 + nsSize(aReflowState.ComputedWidth(), 1.391 + NS_UNCONSTRAINEDSIZE)); 1.392 + 1.393 + // Where we position the thumb depends on its size, so we first reflow 1.394 + // the thumb at {0,0} to obtain its size, then position it afterwards. 1.395 + 1.396 + nsReflowStatus frameStatus; 1.397 + nsHTMLReflowMetrics thumbDesiredSize(aReflowState); 1.398 + nsresult rv = ReflowChild(thumbFrame, aPresContext, thumbDesiredSize, 1.399 + thumbReflowState, 0, 0, 0, frameStatus); 1.400 + NS_ENSURE_SUCCESS(rv, rv); 1.401 + MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus), 1.402 + "We gave our child unconstrained height, so it should be complete"); 1.403 + rv = FinishReflowChild(thumbFrame, aPresContext, thumbDesiredSize, 1.404 + &thumbReflowState, 0, 0, 0); 1.405 + NS_ENSURE_SUCCESS(rv, rv); 1.406 + 1.407 + DoUpdateThumbPosition(thumbFrame, nsSize(aDesiredSize.Width(), 1.408 + aDesiredSize.Height())); 1.409 + } 1.410 + 1.411 + nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame(); 1.412 + 1.413 + if (rangeProgressFrame) { // display:none? 1.414 + nsHTMLReflowState progressReflowState(aPresContext, aReflowState, 1.415 + rangeProgressFrame, 1.416 + nsSize(aReflowState.ComputedWidth(), 1.417 + NS_UNCONSTRAINEDSIZE)); 1.418 + 1.419 + // We first reflow the range-progress frame at {0,0} to obtain its 1.420 + // unadjusted dimensions, then we adjust it to so that the appropriate edge 1.421 + // ends at the thumb. 1.422 + 1.423 + nsReflowStatus frameStatus; 1.424 + nsHTMLReflowMetrics progressDesiredSize(aReflowState); 1.425 + nsresult rv = ReflowChild(rangeProgressFrame, aPresContext, 1.426 + progressDesiredSize, progressReflowState, 0, 0, 1.427 + 0, frameStatus); 1.428 + NS_ENSURE_SUCCESS(rv, rv); 1.429 + MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus), 1.430 + "We gave our child unconstrained height, so it should be complete"); 1.431 + rv = FinishReflowChild(rangeProgressFrame, aPresContext, 1.432 + progressDesiredSize, &progressReflowState, 0, 0, 0); 1.433 + NS_ENSURE_SUCCESS(rv, rv); 1.434 + 1.435 + DoUpdateRangeProgressFrame(rangeProgressFrame, nsSize(aDesiredSize.Width(), 1.436 + aDesiredSize.Height())); 1.437 + } 1.438 + 1.439 + return NS_OK; 1.440 +} 1.441 + 1.442 +#ifdef ACCESSIBILITY 1.443 +a11y::AccType 1.444 +nsRangeFrame::AccessibleType() 1.445 +{ 1.446 + return a11y::eHTMLRangeType; 1.447 +} 1.448 +#endif 1.449 + 1.450 +double 1.451 +nsRangeFrame::GetValueAsFractionOfRange() 1.452 +{ 1.453 + MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast"); 1.454 + dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent); 1.455 + 1.456 + MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE); 1.457 + 1.458 + Decimal value = input->GetValueAsDecimal(); 1.459 + Decimal minimum = input->GetMinimum(); 1.460 + Decimal maximum = input->GetMaximum(); 1.461 + 1.462 + MOZ_ASSERT(value.isFinite() && minimum.isFinite() && maximum.isFinite(), 1.463 + "type=range should have a default maximum/minimum"); 1.464 + 1.465 + if (maximum <= minimum) { 1.466 + MOZ_ASSERT(value == minimum, "Unsanitized value"); 1.467 + return 0.0; 1.468 + } 1.469 + 1.470 + MOZ_ASSERT(value >= minimum && value <= maximum, "Unsanitized value"); 1.471 + 1.472 + return ((value - minimum) / (maximum - minimum)).toDouble(); 1.473 +} 1.474 + 1.475 +Decimal 1.476 +nsRangeFrame::GetValueAtEventPoint(WidgetGUIEvent* aEvent) 1.477 +{ 1.478 + MOZ_ASSERT(aEvent->eventStructType == NS_MOUSE_EVENT || 1.479 + aEvent->eventStructType == NS_TOUCH_EVENT, 1.480 + "Unexpected event type - aEvent->refPoint may be meaningless"); 1.481 + 1.482 + MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast"); 1.483 + dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent); 1.484 + 1.485 + MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE); 1.486 + 1.487 + Decimal minimum = input->GetMinimum(); 1.488 + Decimal maximum = input->GetMaximum(); 1.489 + MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(), 1.490 + "type=range should have a default maximum/minimum"); 1.491 + if (maximum <= minimum) { 1.492 + return minimum; 1.493 + } 1.494 + Decimal range = maximum - minimum; 1.495 + 1.496 + LayoutDeviceIntPoint absPoint; 1.497 + if (aEvent->eventStructType == NS_TOUCH_EVENT) { 1.498 + MOZ_ASSERT(aEvent->AsTouchEvent()->touches.Length() == 1, 1.499 + "Unexpected number of touches"); 1.500 + absPoint = LayoutDeviceIntPoint::FromUntyped( 1.501 + aEvent->AsTouchEvent()->touches[0]->mRefPoint); 1.502 + } else { 1.503 + absPoint = aEvent->refPoint; 1.504 + } 1.505 + nsPoint point = 1.506 + nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, 1.507 + LayoutDeviceIntPoint::ToUntyped(absPoint), this); 1.508 + 1.509 + if (point == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) { 1.510 + // We don't want to change the current value for this error state. 1.511 + return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal(); 1.512 + } 1.513 + 1.514 + nsRect rangeContentRect = GetContentRectRelativeToSelf(); 1.515 + nsSize thumbSize; 1.516 + 1.517 + if (IsThemed()) { 1.518 + // We need to get the size of the thumb from the theme. 1.519 + nsPresContext *presContext = PresContext(); 1.520 + nsRefPtr<nsRenderingContext> tmpCtx = 1.521 + presContext->PresShell()->CreateReferenceRenderingContext(); 1.522 + bool notUsedCanOverride; 1.523 + nsIntSize size; 1.524 + presContext->GetTheme()-> 1.525 + GetMinimumWidgetSize(tmpCtx.get(), this, NS_THEME_RANGE_THUMB, &size, 1.526 + ¬UsedCanOverride); 1.527 + thumbSize.width = presContext->DevPixelsToAppUnits(size.width); 1.528 + thumbSize.height = presContext->DevPixelsToAppUnits(size.height); 1.529 + MOZ_ASSERT(thumbSize.width > 0 && thumbSize.height > 0); 1.530 + } else { 1.531 + nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame(); 1.532 + if (thumbFrame) { // diplay:none? 1.533 + thumbSize = thumbFrame->GetSize(); 1.534 + } 1.535 + } 1.536 + 1.537 + Decimal fraction; 1.538 + if (IsHorizontal()) { 1.539 + nscoord traversableDistance = rangeContentRect.width - thumbSize.width; 1.540 + if (traversableDistance <= 0) { 1.541 + return minimum; 1.542 + } 1.543 + nscoord posAtStart = rangeContentRect.x + thumbSize.width/2; 1.544 + nscoord posAtEnd = posAtStart + traversableDistance; 1.545 + nscoord posOfPoint = mozilla::clamped(point.x, posAtStart, posAtEnd); 1.546 + fraction = Decimal(posOfPoint - posAtStart) / traversableDistance; 1.547 + if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { 1.548 + fraction = Decimal(1) - fraction; 1.549 + } 1.550 + } else { 1.551 + nscoord traversableDistance = rangeContentRect.height - thumbSize.height; 1.552 + if (traversableDistance <= 0) { 1.553 + return minimum; 1.554 + } 1.555 + nscoord posAtStart = rangeContentRect.y + thumbSize.height/2; 1.556 + nscoord posAtEnd = posAtStart + traversableDistance; 1.557 + nscoord posOfPoint = mozilla::clamped(point.y, posAtStart, posAtEnd); 1.558 + // For a vertical range, the top (posAtStart) is the highest value, so we 1.559 + // subtract the fraction from 1.0 to get that polarity correct. 1.560 + fraction = Decimal(1) - Decimal(posOfPoint - posAtStart) / traversableDistance; 1.561 + } 1.562 + 1.563 + MOZ_ASSERT(fraction >= 0 && fraction <= 1); 1.564 + return minimum + fraction * range; 1.565 +} 1.566 + 1.567 +void 1.568 +nsRangeFrame::UpdateForValueChange() 1.569 +{ 1.570 + if (NS_SUBTREE_DIRTY(this)) { 1.571 + return; // we're going to be updated when we reflow 1.572 + } 1.573 + nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame(); 1.574 + nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame(); 1.575 + if (!rangeProgressFrame && !thumbFrame) { 1.576 + return; // diplay:none? 1.577 + } 1.578 + if (rangeProgressFrame) { 1.579 + DoUpdateRangeProgressFrame(rangeProgressFrame, GetSize()); 1.580 + } 1.581 + if (thumbFrame) { 1.582 + DoUpdateThumbPosition(thumbFrame, GetSize()); 1.583 + } 1.584 + if (IsThemed()) { 1.585 + // We don't know the exact dimensions or location of the thumb when native 1.586 + // theming is applied, so we just repaint the entire range. 1.587 + InvalidateFrame(); 1.588 + } 1.589 + 1.590 +#ifdef ACCESSIBILITY 1.591 + nsAccessibilityService* accService = nsIPresShell::AccService(); 1.592 + if (accService) { 1.593 + accService->RangeValueChanged(PresContext()->PresShell(), mContent); 1.594 + } 1.595 +#endif 1.596 + 1.597 + SchedulePaint(); 1.598 +} 1.599 + 1.600 +void 1.601 +nsRangeFrame::DoUpdateThumbPosition(nsIFrame* aThumbFrame, 1.602 + const nsSize& aRangeSize) 1.603 +{ 1.604 + MOZ_ASSERT(aThumbFrame); 1.605 + 1.606 + // The idea here is that we want to position the thumb so that the center 1.607 + // of the thumb is on an imaginary line drawn from the middle of one edge 1.608 + // of the range frame's content box to the middle of the opposite edge of 1.609 + // its content box (the opposite edges being the left/right edge if the 1.610 + // range is horizontal, or else the top/bottom edges if the range is 1.611 + // vertical). How far along this line the center of the thumb is placed 1.612 + // depends on the value of the range. 1.613 + 1.614 + nsMargin borderAndPadding = GetUsedBorderAndPadding(); 1.615 + nsPoint newPosition(borderAndPadding.left, borderAndPadding.top); 1.616 + 1.617 + nsSize rangeContentBoxSize(aRangeSize); 1.618 + rangeContentBoxSize.width -= borderAndPadding.LeftRight(); 1.619 + rangeContentBoxSize.height -= borderAndPadding.TopBottom(); 1.620 + 1.621 + nsSize thumbSize = aThumbFrame->GetSize(); 1.622 + double fraction = GetValueAsFractionOfRange(); 1.623 + MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0); 1.624 + 1.625 + // We are called under Reflow, so we need to pass IsHorizontal a valid rect. 1.626 + nsSize frameSizeOverride(aRangeSize.width, aRangeSize.height); 1.627 + if (IsHorizontal(&frameSizeOverride)) { 1.628 + if (thumbSize.width < rangeContentBoxSize.width) { 1.629 + nscoord traversableDistance = 1.630 + rangeContentBoxSize.width - thumbSize.width; 1.631 + if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { 1.632 + newPosition.x += NSToCoordRound((1.0 - fraction) * traversableDistance); 1.633 + } else { 1.634 + newPosition.x += NSToCoordRound(fraction * traversableDistance); 1.635 + } 1.636 + newPosition.y += (rangeContentBoxSize.height - thumbSize.height)/2; 1.637 + } 1.638 + } else { 1.639 + if (thumbSize.height < rangeContentBoxSize.height) { 1.640 + nscoord traversableDistance = 1.641 + rangeContentBoxSize.height - thumbSize.height; 1.642 + newPosition.x += (rangeContentBoxSize.width - thumbSize.width)/2; 1.643 + newPosition.y += NSToCoordRound((1.0 - fraction) * traversableDistance); 1.644 + } 1.645 + } 1.646 + aThumbFrame->SetPosition(newPosition); 1.647 +} 1.648 + 1.649 +void 1.650 +nsRangeFrame::DoUpdateRangeProgressFrame(nsIFrame* aRangeProgressFrame, 1.651 + const nsSize& aRangeSize) 1.652 +{ 1.653 + MOZ_ASSERT(aRangeProgressFrame); 1.654 + 1.655 + // The idea here is that we want to position the ::-moz-range-progress 1.656 + // pseudo-element so that the center line running along its length is on the 1.657 + // corresponding center line of the nsRangeFrame's content box. In the other 1.658 + // dimension, we align the "start" edge of the ::-moz-range-progress 1.659 + // pseudo-element's border-box with the corresponding edge of the 1.660 + // nsRangeFrame's content box, and we size the progress element's border-box 1.661 + // to have a length of GetValueAsFractionOfRange() times the nsRangeFrame's 1.662 + // content-box size. 1.663 + 1.664 + nsMargin borderAndPadding = GetUsedBorderAndPadding(); 1.665 + nsSize progSize = aRangeProgressFrame->GetSize(); 1.666 + nsRect progRect(borderAndPadding.left, borderAndPadding.top, 1.667 + progSize.width, progSize.height); 1.668 + 1.669 + nsSize rangeContentBoxSize(aRangeSize); 1.670 + rangeContentBoxSize.width -= borderAndPadding.LeftRight(); 1.671 + rangeContentBoxSize.height -= borderAndPadding.TopBottom(); 1.672 + 1.673 + double fraction = GetValueAsFractionOfRange(); 1.674 + MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0); 1.675 + 1.676 + // We are called under Reflow, so we need to pass IsHorizontal a valid rect. 1.677 + nsSize frameSizeOverride(aRangeSize.width, aRangeSize.height); 1.678 + if (IsHorizontal(&frameSizeOverride)) { 1.679 + nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.width); 1.680 + if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { 1.681 + progRect.x += rangeContentBoxSize.width - progLength; 1.682 + } 1.683 + progRect.y += (rangeContentBoxSize.height - progSize.height)/2; 1.684 + progRect.width = progLength; 1.685 + } else { 1.686 + nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.height); 1.687 + progRect.x += (rangeContentBoxSize.width - progSize.width)/2; 1.688 + progRect.y += rangeContentBoxSize.height - progLength; 1.689 + progRect.height = progLength; 1.690 + } 1.691 + aRangeProgressFrame->SetRect(progRect); 1.692 +} 1.693 + 1.694 +nsresult 1.695 +nsRangeFrame::AttributeChanged(int32_t aNameSpaceID, 1.696 + nsIAtom* aAttribute, 1.697 + int32_t aModType) 1.698 +{ 1.699 + NS_ASSERTION(mTrackDiv, "The track div must exist!"); 1.700 + NS_ASSERTION(mThumbDiv, "The thumb div must exist!"); 1.701 + 1.702 + if (aNameSpaceID == kNameSpaceID_None) { 1.703 + if (aAttribute == nsGkAtoms::value || 1.704 + aAttribute == nsGkAtoms::min || 1.705 + aAttribute == nsGkAtoms::max || 1.706 + aAttribute == nsGkAtoms::step) { 1.707 + // We want to update the position of the thumb, except in one special 1.708 + // case: If the value attribute is being set, it is possible that we are 1.709 + // in the middle of a type change away from type=range, under the 1.710 + // SetAttr(..., nsGkAtoms::value, ...) call in HTMLInputElement:: 1.711 + // HandleTypeChange. In that case the HTMLInputElement's type will 1.712 + // already have changed, and if we call UpdateForValueChange() 1.713 + // we'll fail the asserts under that call that check the type of our 1.714 + // HTMLInputElement. Given that we're changing away from being a range 1.715 + // and this frame will shortly be destroyed, there's no point in calling 1.716 + // UpdateForValueChange() anyway. 1.717 + MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast"); 1.718 + bool typeIsRange = static_cast<dom::HTMLInputElement*>(mContent)->GetType() == 1.719 + NS_FORM_INPUT_RANGE; 1.720 + // If script changed the <input>'s type before setting these attributes 1.721 + // then we don't need to do anything since we are going to be reframed. 1.722 + if (typeIsRange) { 1.723 + UpdateForValueChange(); 1.724 + } 1.725 + } else if (aAttribute == nsGkAtoms::orient) { 1.726 + PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eResize, 1.727 + NS_FRAME_IS_DIRTY); 1.728 + } 1.729 + } 1.730 + 1.731 + return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); 1.732 +} 1.733 + 1.734 +nsSize 1.735 +nsRangeFrame::ComputeAutoSize(nsRenderingContext *aRenderingContext, 1.736 + nsSize aCBSize, nscoord aAvailableWidth, 1.737 + nsSize aMargin, nsSize aBorder, 1.738 + nsSize aPadding, bool aShrinkWrap) 1.739 +{ 1.740 + nscoord oneEm = NSToCoordRound(StyleFont()->mFont.size * 1.741 + nsLayoutUtils::FontSizeInflationFor(this)); // 1em 1.742 + 1.743 + // frameSizeOverride values just gets us to fall back to being horizontal 1.744 + // (the actual values are irrelevant, as long as width > height): 1.745 + nsSize frameSizeOverride(10,1); 1.746 + bool isHorizontal = IsHorizontal(&frameSizeOverride); 1.747 + 1.748 + nsSize autoSize; 1.749 + 1.750 + // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being 1.751 + // given too small a size when we're natively themed. If we're themed, we set 1.752 + // our "thickness" dimension to zero below and rely on that 1.753 + // GetMinimumWidgetSize check to correct that dimension to the natural 1.754 + // thickness of a slider in the current theme. 1.755 + 1.756 + if (isHorizontal) { 1.757 + autoSize.width = LONG_SIDE_TO_SHORT_SIDE_RATIO * oneEm; 1.758 + autoSize.height = IsThemed() ? 0 : oneEm; 1.759 + } else { 1.760 + autoSize.width = IsThemed() ? 0 : oneEm; 1.761 + autoSize.height = LONG_SIDE_TO_SHORT_SIDE_RATIO * oneEm; 1.762 + } 1.763 + 1.764 + return autoSize; 1.765 +} 1.766 + 1.767 +nscoord 1.768 +nsRangeFrame::GetMinWidth(nsRenderingContext *aRenderingContext) 1.769 +{ 1.770 + // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being 1.771 + // given too small a size when we're natively themed. If we aren't native 1.772 + // themed, we don't mind how small we're sized. 1.773 + return nscoord(0); 1.774 +} 1.775 + 1.776 +nscoord 1.777 +nsRangeFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) 1.778 +{ 1.779 + // frameSizeOverride values just gets us to fall back to being horizontal: 1.780 + nsSize frameSizeOverride(10,1); 1.781 + bool isHorizontal = IsHorizontal(&frameSizeOverride); 1.782 + 1.783 + if (!isHorizontal && IsThemed()) { 1.784 + // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being 1.785 + // given too small a size when we're natively themed. We return zero and 1.786 + // depend on that correction to get our "natuaral" width when we're a 1.787 + // vertical slider. 1.788 + return 0; 1.789 + } 1.790 + 1.791 + nscoord prefWidth = NSToCoordRound(StyleFont()->mFont.size * 1.792 + nsLayoutUtils::FontSizeInflationFor(this)); // 1em 1.793 + 1.794 + if (isHorizontal) { 1.795 + prefWidth *= LONG_SIDE_TO_SHORT_SIDE_RATIO; 1.796 + } 1.797 + 1.798 + return prefWidth; 1.799 +} 1.800 + 1.801 +bool 1.802 +nsRangeFrame::IsHorizontal(const nsSize *aFrameSizeOverride) const 1.803 +{ 1.804 + dom::HTMLInputElement* element = static_cast<dom::HTMLInputElement*>(mContent); 1.805 + return !element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient, 1.806 + nsGkAtoms::vertical, eCaseMatters); 1.807 +} 1.808 + 1.809 +double 1.810 +nsRangeFrame::GetMin() const 1.811 +{ 1.812 + return static_cast<dom::HTMLInputElement*>(mContent)->GetMinimum().toDouble(); 1.813 +} 1.814 + 1.815 +double 1.816 +nsRangeFrame::GetMax() const 1.817 +{ 1.818 + return static_cast<dom::HTMLInputElement*>(mContent)->GetMaximum().toDouble(); 1.819 +} 1.820 + 1.821 +double 1.822 +nsRangeFrame::GetValue() const 1.823 +{ 1.824 + return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal().toDouble(); 1.825 +} 1.826 + 1.827 +nsIAtom* 1.828 +nsRangeFrame::GetType() const 1.829 +{ 1.830 + return nsGkAtoms::rangeFrame; 1.831 +} 1.832 + 1.833 +#define STYLES_DISABLING_NATIVE_THEMING \ 1.834 + NS_AUTHOR_SPECIFIED_BACKGROUND | \ 1.835 + NS_AUTHOR_SPECIFIED_PADDING | \ 1.836 + NS_AUTHOR_SPECIFIED_BORDER 1.837 + 1.838 +bool 1.839 +nsRangeFrame::ShouldUseNativeStyle() const 1.840 +{ 1.841 + return (StyleDisplay()->mAppearance == NS_THEME_RANGE) && 1.842 + !PresContext()->HasAuthorSpecifiedRules(const_cast<nsRangeFrame*>(this), 1.843 + (NS_AUTHOR_SPECIFIED_BORDER | 1.844 + NS_AUTHOR_SPECIFIED_BACKGROUND)) && 1.845 + !PresContext()->HasAuthorSpecifiedRules(mTrackDiv->GetPrimaryFrame(), 1.846 + STYLES_DISABLING_NATIVE_THEMING) && 1.847 + !PresContext()->HasAuthorSpecifiedRules(mProgressDiv->GetPrimaryFrame(), 1.848 + STYLES_DISABLING_NATIVE_THEMING) && 1.849 + !PresContext()->HasAuthorSpecifiedRules(mThumbDiv->GetPrimaryFrame(), 1.850 + STYLES_DISABLING_NATIVE_THEMING); 1.851 +} 1.852 + 1.853 +Element* 1.854 +nsRangeFrame::GetPseudoElement(nsCSSPseudoElements::Type aType) 1.855 +{ 1.856 + if (aType == nsCSSPseudoElements::ePseudo_mozRangeTrack) { 1.857 + return mTrackDiv; 1.858 + } 1.859 + 1.860 + if (aType == nsCSSPseudoElements::ePseudo_mozRangeThumb) { 1.861 + return mThumbDiv; 1.862 + } 1.863 + 1.864 + if (aType == nsCSSPseudoElements::ePseudo_mozRangeProgress) { 1.865 + return mProgressDiv; 1.866 + } 1.867 + 1.868 + return nsContainerFrame::GetPseudoElement(aType); 1.869 +} 1.870 + 1.871 +nsStyleContext* 1.872 +nsRangeFrame::GetAdditionalStyleContext(int32_t aIndex) const 1.873 +{ 1.874 + // We only implement this so that SetAdditionalStyleContext will be 1.875 + // called if style changes that would change the -moz-focus-outer 1.876 + // pseudo-element have occurred. 1.877 + return aIndex == 0 ? mOuterFocusStyle : nullptr; 1.878 +} 1.879 + 1.880 +void 1.881 +nsRangeFrame::SetAdditionalStyleContext(int32_t aIndex, 1.882 + nsStyleContext* aStyleContext) 1.883 +{ 1.884 + MOZ_ASSERT(aIndex == 0, 1.885 + "GetAdditionalStyleContext is handling other indexes?"); 1.886 + 1.887 + // The -moz-focus-outer pseudo-element's style has changed. 1.888 + mOuterFocusStyle = aStyleContext; 1.889 +}