2 * Copyright (C) 2006, 2008, 2010 Apple Inc. All rights reserved.
3 * Copyright (C) 2010 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
28 #include "core/html/shadow/SpinButtonElement.h"
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"
44 using namespace HTMLNames;
46 inline SpinButtonElement::SpinButtonElement(Document& document, SpinButtonOwner& spinButtonOwner)
47 : HTMLDivElement(document)
48 , m_spinButtonOwner(&spinButtonOwner)
50 , m_upDownState(Indeterminate)
51 , m_pressStartingState(Indeterminate)
52 , m_repeatingTimer(this, &SpinButtonElement::repeatingTimerFired)
56 PassRefPtr<SpinButtonElement> SpinButtonElement::create(Document& document, SpinButtonOwner& spinButtonOwner)
58 RefPtr<SpinButtonElement> element = adoptRef(new SpinButtonElement(document, spinButtonOwner));
59 element->setShadowPseudoId(AtomicString("-webkit-inner-spin-button", AtomicString::ConstructFromLiteral));
60 element->setAttribute(idAttr, ShadowElementNames::spinButton());
61 return element.release();
64 void SpinButtonElement::detach(const AttachContext& context)
67 HTMLDivElement::detach(context);
70 void SpinButtonElement::defaultEventHandler(Event* event)
72 if (!event->isMouseEvent()) {
73 if (!event->defaultHandled())
74 HTMLDivElement::defaultEventHandler(event);
78 RenderBox* box = renderBox();
80 if (!event->defaultHandled())
81 HTMLDivElement::defaultEventHandler(event);
85 if (!shouldRespondToMouseEvents()) {
86 if (!event->defaultHandled())
87 HTMLDivElement::defaultEventHandler(event);
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();
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);
112 event->setDefaultHandled();
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)) {
119 if (Frame* frame = document().frame()) {
120 frame->eventHandler().setCapturingMouseEventsNode(this);
122 if (Page* page = document().page())
123 page->chrome().registerPopupOpeningObserver(this);
126 UpDownState oldUpDownState = m_upDownState;
127 m_upDownState = (local.y() < box->height() / 2) ? Up : Down;
128 if (m_upDownState != oldUpDownState)
129 renderer()->repaint();
132 m_upDownState = Indeterminate;
136 if (!event->defaultHandled())
137 HTMLDivElement::defaultEventHandler(event);
140 void SpinButtonElement::willOpenPopup()
143 m_upDownState = Indeterminate;
146 void SpinButtonElement::forwardEvent(Event* event)
151 if (!event->hasInterface(EventNames::WheelEvent))
154 if (!m_spinButtonOwner)
157 if (!m_spinButtonOwner->shouldSpinButtonRespondToWheelEvents())
160 doStepAction(toWheelEvent(event)->wheelDeltaY());
161 event->setDefaultHandled();
164 bool SpinButtonElement::willRespondToMouseMoveEvents()
166 if (renderBox() && shouldRespondToMouseEvents())
169 return HTMLDivElement::willRespondToMouseMoveEvents();
172 bool SpinButtonElement::willRespondToMouseClickEvents()
174 if (renderBox() && shouldRespondToMouseEvents())
177 return HTMLDivElement::willRespondToMouseClickEvents();
180 void SpinButtonElement::doStepAction(int amount)
182 if (!m_spinButtonOwner)
186 m_spinButtonOwner->spinButtonStepUp();
188 m_spinButtonOwner->spinButtonStepDown();
191 void SpinButtonElement::releaseCapture()
193 stopRepeatingTimer();
195 if (Frame* frame = document().frame()) {
196 frame->eventHandler().setCapturingMouseEventsNode(0);
198 if (Page* page = document().page())
199 page->chrome().unregisterPopupOpeningObserver(this);
204 bool SpinButtonElement::matchesReadOnlyPseudoClass() const
206 return shadowHost()->matchesReadOnlyPseudoClass();
209 bool SpinButtonElement::matchesReadWritePseudoClass() const
211 return shadowHost()->matchesReadWritePseudoClass();
214 void SpinButtonElement::startRepeatingTimer()
216 m_pressStartingState = m_upDownState;
217 ScrollbarTheme* theme = ScrollbarTheme::theme();
218 m_repeatingTimer.start(theme->initialAutoscrollTimerDelay(), theme->autoscrollTimerDelay());
221 void SpinButtonElement::stopRepeatingTimer()
223 m_repeatingTimer.stop();
226 void SpinButtonElement::step(int amount)
228 if (!shouldRespondToMouseEvents())
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.
234 if (m_upDownState != m_pressStartingState)
237 doStepAction(amount);
240 void SpinButtonElement::repeatingTimerFired(Timer<SpinButtonElement>*)
242 if (m_upDownState != Indeterminate)
243 step(m_upDownState == Up ? 1 : -1);
246 void SpinButtonElement::setHovered(bool flag)
249 m_upDownState = Indeterminate;
250 HTMLDivElement::setHovered(flag);
253 bool SpinButtonElement::shouldRespondToMouseEvents()
255 return !m_spinButtonOwner || m_spinButtonOwner->shouldSpinButtonRespondToMouseEvents();