Unreviewed, rolling out r191113.
[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 "PlatformKeyboardEvent.h"
40 #include "PopupMenu.h"
41 #include "RenderLayer.h"
42 #include "RenderScrollbar.h"
43 #include "RenderTheme.h"
44 #include "RenderView.h"
45 #include "Settings.h"
46 #include "StyleResolver.h"
47 #include "TextControlInnerElements.h"
48
49 namespace WebCore {
50
51 using namespace HTMLNames;
52
53 RenderSearchField::RenderSearchField(HTMLInputElement& element, Ref<RenderStyle>&& style)
54     : RenderTextControlSingleLine(element, WTF::move(style))
55     , m_searchPopupIsVisible(false)
56     , m_searchPopup(0)
57 {
58     ASSERT(element.isSearchField());
59 }
60
61 RenderSearchField::~RenderSearchField()
62 {
63     if (m_searchPopup) {
64         m_searchPopup->popupMenu()->disconnectClient();
65         m_searchPopup = nullptr;
66     }
67 }
68
69 inline HTMLElement* RenderSearchField::resultsButtonElement() const
70 {
71     return inputElement().resultsButtonElement();
72 }
73
74 inline HTMLElement* RenderSearchField::cancelButtonElement() const
75 {
76     return inputElement().cancelButtonElement();
77 }
78
79 void RenderSearchField::addSearchResult()
80 {
81     if (inputElement().maxResults() <= 0)
82         return;
83
84     String value = inputElement().value();
85     if (value.isEmpty())
86         return;
87
88     if (frame().page()->usesEphemeralSession())
89         return;
90
91     m_recentSearches.removeAllMatching([value] (const RecentSearch& recentSearch) {
92         return recentSearch.string == value;
93     });
94
95     RecentSearch recentSearch = { value, std::chrono::system_clock::now() };
96     m_recentSearches.insert(0, recentSearch);
97     while (static_cast<int>(m_recentSearches.size()) > inputElement().maxResults())
98         m_recentSearches.removeLast();
99
100     const AtomicString& name = autosaveName();
101     if (!m_searchPopup)
102         m_searchPopup = document().page()->chrome().createSearchPopupMenu(this);
103
104     m_searchPopup->saveRecentSearches(name, m_recentSearches);
105 }
106
107 void RenderSearchField::showPopup()
108 {
109     if (m_searchPopupIsVisible)
110         return;
111
112     if (!m_searchPopup)
113         m_searchPopup = document().page()->chrome().createSearchPopupMenu(this);
114
115     if (!m_searchPopup->enabled())
116         return;
117
118     m_searchPopupIsVisible = true;
119
120     const AtomicString& name = autosaveName();
121     m_searchPopup->loadRecentSearches(name, m_recentSearches);
122
123     // Trim the recent searches list if the maximum size has changed since we last saved.
124     if (static_cast<int>(m_recentSearches.size()) > inputElement().maxResults()) {
125         do {
126             m_recentSearches.removeLast();
127         } while (static_cast<int>(m_recentSearches.size()) > inputElement().maxResults());
128
129         m_searchPopup->saveRecentSearches(name, m_recentSearches);
130     }
131
132     m_searchPopup->popupMenu()->show(snappedIntRect(absoluteBoundingBoxRect()), &view().frameView(), -1);
133 }
134
135 void RenderSearchField::hidePopup()
136 {
137     if (m_searchPopup)
138         m_searchPopup->popupMenu()->hide();
139 }
140
141 LayoutUnit RenderSearchField::computeControlLogicalHeight(LayoutUnit lineHeight, LayoutUnit nonContentHeight) const
142 {
143     HTMLElement* resultsButton = resultsButtonElement();
144     if (RenderBox* resultsRenderer = resultsButton ? resultsButton->renderBox() : 0) {
145         resultsRenderer->updateLogicalHeight();
146         nonContentHeight = std::max(nonContentHeight, resultsRenderer->borderAndPaddingLogicalHeight() + resultsRenderer->marginLogicalHeight());
147         lineHeight = std::max(lineHeight, resultsRenderer->logicalHeight());
148     }
149     HTMLElement* cancelButton = cancelButtonElement();
150     if (RenderBox* cancelRenderer = cancelButton ? cancelButton->renderBox() : 0) {
151         cancelRenderer->updateLogicalHeight();
152         nonContentHeight = std::max(nonContentHeight, cancelRenderer->borderAndPaddingLogicalHeight() + cancelRenderer->marginLogicalHeight());
153         lineHeight = std::max(lineHeight, cancelRenderer->logicalHeight());
154     }
155
156     return lineHeight + nonContentHeight;
157 }
158
159 void RenderSearchField::updateFromElement()
160 {
161     RenderTextControlSingleLine::updateFromElement();
162
163     if (cancelButtonElement())
164         updateCancelButtonVisibility();
165
166     if (m_searchPopupIsVisible)
167         m_searchPopup->popupMenu()->updateFromElement();
168 }
169
170 void RenderSearchField::updateCancelButtonVisibility() const
171 {
172     RenderElement* cancelButtonRenderer = cancelButtonElement()->renderer();
173     if (!cancelButtonRenderer)
174         return;
175
176     const RenderStyle& curStyle = cancelButtonRenderer->style();
177     EVisibility buttonVisibility = visibilityForCancelButton();
178     if (curStyle.visibility() == buttonVisibility)
179         return;
180
181     auto cancelButtonStyle = RenderStyle::clone(&curStyle);
182     cancelButtonStyle.get().setVisibility(buttonVisibility);
183     cancelButtonRenderer->setStyle(WTF::move(cancelButtonStyle));
184 }
185
186 EVisibility RenderSearchField::visibilityForCancelButton() const
187 {
188     return (style().visibility() == HIDDEN || inputElement().value().isEmpty()) ? HIDDEN : VISIBLE;
189 }
190
191 const AtomicString& RenderSearchField::autosaveName() const
192 {
193     return inputElement().fastGetAttribute(autosaveAttr);
194 }
195
196 // PopupMenuClient methods
197 void RenderSearchField::valueChanged(unsigned listIndex, bool fireEvents)
198 {
199     ASSERT(static_cast<int>(listIndex) < listSize());
200     if (static_cast<int>(listIndex) == (listSize() - 1)) {
201         if (fireEvents) {
202             m_recentSearches.clear();
203             const AtomicString& name = autosaveName();
204             if (!name.isEmpty()) {
205                 if (!m_searchPopup)
206                     m_searchPopup = document().page()->chrome().createSearchPopupMenu(this);
207                 m_searchPopup->saveRecentSearches(name, m_recentSearches);
208             }
209         }
210     } else {
211         inputElement().setValue(itemText(listIndex));
212         if (fireEvents)
213             inputElement().onSearch();
214         inputElement().select();
215     }
216 }
217
218 String RenderSearchField::itemText(unsigned listIndex) const
219 {
220 #if !PLATFORM(IOS)
221     int size = listSize();
222     if (size == 1) {
223         ASSERT(!listIndex);
224         return searchMenuNoRecentSearchesText();
225     }
226     if (!listIndex)
227         return searchMenuRecentSearchesText();
228 #endif
229     if (itemIsSeparator(listIndex))
230         return String();
231 #if !PLATFORM(IOS)
232     if (static_cast<int>(listIndex) == (size - 1))
233         return searchMenuClearRecentSearchesText();
234 #endif
235     return m_recentSearches[listIndex - 1].string;
236 }
237
238 String RenderSearchField::itemLabel(unsigned) const
239 {
240     return String();
241 }
242
243 String RenderSearchField::itemIcon(unsigned) const
244 {
245     return String();
246 }
247
248 bool RenderSearchField::itemIsEnabled(unsigned listIndex) const
249 {
250      if (!listIndex || itemIsSeparator(listIndex))
251         return false;
252     return true;
253 }
254
255 PopupMenuStyle RenderSearchField::itemStyle(unsigned) const
256 {
257     return menuStyle();
258 }
259
260 PopupMenuStyle RenderSearchField::menuStyle() const
261 {
262     return PopupMenuStyle(style().visitedDependentColor(CSSPropertyColor), style().visitedDependentColor(CSSPropertyBackgroundColor), style().fontCascade(), style().visibility() == VISIBLE,
263         style().display() == NONE, true, style().textIndent(), style().direction(), isOverride(style().unicodeBidi()), PopupMenuStyle::CustomBackgroundColor);
264 }
265
266 int RenderSearchField::clientInsetLeft() const
267 {
268     // Inset the menu by the radius of the cap on the left so that
269     // it only runs along the straight part of the bezel.
270     return height() / 2;
271 }
272
273 int RenderSearchField::clientInsetRight() const
274 {
275     // Inset the menu by the radius of the cap on the right so that
276     // it only runs along the straight part of the bezel (unless it needs
277     // to be wider).
278     return height() / 2;
279 }
280
281 LayoutUnit RenderSearchField::clientPaddingLeft() const
282 {
283     LayoutUnit padding = paddingLeft();
284     if (RenderBox* box = innerBlockElement() ? innerBlockElement()->renderBox() : 0)
285         padding += box->x();
286     return padding;
287 }
288
289 LayoutUnit RenderSearchField::clientPaddingRight() const
290 {
291     LayoutUnit padding = paddingRight();
292     if (RenderBox* containerBox = containerElement() ? containerElement()->renderBox() : 0) {
293         if (RenderBox* innerBlockBox = innerBlockElement() ? innerBlockElement()->renderBox() : 0)
294             padding += containerBox->width() - (innerBlockBox->x() + innerBlockBox->width());
295     }
296     return padding;
297 }
298
299 int RenderSearchField::listSize() const
300 {
301     // If there are no recent searches, then our menu will have 1 "No recent searches" item.
302     if (!m_recentSearches.size())
303         return 1;
304     // Otherwise, leave room in the menu for a header, a separator, and the "Clear recent searches" item.
305     return m_recentSearches.size() + 3;
306 }
307
308 int RenderSearchField::selectedIndex() const
309 {
310     return -1;
311 }
312
313 void RenderSearchField::popupDidHide()
314 {
315     m_searchPopupIsVisible = false;
316 }
317
318 bool RenderSearchField::itemIsSeparator(unsigned listIndex) const
319 {
320     // The separator will be the second to last item in our list.
321     return static_cast<int>(listIndex) == (listSize() - 2);
322 }
323
324 bool RenderSearchField::itemIsLabel(unsigned listIndex) const
325 {
326     return !listIndex;
327 }
328
329 bool RenderSearchField::itemIsSelected(unsigned) const
330 {
331     return false;
332 }
333
334 void RenderSearchField::setTextFromItem(unsigned listIndex)
335 {
336     inputElement().setValue(itemText(listIndex));
337 }
338
339 FontSelector* RenderSearchField::fontSelector() const
340 {
341     return &document().fontSelector();
342 }
343
344 HostWindow* RenderSearchField::hostWindow() const
345 {
346     return view().frameView().hostWindow();
347 }
348
349 PassRefPtr<Scrollbar> RenderSearchField::createScrollbar(ScrollableArea& scrollableArea, ScrollbarOrientation orientation, ScrollbarControlSize controlSize)
350 {
351     RefPtr<Scrollbar> widget;
352     bool hasCustomScrollbarStyle = style().hasPseudoStyle(SCROLLBAR);
353     if (hasCustomScrollbarStyle)
354         widget = RenderScrollbar::createCustomScrollbar(scrollableArea, orientation, &inputElement());
355     else
356         widget = Scrollbar::createNativeScrollbar(scrollableArea, orientation, controlSize);
357     return widget.release();
358 }
359
360 LayoutUnit RenderSearchField::computeLogicalHeightLimit() const
361 {
362     return logicalHeight();
363 }
364
365 void RenderSearchField::centerContainerIfNeeded(RenderBox* containerRenderer) const
366 {
367     if (!containerRenderer)
368         return;
369
370     if (containerRenderer->logicalHeight() <= contentLogicalHeight())
371         return;
372
373     // A quirk for find-in-page box on Safari Windows.
374     // http://webkit.org/b/63157
375     centerRenderer(*containerRenderer);
376 }
377
378 }