1 /*
2 * Copyright (C) 2006, 2008, 2010 Apple Inc. All rights reserved.
3 * Copyright (C) 2010 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include "third_party/blink/renderer/core/html/forms/spin_button_element.h"
28
29 #include "build/build_config.h"
30 #include "third_party/blink/public/platform/task_type.h"
31 #include "third_party/blink/renderer/core/event_interface_names.h"
32 #include "third_party/blink/renderer/core/events/mouse_event.h"
33 #include "third_party/blink/renderer/core/events/wheel_event.h"
34 #include "third_party/blink/renderer/core/frame/local_frame.h"
35 #include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h"
36 #include "third_party/blink/renderer/core/html_names.h"
37 #include "third_party/blink/renderer/core/input/event_handler.h"
38 #include "third_party/blink/renderer/core/layout/layout_box.h"
39 #include "third_party/blink/renderer/core/page/chrome_client.h"
40 #include "third_party/blink/renderer/core/page/page.h"
41 #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
42
43 namespace blink {
44
SpinButtonElement(Document & document,SpinButtonOwner & spin_button_owner)45 SpinButtonElement::SpinButtonElement(Document& document,
46 SpinButtonOwner& spin_button_owner)
47 : HTMLDivElement(document),
48 spin_button_owner_(&spin_button_owner),
49 capturing_(false),
50 up_down_state_(kIndeterminate),
51 press_starting_state_(kIndeterminate),
52 repeating_timer_(document.GetTaskRunner(TaskType::kInternalDefault),
53 this,
54 &SpinButtonElement::RepeatingTimerFired) {
55 SetShadowPseudoId(AtomicString("-webkit-inner-spin-button"));
56 setAttribute(html_names::kIdAttr, shadow_element_names::SpinButton());
57 }
58
DetachLayoutTree(bool performing_reattach)59 void SpinButtonElement::DetachLayoutTree(bool performing_reattach) {
60 ReleaseCapture(kEventDispatchDisallowed);
61 HTMLDivElement::DetachLayoutTree(performing_reattach);
62 }
63
DefaultEventHandler(Event & event)64 void SpinButtonElement::DefaultEventHandler(Event& event) {
65 auto* mouse_event = DynamicTo<MouseEvent>(event);
66 if (!mouse_event) {
67 if (!event.DefaultHandled())
68 HTMLDivElement::DefaultEventHandler(event);
69 return;
70 }
71
72 LayoutBox* box = GetLayoutBox();
73 if (!box) {
74 if (!event.DefaultHandled())
75 HTMLDivElement::DefaultEventHandler(event);
76 return;
77 }
78
79 if (!ShouldRespondToMouseEvents()) {
80 if (!event.DefaultHandled())
81 HTMLDivElement::DefaultEventHandler(event);
82 return;
83 }
84
85 IntPoint local = RoundedIntPoint(box->AbsoluteToLocalFloatPoint(
86 FloatPoint(mouse_event->AbsoluteLocation())));
87 if (mouse_event->type() == event_type_names::kMousedown &&
88 mouse_event->button() ==
89 static_cast<int16_t>(WebPointerProperties::Button::kLeft)) {
90 if (box->PixelSnappedBorderBoxRect().Contains(local)) {
91 if (spin_button_owner_)
92 spin_button_owner_->FocusAndSelectSpinButtonOwner();
93 if (GetLayoutObject()) {
94 if (up_down_state_ != kIndeterminate) {
95 // A JavaScript event handler called in doStepAction() below
96 // might change the element state and we might need to
97 // cancel the repeating timer by the state change. If we
98 // started the timer after doStepAction(), we would have no
99 // chance to cancel the timer.
100 StartRepeatingTimer();
101 DoStepAction(up_down_state_ == kUp ? 1 : -1);
102 }
103 }
104 // Check |GetLayoutObject| again to make sure element is not removed by
105 // |DoStepAction|
106 if (GetLayoutObject() && !capturing_) {
107 if (LocalFrame* frame = GetDocument().GetFrame()) {
108 frame->GetEventHandler().SetPointerCapture(
109 PointerEventFactory::kMouseId, this);
110 capturing_ = true;
111 if (Page* page = GetDocument().GetPage())
112 page->GetChromeClient().RegisterPopupOpeningObserver(this);
113 }
114 }
115 event.SetDefaultHandled();
116 }
117 } else if (mouse_event->type() == event_type_names::kMouseup &&
118 mouse_event->button() ==
119 static_cast<int16_t>(WebPointerProperties::Button::kLeft)) {
120 ReleaseCapture();
121 } else if (event.type() == event_type_names::kMousemove) {
122 if (box->PixelSnappedBorderBoxRect().Contains(local)) {
123 UpDownState old_up_down_state = up_down_state_;
124 up_down_state_ = (local.Y() < box->Size().Height() / 2) ? kUp : kDown;
125 if (up_down_state_ != old_up_down_state)
126 GetLayoutObject()->SetShouldDoFullPaintInvalidation();
127 } else {
128 ReleaseCapture();
129 up_down_state_ = kIndeterminate;
130 }
131 }
132
133 if (!event.DefaultHandled())
134 HTMLDivElement::DefaultEventHandler(event);
135 }
136
WillOpenPopup()137 void SpinButtonElement::WillOpenPopup() {
138 ReleaseCapture();
139 up_down_state_ = kIndeterminate;
140 }
141
ForwardEvent(Event & event)142 void SpinButtonElement::ForwardEvent(Event& event) {
143 if (!GetLayoutBox())
144 return;
145
146 if (!event.HasInterface(event_interface_names::kWheelEvent))
147 return;
148
149 if (!spin_button_owner_)
150 return;
151
152 if (!spin_button_owner_->ShouldSpinButtonRespondToWheelEvents())
153 return;
154
155 DoStepAction(To<WheelEvent>(event).wheelDeltaY());
156 event.SetDefaultHandled();
157 }
158
WillRespondToMouseMoveEvents()159 bool SpinButtonElement::WillRespondToMouseMoveEvents() {
160 if (GetLayoutBox() && ShouldRespondToMouseEvents())
161 return true;
162
163 return HTMLDivElement::WillRespondToMouseMoveEvents();
164 }
165
WillRespondToMouseClickEvents()166 bool SpinButtonElement::WillRespondToMouseClickEvents() {
167 if (GetLayoutBox() && ShouldRespondToMouseEvents())
168 return true;
169
170 return HTMLDivElement::WillRespondToMouseClickEvents();
171 }
172
DoStepAction(int amount)173 void SpinButtonElement::DoStepAction(int amount) {
174 if (!spin_button_owner_)
175 return;
176
177 if (amount > 0)
178 spin_button_owner_->SpinButtonStepUp();
179 else if (amount < 0)
180 spin_button_owner_->SpinButtonStepDown();
181 }
182
ReleaseCapture(EventDispatch event_dispatch)183 void SpinButtonElement::ReleaseCapture(EventDispatch event_dispatch) {
184 StopRepeatingTimer();
185 if (!capturing_)
186 return;
187 if (LocalFrame* frame = GetDocument().GetFrame()) {
188 frame->GetEventHandler().ReleasePointerCapture(
189 PointerEventFactory::kMouseId, this);
190 capturing_ = false;
191 if (Page* page = GetDocument().GetPage())
192 page->GetChromeClient().UnregisterPopupOpeningObserver(this);
193 }
194 if (spin_button_owner_)
195 spin_button_owner_->SpinButtonDidReleaseMouseCapture(event_dispatch);
196 }
197
MatchesReadOnlyPseudoClass() const198 bool SpinButtonElement::MatchesReadOnlyPseudoClass() const {
199 return OwnerShadowHost()->MatchesReadOnlyPseudoClass();
200 }
201
MatchesReadWritePseudoClass() const202 bool SpinButtonElement::MatchesReadWritePseudoClass() const {
203 return OwnerShadowHost()->MatchesReadWritePseudoClass();
204 }
205
StartRepeatingTimer()206 void SpinButtonElement::StartRepeatingTimer() {
207 press_starting_state_ = up_down_state_;
208 Page* page = GetDocument().GetPage();
209 DCHECK(page);
210 ScrollbarTheme& theme = page->GetScrollbarTheme();
211 repeating_timer_.Start(theme.InitialAutoscrollTimerDelay(),
212 theme.AutoscrollTimerDelay(), FROM_HERE);
213 }
214
StopRepeatingTimer()215 void SpinButtonElement::StopRepeatingTimer() {
216 repeating_timer_.Stop();
217 }
218
Step(int amount)219 void SpinButtonElement::Step(int amount) {
220 if (!ShouldRespondToMouseEvents())
221 return;
222 // On Mac OS, NSStepper updates the value for the button under the mouse
223 // cursor regardless of the button pressed at the beginning. So the
224 // following check is not needed for Mac OS.
225 #if !defined(OS_MACOSX)
226 if (up_down_state_ != press_starting_state_)
227 return;
228 #endif
229 DoStepAction(amount);
230 }
231
RepeatingTimerFired(TimerBase *)232 void SpinButtonElement::RepeatingTimerFired(TimerBase*) {
233 if (up_down_state_ != kIndeterminate)
234 Step(up_down_state_ == kUp ? 1 : -1);
235 }
236
SetHovered(bool hovered)237 void SpinButtonElement::SetHovered(bool hovered) {
238 if (!hovered)
239 up_down_state_ = kIndeterminate;
240 HTMLDivElement::SetHovered(hovered);
241 }
242
ShouldRespondToMouseEvents()243 bool SpinButtonElement::ShouldRespondToMouseEvents() {
244 return !spin_button_owner_ ||
245 spin_button_owner_->ShouldSpinButtonRespondToMouseEvents();
246 }
247
Trace(Visitor * visitor)248 void SpinButtonElement::Trace(Visitor* visitor) {
249 visitor->Trace(spin_button_owner_);
250 HTMLDivElement::Trace(visitor);
251 }
252
253 } // namespace blink
254