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