Backed out changesets 1b268ec6b81e, b68f5c69dce2 (bug 935508) and changesets 29db4402...
[gecko.git] / layout / forms / nsNumberControlFrame.cpp
blobb64ff2477681f4b9b1f98a8f832dc357550ededd
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;
27 nsIFrame*
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)
46 void
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);
57 NS_IMETHODIMP
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?");
84 nsresult rv =
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) {
93 computedHeight =
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);
114 return NS_OK;
117 nsresult
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,
131 aOuterWrapperFrame,
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);
152 NS_IMETHODIMP
153 nsNumberControlFrame::AttributeChanged(int32_t aNameSpaceID,
154 nsIAtom* aAttribute,
155 int32_t aModType)
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);
163 } else {
164 MOZ_ASSERT(aModType == nsIDOMMutationEvent::ADDITION ||
165 aModType == nsIDOMMutationEvent::MODIFICATION);
166 nsAutoString value;
167 mContent->GetAttr(aNameSpaceID, aAttribute, value);
168 mTextField->SetAttr(aNameSpaceID, aAttribute, value, true);
173 return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
174 aModType);
177 nsresult
178 nsNumberControlFrame::MakeAnonymousElement(Element** aResult,
179 nsTArray<ContentInfo>& aElements,
180 nsIAtom* aTagName,
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(),
197 aPseudoType,
198 aParentContext,
199 resultElement);
201 if (!aElements.AppendElement(ContentInfo(resultElement, newStyleContext))) {
202 return NS_ERROR_OUT_OF_MEMORY;
205 resultElement.forget(aResult);
206 return NS_OK;
209 nsresult
210 nsNumberControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
212 nsresult rv;
214 // We create an anonymous tree for our input element that is structured as
215 // follows:
217 // input
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),
230 aElements,
231 nsGkAtoms::div,
232 nsCSSPseudoElements::ePseudo_mozNumberWrapper,
233 mStyleContext);
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,
241 nsGkAtoms::input,
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:
253 nsAutoString 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:
264 int32_t 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,
285 nsGkAtoms::div,
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),
294 spinBoxCI.mChildren,
295 nsGkAtoms::div,
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),
302 spinBoxCI.mChildren,
303 nsGkAtoms::div,
304 nsCSSPseudoElements::ePseudo_mozNumberSpinDown,
305 spinBoxCI.mStyleContext);
306 return rv;
309 nsIAtom*
310 nsNumberControlFrame::GetType() const
312 return nsGkAtoms::numberControlFrame;
315 HTMLInputElement*
316 nsNumberControlFrame::GetAnonTextControl()
318 return mTextField ? HTMLInputElement::FromContent(mTextField) : nullptr;
321 int32_t
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;
336 void
337 nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent)
339 if (aEvent->originalTarget != mTextField) {
340 // Move focus to our text field
341 HTMLInputElement::FromContent(mTextField)->Focus();
345 void
346 nsNumberControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
347 uint32_t aFilter)
349 // Only one direct anonymous child:
350 aElements.MaybeAppendElement(mOuterWrapper);
353 void
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
365 // they type).
366 return;
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
371 // control.
372 HTMLInputElement::FromContent(mTextField)->SetValue(aValue);
375 Element*
376 nsNumberControlFrame::GetPseudoElement(nsCSSPseudoElements::Type aType)
378 if (aType == nsCSSPseudoElements::ePseudo_mozNumberWrapper) {
379 return mOuterWrapper;
382 if (aType == nsCSSPseudoElements::ePseudo_mozNumberText) {
383 return mTextField;
386 if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinBox) {
387 return mSpinBox;
390 if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinUp) {
391 return mSpinUp;
394 if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinDown) {
395 return mSpinDown;
398 return nsContainerFrame::GetPseudoElement(aType);