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
48namespace WebCore {
49
50using namespace HTMLNames;
51
52WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSearchField);
53
54RenderSearchField::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
62RenderSearchField::~RenderSearchField()
63{
64 // Do not add any code here. Add it to willBeDestroyed() instead.
65}
66
67void RenderSearchField::willBeDestroyed()
68{
69 if (m_searchPopup) {
70 m_searchPopup->popupMenu()->disconnectClient();
71 m_searchPopup = nullptr;
72 }
73
74 RenderTextControlSingleLine::willBeDestroyed();
75}
76
77inline HTMLElement* RenderSearchField::resultsButtonElement() const
78{
79 return inputElement().resultsButtonElement();
80}
81
82inline HTMLElement* RenderSearchField::cancelButtonElement() const
83{
84 return inputElement().cancelButtonElement();
85}
86
87void 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
115void 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
146void RenderSearchField::hidePopup()
147{
148 if (m_searchPopup)
149 m_searchPopup->popupMenu()->hide();
150}
151
152LayoutUnit 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
170void RenderSearchField::updateFromElement()
171{
172 RenderTextControlSingleLine::updateFromElement();
173
174 if (cancelButtonElement())
175 updateCancelButtonVisibility();
176
177 if (m_searchPopupIsVisible)
178 m_searchPopup->popupMenu()->updateFromElement();
179}
180
181void RenderSearchField::updateCancelButtonVisibility() const
182{
183 RenderElement* cancelButtonRenderer = cancelButtonElement()->renderer();
184 if (!cancelButtonRenderer)
185 return;
186
187 const RenderStyle& curStyle = cancelButtonRenderer->style();
188 Visibility 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
197Visibility RenderSearchField::visibilityForCancelButton() const
198{
199 return (style().visibility() == Visibility::Hidden || inputElement().value().isEmpty()) ? Visibility::Hidden : Visibility::Visible;
200}
201
202const AtomicString& RenderSearchField::autosaveName() const
203{
204 return inputElement().attributeWithoutSynchronization(autosaveAttr);
205}
206
207// PopupMenuClient methods
208void 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
229String RenderSearchField::itemText(unsigned listIndex) const
230{
231#if !PLATFORM(IOS_FAMILY)
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_FAMILY)
243 if (static_cast<int>(listIndex) == (size - 1))
244 return searchMenuClearRecentSearchesText();
245#endif
246 return m_recentSearches[listIndex - 1].string;
247}
248
249String RenderSearchField::itemLabel(unsigned) const
250{
251 return String();
252}
253
254String RenderSearchField::itemIcon(unsigned) const
255{
256 return String();
257}
258
259bool RenderSearchField::itemIsEnabled(unsigned listIndex) const
260{
261 if (!listIndex || itemIsSeparator(listIndex))
262 return false;
263 return true;
264}
265
266PopupMenuStyle RenderSearchField::itemStyle(unsigned) const
267{
268 return menuStyle();
269}
270
271PopupMenuStyle RenderSearchField::menuStyle() const
272{
273 return PopupMenuStyle(style().visitedDependentColorWithColorFilter(CSSPropertyColor), style().visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor), style().fontCascade(), style().visibility() == Visibility::Visible,
274 style().display() == DisplayType::None, true, style().textIndent(), style().direction(), isOverride(style().unicodeBidi()), PopupMenuStyle::CustomBackgroundColor);
275}
276
277int 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
284int 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
292LayoutUnit 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
300LayoutUnit 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
310int 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
319int RenderSearchField::selectedIndex() const
320{
321 return -1;
322}
323
324void RenderSearchField::popupDidHide()
325{
326 m_searchPopupIsVisible = false;
327}
328
329bool 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
335bool RenderSearchField::itemIsLabel(unsigned listIndex) const
336{
337 return !listIndex;
338}
339
340bool RenderSearchField::itemIsSelected(unsigned) const
341{
342 return false;
343}
344
345void RenderSearchField::setTextFromItem(unsigned listIndex)
346{
347 inputElement().setValue(itemText(listIndex));
348}
349
350FontSelector* RenderSearchField::fontSelector() const
351{
352 return &document().fontSelector();
353}
354
355HostWindow* RenderSearchField::hostWindow() const
356{
357 return view().frameView().hostWindow();
358}
359
360Ref<Scrollbar> RenderSearchField::createScrollbar(ScrollableArea& scrollableArea, ScrollbarOrientation orientation, ScrollbarControlSize controlSize)
361{
362 bool hasCustomScrollbarStyle = style().hasPseudoStyle(PseudoId::Scrollbar);
363 if (hasCustomScrollbarStyle)
364 return RenderScrollbar::createCustomScrollbar(scrollableArea, orientation, &inputElement());
365 return Scrollbar::createNativeScrollbar(scrollableArea, orientation, controlSize);
366}
367
368}
369