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 
dispatchFocusEvent(InputElement * inputElement,Element * element)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 
dispatchBlurEvent(InputElement * inputElement,Element * element)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 
updateFocusAppearance(InputElementData & data,InputElement * inputElement,Element * element,bool restorePreviousSelection)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 
updateSelectionRange(InputElement * inputElement,Element * element,int start,int end)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 
aboutToUnload(InputElement * inputElement,Element * element)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 
setValueFromRenderer(InputElementData & data,InputElement * inputElement,Element * element,const String & value)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 
replaceEOLAndLimitLength(const InputElement * inputElement,const String & proposedValue,int maxLength)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 
sanitizeValueForTextField(const InputElement * inputElement,const String & proposedValue)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 
sanitizeUserInputValue(const InputElement * inputElement,const String & proposedValue,int maxLength)172 String InputElement::sanitizeUserInputValue(const InputElement* inputElement, const String& proposedValue, int maxLength)
173 {
174     return replaceEOLAndLimitLength(inputElement, proposedValue, maxLength);
175 }
176 
handleBeforeTextInsertedEvent(InputElementData & data,InputElement * inputElement,Element * element,Event * event)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 
parseSizeAttribute(InputElementData & data,Element * element,Attribute * attribute)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 
parseMaxLengthAttribute(InputElementData & data,InputElement * inputElement,Element * element,Attribute * attribute)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 
updateValueIfNeeded(InputElementData & data,InputElement * inputElement)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 
notifyFormStateChanged(Element * element)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
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 
~InputElementData()272 InputElementData::~InputElementData()
273 {
274 }
275 
name() const276 const AtomicString& InputElementData::name() const
277 {
278     return m_name.isNull() ? emptyAtom : m_name;
279 }
280 
281 #if ENABLE(WCSS)
formatCodes()282 static inline const AtomicString& formatCodes()
283 {
284     DEFINE_STATIC_LOCAL(AtomicString, codes, ("AaNnXxMm"));
285     return codes;
286 }
287 
cursorPositionToMaskIndex(const String & inputFormatMask,unsigned cursorPosition)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 
isConformToInputMask(const InputElementData & data,const String & inputChars)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 
isConformToInputMask(const InputElementData & data,UChar inChar,unsigned cursorPosition)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 
validateInputMask(InputElementData & data,String & inputMask)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