diff options
Diffstat (limited to 'Source/WebCore/dom/InputElement.cpp')
-rw-r--r-- | Source/WebCore/dom/InputElement.cpp | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/Source/WebCore/dom/InputElement.cpp b/Source/WebCore/dom/InputElement.cpp new file mode 100644 index 0000000..37211d8 --- /dev/null +++ b/Source/WebCore/dom/InputElement.cpp @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * + * 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 "InputElement.h" + +#include "BeforeTextInsertedEvent.h" + +#if ENABLE(WCSS) +#include "CSSPropertyNames.h" +#include "CSSRule.h" +#include "CSSRuleList.h" +#include "CSSStyleRule.h" +#include "CSSStyleSelector.h" +#endif + +#include "Attribute.h" +#include "Chrome.h" +#include "ChromeClient.h" +#include "Document.h" +#include "Event.h" +#include "EventNames.h" +#include "Frame.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "Page.h" +#include "RenderTextControlSingleLine.h" +#include "SelectionController.h" +#include "TextIterator.h" + +#if ENABLE(WML) +#include "WMLInputElement.h" +#include "WMLNames.h" +#endif + +namespace WebCore { + +using namespace HTMLNames; + +// FIXME: According to HTML4, the length attribute's value can be arbitrarily +// large. However, due to https://bugs.webkit.org/show_bug.cgi?id=14536 things +// get rather sluggish when a text field has a larger number of characters than +// this, even when just clicking in the text field. +const int InputElement::s_maximumLength = 524288; +const int InputElement::s_defaultSize = 20; + +void InputElement::dispatchFocusEvent(InputElement* inputElement, Element* element) +{ + if (!inputElement->isTextField()) + return; + + Document* document = element->document(); + if (inputElement->isPasswordField() && document->frame()) + document->setUseSecureKeyboardEntryWhenActive(true); +} + +void InputElement::dispatchBlurEvent(InputElement* inputElement, Element* element) +{ + if (!inputElement->isTextField()) + return; + + Document* document = element->document(); + Frame* frame = document->frame(); + if (!frame) + return; + + if (inputElement->isPasswordField()) + document->setUseSecureKeyboardEntryWhenActive(false); + + frame->editor()->textFieldDidEndEditing(element); +} + +void InputElement::updateFocusAppearance(InputElementData& data, InputElement* inputElement, Element* element, bool restorePreviousSelection) +{ + ASSERT(inputElement->isTextField()); + + if (!restorePreviousSelection || data.cachedSelectionStart() == -1) + inputElement->select(); + else + // Restore the cached selection. + updateSelectionRange(inputElement, element, data.cachedSelectionStart(), data.cachedSelectionEnd()); + + Document* document = element->document(); + if (document && document->frame()) + document->frame()->selection()->revealSelection(); +} + +void InputElement::updateSelectionRange(InputElement* inputElement, Element* element, int start, int end) +{ + if (!inputElement->isTextField()) + return; + + setSelectionRange(element, start, end); +} + +void InputElement::aboutToUnload(InputElement* inputElement, Element* element) +{ + if (!inputElement->isTextField() || !element->focused()) + return; + + Document* document = element->document(); + Frame* frame = document->frame(); + if (!frame) + return; + + frame->editor()->textFieldDidEndEditing(element); +} + +void InputElement::setValueFromRenderer(InputElementData& data, InputElement* inputElement, Element* element, const String& value) +{ + // Renderer and our event handler are responsible for sanitizing values. + ASSERT_UNUSED(inputElement, value == inputElement->sanitizeValue(value) || inputElement->sanitizeValue(value).isEmpty()); + + // Workaround for bug where trailing \n is included in the result of textContent. + // The assert macro above may also be simplified to: value == constrainValue(value) + // http://bugs.webkit.org/show_bug.cgi?id=9661 + if (value == "\n") + data.setValue(""); + else + data.setValue(value); + + element->setFormControlValueMatchesRenderer(true); + + // Input event is fired by the Node::defaultEventHandler for editable controls. + if (!inputElement->isTextField()) + element->dispatchEvent(Event::create(eventNames().inputEvent, true, false)); + notifyFormStateChanged(element); +} + +static String replaceEOLAndLimitLength(const InputElement* inputElement, const String& proposedValue, int maxLength) +{ + if (!inputElement->isTextField()) + return proposedValue; + + String string = proposedValue; + string.replace("\r\n", " "); + string.replace('\r', ' '); + string.replace('\n', ' '); + + unsigned newLength = numCharactersInGraphemeClusters(string, maxLength); + for (unsigned i = 0; i < newLength; ++i) { + const UChar current = string[i]; + if (current < ' ' && current != '\t') { + newLength = i; + break; + } + } + return string.left(newLength); +} + +String InputElement::sanitizeValueForTextField(const InputElement* inputElement, const String& proposedValue) +{ +#if ENABLE(WCSS) + InputElementData data = const_cast<InputElement*>(inputElement)->data(); + if (!isConformToInputMask(data, proposedValue)) { + if (isConformToInputMask(data, data.value())) + return data.value(); + return String(); + } +#endif + return replaceEOLAndLimitLength(inputElement, proposedValue, s_maximumLength); +} + +String InputElement::sanitizeUserInputValue(const InputElement* inputElement, const String& proposedValue, int maxLength) +{ + return replaceEOLAndLimitLength(inputElement, proposedValue, maxLength); +} + +void InputElement::handleBeforeTextInsertedEvent(InputElementData& data, InputElement* inputElement, Element* element, Event* event) +{ + ASSERT(event->isBeforeTextInsertedEvent()); + // Make sure that the text to be inserted will not violate the maxLength. + + // We use RenderTextControlSingleLine::text() instead of InputElement::value() + // because they can be mismatched by sanitizeValue() in + // RenderTextControlSingleLine::subtreeHasChanged() in some cases. + unsigned oldLength = numGraphemeClusters(toRenderTextControlSingleLine(element->renderer())->text()); + + // selectionLength represents the selection length of this text field to be + // removed by this insertion. + // If the text field has no focus, we don't need to take account of the + // selection length. The selection is the source of text drag-and-drop in + // that case, and nothing in the text field will be removed. + unsigned selectionLength = element->focused() ? numGraphemeClusters(plainText(element->document()->frame()->selection()->selection().toNormalizedRange().get())) : 0; + ASSERT(oldLength >= selectionLength); + + // Selected characters will be removed by the next text event. + unsigned baseLength = oldLength - selectionLength; + unsigned maxLength = static_cast<unsigned>(inputElement->supportsMaxLength() ? data.maxLength() : s_maximumLength); // maxLength() can never be negative. + unsigned appendableLength = maxLength > baseLength ? maxLength - baseLength : 0; + + // Truncate the inserted text to avoid violating the maxLength and other constraints. + BeforeTextInsertedEvent* textEvent = static_cast<BeforeTextInsertedEvent*>(event); +#if ENABLE(WCSS) + RefPtr<Range> range = element->document()->frame()->selection()->selection().toNormalizedRange(); + String candidateString = toRenderTextControlSingleLine(element->renderer())->text(); + if (selectionLength) + candidateString.replace(range->startOffset(), range->endOffset(), textEvent->text()); + else + candidateString.insert(textEvent->text(), range->startOffset()); + if (!isConformToInputMask(inputElement->data(), candidateString)) { + textEvent->setText(""); + return; + } +#endif + textEvent->setText(sanitizeUserInputValue(inputElement, textEvent->text(), appendableLength)); +} + +void InputElement::parseSizeAttribute(InputElementData& data, Element* element, Attribute* attribute) +{ + data.setSize(attribute->isNull() ? InputElement::s_defaultSize : attribute->value().toInt()); + + if (RenderObject* renderer = element->renderer()) + renderer->setNeedsLayoutAndPrefWidthsRecalc(); +} + +void InputElement::parseMaxLengthAttribute(InputElementData& data, InputElement* inputElement, Element* element, Attribute* attribute) +{ + int maxLength = attribute->isNull() ? InputElement::s_maximumLength : attribute->value().toInt(); + if (maxLength <= 0 || maxLength > InputElement::s_maximumLength) + maxLength = InputElement::s_maximumLength; + + int oldMaxLength = data.maxLength(); + data.setMaxLength(maxLength); + + if (oldMaxLength != maxLength) + updateValueIfNeeded(data, inputElement); + + element->setNeedsStyleRecalc(); +} + +void InputElement::updateValueIfNeeded(InputElementData& data, InputElement* inputElement) +{ + String oldValue = data.value(); + String newValue = inputElement->sanitizeValue(oldValue); + if (newValue != oldValue) + inputElement->setValue(newValue); +} + +void InputElement::notifyFormStateChanged(Element* element) +{ + Document* document = element->document(); + Frame* frame = document->frame(); + if (!frame) + return; + + if (Page* page = frame->page()) + page->chrome()->client()->formStateDidChange(element); +} + +// InputElementData +InputElementData::InputElementData() + : m_size(InputElement::s_defaultSize) + , m_maxLength(InputElement::s_maximumLength) + , m_cachedSelectionStart(-1) + , m_cachedSelectionEnd(-1) +#if ENABLE(WCSS) + , m_inputFormatMask("*m") + , m_maxInputCharsAllowed(InputElement::s_maximumLength) +#endif +{ +} + +InputElementData::~InputElementData() +{ +} + +const AtomicString& InputElementData::name() const +{ + return m_name.isNull() ? emptyAtom : m_name; +} + +InputElement* toInputElement(Element* element) +{ + if (element->isHTMLElement() && (element->hasTagName(inputTag) || element->hasTagName(isindexTag))) + return static_cast<HTMLInputElement*>(element); + +#if ENABLE(WML) + if (element->isWMLElement() && element->hasTagName(WMLNames::inputTag)) + return static_cast<WMLInputElement*>(element); +#endif + + return 0; +} + +#if ENABLE(WCSS) +static inline const AtomicString& formatCodes() +{ + DEFINE_STATIC_LOCAL(AtomicString, codes, ("AaNnXxMm")); + return codes; +} + +static unsigned cursorPositionToMaskIndex(const String& inputFormatMask, unsigned cursorPosition) +{ + UChar mask; + int index = -1; + do { + mask = inputFormatMask[++index]; + if (mask == '\\') + ++index; + else if (mask == '*' || (isASCIIDigit(mask) && mask != '0')) { + index = inputFormatMask.length() - 1; + break; + } + } while (cursorPosition--); + + return index; +} + +bool InputElement::isConformToInputMask(const InputElementData& data, const String& inputChars) +{ + for (unsigned i = 0; i < inputChars.length(); ++i) + if (!isConformToInputMask(data, inputChars[i], i)) + return false; + return true; +} + +bool InputElement::isConformToInputMask(const InputElementData& data, UChar inChar, unsigned cursorPosition) +{ + String inputFormatMask = data.inputFormatMask(); + + if (inputFormatMask.isEmpty() || inputFormatMask == "*M" || inputFormatMask == "*m") + return true; + + if (cursorPosition >= data.maxInputCharsAllowed()) + return false; + + unsigned maskIndex = cursorPositionToMaskIndex(inputFormatMask, cursorPosition); + bool ok = true; + UChar mask = inputFormatMask[maskIndex]; + // Match the inputed character with input mask + switch (mask) { + case 'A': + ok = !isASCIIDigit(inChar) && !isASCIILower(inChar) && isASCIIPrintable(inChar); + break; + case 'a': + ok = !isASCIIDigit(inChar) && !isASCIIUpper(inChar) && isASCIIPrintable(inChar); + break; + case 'N': + ok = isASCIIDigit(inChar); + break; + case 'n': + ok = !isASCIIAlpha(inChar) && isASCIIPrintable(inChar); + break; + case 'X': + ok = !isASCIILower(inChar) && isASCIIPrintable(inChar); + break; + case 'x': + ok = !isASCIIUpper(inChar) && isASCIIPrintable(inChar); + break; + case 'M': + case 'm': + ok = isASCIIPrintable(inChar); + break; + default: + ok = (mask == inChar); + break; + } + + return ok; +} + +String InputElement::validateInputMask(InputElementData& data, String& inputMask) +{ + inputMask.replace("\\\\", "\\"); + + bool isValid = true; + bool hasWildcard = false; + unsigned escapeCharCount = 0; + unsigned maskLength = inputMask.length(); + UChar formatCode; + for (unsigned i = 0; i < maskLength; ++i) { + formatCode = inputMask[i]; + if (formatCodes().find(formatCode) == -1) { + if (formatCode == '*' || (isASCIIDigit(formatCode) && formatCode != '0')) { + // Validate codes which ends with '*f' or 'nf' + formatCode = inputMask[++i]; + if ((i + 1 != maskLength) || formatCodes().find(formatCode) == -1) { + isValid = false; + break; + } + hasWildcard = true; + } else if (formatCode == '\\') { + // skip over the next mask character + ++i; + ++escapeCharCount; + } else { + isValid = false; + break; + } + } + } + + if (!isValid) + return String(); + // calculate the number of characters allowed to be entered by input mask + unsigned allowedLength = maskLength; + if (escapeCharCount) + allowedLength -= escapeCharCount; + + if (hasWildcard) { + formatCode = inputMask[maskLength - 2]; + if (formatCode == '*') + allowedLength = data.maxInputCharsAllowed(); + else { + unsigned leftLen = String(&formatCode).toInt(); + allowedLength = leftLen + allowedLength - 2; + } + } + + if (allowedLength < data.maxInputCharsAllowed()) + data.setMaxInputCharsAllowed(allowedLength); + + return inputMask; +} + +#endif + +} |