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/forms/RangeInputType.h"
34
35#include "bindings/v8/ExceptionStatePlaceholder.h"
36#include "core/HTMLNames.h"
37#include "core/InputTypeNames.h"
38#include "core/accessibility/AXObjectCache.h"
39#include "core/events/KeyboardEvent.h"
40#include "core/events/MouseEvent.h"
41#include "core/events/ScopedEventQueue.h"
42#include "core/dom/Touch.h"
43#include "core/events/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/forms/StepRange.h"
51#include "core/html/parser/HTMLParserIdioms.h"
52#include "core/html/shadow/ShadowElementNames.h"
53#include "core/html/shadow/SliderThumbElement.h"
54#include "core/rendering/RenderSlider.h"
55#include "platform/PlatformMouseEvent.h"
56#include "wtf/MathExtras.h"
57#include "wtf/NonCopyingSort.h"
58#include "wtf/PassOwnPtr.h"
59#include <limits>
60
61namespace WebCore {
62
63using namespace HTMLNames;
64
65static const int rangeDefaultMinimum = 0;
66static const int rangeDefaultMaximum = 100;
67static const int rangeDefaultStep = 1;
68static const int rangeDefaultStepBase = 0;
69static const int rangeStepScaleFactor = 1;
70
71static Decimal ensureMaximum(const Decimal& proposedValue, const Decimal& minimum, const Decimal& fallbackValue)
72{
73    return proposedValue >= minimum ? proposedValue : std::max(minimum, fallbackValue);
74}
75
76PassRefPtrWillBeRawPtr<InputType> RangeInputType::create(HTMLInputElement& element)
77{
78    return adoptRefWillBeNoop(new RangeInputType(element));
79}
80
81RangeInputType::RangeInputType(HTMLInputElement& element)
82    : InputType(element)
83    , m_tickMarkValuesDirty(true)
84{
85}
86
87void RangeInputType::countUsage()
88{
89    countUsageIfVisible(UseCounter::InputTypeRange);
90}
91
92bool RangeInputType::isRangeControl() const
93{
94    return true;
95}
96
97const AtomicString& RangeInputType::formControlType() const
98{
99    return InputTypeNames::range;
100}
101
102double RangeInputType::valueAsDouble() const
103{
104    return parseToDoubleForNumberType(element().value());
105}
106
107void RangeInputType::setValueAsDouble(double newValue, TextFieldEventBehavior eventBehavior, ExceptionState& exceptionState) const
108{
109    setValueAsDecimal(Decimal::fromDouble(newValue), eventBehavior, exceptionState);
110}
111
112bool RangeInputType::typeMismatchFor(const String& value) const
113{
114    return !value.isEmpty() && !std::isfinite(parseToDoubleForNumberType(value));
115}
116
117bool RangeInputType::supportsRequired() const
118{
119    return false;
120}
121
122StepRange RangeInputType::createStepRange(AnyStepHandling anyStepHandling) const
123{
124    DEFINE_STATIC_LOCAL(const StepRange::StepDescription, stepDescription, (rangeDefaultStep, rangeDefaultStepBase, rangeStepScaleFactor));
125
126    const Decimal stepBase = findStepBase(rangeDefaultStepBase);
127    const Decimal minimum = parseToNumber(element().fastGetAttribute(minAttr), rangeDefaultMinimum);
128    const Decimal maximum = ensureMaximum(parseToNumber(element().fastGetAttribute(maxAttr), rangeDefaultMaximum), minimum, rangeDefaultMaximum);
129
130    const Decimal step = StepRange::parseStep(anyStepHandling, stepDescription, element().fastGetAttribute(stepAttr));
131    return StepRange(stepBase, minimum, maximum, step, stepDescription);
132}
133
134bool RangeInputType::isSteppable() const
135{
136    return true;
137}
138
139void RangeInputType::handleMouseDownEvent(MouseEvent* event)
140{
141    if (element().isDisabledOrReadOnly())
142        return;
143
144    Node* targetNode = event->target()->toNode();
145    if (event->button() != LeftButton || !targetNode)
146        return;
147    ASSERT(element().shadow());
148    if (targetNode != element() && !targetNode->isDescendantOf(element().userAgentShadowRoot()))
149        return;
150    SliderThumbElement* thumb = sliderThumbElement();
151    if (targetNode == thumb)
152        return;
153    thumb->dragFrom(event->absoluteLocation());
154}
155
156void RangeInputType::handleTouchEvent(TouchEvent* event)
157{
158    if (element().isDisabledOrReadOnly())
159        return;
160
161    if (event->type() == EventTypeNames::touchend) {
162        element().dispatchFormControlChangeEvent();
163        event->setDefaultHandled();
164        return;
165    }
166
167    TouchList* touches = event->targetTouches();
168    if (touches->length() == 1) {
169        sliderThumbElement()->setPositionFromPoint(touches->item(0)->absoluteLocation());
170        event->setDefaultHandled();
171    }
172}
173
174bool RangeInputType::hasTouchEventHandler() const
175{
176    return true;
177}
178
179void RangeInputType::handleKeydownEvent(KeyboardEvent* event)
180{
181    if (element().isDisabledOrReadOnly())
182        return;
183
184    const String& key = event->keyIdentifier();
185
186    const Decimal current = parseToNumberOrNaN(element().value());
187    ASSERT(current.isFinite());
188
189    StepRange stepRange(createStepRange(RejectAny));
190
191
192    // FIXME: We can't use stepUp() for the step value "any". So, we increase
193    // or decrease the value by 1/100 of the value range. Is it reasonable?
194    const Decimal step = equalIgnoringCase(element().fastGetAttribute(stepAttr), "any") ? (stepRange.maximum() - stepRange.minimum()) / 100 : stepRange.step();
195    const Decimal bigStep = std::max((stepRange.maximum() - stepRange.minimum()) / 10, step);
196
197    bool isVertical = false;
198    if (element().renderer()) {
199        ControlPart part = element().renderer()->style()->appearance();
200        isVertical = part == SliderVerticalPart;
201    }
202
203    Decimal newValue;
204    if (key == "Up")
205        newValue = current + step;
206    else if (key == "Down")
207        newValue = current - step;
208    else if (key == "Left")
209        newValue = isVertical ? current + step : current - step;
210    else if (key == "Right")
211        newValue = isVertical ? current - step : current + step;
212    else if (key == "PageUp")
213        newValue = current + bigStep;
214    else if (key == "PageDown")
215        newValue = current - bigStep;
216    else if (key == "Home")
217        newValue = isVertical ? stepRange.maximum() : stepRange.minimum();
218    else if (key == "End")
219        newValue = isVertical ? stepRange.minimum() : stepRange.maximum();
220    else
221        return; // Did not match any key binding.
222
223    newValue = stepRange.clampValue(newValue);
224
225    if (newValue != current) {
226        EventQueueScope scope;
227        TextFieldEventBehavior eventBehavior = DispatchInputAndChangeEvent;
228        setValueAsDecimal(newValue, eventBehavior, IGNORE_EXCEPTION);
229
230        if (AXObjectCache* cache = element().document().existingAXObjectCache())
231            cache->postNotification(&element(), AXObjectCache::AXValueChanged, true);
232    }
233
234    event->setDefaultHandled();
235}
236
237void RangeInputType::createShadowSubtree()
238{
239    ASSERT(element().shadow());
240
241    Document& document = element().document();
242    RefPtrWillBeRawPtr<HTMLDivElement> track = HTMLDivElement::create(document);
243    track->setShadowPseudoId(AtomicString("-webkit-slider-runnable-track", AtomicString::ConstructFromLiteral));
244    track->setAttribute(idAttr, ShadowElementNames::sliderTrack());
245    track->appendChild(SliderThumbElement::create(document));
246    RefPtrWillBeRawPtr<HTMLElement> container = SliderContainerElement::create(document);
247    container->appendChild(track.release());
248    element().userAgentShadowRoot()->appendChild(container.release());
249}
250
251RenderObject* RangeInputType::createRenderer(RenderStyle*) const
252{
253    return new RenderSlider(&element());
254}
255
256Decimal RangeInputType::parseToNumber(const String& src, const Decimal& defaultValue) const
257{
258    return parseToDecimalForNumberType(src, defaultValue);
259}
260
261String RangeInputType::serialize(const Decimal& value) const
262{
263    if (!value.isFinite())
264        return String();
265    return serializeForNumberType(value);
266}
267
268// FIXME: Could share this with BaseClickableWithKeyInputType and BaseCheckableInputType if we had a common base class.
269void RangeInputType::accessKeyAction(bool sendMouseEvents)
270{
271    InputType::accessKeyAction(sendMouseEvents);
272
273    element().dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
274}
275
276void RangeInputType::sanitizeValueInResponseToMinOrMaxAttributeChange()
277{
278    if (element().hasDirtyValue())
279        element().setValue(element().value());
280
281    sliderThumbElement()->setPositionFromValue();
282}
283
284void RangeInputType::setValue(const String& value, bool valueChanged, TextFieldEventBehavior eventBehavior)
285{
286    InputType::setValue(value, valueChanged, eventBehavior);
287
288    if (!valueChanged)
289        return;
290
291    sliderThumbElement()->setPositionFromValue();
292}
293
294String RangeInputType::fallbackValue() const
295{
296    return serializeForNumberType(createStepRange(RejectAny).defaultValue());
297}
298
299String RangeInputType::sanitizeValue(const String& proposedValue) const
300{
301    StepRange stepRange(createStepRange(RejectAny));
302    const Decimal proposedNumericValue = parseToNumber(proposedValue, stepRange.defaultValue());
303    return serializeForNumberType(stepRange.clampValue(proposedNumericValue));
304}
305
306void RangeInputType::disabledAttributeChanged()
307{
308    if (element().isDisabledFormControl())
309        sliderThumbElement()->stopDragging();
310}
311
312bool RangeInputType::shouldRespectListAttribute()
313{
314    return true;
315}
316
317inline SliderThumbElement* RangeInputType::sliderThumbElement() const
318{
319    return toSliderThumbElement(element().userAgentShadowRoot()->getElementById(ShadowElementNames::sliderThumb()));
320}
321
322inline Element* RangeInputType::sliderTrackElement() const
323{
324    return element().userAgentShadowRoot()->getElementById(ShadowElementNames::sliderTrack());
325}
326
327void RangeInputType::listAttributeTargetChanged()
328{
329    m_tickMarkValuesDirty = true;
330    Element* sliderTrackElement = this->sliderTrackElement();
331    if (sliderTrackElement->renderer())
332        sliderTrackElement->renderer()->setNeedsLayoutAndFullPaintInvalidation();
333}
334
335static bool decimalCompare(const Decimal& a, const Decimal& b)
336{
337    return a < b;
338}
339
340void RangeInputType::updateTickMarkValues()
341{
342    if (!m_tickMarkValuesDirty)
343        return;
344    m_tickMarkValues.clear();
345    m_tickMarkValuesDirty = false;
346    HTMLDataListElement* dataList = element().dataList();
347    if (!dataList)
348        return;
349    RefPtrWillBeRawPtr<HTMLCollection> options = dataList->options();
350    m_tickMarkValues.reserveCapacity(options->length());
351    for (unsigned i = 0; i < options->length(); ++i) {
352        Element* element = options->item(i);
353        HTMLOptionElement* optionElement = toHTMLOptionElement(element);
354        String optionValue = optionElement->value();
355        if (!this->element().isValidValue(optionValue))
356            continue;
357        m_tickMarkValues.append(parseToNumber(optionValue, Decimal::nan()));
358    }
359    m_tickMarkValues.shrinkToFit();
360    nonCopyingSort(m_tickMarkValues.begin(), m_tickMarkValues.end(), decimalCompare);
361}
362
363Decimal RangeInputType::findClosestTickMarkValue(const Decimal& value)
364{
365    updateTickMarkValues();
366    if (!m_tickMarkValues.size())
367        return Decimal::nan();
368
369    size_t left = 0;
370    size_t right = m_tickMarkValues.size();
371    size_t middle;
372    while (true) {
373        ASSERT(left <= right);
374        middle = left + (right - left) / 2;
375        if (!middle)
376            break;
377        if (middle == m_tickMarkValues.size() - 1 && m_tickMarkValues[middle] < value) {
378            middle++;
379            break;
380        }
381        if (m_tickMarkValues[middle - 1] <= value && m_tickMarkValues[middle] >= value)
382            break;
383
384        if (m_tickMarkValues[middle] < value)
385            left = middle;
386        else
387            right = middle;
388    }
389    const Decimal closestLeft = middle ? m_tickMarkValues[middle - 1] : Decimal::infinity(Decimal::Negative);
390    const Decimal closestRight = middle != m_tickMarkValues.size() ? m_tickMarkValues[middle] : Decimal::infinity(Decimal::Positive);
391    if (closestRight - value < value - closestLeft)
392        return closestRight;
393    return closestLeft;
394}
395
396} // namespace WebCore
397