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