1 /* 2 * Copyright (C) 2010 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "core/html/forms/ColorInputType.h" 33 34 #include "bindings/v8/ExceptionStatePlaceholder.h" 35 #include "bindings/v8/ScriptController.h" 36 #include "core/CSSPropertyNames.h" 37 #include "core/InputTypeNames.h" 38 #include "core/events/MouseEvent.h" 39 #include "core/dom/shadow/ShadowRoot.h" 40 #include "core/html/HTMLDataListElement.h" 41 #include "core/html/HTMLDivElement.h" 42 #include "core/html/HTMLInputElement.h" 43 #include "core/html/HTMLOptionElement.h" 44 #include "core/page/Chrome.h" 45 #include "core/rendering/RenderView.h" 46 #include "platform/ColorChooser.h" 47 #include "platform/RuntimeEnabledFeatures.h" 48 #include "platform/UserGestureIndicator.h" 49 #include "platform/graphics/Color.h" 50 #include "wtf/PassOwnPtr.h" 51 #include "wtf/text/WTFString.h" 52 53 namespace WebCore { 54 55 using namespace HTMLNames; 56 57 // Upper limit of number of datalist suggestions shown. 58 static const unsigned maxSuggestions = 1000; 59 // Upper limit for the length of the labels for datalist suggestions. 60 static const unsigned maxSuggestionLabelLength = 1000; 61 62 static bool isValidColorString(const String& value) 63 { 64 if (value.isEmpty()) 65 return false; 66 if (value[0] != '#') 67 return false; 68 69 // We don't accept #rgb and #aarrggbb formats. 70 if (value.length() != 7) 71 return false; 72 Color color; 73 return color.setFromString(value) && !color.hasAlpha(); 74 } 75 76 PassRefPtrWillBeRawPtr<InputType> ColorInputType::create(HTMLInputElement& element) 77 { 78 return adoptRefWillBeNoop(new ColorInputType(element)); 79 } 80 81 ColorInputType::~ColorInputType() 82 { 83 endColorChooser(); 84 } 85 86 void ColorInputType::countUsage() 87 { 88 countUsageIfVisible(UseCounter::InputTypeColor); 89 } 90 91 bool ColorInputType::isColorControl() const 92 { 93 return true; 94 } 95 96 const AtomicString& ColorInputType::formControlType() const 97 { 98 return InputTypeNames::color; 99 } 100 101 bool ColorInputType::supportsRequired() const 102 { 103 return false; 104 } 105 106 String ColorInputType::fallbackValue() const 107 { 108 return String("#000000"); 109 } 110 111 String ColorInputType::sanitizeValue(const String& proposedValue) const 112 { 113 if (!isValidColorString(proposedValue)) 114 return fallbackValue(); 115 116 return proposedValue.lower(); 117 } 118 119 Color ColorInputType::valueAsColor() const 120 { 121 Color color; 122 bool success = color.setFromString(element().value()); 123 ASSERT_UNUSED(success, success); 124 return color; 125 } 126 127 void ColorInputType::createShadowSubtree() 128 { 129 ASSERT(element().shadow()); 130 131 Document& document = element().document(); 132 RefPtrWillBeRawPtr<HTMLDivElement> wrapperElement = HTMLDivElement::create(document); 133 wrapperElement->setShadowPseudoId(AtomicString("-webkit-color-swatch-wrapper", AtomicString::ConstructFromLiteral)); 134 RefPtrWillBeRawPtr<HTMLDivElement> colorSwatch = HTMLDivElement::create(document); 135 colorSwatch->setShadowPseudoId(AtomicString("-webkit-color-swatch", AtomicString::ConstructFromLiteral)); 136 wrapperElement->appendChild(colorSwatch.release()); 137 element().userAgentShadowRoot()->appendChild(wrapperElement.release()); 138 139 element().updateView(); 140 } 141 142 void ColorInputType::setValue(const String& value, bool valueChanged, TextFieldEventBehavior eventBehavior) 143 { 144 InputType::setValue(value, valueChanged, eventBehavior); 145 146 if (!valueChanged) 147 return; 148 149 element().updateView(); 150 if (m_chooser) 151 m_chooser->setSelectedColor(valueAsColor()); 152 } 153 154 void ColorInputType::handleDOMActivateEvent(Event* event) 155 { 156 if (element().isDisabledFormControl() || !element().renderer()) 157 return; 158 159 if (!UserGestureIndicator::processingUserGesture()) 160 return; 161 162 Chrome* chrome = this->chrome(); 163 if (chrome && !m_chooser) 164 m_chooser = chrome->createColorChooser(element().document().frame(), this, valueAsColor()); 165 166 event->setDefaultHandled(); 167 } 168 169 void ColorInputType::closePopupView() 170 { 171 endColorChooser(); 172 } 173 174 bool ColorInputType::shouldRespectListAttribute() 175 { 176 return true; 177 } 178 179 bool ColorInputType::typeMismatchFor(const String& value) const 180 { 181 return !isValidColorString(value); 182 } 183 184 void ColorInputType::didChooseColor(const Color& color) 185 { 186 if (element().isDisabledFormControl() || color == valueAsColor()) 187 return; 188 element().setValueFromRenderer(color.serialized()); 189 element().updateView(); 190 element().dispatchFormControlChangeEvent(); 191 } 192 193 void ColorInputType::didEndChooser() 194 { 195 m_chooser.clear(); 196 } 197 198 void ColorInputType::endColorChooser() 199 { 200 if (m_chooser) 201 m_chooser->endChooser(); 202 } 203 204 void ColorInputType::updateView() 205 { 206 HTMLElement* colorSwatch = shadowColorSwatch(); 207 if (!colorSwatch) 208 return; 209 210 colorSwatch->setInlineStyleProperty(CSSPropertyBackgroundColor, element().value()); 211 } 212 213 HTMLElement* ColorInputType::shadowColorSwatch() const 214 { 215 ShadowRoot* shadow = element().userAgentShadowRoot(); 216 return shadow ? toHTMLElement(shadow->firstChild()->firstChild()) : 0; 217 } 218 219 IntRect ColorInputType::elementRectRelativeToRootView() const 220 { 221 return element().document().view()->contentsToRootView(element().pixelSnappedBoundingBox()); 222 } 223 224 Color ColorInputType::currentColor() 225 { 226 return valueAsColor(); 227 } 228 229 bool ColorInputType::shouldShowSuggestions() const 230 { 231 return element().fastHasAttribute(listAttr); 232 } 233 234 Vector<ColorSuggestion> ColorInputType::suggestions() const 235 { 236 Vector<ColorSuggestion> suggestions; 237 HTMLDataListElement* dataList = element().dataList(); 238 if (dataList) { 239 RefPtrWillBeRawPtr<HTMLCollection> options = dataList->options(); 240 for (unsigned i = 0; HTMLOptionElement* option = toHTMLOptionElement(options->item(i)); i++) { 241 if (!element().isValidValue(option->value())) 242 continue; 243 Color color; 244 if (!color.setFromString(option->value())) 245 continue; 246 ColorSuggestion suggestion(color, option->label().left(maxSuggestionLabelLength)); 247 suggestions.append(suggestion); 248 if (suggestions.size() >= maxSuggestions) 249 break; 250 } 251 } 252 return suggestions; 253 } 254 255 } // namespace WebCore 256