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