--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/dom/InputElement.cpp Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,433 @@
+/*
+ * 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->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()->revealSelection();
+}
+
+void InputElement::updateSelectionRange(InputElement* inputElement, Element* element, int start, int end)
+{
+ if (!inputElement->isTextField())
+ return;
+
+ element->document()->updateLayoutIgnorePendingStylesheets();
+
+ if (RenderTextControl* renderer = toRenderTextControl(element->renderer()))
+ renderer->setSelectionRange(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->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);
+
+ 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>(data.maxLength()); // 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
+{
+}
+
+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
+
+}