Home | History | Annotate | Download | only in dom
      1 /*
      2  * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
      3  *
      4  * This library is free software; you can redistribute it and/or
      5  * modify it under the terms of the GNU Library General Public
      6  * License as published by the Free Software Foundation; either
      7  * version 2 of the License, or (at your option) any later version.
      8  *
      9  * This library is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     12  * Library General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Library General Public License
     15  * along with this library; see the file COPYING.LIB.  If not, write to
     16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     17  * Boston, MA 02110-1301, USA.
     18  *
     19  */
     20 
     21 #include "config.h"
     22 #include "InputElement.h"
     23 
     24 #include "BeforeTextInsertedEvent.h"
     25 
     26 #if ENABLE(WCSS)
     27 #include "CSSPropertyNames.h"
     28 #include "CSSRule.h"
     29 #include "CSSRuleList.h"
     30 #include "CSSStyleRule.h"
     31 #include "CSSStyleSelector.h"
     32 #endif
     33 
     34 #include "Attribute.h"
     35 #include "Chrome.h"
     36 #include "ChromeClient.h"
     37 #include "Document.h"
     38 #include "Event.h"
     39 #include "EventNames.h"
     40 #include "Frame.h"
     41 #include "Page.h"
     42 #include "RenderTextControlSingleLine.h"
     43 #include "SelectionController.h"
     44 #include "TextIterator.h"
     45 
     46 namespace WebCore {
     47 
     48 // FIXME: According to HTML4, the length attribute's value can be arbitrarily
     49 // large. However, due to https://bugs.webkit.org/show_bug.cgi?id=14536 things
     50 // get rather sluggish when a text field has a larger number of characters than
     51 // this, even when just clicking in the text field.
     52 const int InputElement::s_maximumLength = 524288;
     53 const int InputElement::s_defaultSize = 20;
     54 
     55 void InputElement::dispatchFocusEvent(InputElement* inputElement, Element* element)
     56 {
     57     if (!inputElement->isTextField())
     58         return;
     59 
     60     Document* document = element->document();
     61     if (inputElement->isPasswordField() && document->frame())
     62         document->setUseSecureKeyboardEntryWhenActive(true);
     63 }
     64 
     65 void InputElement::dispatchBlurEvent(InputElement* inputElement, Element* element)
     66 {
     67     if (!inputElement->isTextField())
     68         return;
     69 
     70     Document* document = element->document();
     71     Frame* frame = document->frame();
     72     if (!frame)
     73         return;
     74 
     75     if (inputElement->isPasswordField())
     76         document->setUseSecureKeyboardEntryWhenActive(false);
     77 
     78     frame->editor()->textFieldDidEndEditing(element);
     79 }
     80 
     81 void InputElement::updateFocusAppearance(InputElementData& data, InputElement* inputElement, Element* element, bool restorePreviousSelection)
     82 {
     83     ASSERT(inputElement->isTextField());
     84 
     85     if (!restorePreviousSelection || data.cachedSelectionStart() == -1)
     86         inputElement->select();
     87     else
     88         // Restore the cached selection.
     89         updateSelectionRange(inputElement, element, data.cachedSelectionStart(), data.cachedSelectionEnd());
     90 
     91     Document* document = element->document();
     92     if (document && document->frame())
     93         document->frame()->selection()->revealSelection();
     94 }
     95 
     96 void InputElement::updateSelectionRange(InputElement* inputElement, Element* element, int start, int end)
     97 {
     98     if (!inputElement->isTextField())
     99         return;
    100 
    101     setSelectionRange(element, start, end);
    102 }
    103 
    104 void InputElement::aboutToUnload(InputElement* inputElement, Element* element)
    105 {
    106     if (!inputElement->isTextField() || !element->focused())
    107         return;
    108 
    109     Document* document = element->document();
    110     Frame* frame = document->frame();
    111     if (!frame)
    112         return;
    113 
    114     frame->editor()->textFieldDidEndEditing(element);
    115 }
    116 
    117 void InputElement::setValueFromRenderer(InputElementData& data, InputElement* inputElement, Element* element, const String& value)
    118 {
    119     // Renderer and our event handler are responsible for sanitizing values.
    120     ASSERT_UNUSED(inputElement, value == inputElement->sanitizeValue(value) || inputElement->sanitizeValue(value).isEmpty());
    121 
    122     // Workaround for bug where trailing \n is included in the result of textContent.
    123     // The assert macro above may also be simplified to:  value == constrainValue(value)
    124     // http://bugs.webkit.org/show_bug.cgi?id=9661
    125     if (value == "\n")
    126         data.setValue("");
    127     else
    128         data.setValue(value);
    129 
    130     element->setFormControlValueMatchesRenderer(true);
    131 
    132     // Input event is fired by the Node::defaultEventHandler for editable controls.
    133     if (!inputElement->isTextField())
    134         element->dispatchInputEvent();
    135     notifyFormStateChanged(element);
    136 }
    137 
    138 static String replaceEOLAndLimitLength(const InputElement* inputElement, const String& proposedValue, int maxLength)
    139 {
    140     if (!inputElement->isTextField())
    141         return proposedValue;
    142 
    143     String string = proposedValue;
    144     string.replace("\r\n", " ");
    145     string.replace('\r', ' ');
    146     string.replace('\n', ' ');
    147 
    148     unsigned newLength = numCharactersInGraphemeClusters(string, maxLength);
    149     for (unsigned i = 0; i < newLength; ++i) {
    150         const UChar current = string[i];
    151         if (current < ' ' && current != '\t') {
    152             newLength = i;
    153             break;
    154         }
    155     }
    156     return string.left(newLength);
    157 }
    158 
    159 String InputElement::sanitizeValueForTextField(const InputElement* inputElement, const String& proposedValue)
    160 {
    161 #if ENABLE(WCSS)
    162     InputElementData data = const_cast<InputElement*>(inputElement)->data();
    163     if (!isConformToInputMask(data, proposedValue)) {
    164         if (isConformToInputMask(data, data.value()))
    165             return data.value();
    166         return String();
    167     }
    168 #endif
    169     return replaceEOLAndLimitLength(inputElement, proposedValue, s_maximumLength);
    170 }
    171 
    172 String InputElement::sanitizeUserInputValue(const InputElement* inputElement, const String& proposedValue, int maxLength)
    173 {
    174     return replaceEOLAndLimitLength(inputElement, proposedValue, maxLength);
    175 }
    176 
    177 void InputElement::handleBeforeTextInsertedEvent(InputElementData& data, InputElement* inputElement, Element* element, Event* event)
    178 {
    179     ASSERT(event->isBeforeTextInsertedEvent());
    180     // Make sure that the text to be inserted will not violate the maxLength.
    181 
    182     // We use RenderTextControlSingleLine::text() instead of InputElement::value()
    183     // because they can be mismatched by sanitizeValue() in
    184     // RenderTextControlSingleLine::subtreeHasChanged() in some cases.
    185     unsigned oldLength = numGraphemeClusters(toRenderTextControlSingleLine(element->renderer())->text());
    186 
    187     // selectionLength represents the selection length of this text field to be
    188     // removed by this insertion.
    189     // If the text field has no focus, we don't need to take account of the
    190     // selection length. The selection is the source of text drag-and-drop in
    191     // that case, and nothing in the text field will be removed.
    192     unsigned selectionLength = element->focused() ? numGraphemeClusters(plainText(element->document()->frame()->selection()->selection().toNormalizedRange().get())) : 0;
    193     ASSERT(oldLength >= selectionLength);
    194 
    195     // Selected characters will be removed by the next text event.
    196     unsigned baseLength = oldLength - selectionLength;
    197     unsigned maxLength = static_cast<unsigned>(inputElement->supportsMaxLength() ? data.maxLength() : s_maximumLength); // maxLength() can never be negative.
    198     unsigned appendableLength = maxLength > baseLength ? maxLength - baseLength : 0;
    199 
    200     // Truncate the inserted text to avoid violating the maxLength and other constraints.
    201     BeforeTextInsertedEvent* textEvent = static_cast<BeforeTextInsertedEvent*>(event);
    202 #if ENABLE(WCSS)
    203     RefPtr<Range> range = element->document()->frame()->selection()->selection().toNormalizedRange();
    204     String candidateString = toRenderTextControlSingleLine(element->renderer())->text();
    205     if (selectionLength)
    206         candidateString.replace(range->startOffset(), range->endOffset(), textEvent->text());
    207     else
    208         candidateString.insert(textEvent->text(), range->startOffset());
    209     if (!isConformToInputMask(inputElement->data(), candidateString)) {
    210         textEvent->setText("");
    211         return;
    212     }
    213 #endif
    214     textEvent->setText(sanitizeUserInputValue(inputElement, textEvent->text(), appendableLength));
    215 }
    216 
    217 void InputElement::parseSizeAttribute(InputElementData& data, Element* element, Attribute* attribute)
    218 {
    219     data.setSize(attribute->isNull() ? InputElement::s_defaultSize : attribute->value().toInt());
    220 
    221     if (RenderObject* renderer = element->renderer())
    222         renderer->setNeedsLayoutAndPrefWidthsRecalc();
    223 }
    224 
    225 void InputElement::parseMaxLengthAttribute(InputElementData& data, InputElement* inputElement, Element* element, Attribute* attribute)
    226 {
    227     int maxLength = attribute->isNull() ? InputElement::s_maximumLength : attribute->value().toInt();
    228     if (maxLength <= 0 || maxLength > InputElement::s_maximumLength)
    229         maxLength = InputElement::s_maximumLength;
    230 
    231     int oldMaxLength = data.maxLength();
    232     data.setMaxLength(maxLength);
    233 
    234     if (oldMaxLength != maxLength)
    235         updateValueIfNeeded(data, inputElement);
    236 
    237     element->setNeedsStyleRecalc();
    238 }
    239 
    240 void InputElement::updateValueIfNeeded(InputElementData& data, InputElement* inputElement)
    241 {
    242     String oldValue = data.value();
    243     String newValue = inputElement->sanitizeValue(oldValue);
    244     if (newValue != oldValue)
    245         inputElement->setValue(newValue);
    246 }
    247 
    248 void InputElement::notifyFormStateChanged(Element* element)
    249 {
    250     Document* document = element->document();
    251     Frame* frame = document->frame();
    252     if (!frame)
    253         return;
    254 
    255     if (Page* page = frame->page())
    256         page->chrome()->client()->formStateDidChange(element);
    257 }
    258 
    259 // InputElementData
    260 InputElementData::InputElementData()
    261     : m_size(InputElement::s_defaultSize)
    262     , m_maxLength(InputElement::s_maximumLength)
    263     , m_cachedSelectionStart(-1)
    264     , m_cachedSelectionEnd(-1)
    265 #if ENABLE(WCSS)
    266     , m_inputFormatMask("*m")
    267     , m_maxInputCharsAllowed(InputElement::s_maximumLength)
    268 #endif
    269 {
    270 }
    271 
    272 InputElementData::~InputElementData()
    273 {
    274 }
    275 
    276 const AtomicString& InputElementData::name() const
    277 {
    278     return m_name.isNull() ? emptyAtom : m_name;
    279 }
    280 
    281 #if ENABLE(WCSS)
    282 static inline const AtomicString& formatCodes()
    283 {
    284     DEFINE_STATIC_LOCAL(AtomicString, codes, ("AaNnXxMm"));
    285     return codes;
    286 }
    287 
    288 static unsigned cursorPositionToMaskIndex(const String& inputFormatMask, unsigned cursorPosition)
    289 {
    290     UChar mask;
    291     int index = -1;
    292     do {
    293         mask = inputFormatMask[++index];
    294         if (mask == '\\')
    295             ++index;
    296         else if (mask == '*' || (isASCIIDigit(mask) && mask != '0')) {
    297             index = inputFormatMask.length() - 1;
    298             break;
    299         }
    300     } while (cursorPosition--);
    301 
    302     return index;
    303 }
    304 
    305 bool InputElement::isConformToInputMask(const InputElementData& data, const String& inputChars)
    306 {
    307     for (unsigned i = 0; i < inputChars.length(); ++i)
    308         if (!isConformToInputMask(data, inputChars[i], i))
    309             return false;
    310     return true;
    311 }
    312 
    313 bool InputElement::isConformToInputMask(const InputElementData& data, UChar inChar, unsigned cursorPosition)
    314 {
    315     String inputFormatMask = data.inputFormatMask();
    316 
    317     if (inputFormatMask.isEmpty() || inputFormatMask == "*M" || inputFormatMask == "*m")
    318         return true;
    319 
    320     if (cursorPosition >= data.maxInputCharsAllowed())
    321         return false;
    322 
    323     unsigned maskIndex = cursorPositionToMaskIndex(inputFormatMask, cursorPosition);
    324     bool ok = true;
    325     UChar mask = inputFormatMask[maskIndex];
    326     // Match the inputed character with input mask
    327     switch (mask) {
    328     case 'A':
    329         ok = !isASCIIDigit(inChar) && !isASCIILower(inChar) && isASCIIPrintable(inChar);
    330         break;
    331     case 'a':
    332         ok = !isASCIIDigit(inChar) && !isASCIIUpper(inChar) && isASCIIPrintable(inChar);
    333         break;
    334     case 'N':
    335         ok = isASCIIDigit(inChar);
    336         break;
    337     case 'n':
    338         ok = !isASCIIAlpha(inChar) && isASCIIPrintable(inChar);
    339         break;
    340     case 'X':
    341         ok = !isASCIILower(inChar) && isASCIIPrintable(inChar);
    342         break;
    343     case 'x':
    344         ok = !isASCIIUpper(inChar) && isASCIIPrintable(inChar);
    345         break;
    346     case 'M':
    347     case 'm':
    348         ok = isASCIIPrintable(inChar);
    349         break;
    350     default:
    351         ok = (mask == inChar);
    352         break;
    353     }
    354 
    355     return ok;
    356 }
    357 
    358 String InputElement::validateInputMask(InputElementData& data, String& inputMask)
    359 {
    360     inputMask.replace("\\\\", "\\");
    361 
    362     bool isValid = true;
    363     bool hasWildcard = false;
    364     unsigned escapeCharCount = 0;
    365     unsigned maskLength = inputMask.length();
    366     UChar formatCode;
    367     for (unsigned i = 0; i < maskLength; ++i) {
    368         formatCode = inputMask[i];
    369         if (formatCodes().find(formatCode) == -1) {
    370             if (formatCode == '*' || (isASCIIDigit(formatCode) && formatCode != '0')) {
    371                 // Validate codes which ends with '*f' or 'nf'
    372                 formatCode = inputMask[++i];
    373                 if ((i + 1 != maskLength) || formatCodes().find(formatCode) == -1) {
    374                     isValid = false;
    375                     break;
    376                 }
    377                 hasWildcard = true;
    378             } else if (formatCode == '\\') {
    379                 // skip over the next mask character
    380                 ++i;
    381                 ++escapeCharCount;
    382             } else {
    383                 isValid = false;
    384                 break;
    385             }
    386         }
    387     }
    388 
    389     if (!isValid)
    390         return String();
    391     // calculate the number of characters allowed to be entered by input mask
    392     unsigned allowedLength = maskLength;
    393     if (escapeCharCount)
    394         allowedLength -= escapeCharCount;
    395 
    396     if (hasWildcard) {
    397         formatCode = inputMask[maskLength - 2];
    398         if (formatCode == '*')
    399             allowedLength = data.maxInputCharsAllowed();
    400         else {
    401             unsigned leftLen = String(&formatCode).toInt();
    402             allowedLength = leftLen + allowedLength - 2;
    403         }
    404     }
    405 
    406     if (allowedLength < data.maxInputCharsAllowed())
    407         data.setMaxInputCharsAllowed(allowedLength);
    408 
    409     return inputMask;
    410 }
    411 
    412 #endif
    413 
    414 }
    415