WebCore/dom/InputElement.cpp
changeset 0 4f2f89ce4247
--- /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
+
+}