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