Line data Source code
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 "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 "nsCSSPseudoElements.h"
24 : #include "nsStyleSet.h"
25 : #include "mozilla/StyleSetHandle.h"
26 : #include "mozilla/StyleSetHandleInlines.h"
27 : #include "nsIDOMMutationEvent.h"
28 : #include "nsThreadUtils.h"
29 : #include "mozilla/FloatingPoint.h"
30 :
31 : #ifdef ACCESSIBILITY
32 : #include "mozilla/a11y/AccTypes.h"
33 : #endif
34 :
35 : using namespace mozilla;
36 : using namespace mozilla::dom;
37 :
38 : nsIFrame*
39 0 : NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
40 : {
41 0 : return new (aPresShell) nsNumberControlFrame(aContext);
42 : }
43 :
44 0 : NS_IMPL_FRAMEARENA_HELPERS(nsNumberControlFrame)
45 :
46 0 : NS_QUERYFRAME_HEAD(nsNumberControlFrame)
47 0 : NS_QUERYFRAME_ENTRY(nsNumberControlFrame)
48 0 : NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
49 0 : NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
50 0 : NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
51 :
52 0 : nsNumberControlFrame::nsNumberControlFrame(nsStyleContext* aContext)
53 : : nsContainerFrame(aContext, kClassID)
54 0 : , mHandlingInputEvent(false)
55 : {
56 0 : }
57 :
58 : void
59 0 : nsNumberControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
60 : {
61 0 : NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
62 : "nsNumberControlFrame should not have continuations; if it does we "
63 : "need to call RegUnregAccessKey only for the first");
64 0 : nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
65 0 : nsContentUtils::DestroyAnonymousContent(&mOuterWrapper);
66 0 : nsContainerFrame::DestroyFrom(aDestructRoot);
67 0 : }
68 :
69 : nscoord
70 0 : nsNumberControlFrame::GetMinISize(gfxContext* aRenderingContext)
71 : {
72 : nscoord result;
73 0 : DISPLAY_MIN_WIDTH(this, result);
74 :
75 0 : nsIFrame* kid = mFrames.FirstChild();
76 0 : if (kid) { // display:none?
77 0 : result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
78 : kid,
79 : nsLayoutUtils::MIN_ISIZE);
80 : } else {
81 0 : result = 0;
82 : }
83 :
84 0 : return result;
85 : }
86 :
87 : nscoord
88 0 : nsNumberControlFrame::GetPrefISize(gfxContext* aRenderingContext)
89 : {
90 : nscoord result;
91 0 : DISPLAY_PREF_WIDTH(this, result);
92 :
93 0 : nsIFrame* kid = mFrames.FirstChild();
94 0 : if (kid) { // display:none?
95 0 : result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
96 : kid,
97 : nsLayoutUtils::PREF_ISIZE);
98 : } else {
99 0 : result = 0;
100 : }
101 :
102 0 : return result;
103 : }
104 :
105 : void
106 0 : nsNumberControlFrame::Reflow(nsPresContext* aPresContext,
107 : ReflowOutput& aDesiredSize,
108 : const ReflowInput& aReflowInput,
109 : nsReflowStatus& aStatus)
110 : {
111 0 : MarkInReflow();
112 0 : DO_GLOBAL_REFLOW_COUNT("nsNumberControlFrame");
113 0 : DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
114 :
115 0 : NS_ASSERTION(mOuterWrapper, "Outer wrapper div must exist!");
116 :
117 0 : NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
118 : "nsNumberControlFrame should not have continuations; if it does we "
119 : "need to call RegUnregAccessKey only for the first");
120 :
121 0 : NS_ASSERTION(!mFrames.FirstChild() ||
122 : !mFrames.FirstChild()->GetNextSibling(),
123 : "We expect at most one direct child frame");
124 :
125 0 : if (mState & NS_FRAME_FIRST_REFLOW) {
126 0 : nsFormControlFrame::RegUnRegAccessKey(this, true);
127 : }
128 :
129 0 : const WritingMode myWM = aReflowInput.GetWritingMode();
130 :
131 : // The ISize of our content box, which is the available ISize
132 : // for our anonymous content:
133 0 : const nscoord contentBoxISize = aReflowInput.ComputedISize();
134 0 : nscoord contentBoxBSize = aReflowInput.ComputedBSize();
135 :
136 : // Figure out our border-box sizes as well (by adding borderPadding to
137 : // content-box sizes):
138 : const nscoord borderBoxISize = contentBoxISize +
139 0 : aReflowInput.ComputedLogicalBorderPadding().IStartEnd(myWM);
140 :
141 : nscoord borderBoxBSize;
142 0 : if (contentBoxBSize != NS_INTRINSICSIZE) {
143 0 : borderBoxBSize = contentBoxBSize +
144 0 : aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
145 : } // else, we'll figure out borderBoxBSize after we resolve contentBoxBSize.
146 :
147 0 : nsIFrame* outerWrapperFrame = mOuterWrapper->GetPrimaryFrame();
148 :
149 0 : if (!outerWrapperFrame) { // display:none?
150 0 : if (contentBoxBSize == NS_INTRINSICSIZE) {
151 0 : contentBoxBSize = 0;
152 : borderBoxBSize =
153 0 : aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
154 : }
155 : } else {
156 0 : NS_ASSERTION(outerWrapperFrame == mFrames.FirstChild(), "huh?");
157 :
158 0 : ReflowOutput wrappersDesiredSize(aReflowInput);
159 :
160 0 : WritingMode wrapperWM = outerWrapperFrame->GetWritingMode();
161 0 : LogicalSize availSize = aReflowInput.ComputedSize(wrapperWM);
162 0 : availSize.BSize(wrapperWM) = NS_UNCONSTRAINEDSIZE;
163 :
164 : ReflowInput wrapperReflowInput(aPresContext, aReflowInput,
165 0 : outerWrapperFrame, availSize);
166 :
167 : // Convert wrapper margin into my own writing-mode (in case it differs):
168 : LogicalMargin wrapperMargin =
169 0 : wrapperReflowInput.ComputedLogicalMargin().ConvertTo(myWM, wrapperWM);
170 :
171 : // offsets of wrapper frame within this frame:
172 : LogicalPoint
173 : wrapperOffset(myWM,
174 0 : aReflowInput.ComputedLogicalBorderPadding().IStart(myWM) +
175 0 : wrapperMargin.IStart(myWM),
176 0 : aReflowInput.ComputedLogicalBorderPadding().BStart(myWM) +
177 0 : wrapperMargin.BStart(myWM));
178 :
179 0 : nsReflowStatus childStatus;
180 : // We initially reflow the child with a dummy containerSize; positioning
181 : // will be fixed later.
182 0 : const nsSize dummyContainerSize;
183 0 : ReflowChild(outerWrapperFrame, aPresContext, wrappersDesiredSize,
184 : wrapperReflowInput, myWM, wrapperOffset, dummyContainerSize, 0,
185 0 : childStatus);
186 0 : MOZ_ASSERT(childStatus.IsFullyComplete(),
187 : "We gave our child unconstrained available block-size, "
188 : "so it should be complete");
189 :
190 : nscoord wrappersMarginBoxBSize =
191 0 : wrappersDesiredSize.BSize(myWM) + wrapperMargin.BStartEnd(myWM);
192 :
193 0 : if (contentBoxBSize == NS_INTRINSICSIZE) {
194 : // We are intrinsically sized -- we should shrinkwrap the outer wrapper's
195 : // block-size:
196 0 : contentBoxBSize = wrappersMarginBoxBSize;
197 :
198 : // Make sure we obey min/max-bsize in the case when we're doing intrinsic
199 : // sizing (we get it for free when we have a non-intrinsic
200 : // aReflowInput.ComputedBSize()). Note that we do this before
201 : // adjusting for borderpadding, since ComputedMaxBSize and
202 : // ComputedMinBSize are content heights.
203 : contentBoxBSize =
204 0 : NS_CSS_MINMAX(contentBoxBSize,
205 : aReflowInput.ComputedMinBSize(),
206 0 : aReflowInput.ComputedMaxBSize());
207 :
208 0 : borderBoxBSize = contentBoxBSize +
209 0 : aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
210 : }
211 :
212 : // Center child in block axis
213 0 : nscoord extraSpace = contentBoxBSize - wrappersMarginBoxBSize;
214 0 : wrapperOffset.B(myWM) += std::max(0, extraSpace / 2);
215 :
216 : // Needed in FinishReflowChild, for logical-to-physical conversion:
217 0 : nsSize borderBoxSize = LogicalSize(myWM, borderBoxISize, borderBoxBSize).
218 0 : GetPhysicalSize(myWM);
219 :
220 : // Place the child
221 : FinishReflowChild(outerWrapperFrame, aPresContext, wrappersDesiredSize,
222 : &wrapperReflowInput, myWM, wrapperOffset,
223 0 : borderBoxSize, 0);
224 :
225 : nsSize contentBoxSize =
226 0 : LogicalSize(myWM, contentBoxISize, contentBoxBSize).
227 0 : GetPhysicalSize(myWM);
228 0 : aDesiredSize.SetBlockStartAscent(
229 0 : wrappersDesiredSize.BlockStartAscent() +
230 0 : outerWrapperFrame->BStart(aReflowInput.GetWritingMode(),
231 0 : contentBoxSize));
232 : }
233 :
234 0 : LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
235 0 : aDesiredSize.SetSize(myWM, logicalDesiredSize);
236 :
237 0 : aDesiredSize.SetOverflowAreasToDesiredBounds();
238 :
239 0 : if (outerWrapperFrame) {
240 0 : ConsiderChildOverflow(aDesiredSize.mOverflowAreas, outerWrapperFrame);
241 : }
242 :
243 0 : FinishAndStoreOverflow(&aDesiredSize);
244 :
245 0 : aStatus.Reset();
246 :
247 0 : NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
248 0 : }
249 :
250 : void
251 0 : nsNumberControlFrame::SyncDisabledState()
252 : {
253 0 : EventStates eventStates = mContent->AsElement()->State();
254 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
255 0 : mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, EmptyString(),
256 0 : true);
257 : } else {
258 0 : mTextField->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
259 : }
260 0 : }
261 :
262 : nsresult
263 0 : nsNumberControlFrame::AttributeChanged(int32_t aNameSpaceID,
264 : nsIAtom* aAttribute,
265 : int32_t aModType)
266 : {
267 : // nsGkAtoms::disabled is handled by SyncDisabledState
268 0 : if (aNameSpaceID == kNameSpaceID_None) {
269 0 : if (aAttribute == nsGkAtoms::placeholder ||
270 0 : aAttribute == nsGkAtoms::readonly ||
271 0 : aAttribute == nsGkAtoms::tabindex) {
272 0 : if (aModType == nsIDOMMutationEvent::REMOVAL) {
273 0 : mTextField->UnsetAttr(aNameSpaceID, aAttribute, true);
274 : } else {
275 0 : MOZ_ASSERT(aModType == nsIDOMMutationEvent::ADDITION ||
276 : aModType == nsIDOMMutationEvent::MODIFICATION);
277 0 : nsAutoString value;
278 0 : mContent->GetAttr(aNameSpaceID, aAttribute, value);
279 0 : mTextField->SetAttr(aNameSpaceID, aAttribute, value, true);
280 : }
281 : }
282 : }
283 :
284 0 : return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
285 0 : aModType);
286 : }
287 :
288 : void
289 0 : nsNumberControlFrame::ContentStatesChanged(EventStates aStates)
290 : {
291 0 : if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
292 0 : nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
293 : }
294 0 : }
295 :
296 : nsITextControlFrame*
297 0 : nsNumberControlFrame::GetTextFieldFrame()
298 : {
299 0 : return do_QueryFrame(GetAnonTextControl()->GetPrimaryFrame());
300 : }
301 :
302 0 : class FocusTextField : public Runnable
303 : {
304 : public:
305 0 : FocusTextField(nsIContent* aNumber, nsIContent* aTextField)
306 0 : : mozilla::Runnable("FocusTextField")
307 : , mNumber(aNumber)
308 0 : , mTextField(aTextField)
309 0 : {}
310 :
311 0 : NS_IMETHOD Run() override
312 : {
313 0 : if (mNumber->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS)) {
314 0 : HTMLInputElement::FromContent(mTextField)->Focus();
315 : }
316 :
317 0 : return NS_OK;
318 : }
319 :
320 : private:
321 : nsCOMPtr<nsIContent> mNumber;
322 : nsCOMPtr<nsIContent> mTextField;
323 : };
324 :
325 : nsresult
326 0 : nsNumberControlFrame::MakeAnonymousElement(Element** aResult,
327 : nsTArray<ContentInfo>& aElements,
328 : nsIAtom* aTagName,
329 : CSSPseudoElementType aPseudoType)
330 : {
331 : // Get the NodeInfoManager and tag necessary to create the anonymous divs.
332 0 : nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
333 0 : RefPtr<Element> resultElement = doc->CreateHTMLElement(aTagName);
334 0 : resultElement->SetPseudoElementType(aPseudoType);
335 :
336 : // Associate the pseudo-element with the anonymous child
337 0 : if (!aElements.AppendElement(resultElement)) {
338 0 : return NS_ERROR_OUT_OF_MEMORY;
339 : }
340 :
341 0 : if (aPseudoType == CSSPseudoElementType::mozNumberSpinDown ||
342 : aPseudoType == CSSPseudoElementType::mozNumberSpinUp) {
343 0 : resultElement->SetAttr(kNameSpaceID_None, nsGkAtoms::role,
344 0 : NS_LITERAL_STRING("button"), false);
345 : }
346 :
347 0 : resultElement.forget(aResult);
348 0 : return NS_OK;
349 : }
350 :
351 : nsresult
352 0 : nsNumberControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
353 : {
354 : nsresult rv;
355 :
356 : // We create an anonymous tree for our input element that is structured as
357 : // follows:
358 : //
359 : // input
360 : // div - outer wrapper with "display:flex" by default
361 : // input - text input field
362 : // div - spin box wrapping up/down arrow buttons
363 : // div - spin up (up arrow button)
364 : // div - spin down (down arrow button)
365 : //
366 : // If you change this, be careful to change the destruction order in
367 : // nsNumberControlFrame::DestroyFrom.
368 :
369 :
370 : // Create the anonymous outer wrapper:
371 0 : rv = MakeAnonymousElement(getter_AddRefs(mOuterWrapper),
372 : aElements,
373 : nsGkAtoms::div,
374 0 : CSSPseudoElementType::mozNumberWrapper);
375 0 : NS_ENSURE_SUCCESS(rv, rv);
376 :
377 0 : ContentInfo& outerWrapperCI = aElements.LastElement();
378 :
379 : // Create the ::-moz-number-text pseudo-element:
380 0 : rv = MakeAnonymousElement(getter_AddRefs(mTextField),
381 : outerWrapperCI.mChildren,
382 : nsGkAtoms::input,
383 0 : CSSPseudoElementType::mozNumberText);
384 0 : NS_ENSURE_SUCCESS(rv, rv);
385 :
386 0 : mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
387 0 : NS_LITERAL_STRING("text"), PR_FALSE);
388 :
389 0 : HTMLInputElement* content = HTMLInputElement::FromContent(mContent);
390 0 : HTMLInputElement* textField = HTMLInputElement::FromContent(mTextField);
391 :
392 : // Initialize the text field value:
393 0 : nsAutoString value;
394 0 : content->GetValue(value, CallerType::System);
395 0 : SetValueOfAnonTextControl(value);
396 :
397 : // If we're readonly, make sure our anonymous text control is too:
398 0 : nsAutoString readonly;
399 0 : if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly)) {
400 0 : mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly, false);
401 : }
402 :
403 : // Propogate our tabindex:
404 : int32_t tabIndex;
405 0 : content->GetTabIndex(&tabIndex);
406 0 : textField->SetTabIndex(tabIndex);
407 :
408 : // Initialize the text field's placeholder, if ours is set:
409 0 : nsAutoString placeholder;
410 0 : if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholder)) {
411 0 : mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholder, false);
412 : }
413 :
414 0 : if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS)) {
415 : // We don't want to focus the frame but the text field.
416 0 : RefPtr<FocusTextField> focusJob = new FocusTextField(mContent, mTextField);
417 0 : nsContentUtils::AddScriptRunner(focusJob);
418 : }
419 :
420 0 : if (StyleDisplay()->mAppearance == NS_THEME_TEXTFIELD) {
421 : // The author has elected to hide the spinner by setting this
422 : // -moz-appearance. We will reframe if it changes.
423 0 : return rv;
424 : }
425 :
426 : // Create the ::-moz-number-spin-box pseudo-element:
427 0 : rv = MakeAnonymousElement(getter_AddRefs(mSpinBox),
428 : outerWrapperCI.mChildren,
429 : nsGkAtoms::div,
430 0 : CSSPseudoElementType::mozNumberSpinBox);
431 0 : NS_ENSURE_SUCCESS(rv, rv);
432 :
433 0 : ContentInfo& spinBoxCI = outerWrapperCI.mChildren.LastElement();
434 :
435 : // Create the ::-moz-number-spin-up pseudo-element:
436 0 : rv = MakeAnonymousElement(getter_AddRefs(mSpinUp),
437 : spinBoxCI.mChildren,
438 : nsGkAtoms::div,
439 0 : CSSPseudoElementType::mozNumberSpinUp);
440 0 : NS_ENSURE_SUCCESS(rv, rv);
441 :
442 : // Create the ::-moz-number-spin-down pseudo-element:
443 0 : rv = MakeAnonymousElement(getter_AddRefs(mSpinDown),
444 : spinBoxCI.mChildren,
445 : nsGkAtoms::div,
446 0 : CSSPseudoElementType::mozNumberSpinDown);
447 :
448 0 : SyncDisabledState();
449 :
450 0 : return rv;
451 : }
452 :
453 : void
454 0 : nsNumberControlFrame::SetFocus(bool aOn, bool aRepaint)
455 : {
456 0 : GetTextFieldFrame()->SetFocus(aOn, aRepaint);
457 0 : }
458 :
459 : nsresult
460 0 : nsNumberControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
461 : {
462 0 : return GetTextFieldFrame()->SetFormProperty(aName, aValue);
463 : }
464 :
465 : HTMLInputElement*
466 0 : nsNumberControlFrame::GetAnonTextControl()
467 : {
468 0 : return mTextField ? HTMLInputElement::FromContent(mTextField) : nullptr;
469 : }
470 :
471 : /* static */ nsNumberControlFrame*
472 0 : nsNumberControlFrame::GetNumberControlFrameForTextField(nsIFrame* aFrame)
473 : {
474 : // If aFrame is the anon text field for an <input type=number> then we expect
475 : // the frame of its mContent's grandparent to be that input's frame. We
476 : // have to check for this via the content tree because we don't know whether
477 : // extra frames will be wrapped around any of the elements between aFrame and
478 : // the nsNumberControlFrame that we're looking for (e.g. flex wrappers).
479 0 : nsIContent* content = aFrame->GetContent();
480 0 : if (content->IsInNativeAnonymousSubtree() &&
481 0 : content->GetParent() && content->GetParent()->GetParent()) {
482 0 : nsIContent* grandparent = content->GetParent()->GetParent();
483 0 : if (grandparent->IsHTMLElement(nsGkAtoms::input) &&
484 0 : grandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
485 : nsGkAtoms::number, eCaseMatters)) {
486 0 : return do_QueryFrame(grandparent->GetPrimaryFrame());
487 : }
488 : }
489 0 : return nullptr;
490 : }
491 :
492 : /* static */ nsNumberControlFrame*
493 18 : nsNumberControlFrame::GetNumberControlFrameForSpinButton(nsIFrame* aFrame)
494 : {
495 : // If aFrame is a spin button for an <input type=number> then we expect the
496 : // frame of its mContent's great-grandparent to be that input's frame. We
497 : // have to check for this via the content tree because we don't know whether
498 : // extra frames will be wrapped around any of the elements between aFrame and
499 : // the nsNumberControlFrame that we're looking for (e.g. flex wrappers).
500 18 : nsIContent* content = aFrame->GetContent();
501 36 : if (content->IsInNativeAnonymousSubtree() &&
502 18 : content->GetParent() && content->GetParent()->GetParent() &&
503 0 : content->GetParent()->GetParent()->GetParent()) {
504 0 : nsIContent* greatgrandparent = content->GetParent()->GetParent()->GetParent();
505 0 : if (greatgrandparent->IsHTMLElement(nsGkAtoms::input) &&
506 0 : greatgrandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
507 : nsGkAtoms::number, eCaseMatters)) {
508 0 : return do_QueryFrame(greatgrandparent->GetPrimaryFrame());
509 : }
510 : }
511 18 : return nullptr;
512 : }
513 :
514 : int32_t
515 0 : nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const
516 : {
517 0 : MOZ_ASSERT(aEvent->mClass == eMouseEventClass, "Unexpected event type");
518 :
519 0 : if (!mSpinBox) {
520 : // we don't have a spinner
521 0 : return eSpinButtonNone;
522 : }
523 0 : if (aEvent->mOriginalTarget == mSpinUp) {
524 0 : return eSpinButtonUp;
525 : }
526 0 : if (aEvent->mOriginalTarget == mSpinDown) {
527 0 : return eSpinButtonDown;
528 : }
529 0 : if (aEvent->mOriginalTarget == mSpinBox) {
530 : // In the case that the up/down buttons are hidden (display:none) we use
531 : // just the spin box element, spinning up if the pointer is over the top
532 : // half of the element, or down if it's over the bottom half. This is
533 : // important to handle since this is the state things are in for the
534 : // default UA style sheet. See the comment in forms.css for why.
535 0 : LayoutDeviceIntPoint absPoint = aEvent->mRefPoint;
536 : nsPoint point =
537 : nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
538 0 : absPoint, mSpinBox->GetPrimaryFrame());
539 0 : if (point != nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
540 0 : if (point.y < mSpinBox->GetPrimaryFrame()->GetSize().height / 2) {
541 0 : return eSpinButtonUp;
542 : }
543 0 : return eSpinButtonDown;
544 : }
545 : }
546 0 : return eSpinButtonNone;
547 : }
548 :
549 : void
550 0 : nsNumberControlFrame::SpinnerStateChanged() const
551 : {
552 0 : MOZ_ASSERT(mSpinUp && mSpinDown,
553 : "We should not be called when we have no spinner");
554 :
555 0 : nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame();
556 0 : if (spinUpFrame && spinUpFrame->IsThemed()) {
557 0 : spinUpFrame->InvalidateFrame();
558 : }
559 0 : nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame();
560 0 : if (spinDownFrame && spinDownFrame->IsThemed()) {
561 0 : spinDownFrame->InvalidateFrame();
562 : }
563 0 : }
564 :
565 : bool
566 0 : nsNumberControlFrame::SpinnerUpButtonIsDepressed() const
567 : {
568 0 : return HTMLInputElement::FromContent(mContent)->
569 0 : NumberSpinnerUpButtonIsDepressed();
570 : }
571 :
572 : bool
573 0 : nsNumberControlFrame::SpinnerDownButtonIsDepressed() const
574 : {
575 0 : return HTMLInputElement::FromContent(mContent)->
576 0 : NumberSpinnerDownButtonIsDepressed();
577 : }
578 :
579 : bool
580 0 : nsNumberControlFrame::IsFocused() const
581 : {
582 : // Normally this depends on the state of our anonymous text control (which
583 : // takes focus for us), but in the case that it does not have a frame we will
584 : // have focus ourself.
585 0 : return mTextField->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS) ||
586 0 : mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS);
587 : }
588 :
589 : void
590 0 : nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent)
591 : {
592 0 : if (aEvent->mOriginalTarget != mTextField) {
593 : // Move focus to our text field
594 0 : HTMLInputElement::FromContent(mTextField)->Focus();
595 : }
596 0 : }
597 :
598 : nsresult
599 0 : nsNumberControlFrame::HandleSelectCall()
600 : {
601 0 : return HTMLInputElement::FromContent(mTextField)->Select();
602 : }
603 :
604 : #define STYLES_DISABLING_NATIVE_THEMING \
605 : NS_AUTHOR_SPECIFIED_BACKGROUND | \
606 : NS_AUTHOR_SPECIFIED_PADDING | \
607 : NS_AUTHOR_SPECIFIED_BORDER
608 :
609 : bool
610 0 : nsNumberControlFrame::ShouldUseNativeStyleForSpinner() const
611 : {
612 0 : MOZ_ASSERT(mSpinUp && mSpinDown,
613 : "We should not be called when we have no spinner");
614 :
615 0 : nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame();
616 0 : nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame();
617 :
618 0 : return spinUpFrame &&
619 0 : spinUpFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_UPBUTTON &&
620 0 : !PresContext()->HasAuthorSpecifiedRules(spinUpFrame,
621 0 : STYLES_DISABLING_NATIVE_THEMING) &&
622 0 : spinDownFrame &&
623 0 : spinDownFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_DOWNBUTTON &&
624 0 : !PresContext()->HasAuthorSpecifiedRules(spinDownFrame,
625 0 : STYLES_DISABLING_NATIVE_THEMING);
626 : }
627 :
628 : void
629 0 : nsNumberControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
630 : uint32_t aFilter)
631 : {
632 : // Only one direct anonymous child:
633 0 : if (mOuterWrapper) {
634 0 : aElements.AppendElement(mOuterWrapper);
635 : }
636 0 : }
637 :
638 : void
639 0 : nsNumberControlFrame::SetValueOfAnonTextControl(const nsAString& aValue)
640 : {
641 0 : if (mHandlingInputEvent) {
642 : // We have been called while our HTMLInputElement is processing a DOM
643 : // 'input' event targeted at our anonymous text control. Our
644 : // HTMLInputElement has taken the value of our anon text control and
645 : // called SetValueInternal on itself to keep its own value in sync. As a
646 : // result SetValueInternal has called us. In this one case we do not want
647 : // to update our anon text control, especially since aValue will be the
648 : // sanitized value, and only the internal value should be sanitized (not
649 : // the value shown to the user, and certainly we shouldn't change it as
650 : // they type).
651 0 : return;
652 : }
653 :
654 : // Init to aValue so that we set aValue as the value of our text control if
655 : // aValue isn't a valid number (in which case the HTMLInputElement's validity
656 : // state will be set to invalid) or if aValue can't be localized:
657 0 : nsAutoString localizedValue(aValue);
658 :
659 : #ifdef ENABLE_INTL_API
660 : // Try and localize the value we will set:
661 0 : Decimal val = HTMLInputElement::StringToDecimal(aValue);
662 0 : if (val.isFinite()) {
663 0 : ICUUtils::LanguageTagIterForContent langTagIter(mContent);
664 0 : ICUUtils::LocalizeNumber(val.toDouble(), langTagIter, localizedValue);
665 : }
666 : #endif
667 :
668 : // We need to update the value of our anonymous text control here. Note that
669 : // this must be its value, and not its 'value' attribute (the default value),
670 : // since the default value is ignored once a user types into the text
671 : // control.
672 : //
673 : // Pass NonSystem as the caller type; this should work fine for actual number
674 : // inputs, and be safe in case our input has a type we don't expect for some
675 : // reason.
676 0 : IgnoredErrorResult rv;
677 0 : HTMLInputElement::FromContent(mTextField)->SetValue(localizedValue,
678 : CallerType::NonSystem,
679 0 : rv);
680 : }
681 :
682 : void
683 0 : nsNumberControlFrame::GetValueOfAnonTextControl(nsAString& aValue)
684 : {
685 0 : if (!mTextField) {
686 0 : aValue.Truncate();
687 0 : return;
688 : }
689 :
690 0 : HTMLInputElement::FromContent(mTextField)->GetValue(aValue, CallerType::System);
691 :
692 : #ifdef ENABLE_INTL_API
693 : // Here we need to de-localize any number typed in by the user. That is, we
694 : // need to convert it from the number format of the user's language, region,
695 : // etc. to the format that the HTML 5 spec defines to be a "valid
696 : // floating-point number":
697 : //
698 : // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#floating-point-numbers
699 : //
700 : // This is necessary to allow the number that we return to be parsed by
701 : // functions like HTMLInputElement::StringToDecimal (the HTML-5-conforming
702 : // parsing function) which don't know how to handle numbers that are
703 : // formatted differently (for example, with non-ASCII digits, with grouping
704 : // separator characters or with a decimal separator character other than
705 : // '.').
706 :
707 0 : ICUUtils::LanguageTagIterForContent langTagIter(mContent);
708 0 : double value = ICUUtils::ParseNumber(aValue, langTagIter);
709 0 : if (!IsFinite(value)) {
710 0 : aValue.Truncate();
711 0 : return;
712 : }
713 0 : if (value == HTMLInputElement::StringToDecimal(aValue).toDouble()) {
714 : // We want to preserve the formatting of the number as typed in by the user
715 : // whenever possible. Since the localized serialization parses to the same
716 : // number as the de-localized serialization, we can do that. This helps
717 : // prevent normalization of input such as "2e2" (which would otherwise be
718 : // converted to "200"). Content relies on this.
719 : //
720 : // Typically we will only get here for locales in which numbers are
721 : // formatted in the same way as they are for HTML5's "valid floating-point
722 : // number" format.
723 0 : return;
724 : }
725 : // We can't preserve the formatting, otherwise functions such as
726 : // HTMLInputElement::StringToDecimal would incorrectly process the number
727 : // input by the user. For example, "12.345" with lang=de de-localizes as
728 : // 12345, but HTMLInputElement::StringToDecimal would mistakenly parse it as
729 : // 12.345. Another example would be "12,345" with lang=de which de-localizes
730 : // as 12.345, but HTMLInputElement::StringToDecimal would parse it to NaN.
731 0 : aValue.Truncate();
732 0 : aValue.AppendFloat(value);
733 : #endif
734 : }
735 :
736 : bool
737 0 : nsNumberControlFrame::AnonTextControlIsEmpty()
738 : {
739 0 : if (!mTextField) {
740 0 : return true;
741 : }
742 0 : nsAutoString value;
743 0 : HTMLInputElement::FromContent(mTextField)->GetValue(value, CallerType::System);
744 0 : return value.IsEmpty();
745 : }
746 :
747 : Element*
748 0 : nsNumberControlFrame::GetPseudoElement(CSSPseudoElementType aType)
749 : {
750 0 : if (aType == CSSPseudoElementType::mozNumberWrapper) {
751 0 : return mOuterWrapper;
752 : }
753 :
754 0 : if (aType == CSSPseudoElementType::mozNumberText) {
755 0 : return mTextField;
756 : }
757 :
758 0 : if (aType == CSSPseudoElementType::mozNumberSpinBox) {
759 : // Might be null.
760 0 : return mSpinBox;
761 : }
762 :
763 0 : if (aType == CSSPseudoElementType::mozNumberSpinUp) {
764 : // Might be null.
765 0 : return mSpinUp;
766 : }
767 :
768 0 : if (aType == CSSPseudoElementType::mozNumberSpinDown) {
769 : // Might be null.
770 0 : return mSpinDown;
771 : }
772 :
773 0 : return nsContainerFrame::GetPseudoElement(aType);
774 : }
775 :
776 : #ifdef ACCESSIBILITY
777 : a11y::AccType
778 0 : nsNumberControlFrame::AccessibleType()
779 : {
780 0 : return a11y::eHTMLSpinnerType;
781 : }
782 : #endif
|