Bumping manifests a=b2g-bump
[gecko.git] / layout / forms / nsNumberControlFrame.cpp
bloba2e1d686bfcd52b0b3b593c3685e3171701aa496
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"
9 #include "ICUUtils.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"
28 #ifdef ACCESSIBILITY
29 #include "mozilla/a11y/AccTypes.h"
30 #endif
32 using namespace mozilla;
33 using namespace mozilla::dom;
35 nsIFrame*
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)
56 void
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);
67 nscoord
68 nsNumberControlFrame::GetMinISize(nsRenderingContext* aRenderingContext)
70 nscoord result;
71 DISPLAY_MIN_WIDTH(this, result);
73 nsIFrame* kid = mFrames.FirstChild();
74 if (kid) { // display:none?
75 result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
76 kid,
77 nsLayoutUtils::MIN_ISIZE);
78 } else {
79 result = 0;
82 return result;
85 nscoord
86 nsNumberControlFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
88 nscoord result;
89 DISPLAY_PREF_WIDTH(this, result);
91 nsIFrame* kid = mFrames.FirstChild();
92 if (kid) { // display:none?
93 result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
94 kid,
95 nsLayoutUtils::PREF_ISIZE);
96 } else {
97 result = 0;
100 return result;
103 void
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;
137 } else {
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
166 // height:
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.
174 contentBoxHeight =
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);
184 // Place the child
185 FinishReflowChild(outerWrapperFrame, aPresContext, wrappersDesiredSize,
186 &wrapperReflowState, xoffset, yoffset, 0);
188 aDesiredSize.SetBlockStartAscent(
189 wrappersDesiredSize.BlockStartAscent() +
190 outerWrapperFrame->BStart(aReflowState.GetWritingMode(),
191 contentBoxWidth));
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);
212 void
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(),
218 true);
219 } else {
220 mTextField->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
224 nsresult
225 nsNumberControlFrame::AttributeChanged(int32_t aNameSpaceID,
226 nsIAtom* aAttribute,
227 int32_t aModType)
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);
236 } else {
237 MOZ_ASSERT(aModType == nsIDOMMutationEvent::ADDITION ||
238 aModType == nsIDOMMutationEvent::MODIFICATION);
239 nsAutoString value;
240 mContent->GetAttr(aNameSpaceID, aAttribute, value);
241 mTextField->SetAttr(aNameSpaceID, aAttribute, value, true);
246 return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
247 aModType);
250 void
251 nsNumberControlFrame::ContentStatesChanged(EventStates aStates)
253 if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
254 nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
258 nsITextControlFrame*
259 nsNumberControlFrame::GetTextFieldFrame()
261 return do_QueryFrame(GetAnonTextControl()->GetPrimaryFrame());
264 class FocusTextField : public nsRunnable
266 public:
267 FocusTextField(nsIContent* aNumber, nsIContent* aTextField)
268 : mNumber(aNumber),
269 mTextField(aTextField)
272 NS_IMETHODIMP Run() MOZ_OVERRIDE
274 if (mNumber->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS)) {
275 HTMLInputElement::FromContent(mTextField)->Focus();
278 return NS_OK;
281 private:
282 nsCOMPtr<nsIContent> mNumber;
283 nsCOMPtr<nsIContent> mTextField;
286 nsresult
287 nsNumberControlFrame::MakeAnonymousElement(Element** aResult,
288 nsTArray<ContentInfo>& aElements,
289 nsIAtom* aTagName,
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(),
306 aPseudoType,
307 aParentContext,
308 resultElement);
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);
321 return NS_OK;
324 nsresult
325 nsNumberControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
327 nsresult rv;
329 // We create an anonymous tree for our input element that is structured as
330 // follows:
332 // input
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),
345 aElements,
346 nsGkAtoms::div,
347 nsCSSPseudoElements::ePseudo_mozNumberWrapper,
348 mStyleContext);
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,
356 nsGkAtoms::input,
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:
368 nsAutoString 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:
379 int32_t 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.
398 return rv;
401 // Create the ::-moz-number-spin-box pseudo-element:
402 rv = MakeAnonymousElement(getter_AddRefs(mSpinBox),
403 outerWrapperCI.mChildren,
404 nsGkAtoms::div,
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),
413 spinBoxCI.mChildren,
414 nsGkAtoms::div,
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),
421 spinBoxCI.mChildren,
422 nsGkAtoms::div,
423 nsCSSPseudoElements::ePseudo_mozNumberSpinDown,
424 spinBoxCI.mStyleContext);
426 SyncDisabledState();
428 return rv;
431 nsIAtom*
432 nsNumberControlFrame::GetType() const
434 return nsGkAtoms::numberControlFrame;
437 NS_IMETHODIMP
438 nsNumberControlFrame::GetEditor(nsIEditor **aEditor)
440 return GetTextFieldFrame()->GetEditor(aEditor);
443 NS_IMETHODIMP
444 nsNumberControlFrame::SetSelectionStart(int32_t aSelectionStart)
446 return GetTextFieldFrame()->SetSelectionStart(aSelectionStart);
449 NS_IMETHODIMP
450 nsNumberControlFrame::SetSelectionEnd(int32_t aSelectionEnd)
452 return GetTextFieldFrame()->SetSelectionEnd(aSelectionEnd);
455 NS_IMETHODIMP
456 nsNumberControlFrame::SetSelectionRange(int32_t aSelectionStart,
457 int32_t aSelectionEnd,
458 SelectionDirection aDirection)
460 return GetTextFieldFrame()->SetSelectionRange(aSelectionStart, aSelectionEnd,
461 aDirection);
464 NS_IMETHODIMP
465 nsNumberControlFrame::GetSelectionRange(int32_t* aSelectionStart,
466 int32_t* aSelectionEnd,
467 SelectionDirection* aDirection)
469 return GetTextFieldFrame()->GetSelectionRange(aSelectionStart, aSelectionEnd,
470 aDirection);
473 NS_IMETHODIMP
474 nsNumberControlFrame::GetOwnedSelectionController(nsISelectionController** aSelCon)
476 return GetTextFieldFrame()->GetOwnedSelectionController(aSelCon);
479 nsFrameSelection*
480 nsNumberControlFrame::GetOwnedFrameSelection()
482 return GetTextFieldFrame()->GetOwnedFrameSelection();
485 nsresult
486 nsNumberControlFrame::GetPhonetic(nsAString& aPhonetic)
488 return GetTextFieldFrame()->GetPhonetic(aPhonetic);
491 nsresult
492 nsNumberControlFrame::EnsureEditorInitialized()
494 return GetTextFieldFrame()->EnsureEditorInitialized();
497 nsresult
498 nsNumberControlFrame::ScrollSelectionIntoView()
500 return GetTextFieldFrame()->ScrollSelectionIntoView();
503 void
504 nsNumberControlFrame::SetFocus(bool aOn, bool aRepaint)
506 GetTextFieldFrame()->SetFocus(aOn, aRepaint);
509 nsresult
510 nsNumberControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
512 return GetTextFieldFrame()->SetFormProperty(aName, aValue);
515 HTMLInputElement*
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());
539 return nullptr;
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());
561 return nullptr;
564 int32_t
565 nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const
567 MOZ_ASSERT(aEvent->mClass == eMouseEventClass, "Unexpected event type");
569 if (!mSpinBox) {
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;
586 nsPoint point =
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;
600 void
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();
616 bool
617 nsNumberControlFrame::SpinnerUpButtonIsDepressed() const
619 return HTMLInputElement::FromContent(mContent)->
620 NumberSpinnerUpButtonIsDepressed();
623 bool
624 nsNumberControlFrame::SpinnerDownButtonIsDepressed() const
626 return HTMLInputElement::FromContent(mContent)->
627 NumberSpinnerDownButtonIsDepressed();
630 bool
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);
640 void
641 nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent)
643 if (aEvent->originalTarget != mTextField) {
644 // Move focus to our text field
645 HTMLInputElement::FromContent(mTextField)->Focus();
649 nsresult
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
660 bool
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) &&
673 spinDownFrame &&
674 spinDownFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_DOWN_BUTTON &&
675 !PresContext()->HasAuthorSpecifiedRules(spinDownFrame,
676 STYLES_DISABLING_NATIVE_THEMING);
679 void
680 nsNumberControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
681 uint32_t aFilter)
683 // Only one direct anonymous child:
684 if (mOuterWrapper) {
685 aElements.AppendElement(mOuterWrapper);
689 void
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
701 // they type).
702 return;
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);
717 #endif
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
722 // control.
723 HTMLInputElement::FromContent(mTextField)->SetValue(localizedValue);
726 void
727 nsNumberControlFrame::GetValueOfAnonTextControl(nsAString& aValue)
729 if (!mTextField) {
730 aValue.Truncate();
731 return;
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
773 // serialization.
774 ICUUtils::LanguageTagIterForContent langTagIter(mContent);
775 double value = ICUUtils::ParseNumber(aValue, langTagIter);
776 if (IsFinite(value) &&
777 value != HTMLInputElement::StringToDecimal(aValue).toDouble()) {
778 aValue.Truncate();
779 aValue.AppendFloat(value);
781 #endif
782 // else, we return whatever FromContent put into aValue (the number as typed
783 // in by the user)
786 bool
787 nsNumberControlFrame::AnonTextControlIsEmpty()
789 if (!mTextField) {
790 return true;
792 nsAutoString value;
793 HTMLInputElement::FromContent(mTextField)->GetValue(value);
794 return value.IsEmpty();
797 Element*
798 nsNumberControlFrame::GetPseudoElement(nsCSSPseudoElements::Type aType)
800 if (aType == nsCSSPseudoElements::ePseudo_mozNumberWrapper) {
801 return mOuterWrapper;
804 if (aType == nsCSSPseudoElements::ePseudo_mozNumberText) {
805 return mTextField;
808 if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinBox) {
809 MOZ_ASSERT(mSpinBox);
810 return mSpinBox;
813 if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinUp) {
814 MOZ_ASSERT(mSpinUp);
815 return mSpinUp;
818 if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinDown) {
819 MOZ_ASSERT(mSpinDown);
820 return mSpinDown;
823 return nsContainerFrame::GetPseudoElement(aType);
826 #ifdef ACCESSIBILITY
827 a11y::AccType
828 nsNumberControlFrame::AccessibleType()
830 return a11y::eHTMLSpinnerType;
832 #endif