[WTF] Import std::optional reference implementation as WTF::Optional
[WebKit-https.git] / Source / WebCore / html / RangeInputType.cpp
index c2f73be..dbabfcd 100644 (file)
 #include "RangeInputType.h"
 
 #include "AXObjectCache.h"
-#include "ElementShadow.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 int rangeDefaultMinimum = 0;
 static const int rangeDefaultMaximum = 100;
@@ -60,14 +69,14 @@ static const int rangeDefaultStep = 1;
 static const int rangeDefaultStepBase = 0;
 static const int rangeStepScaleFactor = 1;
 
-static InputNumber ensureMaximum(const InputNumber& proposedValue, const InputNumber& minimum, const InputNumber& fallbackValue)
+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
@@ -82,12 +91,18 @@ const AtomicString& RangeInputType::formControlType() const
 
 double RangeInputType::valueAsDouble() const
 {
-    return parseToDoubleForNumberType(element()->value());
+    return parseToDoubleForNumberType(element().value());
 }
 
-void RangeInputType::setValueAsInputNumber(const InputNumber& newValue, TextFieldEventBehavior eventBehavior, ExceptionCode&) const
+ExceptionOr<void> RangeInputType::setValueAsDecimal(const Decimal& newValue, TextFieldEventBehavior eventBehavior) const
 {
-    element()->setValue(serialize(newValue), eventBehavior);
+    element().setValue(serialize(newValue), eventBehavior);
+    return { };
+}
+
+bool RangeInputType::typeMismatchFor(const String& value) const
+{
+    return !value.isEmpty() && !std::isfinite(parseToDoubleForNumberType(value));
 }
 
 bool RangeInputType::supportsRequired() const
@@ -97,19 +112,19 @@ bool RangeInputType::supportsRequired() const
 
 StepRange RangeInputType::createStepRange(AnyStepHandling anyStepHandling) const
 {
-    DEFINE_STATIC_LOCAL(const StepRange::StepDescription, stepDescription, (rangeDefaultStep, rangeDefaultStepBase, rangeStepScaleFactor));
+    static NeverDestroyed<const StepRange::StepDescription> stepDescription(rangeDefaultStep, rangeDefaultStepBase, rangeStepScaleFactor);
 
-    const InputNumber minimum = parseToNumber(element()->fastGetAttribute(minAttr), rangeDefaultMinimum);
-    const InputNumber maximum = ensureMaximum(parseToNumber(element()->fastGetAttribute(maxAttr), rangeDefaultMaximum), minimum, rangeDefaultMaximum);
+    const Decimal minimum = parseToNumber(element().attributeWithoutSynchronization(minAttr), rangeDefaultMinimum);
+    const Decimal maximum = ensureMaximum(parseToNumber(element().attributeWithoutSynchronization(maxAttr), rangeDefaultMaximum), minimum, rangeDefaultMaximum);
 
-    const AtomicString& precisionValue = element()->fastGetAttribute(precisionAttr);
+    const AtomicString& precisionValue = element().attributeWithoutSynchronization(precisionAttr);
     if (!precisionValue.isNull()) {
-        StepRange::NumberWithDecimalPlacesOrMissing step(1, !equalIgnoringCase(precisionValue, "float"));
-        return StepRange(minimum, minimum, maximum, step, stepDescription);
+        const Decimal step = equalLettersIgnoringASCIICase(precisionValue, "float") ? Decimal::nan() : 1;
+        return StepRange(minimum, RangeLimitations::Valid, minimum, maximum, step, stepDescription);
     }
 
-    StepRange::NumberWithDecimalPlacesOrMissing step = StepRange::parseStep(anyStepHandling, stepDescription, element()->fastGetAttribute(stepAttr));
-    return StepRange(minimum, minimum, maximum, step, stepDescription);
+    const Decimal step = StepRange::parseStep(anyStepHandling, stepDescription, element().attributeWithoutSynchronization(stepAttr));
+    return StepRange(minimum, RangeLimitations::Valid, minimum, maximum, step, stepDescription);
 }
 
 bool RangeInputType::isSteppable() const
@@ -117,48 +132,89 @@ bool RangeInputType::isSteppable() const
     return true;
 }
 
-void RangeInputType::handleMouseDownEvent(MouseEvent* event)
+#if !PLATFORM(IOS)
+void RangeInputType::handleMouseDownEvent(MouseEvent& event)
 {
-    if (element()->disabled() || element()->readOnly())
+    if (element().isDisabledOrReadOnly())
         return;
 
-    Node* targetNode = event->target()->toNode();
-    if (event->button() != LeftButton || !targetNode)
+    Node* targetNode = event.target()->toNode();
+    if (event.button() != LeftButton || !targetNode)
         return;
-    ASSERT(element()->shadow());
-    if (targetNode != element() && !targetNode->isDescendantOf(element()->shadow()->oldestShadowRoot()))
+    ASSERT(element().shadowRoot());
+    if (targetNode != &element() && !targetNode->isDescendantOf(element().userAgentShadowRoot()))
         return;
-    SliderThumbElement* thumb = sliderThumbElementOf(element());
-    if (targetNode == thumb)
+    SliderThumbElement& thumb = typedSliderThumbElement();
+    if (targetNode == &thumb)
         return;
-    thumb->dragFrom(event->absoluteLocation());
+    thumb.dragFrom(event.absoluteLocation());
+}
+#endif
+
+#if ENABLE(TOUCH_EVENTS)
+void RangeInputType::handleTouchEvent(TouchEvent& event)
+{
+#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
 }
 
