Home | History | Annotate | Download | only in html
      1 /*
      2  * Copyright (C) 2010 Google Inc. All rights reserved.
      3  * Copyright (C) 2011 Apple 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 are
      7  * met:
      8  *
      9  *     * Redistributions of source code must retain the above copyright
     10  * notice, this list of conditions and the following disclaimer.
     11  *     * Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *     * Neither the name of Google Inc. nor the names of its
     16  * contributors may be used to endorse or promote products derived from
     17  * this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 #include "config.h"
     33 #include "core/html/RangeInputType.h"
     34 
     35 #include <limits>
     36 #include "HTMLNames.h"
     37 #include "bindings/v8/ExceptionStatePlaceholder.h"
     38 #include "core/accessibility/AXObjectCache.h"
     39 #include "core/dom/KeyboardEvent.h"
     40 #include "core/dom/MouseEvent.h"
     41 #include "core/dom/ScopedEventQueue.h"
     42 #include "core/dom/Touch.h"
     43 #include "core/dom/TouchEvent.h"
     44 #include "core/dom/TouchList.h"
     45 #include "core/dom/shadow/ShadowRoot.h"
     46 #include "core/html/HTMLDataListElement.h"
     47 #include "core/html/HTMLDivElement.h"
     48 #include "core/html/HTMLInputElement.h"
     49 #include "core/html/HTMLOptionElement.h"
     50 #include "core/html/InputTypeNames.h"
     51 #include "core/html/StepRange.h"
     52 #include "core/html/parser/HTMLParserIdioms.h"
     53 #include "core/html/shadow/SliderThumbElement.h"
     54 #include "core/platform/PlatformMouseEvent.h"
     55 #include "core/rendering/RenderSlider.h"
     56 #include "wtf/MathExtras.h"
     57 #include "wtf/NonCopyingSort.h"
     58 #include "wtf/PassOwnPtr.h"
     59 
     60 namespace WebCore {
     61 
     62 using namespace HTMLNames;
     63 using namespace std;
     64 
     65 static const int rangeDefaultMinimum = 0;
     66 static const int rangeDefaultMaximum = 100;
     67 static const int rangeDefaultStep = 1;
     68 static const int rangeDefaultStepBase = 0;
     69 static const int rangeStepScaleFactor = 1;
     70 
     71 static Decimal ensureMaximum(const Decimal& proposedValue, const Decimal& minimum, const Decimal& fallbackValue)
     72 {
     73     return proposedValue >= minimum ? proposedValue : std::max(minimum, fallbackValue);
     74 }
     75 
     76 PassOwnPtr<InputType> RangeInputType::create(HTMLInputElement* element)
     77 {
     78     return adoptPtr(new RangeInputType(element));
     79 }
     80 
     81 RangeInputType::RangeInputType(HTMLInputElement* element)
     82     : InputType(element)
     83     , m_tickMarkValuesDirty(true)
     84 {
     85 }
     86 
     87 void RangeInputType::attach()
     88 {
     89     observeFeatureIfVisible(UseCounter::InputTypeRange);
     90 }
     91 
     92 bool RangeInputType::isRangeControl() const
     93 {
     94     return true;
     95 }
     96 
     97 const AtomicString& RangeInputType::formControlType() const
     98 {
     99     return InputTypeNames::range();
    100 }
    101 
    102 double RangeInputType::valueAsDouble() const
    103 {
    104     return parseToDoubleForNumberType(element()->value());
    105 }
    106 
    107 void RangeInputType::setValueAsDecimal(const Decimal& newValue, TextFieldEventBehavior eventBehavior, ExceptionState&) const
    108 {
    109     element()->setValue(serialize(newValue), eventBehavior);
    110 }
    111 
    112 bool RangeInputType::typeMismatchFor(const String& value) const
    113 {
    114     return !value.isEmpty() && !std::isfinite(parseToDoubleForNumberType(value));
    115 }
    116 
    117 bool RangeInputType::supportsRequired() const
    118 {
    119     return false;
    120 }
    121 
    122 StepRange RangeInputType::createStepRange(AnyStepHandling anyStepHandling) const
    123 {
    124     DEFINE_STATIC_LOCAL(const StepRange::StepDescription, stepDescription, (rangeDefaultStep, rangeDefaultStepBase, rangeStepScaleFactor));
    125 
    126     const Decimal minimum = parseToNumber(element()->fastGetAttribute(minAttr), rangeDefaultMinimum);
    127     const Decimal maximum = ensureMaximum(parseToNumber(element()->fastGetAttribute(maxAttr), rangeDefaultMaximum), minimum, rangeDefaultMaximum);
    128 
    129     const AtomicString& precisionValue = element()->fastGetAttribute(precisionAttr);
    130     if (!precisionValue.isNull()) {
    131         const Decimal step = equalIgnoringCase(precisionValue, "float") ? Decimal::nan() : 1;
    132         return StepRange(minimum, minimum, maximum, step, stepDescription);
    133     }
    134 
    135     const Decimal step = StepRange::parseStep(anyStepHandling, stepDescription, element()->fastGetAttribute(stepAttr));
    136     return StepRange(minimum, minimum, maximum, step, stepDescription);
    137 }
    138 
    139 bool RangeInputType::isSteppable() const
    140 {
    141     return true;
    142 }
    143 
    144 void RangeInputType::handleMouseDownEvent(MouseEvent* event)
    145 {
    146     if (element()->isDisabledOrReadOnly())
    147         return;
    148 
    149     Node* targetNode = event->target()->toNode();
    150     if (event->button() != LeftButton || !targetNode)
    151         return;
    152     ASSERT(element()->shadow());
    153     if (targetNode != element() && !targetNode->isDescendantOf(element()->userAgentShadowRoot()))
    154         return;
    155     SliderThumbElement* thumb = sliderThumbElementOf(element());
    156     if (targetNode == thumb)
    157         return;
    158     thumb->dragFrom(event->absoluteLocation());
    159 }
    160 
    161 void RangeInputType::handleTouchEvent(TouchEvent* event)
    162 {
    163     if (element()->isDisabledOrReadOnly())
    164         return;
    165 
    166     if (event->type() == eventNames().touchendEvent) {
    167         event->setDefaultHandled();
    168         return;
    169     }
    170 
    171     TouchList* touches = event->targetTouches();
    172     if (touches->length() == 1) {
    173         Touch* touch = touches->item(0);
    174         SliderThumbElement* thumb = sliderThumbElementOf(element());
    175         thumb->setPositionFromPoint(touch->absoluteLocation());
    176         event->setDefaultHandled();
    177     }
    178 }
    179 
    180 bool RangeInputType::hasTouchEventHandler() const
    181 {
    182     return true;
    183 }
    184 
    185 void RangeInputType::handleKeydownEvent(KeyboardEvent* event)
    186 {
    187     if (element()->isDisabledOrReadOnly())
    188         return;
    189 
    190     const String& key = event->keyIdentifier();
    191 
    192     const Decimal current = parseToNumberOrNaN(element()->value());
    193     ASSERT(current.isFinite());
    194 
    195     StepRange stepRange(createStepRange(RejectAny));
    196 
    197 
    198     // FIXME: We can't use stepUp() for the step value "any". So, we increase
    199     // or decrease the value by 1/100 of the value range. Is it reasonable?
    200     const Decimal step = equalIgnoringCase(element()->fastGetAttribute(stepAttr), "any") ? (stepRange.maximum() - stepRange.minimum()) / 100 : stepRange.step();
    201     const Decimal bigStep = max((stepRange.maximum() - stepRange.minimum()) / 10, step);
    202 
    203     bool isVertical = false;
    204     if (element()->renderer()) {
    205         ControlPart part = element()->renderer()->style()->appearance();
    206         isVertical = part == SliderVerticalPart || part == MediaVolumeSliderPart;
    207     }
    208 
    209     Decimal newValue;
    210     if (key == "Up")
    211         newValue = current + step;
    212     else if (key == "Down")
    213         newValue = current - step;
    214     else if (key == "Left")
    215         newValue = isVertical ? current + step : current - step;
    216     else if (key == "Right")
    217         newValue = isVertical ? current - step : current + step;
    218     else if (key == "PageUp")
    219         newValue = current + bigStep;
    220     else if (key == "PageDown")
    221         newValue = current - bigStep;
    222     else if (key == "Home")
    223         newValue = isVertical ? stepRange.maximum() : stepRange.minimum();
    224     else if (key == "End")
    225         newValue = isVertical ? stepRange.minimum() : stepRange.maximum();
    226     else
    227         return; // Did not match any key binding.
    228 
    229     newValue = stepRange.clampValue(newValue);
    230 
    231     if (newValue != current) {
    232         EventQueueScope scope;
    233         TextFieldEventBehavior eventBehavior = DispatchChangeEvent;
    234         setValueAsDecimal(newValue, eventBehavior, IGNORE_EXCEPTION);
    235 
    236         if (AXObjectCache* cache = element()->document()->existingAXObjectCache())
    237             cache->postNotification(element(), AXObjectCache::AXValueChanged, true);
    238         element()->dispatchFormControlChangeEvent();
    239     }
    240 
    241     event->setDefaultHandled();
    242 }
    243 
    244 void RangeInputType::createShadowSubtree()
    245 {
    246     ASSERT(element()->shadow());
    247 
    248     Document* document = element()->document();
    249     RefPtr<HTMLDivElement> track = HTMLDivElement::create(document);
    250     track->setPart(AtomicString("-webkit-slider-runnable-track", AtomicString::ConstructFromLiteral));
    251     track->appendChild(SliderThumbElement::create(document), IGNORE_EXCEPTION);
    252     RefPtr<HTMLElement> container = SliderContainerElement::create(document);
    253     container->appendChild(track.release(), IGNORE_EXCEPTION);
    254     element()->userAgentShadowRoot()->appendChild(container.release(), IGNORE_EXCEPTION);
    255 }
    256 
    257 RenderObject* RangeInputType::createRenderer(RenderStyle*) const
    258 {
    259     return new RenderSlider(element());
    260 }
    261 
    262 Decimal RangeInputType::parseToNumber(const String& src, const Decimal& defaultValue) const
    263 {
    264     return parseToDecimalForNumberType(src, defaultValue);
    265 }
    266 
    267 String RangeInputType::serialize(const Decimal& value) const
    268 {
    269     if (!value.isFinite())
    270         return String();
    271     return serializeForNumberType(value);
    272 }
    273 
    274 // FIXME: Could share this with BaseClickableWithKeyInputType and BaseCheckableInputType if we had a common base class.
    275 void RangeInputType::accessKeyAction(bool sendMouseEvents)
    276 {
    277     InputType::accessKeyAction(sendMouseEvents);
    278 
    279     element()->dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
    280 }
    281 
    282 void RangeInputType::minOrMaxAttributeChanged()
    283 {
    284     InputType::minOrMaxAttributeChanged();
    285 
    286     // Sanitize the value.
    287     if (element()->hasDirtyValue())
    288         element()->setValue(element()->value());
    289 
    290     sliderThumbElementOf(element())->setPositionFromValue();
    291 }
    292 
    293 void RangeInputType::setValue(const String& value, bool valueChanged, TextFieldEventBehavior eventBehavior)
    294 {
    295     InputType::setValue(value, valueChanged, eventBehavior);
    296 
    297     if (!valueChanged)
    298         return;
    299 
    300     sliderThumbElementOf(element())->setPositionFromValue();
    301 }
    302 
    303 String RangeInputType::fallbackValue() const
    304 {
    305     return serializeForNumberType(createStepRange(RejectAny).defaultValue());
    306 }
    307 
    308 String RangeInputType::sanitizeValue(const String& proposedValue) const
    309 {
    310     StepRange stepRange(createStepRange(RejectAny));
    311     const Decimal proposedNumericValue = parseToNumber(proposedValue, stepRange.defaultValue());
    312     return serializeForNumberType(stepRange.clampValue(proposedNumericValue));
    313 }
    314 
    315 bool RangeInputType::shouldRespectListAttribute()
    316 {
    317     return InputType::themeSupportsDataListUI(this);
    318 }
    319 
    320 HTMLElement* RangeInputType::sliderThumbElement() const
    321 {
    322     return sliderThumbElementOf(element());
    323 }
    324 
    325 HTMLElement* RangeInputType::sliderTrackElement() const
    326 {
    327     return sliderTrackElementOf(element());
    328 }
    329 
    330 void RangeInputType::listAttributeTargetChanged()
    331 {
    332     m_tickMarkValuesDirty = true;
    333     HTMLElement* sliderTrackElement = sliderTrackElementOf(element());
    334     if (sliderTrackElement->renderer())
    335         sliderTrackElement->renderer()->setNeedsLayout();
    336 }
    337 
    338 static bool decimalCompare(const Decimal& a, const Decimal& b)
    339 {
    340     return a < b;
    341 }
    342 
    343 void RangeInputType::updateTickMarkValues()
    344 {
    345     if (!m_tickMarkValuesDirty)
    346         return;
    347     m_tickMarkValues.clear();
    348     m_tickMarkValuesDirty = false;
    349     HTMLDataListElement* dataList = element()->dataList();
    350     if (!dataList)
    351         return;
    352     RefPtr<HTMLCollection> options = dataList->options();
    353     m_tickMarkValues.reserveCapacity(options->length());
    354     for (unsigned i = 0; i < options->length(); ++i) {
    355         Node* node = options->item(i);
    356         HTMLOptionElement* optionElement = toHTMLOptionElement(node);
    357         String optionValue = optionElement->value();
    358         if (!element()->isValidValue(optionValue))
    359             continue;
    360         m_tickMarkValues.append(parseToNumber(optionValue, Decimal::nan()));
    361     }
    362     m_tickMarkValues.shrinkToFit();
    363     nonCopyingSort(m_tickMarkValues.begin(), m_tickMarkValues.end(), decimalCompare);
    364 }
    365 
    366 Decimal RangeInputType::findClosestTickMarkValue(const Decimal& value)
    367 {
    368     updateTickMarkValues();
    369     if (!m_tickMarkValues.size())
    370         return Decimal::nan();
    371 
    372     size_t left = 0;
    373     size_t right = m_tickMarkValues.size();
    374     size_t middle;
    375     while (true) {
    376         ASSERT(left <= right);
    377         middle = left + (right - left) / 2;
    378         if (!middle)
    379             break;
    380         if (middle == m_tickMarkValues.size() - 1 && m_tickMarkValues[middle] < value) {
    381             middle++;
    382             break;
    383         }
    384         if (m_tickMarkValues[middle - 1] <= value && m_tickMarkValues[middle] >= value)
    385             break;
    386 
    387         if (m_tickMarkValues[middle] < value)
    388             left = middle;
    389         else
    390             right = middle;
    391     }
    392     const Decimal closestLeft = middle ? m_tickMarkValues[middle - 1] : Decimal::infinity(Decimal::Negative);
    393     const Decimal closestRight = middle != m_tickMarkValues.size() ? m_tickMarkValues[middle] : Decimal::infinity(Decimal::Positive);
    394     if (closestRight - value < value - closestLeft)
    395         return closestRight;
    396     return closestLeft;
    397 }
    398 
    399 } // namespace WebCore
    400