Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 "nsINodeInfo.h"
17 #include "nsNameSpaceManager.h"
18 #include "nsThemeConstants.h"
19 #include "mozilla/BasicEvents.h"
20 #include "mozilla/EventStates.h"
21 #include "nsContentUtils.h"
22 #include "nsContentCreatorFunctions.h"
23 #include "nsContentList.h"
24 #include "nsStyleSet.h"
25 #include "nsIDOMMutationEvent.h"
27 #ifdef ACCESSIBILITY
28 #include "mozilla/a11y/AccTypes.h"
29 #endif
31 using namespace mozilla;
32 using namespace mozilla::dom;
34 nsIFrame*
35 NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
36 {
37 return new (aPresShell) nsNumberControlFrame(aContext);
38 }
40 NS_IMPL_FRAMEARENA_HELPERS(nsNumberControlFrame)
42 NS_QUERYFRAME_HEAD(nsNumberControlFrame)
43 NS_QUERYFRAME_ENTRY(nsNumberControlFrame)
44 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
45 NS_QUERYFRAME_ENTRY(nsITextControlFrame)
46 NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
47 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
49 nsNumberControlFrame::nsNumberControlFrame(nsStyleContext* aContext)
50 : nsContainerFrame(aContext)
51 , mHandlingInputEvent(false)
52 {
53 }
55 void
56 nsNumberControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
57 {
58 NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
59 "nsNumberControlFrame should not have continuations; if it does we "
60 "need to call RegUnregAccessKey only for the first");
61 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
62 nsContentUtils::DestroyAnonymousContent(&mOuterWrapper);
63 nsContainerFrame::DestroyFrom(aDestructRoot);
64 }
66 nscoord
67 nsNumberControlFrame::GetMinWidth(nsRenderingContext* aRenderingContext)
68 {
69 nscoord result;
70 DISPLAY_MIN_WIDTH(this, result);
72 nsIFrame* kid = mFrames.FirstChild();
73 if (kid) { // display:none?
74 result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
75 kid,
76 nsLayoutUtils::MIN_WIDTH);
77 } else {
78 result = 0;
79 }
81 return result;
82 }
84 nscoord
85 nsNumberControlFrame::GetPrefWidth(nsRenderingContext* aRenderingContext)
86 {
87 nscoord result;
88 DISPLAY_PREF_WIDTH(this, result);
90 nsIFrame* kid = mFrames.FirstChild();
91 if (kid) { // display:none?
92 result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
93 kid,
94 nsLayoutUtils::PREF_WIDTH);
95 } else {
96 result = 0;
97 }
99 return result;
100 }
102 nsresult
103 nsNumberControlFrame::Reflow(nsPresContext* aPresContext,
104 nsHTMLReflowMetrics& aDesiredSize,
105 const nsHTMLReflowState& aReflowState,
106 nsReflowStatus& aStatus)
107 {
108 DO_GLOBAL_REFLOW_COUNT("nsNumberControlFrame");
109 DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
111 NS_ASSERTION(mOuterWrapper, "Outer wrapper div must exist!");
113 NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
114 "nsNumberControlFrame should not have continuations; if it does we "
115 "need to call RegUnregAccessKey only for the first");
117 NS_ASSERTION(!mFrames.FirstChild() ||
118 !mFrames.FirstChild()->GetNextSibling(),
119 "We expect at most one direct child frame");
121 if (mState & NS_FRAME_FIRST_REFLOW) {
122 nsFormControlFrame::RegUnRegAccessKey(this, true);
123 }
125 // The width of our content box, which is the available width
126 // for our anonymous content:
127 const nscoord contentBoxWidth = aReflowState.ComputedWidth();
128 nscoord contentBoxHeight = aReflowState.ComputedHeight();
130 nsIFrame* outerWrapperFrame = mOuterWrapper->GetPrimaryFrame();
132 if (!outerWrapperFrame) { // display:none?
133 if (contentBoxHeight == NS_INTRINSICSIZE) {
134 contentBoxHeight = 0;
135 }
136 } else {
137 NS_ASSERTION(outerWrapperFrame == mFrames.FirstChild(), "huh?");
139 nsHTMLReflowMetrics wrappersDesiredSize(aReflowState);
141 nsHTMLReflowState wrapperReflowState(aPresContext, aReflowState,
142 outerWrapperFrame,
143 nsSize(contentBoxWidth,
144 NS_UNCONSTRAINEDSIZE));
146 // offsets of wrapper frame
147 nscoord xoffset = aReflowState.ComputedPhysicalBorderPadding().left +
148 wrapperReflowState.ComputedPhysicalMargin().left;
149 nscoord yoffset = aReflowState.ComputedPhysicalBorderPadding().top +
150 wrapperReflowState.ComputedPhysicalMargin().top;
152 nsReflowStatus childStatus;
153 nsresult rv = ReflowChild(outerWrapperFrame, aPresContext,
154 wrappersDesiredSize, wrapperReflowState,
155 xoffset, yoffset, 0, childStatus);
156 NS_ENSURE_SUCCESS(rv, rv);
157 MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(childStatus),
158 "We gave our child unconstrained height, so it should be complete");
160 nscoord wrappersMarginBoxHeight = wrappersDesiredSize.Height() +
161 wrapperReflowState.ComputedPhysicalMargin().TopBottom();
163 if (contentBoxHeight == NS_INTRINSICSIZE) {
164 // We are intrinsically sized -- we should shrinkwrap the outer wrapper's
165 // height:
166 contentBoxHeight = wrappersMarginBoxHeight;
168 // Make sure we obey min/max-height in the case when we're doing intrinsic
169 // sizing (we get it for free when we have a non-intrinsic
170 // aReflowState.ComputedHeight()). Note that we do this before
171 // adjusting for borderpadding, since mComputedMaxHeight and
172 // mComputedMinHeight are content heights.
173 contentBoxHeight =
174 NS_CSS_MINMAX(contentBoxHeight,
175 aReflowState.ComputedMinHeight(),
176 aReflowState.ComputedMaxHeight());
177 }
179 // Center child vertically
180 nscoord extraSpace = contentBoxHeight - wrappersMarginBoxHeight;
181 yoffset += std::max(0, extraSpace / 2);
183 // Place the child
184 rv = FinishReflowChild(outerWrapperFrame, aPresContext,
185 wrappersDesiredSize, &wrapperReflowState,
186 xoffset, yoffset, 0);
187 NS_ENSURE_SUCCESS(rv, rv);
189 aDesiredSize.SetTopAscent(wrappersDesiredSize.TopAscent() +
190 outerWrapperFrame->GetPosition().y);
191 }
193 aDesiredSize.Width() = contentBoxWidth +
194 aReflowState.ComputedPhysicalBorderPadding().LeftRight();
195 aDesiredSize.Height() = contentBoxHeight +
196 aReflowState.ComputedPhysicalBorderPadding().TopBottom();
198 aDesiredSize.SetOverflowAreasToDesiredBounds();
200 if (outerWrapperFrame) {
201 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, outerWrapperFrame);
202 }
204 FinishAndStoreOverflow(&aDesiredSize);
206 aStatus = NS_FRAME_COMPLETE;
208 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
210 return NS_OK;
211 }
213 void
214 nsNumberControlFrame::SyncDisabledState()
215 {
216 EventStates eventStates = mContent->AsElement()->State();
217 if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
218 mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, EmptyString(),
219 true);
220 } else {
221 mTextField->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
222 }
223 }
225 nsresult
226 nsNumberControlFrame::AttributeChanged(int32_t aNameSpaceID,
227 nsIAtom* aAttribute,
228 int32_t aModType)
229 {
230 // nsGkAtoms::disabled is handled by SyncDisabledState
231 if (aNameSpaceID == kNameSpaceID_None) {
232 if (aAttribute == nsGkAtoms::placeholder ||
233 aAttribute == nsGkAtoms::readonly ||
234 aAttribute == nsGkAtoms::tabindex) {
235 if (aModType == nsIDOMMutationEvent::REMOVAL) {
236 mTextField->UnsetAttr(aNameSpaceID, aAttribute, true);
237 } else {
238 MOZ_ASSERT(aModType == nsIDOMMutationEvent::ADDITION ||
239 aModType == nsIDOMMutationEvent::MODIFICATION);
240 nsAutoString value;
241 mContent->GetAttr(aNameSpaceID, aAttribute, value);
242 mTextField->SetAttr(aNameSpaceID, aAttribute, value, true);
243 }
244 }
245 }
247 return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
248 aModType);
249 }
251 void
252 nsNumberControlFrame::ContentStatesChanged(EventStates aStates)
253 {
254 if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
255 nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
256 }
257 }
259 nsITextControlFrame*
260 nsNumberControlFrame::GetTextFieldFrame()
261 {
262 return do_QueryFrame(GetAnonTextControl()->GetPrimaryFrame());
263 }
265 nsresult
266 nsNumberControlFrame::MakeAnonymousElement(Element** aResult,
267 nsTArray<ContentInfo>& aElements,
268 nsIAtom* aTagName,
269 nsCSSPseudoElements::Type aPseudoType,
270 nsStyleContext* aParentContext)
271 {
272 // Get the NodeInfoManager and tag necessary to create the anonymous divs.
273 nsCOMPtr<nsIDocument> doc = mContent->GetDocument();
274 nsRefPtr<Element> resultElement = doc->CreateHTMLElement(aTagName);
276 // If we legitimately fail this assertion and need to allow
277 // non-pseudo-element anonymous children, then we'll need to add a branch
278 // that calls ResolveStyleFor((*aResult)->AsElement(), aParentContext)") to
279 // set newStyleContext.
280 NS_ASSERTION(aPseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement,
281 "Expecting anonymous children to all be pseudo-elements");
282 // Associate the pseudo-element with the anonymous child
283 nsRefPtr<nsStyleContext> newStyleContext =
284 PresContext()->StyleSet()->ResolvePseudoElementStyle(mContent->AsElement(),
285 aPseudoType,
286 aParentContext,
287 resultElement);
289 if (!aElements.AppendElement(ContentInfo(resultElement, newStyleContext))) {
290 return NS_ERROR_OUT_OF_MEMORY;
291 }
293 if (aPseudoType == nsCSSPseudoElements::ePseudo_mozNumberSpinDown ||
294 aPseudoType == nsCSSPseudoElements::ePseudo_mozNumberSpinUp) {
295 resultElement->SetAttr(kNameSpaceID_None, nsGkAtoms::role,
296 NS_LITERAL_STRING("button"), false);
297 }
299 resultElement.forget(aResult);
300 return NS_OK;
301 }
303 nsresult
304 nsNumberControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
305 {
306 nsresult rv;
308 // We create an anonymous tree for our input element that is structured as
309 // follows:
310 //
311 // input
312 // div - outer wrapper with "display:flex" by default
313 // input - text input field
314 // div - spin box wrapping up/down arrow buttons
315 // div - spin up (up arrow button)
316 // div - spin down (down arrow button)
317 //
318 // If you change this, be careful to change the destruction order in
319 // nsNumberControlFrame::DestroyFrom.
322 // Create the anonymous outer wrapper:
323 rv = MakeAnonymousElement(getter_AddRefs(mOuterWrapper),
324 aElements,
325 nsGkAtoms::div,
326 nsCSSPseudoElements::ePseudo_mozNumberWrapper,
327 mStyleContext);
328 NS_ENSURE_SUCCESS(rv, rv);
330 ContentInfo& outerWrapperCI = aElements.LastElement();
332 // Create the ::-moz-number-text pseudo-element:
333 rv = MakeAnonymousElement(getter_AddRefs(mTextField),
334 outerWrapperCI.mChildren,
335 nsGkAtoms::input,
336 nsCSSPseudoElements::ePseudo_mozNumberText,
337 outerWrapperCI.mStyleContext);
338 NS_ENSURE_SUCCESS(rv, rv);
340 mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
341 NS_LITERAL_STRING("text"), PR_FALSE);
343 HTMLInputElement* content = HTMLInputElement::FromContent(mContent);
344 HTMLInputElement* textField = HTMLInputElement::FromContent(mTextField);
346 // Initialize the text field value:
347 nsAutoString value;
348 content->GetValue(value);
349 SetValueOfAnonTextControl(value);
351 // If we're readonly, make sure our anonymous text control is too:
352 nsAutoString readonly;
353 if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly)) {
354 mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly, false);
355 }
357 // Propogate our tabindex:
358 int32_t tabIndex;
359 content->GetTabIndex(&tabIndex);
360 textField->SetTabIndex(tabIndex);
362 // Initialize the text field's placeholder, if ours is set:
363 nsAutoString placeholder;
364 if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholder)) {
365 mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholder, false);
366 }
368 if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS)) {
369 // We don't want to focus the frame but the text field.
370 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
371 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mTextField);
372 NS_ASSERTION(element, "Really, this should be a nsIDOMElement!");
373 fm->SetFocus(element, 0);
374 }
376 if (StyleDisplay()->mAppearance == NS_THEME_TEXTFIELD) {
377 // The author has elected to hide the spinner by setting this
378 // -moz-appearance. We will reframe if it changes.
379 return rv;
380 }
382 // Create the ::-moz-number-spin-box pseudo-element:
383 rv = MakeAnonymousElement(getter_AddRefs(mSpinBox),
384 outerWrapperCI.mChildren,
385 nsGkAtoms::div,
386 nsCSSPseudoElements::ePseudo_mozNumberSpinBox,
387 outerWrapperCI.mStyleContext);
388 NS_ENSURE_SUCCESS(rv, rv);
390 ContentInfo& spinBoxCI = outerWrapperCI.mChildren.LastElement();
392 // Create the ::-moz-number-spin-up pseudo-element:
393 rv = MakeAnonymousElement(getter_AddRefs(mSpinUp),
394 spinBoxCI.mChildren,
395 nsGkAtoms::div,
396 nsCSSPseudoElements::ePseudo_mozNumberSpinUp,
397 spinBoxCI.mStyleContext);
398 NS_ENSURE_SUCCESS(rv, rv);
400 // Create the ::-moz-number-spin-down pseudo-element:
401 rv = MakeAnonymousElement(getter_AddRefs(mSpinDown),
402 spinBoxCI.mChildren,
403 nsGkAtoms::div,
404 nsCSSPseudoElements::ePseudo_mozNumberSpinDown,
405 spinBoxCI.mStyleContext);
407 SyncDisabledState();
409 return rv;
410 }
412 nsIAtom*
413 nsNumberControlFrame::GetType() const
414 {
415 return nsGkAtoms::numberControlFrame;
416 }
418 NS_IMETHODIMP
419 nsNumberControlFrame::GetEditor(nsIEditor **aEditor)
420 {
421 return GetTextFieldFrame()->GetEditor(aEditor);
422 }
424 NS_IMETHODIMP
425 nsNumberControlFrame::SetSelectionStart(int32_t aSelectionStart)
426 {
427 return GetTextFieldFrame()->SetSelectionStart(aSelectionStart);
428 }
430 NS_IMETHODIMP
431 nsNumberControlFrame::SetSelectionEnd(int32_t aSelectionEnd)
432 {
433 return GetTextFieldFrame()->SetSelectionEnd(aSelectionEnd);
434 }
436 NS_IMETHODIMP
437 nsNumberControlFrame::SetSelectionRange(int32_t aSelectionStart,
438 int32_t aSelectionEnd,
439 SelectionDirection aDirection)
440 {
441 return GetTextFieldFrame()->SetSelectionRange(aSelectionStart, aSelectionEnd,
442 aDirection);
443 }
445 NS_IMETHODIMP
446 nsNumberControlFrame::GetSelectionRange(int32_t* aSelectionStart,
447 int32_t* aSelectionEnd,
448 SelectionDirection* aDirection)
449 {
450 return GetTextFieldFrame()->GetSelectionRange(aSelectionStart, aSelectionEnd,
451 aDirection);
452 }
454 NS_IMETHODIMP
455 nsNumberControlFrame::GetOwnedSelectionController(nsISelectionController** aSelCon)
456 {
457 return GetTextFieldFrame()->GetOwnedSelectionController(aSelCon);
458 }
460 nsFrameSelection*
461 nsNumberControlFrame::GetOwnedFrameSelection()
462 {
463 return GetTextFieldFrame()->GetOwnedFrameSelection();
464 }
466 nsresult
467 nsNumberControlFrame::GetPhonetic(nsAString& aPhonetic)
468 {
469 return GetTextFieldFrame()->GetPhonetic(aPhonetic);
470 }
472 nsresult
473 nsNumberControlFrame::EnsureEditorInitialized()
474 {
475 return GetTextFieldFrame()->EnsureEditorInitialized();
476 }
478 nsresult
479 nsNumberControlFrame::ScrollSelectionIntoView()
480 {
481 return GetTextFieldFrame()->ScrollSelectionIntoView();
482 }
484 void
485 nsNumberControlFrame::SetFocus(bool aOn, bool aRepaint)
486 {
487 GetTextFieldFrame()->SetFocus(aOn, aRepaint);
488 }
490 nsresult
491 nsNumberControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
492 {
493 return GetTextFieldFrame()->SetFormProperty(aName, aValue);
494 }
496 HTMLInputElement*
497 nsNumberControlFrame::GetAnonTextControl()
498 {
499 return mTextField ? HTMLInputElement::FromContent(mTextField) : nullptr;
500 }
502 /* static */ nsNumberControlFrame*
503 nsNumberControlFrame::GetNumberControlFrameForTextField(nsIFrame* aFrame)
504 {
505 // If aFrame is the anon text field for an <input type=number> then we expect
506 // the frame of its mContent's grandparent to be that input's frame. We
507 // have to check for this via the content tree because we don't know whether
508 // extra frames will be wrapped around any of the elements between aFrame and
509 // the nsNumberControlFrame that we're looking for (e.g. flex wrappers).
510 nsIContent* content = aFrame->GetContent();
511 if (content->IsInNativeAnonymousSubtree() &&
512 content->GetParent() && content->GetParent()->GetParent()) {
513 nsIContent* grandparent = content->GetParent()->GetParent();
514 if (grandparent->IsHTML(nsGkAtoms::input) &&
515 grandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
516 nsGkAtoms::number, eCaseMatters)) {
517 return do_QueryFrame(grandparent->GetPrimaryFrame());
518 }
519 }
520 return nullptr;
521 }
523 /* static */ nsNumberControlFrame*
524 nsNumberControlFrame::GetNumberControlFrameForSpinButton(nsIFrame* aFrame)
525 {
526 // If aFrame is a spin button for an <input type=number> then we expect the
527 // frame of its mContent's great-grandparent to be that input's frame. We
528 // have to check for this via the content tree because we don't know whether
529 // extra frames will be wrapped around any of the elements between aFrame and
530 // the nsNumberControlFrame that we're looking for (e.g. flex wrappers).
531 nsIContent* content = aFrame->GetContent();
532 if (content->IsInNativeAnonymousSubtree() &&
533 content->GetParent() && content->GetParent()->GetParent() &&
534 content->GetParent()->GetParent()->GetParent()) {
535 nsIContent* greatgrandparent = content->GetParent()->GetParent()->GetParent();
536 if (greatgrandparent->IsHTML(nsGkAtoms::input) &&
537 greatgrandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
538 nsGkAtoms::number, eCaseMatters)) {
539 return do_QueryFrame(greatgrandparent->GetPrimaryFrame());
540 }
541 }
542 return nullptr;
543 }
545 int32_t
546 nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const
547 {
548 MOZ_ASSERT(aEvent->eventStructType == NS_MOUSE_EVENT,
549 "Unexpected event type");
551 if (!mSpinBox) {
552 // we don't have a spinner
553 return eSpinButtonNone;
554 }
555 if (aEvent->originalTarget == mSpinUp) {
556 return eSpinButtonUp;
557 }
558 if (aEvent->originalTarget == mSpinDown) {
559 return eSpinButtonDown;
560 }
561 if (aEvent->originalTarget == mSpinBox) {
562 // In the case that the up/down buttons are hidden (display:none) we use
563 // just the spin box element, spinning up if the pointer is over the top
564 // half of the element, or down if it's over the bottom half. This is
565 // important to handle since this is the state things are in for the
566 // default UA style sheet. See the comment in forms.css for why.
567 LayoutDeviceIntPoint absPoint = aEvent->refPoint;
568 nsPoint point =
569 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
570 LayoutDeviceIntPoint::ToUntyped(absPoint),
571 mSpinBox->GetPrimaryFrame());
572 if (point != nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
573 if (point.y < mSpinBox->GetPrimaryFrame()->GetSize().height / 2) {
574 return eSpinButtonUp;
575 }
576 return eSpinButtonDown;
577 }
578 }
579 return eSpinButtonNone;
580 }
582 void
583 nsNumberControlFrame::SpinnerStateChanged() const
584 {
585 MOZ_ASSERT(mSpinUp && mSpinDown,
586 "We should not be called when we have no spinner");
588 nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame();
589 if (spinUpFrame && spinUpFrame->IsThemed()) {
590 spinUpFrame->InvalidateFrame();
591 }
592 nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame();
593 if (spinDownFrame && spinDownFrame->IsThemed()) {
594 spinDownFrame->InvalidateFrame();
595 }
596 }
598 bool
599 nsNumberControlFrame::SpinnerUpButtonIsDepressed() const
600 {
601 return HTMLInputElement::FromContent(mContent)->
602 NumberSpinnerUpButtonIsDepressed();
603 }
605 bool
606 nsNumberControlFrame::SpinnerDownButtonIsDepressed() const
607 {
608 return HTMLInputElement::FromContent(mContent)->
609 NumberSpinnerDownButtonIsDepressed();
610 }
612 bool
613 nsNumberControlFrame::IsFocused() const
614 {
615 // Normally this depends on the state of our anonymous text control (which
616 // takes focus for us), but in the case that it does not have a frame we will
617 // have focus ourself.
618 return mTextField->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS) ||
619 mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS);
620 }
622 void
623 nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent)
624 {
625 if (aEvent->originalTarget != mTextField) {
626 // Move focus to our text field
627 HTMLInputElement::FromContent(mTextField)->Focus();
628 }
629 }
631 nsresult
632 nsNumberControlFrame::HandleSelectCall()
633 {
634 return HTMLInputElement::FromContent(mTextField)->Select();
635 }
637 #define STYLES_DISABLING_NATIVE_THEMING \
638 NS_AUTHOR_SPECIFIED_BACKGROUND | \
639 NS_AUTHOR_SPECIFIED_PADDING | \
640 NS_AUTHOR_SPECIFIED_BORDER
642 bool
643 nsNumberControlFrame::ShouldUseNativeStyleForSpinner() const
644 {
645 MOZ_ASSERT(mSpinUp && mSpinDown,
646 "We should not be called when we have no spinner");
648 nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame();
649 nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame();
651 return spinUpFrame &&
652 spinUpFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_UP_BUTTON &&
653 !PresContext()->HasAuthorSpecifiedRules(spinUpFrame,
654 STYLES_DISABLING_NATIVE_THEMING) &&
655 spinDownFrame &&
656 spinDownFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_DOWN_BUTTON &&
657 !PresContext()->HasAuthorSpecifiedRules(spinDownFrame,
658 STYLES_DISABLING_NATIVE_THEMING);
659 }
661 void
662 nsNumberControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
663 uint32_t aFilter)
664 {
665 // Only one direct anonymous child:
666 aElements.MaybeAppendElement(mOuterWrapper);
667 }
669 void
670 nsNumberControlFrame::SetValueOfAnonTextControl(const nsAString& aValue)
671 {
672 if (mHandlingInputEvent) {
673 // We have been called while our HTMLInputElement is processing a DOM
674 // 'input' event targeted at our anonymous text control. Our
675 // HTMLInputElement has taken the value of our anon text control and
676 // called SetValueInternal on itself to keep its own value in sync. As a
677 // result SetValueInternal has called us. In this one case we do not want
678 // to update our anon text control, especially since aValue will be the
679 // sanitized value, and only the internal value should be sanitized (not
680 // the value shown to the user, and certainly we shouldn't change it as
681 // they type).
682 return;
683 }
685 // Init to aValue so that we set aValue as the value of our text control if
686 // aValue isn't a valid number (in which case the HTMLInputElement's validity
687 // state will be set to invalid) or if aValue can't be localized:
688 nsAutoString localizedValue(aValue);
690 #ifdef ENABLE_INTL_API
691 // Try and localize the value we will set:
692 Decimal val = HTMLInputElement::StringToDecimal(aValue);
693 if (val.isFinite()) {
694 ICUUtils::LanguageTagIterForContent langTagIter(mContent);
695 ICUUtils::LocalizeNumber(val.toDouble(), langTagIter, localizedValue);
696 }
697 #endif
699 // We need to update the value of our anonymous text control here. Note that
700 // this must be its value, and not its 'value' attribute (the default value),
701 // since the default value is ignored once a user types into the text
702 // control.
703 HTMLInputElement::FromContent(mTextField)->SetValue(localizedValue);
704 }
706 void
707 nsNumberControlFrame::GetValueOfAnonTextControl(nsAString& aValue)
708 {
709 if (!mTextField) {
710 aValue.Truncate();
711 return;
712 }
714 HTMLInputElement::FromContent(mTextField)->GetValue(aValue);
716 #ifdef ENABLE_INTL_API
717 // Here we need to de-localize any number typed in by the user. That is, we
718 // need to convert it from the number format of the user's language, region,
719 // etc. to the format that the HTML 5 spec defines to be a "valid
720 // floating-point number":
721 //
722 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#floating-point-numbers
723 //
724 // so that it can be parsed by functions like HTMLInputElement::
725 // StringToDecimal (the HTML-5-conforming parsing function) which don't know
726 // how to handle numbers that are formatted differently (for example, with
727 // non-ASCII digits, with grouping separator characters or with a decimal
728 // separator character other than '.').
729 //
730 // We need to be careful to avoid normalizing numbers that are already
731 // formatted for a locale that matches the format of HTML 5's "valid
732 // floating-point number" and have no grouping separator characters. (In
733 // other words we want to return the number as specified by the user, not the
734 // de-localized serialization, since the latter will normalize the value.)
735 // For example, if the user's locale is English and the user types in "2e2"
736 // then inputElement.value should be "2e2" and not "100". This is because
737 // content (and tests) expect us to avoid "normalizing" the number that the
738 // user types in if it's not necessary in order to make sure it conforms to
739 // HTML 5's "valid floating-point number" format.
740 //
741 // Note that we also need to be careful when trying to avoid normalization.
742 // For example, just because "1.234" _looks_ like a valid floating-point
743 // number according to the spec does not mean that it should be returned
744 // as-is. If the user's locale is German, then this represents the value
745 // 1234, not 1.234, so it still needs to be de-localized. Alternatively, if
746 // the user's locale is English and they type in "1,234" we _do_ need to
747 // normalize the number to "1234" because HTML 5's valid floating-point
748 // number format does not allow the ',' grouping separator. We can detect all
749 // the cases where we need to convert by seeing if the locale-specific
750 // parsing function understands the user input to mean the same thing as the
751 // HTML-5-conforming parsing function. If so, then we should return the value
752 // as-is to avoid normalization. Otherwise, we return the de-localized
753 // serialization.
754 ICUUtils::LanguageTagIterForContent langTagIter(mContent);
755 double value = ICUUtils::ParseNumber(aValue, langTagIter);
756 if (NS_finite(value) &&
757 value != HTMLInputElement::StringToDecimal(aValue).toDouble()) {
758 aValue.Truncate();
759 aValue.AppendFloat(value);
760 }
761 #endif
762 // else, we return whatever FromContent put into aValue (the number as typed
763 // in by the user)
764 }
766 bool
767 nsNumberControlFrame::AnonTextControlIsEmpty()
768 {
769 if (!mTextField) {
770 return true;
771 }
772 nsAutoString value;
773 HTMLInputElement::FromContent(mTextField)->GetValue(value);
774 return value.IsEmpty();
775 }
777 Element*
778 nsNumberControlFrame::GetPseudoElement(nsCSSPseudoElements::Type aType)
779 {
780 if (aType == nsCSSPseudoElements::ePseudo_mozNumberWrapper) {
781 return mOuterWrapper;
782 }
784 if (aType == nsCSSPseudoElements::ePseudo_mozNumberText) {
785 return mTextField;
786 }
788 if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinBox) {
789 MOZ_ASSERT(mSpinBox);
790 return mSpinBox;
791 }
793 if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinUp) {
794 MOZ_ASSERT(mSpinUp);
795 return mSpinUp;
796 }
798 if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinDown) {
799 MOZ_ASSERT(mSpinDown);
800 return mSpinDown;
801 }
803 return nsContainerFrame::GetPseudoElement(aType);
804 }
806 #ifdef ACCESSIBILITY
807 a11y::AccType
808 nsNumberControlFrame::AccessibleType()
809 {
810 return a11y::eHTMLSpinnerType;
811 }
812 #endif