[macOS] Color wells should appear pressed when presenting a color picker
[WebKit-https.git] / Source / WebCore / html / ColorInputType.cpp
index 5cd2a5e..1415782 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2015-2018 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
  */
 
 #include "config.h"
+
+#if ENABLE(INPUT_TYPE_COLOR)
+
 #include "ColorInputType.h"
 
+#include "CSSPropertyNames.h"
 #include "Chrome.h"
 #include "Color.h"
+#include "ElementChildIterator.h"
+#include "Event.h"
+#include "HTMLDataListElement.h"
 #include "HTMLDivElement.h"
 #include "HTMLInputElement.h"
-#include "MouseEvent.h"
-#include "ScriptController.h"
+#include "HTMLOptionElement.h"
+#include "InputTypeNames.h"
+#include "RenderView.h"
+#include "ScopedEventQueue.h"
 #include "ShadowRoot.h"
-#include <wtf/PassOwnPtr.h>
-#include <wtf/text/WTFString.h>
-
-#if ENABLE(INPUT_COLOR)
+#include "UserGestureIndicator.h"
 
 namespace WebCore {
 
-static bool isValidColorString(const String& value)
+using namespace HTMLNames;
+
+// https://html.spec.whatwg.org/multipage/infrastructure.html#valid-simple-colour
+static bool isValidSimpleColor(StringView string)
 {
-    if (value.isEmpty())
+    if (string.length() != 7)
         return false;
-    if (value[0] != '#')
+    if (string[0] != '#')
         return false;
-
-    // We don't accept #rgb and #aarrggbb formats.
-    if (value.length() != 7)
-        return false;
-    Color color(value);
-    return color.isValid() && !color.hasAlpha();
+    for (unsigned i = 1; i < 7; ++i) {
+        if (!isASCIIHexDigit(string[i]))
+            return false;
+    }
+    return true;
 }
 
-PassOwnPtr<InputType> ColorInputType::create(HTMLInputElement* element)
+// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-simple-colour-values
+static std::optional<RGBA32> parseSimpleColorValue(StringView string)
 {
-    return adoptPtr(new ColorInputType(element));
+    if (!isValidSimpleColor(string))
+        return std::nullopt;
+    return makeRGB(toASCIIHexValue(string[1], string[2]), toASCIIHexValue(string[3], string[4]), toASCIIHexValue(string[5], string[6]));
 }
 
 ColorInputType::~ColorInputType()
 {
-    cleanupColorChooserIfCurrentClient();
+    endColorChooser();
+}
+
+bool ColorInputType::isMouseFocusable() const
+{
+    ASSERT(element());
+    return element()->isTextFormControlFocusable();
+}
+
+bool ColorInputType::isKeyboardFocusable(KeyboardEvent*) const
+{
+    ASSERT(element());
+#if PLATFORM(IOS)
+    if (element()->isReadOnly())
+        return false;
+
+    return element()->isTextFormControlFocusable();
+#endif
+    return false;
 }
 
 bool ColorInputType::isColorControl() const
@@ -74,6 +104,11 @@ bool ColorInputType::isColorControl() const
     return true;
 }
 
+bool ColorInputType::isPresentingAttachedView() const
+{
+    return !!m_chooser;
+}
+
 const AtomicString& ColorInputType::formControlType() const
 {
     return InputTypeNames::color();
@@ -84,130 +119,193 @@ bool ColorInputType::supportsRequired() const
     return false;
 }
 
-String ColorInputType::fallbackValue()
+String ColorInputType::fallbackValue() const
 {
-    return String("#000000");
+    return "#000000"_s;
 }
 
