|
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/. */ |
|
5 |
|
6 #include "nsNumberControlFrame.h" |
|
7 |
|
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" |
|
26 |
|
27 #ifdef ACCESSIBILITY |
|
28 #include "mozilla/a11y/AccTypes.h" |
|
29 #endif |
|
30 |
|
31 using namespace mozilla; |
|
32 using namespace mozilla::dom; |
|
33 |
|
34 nsIFrame* |
|
35 NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) |
|
36 { |
|
37 return new (aPresShell) nsNumberControlFrame(aContext); |
|
38 } |
|
39 |
|
40 NS_IMPL_FRAMEARENA_HELPERS(nsNumberControlFrame) |
|
41 |
|
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) |
|
48 |
|
49 nsNumberControlFrame::nsNumberControlFrame(nsStyleContext* aContext) |
|
50 : nsContainerFrame(aContext) |
|
51 , mHandlingInputEvent(false) |
|
52 { |
|
53 } |
|
54 |
|
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 } |
|
65 |
|
66 nscoord |
|
67 nsNumberControlFrame::GetMinWidth(nsRenderingContext* aRenderingContext) |
|
68 { |
|
69 nscoord result; |
|
70 DISPLAY_MIN_WIDTH(this, result); |
|
71 |
|
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 } |
|
80 |
|
81 return result; |
|
82 } |
|
83 |
|
84 nscoord |
|
85 nsNumberControlFrame::GetPrefWidth(nsRenderingContext* aRenderingContext) |
|
86 { |
|
87 nscoord result; |
|
88 DISPLAY_PREF_WIDTH(this, result); |
|
89 |
|
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 } |
|
98 |
|
99 return result; |
|
100 } |
|
101 |
|
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); |
|
110 |
|
111 NS_ASSERTION(mOuterWrapper, "Outer wrapper div must exist!"); |
|
112 |
|
113 NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(), |
|
114 "nsNumberControlFrame should not have continuations; if it does we " |
|
115 "need to call RegUnregAccessKey only for the first"); |
|
116 |
|
117 NS_ASSERTION(!mFrames.FirstChild() || |
|
118 !mFrames.FirstChild()->GetNextSibling(), |
|
119 "We expect at most one direct child frame"); |
|
120 |
|
121 if (mState & NS_FRAME_FIRST_REFLOW) { |
|
122 nsFormControlFrame::RegUnRegAccessKey(this, true); |
|
123 } |
|
124 |
|
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(); |
|
129 |
|
130 nsIFrame* outerWrapperFrame = mOuterWrapper->GetPrimaryFrame(); |
|
131 |
|
132 if (!outerWrapperFrame) { // display:none? |
|
133 if (contentBoxHeight == NS_INTRINSICSIZE) { |
|
134 contentBoxHeight = 0; |
|
135 } |
|
136 } else { |
|
137 NS_ASSERTION(outerWrapperFrame == mFrames.FirstChild(), "huh?"); |
|
138 |
|
139 nsHTMLReflowMetrics wrappersDesiredSize(aReflowState); |
|
140 |
|
141 nsHTMLReflowState wrapperReflowState(aPresContext, aReflowState, |
|
142 outerWrapperFrame, |
|
143 nsSize(contentBoxWidth, |
|
144 NS_UNCONSTRAINEDSIZE)); |
|
145 |
|
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; |
|
151 |
|
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"); |
|
159 |
|
160 nscoord wrappersMarginBoxHeight = wrappersDesiredSize.Height() + |
|
161 wrapperReflowState.ComputedPhysicalMargin().TopBottom(); |
|
162 |
|
163 if (contentBoxHeight == NS_INTRINSICSIZE) { |
|
164 // We are intrinsically sized -- we should shrinkwrap the outer wrapper's |
|
165 // height: |
|
166 contentBoxHeight = wrappersMarginBoxHeight; |
|
167 |
|
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 } |
|
178 |
|
179 // Center child vertically |
|
180 nscoord extraSpace = contentBoxHeight - wrappersMarginBoxHeight; |
|
181 yoffset += std::max(0, extraSpace / 2); |
|
182 |
|
183 // Place the child |
|
184 rv = FinishReflowChild(outerWrapperFrame, aPresContext, |
|
185 wrappersDesiredSize, &wrapperReflowState, |
|
186 xoffset, yoffset, 0); |
|
187 NS_ENSURE_SUCCESS(rv, rv); |
|
188 |
|
189 aDesiredSize.SetTopAscent(wrappersDesiredSize.TopAscent() + |
|
190 outerWrapperFrame->GetPosition().y); |
|
191 } |
|
192 |
|
193 aDesiredSize.Width() = contentBoxWidth + |
|
194 aReflowState.ComputedPhysicalBorderPadding().LeftRight(); |
|
195 aDesiredSize.Height() = contentBoxHeight + |
|
196 aReflowState.ComputedPhysicalBorderPadding().TopBottom(); |
|
197 |
|
198 aDesiredSize.SetOverflowAreasToDesiredBounds(); |
|
199 |
|
200 if (outerWrapperFrame) { |
|
201 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, outerWrapperFrame); |
|
202 } |
|
203 |
|
204 FinishAndStoreOverflow(&aDesiredSize); |
|
205 |
|
206 aStatus = NS_FRAME_COMPLETE; |
|
207 |
|
208 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); |
|
209 |
|
210 return NS_OK; |
|
211 } |
|
212 |
|
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 } |
|
224 |
|
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 } |
|
246 |
|
247 return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, |
|
248 aModType); |
|
249 } |
|
250 |
|
251 void |
|
252 nsNumberControlFrame::ContentStatesChanged(EventStates aStates) |
|
253 { |
|
254 if (aStates.HasState(NS_EVENT_STATE_DISABLED)) { |
|
255 nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this)); |
|
256 } |
|
257 } |
|
258 |
|
259 nsITextControlFrame* |
|
260 nsNumberControlFrame::GetTextFieldFrame() |
|
261 { |
|
262 return do_QueryFrame(GetAnonTextControl()->GetPrimaryFrame()); |
|
263 } |
|
264 |
|
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); |
|
275 |
|
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); |
|
288 |
|
289 if (!aElements.AppendElement(ContentInfo(resultElement, newStyleContext))) { |
|
290 return NS_ERROR_OUT_OF_MEMORY; |
|
291 } |
|
292 |
|
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 } |
|
298 |
|
299 resultElement.forget(aResult); |
|
300 return NS_OK; |
|
301 } |
|
302 |
|
303 nsresult |
|
304 nsNumberControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements) |
|
305 { |
|
306 nsresult rv; |
|
307 |
|
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. |
|
320 |
|
321 |
|
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); |
|
329 |
|
330 ContentInfo& outerWrapperCI = aElements.LastElement(); |
|
331 |
|
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); |
|
339 |
|
340 mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::type, |
|
341 NS_LITERAL_STRING("text"), PR_FALSE); |
|
342 |
|
343 HTMLInputElement* content = HTMLInputElement::FromContent(mContent); |
|
344 HTMLInputElement* textField = HTMLInputElement::FromContent(mTextField); |
|
345 |
|
346 // Initialize the text field value: |
|
347 nsAutoString value; |
|
348 content->GetValue(value); |
|
349 SetValueOfAnonTextControl(value); |
|
350 |
|
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 } |
|
356 |
|
357 // Propogate our tabindex: |
|
358 int32_t tabIndex; |
|
359 content->GetTabIndex(&tabIndex); |
|
360 textField->SetTabIndex(tabIndex); |
|
361 |
|
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 } |
|
367 |
|
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 } |
|
375 |
|
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 } |
|
381 |
|
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); |
|
389 |
|
390 ContentInfo& spinBoxCI = outerWrapperCI.mChildren.LastElement(); |
|
391 |
|
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); |
|
399 |
|
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); |
|
406 |
|
407 SyncDisabledState(); |
|
408 |
|
409 return rv; |
|
410 } |
|
411 |
|
412 nsIAtom* |
|
413 nsNumberControlFrame::GetType() const |
|
414 { |
|
415 return nsGkAtoms::numberControlFrame; |
|
416 } |
|
417 |
|
418 NS_IMETHODIMP |
|
419 nsNumberControlFrame::GetEditor(nsIEditor **aEditor) |
|
420 { |
|
421 return GetTextFieldFrame()->GetEditor(aEditor); |
|
422 } |
|
423 |
|
424 NS_IMETHODIMP |
|
425 nsNumberControlFrame::SetSelectionStart(int32_t aSelectionStart) |
|
426 { |
|
427 return GetTextFieldFrame()->SetSelectionStart(aSelectionStart); |
|
428 } |
|
429 |
|
430 NS_IMETHODIMP |
|
431 nsNumberControlFrame::SetSelectionEnd(int32_t aSelectionEnd) |
|
432 { |
|
433 return GetTextFieldFrame()->SetSelectionEnd(aSelectionEnd); |
|
434 } |
|
435 |
|
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 } |
|
444 |
|
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 } |
|
453 |
|
454 NS_IMETHODIMP |
|
455 nsNumberControlFrame::GetOwnedSelectionController(nsISelectionController** aSelCon) |
|
456 { |
|
457 return GetTextFieldFrame()->GetOwnedSelectionController(aSelCon); |
|
458 } |
|
459 |
|
460 nsFrameSelection* |
|
461 nsNumberControlFrame::GetOwnedFrameSelection() |
|
462 { |
|
463 return GetTextFieldFrame()->GetOwnedFrameSelection(); |
|
464 } |
|
465 |
|
466 nsresult |
|
467 nsNumberControlFrame::GetPhonetic(nsAString& aPhonetic) |
|
468 { |
|
469 return GetTextFieldFrame()->GetPhonetic(aPhonetic); |
|
470 } |
|
471 |
|
472 nsresult |
|
473 nsNumberControlFrame::EnsureEditorInitialized() |
|
474 { |
|
475 return GetTextFieldFrame()->EnsureEditorInitialized(); |
|
476 } |
|
477 |
|
478 nsresult |
|
479 nsNumberControlFrame::ScrollSelectionIntoView() |
|
480 { |
|
481 return GetTextFieldFrame()->ScrollSelectionIntoView(); |
|
482 } |
|
483 |
|
484 void |
|
485 nsNumberControlFrame::SetFocus(bool aOn, bool aRepaint) |
|
486 { |
|
487 GetTextFieldFrame()->SetFocus(aOn, aRepaint); |
|
488 } |
|
489 |
|
490 nsresult |
|
491 nsNumberControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue) |
|
492 { |
|
493 return GetTextFieldFrame()->SetFormProperty(aName, aValue); |
|
494 } |
|
495 |
|
496 HTMLInputElement* |
|
497 nsNumberControlFrame::GetAnonTextControl() |
|
498 { |
|
499 return mTextField ? HTMLInputElement::FromContent(mTextField) : nullptr; |
|
500 } |
|
501 |
|
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 } |
|
522 |
|
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 } |
|
544 |
|
545 int32_t |
|
546 nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const |
|
547 { |
|
548 MOZ_ASSERT(aEvent->eventStructType == NS_MOUSE_EVENT, |
|
549 "Unexpected event type"); |
|
550 |
|
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 } |
|
581 |
|
582 void |
|
583 nsNumberControlFrame::SpinnerStateChanged() const |
|
584 { |
|
585 MOZ_ASSERT(mSpinUp && mSpinDown, |
|
586 "We should not be called when we have no spinner"); |
|
587 |
|
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 } |
|
597 |
|
598 bool |
|
599 nsNumberControlFrame::SpinnerUpButtonIsDepressed() const |
|
600 { |
|
601 return HTMLInputElement::FromContent(mContent)-> |
|
602 NumberSpinnerUpButtonIsDepressed(); |
|
603 } |
|
604 |
|
605 bool |
|
606 nsNumberControlFrame::SpinnerDownButtonIsDepressed() const |
|
607 { |
|
608 return HTMLInputElement::FromContent(mContent)-> |
|
609 NumberSpinnerDownButtonIsDepressed(); |
|
610 } |
|
611 |
|
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 } |
|
621 |
|
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 } |
|
630 |
|
631 nsresult |
|
632 nsNumberControlFrame::HandleSelectCall() |
|
633 { |
|
634 return HTMLInputElement::FromContent(mTextField)->Select(); |
|
635 } |
|
636 |
|
637 #define STYLES_DISABLING_NATIVE_THEMING \ |
|
638 NS_AUTHOR_SPECIFIED_BACKGROUND | \ |
|
639 NS_AUTHOR_SPECIFIED_PADDING | \ |
|
640 NS_AUTHOR_SPECIFIED_BORDER |
|
641 |
|
642 bool |
|
643 nsNumberControlFrame::ShouldUseNativeStyleForSpinner() const |
|
644 { |
|
645 MOZ_ASSERT(mSpinUp && mSpinDown, |
|
646 "We should not be called when we have no spinner"); |
|
647 |
|
648 nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame(); |
|
649 nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame(); |
|
650 |
|
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 } |
|
660 |
|
661 void |
|
662 nsNumberControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements, |
|
663 uint32_t aFilter) |
|
664 { |
|
665 // Only one direct anonymous child: |
|
666 aElements.MaybeAppendElement(mOuterWrapper); |
|
667 } |
|
668 |
|
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 } |
|
684 |
|
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); |
|
689 |
|
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 |
|
698 |
|
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 } |
|
705 |
|
706 void |
|
707 nsNumberControlFrame::GetValueOfAnonTextControl(nsAString& aValue) |
|
708 { |
|
709 if (!mTextField) { |
|
710 aValue.Truncate(); |
|
711 return; |
|
712 } |
|
713 |
|
714 HTMLInputElement::FromContent(mTextField)->GetValue(aValue); |
|
715 |
|
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 } |
|
765 |
|
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 } |
|
776 |
|
777 Element* |
|
778 nsNumberControlFrame::GetPseudoElement(nsCSSPseudoElements::Type aType) |
|
779 { |
|
780 if (aType == nsCSSPseudoElements::ePseudo_mozNumberWrapper) { |
|
781 return mOuterWrapper; |
|
782 } |
|
783 |
|
784 if (aType == nsCSSPseudoElements::ePseudo_mozNumberText) { |
|
785 return mTextField; |
|
786 } |
|
787 |
|
788 if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinBox) { |
|
789 MOZ_ASSERT(mSpinBox); |
|
790 return mSpinBox; |
|
791 } |
|
792 |
|
793 if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinUp) { |
|
794 MOZ_ASSERT(mSpinUp); |
|
795 return mSpinUp; |
|
796 } |
|
797 |
|
798 if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinDown) { |
|
799 MOZ_ASSERT(mSpinDown); |
|
800 return mSpinDown; |
|
801 } |
|
802 |
|
803 return nsContainerFrame::GetPseudoElement(aType); |
|
804 } |
|
805 |
|
806 #ifdef ACCESSIBILITY |
|
807 a11y::AccType |
|
808 nsNumberControlFrame::AccessibleType() |
|
809 { |
|
810 return a11y::eHTMLSpinnerType; |
|
811 } |
|
812 #endif |