Rename AtomicString to AtomString
[WebKit-https.git] / Source / WebCore / html / shadow / SpinButtonElement.cpp
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 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 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 "config.h"
28 #include "SpinButtonElement.h"
29
30 #include "Chrome.h"
31 #include "EventHandler.h"
32 #include "EventNames.h"
33 #include "Frame.h"
34 #include "HTMLNames.h"
35 #include "MouseEvent.h"
36 #include "Page.h"
37 #include "RenderBox.h"
38 #include "RenderTheme.h"
39 #include "ScrollbarTheme.h"
40 #include "WheelEvent.h"
41 #include <wtf/IsoMallocInlines.h>
42 #include <wtf/Ref.h>
43
44 namespace WebCore {
45
46 WTF_MAKE_ISO_ALLOCATED_IMPL(SpinButtonElement);
47
48 using namespace HTMLNames;
49
50 inline SpinButtonElement::SpinButtonElement(Document& document, SpinButtonOwner& spinButtonOwner)
51     : HTMLDivElement(divTag, document)
52     , m_spinButtonOwner(&spinButtonOwner)
53     , m_capturing(false)
54     , m_upDownState(Indeterminate)
55     , m_pressStartingState(Indeterminate)
56     , m_repeatingTimer(*this, &SpinButtonElement::repeatingTimerFired)
57 {
58     setHasCustomStyleResolveCallbacks();
59     setPseudo(AtomString("-webkit-inner-spin-button", AtomString::ConstructFromLiteral));
60 }
61
62 Ref<SpinButtonElement> SpinButtonElement::create(Document& document, SpinButtonOwner& spinButtonOwner)
63 {
64     return adoptRef(*new SpinButtonElement(document, spinButtonOwner));
65 }
66
67 void SpinButtonElement::willDetachRenderers()
68 {
69     releaseCapture();
70 }
71
72 void SpinButtonElement::defaultEventHandler(Event& event)
73 {
74     if (!is<MouseEvent>(event)) {
75         if (!event.defaultHandled())
76             HTMLDivElement::defaultEventHandler(event);
77         return;
78     }
79
80     RenderBox* box = renderBox();
81     if (!box) {
82         if (!event.defaultHandled())
83             HTMLDivElement::defaultEventHandler(event);
84         return;
85     }
86
87     if (!shouldRespondToMouseEvents()) {
88         if (!event.defaultHandled())
89             HTMLDivElement::defaultEventHandler(event);
90         return;
91     }
92
93     MouseEvent& mouseEvent = downcast<MouseEvent>(event);
94     IntPoint local = roundedIntPoint(box->absoluteToLocal(mouseEvent.absoluteLocation(), UseTransforms));
95     if (mouseEvent.type() == eventNames().mousedownEvent && mouseEvent.button() == LeftButton) {
96         if (box->borderBoxRect().contains(local)) {
97             // The following functions of HTMLInputElement may run JavaScript
98             // code which detaches this shadow node. We need to take a reference
99             // and check renderer() after such function calls.
100             Ref<SpinButtonElement> protectedThis(*this);
101             if (m_spinButtonOwner)
102                 m_spinButtonOwner->focusAndSelectSpinButtonOwner();
103             if (renderer()) {
104                 if (m_upDownState != Indeterminate) {
105                     // A JavaScript event handler called in doStepAction() below
106                     // might change the element state and we might need to
107                     // cancel the repeating timer by the state change. If we
108                     // started the timer after doStepAction(), we would have no
109                     // chance to cancel the timer.
110                     startRepeatingTimer();
111                     doStepAction(m_upDownState == Up ? 1 : -1);
112                 }
113             }
114             mouseEvent.setDefaultHandled();
115         }
116     } else if (mouseEvent.type() == eventNames().mouseupEvent && mouseEvent.button() == LeftButton)
117         stopRepeatingTimer();
118     else if (mouseEvent.type() == eventNames().mousemoveEvent) {
119         if (box->borderBoxRect().contains(local)) {
120             if (!m_capturing) {
121                 if (RefPtr<Frame> frame = document().frame()) {
122                     frame->eventHandler().setCapturingMouseEventsElement(this);
123                     m_capturing = true;
124                     if (Page* page = document().page())
125                         page->chrome().registerPopupOpeningObserver(*this);
126                 }
127             }
128             UpDownState oldUpDownState = m_upDownState;
129             switch (renderer()->theme().innerSpinButtonLayout(*renderer())) {
130             case RenderTheme::InnerSpinButtonLayout::Vertical:
131                 m_upDownState = local.y() < box->height() / 2 ? Up : Down;
132                 break;
133             case RenderTheme::InnerSpinButtonLayout::HorizontalUpLeft:
134                 m_upDownState = local.x() < box->width() / 2 ? Up : Down;
135                 break;
136             case RenderTheme::InnerSpinButtonLayout::HorizontalUpRight:
137                 m_upDownState = local.x() > box->width() / 2 ? Up : Down;
138                 break;
139             }
140             if (m_upDownState != oldUpDownState)
141                 renderer()->repaint();
142         } else {
143             releaseCapture();
144             m_upDownState = Indeterminate;
145         }
146     }
147
148     if (!mouseEvent.defaultHandled())
149         HTMLDivElement::defaultEventHandler(mouseEvent);
150 }
151
152 void SpinButtonElement::willOpenPopup()
153 {
154     releaseCapture();
155     m_upDownState = Indeterminate;
156 }
157
158 void SpinButtonElement::forwardEvent(Event& event)
159 {
160     if (!renderBox())
161         return;
162
163     if (!is<WheelEvent>(event))
164         return;
165
166     if (!m_spinButtonOwner)
167         return;
168
169     if (!m_spinButtonOwner->shouldSpinButtonRespondToWheelEvents())
170         return;
171
172     doStepAction(downcast<WheelEvent>(event).wheelDeltaY());
173     event.setDefaultHandled();
174 }
175
176 bool SpinButtonElement::willRespondToMouseMoveEvents()
177 {
178     if (renderBox() && shouldRespondToMouseEvents())
179         return true;
180
181     return HTMLDivElement::willRespondToMouseMoveEvents();
182 }
183
184 bool SpinButtonElement::willRespondToMouseClickEvents()
185 {
186     if (renderBox() && shouldRespondToMouseEvents())
187         return true;
188
189     return HTMLDivElement::willRespondToMouseClickEvents();
190 }
191
192 void SpinButtonElement::doStepAction(int amount)
193 {
194     if (!m_spinButtonOwner)
195         return;
196
197     if (amount > 0)
198         m_spinButtonOwner->spinButtonStepUp();
199     else if (amount < 0)
200         m_spinButtonOwner->spinButtonStepDown();
201 }
202
203 void SpinButtonElement::releaseCapture()
204 {
205     stopRepeatingTimer();
206     if (m_capturing) {
207         if (RefPtr<Frame> frame = document().frame()) {
208             frame->eventHandler().setCapturingMouseEventsElement(nullptr);
209             m_capturing = false;
210             if (Page* page = document().page())
211                 page->chrome().unregisterPopupOpeningObserver(*this);
212         }
213     }
214 }
215
216 bool SpinButtonElement::matchesReadWritePseudoClass() const
217 {
218     return shadowHost()->matchesReadWritePseudoClass();
219 }
220
221 void SpinButtonElement::startRepeatingTimer()
222 {
223     m_pressStartingState = m_upDownState;
224     ScrollbarTheme& theme = ScrollbarTheme::theme();
225     m_repeatingTimer.start(theme.initialAutoscrollTimerDelay(), theme.autoscrollTimerDelay());
226 }
227
228 void SpinButtonElement::stopRepeatingTimer()
229 {
230     m_repeatingTimer.stop();
231 }
232
233 void SpinButtonElement::step(int amount)
234 {
235     if (!shouldRespondToMouseEvents())
236         return;
237     // On Mac OS, NSStepper updates the value for the button under the mouse
238     // cursor regardless of the button pressed at the beginning. So the
239     // following check is not needed for Mac OS.
240 #if !OS(MAC_OS_X)
241     if (m_upDownState != m_pressStartingState)
242         return;
243 #endif
244     doStepAction(amount);
245 }
246     
247 void SpinButtonElement::repeatingTimerFired()
248 {
249     if (m_upDownState != Indeterminate)
250         step(m_upDownState == Up ? 1 : -1);
251 }
252
253 void SpinButtonElement::setHovered(bool flag)
254 {
255     if (!flag)
256         m_upDownState = Indeterminate;
257     HTMLDivElement::setHovered(flag);
258 }
259
260 bool SpinButtonElement::shouldRespondToMouseEvents()
261 {
262     return !m_spinButtonOwner || m_spinButtonOwner->shouldSpinButtonRespondToMouseEvents();
263 }
264
265 }