-void RangeInputType::handleKeydownEvent(KeyboardEvent* event)
+#if ENABLE(TOUCH_SLIDER)
+bool RangeInputType::hasTouchEventHandler() const
 {
-    if (element()->disabled() || element()->readOnly())
+    return true;
+}
+#endif
+
+#if PLATFORM(IOS)
+void RangeInputType::disabledAttributeChanged()
+{
+    typedSliderThumbElement().disabledAttributeChanged();
+}
+#endif
+#endif // ENABLE(TOUCH_EVENTS)
+
+void RangeInputType::handleKeydownEvent(KeyboardEvent& event)
+{
+    if (element().isDisabledOrReadOnly())
         return;
 
-    const String& key = event->keyIdentifier();
+    const String& key = event.keyIdentifier();
 
-    const InputNumber current = parseToNumberOrNaN(element()->value());
-    ASSERT(isfinite(current));
+    const Decimal current = parseToNumberOrNaN(element().value());
+    ASSERT(current.isFinite());
 
     StepRange stepRange(createStepRange(RejectAny));
 
 
     // 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 InputNumber step = equalIgnoringCase(element()->fastGetAttribute(stepAttr), "any") ? (stepRange.maximum() - stepRange.minimum()) / 100 : stepRange.step();
-    const InputNumber bigStep = max((stepRange.maximum() - stepRange.minimum()) / 10, step);
+    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;
     }
 
-    InputNumber newValue;
+    Decimal newValue;
     if (key == "Up")
         newValue = current + step;
     else if (key == "Down")
@@ -181,46 +237,65 @@ void RangeInputType::handleKeydownEvent(KeyboardEvent* event)
     newValue = stepRange.clampValue(newValue);
 
     if (newValue != current) {
-        ExceptionCode ec;
-        TextFieldEventBehavior eventBehavior = DispatchChangeEvent;
-        setValueAsInputNumber(newValue, eventBehavior, 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()
 {
-    ASSERT(element()->shadow());
-
-    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()->shadow()->oldestShadowRoot()->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);
+}
+
+HTMLElement* RangeInputType::sliderTrackElement() const
+{
+    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());
 }
 
-RenderObject* RangeInputType::createRenderer(RenderArena* arena, RenderStyle*) const
+SliderThumbElement& RangeInputType::typedSliderThumbElement() const
 {
-    return new (arena) RenderSlider(element());
+    ASSERT(sliderTrackElement()->firstChild()); // thumb
+    ASSERT(sliderTrackElement()->firstChild()->isHTMLElement());
+
+    return static_cast<SliderThumbElement&>(*sliderTrackElement()->firstChild());
 }
 
-InputNumber RangeInputType::parseToNumber(const String& src, const InputNumber& defaultValue) const
+HTMLElement* RangeInputType::sliderThumbElement() const
 {
-    return convertDoubleToInputNumber(parseToDoubleForNumberType(src, defaultValue));
+    return &typedSliderThumbElement();
 }
 
-String RangeInputType::serialize(const InputNumber& value) const
+RenderPtr<RenderElement> RangeInputType::createInputRenderer(RenderStyle&& style)
 {
-    if (!isfinite(value))
+    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);
 }
@@ -230,9 +305,7 @@ void RangeInputType::accessKeyAction(bool sendMouseEvents)
 {
     InputType::accessKeyAction(sendMouseEvents);
 
-    // Send mouse button events if the caller specified sendMouseEvents.
-    // FIXME: The comment above is no good. It says what we do, but not why.
-    element()->dispatchSimulatedClick(0, sendMouseEvents);
+    element().dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
 }
 
 void RangeInputType::minOrMaxAttributeChanged()
@@ -240,9 +313,10 @@ 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, TextFieldEventBehavior eventBehavior)
@@ -252,7 +326,10 @@ void RangeInputType::setValue(const String& value, bool valueChanged, TextFieldE
     if (!valueChanged)
         return;
 
-    sliderThumbElementOf(element())->setPositionFromValue();
+    if (eventBehavior == DispatchNoEvent)
+        element().setTextAsOfLastFormControlChangeEvent(value);
+
+    typedSliderThumbElement().setPositionFromValue();
 }
 
 String RangeInputType::fallbackValue() const
@@ -263,7 +340,7 @@ String RangeInputType::fallbackValue() const
 String RangeInputType::sanitizeValue(const String& proposedValue) const
 {
     StepRange stepRange(createStepRange(RejectAny));
-    const InputNumber proposedNumericValue = parseToNumber(proposedValue, stepRange.defaultValue());
+    const Decimal proposedNumericValue = parseToNumber(proposedValue, stepRange.defaultValue());
     return serializeForNumberType(stepRange.clampValue(proposedNumericValue));
 }
 
@@ -272,4 +349,78 @@ bool RangeInputType::shouldRespectListAttribute()
     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