1/*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 * Copyright (C) 2011-2018 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 "ElementChildIterator.h"
37#include "EventNames.h"
38#include "HTMLInputElement.h"
39#include "HTMLParserIdioms.h"
40#include "InputTypeNames.h"
41#include "KeyboardEvent.h"
42#include "MouseEvent.h"
43#include "PlatformMouseEvent.h"
44#include "RenderSlider.h"
45#include "RuntimeEnabledFeatures.h"
46#include "ScopedEventQueue.h"
47#include "ShadowRoot.h"
48#include "SliderThumbElement.h"
49#include <limits>
50#include <wtf/MathExtras.h>
51
52#if ENABLE(TOUCH_EVENTS)
53#include "Touch.h"
54#include "TouchEvent.h"
55#include "TouchList.h"
56#endif
57
58#if ENABLE(DATALIST_ELEMENT)
59#include "HTMLDataListElement.h"
60#include "HTMLOptionElement.h"
61#endif
62
63namespace WebCore {
64
65using namespace HTMLNames;
66
67static const int rangeDefaultMinimum = 0;
68static const int rangeDefaultMaximum = 100;
69static const int rangeDefaultStep = 1;
70static const int rangeDefaultStepBase = 0;
71static const int rangeStepScaleFactor = 1;
72static const StepRange::StepDescription rangeStepDescription { rangeDefaultStep, rangeDefaultStepBase, rangeStepScaleFactor };
73
74static Decimal ensureMaximum(const Decimal& proposedValue, const Decimal& minimum, const Decimal& fallbackValue)
75{
76 return proposedValue >= minimum ? proposedValue : std::max(minimum, fallbackValue);
77}
78
79RangeInputType::RangeInputType(HTMLInputElement& element)
80 : InputType(element)
81{
82}
83
84bool RangeInputType::isRangeControl() const
85{
86 return true;
87}
88
89const AtomicString& RangeInputType::formControlType() const
90{
91 return InputTypeNames::range();
92}
93
94double RangeInputType::valueAsDouble() const
95{
96 ASSERT(element());
97 return parseToDoubleForNumberType(element()->value());
98}
99
100ExceptionOr<void> RangeInputType::setValueAsDecimal(const Decimal& newValue, TextFieldEventBehavior eventBehavior) const
101{
102 ASSERT(element());
103 element()->setValue(serialize(newValue), eventBehavior);
104 return { };
105}
106
107bool RangeInputType::typeMismatchFor(const String& value) const
108{
109 return !value.isEmpty() && !std::isfinite(parseToDoubleForNumberType(value));
110}
111
112bool RangeInputType::supportsRequired() const
113{
114 return false;
115}
116
117StepRange RangeInputType::createStepRange(AnyStepHandling anyStepHandling) const
118{
119 ASSERT(element());
120 const Decimal minimum = parseToNumber(element()->attributeWithoutSynchronization(minAttr), rangeDefaultMinimum);
121 const Decimal maximum = ensureMaximum(parseToNumber(element()->attributeWithoutSynchronization(maxAttr), rangeDefaultMaximum), minimum, rangeDefaultMaximum);
122
123 const AtomicString& precisionValue = element()->attributeWithoutSynchronization(precisionAttr);
124 if (!precisionValue.isNull()) {
125 const Decimal step = equalLettersIgnoringASCIICase(precisionValue, "float") ? Decimal::nan() : 1;
126 return StepRange(minimum, RangeLimitations::Valid, minimum, maximum, step, rangeStepDescription);
127 }
128
129 const Decimal step = StepRange::parseStep(anyStepHandling, rangeStepDescription, element()->attributeWithoutSynchronization(stepAttr));
130 return StepRange(minimum, RangeLimitations::Valid, minimum, maximum, step, rangeStepDescription);
131}
132
133bool RangeInputType::isSteppable() const
134{
135 return true;
136}
137
138#if !PLATFORM(IOS_FAMILY)
139
140void RangeInputType::handleMouseDownEvent(MouseEvent& event)
141{
142 ASSERT(element());
143 if (element()->isDisabledFormControl())
144 return;
145
146 if (event.button() != LeftButton || !is<Node>(event.target()))
147 return;
148 ASSERT(element()->shadowRoot());
149 auto& targetNode = downcast<Node>(*event.target());
150 if (&targetNode != element() && !targetNode.isDescendantOf(element()->userAgentShadowRoot().get()))
151 return;
152 auto& thumb = typedSliderThumbElement();
153 if (&targetNode == &thumb)
154 return;
155 thumb.dragFrom(event.absoluteLocation());
156}
157
158#endif
159
160#if ENABLE(TOUCH_EVENTS)
161void RangeInputType::handleTouchEvent(TouchEvent& event)
162{
163#if PLATFORM(IOS_FAMILY)
164 typedSliderThumbElement().handleTouchEvent(event);
165#elif ENABLE(TOUCH_SLIDER)
166 ASSERT(element());
167 if (element()->isDisabledFormControl())
168 return;
169
170 if (event.type() == eventNames().touchendEvent) {
171 event.setDefaultHandled();
172 return;
173 }
174
175 RefPtr<TouchList> touches = event.targetTouches();
176 if (touches->length() == 1) {
177 typedSliderThumbElement().setPositionFromPoint(touches->item(0)->absoluteLocation());
178 event.setDefaultHandled();
179 }
180#else
181 UNUSED_PARAM(event);
182#endif
183}
184
185#if ENABLE(TOUCH_SLIDER)
186bool RangeInputType::hasTouchEventHandler() const
187{
188 return true;
189}
190#endif
191#endif // ENABLE(TOUCH_EVENTS)
192
193void RangeInputType::disabledStateChanged()
194{
195 typedSliderThumbElement().hostDisabledStateChanged();
196}
197
198auto RangeInputType::handleKeydownEvent(KeyboardEvent& event) -> ShouldCallBaseEventHandler
199{
200 ASSERT(element());
201 if (element()->isDisabledFormControl())
202 return ShouldCallBaseEventHandler::Yes;
203
204 const String& key = event.keyIdentifier();
205
206 const Decimal current = parseToNumberOrNaN(element()->value());
207 ASSERT(current.isFinite());
208
209 StepRange stepRange(createStepRange(RejectAny));
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 = equalLettersIgnoringASCIICase(element()->attributeWithoutSynchronization(stepAttr), "any") ? (stepRange.maximum() - stepRange.minimum()) / 100 : stepRange.step();
214 const Decimal bigStep = std::max((stepRange.maximum() - stepRange.minimum()) / 10, step);
215
216 bool isVertical = false;
217 if (auto* renderer = element()->renderer()) {
218 ControlPart part = 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 ShouldCallBaseEventHandler::Yes; // Did not match any key binding.
241
242 newValue = stepRange.clampValue(newValue);
243
244 if (newValue != current) {
245 EventQueueScope scope;
246 setValueAsDecimal(newValue, DispatchInputAndChangeEvent);
247
248 if (AXObjectCache* cache = element()->document().existingAXObjectCache())
249 cache->postNotification(element(), AXObjectCache::AXValueChanged);
250 }
251
252 event.setDefaultHandled();
253 return ShouldCallBaseEventHandler::Yes;
254}
255
256void RangeInputType::createShadowSubtree()
257{
258 ASSERT(element());
259 ASSERT(element()->userAgentShadowRoot());
260
261 Document& document = element()->document();
262 auto track = HTMLDivElement::create(document);
263 track->setPseudo(AtomicString("-webkit-slider-runnable-track", AtomicString::ConstructFromLiteral));
264 track->appendChild(SliderThumbElement::create(document));
265 auto container = SliderContainerElement::create(document);
266 container->appendChild(track);
267 element()->userAgentShadowRoot()->appendChild(container);
268}
269
270HTMLElement* RangeInputType::sliderTrackElement() const
271{
272 ASSERT(element());
273 ASSERT(element()->userAgentShadowRoot());
274 ASSERT(element()->userAgentShadowRoot()->firstChild()); // container
275 ASSERT(element()->userAgentShadowRoot()->firstChild()->isHTMLElement());
276 ASSERT(element()->userAgentShadowRoot()->firstChild()->firstChild()); // track
277
278 RefPtr<ShadowRoot> root = element()->userAgentShadowRoot();
279 if (!root)
280 return nullptr;
281
282 auto* container = childrenOfType<SliderContainerElement>(*root).first();
283 if (!container)
284 return nullptr;
285
286 return childrenOfType<HTMLElement>(*container).first();
287}
288
289SliderThumbElement& RangeInputType::typedSliderThumbElement() const
290{
291 ASSERT(sliderTrackElement()->firstChild()); // thumb
292 ASSERT(sliderTrackElement()->firstChild()->isHTMLElement());
293
294 return static_cast<SliderThumbElement&>(*sliderTrackElement()->firstChild());
295}
296
297HTMLElement* RangeInputType::sliderThumbElement() const
298{
299 return &typedSliderThumbElement();
300}
301
302RenderPtr<RenderElement> RangeInputType::createInputRenderer(RenderStyle&& style)
303{
304 ASSERT(element());
305 return createRenderer<RenderSlider>(*element(), WTFMove(style));
306}
307
308Decimal RangeInputType::parseToNumber(const String& src, const Decimal& defaultValue) const
309{
310 return parseToDecimalForNumberType(src, defaultValue);
311}
312
313String RangeInputType::serialize(const Decimal& value) const
314{
315 if (!value.isFinite())
316 return String();
317 return serializeForNumberType(value);
318}
319
320// FIXME: Could share this with BaseClickableWithKeyInputType and BaseCheckableInputType if we had a common base class.
321void RangeInputType::accessKeyAction(bool sendMouseEvents)
322{
323 InputType::accessKeyAction(sendMouseEvents);
324
325 if (auto* element = this->element())
326 element->dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
327}
328
329void RangeInputType::attributeChanged(const QualifiedName& name)
330{
331 // FIXME: Don't we need to do this work for precisionAttr too?
332 if (name == maxAttr || name == minAttr) {
333 // Sanitize the value.
334 if (auto* element = this->element()) {
335 if (element->hasDirtyValue())
336 element->setValue(element->value());
337 }
338 typedSliderThumbElement().setPositionFromValue();
339 }
340 InputType::attributeChanged(name);
341}
342
343void RangeInputType::setValue(const String& value, bool valueChanged, TextFieldEventBehavior eventBehavior)
344{
345 InputType::setValue(value, valueChanged, eventBehavior);
346
347 if (!valueChanged)
348 return;
349
350 if (eventBehavior == DispatchNoEvent) {
351 ASSERT(element());
352 element()->setTextAsOfLastFormControlChangeEvent(value);
353 }
354
355 typedSliderThumbElement().setPositionFromValue();
356}
357
358String RangeInputType::fallbackValue() const
359{
360 return serializeForNumberType(createStepRange(RejectAny).defaultValue());
361}
362
363String RangeInputType::sanitizeValue(const String& proposedValue) const
364{
365 StepRange stepRange(createStepRange(RejectAny));
366 const Decimal proposedNumericValue = parseToNumber(proposedValue, stepRange.defaultValue());
367 return serializeForNumberType(stepRange.clampValue(proposedNumericValue));
368}
369
370bool RangeInputType::shouldRespectListAttribute()
371{
372#if ENABLE(DATALIST_ELEMENT)
373 return RuntimeEnabledFeatures::sharedFeatures().dataListElementEnabled();
374#else
375 return InputType::themeSupportsDataListUI(this);
376#endif
377}
378
379#if ENABLE(DATALIST_ELEMENT)
380void RangeInputType::listAttributeTargetChanged()
381{
382 m_tickMarkValuesDirty = true;
383 RefPtr<HTMLElement> sliderTrackElement = this->sliderTrackElement();
384 if (sliderTrackElement->renderer())
385 sliderTrackElement->renderer()->setNeedsLayout();
386}
387
388void RangeInputType::updateTickMarkValues()
389{
390 if (!m_tickMarkValuesDirty)
391 return;
392 m_tickMarkValues.clear();
393 m_tickMarkValuesDirty = false;
394 ASSERT(element());
395 auto dataList = element()->dataList();
396 if (!dataList)
397 return;
398 Ref<HTMLCollection> options = dataList->options();
399 m_tickMarkValues.reserveCapacity(options->length());
400 for (unsigned i = 0; i < options->length(); ++i) {
401 RefPtr<Node> node = options->item(i);
402 HTMLOptionElement& optionElement = downcast<HTMLOptionElement>(*node);
403 String optionValue = optionElement.value();
404 if (!element()->isValidValue(optionValue))
405 continue;
406 m_tickMarkValues.append(parseToNumber(optionValue, Decimal::nan()));
407 }
408 m_tickMarkValues.shrinkToFit();
409 std::sort(m_tickMarkValues.begin(), m_tickMarkValues.end());
410}
411
412Optional<Decimal> RangeInputType::findClosestTickMarkValue(const Decimal& value)
413{
414 updateTickMarkValues();
415 if (!m_tickMarkValues.size())
416 return WTF::nullopt;
417
418 size_t left = 0;
419 size_t right = m_tickMarkValues.size();
420 size_t middle;
421 while (true) {
422 ASSERT(left <= right);
423 middle = left + (right - left) / 2;
424 if (!middle)
425 break;
426 if (middle == m_tickMarkValues.size() - 1 && m_tickMarkValues[middle] < value) {
427 middle++;
428 break;
429 }
430 if (m_tickMarkValues[middle - 1] <= value && m_tickMarkValues[middle] >= value)
431 break;
432
433 if (m_tickMarkValues[middle] < value)
434 left = middle;
435 else
436 right = middle;
437 }
438
439 Optional<Decimal> closestLeft = middle ? makeOptional(m_tickMarkValues[middle - 1]) : WTF::nullopt;
440 Optional<Decimal> closestRight = middle != m_tickMarkValues.size() ? makeOptional(m_tickMarkValues[middle]) : WTF::nullopt;
441
442 if (!closestLeft)
443 return closestRight;
444 if (!closestRight)
445 return closestLeft;
446
447 if (*closestRight - value < value - *closestLeft)
448 return closestRight;
449
450 return closestLeft;
451}
452#endif
453
454} // namespace WebCore
455