1 | /* |
2 | * Copyright (C) 2006-2018 Apple Inc. All rights reserved. |
3 | * Copyright (C) 2010 Google Inc. All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
8 | * 1. Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * 2. Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * |
14 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
15 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
17 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
18 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
19 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
20 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
21 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
22 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 | */ |
26 | |
27 | #include "config.h" |
28 | #include "TextControlInnerElements.h" |
29 | |
30 | #include "CSSPrimitiveValue.h" |
31 | #include "CSSToLengthConversionData.h" |
32 | #include "Document.h" |
33 | #include "EventNames.h" |
34 | #include "Frame.h" |
35 | #include "HTMLInputElement.h" |
36 | #include "HTMLNames.h" |
37 | #include "LocalizedStrings.h" |
38 | #include "MouseEvent.h" |
39 | #include "PlatformMouseEvent.h" |
40 | #include "RenderSearchField.h" |
41 | #include "RenderTextControl.h" |
42 | #include "RenderView.h" |
43 | #include "ScriptController.h" |
44 | #include "ShadowRoot.h" |
45 | #include "StyleResolver.h" |
46 | #include "TextEvent.h" |
47 | #include "TextEventInputType.h" |
48 | #include <wtf/IsoMallocInlines.h> |
49 | #include <wtf/Ref.h> |
50 | |
51 | namespace WebCore { |
52 | |
53 | WTF_MAKE_ISO_ALLOCATED_IMPL(TextControlInnerContainer); |
54 | WTF_MAKE_ISO_ALLOCATED_IMPL(TextControlInnerElement); |
55 | WTF_MAKE_ISO_ALLOCATED_IMPL(TextControlInnerTextElement); |
56 | WTF_MAKE_ISO_ALLOCATED_IMPL(TextControlPlaceholderElement); |
57 | WTF_MAKE_ISO_ALLOCATED_IMPL(SearchFieldResultsButtonElement); |
58 | WTF_MAKE_ISO_ALLOCATED_IMPL(SearchFieldCancelButtonElement); |
59 | |
60 | using namespace HTMLNames; |
61 | |
62 | TextControlInnerContainer::TextControlInnerContainer(Document& document) |
63 | : HTMLDivElement(divTag, document) |
64 | { |
65 | setHasCustomStyleResolveCallbacks(); |
66 | } |
67 | |
68 | Ref<TextControlInnerContainer> TextControlInnerContainer::create(Document& document) |
69 | { |
70 | return adoptRef(*new TextControlInnerContainer(document)); |
71 | } |
72 | |
73 | RenderPtr<RenderElement> TextControlInnerContainer::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
74 | { |
75 | return createRenderer<RenderTextControlInnerContainer>(*this, WTFMove(style)); |
76 | } |
77 | |
78 | static inline bool isStrongPasswordTextField(const Element* element) |
79 | { |
80 | return is<HTMLInputElement>(element) && downcast<HTMLInputElement>(element)->hasAutoFillStrongPasswordButton(); |
81 | } |
82 | |
83 | Optional<ElementStyle> TextControlInnerContainer::resolveCustomStyle(const RenderStyle& parentStyle, const RenderStyle*) |
84 | { |
85 | auto elementStyle = resolveStyle(&parentStyle); |
86 | if (isStrongPasswordTextField(shadowHost())) { |
87 | elementStyle.renderStyle->setFlexWrap(FlexWrap::Wrap); |
88 | elementStyle.renderStyle->setOverflowX(Overflow::Hidden); |
89 | elementStyle.renderStyle->setOverflowY(Overflow::Hidden); |
90 | } |
91 | return elementStyle; |
92 | } |
93 | |
94 | TextControlInnerElement::TextControlInnerElement(Document& document) |
95 | : HTMLDivElement(divTag, document) |
96 | { |
97 | setHasCustomStyleResolveCallbacks(); |
98 | } |
99 | |
100 | Ref<TextControlInnerElement> TextControlInnerElement::create(Document& document) |
101 | { |
102 | return adoptRef(*new TextControlInnerElement(document)); |
103 | } |
104 | |
105 | Optional<ElementStyle> TextControlInnerElement::resolveCustomStyle(const RenderStyle&, const RenderStyle* shadowHostStyle) |
106 | { |
107 | auto newStyle = RenderStyle::createPtr(); |
108 | newStyle->inheritFrom(*shadowHostStyle); |
109 | newStyle->setFlexGrow(1); |
110 | newStyle->setMinWidth(Length { 0, Fixed }); // Needed for correct shrinking. |
111 | newStyle->setDisplay(DisplayType::Block); |
112 | newStyle->setDirection(TextDirection::LTR); |
113 | // We don't want the shadow DOM to be editable, so we set this block to read-only in case the input itself is editable. |
114 | newStyle->setUserModify(UserModify::ReadOnly); |
115 | |
116 | if (isStrongPasswordTextField(shadowHost())) { |
117 | newStyle->setFlexShrink(0); |
118 | newStyle->setTextOverflow(TextOverflow::Clip); |
119 | newStyle->setOverflowX(Overflow::Hidden); |
120 | newStyle->setOverflowY(Overflow::Hidden); |
121 | |
122 | // Set "flex-basis: 1em". Note that CSSPrimitiveValue::computeLengthInt() only needs the element's |
123 | // style to calculate em lengths. Since the element might not be in a document, just pass nullptr |
124 | // for the root element style and the render view. |
125 | auto emSize = CSSPrimitiveValue::create(1, CSSPrimitiveValue::CSS_EMS); |
126 | int pixels = emSize->computeLength<int>(CSSToLengthConversionData { newStyle.get(), nullptr, nullptr, 1.0, false }); |
127 | newStyle->setFlexBasis(Length { pixels, Fixed }); |
128 | } |
129 | |
130 | return ElementStyle { WTFMove(newStyle) }; |
131 | } |
132 | |
133 | // MARK: TextControlInnerTextElement |
134 | |
135 | inline TextControlInnerTextElement::TextControlInnerTextElement(Document& document) |
136 | : HTMLDivElement(divTag, document) |
137 | { |
138 | setHasCustomStyleResolveCallbacks(); |
139 | } |
140 | |
141 | Ref<TextControlInnerTextElement> TextControlInnerTextElement::create(Document& document) |
142 | { |
143 | return adoptRef(*new TextControlInnerTextElement(document)); |
144 | } |
145 | |
146 | void TextControlInnerTextElement::defaultEventHandler(Event& event) |
147 | { |
148 | // FIXME: In the future, we should add a way to have default event listeners. |
149 | // Then we would add one to the text field's inner div, and we wouldn't need this subclass. |
150 | // Or possibly we could just use a normal event listener. |
151 | if (event.isBeforeTextInsertedEvent()) { |
152 | // A TextControlInnerTextElement can have no host if its been detached, |
153 | // but kept alive by an EditCommand. In this case, an undo/redo can |
154 | // cause events to be sent to the TextControlInnerTextElement. To |
155 | // prevent an infinite loop, we must check for this case before sending |
156 | // the event up the chain. |
157 | if (auto host = makeRefPtr(shadowHost())) |
158 | host->defaultEventHandler(event); |
159 | } |
160 | if (!event.defaultHandled()) |
161 | HTMLDivElement::defaultEventHandler(event); |
162 | } |
163 | |
164 | RenderPtr<RenderElement> TextControlInnerTextElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
165 | { |
166 | return createRenderer<RenderTextControlInnerBlock>(*this, WTFMove(style)); |
167 | } |
168 | |
169 | RenderTextControlInnerBlock* TextControlInnerTextElement::renderer() const |
170 | { |
171 | return downcast<RenderTextControlInnerBlock>(HTMLDivElement::renderer()); |
172 | } |
173 | |
174 | Optional<ElementStyle> TextControlInnerTextElement::resolveCustomStyle(const RenderStyle&, const RenderStyle* shadowHostStyle) |
175 | { |
176 | auto style = downcast<HTMLTextFormControlElement>(*shadowHost()).createInnerTextStyle(*shadowHostStyle); |
177 | return ElementStyle(std::make_unique<RenderStyle>(WTFMove(style))); |
178 | } |
179 | |
180 | // MARK: TextControlPlaceholderElement |
181 | |
182 | inline TextControlPlaceholderElement::TextControlPlaceholderElement(Document& document) |
183 | : HTMLDivElement(divTag, document) |
184 | { |
185 | setPseudo(AtomicString("placeholder" , AtomicString::ConstructFromLiteral)); |
186 | setHasCustomStyleResolveCallbacks(); |
187 | } |
188 | |
189 | Ref<TextControlPlaceholderElement> TextControlPlaceholderElement::create(Document& document) |
190 | { |
191 | return adoptRef(*new TextControlPlaceholderElement(document)); |
192 | } |
193 | |
194 | Optional<ElementStyle> TextControlPlaceholderElement::resolveCustomStyle(const RenderStyle& parentStyle, const RenderStyle* shadowHostStyle) |
195 | { |
196 | auto style = resolveStyle(&parentStyle); |
197 | |
198 | auto& controlElement = downcast<HTMLTextFormControlElement>(*containingShadowRoot()->host()); |
199 | style.renderStyle->setDisplay(controlElement.isPlaceholderVisible() ? DisplayType::Block : DisplayType::None); |
200 | |
201 | if (is<HTMLInputElement>(controlElement)) { |
202 | auto& inputElement = downcast<HTMLInputElement>(controlElement); |
203 | style.renderStyle->setTextOverflow(inputElement.shouldTruncateText(*shadowHostStyle) ? TextOverflow::Ellipsis : TextOverflow::Clip); |
204 | } |
205 | return style; |
206 | } |
207 | |
208 | // MARK: SearchFieldResultsButtonElement |
209 | |
210 | inline SearchFieldResultsButtonElement::SearchFieldResultsButtonElement(Document& document) |
211 | : HTMLDivElement(divTag, document) |
212 | { |
213 | } |
214 | |
215 | Ref<SearchFieldResultsButtonElement> SearchFieldResultsButtonElement::create(Document& document) |
216 | { |
217 | return adoptRef(*new SearchFieldResultsButtonElement(document)); |
218 | } |
219 | |
220 | void SearchFieldResultsButtonElement::defaultEventHandler(Event& event) |
221 | { |
222 | // On mousedown, bring up a menu, if needed |
223 | auto input = makeRefPtr(downcast<HTMLInputElement>(shadowHost())); |
224 | if (input && event.type() == eventNames().mousedownEvent && is<MouseEvent>(event) && downcast<MouseEvent>(event).button() == LeftButton) { |
225 | input->focus(); |
226 | input->select(); |
227 | #if !PLATFORM(IOS_FAMILY) |
228 | if (auto* renderer = input->renderer()) { |
229 | auto& searchFieldRenderer = downcast<RenderSearchField>(*renderer); |
230 | if (searchFieldRenderer.popupIsVisible()) |
231 | searchFieldRenderer.hidePopup(); |
232 | else if (input->maxResults() > 0) |
233 | searchFieldRenderer.showPopup(); |
234 | } |
235 | #endif |
236 | event.setDefaultHandled(); |
237 | } |
238 | |
239 | if (!event.defaultHandled()) |
240 | HTMLDivElement::defaultEventHandler(event); |
241 | } |
242 | |
243 | #if !PLATFORM(IOS_FAMILY) |
244 | bool SearchFieldResultsButtonElement::willRespondToMouseClickEvents() |
245 | { |
246 | return true; |
247 | } |
248 | #endif |
249 | |
250 | // MARK: SearchFieldCancelButtonElement |
251 | |
252 | inline SearchFieldCancelButtonElement::SearchFieldCancelButtonElement(Document& document) |
253 | : HTMLDivElement(divTag, document) |
254 | { |
255 | setPseudo(AtomicString("-webkit-search-cancel-button" , AtomicString::ConstructFromLiteral)); |
256 | #if !PLATFORM(IOS_FAMILY) |
257 | setAttributeWithoutSynchronization(aria_labelAttr, AXSearchFieldCancelButtonText()); |
258 | #endif |
259 | setAttributeWithoutSynchronization(roleAttr, AtomicString("button" , AtomicString::ConstructFromLiteral)); |
260 | } |
261 | |
262 | Ref<SearchFieldCancelButtonElement> SearchFieldCancelButtonElement::create(Document& document) |
263 | { |
264 | return adoptRef(*new SearchFieldCancelButtonElement(document)); |
265 | } |
266 | |
267 | void SearchFieldCancelButtonElement::defaultEventHandler(Event& event) |
268 | { |
269 | RefPtr<HTMLInputElement> input(downcast<HTMLInputElement>(shadowHost())); |
270 | if (!input || input->isDisabledOrReadOnly()) { |
271 | if (!event.defaultHandled()) |
272 | HTMLDivElement::defaultEventHandler(event); |
273 | return; |
274 | } |
275 | |
276 | if (event.type() == eventNames().mousedownEvent && is<MouseEvent>(event) && downcast<MouseEvent>(event).button() == LeftButton) { |
277 | input->focus(); |
278 | input->select(); |
279 | event.setDefaultHandled(); |
280 | } |
281 | |
282 | if (event.type() == eventNames().clickEvent) { |
283 | input->setValueForUser(emptyString()); |
284 | input->onSearch(); |
285 | event.setDefaultHandled(); |
286 | } |
287 | |
288 | if (!event.defaultHandled()) |
289 | HTMLDivElement::defaultEventHandler(event); |
290 | } |
291 | |
292 | #if !PLATFORM(IOS_FAMILY) |
293 | bool SearchFieldCancelButtonElement::willRespondToMouseClickEvents() |
294 | { |
295 | const RefPtr<HTMLInputElement> input = downcast<HTMLInputElement>(shadowHost()); |
296 | if (input && !input->isDisabledOrReadOnly()) |
297 | return true; |
298 | |
299 | return HTMLDivElement::willRespondToMouseClickEvents(); |
300 | } |
301 | #endif |
302 | |
303 | } |
304 | |