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 "nsIFocusManager.h"
10 #include "nsIPresShell.h"
11 #include "nsFocusManager.h"
12 #include "nsFontMetrics.h"
13 #include "nsFormControlFrame.h"
14 #include "nsGkAtoms.h"
15 #include "nsINodeInfo.h"
16 #include "nsINameSpaceManager.h"
17 #include "mozilla/BasicEvents.h"
18 #include "nsContentUtils.h"
19 #include "nsContentCreatorFunctions.h"
20 #include "nsContentList.h"
21 #include "nsStyleSet.h"
22 #include "nsIDOMMutationEvent.h"
24 using namespace mozilla
;
25 using namespace mozilla::dom
;
28 NS_NewNumberControlFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
30 return new (aPresShell
) nsNumberControlFrame(aContext
);
33 NS_IMPL_FRAMEARENA_HELPERS(nsNumberControlFrame
)
35 NS_QUERYFRAME_HEAD(nsNumberControlFrame
)
36 NS_QUERYFRAME_ENTRY(nsNumberControlFrame
)
37 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator
)
38 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
40 nsNumberControlFrame::nsNumberControlFrame(nsStyleContext
* aContext
)
41 : nsContainerFrame(aContext
)
42 , mHandlingInputEvent(false)
47 nsNumberControlFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
49 NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
50 "nsNumberControlFrame should not have continuations; if it does we "
51 "need to call RegUnregAccessKey only for the first");
52 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame
*>(this), false);
53 nsContentUtils::DestroyAnonymousContent(&mOuterWrapper
);
54 nsContainerFrame::DestroyFrom(aDestructRoot
);
58 nsNumberControlFrame::Reflow(nsPresContext
* aPresContext
,
59 nsHTMLReflowMetrics
& aDesiredSize
,
60 const nsHTMLReflowState
& aReflowState
,
61 nsReflowStatus
& aStatus
)
63 DO_GLOBAL_REFLOW_COUNT("nsNumberControlFrame");
64 DISPLAY_REFLOW(aPresContext
, this, aReflowState
, aDesiredSize
, aStatus
);
66 NS_ASSERTION(mOuterWrapper
, "Outer wrapper div must exist!");
68 NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
69 "nsNumberControlFrame should not have continuations; if it does we "
70 "need to call RegUnregAccessKey only for the first");
72 NS_ASSERTION(!mFrames
.FirstChild() ||
73 !mFrames
.FirstChild()->GetNextSibling(),
74 "We expect at most one direct child frame");
76 if (mState
& NS_FRAME_FIRST_REFLOW
) {
77 nsFormControlFrame::RegUnRegAccessKey(this, true);
80 nsHTMLReflowMetrics wrappersDesiredSize
;
81 nsIFrame
* outerWrapperFrame
= mOuterWrapper
->GetPrimaryFrame();
82 if (outerWrapperFrame
) { // display:none?
83 NS_ASSERTION(outerWrapperFrame
== mFrames
.FirstChild(), "huh?");
85 ReflowAnonymousContent(aPresContext
, wrappersDesiredSize
,
86 aReflowState
, outerWrapperFrame
);
87 NS_ENSURE_SUCCESS(rv
, rv
);
88 ConsiderChildOverflow(aDesiredSize
.mOverflowAreas
, outerWrapperFrame
);
91 nscoord computedHeight
= aReflowState
.ComputedHeight();
92 if (computedHeight
== NS_AUTOHEIGHT
) {
94 outerWrapperFrame
? outerWrapperFrame
->GetSize().height
: 0;
96 aDesiredSize
.width
= aReflowState
.ComputedWidth() +
97 aReflowState
.mComputedBorderPadding
.LeftRight();
98 aDesiredSize
.height
= computedHeight
+
99 aReflowState
.mComputedBorderPadding
.TopBottom();
101 if (outerWrapperFrame
) {
102 aDesiredSize
.ascent
= wrappersDesiredSize
.ascent
+
103 outerWrapperFrame
->GetPosition().y
;
106 aDesiredSize
.SetOverflowAreasToDesiredBounds();
108 FinishAndStoreOverflow(&aDesiredSize
);
110 aStatus
= NS_FRAME_COMPLETE
;
112 NS_FRAME_SET_TRUNCATION(aStatus
, aReflowState
, aDesiredSize
);
118 nsNumberControlFrame::
119 ReflowAnonymousContent(nsPresContext
* aPresContext
,
120 nsHTMLReflowMetrics
& aWrappersDesiredSize
,
121 const nsHTMLReflowState
& aParentReflowState
,
122 nsIFrame
* aOuterWrapperFrame
)
124 MOZ_ASSERT(aOuterWrapperFrame
);
126 // The width of our content box, which is the available width
127 // for our anonymous content:
128 nscoord inputFrameContentBoxWidth
= aParentReflowState
.ComputedWidth();
130 nsHTMLReflowState
wrapperReflowState(aPresContext
, aParentReflowState
,
132 nsSize(inputFrameContentBoxWidth
,
133 NS_UNCONSTRAINEDSIZE
));
135 nscoord xoffset
= aParentReflowState
.mComputedBorderPadding
.left
+
136 wrapperReflowState
.mComputedMargin
.left
;
137 nscoord yoffset
= aParentReflowState
.mComputedBorderPadding
.top
+
138 wrapperReflowState
.mComputedMargin
.top
;
140 nsReflowStatus childStatus
;
141 nsresult rv
= ReflowChild(aOuterWrapperFrame
, aPresContext
,
142 aWrappersDesiredSize
, wrapperReflowState
,
143 xoffset
, yoffset
, 0, childStatus
);
144 NS_ENSURE_SUCCESS(rv
, rv
);
145 MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(childStatus
),
146 "We gave our child unconstrained height, so it should be complete");
147 return FinishReflowChild(aOuterWrapperFrame
, aPresContext
,
148 &wrapperReflowState
, aWrappersDesiredSize
,
149 xoffset
, yoffset
, 0);
153 nsNumberControlFrame::AttributeChanged(int32_t aNameSpaceID
,
157 if (aNameSpaceID
== kNameSpaceID_None
) {
158 if (aAttribute
== nsGkAtoms::placeholder
||
159 aAttribute
== nsGkAtoms::readonly
||
160 aAttribute
== nsGkAtoms::tabindex
) {
161 if (aModType
== nsIDOMMutationEvent::REMOVAL
) {
162 mTextField
->UnsetAttr(aNameSpaceID
, aAttribute
, true);
164 MOZ_ASSERT(aModType
== nsIDOMMutationEvent::ADDITION
||
165 aModType
== nsIDOMMutationEvent::MODIFICATION
);
167 mContent
->GetAttr(aNameSpaceID
, aAttribute
, value
);
168 mTextField
->SetAttr(aNameSpaceID
, aAttribute
, value
, true);
173 return nsContainerFrame::AttributeChanged(aNameSpaceID
, aAttribute
,
178 nsNumberControlFrame::MakeAnonymousElement(Element
** aResult
,
179 nsTArray
<ContentInfo
>& aElements
,
181 nsCSSPseudoElements::Type aPseudoType
,
182 nsStyleContext
* aParentContext
)
184 // Get the NodeInfoManager and tag necessary to create the anonymous divs.
185 nsCOMPtr
<nsIDocument
> doc
= mContent
->GetDocument();
186 nsRefPtr
<Element
> resultElement
= doc
->CreateHTMLElement(aTagName
);
188 // If we legitimately fail this assertion and need to allow
189 // non-pseudo-element anonymous children, then we'll need to add a branch
190 // that calls ResolveStyleFor((*aResult)->AsElement(), aParentContext)") to
191 // set newStyleContext.
192 NS_ASSERTION(aPseudoType
!= nsCSSPseudoElements::ePseudo_NotPseudoElement
,
193 "Expecting anonymous children to all be pseudo-elements");
194 // Associate the pseudo-element with the anonymous child
195 nsRefPtr
<nsStyleContext
> newStyleContext
=
196 PresContext()->StyleSet()->ResolvePseudoElementStyle(mContent
->AsElement(),
201 if (!aElements
.AppendElement(ContentInfo(resultElement
, newStyleContext
))) {
202 return NS_ERROR_OUT_OF_MEMORY
;
205 resultElement
.forget(aResult
);
210 nsNumberControlFrame::CreateAnonymousContent(nsTArray
<ContentInfo
>& aElements
)
214 // We create an anonymous tree for our input element that is structured as
218 // div - outer wrapper with "display:flex" by default
219 // input - text input field
220 // div - spin box wrapping up/down arrow buttons
221 // div - spin up (up arrow button)
222 // div - spin down (down arrow button)
224 // If you change this, be careful to change the destruction order in
225 // nsNumberControlFrame::DestroyFrom.
228 // Create the anonymous outer wrapper:
229 rv
= MakeAnonymousElement(getter_AddRefs(mOuterWrapper
),
232 nsCSSPseudoElements::ePseudo_mozNumberWrapper
,
234 NS_ENSURE_SUCCESS(rv
, rv
);
236 ContentInfo
& outerWrapperCI
= aElements
.LastElement();
238 // Create the ::-moz-number-text pseudo-element:
239 rv
= MakeAnonymousElement(getter_AddRefs(mTextField
),
240 outerWrapperCI
.mChildren
,
242 nsCSSPseudoElements::ePseudo_mozNumberText
,
243 outerWrapperCI
.mStyleContext
);
244 NS_ENSURE_SUCCESS(rv
, rv
);
246 mTextField
->SetAttr(kNameSpaceID_None
, nsGkAtoms::type
,
247 NS_LITERAL_STRING("text"), PR_FALSE
);
249 HTMLInputElement
* content
= HTMLInputElement::FromContent(mContent
);
250 HTMLInputElement
* textField
= HTMLInputElement::FromContent(mTextField
);
252 // Initialize the text field value:
254 content
->GetValue(value
);
255 mTextField
->SetAttr(kNameSpaceID_None
, nsGkAtoms::value
, value
, false);
257 // If we're readonly, make sure our anonymous text control is too:
258 nsAutoString readonly
;
259 if (mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::readonly
, readonly
)) {
260 mTextField
->SetAttr(kNameSpaceID_None
, nsGkAtoms::readonly
, readonly
, false);
263 // Propogate our tabindex:
265 content
->GetTabIndex(&tabIndex
);
266 textField
->SetTabIndex(tabIndex
);
268 // Initialize the text field's placeholder, if ours is set:
269 nsAutoString placeholder
;
270 if (mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::placeholder
, placeholder
)) {
271 mTextField
->SetAttr(kNameSpaceID_None
, nsGkAtoms::placeholder
, placeholder
, false);
274 if (mContent
->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS
)) {
275 // We don't want to focus the frame but the text field.
276 nsIFocusManager
* fm
= nsFocusManager::GetFocusManager();
277 nsCOMPtr
<nsIDOMElement
> element
= do_QueryInterface(mTextField
);
278 NS_ASSERTION(element
, "Really, this should be a nsIDOMElement!");
279 fm
->SetFocus(element
, 0);
282 // Create the ::-moz-number-spin-box pseudo-element:
283 rv
= MakeAnonymousElement(getter_AddRefs(mSpinBox
),
284 outerWrapperCI
.mChildren
,
286 nsCSSPseudoElements::ePseudo_mozNumberSpinBox
,
287 outerWrapperCI
.mStyleContext
);
288 NS_ENSURE_SUCCESS(rv
, rv
);
290 ContentInfo
& spinBoxCI
= outerWrapperCI
.mChildren
.LastElement();
292 // Create the ::-moz-number-spin-up pseudo-element:
293 rv
= MakeAnonymousElement(getter_AddRefs(mSpinUp
),
296 nsCSSPseudoElements::ePseudo_mozNumberSpinUp
,
297 spinBoxCI
.mStyleContext
);
298 NS_ENSURE_SUCCESS(rv
, rv
);
300 // Create the ::-moz-number-spin-down pseudo-element:
301 rv
= MakeAnonymousElement(getter_AddRefs(mSpinDown
),
304 nsCSSPseudoElements::ePseudo_mozNumberSpinDown
,
305 spinBoxCI
.mStyleContext
);
310 nsNumberControlFrame::GetType() const
312 return nsGkAtoms::numberControlFrame
;
316 nsNumberControlFrame::GetAnonTextControl()
318 return mTextField
? HTMLInputElement::FromContent(mTextField
) : nullptr;
322 nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent
* aEvent
) const
324 MOZ_ASSERT(aEvent
->eventStructType
== NS_MOUSE_EVENT
,
325 "Unexpected event type");
327 if (aEvent
->originalTarget
== mSpinUp
) {
328 return eSpinButtonUp
;
330 if (aEvent
->originalTarget
== mSpinDown
) {
331 return eSpinButtonDown
;
333 return eSpinButtonNone
;
337 nsNumberControlFrame::HandleFocusEvent(WidgetEvent
* aEvent
)
339 if (aEvent
->originalTarget
!= mTextField
) {
340 // Move focus to our text field
341 HTMLInputElement::FromContent(mTextField
)->Focus();
346 nsNumberControlFrame::AppendAnonymousContentTo(nsBaseContentList
& aElements
,
349 // Only one direct anonymous child:
350 aElements
.MaybeAppendElement(mOuterWrapper
);
354 nsNumberControlFrame::UpdateForValueChange(const nsAString
& aValue
)
356 if (mHandlingInputEvent
) {
357 // We have been called while our HTMLInputElement is processing a DOM
358 // 'input' event targeted at our anonymous text control. Our
359 // HTMLInputElement has taken the value of our anon text control and
360 // called SetValueInternal on itself to keep its own value in sync. As a
361 // result SetValueInternal has called us. In this one case we do not want
362 // to update our anon text control, especially since aValue will be the
363 // sanitized value, and only the internal value should be sanitized (not
364 // the value shown to the user, and certainly we shouldn't change it as
368 // We need to update the value of our anonymous text control here. Note that
369 // this must be its value, and not its 'value' attribute (the default value),
370 // since the default value is ignored once a user types into the text
372 HTMLInputElement::FromContent(mTextField
)->SetValue(aValue
);
376 nsNumberControlFrame::GetPseudoElement(nsCSSPseudoElements::Type aType
)
378 if (aType
== nsCSSPseudoElements::ePseudo_mozNumberWrapper
) {
379 return mOuterWrapper
;
382 if (aType
== nsCSSPseudoElements::ePseudo_mozNumberText
) {
386 if (aType
== nsCSSPseudoElements::ePseudo_mozNumberSpinBox
) {
390 if (aType
== nsCSSPseudoElements::ePseudo_mozNumberSpinUp
) {
394 if (aType
== nsCSSPseudoElements::ePseudo_mozNumberSpinDown
) {
398 return nsContainerFrame::GetPseudoElement(aType
);