-String ColorInputType::sanitizeValue(const String& proposedValue)
+String ColorInputType::sanitizeValue(const String& proposedValue) const
 {
-    if (proposedValue.isNull())
-        return proposedValue;
-
-    if (!isValidColorString(proposedValue))
+    if (!isValidSimpleColor(proposedValue))
         return fallbackValue();
 
-    return proposedValue.lower();
+    return proposedValue.convertToASCIILowercase();
 }
 
 Color ColorInputType::valueAsColor() const
 {
-    return Color(element()->value());
+    ASSERT(element());
+    return parseSimpleColorValue(element()->value()).value();
 }
 
 void ColorInputType::createShadowSubtree()
 {
-    Document* document = element()->document();
-    RefPtr<HTMLDivElement> wrapperElement = HTMLDivElement::create(document);
-    wrapperElement->setShadowPseudoId("-webkit-color-swatch-wrapper");
-    RefPtr<HTMLDivElement> colorSwatch = HTMLDivElement::create(document);
-    colorSwatch->setShadowPseudoId("-webkit-color-swatch");
-    ExceptionCode ec = 0;
-    wrapperElement->appendChild(colorSwatch.release(), ec);
-    ASSERT(!ec);
-    element()->ensureShadowRoot()->appendChild(wrapperElement.release(), ec);
-    ASSERT(!ec);
-    
+    ASSERT(element());
+    ASSERT(element()->shadowRoot());
+
+    Document& document = element()->document();
+    auto wrapperElement = HTMLDivElement::create(document);
+    wrapperElement->setPseudo(AtomicString("-webkit-color-swatch-wrapper", AtomicString::ConstructFromLiteral));
+    auto colorSwatch = HTMLDivElement::create(document);
+    colorSwatch->setPseudo(AtomicString("-webkit-color-swatch", AtomicString::ConstructFromLiteral));
+    wrapperElement->appendChild(colorSwatch);
+    element()->userAgentShadowRoot()->appendChild(wrapperElement);
+
     updateColorSwatch();
 }
 
-void ColorInputType::setValue(const String& value, bool valueChanged, bool sendChangeEvent);
+void ColorInputType::setValue(const String& value, bool valueChanged, TextFieldEventBehavior eventBehavior)
 {
-    InputType::setValue(value, valueChanged, sendChangeEvent);
+    InputType::setValue(value, valueChanged, eventBehavior);
 
     if (!valueChanged)
         return;
 
     updateColorSwatch();
-    if (ColorChooser::chooser()->client() == this) {
-        if (Chrome* chrome = this->chrome())
-            chrome->setSelectedColorInColorChooser(valueAsColor());
-    }
+    if (m_chooser)
+        m_chooser->setSelectedColor(valueAsColor());
 }
 
-void ColorInputType::handleClickEvent(MouseEvent* event)
+void ColorInputType::handleDOMActivateEvent(Event& event)
 {
-    if (event->isSimulated())
+    ASSERT(element());
+    if (element()->isDisabledFormControl() || !element()->renderer())
         return;
 
-    if (element()->disabled() || element()->readOnly())
+    if (!UserGestureIndicator::processingUserGesture())
         return;
 
     if (Chrome* chrome = this->chrome()) {
-        ColorChooser::chooser()->connectClient(this);
-        chrome->openColorChooser(ColorChooser::chooser(), valueAsColor());
+        if (!m_chooser)
+            m_chooser = chrome->createColorChooser(*this, valueAsColor());
+        else
+            m_chooser->reattachColorChooser(valueAsColor());
     }
-    event->setDefaultHandled();
+
+    event.setDefaultHandled();
 }
 
-void ColorInputType::handleDOMActivateEvent(Event* event)
+void ColorInputType::detach()
 {
-    if (element()->disabled() || element()->readOnly() || !element()->renderer())
-        return;
+    endColorChooser();
+}
 
-    if (!ScriptController::processingUserGesture())
-        return;
+void ColorInputType::elementDidBlur()
+{
+    endColorChooser();
+}
 
-    if (Chrome* chrome = this->chrome()) {
-        ColorChooser::chooser()->connectClient(this);
-        chrome->openColorChooser(ColorChooser::chooser(), valueAsColor());
-    }
-    event->setDefaultHandled();
+bool ColorInputType::shouldRespectListAttribute()
+{
+    return true;
 }
 
