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 "HTMLNames.h"
     31 #include "core/dom/EventNames.h"
     32 #include "core/dom/MouseEvent.h"
     33 #include "core/dom/WheelEvent.h"
     34 #include "core/html/shadow/ShadowElementNames.h"
     35 #include "core/page/Chrome.h"
     36 #include "core/page/EventHandler.h"
     37 #include "core/page/Frame.h"
     38 #include "core/page/Page.h"
     39 #include "core/platform/ScrollbarTheme.h"
     40 #include "core/rendering/RenderBox.h"
     41 
     42 namespace WebCore {
     43 
     44 using namespace HTMLNames;
     45 
     46 inline 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 }
     55 
     56 PassRefPtr<SpinButtonElement> SpinButtonElement::create(Document* document, SpinButtonOwner& spinButtonOwner)
     57 {
     58     RefPtr<SpinButtonElement> element = adoptRef(new SpinButtonElement(document, spinButtonOwner));
     59     element->setPart(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() == eventNames().mousedownEvent && 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() == eventNames().mouseupEvent && mouseEvent->button() == LeftButton)
    115         stopRepeatingTimer();
    116     else if (event->type() == eventNames().mousemoveEvent) {
    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().interfaceForWheelEvent))
    152         return;
    153 
    154     if (!m_spinButtonOwner)
    155         return;
    156 
    157     if (!m_spinButtonOwner->shouldSpinButtonRespondToWheelEvents())
    158         return;
    159 
    160     doStepAction(static_cast<WheelEvent*>(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(DARWIN)
    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