1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2001 Dirk Mueller (mueller (at) kde.org) 5 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010 Apple Inc. All rights reserved. 6 * (C) 2006 Alexey Proskuryakov (ap (at) nypop.com) 7 * Copyright (C) 2007 Samuel Weinig (sam (at) webkit.org) 8 * 9 * This library is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU Library General Public 11 * License as published by the Free Software Foundation; either 12 * version 2 of the License, or (at your option) any later version. 13 * 14 * This library is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Library General Public License for more details. 18 * 19 * You should have received a copy of the GNU Library General Public License 20 * along with this library; see the file COPYING.LIB. If not, write to 21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 * Boston, MA 02110-1301, USA. 23 * 24 */ 25 26 #include "config.h" 27 #include "core/html/HTMLTextAreaElement.h" 28 29 #include "CSSValueKeywords.h" 30 #include "HTMLNames.h" 31 #include "bindings/v8/ExceptionState.h" 32 #include "bindings/v8/ExceptionStatePlaceholder.h" 33 #include "core/dom/Document.h" 34 #include "core/dom/ExceptionCode.h" 35 #include "core/dom/Text.h" 36 #include "core/dom/shadow/ShadowRoot.h" 37 #include "core/editing/FrameSelection.h" 38 #include "core/editing/SpellChecker.h" 39 #include "core/editing/TextIterator.h" 40 #include "core/events/BeforeTextInsertedEvent.h" 41 #include "core/events/Event.h" 42 #include "core/events/ThreadLocalEventNames.h" 43 #include "core/html/FormDataList.h" 44 #include "core/html/forms/FormController.h" 45 #include "core/html/shadow/ShadowElementNames.h" 46 #include "core/html/shadow/TextControlInnerElements.h" 47 #include "core/frame/Frame.h" 48 #include "core/rendering/RenderTextControlMultiLine.h" 49 #include "platform/text/PlatformLocale.h" 50 #include "wtf/StdLibExtras.h" 51 #include "wtf/text/StringBuilder.h" 52 53 namespace WebCore { 54 55 using namespace HTMLNames; 56 57 static const int defaultRows = 2; 58 static const int defaultCols = 20; 59 60 // On submission, LF characters are converted into CRLF. 61 // This function returns number of characters considering this. 62 static unsigned numberOfLineBreaks(const String& text) 63 { 64 unsigned length = text.length(); 65 unsigned count = 0; 66 for (unsigned i = 0; i < length; i++) { 67 if (text[i] == '\n') 68 count++; 69 } 70 return count; 71 } 72 73 static inline unsigned computeLengthForSubmission(const String& text) 74 { 75 return text.length() + numberOfLineBreaks(text); 76 } 77 78 HTMLTextAreaElement::HTMLTextAreaElement(Document& document, HTMLFormElement* form) 79 : HTMLTextFormControlElement(textareaTag, document, form) 80 , m_rows(defaultRows) 81 , m_cols(defaultCols) 82 , m_wrap(SoftWrap) 83 , m_isDirty(false) 84 { 85 setFormControlValueMatchesRenderer(true); 86 ScriptWrappable::init(this); 87 } 88 89 PassRefPtr<HTMLTextAreaElement> HTMLTextAreaElement::create(Document& document, HTMLFormElement* form) 90 { 91 RefPtr<HTMLTextAreaElement> textArea = adoptRef(new HTMLTextAreaElement(document, form)); 92 textArea->ensureUserAgentShadowRoot(); 93 return textArea.release(); 94 } 95 96 void HTMLTextAreaElement::didAddUserAgentShadowRoot(ShadowRoot& root) 97 { 98 root.appendChild(TextControlInnerTextElement::create(document())); 99 } 100 101 const AtomicString& HTMLTextAreaElement::formControlType() const 102 { 103 DEFINE_STATIC_LOCAL(const AtomicString, textarea, ("textarea", AtomicString::ConstructFromLiteral)); 104 return textarea; 105 } 106 107 FormControlState HTMLTextAreaElement::saveFormControlState() const 108 { 109 return m_isDirty ? FormControlState(value()) : FormControlState(); 110 } 111 112 void HTMLTextAreaElement::restoreFormControlState(const FormControlState& state) 113 { 114 setValue(state[0]); 115 } 116 117 void HTMLTextAreaElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta) 118 { 119 HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); 120 setLastChangeWasNotUserEdit(); 121 if (m_isDirty) 122 setInnerTextValue(value()); 123 else 124 setNonDirtyValue(defaultValue()); 125 } 126 127 bool HTMLTextAreaElement::isPresentationAttribute(const QualifiedName& name) const 128 { 129 if (name == alignAttr) { 130 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do. 131 // See http://bugs.webkit.org/show_bug.cgi?id=7075 132 return false; 133 } 134 135 if (name == wrapAttr) 136 return true; 137 return HTMLTextFormControlElement::isPresentationAttribute(name); 138 } 139 140 void HTMLTextAreaElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style) 141 { 142 if (name == wrapAttr) { 143 if (shouldWrapText()) { 144 addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValuePreWrap); 145 addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueBreakWord); 146 } else { 147 addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValuePre); 148 addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueNormal); 149 } 150 } else 151 HTMLTextFormControlElement::collectStyleForPresentationAttribute(name, value, style); 152 } 153 154 void HTMLTextAreaElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 155 { 156 if (name == rowsAttr) { 157 int rows = value.toInt(); 158 if (rows <= 0) 159 rows = defaultRows; 160 if (m_rows != rows) { 161 m_rows = rows; 162 if (renderer()) 163 renderer()->setNeedsLayoutAndPrefWidthsRecalc(); 164 } 165 } else if (name == colsAttr) { 166 int cols = value.toInt(); 167 if (cols <= 0) 168 cols = defaultCols; 169 if (m_cols != cols) { 170 m_cols = cols; 171 if (renderer()) 172 renderer()->setNeedsLayoutAndPrefWidthsRecalc(); 173 } 174 } else if (name == wrapAttr) { 175 // The virtual/physical values were a Netscape extension of HTML 3.0, now deprecated. 176 // The soft/hard /off values are a recommendation for HTML 4 extension by IE and NS 4. 177 WrapMethod wrap; 178 if (equalIgnoringCase(value, "physical") || equalIgnoringCase(value, "hard") || equalIgnoringCase(value, "on")) 179 wrap = HardWrap; 180 else if (equalIgnoringCase(value, "off")) 181 wrap = NoWrap; 182 else 183 wrap = SoftWrap; 184 if (wrap != m_wrap) { 185 m_wrap = wrap; 186 if (renderer()) 187 renderer()->setNeedsLayoutAndPrefWidthsRecalc(); 188 } 189 } else if (name == accesskeyAttr) { 190 // ignore for the moment 191 } else if (name == maxlengthAttr) 192 setNeedsValidityCheck(); 193 else 194 HTMLTextFormControlElement::parseAttribute(name, value); 195 } 196 197 RenderObject* HTMLTextAreaElement::createRenderer(RenderStyle*) 198 { 199 return new RenderTextControlMultiLine(this); 200 } 201 202 bool HTMLTextAreaElement::appendFormData(FormDataList& encoding, bool) 203 { 204 if (name().isEmpty()) 205 return false; 206 207 document().updateLayout(); 208 209 const String& text = (m_wrap == HardWrap) ? valueWithHardLineBreaks() : value(); 210 encoding.appendData(name(), text); 211 212 const AtomicString& dirnameAttrValue = fastGetAttribute(dirnameAttr); 213 if (!dirnameAttrValue.isNull()) 214 encoding.appendData(dirnameAttrValue, directionForFormData()); 215 return true; 216 } 217 218 void HTMLTextAreaElement::resetImpl() 219 { 220 setNonDirtyValue(defaultValue()); 221 } 222 223 bool HTMLTextAreaElement::hasCustomFocusLogic() const 224 { 225 return true; 226 } 227 228 bool HTMLTextAreaElement::isKeyboardFocusable() const 229 { 230 // If a given text area can be focused at all, then it will always be keyboard focusable. 231 return isFocusable(); 232 } 233 234 bool HTMLTextAreaElement::shouldShowFocusRingOnMouseFocus() const 235 { 236 return true; 237 } 238 239 void HTMLTextAreaElement::updateFocusAppearance(bool restorePreviousSelection) 240 { 241 if (!restorePreviousSelection || !hasCachedSelection()) { 242 // If this is the first focus, set a caret at the beginning of the text. 243 // This matches some browsers' behavior; see bug 11746 Comment #15. 244 // http://bugs.webkit.org/show_bug.cgi?id=11746#c15 245 setSelectionRange(0, 0); 246 } else 247 restoreCachedSelection(); 248 249 if (document().frame()) 250 document().frame()->selection().revealSelection(); 251 } 252 253 void HTMLTextAreaElement::defaultEventHandler(Event* event) 254 { 255 if (renderer() && (event->isMouseEvent() || event->isDragEvent() || event->hasInterface(EventNames::WheelEvent) || event->type() == EventTypeNames::blur)) 256 forwardEvent(event); 257 else if (renderer() && event->isBeforeTextInsertedEvent()) 258 handleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(event)); 259 260 HTMLTextFormControlElement::defaultEventHandler(event); 261 } 262 263 void HTMLTextAreaElement::handleFocusEvent(Element*, FocusDirection) 264 { 265 if (Frame* frame = document().frame()) 266 frame->spellChecker().didBeginEditing(this); 267 } 268 269 void HTMLTextAreaElement::subtreeHasChanged() 270 { 271 setChangedSinceLastFormControlChangeEvent(true); 272 setFormControlValueMatchesRenderer(false); 273 setNeedsValidityCheck(); 274 275 if (!focused()) 276 return; 277 278 // When typing in a textarea, childrenChanged is not called, so we need to force the directionality check. 279 calculateAndAdjustDirectionality(); 280 } 281 282 void HTMLTextAreaElement::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent* event) const 283 { 284 ASSERT(event); 285 ASSERT(renderer()); 286 int signedMaxLength = maxLength(); 287 if (signedMaxLength < 0) 288 return; 289 unsigned unsignedMaxLength = static_cast<unsigned>(signedMaxLength); 290 291 const String& currentValue = innerTextValue(); 292 unsigned currentLength = computeLengthForSubmission(currentValue); 293 if (currentLength + computeLengthForSubmission(event->text()) < unsignedMaxLength) 294 return; 295 296 // selectionLength represents the selection length of this text field to be 297 // removed by this insertion. 298 // If the text field has no focus, we don't need to take account of the 299 // selection length. The selection is the source of text drag-and-drop in 300 // that case, and nothing in the text field will be removed. 301 unsigned selectionLength = focused() ? computeLengthForSubmission(plainText(document().frame()->selection().selection().toNormalizedRange().get())) : 0; 302 ASSERT(currentLength >= selectionLength); 303 unsigned baseLength = currentLength - selectionLength; 304 unsigned appendableLength = unsignedMaxLength > baseLength ? unsignedMaxLength - baseLength : 0; 305 event->setText(sanitizeUserInputValue(event->text(), appendableLength)); 306 } 307 308 String HTMLTextAreaElement::sanitizeUserInputValue(const String& proposedValue, unsigned maxLength) 309 { 310 if (maxLength > 0 && U16_IS_LEAD(proposedValue[maxLength - 1])) 311 --maxLength; 312 return proposedValue.left(maxLength); 313 } 314 315 void HTMLTextAreaElement::rendererWillBeDestroyed() 316 { 317 updateValue(); 318 } 319 320 void HTMLTextAreaElement::updateValue() const 321 { 322 if (formControlValueMatchesRenderer()) 323 return; 324 325 ASSERT(renderer()); 326 m_value = innerTextValue(); 327 const_cast<HTMLTextAreaElement*>(this)->setFormControlValueMatchesRenderer(true); 328 const_cast<HTMLTextAreaElement*>(this)->notifyFormStateChanged(); 329 m_isDirty = true; 330 const_cast<HTMLTextAreaElement*>(this)->updatePlaceholderVisibility(false); 331 } 332 333 String HTMLTextAreaElement::value() const 334 { 335 updateValue(); 336 return m_value; 337 } 338 339 void HTMLTextAreaElement::setValue(const String& value) 340 { 341 setValueCommon(value); 342 m_isDirty = true; 343 setNeedsValidityCheck(); 344 } 345 346 void HTMLTextAreaElement::setNonDirtyValue(const String& value) 347 { 348 setValueCommon(value); 349 m_isDirty = false; 350 setNeedsValidityCheck(); 351 } 352 353 void HTMLTextAreaElement::setValueCommon(const String& newValue) 354 { 355 // Code elsewhere normalizes line endings added by the user via the keyboard or pasting. 356 // We normalize line endings coming from JavaScript here. 357 String normalizedValue = newValue.isNull() ? "" : newValue; 358 normalizedValue.replace("\r\n", "\n"); 359 normalizedValue.replace('\r', '\n'); 360 361 // Return early because we don't want to move the caret or trigger other side effects 362 // when the value isn't changing. This matches Firefox behavior, at least. 363 if (normalizedValue == value()) 364 return; 365 366 m_value = normalizedValue; 367 setInnerTextValue(m_value); 368 setLastChangeWasNotUserEdit(); 369 updatePlaceholderVisibility(false); 370 setNeedsStyleRecalc(); 371 setFormControlValueMatchesRenderer(true); 372 373 // Set the caret to the end of the text value. 374 if (document().focusedElement() == this) { 375 unsigned endOfString = m_value.length(); 376 setSelectionRange(endOfString, endOfString); 377 } 378 379 notifyFormStateChanged(); 380 setTextAsOfLastFormControlChangeEvent(normalizedValue); 381 } 382 383 String HTMLTextAreaElement::defaultValue() const 384 { 385 StringBuilder value; 386 387 // Since there may be comments, ignore nodes other than text nodes. 388 for (Node* n = firstChild(); n; n = n->nextSibling()) { 389 if (n->isTextNode()) 390 value.append(toText(n)->data()); 391 } 392 393 return value.toString(); 394 } 395 396 void HTMLTextAreaElement::setDefaultValue(const String& defaultValue) 397 { 398 RefPtr<Node> protectFromMutationEvents(this); 399 400 // To preserve comments, remove only the text nodes, then add a single text node. 401 Vector<RefPtr<Node> > textNodes; 402 for (Node* n = firstChild(); n; n = n->nextSibling()) { 403 if (n->isTextNode()) 404 textNodes.append(n); 405 } 406 size_t size = textNodes.size(); 407 for (size_t i = 0; i < size; ++i) 408 removeChild(textNodes[i].get(), IGNORE_EXCEPTION); 409 410 // Normalize line endings. 411 String value = defaultValue; 412 value.replace("\r\n", "\n"); 413 value.replace('\r', '\n'); 414 415 insertBefore(document().createTextNode(value), firstChild(), IGNORE_EXCEPTION); 416 417 if (!m_isDirty) 418 setNonDirtyValue(value); 419 } 420 421 int HTMLTextAreaElement::maxLength() const 422 { 423 bool ok; 424 int value = getAttribute(maxlengthAttr).string().toInt(&ok); 425 return ok && value >= 0 ? value : -1; 426 } 427 428 void HTMLTextAreaElement::setMaxLength(int newValue, ExceptionState& exceptionState) 429 { 430 if (newValue < 0) 431 exceptionState.throwDOMException(IndexSizeError, "The value provided (" + String::number(newValue) + ") is not positive or 0."); 432 else 433 setIntegralAttribute(maxlengthAttr, newValue); 434 } 435 436 String HTMLTextAreaElement::validationMessage() const 437 { 438 if (!willValidate()) 439 return String(); 440 441 if (customError()) 442 return customValidationMessage(); 443 444 if (valueMissing()) 445 return locale().queryString(blink::WebLocalizedString::ValidationValueMissing); 446 447 if (tooLong()) 448 return locale().validationMessageTooLongText(computeLengthForSubmission(value()), maxLength()); 449 450 return String(); 451 } 452 453 bool HTMLTextAreaElement::valueMissing() const 454 { 455 return willValidate() && valueMissing(value()); 456 } 457 458 bool HTMLTextAreaElement::tooLong() const 459 { 460 return willValidate() && tooLong(value(), CheckDirtyFlag); 461 } 462 463 bool HTMLTextAreaElement::tooLong(const String& value, NeedsToCheckDirtyFlag check) const 464 { 465 // Return false for the default value or value set by script even if it is 466 // longer than maxLength. 467 if (check == CheckDirtyFlag && !lastChangeWasUserEdit()) 468 return false; 469 470 int max = maxLength(); 471 if (max < 0) 472 return false; 473 return computeLengthForSubmission(value) > static_cast<unsigned>(max); 474 } 475 476 bool HTMLTextAreaElement::isValidValue(const String& candidate) const 477 { 478 return !valueMissing(candidate) && !tooLong(candidate, IgnoreDirtyFlag); 479 } 480 481 void HTMLTextAreaElement::accessKeyAction(bool) 482 { 483 focus(); 484 } 485 486 void HTMLTextAreaElement::setCols(int cols) 487 { 488 setIntegralAttribute(colsAttr, cols); 489 } 490 491 void HTMLTextAreaElement::setRows(int rows) 492 { 493 setIntegralAttribute(rowsAttr, rows); 494 } 495 496 bool HTMLTextAreaElement::shouldUseInputMethod() 497 { 498 return true; 499 } 500 501 bool HTMLTextAreaElement::matchesReadOnlyPseudoClass() const 502 { 503 return isReadOnly(); 504 } 505 506 bool HTMLTextAreaElement::matchesReadWritePseudoClass() const 507 { 508 return !isReadOnly(); 509 } 510 511 void HTMLTextAreaElement::updatePlaceholderText() 512 { 513 HTMLElement* placeholder = placeholderElement(); 514 String placeholderText = strippedPlaceholder(); 515 if (placeholderText.isEmpty()) { 516 if (placeholder) 517 userAgentShadowRoot()->removeChild(placeholder); 518 return; 519 } 520 if (!placeholder) { 521 RefPtr<HTMLDivElement> newElement = HTMLDivElement::create(document()); 522 placeholder = newElement.get(); 523 placeholder->setPseudo(AtomicString("-webkit-input-placeholder", AtomicString::ConstructFromLiteral)); 524 placeholder->setAttribute(idAttr, ShadowElementNames::placeholder()); 525 userAgentShadowRoot()->insertBefore(placeholder, innerTextElement()->nextSibling()); 526 } 527 placeholder->setTextContent(placeholderText); 528 } 529 530 bool HTMLTextAreaElement::isInteractiveContent() const 531 { 532 return true; 533 } 534 535 } 536