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