[WTF] Import std::optional reference implementation as WTF::Optional
[WebKit-https.git] / Source / WebCore / html / RangeInputType.cpp
index 7c89202..dbabfcd 100644 (file)
 #include "RangeInputType.h"
 
 #include "AXObjectCache.h"
-#include "HTMLDivElement.h"
+#include "EventNames.h"
 #include "HTMLInputElement.h"
-#include "HTMLNames.h"
 #include "HTMLParserIdioms.h"
+#include "InputTypeNames.h"
 #include "KeyboardEvent.h"
 #include "MouseEvent.h"
 #include "PlatformMouseEvent.h"
 #include "RenderSlider.h"
+#include "ScopedEventQueue.h"
 #include "ShadowRoot.h"
 #include "SliderThumbElement.h"
-#include "StepRange.h"
 #include <limits>
 #include <wtf/MathExtras.h>
-#include <wtf/PassOwnPtr.h>
+#include <wtf/NeverDestroyed.h>
+
+#if ENABLE(TOUCH_EVENTS)
+#include "Touch.h"
+#include "TouchEvent.h"
+#include "TouchList.h"
+#endif
+
+#if ENABLE(DATALIST_ELEMENT)
+#include "HTMLDataListElement.h"
+#include "HTMLOptionElement.h"
+#endif
 
 namespace WebCore {
 
 using namespace HTMLNames;
-using namespace std;
 
-static const double rangeDefaultMinimum = 0.0;
-static const double rangeDefaultMaximum = 100.0;
-static const double rangeDefaultStep = 1.0;
-static const double rangeStepScaleFactor = 1.0;
+static const int rangeDefaultMinimum = 0;
+static const int rangeDefaultMaximum = 100;
+static const int rangeDefaultStep = 1;
+static const int rangeDefaultStepBase = 0;
+static const int rangeStepScaleFactor = 1;
+
+static Decimal ensureMaximum(const Decimal& proposedValue, const Decimal& minimum, const Decimal& fallbackValue)
+{
+    return proposedValue >= minimum ? proposedValue : std::max(minimum, fallbackValue);
+}
 
-PassOwnPtr<InputType> RangeInputType::create(HTMLInputElement* element)
+RangeInputType::RangeInputType(HTMLInputElement& element)
+    : InputType(element)
 {
-    return adoptPtr(new RangeInputType(element));
 }
 
 bool RangeInputType::isRangeControl() const
@@ -73,54 +89,42 @@ const AtomicString& RangeInputType::formControlType() const
     return InputTypeNames::range();
 }
 
-double RangeInputType::valueAsNumber() const
+double RangeInputType::valueAsDouble() const
 {
-    return parseToDouble(element()->value(), numeric_limits<double>::quiet_NaN());
+    return parseToDoubleForNumberType(element().value());
 }
 
-void RangeInputType::setValueAsNumber(double newValue, bool sendChangeEvent, ExceptionCode&) const
+ExceptionOr<void> RangeInputType::setValueAsDecimal(const Decimal& newValue, TextFieldEventBehavior eventBehavior) const
 {
-    element()->setValue(serialize(newValue), sendChangeEvent);
+    element().setValue(serialize(newValue), eventBehavior);
+    return { };
 }
 
-bool RangeInputType::supportsRequired() const
+bool RangeInputType::typeMismatchFor(const String& value) const
 {
-    return false;
+    return !value.isEmpty() && !std::isfinite(parseToDoubleForNumberType(value));
 }
 
-bool RangeInputType::rangeUnderflow(const String& value) const
+bool RangeInputType::supportsRequired() const
 {
-    // Guaranteed by sanitization.
-    ASSERT_UNUSED(value, parseToDouble(value, numeric_limits<double>::quiet_NaN()) >= minimum());
     return false;
 }
 
-bool RangeInputType::rangeOverflow(const String& value) const
+StepRange RangeInputType::createStepRange(AnyStepHandling anyStepHandling) const
 {
-    // Guaranteed by sanitization.
-    ASSERT_UNUSED(value, parseToDouble(value, numeric_limits<double>::quiet_NaN()) <= maximum());
-    return false;
-}
+    static NeverDestroyed<const StepRange::StepDescription> stepDescription(rangeDefaultStep, rangeDefaultStepBase, rangeStepScaleFactor);
 
