Home | History | Annotate | Download | only in shadow
      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