nsNumberControlFrame.cpp (5714B)
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 "mozilla/BasicEvents.h" 11 #include "mozilla/PresShell.h" 12 #include "nsCSSPseudoElements.h" 13 #include "nsContentCreatorFunctions.h" 14 #include "nsContentUtils.h" 15 #include "nsGkAtoms.h" 16 #include "nsLayoutUtils.h" 17 #include "nsNameSpaceManager.h" 18 #include "nsStyleConsts.h" 19 20 #ifdef ACCESSIBILITY 21 # include "mozilla/a11y/AccTypes.h" 22 #endif 23 24 using namespace mozilla; 25 using namespace mozilla::dom; 26 27 nsIFrame* NS_NewNumberControlFrame(PresShell* aPresShell, 28 ComputedStyle* aStyle) { 29 return new (aPresShell) 30 nsNumberControlFrame(aStyle, aPresShell->GetPresContext()); 31 } 32 33 NS_IMPL_FRAMEARENA_HELPERS(nsNumberControlFrame) 34 35 NS_QUERYFRAME_HEAD(nsNumberControlFrame) 36 NS_QUERYFRAME_ENTRY(nsNumberControlFrame) 37 NS_QUERYFRAME_TAIL_INHERITING(nsTextControlFrame) 38 39 nsNumberControlFrame::nsNumberControlFrame(ComputedStyle* aStyle, 40 nsPresContext* aPresContext) 41 : nsTextControlFrame(aStyle, aPresContext, kClassID) {} 42 43 nsresult nsNumberControlFrame::CreateAnonymousContent( 44 nsTArray<ContentInfo>& aElements) { 45 // We create an anonymous tree for our input element that is structured as 46 // follows: 47 // 48 // input 49 // div - placeholder 50 // div - preview div 51 // div - editor root 52 // div - spin box wrapping up/down arrow buttons 53 // div - spin up (up arrow button) 54 // div - spin down (down arrow button) 55 // 56 // If you change this, be careful to change the order of stuff returned in 57 // AppendAnonymousContentTo. 58 59 nsTextControlFrame::CreateAnonymousContent(aElements); 60 61 #if defined(MOZ_WIDGET_ANDROID) 62 // We don't want spin buttons on Android 63 return NS_OK; 64 #else 65 // The author has elected to hide the spinner by setting this 66 // -moz-appearance. We will reframe if it changes. 67 if (StyleDisplay()->EffectiveAppearance() == StyleAppearance::Textfield) { 68 return NS_OK; 69 } 70 71 // Create the ::-moz-number-spin-box pseudo-element: 72 mButton = MakeAnonElement(PseudoStyleType::mozNumberSpinBox); 73 74 // Create the ::-moz-number-spin-up pseudo-element: 75 mSpinUp = MakeAnonElement(PseudoStyleType::mozNumberSpinUp, mButton); 76 77 // Create the ::-moz-number-spin-down pseudo-element: 78 mSpinDown = MakeAnonElement(PseudoStyleType::mozNumberSpinDown, mButton); 79 80 aElements.AppendElement(mButton); 81 82 return NS_OK; 83 #endif 84 } 85 86 /* static */ 87 nsNumberControlFrame* nsNumberControlFrame::GetNumberControlFrameForSpinButton( 88 nsIFrame* aFrame) { 89 // If aFrame is a spin button for an <input type=number> then we expect the 90 // frame of the NAC root parent to be that input's frame. We have to check for 91 // this via the content tree because we don't know whether extra frames will 92 // be wrapped around any of the elements between aFrame and the 93 // nsNumberControlFrame that we're looking for (e.g. flex wrappers). 94 nsIContent* content = aFrame->GetContent(); 95 auto* nacHost = content->GetClosestNativeAnonymousSubtreeRootParentOrHost(); 96 if (!nacHost) { 97 return nullptr; 98 } 99 auto* input = HTMLInputElement::FromNode(nacHost); 100 if (!input || input->ControlType() != FormControlType::InputNumber) { 101 return nullptr; 102 } 103 return do_QueryFrame(input->GetPrimaryFrame()); 104 } 105 106 int32_t nsNumberControlFrame::GetSpinButtonForPointerEvent( 107 WidgetGUIEvent* aEvent) const { 108 MOZ_ASSERT(aEvent->mClass == eMouseEventClass, "Unexpected event type"); 109 110 if (!mButton) { 111 // we don't have a spinner 112 return eSpinButtonNone; 113 } 114 if (aEvent->mOriginalTarget == mSpinUp) { 115 return eSpinButtonUp; 116 } 117 if (aEvent->mOriginalTarget == mSpinDown) { 118 return eSpinButtonDown; 119 } 120 if (aEvent->mOriginalTarget == mButton) { 121 // In the case that the up/down buttons are hidden (display:none) we use 122 // just the spin box element, spinning up if the pointer is over the top 123 // half of the element, or down if it's over the bottom half. This is 124 // important to handle since this is the state things are in for the 125 // default UA style sheet. See the comment in forms.css for why. 126 LayoutDeviceIntPoint absPoint = aEvent->mRefPoint; 127 nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo( 128 aEvent, absPoint, RelativeTo{mButton->GetPrimaryFrame()}); 129 if (point != nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) { 130 if (point.y < mButton->GetPrimaryFrame()->GetSize().height / 2) { 131 return eSpinButtonUp; 132 } 133 return eSpinButtonDown; 134 } 135 } 136 return eSpinButtonNone; 137 } 138 139 void nsNumberControlFrame::SpinnerStateChanged() const { 140 if (mSpinUp) { 141 nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame(); 142 if (spinUpFrame && spinUpFrame->IsThemed()) { 143 spinUpFrame->InvalidateFrame(); 144 } 145 } 146 if (mSpinDown) { 147 nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame(); 148 if (spinDownFrame && spinDownFrame->IsThemed()) { 149 spinDownFrame->InvalidateFrame(); 150 } 151 } 152 } 153 154 bool nsNumberControlFrame::SpinnerUpButtonIsDepressed() const { 155 return HTMLInputElement::FromNode(mContent) 156 ->NumberSpinnerUpButtonIsDepressed(); 157 } 158 159 bool nsNumberControlFrame::SpinnerDownButtonIsDepressed() const { 160 return HTMLInputElement::FromNode(mContent) 161 ->NumberSpinnerDownButtonIsDepressed(); 162 } 163 164 #ifdef ACCESSIBILITY 165 a11y::AccType nsNumberControlFrame::AccessibleType() { 166 return a11y::eHTMLSpinnerType; 167 } 168 #endif