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