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