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 "RangeInputType.h"
34
35#include "HTMLInputElement.h"
36#include "HTMLNames.h"
37#include "HTMLParserIdioms.h"
38#include "KeyboardEvent.h"
39#include "MouseEvent.h"
40#include "PlatformMouseEvent.h"
41#include "RenderSlider.h"
42#include "ShadowRoot.h"
43#include "SliderThumbElement.h"
44#include "StepRange.h"
45#include <limits>
46#include <wtf/MathExtras.h>
47#include <wtf/PassOwnPtr.h>
48
49#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
50#include "TouchEvent.h"
51#endif
52
53namespace WebCore {
54
55using namespace HTMLNames;
56using namespace std;
57
58static const double rangeDefaultMinimum = 0.0;
59static const double rangeDefaultMaximum = 100.0;
60static const double rangeDefaultStep = 1.0;
61static const double rangeStepScaleFactor = 1.0;
62
63PassOwnPtr<InputType> RangeInputType::create(HTMLInputElement* element)
64{
65    return adoptPtr(new RangeInputType(element));
66}
67
68bool RangeInputType::isRangeControl() const
69{
70    return true;
71}
72
73const AtomicString& RangeInputType::formControlType() const
74{
75    return InputTypeNames::range();
76}
77
78double RangeInputType::valueAsNumber() const
79{
80    return parseToDouble(element()->value(), numeric_limits<double>::quiet_NaN());
81}
82
83void RangeInputType::setValueAsNumber(double newValue, ExceptionCode&) const
84{
85    element()->setValue(serialize(newValue));
86}
87
88bool RangeInputType::supportsRequired() const
89{
90    return false;
91}
92
93bool RangeInputType::rangeUnderflow(const String& value) const
94{
95    // Guaranteed by sanitization.
96    ASSERT_UNUSED(value, parseToDouble(value, numeric_limits<double>::quiet_NaN()) >= minimum());
97    return false;
98}
99
100bool RangeInputType::rangeOverflow(const String& value) const
101{
102    // Guaranteed by sanitization.
103    ASSERT_UNUSED(value, parseToDouble(value, numeric_limits<double>::quiet_NaN()) <= maximum());
104    return false;
105}
106
107bool RangeInputType::supportsRangeLimitation() const
108{
109    return true;
110}
111
112double RangeInputType::minimum() const
113{
114    return parseToDouble(element()->fastGetAttribute(minAttr), rangeDefaultMinimum);
115}
116
117double RangeInputType::maximum() const
118{
119    double max = parseToDouble(element()->fastGetAttribute(maxAttr), rangeDefaultMaximum);
120    // A remedy for the inconsistent min/max values.
121    // Sets the maximum to the default or the minimum value.
122    double min = minimum();
123    if (max < min)
124        max = std::max(min, rangeDefaultMaximum);
125    return max;
126}
127
128bool RangeInputType::stepMismatch(const String&, double) const
129{
130    // stepMismatch doesn't occur for type=range. RenderSlider guarantees the
131    // value matches to step on user input, and sanitization takes care
132    // of the general case.
133    return false;
134}
135
136double RangeInputType::stepBase() const
137{
138    return minimum();
139}
140
141double RangeInputType::defaultStep() const
142{
143    return rangeDefaultStep;
144}
145
146double RangeInputType::stepScaleFactor() const
147{
148    return rangeStepScaleFactor;
149}
150
151void RangeInputType::handleMouseDownEvent(MouseEvent* event)
152{
153    if (event->button() != LeftButton || event->target() != element())
154        return;
155
156    if (SliderThumbElement* thumb = shadowSliderThumb())
157        thumb->dragFrom(event->absoluteLocation());
158}
159
160void RangeInputType::handleKeydownEvent(KeyboardEvent* event)
161{
162    if (element()->disabled() || element()->readOnly())
163        return;
164    const String& key = event->keyIdentifier();
165    if (key != "Up" && key != "Right" && key != "Down" && key != "Left")
166        return;
167
168    ExceptionCode ec;
169    if (equalIgnoringCase(element()->fastGetAttribute(stepAttr), "any")) {
170        double min = minimum();
171        double max = maximum();
172        // FIXME: We can't use stepUp() for the step value "any". So, we increase
173        // or decrease the value by 1/100 of the value range. Is it reasonable?
174        double step = (max - min) / 100;
175        double current = parseToDouble(element()->value(), numeric_limits<double>::quiet_NaN());
176        ASSERT(isfinite(current));
177        // Stepping-up and -down for step="any" are special cases for type="range" from renderer for convenient.
178        // No stepping normally for step="any". They cannot be handled by stepUp()/stepDown()/stepUpFromRenderer().
179        // So calculating values stepped-up or -down here.
180        double newValue;
181        if (key == "Up" || key == "Right") {
182            newValue = current + step;
183            if (newValue > max)
184                newValue = max;
185        } else {
186            newValue = current - step;
187            if (newValue < min)
188                newValue = min;
189        }
190        if (newValue != current) {
191            setValueAsNumber(newValue, ec);
192            element()->dispatchFormControlChangeEvent();
193        }
194    } else {
195        int stepMagnification = (key == "Up" || key == "Right") ? 1 : -1;
196        // Reasonable stepping-up/-down by stepUpFromRenderer() unless step="any"
197        element()->stepUpFromRenderer(stepMagnification);
198    }
199    event->setDefaultHandled();
200}
201
202#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
203void RangeInputType::handleTouchStartEvent(TouchEvent* touchEvent)
204{
205    if (SliderThumbElement* thumb = shadowSliderThumb()) {
206        if (touchEvent->touches() && touchEvent->touches()->item(0)) {
207            IntPoint curPoint;
208            curPoint.setX(touchEvent->touches()->item(0)->pageX());
209            curPoint.setY(touchEvent->touches()->item(0)->pageY());
210            thumb->dragFrom(curPoint);
211            touchEvent->setDefaultHandled();
212            touchEvent->setDefaultPrevented(true);
213        }
214    }
215}
216#endif
217
218void RangeInputType::createShadowSubtree()
219{
220    ExceptionCode ec = 0;
221    element()->ensureShadowRoot()->appendChild(SliderThumbElement::create(element()->document()), ec);
222}
223
224RenderObject* RangeInputType::createRenderer(RenderArena* arena, RenderStyle*) const
225{
226    return new (arena) RenderSlider(element());
227}
228
229double RangeInputType::parseToDouble(const String& src, double defaultValue) const
230{
231    double numberValue;
232    if (!parseToDoubleForNumberType(src, &numberValue))
233        return defaultValue;
234    ASSERT(isfinite(numberValue));
235    return numberValue;
236}
237
238String RangeInputType::serialize(double value) const
239{
240    if (!isfinite(value))
241        return String();
242    return serializeForNumberType(value);
243}
244
245// FIXME: Could share this with BaseButtonInputType and BaseCheckableInputType if we had a common base class.
246void RangeInputType::accessKeyAction(bool sendToAnyElement)
247{
248    InputType::accessKeyAction(sendToAnyElement);
249
250    // Send mouse button events if the caller specified sendToAnyElement.
251    // FIXME: The comment above is no good. It says what we do, but not why.
252    element()->dispatchSimulatedClick(0, sendToAnyElement);
253}
254
255void RangeInputType::minOrMaxAttributeChanged()
256{
257    InputType::minOrMaxAttributeChanged();
258
259    // Sanitize the value.
260    element()->setValue(element()->value());
261    element()->setNeedsStyleRecalc();
262}
263
264void RangeInputType::valueChanged()
265{
266    shadowSliderThumb()->setPositionFromValue();
267}
268
269String RangeInputType::fallbackValue()
270{
271    return serializeForNumberType(StepRange(element()).defaultValue());
272}
273
274String RangeInputType::sanitizeValue(const String& proposedValue)
275{
276    // If the proposedValue is null than this is a reset scenario and we
277    // want the range input's value attribute to take priority over the
278    // calculated default (middle) value.
279    if (proposedValue.isNull())
280        return proposedValue;
281
282    return serializeForNumberType(StepRange(element()).clampValue(proposedValue));
283}
284
285bool RangeInputType::shouldRespectListAttribute()
286{
287    return true;
288}
289
290SliderThumbElement* RangeInputType::shadowSliderThumb() const
291{
292    Node* shadow = element()->shadowRoot();
293    return shadow ? toSliderThumbElement(shadow->firstChild()) : 0;
294}
295
296} // namespace WebCore
297