/** * 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 "PopupMenu.h" #include "RenderLayer.h" #include "RenderScrollbar.h" #include "RenderTheme.h" #include "RenderView.h" #include "StyleResolver.h" #include "TextControlInnerElements.h" #include namespace WebCore { using namespace HTMLNames; WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSearchField); RenderSearchField::RenderSearchField(HTMLInputElement& element, RenderStyle&& style) : RenderTextControlSingleLine(element, WTFMove(style)) , m_searchPopupIsVisible(false) , m_searchPopup(0) { ASSERT(element.isSearchField()); } RenderSearchField::~RenderSearchField() { // Do not add any code here. Add it to willBeDestroyed() instead. } void RenderSearchField::willBeDestroyed() { if (m_searchPopup) { m_searchPopup->popupMenu()->disconnectClient(); m_searchPopup = nullptr; } RenderTextControlSingleLine::willBeDestroyed(); } 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 (page().usesEphemeralSession()) return; m_recentSearches.removeAllMatching([value] (const RecentSearch& recentSearch) { return recentSearch.string == value; }); RecentSearch recentSearch = { value, WallTime::now() }; m_recentSearches.insert(0, recentSearch); while (static_cast(m_recentSearches.size()) > inputElement().maxResults()) m_recentSearches.removeLast(); const AtomicString& name = autosaveName(); if (!m_searchPopup) m_searchPopup = page().chrome().createSearchPopupMenu(*this); m_searchPopup->saveRecentSearches(name, m_recentSearches); } void RenderSearchField::showPopup() { if (m_searchPopupIsVisible) return; if (!m_searchPopup) m_searchPopup = 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); } FloatPoint absTopLeft = localToAbsolute(FloatPoint(), UseTransforms); IntRect absBounds = absoluteBoundingBoxRectIgnoringTransforms(); absBounds.setLocation(roundedIntPoint(absTopLeft)); m_searchPopup->popupMenu()->show(absBounds, &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(); Visibility buttonVisibility = visibilityForCancelButton(); if (curStyle.visibility() == buttonVisibility) return; auto cancelButtonStyle = RenderStyle::clone(curStyle); cancelButtonStyle.setVisibility(buttonVisibility); cancelButtonRenderer->setStyle(WTFMove(cancelButtonStyle)); } Visibility RenderSearchField::visibilityForCancelButton() const { return (style().visibility() == Visibility::Hidden || inputElement().value().isEmpty()) ? Visibility::Hidden : Visibility::Visible; } const AtomicString& RenderSearchField::autosaveName() const { return inputElement().attributeWithoutSynchronization(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 = 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; } 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().visitedDependentColorWithColorFilter(CSSPropertyColor), style().visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor), style().fontCascade(), style().visibility() == Visibility::Visible, style().display() == DisplayType::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(); } Ref RenderSearchField::createScrollbar(ScrollableArea& scrollableArea, ScrollbarOrientation orientation, ScrollbarControlSize controlSize) { bool hasCustomScrollbarStyle = style().hasPseudoStyle(PseudoId::Scrollbar); if (hasCustomScrollbarStyle) return RenderScrollbar::createCustomScrollbar(scrollableArea, orientation, &inputElement()); return Scrollbar::createNativeScrollbar(scrollableArea, orientation, controlSize); } }