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 41namespace WebCore { 42 43using namespace HTMLNames; 44 45inline 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 55PassRefPtrWillBeRawPtr<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 63void SpinButtonElement::detach(const AttachContext& context) 64{ 65 releaseCapture(EventDispatchDisallowed); 66 HTMLDivElement::detach(context); 67} 68 69void 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()->paintInvalidationForWholeRenderer(); 129 } else { 130 releaseCapture(); 131 m_upDownState = Indeterminate; 132 } 133 } 134 135 if (!event->defaultHandled()) 136 HTMLDivElement::defaultEventHandler(event); 137} 138 139void SpinButtonElement::willOpenPopup() 140{ 141 releaseCapture(); 142 m_upDownState = Indeterminate; 143} 144 145void 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 163bool SpinButtonElement::willRespondToMouseMoveEvents() 164{ 165 if (renderBox() && shouldRespondToMouseEvents()) 166 return true; 167 168 return HTMLDivElement::willRespondToMouseMoveEvents(); 169} 170 171bool SpinButtonElement::willRespondToMouseClickEvents() 172{ 173 if (renderBox() && shouldRespondToMouseEvents()) 174 return true; 175 176 return HTMLDivElement::willRespondToMouseClickEvents(); 177} 178 179void 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 190void 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 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(), FROM_HERE); 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(MACOSX) 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 260void SpinButtonElement::trace(Visitor* visitor) 261{ 262 visitor->trace(m_spinButtonOwner); 263 HTMLDivElement::trace(visitor); 264} 265 266} 267