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/BeforeTextInsertedEvent.h" 34 #include "core/dom/Document.h" 35 #include "core/dom/Event.h" 36 #include "core/dom/EventNames.h" 37 #include "core/dom/ExceptionCode.h" 38 #include "core/dom/Text.h" 39 #include "core/dom/shadow/ShadowRoot.h" 40 #include "core/editing/FrameSelection.h" 41 #include "core/editing/TextIterator.h" 42 #include "core/html/FormController.h" 43 #include "core/html/FormDataList.h" 44 #include "core/html/shadow/TextControlInnerElements.h" 45 #include "core/page/Frame.h" 46 #include "core/platform/LocalizedStrings.h" 47 #include "core/rendering/RenderTextControlMultiLine.h" 48 #include "wtf/StdLibExtras.h" 49 #include "wtf/text/StringBuilder.h" 50 51 namespace WebCore { 52 53 using namespace HTMLNames; 54 55 static const int defaultRows = 2; 56 static const int defaultCols = 20; 57 58 // On submission, LF characters are converted into CRLF. 59 // This function returns number of characters considering this. 60 static inline unsigned computeLengthForSubmission(const String& text, unsigned numberOfLineBreaks) 61 { 62 return text.length() + numberOfLineBreaks; 63 } 64 65 static unsigned numberOfLineBreaks(const String& text) 66 { 67 unsigned length = text.length(); 68 unsigned count = 0; 69 for (unsigned i = 0; i < length; i++) { 70 if (text[i] == '\n') 71 count++; 72 } 73 return count; 74 } 75 76 static inline unsigned computeLengthForSubmission(const String& text) 77 { 78 return text.length() + numberOfLineBreaks(text); 79 } 80 81 HTMLTextAreaElement::HTMLTextAreaElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form) 82 : HTMLTextFormControlElement(tagName, document, form) 83 , m_rows(defaultRows) 84 , m_cols(defaultCols) 85 , m_wrap(SoftWrap) 86 , m_placeholder(0) 87 , m_isDirty(false) 88 , m_wasModifiedByUser(false) 89 { 90 ASSERT(hasTagName(textareaTag)); 91 setFormControlValueMatchesRenderer(true); 92 ScriptWrappable::init(this); 93 } 94 95 PassRefPtr<HTMLTextAreaElement> HTMLTextAreaElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form) 96 { 97 RefPtr<HTMLTextAreaElement> textArea = adoptRef(new HTMLTextAreaElement(tagName, document, form)); 98 textArea->ensureUserAgentShadowRoot(); 99 return textArea.release(); 100 } 101 102 void HTMLTextAreaElement::didAddUserAgentShadowRoot(ShadowRoot* root) 103 { 104 root->appendChild(TextControlInnerTextElement::create(document()), ASSERT_NO_EXCEPTION); 105 } 106 107 const AtomicString& HTMLTextAreaElement::formControlType() const 108 { 109 DEFINE_STATIC_LOCAL(const AtomicString, textarea, ("textarea", AtomicString::ConstructFromLiteral)); 110 return textarea; 111 } 112 113 FormControlState HTMLTextAreaElement::saveFormControlState() const 114 { 115 return m_isDirty ? FormControlState(value()) : FormControlState(); 116 } 117 118 void HTMLTextAreaElement::restoreFormControlState(const FormControlState& state) 119 { 120 setValue(state[0]); 121 } 122 123 void HTMLTextAreaElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta) 124 { 125 HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); 126 setLastChangeWasNotUserEdit(); 127 if (m_isDirty) 128 setInnerTextValue(value()); 129 else 130 setNonDirtyValue(defaultValue()); 131 } 132 133 bool HTMLTextAreaElement::isPresentationAttribute(const QualifiedName& name) const 134 { 135 if (name == alignAttr) { 136 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do. 137 // See http://bugs.webkit.org/show_bug.cgi?id=7075 138 return false; 139 } 140 141 if (name == wrapAttr) 142 return true; 143 return HTMLTextFormControlElement::isPresentationAttribute(name); 144 } 145 146 void HTMLTextAreaElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style) 147 { 148 if (name == wrapAttr) { 149 if (shouldWrapText()) { 150 addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValuePreWrap); 151 addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueBreakWord); 152 } else { 153 addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValuePre); 154 addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueNormal); 155 } 156 } else 157 HTMLTextFormControlElement::collectStyleForPresentationAttribute(name, value, style); 158 } 159 160 void HTMLTextAreaElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 161 { 162 if (name == rowsAttr) { 163 int rows = value.toInt(); 164 if (rows <= 0) 165 rows = defaultRows; 166 if (m_rows != rows) { 167 m_rows = rows; 168 if (renderer()) 169 renderer()->setNeedsLayoutAndPrefWidthsRecalc(); 170 } 171 } else if (name == colsAttr) { 172 int cols = value.toInt(); 173 if (cols <= 0) 174 cols = defaultCols; 175 if (m_cols != cols) { 176 m_cols = cols; 177 if (renderer()) 178 renderer()->setNeedsLayoutAndPrefWidthsRecalc(); 179 } 180 } else if (name == wrapAttr) { 181 // The virtual/physical values were a Netscape extension of HTML 3.0, now deprecated. 182 // The soft/hard /off values are a recommendation for HTML 4 extension by IE and NS 4. 183 WrapMethod wrap; 184 if (equalIgnoringCase(value, "physical") || equalIgnoringCase(value, "hard") || equalIgnoringCase(value, "on")) 185 wrap = HardWrap; 186 else if (equalIgnoringCase(value, "off")) 187 wrap = NoWrap; 188 else 189 wrap = SoftWrap; 190 if (wrap != m_wrap) { 191 m_wrap = wrap; 192 if (renderer()) 193 renderer()->setNeedsLayoutAndPrefWidthsRecalc(); 194 } 195 } else if (name == accesskeyAttr) { 196 // ignore for the moment 197 } else if (name == maxlengthAttr) 198 setNeedsValidityCheck(); 199 else 200 HTMLTextFormControlElement::parseAttribute(name, value); 201 } 202 203 RenderObject* HTMLTextAreaElement::createRenderer(RenderStyle*) 204 { 205 return new RenderTextControlMultiLine(this); 206 } 207 208 bool HTMLTextAreaElement::appendFormData(FormDataList& encoding, bool) 209 { 210 if (name().isEmpty()) 211 return false; 212 213 document()->updateLayout(); 214 215 const String& text = (m_wrap == HardWrap) ? valueWithHardLineBreaks() : value(); 216 encoding.appendData(name(), text); 217 218 const AtomicString& dirnameAttrValue = fastGetAttribute(dirnameAttr); 219 if (!dirnameAttrValue.isNull()) 220 encoding.appendData(dirnameAttrValue, directionForFormData()); 221 return true; 222 } 223 224 void HTMLTextAreaElement::reset() 225 { 226 setNonDirtyValue(defaultValue()); 227 } 228 229 bool HTMLTextAreaElement::hasCustomFocusLogic() const 230 { 231 return true; 232 } 233 234 bool HTMLTextAreaElement::isKeyboardFocusable() const 235 { 236 // If a given text area can be focused at all, then it will always be keyboard focusable. 237 return isFocusable(); 238 } 239 240 bool HTMLTextAreaElement::shouldShowFocusRingOnMouseFocus() const 241 { 242 return true; 243 } 244 245 void HTMLTextAreaElement::updateFocusAppearance(bool restorePreviousSelection) 246 { 247 if (!restorePreviousSelection || !hasCachedSelection()) { 248 // If this is the first focus, set a caret at the beginning of the text. 249 // This matches some browsers' behavior; see bug 11746 Comment #15. 250 // http://bugs.webkit.org/show_bug.cgi?id=11746#c15 251 setSelectionRange(0, 0); 252 } else 253 restoreCachedSelection(); 254 255 if (document()->frame()) 256 document()->frame()->selection()->revealSelection(); 257 } 258 259 void HTMLTextAreaElement::defaultEventHandler(Event* event) 260 { 261 if (renderer() && (event->isMouseEvent() || event->isDragEvent() || event->hasInterface(eventNames().interfaceForWheelEvent) || event->type() == eventNames().blurEvent)) 262 forwardEvent(event); 263 else if (renderer() && event->isBeforeTextInsertedEvent()) 264 handleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(event)); 265 266 HTMLTextFormControlElement::defaultEventHandler(event); 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 HTMLElement* HTMLTextAreaElement::innerTextElement() const 316 { 317 Node* node = userAgentShadowRoot()->firstChild(); 318 ASSERT(!node || node->hasTagName(divTag)); 319 return toHTMLElement(node); 320 } 321 322 void HTMLTextAreaElement::rendererWillBeDestroyed() 323 { 324 updateValue(); 325 } 326 327 void HTMLTextAreaElement::updateValue() const 328 { 329 if (formControlValueMatchesRenderer()) 330 return; 331 332 ASSERT(renderer()); 333 m_value = innerTextValue(); 334 const_cast<HTMLTextAreaElement*>(this)->setFormControlValueMatchesRenderer(true); 335 const_cast<HTMLTextAreaElement*>(this)->notifyFormStateChanged(); 336 m_isDirty = true; 337 m_wasModifiedByUser = true; 338 const_cast<HTMLTextAreaElement*>(this)->updatePlaceholderVisibility(false); 339 } 340 341 String HTMLTextAreaElement::value() const 342 { 343 updateValue(); 344 return m_value; 345 } 346 347 void HTMLTextAreaElement::setValue(const String& value) 348 { 349 setValueCommon(value); 350 m_isDirty = true; 351 setNeedsValidityCheck(); 352 } 353 354 void HTMLTextAreaElement::setNonDirtyValue(const String& value) 355 { 356 setValueCommon(value); 357 m_isDirty = false; 358 setNeedsValidityCheck(); 359 } 360 361 void HTMLTextAreaElement::setValueCommon(const String& newValue) 362 { 363 m_wasModifiedByUser = false; 364 // Code elsewhere normalizes line endings added by the user via the keyboard or pasting. 365 // We normalize line endings coming from JavaScript here. 366 String normalizedValue = newValue.isNull() ? "" : newValue; 367 normalizedValue.replace("\r\n", "\n"); 368 normalizedValue.replace('\r', '\n'); 369 370 // Return early because we don't want to move the caret or trigger other side effects 371 // when the value isn't changing. This matches Firefox behavior, at least. 372 if (normalizedValue == value()) 373 return; 374 375 m_value = normalizedValue; 376 setInnerTextValue(m_value); 377 setLastChangeWasNotUserEdit(); 378 updatePlaceholderVisibility(false); 379 setNeedsStyleRecalc(); 380 setFormControlValueMatchesRenderer(true); 381 382 // Set the caret to the end of the text value. 383 if (document()->focusedElement() == this) { 384 unsigned endOfString = m_value.length(); 385 setSelectionRange(endOfString, endOfString); 386 } 387 388 notifyFormStateChanged(); 389 setTextAsOfLastFormControlChangeEvent(normalizedValue); 390 } 391 392 String HTMLTextAreaElement::defaultValue() const 393 { 394 StringBuilder value; 395 396 // Since there may be comments, ignore nodes other than text nodes. 397 for (Node* n = firstChild(); n; n = n->nextSibling()) { 398 if (n->isTextNode()) 399 value.append(toText(n)->data()); 400 } 401 402 return value.toString(); 403 } 404 405 void HTMLTextAreaElement::setDefaultValue(const String& defaultValue) 406 { 407 RefPtr<Node> protectFromMutationEvents(this); 408 409 // To preserve comments, remove only the text nodes, then add a single text node. 410 Vector<RefPtr<Node> > textNodes; 411 for (Node* n = firstChild(); n; n = n->nextSibling()) { 412 if (n->isTextNode()) 413 textNodes.append(n); 414 } 415 size_t size = textNodes.size(); 416 for (size_t i = 0; i < size; ++i) 417 removeChild(textNodes[i].get(), IGNORE_EXCEPTION); 418 419 // Normalize line endings. 420 String value = defaultValue; 421 value.replace("\r\n", "\n"); 422 value.replace('\r', '\n'); 423 424 insertBefore(document()->createTextNode(value), firstChild(), IGNORE_EXCEPTION); 425 426 if (!m_isDirty) 427 setNonDirtyValue(value); 428 } 429 430 int HTMLTextAreaElement::maxLength() const 431 { 432 bool ok; 433 int value = getAttribute(maxlengthAttr).string().toInt(&ok); 434 return ok && value >= 0 ? value : -1; 435 } 436 437 void HTMLTextAreaElement::setMaxLength(int newValue, ExceptionState& es) 438 { 439 if (newValue < 0) 440 es.throwDOMException(IndexSizeError); 441 else 442 setAttribute(maxlengthAttr, String::number(newValue)); 443 } 444 445 String HTMLTextAreaElement::validationMessage() const 446 { 447 if (!willValidate()) 448 return String(); 449 450 if (customError()) 451 return customValidationMessage(); 452 453 if (valueMissing()) 454 return validationMessageValueMissingText(); 455 456 if (tooLong()) 457 return validationMessageTooLongText(computeLengthForSubmission(value()), maxLength()); 458 459 return String(); 460 } 461 462 bool HTMLTextAreaElement::valueMissing() const 463 { 464 return willValidate() && valueMissing(value()); 465 } 466 467 bool HTMLTextAreaElement::tooLong() const 468 { 469 return willValidate() && tooLong(value(), CheckDirtyFlag); 470 } 471 472 bool HTMLTextAreaElement::tooLong(const String& value, NeedsToCheckDirtyFlag check) const 473 { 474 // Return false for the default value or value set by script even if it is 475 // longer than maxLength. 476 if (check == CheckDirtyFlag && !m_wasModifiedByUser) 477 return false; 478 479 int max = maxLength(); 480 if (max < 0) 481 return false; 482 return computeLengthForSubmission(value) > static_cast<unsigned>(max); 483 } 484 485 bool HTMLTextAreaElement::isValidValue(const String& candidate) const 486 { 487 return !valueMissing(candidate) && !tooLong(candidate, IgnoreDirtyFlag); 488 } 489 490 void HTMLTextAreaElement::accessKeyAction(bool) 491 { 492 focus(); 493 } 494 495 void HTMLTextAreaElement::setCols(int cols) 496 { 497 setAttribute(colsAttr, String::number(cols)); 498 } 499 500 void HTMLTextAreaElement::setRows(int rows) 501 { 502 setAttribute(rowsAttr, String::number(rows)); 503 } 504 505 bool HTMLTextAreaElement::shouldUseInputMethod() 506 { 507 return true; 508 } 509 510 HTMLElement* HTMLTextAreaElement::placeholderElement() const 511 { 512 return m_placeholder; 513 } 514 515 void HTMLTextAreaElement::attach(const AttachContext& context) 516 { 517 HTMLTextFormControlElement::attach(context); 518 fixPlaceholderRenderer(m_placeholder, innerTextElement()); 519 } 520 521 bool HTMLTextAreaElement::matchesReadOnlyPseudoClass() const 522 { 523 return isReadOnly(); 524 } 525 526 bool HTMLTextAreaElement::matchesReadWritePseudoClass() const 527 { 528 return !isReadOnly(); 529 } 530 531 void HTMLTextAreaElement::updatePlaceholderText() 532 { 533 String placeholderText = strippedPlaceholder(); 534 if (placeholderText.isEmpty()) { 535 if (m_placeholder) { 536 userAgentShadowRoot()->removeChild(m_placeholder, ASSERT_NO_EXCEPTION); 537 m_placeholder = 0; 538 } 539 return; 540 } 541 if (!m_placeholder) { 542 RefPtr<HTMLDivElement> placeholder = HTMLDivElement::create(document()); 543 m_placeholder = placeholder.get(); 544 m_placeholder->setPart(AtomicString("-webkit-input-placeholder", AtomicString::ConstructFromLiteral)); 545 userAgentShadowRoot()->insertBefore(m_placeholder, innerTextElement()->nextSibling(), ASSERT_NO_EXCEPTION); 546 } 547 m_placeholder->setInnerText(placeholderText, ASSERT_NO_EXCEPTION); 548 fixPlaceholderRenderer(m_placeholder, innerTextElement()); 549 } 550 551 } 552