[macOS] Color wells should appear pressed when presenting a color picker
[WebKit-https.git] / Source / WebCore / html / ColorInputType.cpp
index e6cf052..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 "HTMLOptionElement.h"
 #include "InputTypeNames.h"
-#include "MouseEvent.h"
-#include "RenderObject.h"
 #include "RenderView.h"
-#include "ScriptController.h"
+#include "ScopedEventQueue.h"
 #include "ShadowRoot.h"
+#include "UserGestureIndicator.h"
 
 namespace WebCore {
 
 using namespace HTMLNames;
 
-static bool isValidColorString(const String& value)
+// 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;
+    for (unsigned i = 1; i < 7; ++i) {
+        if (!isASCIIHexDigit(string[i]))
+            return false;
+    }
+    return true;
+}
 
-    // We don't accept #rgb and #aarrggbb formats.
-    if (value.length() != 7)
-        return false;
-    Color color(value);
-    return color.isValid() && !color.hasAlpha();
+// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-simple-colour-values
+static std::optional<RGBA32> parseSimpleColorValue(StringView string)
+{
+    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()
@@ -69,11 +81,34 @@ ColorInputType::~ColorInputType()
     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
 {
     return true;
 }
 
+bool ColorInputType::isPresentingAttachedView() const
+{
+    return !!m_chooser;
+}
+
 const AtomicString& ColorInputType::formControlType() const
 {
     return InputTypeNames::color();
@@ -86,34 +121,36 @@ bool ColorInputType::supportsRequired() const
 
 String ColorInputType::fallbackValue() const
 {
-    return String("#000000");
+    return "#000000"_s;
 }
 
 String ColorInputType::sanitizeValue(const String& proposedValue) const
 {
-    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()
 {
-    ASSERT(element().shadowRoot());
+    ASSERT(element());
+    ASSERT(element()->shadowRoot());
 
-    Document& document = element().document();
-    RefPtr<HTMLDivElement> wrapperElement = HTMLDivElement::create(document);
+    Document& document = element()->document();
+    auto wrapperElement = HTMLDivElement::create(document);
     wrapperElement->setPseudo(AtomicString("-webkit-color-swatch-wrapper", AtomicString::ConstructFromLiteral));
-    RefPtr<HTMLDivElement> colorSwatch = HTMLDivElement::create(document);
+    auto colorSwatch = HTMLDivElement::create(document);
     colorSwatch->setPseudo(AtomicString("-webkit-color-swatch", AtomicString::ConstructFromLiteral));
-    wrapperElement->appendChild(colorSwatch.release(), ASSERT_NO_EXCEPTION);
-    element().userAgentShadowRoot()->appendChild(wrapperElement.release(), ASSERT_NO_EXCEPTION);
-    
+    wrapperElement->appendChild(colorSwatch);
+    element()->userAgentShadowRoot()->appendChild(wrapperElement);
+
     updateColorSwatch();
 }
 
@@ -129,22 +166,23 @@ void ColorInputType::setValue(const String& value, bool valueChanged, TextFieldE
         m_chooser->setSelectedColor(valueAsColor());
 }
 
-void ColorInputType::handleDOMActivateEvent(Event* event)
+void ColorInputType::handleDOMActivateEvent(Event& event)
 {
-    if (element().isDisabledOrReadOnly() || !element().renderer())
+    ASSERT(element());
+    if (element()->isDisabledFormControl() || !element()->renderer())
         return;
 
-    if (!ScriptController::processingUserGesture())
+    if (!UserGestureIndicator::processingUserGesture())
         return;
 
     if (Chrome* chrome = this->chrome()) {
         if (!m_chooser)
-            m_chooser = chrome->createColorChooser(this, valueAsColor());
+            m_chooser = chrome->createColorChooser(*this, valueAsColor());
         else
             m_chooser->reattachColorChooser(valueAsColor());
     }
 
-    event->setDefaultHandled();
+    event.setDefaultHandled();
 }
 
 void ColorInputType::detach()
@@ -152,14 +190,19 @@ void ColorInputType::detach()
     endColorChooser();
 }
 
+void ColorInputType::elementDidBlur()
+{
+    endColorChooser();
+}
+
 bool ColorInputType::shouldRespectListAttribute()
 {
-    return InputType::themeSupportsDataListUI(this);
+    return true;
 }
 
 bool ColorInputType::typeMismatchFor(const String& value) const
 {
-    return !isValidColorString(value);
+    return !isValidSimpleColor(value);
 }
 
 bool ColorInputType::shouldResetOnDocumentActivation()
@@ -169,16 +212,20 @@ bool ColorInputType::shouldResetOnDocumentActivation()
 
 void ColorInputType::didChooseColor(const Color& color)
 {
-    if (element().isDisabledOrReadOnly() || color == valueAsColor())
+    ASSERT(element());
+    if (element()->isDisabledFormControl() || color == valueAsColor())
         return;
-    element().setValueFromRenderer(color.serialized());
+    EventQueueScope scope;
+    element()->setValueFromRenderer(color.serialized());
     updateColorSwatch();
-    element().dispatchFormControlChangeEvent();
+    element()->dispatchFormControlChangeEvent();
 }
 
 void ColorInputType::didEndChooser()
 {
     m_chooser = nullptr;
+    if (element()->renderer())
+        element()->renderer()->repaint();
 }
 
 void ColorInputType::endColorChooser()
@@ -189,22 +236,34 @@ void ColorInputType::endColorChooser()
 
 void ColorInputType::updateColorSwatch()
 {
-    HTMLElement* colorSwatch = shadowColorSwatch();
+    RefPtr<HTMLElement> colorSwatch = shadowColorSwatch();
     if (!colorSwatch)
         return;
 
-    colorSwatch->setInlineStyleProperty(CSSPropertyBackgroundColor, element().value(), false);
+    ASSERT(element());
+    colorSwatch->setInlineStyleProperty(CSSPropertyBackgroundColor, element()->value(), false);
 }
 
 HTMLElement* ColorInputType::shadowColorSwatch() const
 {
-    ShadowRoot* shadow = element().userAgentShadowRoot();
-    return shadow ? downcast<HTMLElement>(shadow->firstChild()->firstChild()) : nullptr;
+    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
 {
-    return element().document().view()->contentsToRootView(rendererBoundingBox(element()));
+    ASSERT(element());
+    if (!element()->renderer())
+        return IntRect();
+    return element()->document().view()->contentsToRootView(element()->renderer()->absoluteBoundingBoxRect());
 }
 
 Color ColorInputType::currentColor()
@@ -215,7 +274,8 @@ Color ColorInputType::currentColor()
 bool ColorInputType::shouldShowSuggestions() const
 {
 #if ENABLE(DATALIST_ELEMENT)
-    return element().fastHasAttribute(listAttr);
+    ASSERT(element());
+    return element()->hasAttributeWithoutSynchronization(listAttr);
 #else
     return false;
 #endif
@@ -225,22 +285,27 @@ Vector<Color> ColorInputType::suggestions() const
 {
     Vector<Color> suggestions;
 #if ENABLE(DATALIST_ELEMENT)
-    HTMLDataListElement* dataList = element().dataList();
-    if (dataList) {
-        RefPtr<HTMLCollection> options = dataList->options();
-        for (unsigned i = 0; HTMLOptionElement* option = downcast<HTMLOptionElement>(options->item(i)); ++i) {
-            if (!element().isValidValue(option->value()))
-                continue;
-            Color color(option->value());
-            if (!color.isValid())
-                continue;
-            suggestions.append(color);
+    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_TYPE_COLOR)