-void ColorInputType::detach()
+bool ColorInputType::typeMismatchFor(const String& value) const
 {
-    cleanupColorChooserIfCurrentClient();
+    return !isValidSimpleColor(value);
+}
+
+bool ColorInputType::shouldResetOnDocumentActivation()
+{
+    return true;
 }
 
 void ColorInputType::didChooseColor(const Color& color)
 {
-    if (element()->disabled() || element()->readOnly() || color == valueAsColor())
+    ASSERT(element());
+    if (element()->isDisabledFormControl() || color == valueAsColor())
         return;
+    EventQueueScope scope;
     element()->setValueFromRenderer(color.serialized());
     updateColorSwatch();
     element()->dispatchFormControlChangeEvent();
 }
 
-bool ColorInputType::isColorInputType() const
+void ColorInputType::didEndChooser()
 {
-    return true;
+    m_chooser = nullptr;
+    if (element()->renderer())
+        element()->renderer()->repaint();
 }
 
-void ColorInputType::cleanupColorChooserIfCurrentClient() const
+void ColorInputType::endColorChooser()
 {
-    if (ColorChooser::chooser()->client() != this)
-        return;
-    if (Chrome* chrome = this->chrome())
-        chrome->cleanupColorChooser();
+    if (m_chooser)
+        m_chooser->endChooser();
 }
 
 void ColorInputType::updateColorSwatch()
 {
-    HTMLElement* colorSwatch = shadowColorSwatch();
+    RefPtr<HTMLElement> colorSwatch = shadowColorSwatch();
     if (!colorSwatch)
         return;
 
-    ExceptionCode ec;
-    colorSwatch->style()->setProperty("background-color", element()->value(), ec);
+    ASSERT(element());
+    colorSwatch->setInlineStyleProperty(CSSPropertyBackgroundColor, element()->value(), false);
 }
 
 HTMLElement* ColorInputType::shadowColorSwatch() const
 {
-    ShadowRoot* shadow = element()->shadowRoot();
-    return shadow ? toHTMLElement(shadow->firstChild()->firstChild()) : 0;
+    ASSERT(element());
+    RefPtr<ShadowRoot> shadow = element()->userAgentShadowRoot();
+    if (!shadow)
+        return nullptr;
+
+    auto wrapper = childrenOfType<HTMLDivElement>(*shadow).first();
+    if (!wrapper)
+        return nullptr;
+
+    return childrenOfType<HTMLDivElement>(*wrapper).first();
+}
+
+IntRect ColorInputType::elementRectRelativeToRootView() const
+{
+    ASSERT(element());
+    if (!element()->renderer())
+        return IntRect();
+    return element()->document().view()->contentsToRootView(element()->renderer()->absoluteBoundingBoxRect());
+}
+
+Color ColorInputType::currentColor()
+{
+    return valueAsColor();
+}
+
+bool ColorInputType::shouldShowSuggestions() const
+{
+#if ENABLE(DATALIST_ELEMENT)
+    ASSERT(element());
+    return element()->hasAttributeWithoutSynchronization(listAttr);
+#else
+    return false;
+#endif
+}
+
+Vector<Color> ColorInputType::suggestions() const
+{
+    Vector<Color> suggestions;
+#if ENABLE(DATALIST_ELEMENT)
+    ASSERT(element());
+    if (auto dataList = element()->dataList()) {
+        Ref<HTMLCollection> options = dataList->options();
+        unsigned length = options->length();
+        suggestions.reserveInitialCapacity(length);
+        for (unsigned i = 0; i != length; ++i) {
+            auto value = downcast<HTMLOptionElement>(*options->item(i)).value();
+            if (isValidSimpleColor(value))
+                suggestions.uncheckedAppend(Color(value));
+        }
+    }
+#endif
+    return suggestions;
+}
+
+void ColorInputType::selectColor(StringView string)
+{
+    if (auto color = parseSimpleColorValue(string))
+        didChooseColor(color.value());
 }
 
 } // namespace WebCore
 
-#endif // ENABLE(INPUT_COLOR)
+#endif // ENABLE(INPUT_TYPE_COLOR)