88fd64abac8045f5bf253721418729fa4a793795
[WebKit-https.git] / Source / WebCore / rendering / RenderSearchField.cpp
1 /**
2  * Copyright (C) 2006, 2007, 2010, 2015 Apple Inc. All rights reserved.
3  *           (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) 
4  * Copyright (C) 2010 Google Inc. All rights reserved.
5  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  */
23
24 #include "config.h"
25 #include "RenderSearchField.h"
26
27 #include "CSSFontSelector.h"
28 #include "CSSValueKeywords.h"
29 #include "Chrome.h"
30 #include "Font.h"
31 #include "Frame.h"
32 #include "FrameSelection.h"
33 #include "FrameView.h"
34 #include "HTMLInputElement.h"
35 #include "HTMLNames.h"
36 #include "HitTestResult.h"
37 #include "LocalizedStrings.h"
38 #include "Page.h"
39 #include "PopupMenu.h"
40 #include "RenderLayer.h"
41 #include "RenderScrollbar.h"
42 #include "RenderTheme.h"
43 #include "RenderView.h"
44 #include "StyleResolver.h"
45 #include "TextControlInnerElements.h"
46 #include <wtf/IsoMallocInlines.h>
47
48 namespace WebCore {
49
50 using namespace HTMLNames;
51
52 WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSearchField);
53
54 RenderSearchField::RenderSearchField(HTMLInputElement& element, RenderStyle&& style)
55     : RenderTextControlSingleLine(element, WTFMove(style))
56     , m_searchPopupIsVisible(false)
57     , m_searchPopup(0)
58 {
59     ASSERT(element.isSearchField());
60 }
61
62 RenderSearchField::~RenderSearchField()
63 {
64     // Do not add any code here. Add it to willBeDestroyed() instead.
65 }
66
67 void RenderSearchField::willBeDestroyed()
68 {
69     if (m_searchPopup) {
70         m_searchPopup->popupMenu()->disconnectClient();
71         m_searchPopup = nullptr;
72     }
73
74     RenderTextControlSingleLine::willBeDestroyed();
75 }
76
77 inline HTMLElement* RenderSearchField::resultsButtonElement() const
78 {
79     return inputElement().resultsButtonElement();
80 }
81
82 inline HTMLElement* RenderSearchField::cancelButtonElement() const
83 {
84     return inputElement().cancelButtonElement();
85 }
86
87 void RenderSearchField::addSearchResult()
88 {
89     if (inputElement().maxResults() <= 0)
90         return;
91
92     String value = inputElement().value();
93     if (value.isEmpty())
94         return;
95
96     if (page().usesEphemeralSession())
97         return;
98
99     m_recentSearches.removeAllMatching([value] (const RecentSearch& recentSearch) {
100         return recentSearch.string == value;
101     });
102
103     RecentSearch recentSearch = { value, WallTime::now() };
104     m_recentSearches.insert(0, recentSearch);
105     while (static_cast<int>(m_recentSearches.size()) > inputElement().maxResults())
106         m_recentSearches.removeLast();
107
108     const AtomicString& name = autosaveName();
109     if (!m_searchPopup)
110         m_searchPopup = page().chrome().createSearchPopupMenu(*this);
111
112     m_searchPopup->saveRecentSearches(name, m_recentSearches);
113 }
114
115 void RenderSearchField::showPopup()
116 {
117     if (m_searchPopupIsVisible)
118         return;
119
120     if (!m_searchPopup)
121         m_searchPopup = page().chrome().createSearchPopupMenu(*this);
122
123     if (!m_searchPopup->enabled())
124         return;
125
126     m_searchPopupIsVisible = true;
127
128     const AtomicString& name = autosaveName();
129     m_searchPopup->loadRecentSearches(name, m_recentSearches);
130
131     // Trim the recent searches list if the maximum size has changed since we last saved.
132     if (static_cast<int>(m_recentSearches.size()) > inputElement().maxResults()) {
133         do {
134             m_recentSearches.removeLast();
135         } while (static_cast<int>(m_recentSearches.size()) > inputElement().maxResults());
136
137         m_searchPopup->saveRecentSearches(name, m_recentSearches);
138     }
139
140     FloatPoint absTopLeft = localToAbsolute(FloatPoint(), UseTransforms);
141     IntRect absBounds = absoluteBoundingBoxRectIgnoringTransforms();
142     absBounds.setLocation(roundedIntPoint(absTopLeft));
143     m_searchPopup->popupMenu()->show(absBounds, &view().frameView(), -1);
144 }
145
146 void RenderSearchField::hidePopup()
147 {
148     if (m_searchPopup)
149         m_searchPopup->popupMenu()->hide();
150 }
151
152 LayoutUnit RenderSearchField::computeControlLogicalHeight(LayoutUnit lineHeight, LayoutUnit nonContentHeight) const
153 {
154     HTMLElement* resultsButton = resultsButtonElement();
155     if (RenderBox* resultsRenderer = resultsButton ? resultsButton->renderBox() : 0) {
156         resultsRenderer->updateLogicalHeight();
157         nonContentHeight = std::max(nonContentHeight, resultsRenderer->borderAndPaddingLogicalHeight() + resultsRenderer->marginLogicalHeight());
158         lineHeight = std::max(lineHeight, resultsRenderer->logicalHeight());
159     }
160     HTMLElement* cancelButton = cancelButtonElement();
161     if (RenderBox* cancelRenderer = cancelButton ? cancelButton->renderBox() : 0) {
162         cancelRenderer->updateLogicalHeight();
163         nonContentHeight = std::max(nonContentHeight, cancelRenderer->borderAndPaddingLogicalHeight() + cancelRenderer->marginLogicalHeight());
164         lineHeight = std::max(lineHeight, cancelRenderer->logicalHeight());
165     }
166
167     return lineHeight + nonContentHeight;
168 }
169
170 void RenderSearchField::updateFromElement()
171 {
172     RenderTextControlSingleLine::updateFromElement();
173
174     if (cancelButtonElement())
175         updateCancelButtonVisibility();
176
177     if (m_searchPopupIsVisible)
178         m_searchPopup->popupMenu()->updateFromElement();
179 }
180
181 void RenderSearchField::updateCancelButtonVisibility() const
182 {
183     RenderElement* cancelButtonRenderer = cancelButtonElement()->renderer();
184     if (!cancelButtonRenderer)
185         return;
186
187     const RenderStyle& curStyle = cancelButtonRenderer->style();
188     EVisibility buttonVisibility = visibilityForCancelButton();
189     if (curStyle.visibility() == buttonVisibility)
190         return;
191
192     auto cancelButtonStyle = RenderStyle::clone(curStyle);
193     cancelButtonStyle.setVisibility(buttonVisibility);
194     cancelButtonRenderer->setStyle(WTFMove(cancelButtonStyle));
195 }
196
197 EVisibility RenderSearchField::visibilityForCancelButton() const
198 {
199     return (style().visibility() == HIDDEN || inputElement().value().isEmpty()) ? HIDDEN : VISIBLE;
200 }
201
202 const AtomicString& RenderSearchField::autosaveName() const
203 {
204     return inputElement().attributeWithoutSynchronization(autosaveAttr);
205 }
206
207 // PopupMenuClient methods
208 void RenderSearchField::valueChanged(unsigned listIndex, bool fireEvents)
209 {
210     ASSERT(static_cast<int>(listIndex) < listSize());
211     if (static_cast<int>(listIndex) == (listSize() - 1)) {
212         if (fireEvents) {
213             m_recentSearches.clear();
214             const AtomicString& name = autosaveName();
215             if (!name.isEmpty()) {
216                 if (!m_searchPopup)
217                     m_searchPopup = page().chrome().createSearchPopupMenu(*this);
218                 m_searchPopup->saveRecentSearches(name, m_recentSearches);
219             }
220         }
221     } else {
222         inputElement().setValue(itemText(listIndex));
223         if (fireEvents)
224             inputElement().onSearch();
225         inputElement().select();
226     }
227 }
228
229 String RenderSearchField::itemText(unsigned listIndex) const
230 {
231 #if !PLATFORM(IOS)
232     int size = listSize();
233     if (size == 1) {
234         ASSERT(!listIndex);
235         return searchMenuNoRecentSearchesText();
236     }
237     if (!listIndex)
238         return searchMenuRecentSearchesText();
239 #endif
240     if (itemIsSeparator(listIndex))
241         return String();
242 #if !PLATFORM(IOS)
243     if (static_cast<int>(listIndex) == (size - 1))
244         return searchMenuClearRecentSearchesText();
245 #endif
246     return m_recentSearches[listIndex - 1].string;
247 }
248
249 String RenderSearchField::itemLabel(unsigned) const
250 {
251     return String();
252 }
253
254 String RenderSearchField::itemIcon(unsigned) const
255 {
256     return String();
257 }
258
259 bool RenderSearchField::itemIsEnabled(unsigned listIndex) const
260 {
261      if (!listIndex || itemIsSeparator(listIndex))
262         return false;
263     return true;
264 }
265
266 PopupMenuStyle RenderSearchField::itemStyle(unsigned) const
267 {
268     return menuStyle();
269 }
270
271 PopupMenuStyle RenderSearchField::menuStyle() const
272 {
273     return PopupMenuStyle(style().visitedDependentColor(CSSPropertyColor), style().visitedDependentColor(CSSPropertyBackgroundColor), style().fontCascade(), style().visibility() == VISIBLE,
274         style().display() == NONE, true, style().textIndent(), style().direction(), isOverride(style().unicodeBidi()), PopupMenuStyle::CustomBackgroundColor);
275 }
276
277 int RenderSearchField::clientInsetLeft() const
278 {
279     // Inset the menu by the radius of the cap on the left so that
280     // it only runs along the straight part of the bezel.
281     return height() / 2;
282 }
283
284 int RenderSearchField::clientInsetRight() const
285 {
286     // Inset the menu by the radius of the cap on the right so that
287     // it only runs along the straight part of the bezel (unless it needs
288     // to be wider).
289     return height() / 2;
290 }
291
292 LayoutUnit RenderSearchField::clientPaddingLeft() const
293 {
294     LayoutUnit padding = paddingLeft();
295     if (RenderBox* box = innerBlockElement() ? innerBlockElement()->renderBox() : 0)
296         padding += box->x();
297     return padding;
298 }
299
300 LayoutUnit RenderSearchField::clientPaddingRight() const
301 {
302     LayoutUnit padding = paddingRight();
303     if (RenderBox* containerBox = containerElement() ? containerElement()->renderBox() : 0) {
304         if (RenderBox* innerBlockBox = innerBlockElement() ? innerBlockElement()->renderBox() : 0)
305             padding += containerBox->width() - (innerBlockBox->x() + innerBlockBox->width());
306     }
307     return padding;
308 }
309
310 int RenderSearchField::listSize() const
311 {
312     // If there are no recent searches, then our menu will have 1 "No recent searches" item.
313     if (!m_recentSearches.size())
314         return 1;
315     // Otherwise, leave room in the menu for a header, a separator, and the "Clear recent searches" item.
316     return m_recentSearches.size() + 3;
317 }
318
319 int RenderSearchField::selectedIndex() const
320 {
321     return -1;
322 }
323
324 void RenderSearchField::popupDidHide()
325 {
326     m_searchPopupIsVisible = false;
327 }
328
329 bool RenderSearchField::itemIsSeparator(unsigned listIndex) const
330 {
331     // The separator will be the second to last item in our list.
332     return static_cast<int>(listIndex) == (listSize() - 2);
333 }
334
335 bool RenderSearchField::itemIsLabel(unsigned listIndex) const
336 {
337     return !listIndex;
338 }
339
340 bool RenderSearchField::itemIsSelected(unsigned) const
341 {
342     return false;
343 }
344
345 void RenderSearchField::setTextFromItem(unsigned listIndex)
346 {
347     inputElement().setValue(itemText(listIndex));
348 }
349
350 FontSelector* RenderSearchField::fontSelector() const
351 {
352     return &document().fontSelector();
353 }
354
355 HostWindow* RenderSearchField::hostWindow() const
356 {
357     return view().frameView().hostWindow();
358 }
359
360 Ref<Scrollbar> RenderSearchField::createScrollbar(ScrollableArea& scrollableArea, ScrollbarOrientation orientation, ScrollbarControlSize controlSize)
361 {
362     bool hasCustomScrollbarStyle = style().hasPseudoStyle(SCROLLBAR);
363     if (hasCustomScrollbarStyle)
364         return RenderScrollbar::createCustomScrollbar(scrollableArea, orientation, &inputElement());
365     return Scrollbar::createNativeScrollbar(scrollableArea, orientation, controlSize);
366 }
367
368 }