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