-bool RangeInputType::supportsRangeLimitation() const
-{
-    return true;
-}
+    const Decimal minimum = parseToNumber(element().attributeWithoutSynchronization(minAttr), rangeDefaultMinimum);
+    const Decimal maximum = ensureMaximum(parseToNumber(element().attributeWithoutSynchronization(maxAttr), rangeDefaultMaximum), minimum, rangeDefaultMaximum);
 
-double RangeInputType::minimum() const
-{
-    return parseToDouble(element()->fastGetAttribute(minAttr), rangeDefaultMinimum);
-}
+    const AtomicString& precisionValue = element().attributeWithoutSynchronization(precisionAttr);
+    if (!precisionValue.isNull()) {
+        const Decimal step = equalLettersIgnoringASCIICase(precisionValue, "float") ? Decimal::nan() : 1;
+        return StepRange(minimum, RangeLimitations::Valid, minimum, maximum, step, stepDescription);
+    }
 
-double RangeInputType::maximum() const
-{
-    double max = parseToDouble(element()->fastGetAttribute(maxAttr), rangeDefaultMaximum);
-    // A remedy for the inconsistent min/max values.
-    // Sets the maximum to the default or the minimum value.
-    double min = minimum();
-    if (max < min)
-        max = std::max(min, rangeDefaultMaximum);
-    return max;
+    const Decimal step = StepRange::parseStep(anyStepHandling, stepDescription, element().attributeWithoutSynchronization(stepAttr));
+    return StepRange(minimum, RangeLimitations::Valid, minimum, maximum, step, stepDescription);
 }
 
 bool RangeInputType::isSteppable() const
@@ -128,75 +132,89 @@ bool RangeInputType::isSteppable() const
     return true;
 }
 
-bool RangeInputType::stepMismatch(const String&, double) const
+#if !PLATFORM(IOS)
+void RangeInputType::handleMouseDownEvent(MouseEvent& event)
 {
-    // stepMismatch doesn't occur for type=range. RenderSlider guarantees the
-    // value matches to step on user input, and sanitization takes care
-    // of the general case.
-    return false;
-}
+    if (element().isDisabledOrReadOnly())
+        return;
 
-double RangeInputType::stepBase() const
-{
-    return minimum();
+    Node* targetNode = event.target()->toNode();
+    if (event.button() != LeftButton || !targetNode)
+        return;
+    ASSERT(element().shadowRoot());
+    if (targetNode != &element() && !targetNode->isDescendantOf(element().userAgentShadowRoot()))
+        return;
+    SliderThumbElement& thumb = typedSliderThumbElement();
+    if (targetNode == &thumb)
+        return;
+    thumb.dragFrom(event.absoluteLocation());
 }
+#endif
 
-double RangeInputType::defaultStep() const
+#if ENABLE(TOUCH_EVENTS)
+void RangeInputType::handleTouchEvent(TouchEvent& event)
 {
-    return rangeDefaultStep;
+#if PLATFORM(IOS)
+    typedSliderThumbElement().handleTouchEvent(event);
+#elif ENABLE(TOUCH_SLIDER)
+    if (element().isDisabledOrReadOnly())
+        return;
+
+    if (event.type() == eventNames().touchendEvent) {
+        event.setDefaultHandled();
+        return;
+    }
+
+    TouchList* touches = event.targetTouches();
+    if (touches->length() == 1) {
+        typedSliderThumbElement().setPositionFromPoint(touches->item(0)->absoluteLocation());
+        event.setDefaultHandled();
+    }
+#else
+    UNUSED_PARAM(event);
+#endif
 }
 
-double RangeInputType::stepScaleFactor() const
+#if ENABLE(TOUCH_SLIDER)
+bool RangeInputType::hasTouchEventHandler() const
 {
-    return rangeStepScaleFactor;
+    return true;
 }
+#endif
 
-void RangeInputType::handleMouseDownEvent(MouseEvent* event)
+#if PLATFORM(IOS)
+void RangeInputType::disabledAttributeChanged()
 {
-    if (element()->disabled() || element()->readOnly())
-        return;
-
-    Node* targetNode = event->target()->toNode();
-    if (event->button() != LeftButton || !targetNode || (targetNode != element() && !targetNode->isDescendantOf(element()->shadowRoot())))
-        return;
-    SliderThumbElement* thumb = sliderThumbElementOf(element());
-    if (targetNode == thumb)
-        return;
-    thumb->dragFrom(event->absoluteLocation());
+    typedSliderThumbElement().disabledAttributeChanged();
 }
