Rename AtomicString to AtomString
[WebKit-https.git] / Source / WebCore / html / RangeInputType.cpp
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
63 namespace WebCore {
64
65 using namespace HTMLNames;
66
67 static const int rangeDefaultMinimum = 0;
68 static const int rangeDefaultMaximum = 100;
69 static const int rangeDefaultStep = 1;
70 static const int rangeDefaultStepBase = 0;
71 static const int rangeStepScaleFactor = 1;
72 static const StepRange::StepDescription rangeStepDescription { rangeDefaultStep, rangeDefaultStepBase, rangeStepScaleFactor };
73
74 static Decimal ensureMaximum(const Decimal& proposedValue, const Decimal& minimum, const Decimal& fallbackValue)
75 {
76     return proposedValue >= minimum ? proposedValue : std::max(minimum, fallbackValue);
77 }
78
79 RangeInputType::RangeInputType(HTMLInputElement& element)
80     : InputType(element)
81 {
82 }
83
84 bool RangeInputType::isRangeControl() const
85 {
86     return true;
87 }
88
89 const AtomString& RangeInputType::formControlType() const
90 {
91     return InputTypeNames::range();
92 }
93
94 double RangeInputType::valueAsDouble() const
95 {
96     ASSERT(element());
97     return parseToDoubleForNumberType(element()->value());
98 }
99
100 ExceptionOr<void> RangeInputType::setValueAsDecimal(const Decimal& newValue, TextFieldEventBehavior eventBehavior) const
101 {
102     ASSERT(element());
103     element()->setValue(serialize(newValue), eventBehavior);
104     return { };
105 }
106
107 bool RangeInputType::typeMismatchFor(const String& value) const
108 {
109     return !value.isEmpty() && !std::isfinite(parseToDoubleForNumberType(value));
110 }
111
112 bool RangeInputType::supportsRequired() const
113 {
114     return false;
115 }
116
117 StepRange 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 AtomString& 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
133 bool RangeInputType::isSteppable() const
134 {
135     return true;
136 }
137
138 #if !PLATFORM(IOS_FAMILY)
139
140 void 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)
161 void 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)
186 bool RangeInputType::hasTouchEventHandler() const
187 {
188     return true;
189 }
190 #endif
191 #endif // ENABLE(TOUCH_EVENTS)
192
193 void RangeInputType::disabledStateChanged()
194 {
195     typedSliderThumbElement().hostDisabledStateChanged();
196 }
197
198 auto 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
256 void 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(AtomString("-webkit-slider-runnable-track", AtomString::ConstructFromLiteral));
264     track->appendChild(SliderThumbElement::create(document));
265     auto container = SliderContainerElement::create(document);
266     container->appendChild(track);
267     element()->userAgentShadowRoot()->appendChild(container);
268 }
269
270 HTMLElement* 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
289 SliderThumbElement& RangeInputType::typedSliderThumbElement() const
290 {
291     ASSERT(sliderTrackElement()->firstChild()); // thumb
292     ASSERT(sliderTrackElement()->firstChild()->isHTMLElement());
293
294     return static_cast<SliderThumbElement&>(*sliderTrackElement()->firstChild());
295 }
296
297 HTMLElement* RangeInputType::sliderThumbElement() const
298 {
299     return &typedSliderThumbElement();
300 }
301
302 RenderPtr<RenderElement> RangeInputType::createInputRenderer(RenderStyle&& style)
303 {
304     ASSERT(element());
305     return createRenderer<RenderSlider>(*element(), WTFMove(style));
306 }
307
308 Decimal RangeInputType::parseToNumber(const String& src, const Decimal& defaultValue) const
309 {
310     return parseToDecimalForNumberType(src, defaultValue);
311 }
312
313 String 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.
321 void 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
329 void 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
343 void 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
358 String RangeInputType::fallbackValue() const
359 {
360     return serializeForNumberType(createStepRange(RejectAny).defaultValue());
361 }
362
363 String 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
370 bool 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)
380 void RangeInputType::listAttributeTargetChanged()
381 {
382     m_tickMarkValuesDirty = true;
383     RefPtr<HTMLElement> sliderTrackElement = this->sliderTrackElement();
384     if (sliderTrackElement->renderer())
385         sliderTrackElement->renderer()->setNeedsLayout();
386 }
387
388 void 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
412 Optional<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