1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsNumberControlFrame.h"
8 #include "HTMLInputElement.h"
10 #include "nsIFocusManager.h"
11 #include "nsIPresShell.h"
12 #include "nsFocusManager.h"
13 #include "nsFontMetrics.h"
14 #include "nsFormControlFrame.h"
15 #include "nsGkAtoms.h"
16 #include "nsNameSpaceManager.h"
17 #include "nsThemeConstants.h"
18 #include "mozilla/BasicEvents.h"
19 #include "mozilla/EventStates.h"
20 #include "nsContentUtils.h"
21 #include "nsContentCreatorFunctions.h"
22 #include "nsContentList.h"
23 #include "nsStyleSet.h"
24 #include "nsIDOMMutationEvent.h"
25 #include "nsThreadUtils.h"
26 #include "mozilla/FloatingPoint.h"
29 #include "mozilla/a11y/AccTypes.h"
32 using namespace mozilla
;
33 using namespace mozilla::dom
;
36 NS_NewNumberControlFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
38 return new (aPresShell
) nsNumberControlFrame(aContext
);
41 NS_IMPL_FRAMEARENA_HELPERS(nsNumberControlFrame
)
43 NS_QUERYFRAME_HEAD(nsNumberControlFrame
)
44 NS_QUERYFRAME_ENTRY(nsNumberControlFrame
)
45 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator
)
46 NS_QUERYFRAME_ENTRY(nsITextControlFrame
)
47 NS_QUERYFRAME_ENTRY(nsIFormControlFrame
)
48 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
50 nsNumberControlFrame::nsNumberControlFrame(nsStyleContext
* aContext
)
51 : nsContainerFrame(aContext
)
52 , mHandlingInputEvent(false)
57 nsNumberControlFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
59 NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
60 "nsNumberControlFrame should not have continuations; if it does we "
61 "need to call RegUnregAccessKey only for the first");
62 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame
*>(this), false);
63 nsContentUtils::DestroyAnonymousContent(&mOuterWrapper
);
64 nsContainerFrame::DestroyFrom(aDestructRoot
);
68 nsNumberControlFrame::GetMinISize(nsRenderingContext
* aRenderingContext
)
71 DISPLAY_MIN_WIDTH(this, result
);
73 nsIFrame
* kid
= mFrames
.FirstChild();
74 if (kid
) { // display:none?
75 result
= nsLayoutUtils::IntrinsicForContainer(aRenderingContext
,
77 nsLayoutUtils::MIN_ISIZE
);
86 nsNumberControlFrame::GetPrefISize(nsRenderingContext
* aRenderingContext
)
89 DISPLAY_PREF_WIDTH(this, result
);
91 nsIFrame
* kid
= mFrames
.FirstChild();
92 if (kid
) { // display:none?
93 result
= nsLayoutUtils::IntrinsicForContainer(aRenderingContext
,
95 nsLayoutUtils::PREF_ISIZE
);
104 nsNumberControlFrame::Reflow(nsPresContext
* aPresContext
,
105 nsHTMLReflowMetrics
& aDesiredSize
,
106 const nsHTMLReflowState
& aReflowState
,
107 nsReflowStatus
& aStatus
)
109 DO_GLOBAL_REFLOW_COUNT("nsNumberControlFrame");
110 DISPLAY_REFLOW(aPresContext
, this, aReflowState
, aDesiredSize
, aStatus
);
112 NS_ASSERTION(mOuterWrapper
, "Outer wrapper div must exist!");
114 NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
115 "nsNumberControlFrame should not have continuations; if it does we "
116 "need to call RegUnregAccessKey only for the first");
118 NS_ASSERTION(!mFrames
.FirstChild() ||
119 !mFrames
.FirstChild()->GetNextSibling(),
120 "We expect at most one direct child frame");
122 if (mState
& NS_FRAME_FIRST_REFLOW
) {
123 nsFormControlFrame::RegUnRegAccessKey(this, true);
126 // The width of our content box, which is the available width
127 // for our anonymous content:
128 const nscoord contentBoxWidth
= aReflowState
.ComputedWidth();
129 nscoord contentBoxHeight
= aReflowState
.ComputedHeight();
131 nsIFrame
* outerWrapperFrame
= mOuterWrapper
->GetPrimaryFrame();
133 if (!outerWrapperFrame
) { // display:none?
134 if (contentBoxHeight
== NS_INTRINSICSIZE
) {
135 contentBoxHeight
= 0;
138 NS_ASSERTION(outerWrapperFrame
== mFrames
.FirstChild(), "huh?");
140 nsHTMLReflowMetrics
wrappersDesiredSize(aReflowState
);
142 WritingMode wm
= outerWrapperFrame
->GetWritingMode();
143 LogicalSize availSize
= aReflowState
.ComputedSize(wm
);
144 availSize
.BSize(wm
) = NS_UNCONSTRAINEDSIZE
;
146 nsHTMLReflowState
wrapperReflowState(aPresContext
, aReflowState
,
147 outerWrapperFrame
, availSize
);
149 // offsets of wrapper frame
150 nscoord xoffset
= aReflowState
.ComputedPhysicalBorderPadding().left
+
151 wrapperReflowState
.ComputedPhysicalMargin().left
;
152 nscoord yoffset
= aReflowState
.ComputedPhysicalBorderPadding().top
+
153 wrapperReflowState
.ComputedPhysicalMargin().top
;
155 nsReflowStatus childStatus
;
156 ReflowChild(outerWrapperFrame
, aPresContext
, wrappersDesiredSize
,
157 wrapperReflowState
, xoffset
, yoffset
, 0, childStatus
);
158 MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(childStatus
),
159 "We gave our child unconstrained height, so it should be complete");
161 nscoord wrappersMarginBoxHeight
= wrappersDesiredSize
.Height() +
162 wrapperReflowState
.ComputedPhysicalMargin().TopBottom();
164 if (contentBoxHeight
== NS_INTRINSICSIZE
) {
165 // We are intrinsically sized -- we should shrinkwrap the outer wrapper's
167 contentBoxHeight
= wrappersMarginBoxHeight
;
169 // Make sure we obey min/max-height in the case when we're doing intrinsic
170 // sizing (we get it for free when we have a non-intrinsic
171 // aReflowState.ComputedHeight()). Note that we do this before
172 // adjusting for borderpadding, since mComputedMaxHeight and
173 // mComputedMinHeight are content heights.
175 NS_CSS_MINMAX(contentBoxHeight
,
176 aReflowState
.ComputedMinHeight(),
177 aReflowState
.ComputedMaxHeight());
180 // Center child vertically
181 nscoord extraSpace
= contentBoxHeight
- wrappersMarginBoxHeight
;
182 yoffset
+= std::max(0, extraSpace
/ 2);
185 FinishReflowChild(outerWrapperFrame
, aPresContext
, wrappersDesiredSize
,
186 &wrapperReflowState
, xoffset
, yoffset
, 0);
188 aDesiredSize
.SetBlockStartAscent(
189 wrappersDesiredSize
.BlockStartAscent() +
190 outerWrapperFrame
->BStart(aReflowState
.GetWritingMode(),
194 aDesiredSize
.Width() = contentBoxWidth
+
195 aReflowState
.ComputedPhysicalBorderPadding().LeftRight();
196 aDesiredSize
.Height() = contentBoxHeight
+
197 aReflowState
.ComputedPhysicalBorderPadding().TopBottom();
199 aDesiredSize
.SetOverflowAreasToDesiredBounds();
201 if (outerWrapperFrame
) {
202 ConsiderChildOverflow(aDesiredSize
.mOverflowAreas
, outerWrapperFrame
);
205 FinishAndStoreOverflow(&aDesiredSize
);
207 aStatus
= NS_FRAME_COMPLETE
;
209 NS_FRAME_SET_TRUNCATION(aStatus
, aReflowState
, aDesiredSize
);
213 nsNumberControlFrame::SyncDisabledState()
215 EventStates eventStates
= mContent
->AsElement()->State();
216 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
217 mTextField
->SetAttr(kNameSpaceID_None
, nsGkAtoms::disabled
, EmptyString(),
220 mTextField
->UnsetAttr(kNameSpaceID_None
, nsGkAtoms::disabled
, true);
225 nsNumberControlFrame::AttributeChanged(int32_t aNameSpaceID
,
229 // nsGkAtoms::disabled is handled by SyncDisabledState
230 if (aNameSpaceID
== kNameSpaceID_None
) {
231 if (aAttribute
== nsGkAtoms::placeholder
||
232 aAttribute
== nsGkAtoms::readonly
||
233 aAttribute
== nsGkAtoms::tabindex
) {
234 if (aModType
== nsIDOMMutationEvent::REMOVAL
) {
235 mTextField
->UnsetAttr(aNameSpaceID
, aAttribute
, true);
237 MOZ_ASSERT(aModType
== nsIDOMMutationEvent::ADDITION
||
238 aModType
== nsIDOMMutationEvent::MODIFICATION
);
240 mContent
->GetAttr(aNameSpaceID
, aAttribute
, value
);
241 mTextField
->SetAttr(aNameSpaceID
, aAttribute
, value
, true);
246 return nsContainerFrame::AttributeChanged(aNameSpaceID
, aAttribute
,
251 nsNumberControlFrame::ContentStatesChanged(EventStates aStates
)
253 if (aStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
254 nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
259 nsNumberControlFrame::GetTextFieldFrame()
261 return do_QueryFrame(GetAnonTextControl()->GetPrimaryFrame());
264 class FocusTextField
: public nsRunnable
267 FocusTextField(nsIContent
* aNumber
, nsIContent
* aTextField
)
269 mTextField(aTextField
)
272 NS_IMETHODIMP
Run() MOZ_OVERRIDE
274 if (mNumber
->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS
)) {
275 HTMLInputElement::FromContent(mTextField
)->Focus();
282 nsCOMPtr
<nsIContent
> mNumber
;
283 nsCOMPtr
<nsIContent
> mTextField
;
287 nsNumberControlFrame::MakeAnonymousElement(Element
** aResult
,
288 nsTArray
<ContentInfo
>& aElements
,
290 nsCSSPseudoElements::Type aPseudoType
,
291 nsStyleContext
* aParentContext
)
293 // Get the NodeInfoManager and tag necessary to create the anonymous divs.
294 nsCOMPtr
<nsIDocument
> doc
= mContent
->GetComposedDoc();
295 nsRefPtr
<Element
> resultElement
= doc
->CreateHTMLElement(aTagName
);
297 // If we legitimately fail this assertion and need to allow
298 // non-pseudo-element anonymous children, then we'll need to add a branch
299 // that calls ResolveStyleFor((*aResult)->AsElement(), aParentContext)") to
300 // set newStyleContext.
301 NS_ASSERTION(aPseudoType
!= nsCSSPseudoElements::ePseudo_NotPseudoElement
,
302 "Expecting anonymous children to all be pseudo-elements");
303 // Associate the pseudo-element with the anonymous child
304 nsRefPtr
<nsStyleContext
> newStyleContext
=
305 PresContext()->StyleSet()->ResolvePseudoElementStyle(mContent
->AsElement(),
310 if (!aElements
.AppendElement(ContentInfo(resultElement
, newStyleContext
))) {
311 return NS_ERROR_OUT_OF_MEMORY
;
314 if (aPseudoType
== nsCSSPseudoElements::ePseudo_mozNumberSpinDown
||
315 aPseudoType
== nsCSSPseudoElements::ePseudo_mozNumberSpinUp
) {
316 resultElement
->SetAttr(kNameSpaceID_None
, nsGkAtoms::role
,
317 NS_LITERAL_STRING("button"), false);
320 resultElement
.forget(aResult
);
325 nsNumberControlFrame::CreateAnonymousContent(nsTArray
<ContentInfo
>& aElements
)
329 // We create an anonymous tree for our input element that is structured as
333 // div - outer wrapper with "display:flex" by default
334 // input - text input field
335 // div - spin box wrapping up/down arrow buttons
336 // div - spin up (up arrow button)
337 // div - spin down (down arrow button)
339 // If you change this, be careful to change the destruction order in
340 // nsNumberControlFrame::DestroyFrom.
343 // Create the anonymous outer wrapper:
344 rv
= MakeAnonymousElement(getter_AddRefs(mOuterWrapper
),
347 nsCSSPseudoElements::ePseudo_mozNumberWrapper
,
349 NS_ENSURE_SUCCESS(rv
, rv
);
351 ContentInfo
& outerWrapperCI
= aElements
.LastElement();
353 // Create the ::-moz-number-text pseudo-element:
354 rv
= MakeAnonymousElement(getter_AddRefs(mTextField
),
355 outerWrapperCI
.mChildren
,
357 nsCSSPseudoElements::ePseudo_mozNumberText
,
358 outerWrapperCI
.mStyleContext
);
359 NS_ENSURE_SUCCESS(rv
, rv
);
361 mTextField
->SetAttr(kNameSpaceID_None
, nsGkAtoms::type
,
362 NS_LITERAL_STRING("text"), PR_FALSE
);
364 HTMLInputElement
* content
= HTMLInputElement::FromContent(mContent
);
365 HTMLInputElement
* textField
= HTMLInputElement::FromContent(mTextField
);
367 // Initialize the text field value:
369 content
->GetValue(value
);
370 SetValueOfAnonTextControl(value
);
372 // If we're readonly, make sure our anonymous text control is too:
373 nsAutoString readonly
;
374 if (mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::readonly
, readonly
)) {
375 mTextField
->SetAttr(kNameSpaceID_None
, nsGkAtoms::readonly
, readonly
, false);
378 // Propogate our tabindex:
380 content
->GetTabIndex(&tabIndex
);
381 textField
->SetTabIndex(tabIndex
);
383 // Initialize the text field's placeholder, if ours is set:
384 nsAutoString placeholder
;
385 if (mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::placeholder
, placeholder
)) {
386 mTextField
->SetAttr(kNameSpaceID_None
, nsGkAtoms::placeholder
, placeholder
, false);
389 if (mContent
->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS
)) {
390 // We don't want to focus the frame but the text field.
391 nsRefPtr
<FocusTextField
> focusJob
= new FocusTextField(mContent
, mTextField
);
392 nsContentUtils::AddScriptRunner(focusJob
);
395 if (StyleDisplay()->mAppearance
== NS_THEME_TEXTFIELD
) {
396 // The author has elected to hide the spinner by setting this
397 // -moz-appearance. We will reframe if it changes.
401 // Create the ::-moz-number-spin-box pseudo-element:
402 rv
= MakeAnonymousElement(getter_AddRefs(mSpinBox
),
403 outerWrapperCI
.mChildren
,
405 nsCSSPseudoElements::ePseudo_mozNumberSpinBox
,
406 outerWrapperCI
.mStyleContext
);
407 NS_ENSURE_SUCCESS(rv
, rv
);
409 ContentInfo
& spinBoxCI
= outerWrapperCI
.mChildren
.LastElement();
411 // Create the ::-moz-number-spin-up pseudo-element:
412 rv
= MakeAnonymousElement(getter_AddRefs(mSpinUp
),
415 nsCSSPseudoElements::ePseudo_mozNumberSpinUp
,
416 spinBoxCI
.mStyleContext
);
417 NS_ENSURE_SUCCESS(rv
, rv
);
419 // Create the ::-moz-number-spin-down pseudo-element:
420 rv
= MakeAnonymousElement(getter_AddRefs(mSpinDown
),
423 nsCSSPseudoElements::ePseudo_mozNumberSpinDown
,
424 spinBoxCI
.mStyleContext
);
432 nsNumberControlFrame::GetType() const
434 return nsGkAtoms::numberControlFrame
;
438 nsNumberControlFrame::GetEditor(nsIEditor
**aEditor
)
440 return GetTextFieldFrame()->GetEditor(aEditor
);
444 nsNumberControlFrame::SetSelectionStart(int32_t aSelectionStart
)
446 return GetTextFieldFrame()->SetSelectionStart(aSelectionStart
);
450 nsNumberControlFrame::SetSelectionEnd(int32_t aSelectionEnd
)
452 return GetTextFieldFrame()->SetSelectionEnd(aSelectionEnd
);
456 nsNumberControlFrame::SetSelectionRange(int32_t aSelectionStart
,
457 int32_t aSelectionEnd
,
458 SelectionDirection aDirection
)
460 return GetTextFieldFrame()->SetSelectionRange(aSelectionStart
, aSelectionEnd
,
465 nsNumberControlFrame::GetSelectionRange(int32_t* aSelectionStart
,
466 int32_t* aSelectionEnd
,
467 SelectionDirection
* aDirection
)
469 return GetTextFieldFrame()->GetSelectionRange(aSelectionStart
, aSelectionEnd
,
474 nsNumberControlFrame::GetOwnedSelectionController(nsISelectionController
** aSelCon
)
476 return GetTextFieldFrame()->GetOwnedSelectionController(aSelCon
);
480 nsNumberControlFrame::GetOwnedFrameSelection()
482 return GetTextFieldFrame()->GetOwnedFrameSelection();
486 nsNumberControlFrame::GetPhonetic(nsAString
& aPhonetic
)
488 return GetTextFieldFrame()->GetPhonetic(aPhonetic
);
492 nsNumberControlFrame::EnsureEditorInitialized()
494 return GetTextFieldFrame()->EnsureEditorInitialized();
498 nsNumberControlFrame::ScrollSelectionIntoView()
500 return GetTextFieldFrame()->ScrollSelectionIntoView();
504 nsNumberControlFrame::SetFocus(bool aOn
, bool aRepaint
)
506 GetTextFieldFrame()->SetFocus(aOn
, aRepaint
);
510 nsNumberControlFrame::SetFormProperty(nsIAtom
* aName
, const nsAString
& aValue
)
512 return GetTextFieldFrame()->SetFormProperty(aName
, aValue
);
516 nsNumberControlFrame::GetAnonTextControl()
518 return mTextField
? HTMLInputElement::FromContent(mTextField
) : nullptr;
521 /* static */ nsNumberControlFrame
*
522 nsNumberControlFrame::GetNumberControlFrameForTextField(nsIFrame
* aFrame
)
524 // If aFrame is the anon text field for an <input type=number> then we expect
525 // the frame of its mContent's grandparent to be that input's frame. We
526 // have to check for this via the content tree because we don't know whether
527 // extra frames will be wrapped around any of the elements between aFrame and
528 // the nsNumberControlFrame that we're looking for (e.g. flex wrappers).
529 nsIContent
* content
= aFrame
->GetContent();
530 if (content
->IsInNativeAnonymousSubtree() &&
531 content
->GetParent() && content
->GetParent()->GetParent()) {
532 nsIContent
* grandparent
= content
->GetParent()->GetParent();
533 if (grandparent
->IsHTML(nsGkAtoms::input
) &&
534 grandparent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
535 nsGkAtoms::number
, eCaseMatters
)) {
536 return do_QueryFrame(grandparent
->GetPrimaryFrame());
542 /* static */ nsNumberControlFrame
*
543 nsNumberControlFrame::GetNumberControlFrameForSpinButton(nsIFrame
* aFrame
)
545 // If aFrame is a spin button for an <input type=number> then we expect the
546 // frame of its mContent's great-grandparent to be that input's frame. We
547 // have to check for this via the content tree because we don't know whether
548 // extra frames will be wrapped around any of the elements between aFrame and
549 // the nsNumberControlFrame that we're looking for (e.g. flex wrappers).
550 nsIContent
* content
= aFrame
->GetContent();
551 if (content
->IsInNativeAnonymousSubtree() &&
552 content
->GetParent() && content
->GetParent()->GetParent() &&
553 content
->GetParent()->GetParent()->GetParent()) {
554 nsIContent
* greatgrandparent
= content
->GetParent()->GetParent()->GetParent();
555 if (greatgrandparent
->IsHTML(nsGkAtoms::input
) &&
556 greatgrandparent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
557 nsGkAtoms::number
, eCaseMatters
)) {
558 return do_QueryFrame(greatgrandparent
->GetPrimaryFrame());
565 nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent
* aEvent
) const
567 MOZ_ASSERT(aEvent
->mClass
== eMouseEventClass
, "Unexpected event type");
570 // we don't have a spinner
571 return eSpinButtonNone
;
573 if (aEvent
->originalTarget
== mSpinUp
) {
574 return eSpinButtonUp
;
576 if (aEvent
->originalTarget
== mSpinDown
) {
577 return eSpinButtonDown
;
579 if (aEvent
->originalTarget
== mSpinBox
) {
580 // In the case that the up/down buttons are hidden (display:none) we use
581 // just the spin box element, spinning up if the pointer is over the top
582 // half of the element, or down if it's over the bottom half. This is
583 // important to handle since this is the state things are in for the
584 // default UA style sheet. See the comment in forms.css for why.
585 LayoutDeviceIntPoint absPoint
= aEvent
->refPoint
;
587 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent
,
588 LayoutDeviceIntPoint::ToUntyped(absPoint
),
589 mSpinBox
->GetPrimaryFrame());
590 if (point
!= nsPoint(NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
)) {
591 if (point
.y
< mSpinBox
->GetPrimaryFrame()->GetSize().height
/ 2) {
592 return eSpinButtonUp
;
594 return eSpinButtonDown
;
597 return eSpinButtonNone
;
601 nsNumberControlFrame::SpinnerStateChanged() const
603 MOZ_ASSERT(mSpinUp
&& mSpinDown
,
604 "We should not be called when we have no spinner");
606 nsIFrame
* spinUpFrame
= mSpinUp
->GetPrimaryFrame();
607 if (spinUpFrame
&& spinUpFrame
->IsThemed()) {
608 spinUpFrame
->InvalidateFrame();
610 nsIFrame
* spinDownFrame
= mSpinDown
->GetPrimaryFrame();
611 if (spinDownFrame
&& spinDownFrame
->IsThemed()) {
612 spinDownFrame
->InvalidateFrame();
617 nsNumberControlFrame::SpinnerUpButtonIsDepressed() const
619 return HTMLInputElement::FromContent(mContent
)->
620 NumberSpinnerUpButtonIsDepressed();
624 nsNumberControlFrame::SpinnerDownButtonIsDepressed() const
626 return HTMLInputElement::FromContent(mContent
)->
627 NumberSpinnerDownButtonIsDepressed();
631 nsNumberControlFrame::IsFocused() const
633 // Normally this depends on the state of our anonymous text control (which
634 // takes focus for us), but in the case that it does not have a frame we will
635 // have focus ourself.
636 return mTextField
->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS
) ||
637 mContent
->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS
);
641 nsNumberControlFrame::HandleFocusEvent(WidgetEvent
* aEvent
)
643 if (aEvent
->originalTarget
!= mTextField
) {
644 // Move focus to our text field
645 HTMLInputElement::FromContent(mTextField
)->Focus();
650 nsNumberControlFrame::HandleSelectCall()
652 return HTMLInputElement::FromContent(mTextField
)->Select();
655 #define STYLES_DISABLING_NATIVE_THEMING \
656 NS_AUTHOR_SPECIFIED_BACKGROUND | \
657 NS_AUTHOR_SPECIFIED_PADDING | \
658 NS_AUTHOR_SPECIFIED_BORDER
661 nsNumberControlFrame::ShouldUseNativeStyleForSpinner() const
663 MOZ_ASSERT(mSpinUp
&& mSpinDown
,
664 "We should not be called when we have no spinner");
666 nsIFrame
* spinUpFrame
= mSpinUp
->GetPrimaryFrame();
667 nsIFrame
* spinDownFrame
= mSpinDown
->GetPrimaryFrame();
669 return spinUpFrame
&&
670 spinUpFrame
->StyleDisplay()->mAppearance
== NS_THEME_SPINNER_UP_BUTTON
&&
671 !PresContext()->HasAuthorSpecifiedRules(spinUpFrame
,
672 STYLES_DISABLING_NATIVE_THEMING
) &&
674 spinDownFrame
->StyleDisplay()->mAppearance
== NS_THEME_SPINNER_DOWN_BUTTON
&&
675 !PresContext()->HasAuthorSpecifiedRules(spinDownFrame
,
676 STYLES_DISABLING_NATIVE_THEMING
);
680 nsNumberControlFrame::AppendAnonymousContentTo(nsTArray
<nsIContent
*>& aElements
,
683 // Only one direct anonymous child:
685 aElements
.AppendElement(mOuterWrapper
);
690 nsNumberControlFrame::SetValueOfAnonTextControl(const nsAString
& aValue
)
692 if (mHandlingInputEvent
) {
693 // We have been called while our HTMLInputElement is processing a DOM
694 // 'input' event targeted at our anonymous text control. Our
695 // HTMLInputElement has taken the value of our anon text control and
696 // called SetValueInternal on itself to keep its own value in sync. As a
697 // result SetValueInternal has called us. In this one case we do not want
698 // to update our anon text control, especially since aValue will be the
699 // sanitized value, and only the internal value should be sanitized (not
700 // the value shown to the user, and certainly we shouldn't change it as
705 // Init to aValue so that we set aValue as the value of our text control if
706 // aValue isn't a valid number (in which case the HTMLInputElement's validity
707 // state will be set to invalid) or if aValue can't be localized:
708 nsAutoString
localizedValue(aValue
);
710 #ifdef ENABLE_INTL_API
711 // Try and localize the value we will set:
712 Decimal val
= HTMLInputElement::StringToDecimal(aValue
);
713 if (val
.isFinite()) {
714 ICUUtils::LanguageTagIterForContent
langTagIter(mContent
);
715 ICUUtils::LocalizeNumber(val
.toDouble(), langTagIter
, localizedValue
);
719 // We need to update the value of our anonymous text control here. Note that
720 // this must be its value, and not its 'value' attribute (the default value),
721 // since the default value is ignored once a user types into the text
723 HTMLInputElement::FromContent(mTextField
)->SetValue(localizedValue
);
727 nsNumberControlFrame::GetValueOfAnonTextControl(nsAString
& aValue
)
734 HTMLInputElement::FromContent(mTextField
)->GetValue(aValue
);
736 #ifdef ENABLE_INTL_API
737 // Here we need to de-localize any number typed in by the user. That is, we
738 // need to convert it from the number format of the user's language, region,
739 // etc. to the format that the HTML 5 spec defines to be a "valid
740 // floating-point number":
742 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#floating-point-numbers
744 // so that it can be parsed by functions like HTMLInputElement::
745 // StringToDecimal (the HTML-5-conforming parsing function) which don't know
746 // how to handle numbers that are formatted differently (for example, with
747 // non-ASCII digits, with grouping separator characters or with a decimal
748 // separator character other than '.').
750 // We need to be careful to avoid normalizing numbers that are already
751 // formatted for a locale that matches the format of HTML 5's "valid
752 // floating-point number" and have no grouping separator characters. (In
753 // other words we want to return the number as specified by the user, not the
754 // de-localized serialization, since the latter will normalize the value.)
755 // For example, if the user's locale is English and the user types in "2e2"
756 // then inputElement.value should be "2e2" and not "100". This is because
757 // content (and tests) expect us to avoid "normalizing" the number that the
758 // user types in if it's not necessary in order to make sure it conforms to
759 // HTML 5's "valid floating-point number" format.
761 // Note that we also need to be careful when trying to avoid normalization.
762 // For example, just because "1.234" _looks_ like a valid floating-point
763 // number according to the spec does not mean that it should be returned
764 // as-is. If the user's locale is German, then this represents the value
765 // 1234, not 1.234, so it still needs to be de-localized. Alternatively, if
766 // the user's locale is English and they type in "1,234" we _do_ need to
767 // normalize the number to "1234" because HTML 5's valid floating-point
768 // number format does not allow the ',' grouping separator. We can detect all
769 // the cases where we need to convert by seeing if the locale-specific
770 // parsing function understands the user input to mean the same thing as the
771 // HTML-5-conforming parsing function. If so, then we should return the value
772 // as-is to avoid normalization. Otherwise, we return the de-localized
774 ICUUtils::LanguageTagIterForContent
langTagIter(mContent
);
775 double value
= ICUUtils::ParseNumber(aValue
, langTagIter
);
776 if (IsFinite(value
) &&
777 value
!= HTMLInputElement::StringToDecimal(aValue
).toDouble()) {
779 aValue
.AppendFloat(value
);
782 // else, we return whatever FromContent put into aValue (the number as typed
787 nsNumberControlFrame::AnonTextControlIsEmpty()
793 HTMLInputElement::FromContent(mTextField
)->GetValue(value
);
794 return value
.IsEmpty();
798 nsNumberControlFrame::GetPseudoElement(nsCSSPseudoElements::Type aType
)
800 if (aType
== nsCSSPseudoElements::ePseudo_mozNumberWrapper
) {
801 return mOuterWrapper
;
804 if (aType
== nsCSSPseudoElements::ePseudo_mozNumberText
) {
808 if (aType
== nsCSSPseudoElements::ePseudo_mozNumberSpinBox
) {
809 MOZ_ASSERT(mSpinBox
);
813 if (aType
== nsCSSPseudoElements::ePseudo_mozNumberSpinUp
) {
818 if (aType
== nsCSSPseudoElements::ePseudo_mozNumberSpinDown
) {
819 MOZ_ASSERT(mSpinDown
);
823 return nsContainerFrame::GetPseudoElement(aType
);
828 nsNumberControlFrame::AccessibleType()
830 return a11y::eHTMLSpinnerType
;