1/* 2 * Copyright (C) 2010 Google Inc. All rights reserved. 3 * Copyright (C) 2012 Research In Motion Limited. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32#include "config.h" 33#include "SuggestionBoxHandler.h" 34 35#include "CSSPropertyNames.h" 36#include "CSSValueKeywords.h" 37#include "DOMTokenList.h" 38#include "ElementShadow.h" 39#include "ExceptionCodePlaceholder.h" 40#include "HTMLCollection.h" 41#include "HTMLDataListElement.h" 42#include "HTMLDivElement.h" 43#include "HTMLInputElement.h" 44#include "HTMLNames.h" 45#include "HTMLOptionElement.h" 46#include "HTMLSpanElement.h" 47#include "RenderBlock.h" 48#include "RenderObject.h" 49#include "ShadowRoot.h" 50#include "StyleResolver.h" 51#include "SuggestionBoxElement.h" 52#include "Text.h" 53 54#include <BlackBerryPlatformLog.h> 55#include <wtf/PassOwnPtr.h> 56 57namespace WebCore { 58 59using namespace HTMLNames; 60 61SuggestionBoxHandler::SuggestionBoxHandler(HTMLInputElement* element) 62 : m_inputElement(element) 63{ 64} 65 66SuggestionBoxHandler::~SuggestionBoxHandler() 67{ 68 hideDropdownBox(); 69} 70 71PassOwnPtr<SuggestionBoxHandler> SuggestionBoxHandler::create(HTMLInputElement* element) 72{ 73 return adoptPtr(new SuggestionBoxHandler(element)); 74} 75 76void SuggestionBoxHandler::setInputElementAndUpdateDisplay(HTMLInputElement* element, bool allowEmptyPrefix) 77{ 78 hideDropdownBox(); 79 m_inputElement = element; 80 showDropdownBox(allowEmptyPrefix); 81} 82 83void SuggestionBoxHandler::showDropdownBox(bool allowEmptyPrefix) 84{ 85 // Clearing suggestions and parse again 86 m_suggestions.clear(); 87 parseSuggestions(allowEmptyPrefix); 88 89 // Don't bother building the tree if suggestion size is 0. 90 // We need to call insertSuggestions to clear previous suggestions 91 // in the DOM if suggestion size is 0. 92 if (!m_dropdownBox) { 93 if (!m_suggestions.size()) 94 return; 95 buildDropdownBoxTree(); 96 } 97 98 insertSuggestionsToDropdownBox(); 99} 100 101void SuggestionBoxHandler::hideDropdownBox() 102{ 103 if (m_dropdownBox) { 104 if (ShadowRoot* shadowRoot = m_inputElement->userAgentShadowRoot()) { 105 ExceptionCode ec; 106 shadowRoot->removeChild(m_dropdownBox.get(), ec); 107 } 108 m_dropdownBox = 0; 109 } 110 m_suggestions.clear(); 111} 112 113void SuggestionBoxHandler::changeInputElementInnerTextValue(String& text) 114{ 115 m_inputElement->setInnerTextValue(text); 116} 117 118void SuggestionBoxHandler::parseSuggestions(bool allowEmptyPrefix) 119{ 120 // Return if this handler doesn't have an input element to attach to 121 if (!m_inputElement) 122 return; 123 124 // List attribute exists, check if datalist exists. 125 HTMLDataListElement* dList = m_inputElement->dataList(); 126 if (!dList) 127 return; 128 129 RefPtr<HTMLCollection> optionsList = dList->options(); 130 String prefix = m_inputElement->innerTextValue(); 131 132 // Exit if: 133 // 1) If length of nodes in datalist is empty, return. 134 // 2) If we do not allow empty prefixes and prefix is null 135 if (!optionsList->length() || (!allowEmptyPrefix && prefix.isEmpty())) { 136 hideDropdownBox(); 137 return; 138 } 139 140 // Prefix match the inner text value of the input against the candidate suggestions. 141 // Make sure the length of the suggestion is smaller than max length 142 for (unsigned i = 0; i < optionsList->length(); i++) { 143 HTMLOptionElement* target = toHTMLOptionElement((optionsList->item(i))); 144 if (!m_inputElement->isValidValue(target->value())) 145 continue; 146 String candidateString = m_inputElement->sanitizeValue(target->value()); 147 if (!candidateString.findIgnoringCase(prefix) && candidateString.length() <= (unsigned) m_inputElement->maxLength()) 148 m_suggestions.append(candidateString); 149 } 150} 151 152void SuggestionBoxHandler::insertSuggestionsToDropdownBox() 153{ 154 // In case someone is calling this function without successfully building the dropdownBox tree 155 ASSERT(m_dropdownBox); 156 157 Document* document = m_dropdownBox->document(); 158 159 // Clear the dropdown box's previous values 160 m_dropdownBox->removeChildren(); 161 162 int prefixIndex = m_inputElement->innerTextValue().length(); 163 164 unsigned total = m_suggestions.size(); 165 for (unsigned i = 0; i < total; ++i) { 166 RefPtr<HTMLElement> suggestionItem = SuggestionBoxElement::create(this, m_suggestions[i], document); 167 if (i < total - 1) 168 suggestionItem->setPseudo("-webkit-suggestion-dropdown-box-item"); 169 else 170 suggestionItem->setPseudo("-webkit-suggestion-dropdown-box-item-last"); 171 172 // Append the part where it prefix matched 173 RefPtr<HTMLSpanElement> prefixElement = HTMLSpanElement::create(HTMLNames::spanTag, document); 174 prefixElement->appendChild(Text::create(document, m_suggestions[i].substring(0, prefixIndex))); 175 prefixElement->setPseudo("-webkit-suggestion-prefix-text"); 176 suggestionItem->appendChild(prefixElement.release(), ASSERT_NO_EXCEPTION); 177 178 // Append the rest of the text in the suggestion item 179 suggestionItem->appendChild(Text::create(document, m_suggestions[i].substring(prefixIndex)), ASSERT_NO_EXCEPTION); 180 m_dropdownBox->appendChild(suggestionItem.release(), ASSERT_NO_EXCEPTION); 181 } 182} 183 184static void adjustDropdownBoxPosition(const LayoutRect& hostRect, HTMLElement* dropdownBox) 185{ 186 // FIXME: reverse positioning and scroll 187 ASSERT(dropdownBox); 188 if (hostRect.isEmpty()) 189 return; 190 double hostX = hostRect.x(); 191 double hostY = hostRect.y(); 192 if (RenderObject* renderer = dropdownBox->renderer()) { 193 if (RenderBox* container = renderer->containingBlock()) { 194 FloatPoint containerLocation = container->localToAbsolute(); 195 hostX -= containerLocation.x() + container->borderLeft(); 196 hostY -= containerLocation.y() + container->borderTop(); 197 } 198 } 199 200 dropdownBox->setInlineStyleProperty(CSSPropertyTop, hostY + hostRect.height(), CSSPrimitiveValue::CSS_PX); 201 // The 'left' value of ::-webkit-validation-bubble-arrow. 202 const int bubbleArrowTopOffset = 32; 203 double bubbleX = hostX; 204 if (hostRect.width() / 2 < bubbleArrowTopOffset) 205 bubbleX = max(hostX + hostRect.width() / 2 - bubbleArrowTopOffset, 0.0); 206 dropdownBox->setInlineStyleProperty(CSSPropertyLeft, bubbleX, CSSPrimitiveValue::CSS_PX); 207} 208 209void SuggestionBoxHandler::buildDropdownBoxTree() 210{ 211 ShadowRoot* shadowRoot = m_inputElement->ensureUserAgentShadowRoot(); 212 213 // Return if shadowRoot is null 214 if (!shadowRoot) 215 return; 216 217 Document* document = m_inputElement->document(); 218 m_dropdownBox = HTMLDivElement::create(document); 219 m_dropdownBox->setPseudo("-webkit-suggestion-dropdown-box"); 220 // Need to force position:absolute because RenderMenuList doesn't assume it 221 // contains non-absolute or non-fixed renderers as children. 222 m_dropdownBox->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute); 223 224 // Minus 2 pixels because of the 1px borders on the suggestion box. 225 m_dropdownBox->setInlineStyleProperty(CSSPropertyWidth, String::number(m_inputElement->boundingBox().width().rawValue() - 2) + "px", false); 226 ExceptionCode ec = 0; 227 shadowRoot->appendChild(m_dropdownBox.get(), ec); 228 ASSERT(!ec); 229 document->updateLayout(); 230 adjustDropdownBoxPosition(m_inputElement->boundingBox(), m_dropdownBox.get()); 231} 232 233} // namespace WebCore 234