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