Coverage Report

Created: 2018-09-25 14:53

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