/** * Copyright (C) 2006, 2007, 2010, 2015 Apple Inc. All rights reserved. * (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) * Copyright (C) 2010 Google Inc. All rights reserved. * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "config.h" #include "RenderSearchField.h" #include "CSSFontSelector.h" #include "CSSValueKeywords.h" #include "Chrome.h" #include "Font.h" #include "Frame.h" #include "FrameSelection.h" #include "FrameView.h" #include "HTMLInputElement.h" #include "HTMLNames.h" #include "HitTestResult.h" #include "LocalizedStrings.h" #include "Page.h" #include "PlatformKeyboardEvent.h" #include "RenderLayer.h" #include "RenderScrollbar.h" #include "RenderTheme.h" #include "RenderView.h" #include "SearchPopupMenu.h" #include "Settings.h" #include "StyleResolver.h" #include "TextControlInnerElements.h" namespace WebCore { using namespace HTMLNames; RenderSearchField::RenderSearchField(HTMLInputElement& element, Ref&& style) : RenderTextControlSingleLine(element, WTF::move(style)) , m_searchPopupIsVisible(false) , m_searchPopup(0) { ASSERT(element.isSearchField()); } RenderSearchField::~RenderSearchField() { if (m_searchPopup) { m_searchPopup->popupMenu()->disconnectClient(); m_searchPopup = nullptr; } } inline HTMLElement* RenderSearchField::resultsButtonElement() const { return inputElement().resultsButtonElement(); } inline HTMLElement* RenderSearchField::cancelButtonElement() const { return inputElement().cancelButtonElement(); } void RenderSearchField::addSearchResult() { if (inputElement().maxResults() <= 0) return; String value = inputElement().value(); if (value.isEmpty()) return; if (frame().page()->usesEphemeralSession()) return; m_recentSearches.removeAll(value); m_recentSearches.insert(0, value); while (static_cast(m_recentSearches.size()) > inputElement().maxResults()) m_recentSearches.removeLast(); const AtomicString& name = autosaveName(); if (!m_searchPopup) m_searchPopup = document().page()->chrome().createSearchPopupMenu(this); m_searchPopup->saveRecentSearches(name, m_recentSearches); } void RenderSearchField::showPopup() { if (m_searchPopupIsVisible) return; if (!m_searchPopup) m_searchPopup = document().page()->chrome().createSearchPopupMenu(this); if (!m_searchPopup->enabled()) return; m_searchPopupIsVisible = true; const AtomicString& name = autosaveName(); m_searchPopup->loadRecentSearches(name, m_recentSearches); // Trim the recent searches list if the maximum size has changed since we last saved. if (static_cast(m_recentSearches.size()) > inputElement().maxResults()) { do { m_recentSearches.removeLast(); } while (static_cast(m_recentSearches.size()) > inputElement().maxResults()); m_searchPopup->saveRecentSearches(name, m_recentSearches); } m_searchPopup->popupMenu()->show(snappedIntRect(absoluteBoundingBoxRect()), &view().frameView(), -1); } void RenderSearchField::hidePopup() { if (m_searchPopup) m_searchPopup->popupMenu()->hide(); } LayoutUnit RenderSearchField::computeControlLogicalHeight(LayoutUnit lineHeight, LayoutUnit nonContentHeight) const { HTMLElement* resultsButton = resultsButtonElement(); if (RenderBox* resultsRenderer = resultsButton ? resultsButton->renderBox() : 0) { resultsRenderer->updateLogicalHeight(); nonContentHeight = std::max(nonContentHeight, resultsRenderer->borderAndPaddingLogicalHeight() + resultsRenderer->marginLogicalHeight()); lineHeight = std::max(lineHeight, resultsRenderer->logicalHeight()); } HTMLElement* cancelButton = cancelButtonElement(); if (RenderBox* cancelRenderer = cancelButton ? cancelButton->renderBox() : 0) { cancelRenderer->updateLogicalHeight(); nonContentHeight = std::max(nonContentHeight, cancelRenderer->borderAndPaddingLogicalHeight() + cancelRenderer->marginLogicalHeight()); lineHeight = std::max(lineHeight, cancelRenderer->logicalHeight()); } return lineHeight + nonContentHeight; } void RenderSearchField::updateFromElement() { RenderTextControlSingleLine::updateFromElement(); if (cancelButtonElement()) updateCancelButtonVisibility(); if (m_searchPopupIsVisible) m_searchPopup->popupMenu()->updateFromElement(); } void RenderSearchField::updateCancelButtonVisibility() const { RenderElement* cancelButtonRenderer = cancelButtonElement()->renderer(); if (!cancelButtonRenderer) return; const RenderStyle& curStyle = cancelButtonRenderer->style(); EVisibility buttonVisibility = visibilityForCancelButton(); if (curStyle.visibility() == buttonVisibility) return; auto cancelButtonStyle = RenderStyle::clone(&curStyle); cancelButtonStyle.get().setVisibility(buttonVisibility); cancelButtonRenderer->setStyle(WTF::move(cancelButtonStyle)); } EVisibility RenderSearchField::visibilityForCancelButton() const { return (style().visibility() == HIDDEN || inputElement().value().isEmpty()) ? HIDDEN : VISIBLE; } const AtomicString& RenderSearchField::autosaveName() const { return inputElement().fastGetAttribute(autosaveAttr); } // PopupMenuClient methods void RenderSearchField::valueChanged(unsigned listIndex, bool fireEvents) { ASSERT(static_cast(listIndex) < listSize()); if (static_cast(listIndex) == (listSize() - 1)) { if (fireEvents) { m_recentSearches.clear(); const AtomicString& name = autosaveName(); if (!name.isEmpty()) { if (!m_searchPopup) m_searchPopup = document().page()->chrome().createSearchPopupMenu(this); m_searchPopup->saveRecentSearches(name, m_recentSearches); } } } else { inputElement().setValue(itemText(listIndex)); if (fireEvents) inputElement().onSearch(); inputElement().select(); } } String RenderSearchField::itemText(unsigned listIndex) const { #if !PLATFORM(IOS) int size = listSize(); if (size == 1) { ASSERT(!listIndex); return searchMenuNoRecentSearchesText(); } if (!listIndex) return searchMenuRecentSearchesText(); #endif if (itemIsSeparator(listIndex)) return String(); #if !PLATFORM(IOS) if (static_cast(listIndex) == (size - 1)) return searchMenuClearRecentSearchesText(); #endif return m_recentSearches[listIndex - 1]; } String RenderSearchField::itemLabel(unsigned) const { return String(); } String RenderSearchField::itemIcon(unsigned) const { return String(); } bool RenderSearchField::itemIsEnabled(unsigned listIndex) const { if (!listIndex || itemIsSeparator(listIndex)) return false; return true; } PopupMenuStyle RenderSearchField::itemStyle(unsigned) const { return menuStyle(); } PopupMenuStyle RenderSearchField::menuStyle() const { return PopupMenuStyle(style().visitedDependentColor(CSSPropertyColor), style().visitedDependentColor(CSSPropertyBackgroundColor), style().fontCascade(), style().visibility() == VISIBLE, style().display() == NONE, true, style().textIndent(), style().direction(), isOverride(style().unicodeBidi()), PopupMenuStyle::CustomBackgroundColor); } int RenderSearchField::clientInsetLeft() const { // Inset the menu by the radius of the cap on the left so that // it only runs along the straight part of the bezel. return height() / 2; } int RenderSearchField::clientInsetRight() const { // Inset the menu by the radius of the cap on the right so that // it only runs along the straight part of the bezel (unless it needs // to be wider). return height() / 2; } LayoutUnit RenderSearchField::clientPaddingLeft() const { LayoutUnit padding = paddingLeft(); if (RenderBox* box = innerBlockElement() ? innerBlockElement()->renderBox() : 0) padding += box->x(); return padding; } LayoutUnit RenderSearchField::clientPaddingRight() const { LayoutUnit padding = paddingRight(); if (RenderBox* containerBox = containerElement() ? containerElement()->renderBox() : 0) { if (RenderBox* innerBlockBox = innerBlockElement() ? innerBlockElement()->renderBox() : 0) padding += containerBox->width() - (innerBlockBox->x() + innerBlockBox->width()); } return padding; } int RenderSearchField::listSize() const { // If there are no recent searches, then our menu will have 1 "No recent searches" item. if (!m_recentSearches.size()) return 1; // Otherwise, leave room in the menu for a header, a separator, and the "Clear recent searches" item. return m_recentSearches.size() + 3; } int RenderSearchField::selectedIndex() const { return -1; } void RenderSearchField::popupDidHide() { m_searchPopupIsVisible = false; } bool RenderSearchField::itemIsSeparator(unsigned listIndex) const { // The separator will be the second to last item in our list. return static_cast(listIndex) == (listSize() - 2); } bool RenderSearchField::itemIsLabel(unsigned listIndex) const { return !listIndex; } bool RenderSearchField::itemIsSelected(unsigned) const { return false; } void RenderSearchField::setTextFromItem(unsigned listIndex) { inputElement().setValue(itemText(listIndex)); } FontSelector* RenderSearchField::fontSelector() const { return &document().fontSelector(); } HostWindow* RenderSearchField::hostWindow() const { return view().frameView().hostWindow(); } PassRefPtr RenderSearchField::createScrollbar(ScrollableArea& scrollableArea, ScrollbarOrientation orientation, ScrollbarControlSize controlSize) { RefPtr widget; bool hasCustomScrollbarStyle = style().hasPseudoStyle(SCROLLBAR); if (hasCustomScrollbarStyle) widget = RenderScrollbar::createCustomScrollbar(scrollableArea, orientation, &inputElement()); else widget = Scrollbar::createNativeScrollbar(scrollableArea, orientation, controlSize); return widget.release(); } LayoutUnit RenderSearchField::computeLogicalHeightLimit() const { return logicalHeight(); } void RenderSearchField::centerContainerIfNeeded(RenderBox* containerRenderer) const { if (!containerRenderer) return; if (containerRenderer->logicalHeight() <= contentLogicalHeight()) return; // A quirk for find-in-page box on Safari Windows. // http://webkit.org/b/63157 centerRenderer(*containerRenderer); } }