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