blob: a88f300c9359c6b16b5bf1627be0cf6e4cd6b9ec [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
* Copyright (C) 2012 Research In Motion Limited. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "SuggestionBoxHandler.h"
#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
#include "DOMTokenList.h"
#include "ElementShadow.h"
#include "ExceptionCodePlaceholder.h"
#include "HTMLCollection.h"
#include "HTMLDataListElement.h"
#include "HTMLDivElement.h"
#include "HTMLInputElement.h"
#include "HTMLNames.h"
#include "HTMLOptionElement.h"
#include "HTMLSpanElement.h"
#include "RenderBlock.h"
#include "RenderObject.h"
#include "ShadowRoot.h"
#include "StyleResolver.h"
#include "SuggestionBoxElement.h"
#include "Text.h"
#include <BlackBerryPlatformLog.h>
#include <wtf/PassOwnPtr.h>
namespace WebCore {
using namespace HTMLNames;
SuggestionBoxHandler::SuggestionBoxHandler(HTMLInputElement* element)
: m_inputElement(element)
{
}
SuggestionBoxHandler::~SuggestionBoxHandler()
{
hideDropdownBox();
}
PassOwnPtr<SuggestionBoxHandler> SuggestionBoxHandler::create(HTMLInputElement* element)
{
return adoptPtr(new SuggestionBoxHandler(element));
}
void SuggestionBoxHandler::setInputElementAndUpdateDisplay(HTMLInputElement* element, bool allowEmptyPrefix)
{
hideDropdownBox();
m_inputElement = element;
showDropdownBox(allowEmptyPrefix);
}
void SuggestionBoxHandler::showDropdownBox(bool allowEmptyPrefix)
{
// Clearing suggestions and parse again
m_suggestions.clear();
parseSuggestions(allowEmptyPrefix);
// Don't bother building the tree if suggestion size is 0.
// We need to call insertSuggestions to clear previous suggestions
// in the DOM if suggestion size is 0.
if (!m_dropdownBox) {
if (!m_suggestions.size())
return;
buildDropdownBoxTree();
}
insertSuggestionsToDropdownBox();
}
void SuggestionBoxHandler::hideDropdownBox()
{
if (m_dropdownBox) {
if (ShadowRoot* shadowRoot = m_inputElement->userAgentShadowRoot()) {
ExceptionCode ec;
shadowRoot->removeChild(m_dropdownBox.get(), ec);
}
m_dropdownBox = 0;
}
m_suggestions.clear();
}
void SuggestionBoxHandler::changeInputElementInnerTextValue(String& text)
{
m_inputElement->setInnerTextValue(text);
}
void SuggestionBoxHandler::parseSuggestions(bool allowEmptyPrefix)
{
// Return if this handler doesn't have an input element to attach to
if (!m_inputElement)
return;
// List attribute exists, check if datalist exists.
HTMLDataListElement* dList = m_inputElement->dataList();
if (!dList)
return;
RefPtr<HTMLCollection> optionsList = dList->options();
String prefix = m_inputElement->innerTextValue();
// Exit if:
// 1) If length of nodes in datalist is empty, return.
// 2) If we do not allow empty prefixes and prefix is null
if (!optionsList->length() || (!allowEmptyPrefix && prefix.isEmpty())) {
hideDropdownBox();
return;
}
// Prefix match the inner text value of the input against the candidate suggestions.
// Make sure the length of the suggestion is smaller than max length
for (unsigned i = 0; i < optionsList->length(); i++) {
HTMLOptionElement* target = toHTMLOptionElement((optionsList->item(i)));
if (!m_inputElement->isValidValue(target->value()))
continue;
String candidateString = m_inputElement->sanitizeValue(target->value());
if (!candidateString.findIgnoringCase(prefix) && candidateString.length() <= (unsigned) m_inputElement->maxLength())
m_suggestions.append(candidateString);
}
}
void SuggestionBoxHandler::insertSuggestionsToDropdownBox()
{
// In case someone is calling this function without successfully building the dropdownBox tree
ASSERT(m_dropdownBox);
Document* document = m_dropdownBox->document();
// Clear the dropdown box's previous values
m_dropdownBox->removeChildren();
int prefixIndex = m_inputElement->innerTextValue().length();
unsigned total = m_suggestions.size();
for (unsigned i = 0; i < total; ++i) {
RefPtr<HTMLElement> suggestionItem = SuggestionBoxElement::create(this, m_suggestions[i], document);
if (i < total - 1)
suggestionItem->setPseudo("-webkit-suggestion-dropdown-box-item");
else
suggestionItem->setPseudo("-webkit-suggestion-dropdown-box-item-last");
// Append the part where it prefix matched
RefPtr<HTMLSpanElement> prefixElement = HTMLSpanElement::create(HTMLNames::spanTag, document);
prefixElement->appendChild(Text::create(document, m_suggestions[i].substring(0, prefixIndex)));
prefixElement->setPseudo("-webkit-suggestion-prefix-text");
suggestionItem->appendChild(prefixElement.release(), ASSERT_NO_EXCEPTION);
// Append the rest of the text in the suggestion item
suggestionItem->appendChild(Text::create(document, m_suggestions[i].substring(prefixIndex)), ASSERT_NO_EXCEPTION);
m_dropdownBox->appendChild(suggestionItem.release(), ASSERT_NO_EXCEPTION);
}
}
static void adjustDropdownBoxPosition(const LayoutRect& hostRect, HTMLElement* dropdownBox)
{
// FIXME: reverse positioning and scroll
ASSERT(dropdownBox);
if (hostRect.isEmpty())
return;
double hostX = hostRect.x();
double hostY = hostRect.y();
if (RenderObject* renderer = dropdownBox->renderer()) {
if (RenderBox* container = renderer->containingBlock()) {
FloatPoint containerLocation = container->localToAbsolute();
hostX -= containerLocation.x() + container->borderLeft();
hostY -= containerLocation.y() + container->borderTop();
}
}
dropdownBox->setInlineStyleProperty(CSSPropertyTop, hostY + hostRect.height(), CSSPrimitiveValue::CSS_PX);
// The 'left' value of ::-webkit-validation-bubble-arrow.
const int bubbleArrowTopOffset = 32;
double bubbleX = hostX;
if (hostRect.width() / 2 < bubbleArrowTopOffset)
bubbleX = max(hostX + hostRect.width() / 2 - bubbleArrowTopOffset, 0.0);
dropdownBox->setInlineStyleProperty(CSSPropertyLeft, bubbleX, CSSPrimitiveValue::CSS_PX);
}
void SuggestionBoxHandler::buildDropdownBoxTree()
{
ShadowRoot* shadowRoot = m_inputElement->ensureUserAgentShadowRoot();
// Return if shadowRoot is null
if (!shadowRoot)
return;
Document* document = m_inputElement->document();
m_dropdownBox = HTMLDivElement::create(document);
m_dropdownBox->setPseudo("-webkit-suggestion-dropdown-box");
// Need to force position:absolute because RenderMenuList doesn't assume it
// contains non-absolute or non-fixed renderers as children.
m_dropdownBox->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
// Minus 2 pixels because of the 1px borders on the suggestion box.
m_dropdownBox->setInlineStyleProperty(CSSPropertyWidth, String::number(m_inputElement->boundingBox().width().rawValue() - 2) + "px", false);
ExceptionCode ec = 0;
shadowRoot->appendChild(m_dropdownBox.get(), ec);
ASSERT(!ec);
document->updateLayout();
adjustDropdownBoxPosition(m_inputElement->boundingBox(), m_dropdownBox.get());
}
} // namespace WebCore