SearchInputType could end up with a mismatched renderer.
[WebKit-https.git] / Source / WebCore / html / SearchInputType.cpp
1 /*
2  * Copyright (C) 2010 Google Inc. All rights reserved.
3  * Copyright (C) 2014 Apple 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 are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "config.h"
33 #include "SearchInputType.h"
34
35 #include "HTMLInputElement.h"
36 #include "HTMLNames.h"
37 #include "InputTypeNames.h"
38 #include "KeyboardEvent.h"
39 #include "RenderSearchField.h"
40 #include "ShadowRoot.h"
41 #include "TextControlInnerElements.h"
42
43 namespace WebCore {
44
45 using namespace HTMLNames;
46
47 SearchInputType::SearchInputType(HTMLInputElement& element)
48     : BaseTextInputType(element)
49     , m_resultsButton(nullptr)
50     , m_cancelButton(nullptr)
51     , m_searchEventTimer(*this, &SearchInputType::searchEventTimerFired)
52 {
53 }
54
55 void SearchInputType::addSearchResult()
56 {
57 #if !PLATFORM(IOS)
58     // Normally we've got the correct renderer by the time we get here. However when the input type changes
59     // we don't update the associated renderers until after the next tree update, so we could actually end up here
60     // with a mismatched renderer (e.g. through form submission).
61     if (is<RenderSearchField>(element().renderer()))
62         downcast<RenderSearchField>(*element().renderer()).addSearchResult();
63 #endif
64 }
65
66 static void updateResultButtonPseudoType(SearchFieldResultsButtonElement& resultButton, int maxResults)
67 {
68     if (!maxResults)
69         resultButton.setPseudo(AtomicString("-webkit-search-results-decoration", AtomicString::ConstructFromLiteral));
70     else if (maxResults < 0)
71         resultButton.setPseudo(AtomicString("-webkit-search-decoration", AtomicString::ConstructFromLiteral));
72     else if (maxResults > 0)
73         resultButton.setPseudo(AtomicString("-webkit-search-results-button", AtomicString::ConstructFromLiteral));
74 }
75
76 void SearchInputType::maxResultsAttributeChanged()
77 {
78     if (m_resultsButton)
79         updateResultButtonPseudoType(*m_resultsButton, element().maxResults());
80 }
81
82 RenderPtr<RenderElement> SearchInputType::createInputRenderer(RenderStyle&& style)
83 {
84     return createRenderer<RenderSearchField>(element(), WTFMove(style));
85 }
86
87 const AtomicString& SearchInputType::formControlType() const
88 {
89     return InputTypeNames::search();
90 }
91
92 bool SearchInputType::isSearchField() const
93 {
94     return true;
95 }
96
97 bool SearchInputType::needsContainer() const
98 {
99     return true;
100 }
101
102 void SearchInputType::createShadowSubtree()
103 {
104     ASSERT(!m_resultsButton);
105     ASSERT(!m_cancelButton);
106
107     TextFieldInputType::createShadowSubtree();
108     HTMLElement* container = containerElement();
109     HTMLElement* textWrapper = innerBlockElement();
110     ASSERT(container);
111     ASSERT(textWrapper);
112
113     auto resultsButton = SearchFieldResultsButtonElement::create(element().document());
114     m_resultsButton = resultsButton.ptr();
115     updateResultButtonPseudoType(resultsButton.get(), element().maxResults());
116     container->insertBefore(resultsButton, textWrapper);
117
118     auto cancelButton = SearchFieldCancelButtonElement::create(element().document());
119     m_cancelButton = cancelButton.ptr();
120     container->insertBefore(cancelButton, textWrapper->nextSibling());
121 }
122
123 HTMLElement* SearchInputType::resultsButtonElement() const
124 {
125     return m_resultsButton;
126 }
127
128 HTMLElement* SearchInputType::cancelButtonElement() const
129 {
130     return m_cancelButton;
131 }
132
133 void SearchInputType::handleKeydownEvent(KeyboardEvent& event)
134 {
135     if (element().isDisabledOrReadOnly()) {
136         TextFieldInputType::handleKeydownEvent(event);
137         return;
138     }
139
140     const String& key = event.keyIdentifier();
141     if (key == "U+001B") {
142         Ref<HTMLInputElement> input(this->element());
143         input->setValueForUser(emptyString());
144         input->onSearch();
145         event.setDefaultHandled();
146         return;
147     }
148     TextFieldInputType::handleKeydownEvent(event);
149 }
150
151 void SearchInputType::destroyShadowSubtree()
152 {
153     TextFieldInputType::destroyShadowSubtree();
154     m_resultsButton = nullptr;
155     m_cancelButton = nullptr;
156 }
157
158 void SearchInputType::startSearchEventTimer()
159 {
160     ASSERT(element().renderer());
161     unsigned length = element().innerTextValue().length();
162
163     if (!length) {
164         stopSearchEventTimer();
165         element().onSearch();
166         return;
167     }
168
169     // After typing the first key, we wait 0.5 seconds.
170     // After the second key, 0.4 seconds, then 0.3, then 0.2 from then on.
171     m_searchEventTimer.startOneShot(std::max(200_ms, 600_ms - 100_ms * length));
172 }
173
174 void SearchInputType::stopSearchEventTimer()
175 {
176     m_searchEventTimer.stop();
177 }
178
179 void SearchInputType::searchEventTimerFired()
180 {
181     element().onSearch();
182 }
183
184 bool SearchInputType::searchEventsShouldBeDispatched() const
185 {
186     return element().hasAttributeWithoutSynchronization(incrementalAttr);
187 }
188
189 void SearchInputType::didSetValueByUserEdit()
190 {
191     if (m_cancelButton && is<RenderSearchField>(element().renderer()))
192         downcast<RenderSearchField>(*element().renderer()).updateCancelButtonVisibility();
193     // If the incremental attribute is set, then dispatch the search event
194     if (searchEventsShouldBeDispatched())
195         startSearchEventTimer();
196
197     TextFieldInputType::didSetValueByUserEdit();
198 }
199
200 bool SearchInputType::sizeShouldIncludeDecoration(int, int& preferredSize) const
201 {
202     preferredSize = element().size();
203     return true;
204 }
205
206 float SearchInputType::decorationWidth() const
207 {
208     float width = 0;
209     if (m_resultsButton)
210         width += m_resultsButton->computedStyle()->logicalWidth().value();
211     if (m_cancelButton)
212         width += m_cancelButton->computedStyle()->logicalWidth().value();
213     return width;
214 }
215
216 } // namespace WebCore