+#endif
+#endif // ENABLE(TOUCH_EVENTS)
 
-void RangeInputType::handleKeydownEvent(KeyboardEvent* event)
+void RangeInputType::handleKeydownEvent(KeyboardEvent& event)
 {
-    if (element()->disabled() || element()->readOnly())
+    if (element().isDisabledOrReadOnly())
         return;
 
-    const String& key = event->keyIdentifier();
+    const String& key = event.keyIdentifier();
 
-    double current = parseToDouble(element()->value(), numeric_limits<double>::quiet_NaN());
-    ASSERT(isfinite(current));
+    const Decimal current = parseToNumberOrNaN(element().value());
+    ASSERT(current.isFinite());
 
-    double step, bigStep;
-    if (equalIgnoringCase(element()->fastGetAttribute(stepAttr), "any")) {
-        // FIXME: We can't use stepUp() for the step value "any". So, we increase
-        // or decrease the value by 1/100 of the value range. Is it reasonable?
-        step = (maximum() - minimum()) / 100;
-        bigStep = step * 10;
-    } else {
-        if (!element()->getAllowedValueStep(&step))
-            ASSERT_NOT_REACHED();
+    StepRange stepRange(createStepRange(RejectAny));
 
-        bigStep = (maximum() - minimum()) / 10;
-        if (bigStep < step)
-            bigStep = step;
-    }
+
+    // FIXME: We can't use stepUp() for the step value "any". So, we increase
+    // or decrease the value by 1/100 of the value range. Is it reasonable?
+    const Decimal step = equalLettersIgnoringASCIICase(element().attributeWithoutSynchronization(stepAttr), "any") ? (stepRange.maximum() - stepRange.minimum()) / 100 : stepRange.step();
+    const Decimal bigStep = std::max((stepRange.maximum() - stepRange.minimum()) / 10, step);
 
     bool isVertical = false;
-    if (element()->renderer()) {
-        ControlPart part = element()->renderer()->style()->appearance();
+    if (element().renderer()) {
+        ControlPart part = element().renderer()->style().appearance();
         isVertical = part == SliderVerticalPart || part == MediaVolumeSliderPart;
     }
 
-    double newValue;
+    Decimal newValue;
     if (key == "Up")
         newValue = current + step;
     else if (key == "Down")
@@ -210,69 +228,84 @@ void RangeInputType::handleKeydownEvent(KeyboardEvent* event)
     else if (key == "PageDown")
         newValue = current - bigStep;
     else if (key == "Home")
-        newValue = isVertical ? maximum() : minimum();
+        newValue = isVertical ? stepRange.maximum() : stepRange.minimum();
     else if (key == "End")
-        newValue = isVertical ? minimum() : maximum();
+        newValue = isVertical ? stepRange.minimum() : stepRange.maximum();
     else
         return; // Did not match any key binding.
 
-    newValue = StepRange(element()).clampValue(newValue);
+    newValue = stepRange.clampValue(newValue);
 
     if (newValue != current) {
-        ExceptionCode ec;
-        bool sendChangeEvent = true;
-        setValueAsNumber(newValue, sendChangeEvent, ec);
+        EventQueueScope scope;
+        setValueAsDecimal(newValue, DispatchInputAndChangeEvent);
 
-        if (AXObjectCache::accessibilityEnabled())
-            element()->document()->axObjectCache()->postNotification(element()->renderer(), AXObjectCache::AXValueChanged, true);
-        element()->dispatchFormControlChangeEvent();
+        if (AXObjectCache* cache = element().document().existingAXObjectCache())
+            cache->postNotification(&element(), AXObjectCache::AXValueChanged);
     }
 
-    event->setDefaultHandled();
+    event.setDefaultHandled();
 }
 
 void RangeInputType::createShadowSubtree()
 {
-    Document* document = element()->document();
-    RefPtr<HTMLDivElement> track = HTMLDivElement::create(document);
-    track->setShadowPseudoId("-webkit-slider-runnable-track");
-    ExceptionCode ec = 0;
-    track->appendChild(SliderThumbElement::create(document), ec);
-    RefPtr<HTMLElement> container = SliderContainerElement::create(document);
-    container->appendChild(track.release(), ec);
-    container->appendChild(TrackLimiterElement::create(document), ec);
-    element()->ensureShadowRoot()->appendChild(container.release(), ec);
+    ASSERT(element().userAgentShadowRoot());
+
+    Document& document = element().document();
+    auto track = HTMLDivElement::create(document);
+    track->setPseudo(AtomicString("-webkit-slider-runnable-track", AtomicString::ConstructFromLiteral));
+    track->appendChild(SliderThumbElement::create(document));
+    auto container = SliderContainerElement::create(document);
+    container->appendChild(track);
+    element().userAgentShadowRoot()->appendChild(container);
 }
 
-RenderObject* RangeInputType::createRenderer(RenderArena* arena, RenderStyle*) const
+HTMLElement* RangeInputType::sliderTrackElement() const
 {
-    return new (arena) RenderSlider(element());
+    ASSERT(element().userAgentShadowRoot());
+    ASSERT(element().userAgentShadowRoot()->firstChild()); // container
+    ASSERT(element().userAgentShadowRoot()->firstChild()->isHTMLElement());
+    ASSERT(element().userAgentShadowRoot()->firstChild()->firstChild()); // track
+
+    return downcast<HTMLElement>(element().userAgentShadowRoot()->firstChild()->firstChild());
 }
 
-double RangeInputType::parseToDouble(const String& src, double defaultValue) const
+SliderThumbElement& RangeInputType::typedSliderThumbElement() const
 {
-    double numberValue;
-    if (!parseToDoubleForNumberType(src, &numberValue))
-        return defaultValue;
-    ASSERT(isfinite(numberValue));
-    return numberValue;
+    ASSERT(sliderTrackElement()->firstChild()); // thumb
+    ASSERT(sliderTrackElement()->firstChild()->isHTMLElement());
+
+    return static_cast<SliderThumbElement&>(*sliderTrackElement()->firstChild());
 }
 
-String RangeInputType::serialize(double value) const
+HTMLElement* RangeInputType::sliderThumbElement() const
 {
-    if (!isfinite(value))
+    return &typedSliderThumbElement();
+}
+
+RenderPtr<RenderElement> RangeInputType::createInputRenderer(RenderStyle&& style)
+{
+    return createRenderer<RenderSlider>(element(), WTFMove(style));
+}
+
+Decimal RangeInputType::parseToNumber(const String& src, const Decimal& defaultValue) const
+{
+    return parseToDecimalForNumberType(src, defaultValue);
+}
+
+String RangeInputType::serialize(const Decimal& value) const
+{
+    if (!value.isFinite())
         return String();
     return serializeForNumberType(value);
 }
 
-// FIXME: Could share this with BaseButtonInputType and BaseCheckableInputType if we had a common base class.
-void RangeInputType::accessKeyAction(bool sendToAnyElement)
+// FIXME: Could share this with BaseClickableWithKeyInputType and BaseCheckableInputType if we had a common base class.
+void RangeInputType::accessKeyAction(bool sendMouseEvents)
 {
-    InputType::accessKeyAction(sendToAnyElement);
+    InputType::accessKeyAction(sendMouseEvents);
 
-    // Send mouse button events if the caller specified sendToAnyElement.
-    // FIXME: The comment above is no good. It says what we do, but not why.
-    element()->dispatchSimulatedClick(0, sendToAnyElement);
+    element().dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
 }
 
 void RangeInputType::minOrMaxAttributeChanged()
@@ -280,40 +313,114 @@ void RangeInputType::minOrMaxAttributeChanged()
     InputType::minOrMaxAttributeChanged();
 
     // Sanitize the value.
-    if (element()->hasDirtyValue())
-        element()->setValue(element()->value());
-    element()->setNeedsStyleRecalc();
+    if (element().hasDirtyValue())
+        element().setValue(element().value());
+
+    typedSliderThumbElement().setPositionFromValue();
 }
 
