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