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 "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 "ScrollbarTheme.h" 39#include "WheelEvent.h" 40 41namespace WebCore { 42 43using namespace HTMLNames; 44 45inline SpinButtonElement::SpinButtonElement(Document* document, SpinButtonOwner& spinButtonOwner) 46 : HTMLDivElement(divTag, document) 47 , m_spinButtonOwner(&spinButtonOwner) 48 , m_capturing(false) 49 , m_upDownState(Indeterminate) 50 , m_pressStartingState(Indeterminate) 51 , m_repeatingTimer(this, &SpinButtonElement::repeatingTimerFired) 52{ 53} 54 55PassRefPtr<SpinButtonElement> SpinButtonElement::create(Document* document, SpinButtonOwner& spinButtonOwner) 56{ 57 return adoptRef(new SpinButtonElement(document, spinButtonOwner)); 58} 59 60const AtomicString& SpinButtonElement::shadowPseudoId() const 61{ 62 DEFINE_STATIC_LOCAL(AtomicString, innerPseudoId, ("-webkit-inner-spin-button", AtomicString::ConstructFromLiteral)); 63 return innerPseudoId; 64} 65 66void SpinButtonElement::detach(const AttachContext& context) 67{ 68 releaseCapture(); 69 HTMLDivElement::detach(context); 70} 71 72void SpinButtonElement::defaultEventHandler(Event* event) 73{ 74 if (!event->isMouseEvent()) { 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 = static_cast<MouseEvent*>(event); 94 IntPoint local = roundedIntPoint(box->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms)); 95 if (mouseEvent->type() == eventNames().mousedownEvent && mouseEvent->button() == LeftButton) { 96 if (box->pixelSnappedBorderBoxRect().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 RefPtr<Node> protector(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 event->setDefaultHandled(); 115 } 116 } else if (mouseEvent->type() == eventNames().mouseupEvent && mouseEvent->button() == LeftButton) 117 stopRepeatingTimer(); 118 else if (event->type() == eventNames().mousemoveEvent) { 119 if (box->pixelSnappedBorderBoxRect().contains(local)) { 120 if (!m_capturing) { 121 if (Frame* frame = document()->frame()) { 122 frame->eventHandler()->setCapturingMouseEventsNode(this); 123 m_capturing = true; 124 if (Page* page = document()->page()) 125 page->chrome().registerPopupOpeningObserver(this); 126 } 127 } 128 UpDownState oldUpDownState = m_upDownState; 129 m_upDownState = local.y() < box->height() / 2 ? Up : Down; 130 if (m_upDownState != oldUpDownState) 131 renderer()->repaint(); 132 } else { 133 releaseCapture(); 134 m_upDownState = Indeterminate; 135 } 136 } 137 138 if (!event->defaultHandled()) 139 HTMLDivElement::defaultEventHandler(event); 140} 141 142void SpinButtonElement::willOpenPopup() 143{ 144 releaseCapture(); 145 m_upDownState = Indeterminate; 146} 147 148void SpinButtonElement::forwardEvent(Event* event) 149{ 150 if (!renderBox()) 151 return; 152 153 if (!event->hasInterface(eventNames().interfaceForWheelEvent)) 154 return; 155 156 if (!m_spinButtonOwner) 157 return; 158 159 if (!m_spinButtonOwner->shouldSpinButtonRespondToWheelEvents()) 160 return; 161 162 doStepAction(static_cast<WheelEvent*>(event)->wheelDeltaY()); 163 event->setDefaultHandled(); 164} 165 166bool SpinButtonElement::willRespondToMouseMoveEvents() 167{ 168 if (renderBox() && shouldRespondToMouseEvents()) 169 return true; 170 171 return HTMLDivElement::willRespondToMouseMoveEvents(); 172} 173 174bool SpinButtonElement::willRespondToMouseClickEvents() 175{ 176 if (renderBox() && shouldRespondToMouseEvents()) 177 return true; 178 179 return HTMLDivElement::willRespondToMouseClickEvents(); 180} 181 182void SpinButtonElement::doStepAction(int amount) 183{ 184 if (!m_spinButtonOwner) 185 return; 186 187 if (amount > 0) 188 m_spinButtonOwner->spinButtonStepUp(); 189 else if (amount < 0) 190 m_spinButtonOwner->spinButtonStepDown(); 191} 192 193void SpinButtonElement::releaseCapture() 194{ 195 stopRepeatingTimer(); 196 if (m_capturing) { 197 if (Frame* frame = document()->frame()) { 198 frame->eventHandler()->setCapturingMouseEventsNode(0); 199 m_capturing = false; 200 if (Page* page = document()->page()) 201 page->chrome().unregisterPopupOpeningObserver(this); 202 } 203 } 204} 205 206bool SpinButtonElement::matchesReadOnlyPseudoClass() const 207{ 208 return shadowHost()->matchesReadOnlyPseudoClass(); 209} 210 211bool SpinButtonElement::matchesReadWritePseudoClass() const 212{ 213 return shadowHost()->matchesReadWritePseudoClass(); 214} 215 216void SpinButtonElement::startRepeatingTimer() 217{ 218 m_pressStartingState = m_upDownState; 219 ScrollbarTheme* theme = ScrollbarTheme::theme(); 220 m_repeatingTimer.start(theme->initialAutoscrollTimerDelay(), theme->autoscrollTimerDelay()); 221} 222 223void SpinButtonElement::stopRepeatingTimer() 224{ 225 m_repeatingTimer.stop(); 226} 227 228void SpinButtonElement::step(int amount) 229{ 230 if (!shouldRespondToMouseEvents()) 231 return; 232 // On Mac OS, NSStepper updates the value for the button under the mouse 233 // cursor regardless of the button pressed at the beginning. So the 234 // following check is not needed for Mac OS. 235#if !OS(MAC_OS_X) 236 if (m_upDownState != m_pressStartingState) 237 return; 238#endif 239 doStepAction(amount); 240} 241 242void SpinButtonElement::repeatingTimerFired(Timer<SpinButtonElement>*) 243{ 244 if (m_upDownState != Indeterminate) 245 step(m_upDownState == Up ? 1 : -1); 246} 247 248void SpinButtonElement::setHovered(bool flag) 249{ 250 if (!flag) 251 m_upDownState = Indeterminate; 252 HTMLDivElement::setHovered(flag); 253} 254 255bool SpinButtonElement::shouldRespondToMouseEvents() 256{ 257 return !m_spinButtonOwner || m_spinButtonOwner->shouldSpinButtonRespondToMouseEvents(); 258} 259 260} 261