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 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 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 64 void SpinButtonElement::detach(const AttachContext& context) 65 { 66 releaseCapture(); 67 HTMLDivElement::detach(context); 68 } 69 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 140 void SpinButtonElement::willOpenPopup() 141 { 142 releaseCapture(); 143 m_upDownState = Indeterminate; 144 } 145 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 164 bool SpinButtonElement::willRespondToMouseMoveEvents() 165 { 166 if (renderBox() && shouldRespondToMouseEvents()) 167 return true; 168 169 return HTMLDivElement::willRespondToMouseMoveEvents(); 170 } 171 172 bool SpinButtonElement::willRespondToMouseClickEvents() 173 { 174 if (renderBox() && shouldRespondToMouseEvents()) 175 return true; 176 177 return HTMLDivElement::willRespondToMouseClickEvents(); 178 } 179 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 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 204 bool SpinButtonElement::matchesReadOnlyPseudoClass() const 205 { 206 return shadowHost()->matchesReadOnlyPseudoClass(); 207 } 208 209 bool SpinButtonElement::matchesReadWritePseudoClass() const 210 { 211 return shadowHost()->matchesReadWritePseudoClass(); 212 } 213 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 221 void SpinButtonElement::stopRepeatingTimer() 222 { 223 m_repeatingTimer.stop(); 224 } 225 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 240 void SpinButtonElement::repeatingTimerFired(Timer<SpinButtonElement>*) 241 { 242 if (m_upDownState != Indeterminate) 243 step(m_upDownState == Up ? 1 : -1); 244 } 245 246 void SpinButtonElement::setHovered(bool flag) 247 { 248 if (!flag) 249 m_upDownState = Indeterminate; 250 HTMLDivElement::setHovered(flag); 251 } 252 253 bool SpinButtonElement::shouldRespondToMouseEvents() 254 { 255 return !m_spinButtonOwner || m_spinButtonOwner->shouldSpinButtonRespondToMouseEvents(); 256 } 257 258 } 259