-void RangeInputType::setValue(const String& value, bool valueChanged, bool sendChangeEvent)
+void RangeInputType::setValue(const String& value, bool valueChanged, TextFieldEventBehavior eventBehavior)
 {
-    InputType::setValue(value, valueChanged, sendChangeEvent);
+    InputType::setValue(value, valueChanged, eventBehavior);
 
     if (!valueChanged)
         return;
 
-    sliderThumbElementOf(element())->setPositionFromValue();
+    if (eventBehavior == DispatchNoEvent)
+        element().setTextAsOfLastFormControlChangeEvent(value);
+
+    typedSliderThumbElement().setPositionFromValue();
 }
 
-String RangeInputType::fallbackValue()
+String RangeInputType::fallbackValue() const
 {
-    return serializeForNumberType(StepRange(element()).defaultValue());
+    return serializeForNumberType(createStepRange(RejectAny).defaultValue());
 }
 
-String RangeInputType::sanitizeValue(const String& proposedValue)
+String RangeInputType::sanitizeValue(const String& proposedValue) const
 {
-    // If the proposedValue is null than this is a reset scenario and we
-    // want the range input's value attribute to take priority over the
-    // calculated default (middle) value.
-    if (proposedValue.isNull())
-        return proposedValue;
-
-    return serializeForNumberType(StepRange(element()).clampValue(proposedValue));
+    StepRange stepRange(createStepRange(RejectAny));
+    const Decimal proposedNumericValue = parseToNumber(proposedValue, stepRange.defaultValue());
+    return serializeForNumberType(stepRange.clampValue(proposedNumericValue));
 }
 
 bool RangeInputType::shouldRespectListAttribute()
 {
-    return true;
+    return InputType::themeSupportsDataListUI(this);
+}
+
+#if ENABLE(DATALIST_ELEMENT)
+void RangeInputType::listAttributeTargetChanged()
+{
+    m_tickMarkValuesDirty = true;
+    HTMLElement* sliderTrackElement = this->sliderTrackElement();
+    if (sliderTrackElement->renderer())
+        sliderTrackElement->renderer()->setNeedsLayout();
+}
+
+void RangeInputType::updateTickMarkValues()
+{
+    if (!m_tickMarkValuesDirty)
+        return;
+    m_tickMarkValues.clear();
+    m_tickMarkValuesDirty = false;
+    HTMLDataListElement* dataList = element().dataList();
+    if (!dataList)
+        return;
+    Ref<HTMLCollection> options = dataList->options();
+    m_tickMarkValues.reserveCapacity(options->length());
+    for (unsigned i = 0; i < options->length(); ++i) {
+        Node* node = options->item(i);
+        HTMLOptionElement& optionElement = downcast<HTMLOptionElement>(*node);
+        String optionValue = optionElement.value();
+        if (!element().isValidValue(optionValue))
+            continue;
+        m_tickMarkValues.append(parseToNumber(optionValue, Decimal::nan()));
+    }
+    m_tickMarkValues.shrinkToFit();
+    std::sort(m_tickMarkValues.begin(), m_tickMarkValues.end());
+}
+
+std::optional<Decimal> RangeInputType::findClosestTickMarkValue(const Decimal& value)
+{
+    updateTickMarkValues();
+    if (!m_tickMarkValues.size())
+        return std::nullopt;
+
+    size_t left = 0;
+    size_t right = m_tickMarkValues.size();
+    size_t middle;
+    while (true) {
+        ASSERT(left <= right);
+        middle = left + (right - left) / 2;
+        if (!middle)
+            break;
+        if (middle == m_tickMarkValues.size() - 1 && m_tickMarkValues[middle] < value) {
+            middle++;
+            break;
+        }
+        if (m_tickMarkValues[middle - 1] <= value && m_tickMarkValues[middle] >= value)
+            break;
+
+        if (m_tickMarkValues[middle] < value)
+            left = middle;
+        else
+            right = middle;
+    }
+
+    std::optional<Decimal> closestLeft = middle ? std::make_optional(m_tickMarkValues[middle - 1]) : std::nullopt;
+    std::optional<Decimal> closestRight = middle != m_tickMarkValues.size() ? std::make_optional(m_tickMarkValues[middle]) : std::nullopt;
+
+    if (!closestLeft)
+        return closestRight;
+    if (!closestRight)
+        return closestLeft;
+
+    if (*closestRight - value < value - *closestLeft)
+        return closestRight;
+
+    return closestLeft;
 }
+#endif
 
 } // namespace WebCore