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 "third_party/blink/renderer/core/html/forms/color_input_type.h"
32 
33 #include "third_party/blink/public/mojom/choosers/color_chooser.mojom-blink.h"
34 #include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
35 #include "third_party/blink/renderer/core/css/css_property_names.h"
36 #include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
37 #include "third_party/blink/renderer/core/dom/shadow_root.h"
38 #include "third_party/blink/renderer/core/events/mouse_event.h"
39 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
40 #include "third_party/blink/renderer/core/frame/web_feature.h"
41 #include "third_party/blink/renderer/core/html/forms/color_chooser.h"
42 #include "third_party/blink/renderer/core/html/forms/html_data_list_element.h"
43 #include "third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h"
44 #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
45 #include "third_party/blink/renderer/core/html/forms/html_option_element.h"
46 #include "third_party/blink/renderer/core/html/html_div_element.h"
47 #include "third_party/blink/renderer/core/input_type_names.h"
48 #include "third_party/blink/renderer/core/layout/layout_object.h"
49 #include "third_party/blink/renderer/core/layout/layout_theme.h"
50 #include "third_party/blink/renderer/core/page/chrome_client.h"
51 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
52 #include "third_party/blink/renderer/platform/graphics/color.h"
53 #include "third_party/blink/renderer/platform/heap/heap.h"
54 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
55 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
56 #include "ui/base/ui_base_features.h"
57 
58 namespace blink {
59 
60 // Upper limit of number of datalist suggestions shown.
61 static const unsigned kMaxSuggestions = 1000;
62 // Upper limit for the length of the labels for datalist suggestions.
63 static const unsigned kMaxSuggestionLabelLength = 1000;
64 
IsValidColorString(const String & value)65 static bool IsValidColorString(const String& value) {
66   if (value.IsEmpty())
67     return false;
68   if (value[0] != '#')
69     return false;
70 
71   // We don't accept #rgb and #aarrggbb formats.
72   if (value.length() != 7)
73     return false;
74   Color color;
75   return color.SetFromString(value) && !color.HasAlpha();
76 }
77 
ColorInputType(HTMLInputElement & element)78 ColorInputType::ColorInputType(HTMLInputElement& element)
79     : InputType(element), KeyboardClickableInputTypeView(element) {}
80 
81 ColorInputType::~ColorInputType() = default;
82 
Trace(Visitor * visitor)83 void ColorInputType::Trace(Visitor* visitor) {
84   visitor->Trace(chooser_);
85   KeyboardClickableInputTypeView::Trace(visitor);
86   ColorChooserClient::Trace(visitor);
87   InputType::Trace(visitor);
88 }
89 
CreateView()90 InputTypeView* ColorInputType::CreateView() {
91   return this;
92 }
93 
GetValueMode() const94 InputType::ValueMode ColorInputType::GetValueMode() const {
95   return ValueMode::kValue;
96 }
97 
CountUsage()98 void ColorInputType::CountUsage() {
99   CountUsageIfVisible(WebFeature::kInputTypeColor);
100 }
101 
FormControlType() const102 const AtomicString& ColorInputType::FormControlType() const {
103   return input_type_names::kColor;
104 }
105 
SupportsRequired() const106 bool ColorInputType::SupportsRequired() const {
107   return false;
108 }
109 
SanitizeValue(const String & proposed_value) const110 String ColorInputType::SanitizeValue(const String& proposed_value) const {
111   if (!IsValidColorString(proposed_value))
112     return "#000000";
113   return proposed_value.LowerASCII();
114 }
115 
ValueAsColor() const116 Color ColorInputType::ValueAsColor() const {
117   Color color;
118   bool success = color.SetFromString(GetElement().value());
119   DCHECK(success);
120   return color;
121 }
122 
CreateShadowSubtree()123 void ColorInputType::CreateShadowSubtree() {
124   DCHECK(IsShadowHost(GetElement()));
125 
126   Document& document = GetElement().GetDocument();
127   auto* wrapper_element = MakeGarbageCollected<HTMLDivElement>(document);
128   wrapper_element->SetShadowPseudoId(
129       AtomicString("-webkit-color-swatch-wrapper"));
130   auto* color_swatch = MakeGarbageCollected<HTMLDivElement>(document);
131   color_swatch->SetShadowPseudoId(AtomicString("-webkit-color-swatch"));
132   wrapper_element->AppendChild(color_swatch);
133   GetElement().UserAgentShadowRoot()->AppendChild(wrapper_element);
134 
135   GetElement().UpdateView();
136 }
137 
DidSetValue(const String &,bool value_changed)138 void ColorInputType::DidSetValue(const String&, bool value_changed) {
139   if (!value_changed)
140     return;
141   GetElement().UpdateView();
142   if (chooser_)
143     chooser_->SetSelectedColor(ValueAsColor());
144 }
145 
HandleDOMActivateEvent(Event & event)146 void ColorInputType::HandleDOMActivateEvent(Event& event) {
147   if (GetElement().IsDisabledFormControl())
148     return;
149 
150   Document& document = GetElement().GetDocument();
151   if (!LocalFrame::HasTransientUserActivation(document.GetFrame()))
152     return;
153 
154   ChromeClient* chrome_client = GetChromeClient();
155   if (chrome_client && !HasOpenedPopup()) {
156     UseCounter::Count(
157         document,
158         (event.UnderlyingEvent() && event.UnderlyingEvent()->isTrusted())
159             ? WebFeature::kColorInputTypeChooserByTrustedClick
160             : WebFeature::kColorInputTypeChooserByUntrustedClick);
161     chooser_ = chrome_client->OpenColorChooser(document.GetFrame(), this,
162                                                ValueAsColor());
163     if (::features::IsFormControlsRefreshEnabled() &&
164         GetElement().GetLayoutObject()) {
165       // Invalidate paint to ensure that the focus ring is removed.
166       GetElement().GetLayoutObject()->SetShouldDoFullPaintInvalidation();
167     }
168   }
169 
170   event.SetDefaultHandled();
171 }
172 
ClosePopupView()173 void ColorInputType::ClosePopupView() {
174   if (chooser_)
175     chooser_->EndChooser();
176 }
177 
HasOpenedPopup() const178 bool ColorInputType::HasOpenedPopup() const {
179   return chooser_;
180 }
181 
ShouldRespectListAttribute()182 bool ColorInputType::ShouldRespectListAttribute() {
183   return true;
184 }
185 
TypeMismatchFor(const String & value) const186 bool ColorInputType::TypeMismatchFor(const String& value) const {
187   return !IsValidColorString(value);
188 }
189 
WarnIfValueIsInvalid(const String & value) const190 void ColorInputType::WarnIfValueIsInvalid(const String& value) const {
191   if (!EqualIgnoringASCIICase(value, GetElement().SanitizeValue(value)))
192     AddWarningToConsole(
193         "The specified value %s does not conform to the required format.  The "
194         "format is \"#rrggbb\" where rr, gg, bb are two-digit hexadecimal "
195         "numbers.",
196         value);
197 }
198 
ValueAttributeChanged()199 void ColorInputType::ValueAttributeChanged() {
200   if (!GetElement().HasDirtyValue())
201     GetElement().UpdateView();
202 }
203 
DidChooseColor(const Color & color)204 void ColorInputType::DidChooseColor(const Color& color) {
205   if (GetElement().IsDisabledFormControl() || color == ValueAsColor())
206     return;
207   EventQueueScope scope;
208   GetElement().SetValueFromRenderer(color.Serialized());
209   GetElement().UpdateView();
210   if (!LayoutTheme::GetTheme().IsModalColorChooser())
211     GetElement().DispatchFormControlChangeEvent();
212 }
213 
DidEndChooser()214 void ColorInputType::DidEndChooser() {
215   if (LayoutTheme::GetTheme().IsModalColorChooser())
216     GetElement().EnqueueChangeEvent();
217   chooser_.Clear();
218   if (::features::IsFormControlsRefreshEnabled() &&
219       GetElement().GetLayoutObject()) {
220     // Invalidate paint to ensure that the focus ring is shown.
221     GetElement().GetLayoutObject()->SetShouldDoFullPaintInvalidation();
222   }
223 }
224 
UpdateView()225 void ColorInputType::UpdateView() {
226   HTMLElement* color_swatch = ShadowColorSwatch();
227   if (!color_swatch)
228     return;
229 
230   color_swatch->SetInlineStyleProperty(CSSPropertyID::kBackgroundColor,
231                                        GetElement().value());
232 }
233 
ShadowColorSwatch() const234 HTMLElement* ColorInputType::ShadowColorSwatch() const {
235   ShadowRoot* shadow = GetElement().UserAgentShadowRoot();
236   if (shadow) {
237     CHECK(IsA<HTMLElement>(shadow->firstChild()->firstChild()));
238     return To<HTMLElement>(shadow->firstChild()->firstChild());
239   }
240   return nullptr;
241 }
242 
OwnerElement() const243 Element& ColorInputType::OwnerElement() const {
244   return GetElement();
245 }
246 
ElementRectRelativeToViewport() const247 IntRect ColorInputType::ElementRectRelativeToViewport() const {
248   return GetElement().GetDocument().View()->FrameToViewport(
249       GetElement().PixelSnappedBoundingBox());
250 }
251 
CurrentColor()252 Color ColorInputType::CurrentColor() {
253   return ValueAsColor();
254 }
255 
ShouldShowSuggestions() const256 bool ColorInputType::ShouldShowSuggestions() const {
257   return GetElement().FastHasAttribute(html_names::kListAttr);
258 }
259 
Suggestions() const260 Vector<mojom::blink::ColorSuggestionPtr> ColorInputType::Suggestions() const {
261   Vector<mojom::blink::ColorSuggestionPtr> suggestions;
262   HTMLDataListElement* data_list = GetElement().DataList();
263   if (data_list) {
264     HTMLDataListOptionsCollection* options = data_list->options();
265     for (unsigned i = 0; HTMLOptionElement* option = options->Item(i); i++) {
266       if (option->IsDisabledFormControl() || option->value().IsEmpty())
267         continue;
268       if (!GetElement().IsValidValue(option->value()))
269         continue;
270       Color color;
271       if (!color.SetFromString(option->value()))
272         continue;
273       suggestions.push_back(mojom::blink::ColorSuggestion::New(
274           color.Rgb(), option->label().Left(kMaxSuggestionLabelLength)));
275       if (suggestions.size() >= kMaxSuggestions)
276         break;
277     }
278   }
279   return suggestions;
280 }
281 
PopupRootAXObject()282 AXObject* ColorInputType::PopupRootAXObject() {
283   return chooser_ ? chooser_->RootAXObject() : nullptr;
284 }
285 
GetColorChooserClient()286 ColorChooserClient* ColorInputType::GetColorChooserClient() {
287   return this;
288 }
289 
290 
291 }  // namespace blink
292