nsNumberControlFrame.cpp 26 KB

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