Update std::expected to match libc++ coding style
[WebKit-https.git] / Source / WebCore / html / HTMLTextAreaElement.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2001 Dirk Mueller (mueller@kde.org)
5  * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
6  *           (C) 2006 Alexey Proskuryakov (ap@nypop.com)
7  * Copyright (C) 2007 Samuel Weinig (sam@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 "HTMLTextAreaElement.h"
28
29 #include "BeforeTextInsertedEvent.h"
30 #include "CSSValueKeywords.h"
31 #include "DOMFormData.h"
32 #include "Document.h"
33 #include "Editor.h"
34 #include "ElementChildIterator.h"
35 #include "Event.h"
36 #include "EventNames.h"
37 #include "FormController.h"
38 #include "Frame.h"
39 #include "FrameSelection.h"
40 #include "HTMLNames.h"
41 #include "HTMLParserIdioms.h"
42 #include "LocalizedStrings.h"
43 #include "RenderTextControlMultiLine.h"
44 #include "ShadowRoot.h"
45 #include "Text.h"
46 #include "TextControlInnerElements.h"
47 #include "TextIterator.h"
48 #include "TextNodeTraversal.h"
49 #include <wtf/Ref.h>
50 #include <wtf/StdLibExtras.h>
51
52 namespace WebCore {
53
54 using namespace HTMLNames;
55
56 static const int defaultRows = 2;
57 static const int defaultCols = 20;
58
59 // On submission, LF characters are converted into CRLF.
60 // This function returns number of characters considering this.
61 static inline unsigned computeLengthForSubmission(StringView text, unsigned numberOfLineBreaks)
62 {
63     return numGraphemeClusters(text) + numberOfLineBreaks;
64 }
65
66 static unsigned numberOfLineBreaks(StringView text)
67 {
68     unsigned length = text.length();
69     unsigned count = 0;
70     for (unsigned i = 0; i < length; i++) {
71         if (text[i] == '\n')
72             count++;
73     }
74     return count;
75 }
76
77 static inline unsigned computeLengthForSubmission(StringView text)
78 {
79     return numGraphemeClusters(text) + numberOfLineBreaks(text);
80 }
81
82 static inline unsigned upperBoundForLengthForSubmission(StringView text, unsigned numberOfLineBreaks)
83 {
84     return text.length() + numberOfLineBreaks;
85 }
86
87 HTMLTextAreaElement::HTMLTextAreaElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
88     : HTMLTextFormControlElement(tagName, document, form)
89     , m_rows(defaultRows)
90     , m_cols(defaultCols)
91 {
92     ASSERT(hasTagName(textareaTag));
93     setFormControlValueMatchesRenderer(true);
94 }
95
96 Ref<HTMLTextAreaElement> HTMLTextAreaElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
97 {
98     auto textArea = adoptRef(*new HTMLTextAreaElement(tagName, document, form));
99     textArea->ensureUserAgentShadowRoot();
100     return textArea;
101 }
102
103 void HTMLTextAreaElement::didAddUserAgentShadowRoot(ShadowRoot& root)
104 {
105     root.appendChild(TextControlInnerTextElement::create(document()));
106     updateInnerTextElementEditability();
107 }
108
109 const AtomicString& HTMLTextAreaElement::formControlType() const
110 {
111     static NeverDestroyed<const AtomicString> textarea("textarea", AtomicString::ConstructFromLiteral);
112     return textarea;
113 }
114
115 FormControlState HTMLTextAreaElement::saveFormControlState() const
116 {
117     return m_isDirty ? FormControlState { { value() } } : FormControlState { };
118 }
119
120 void HTMLTextAreaElement::restoreFormControlState(const FormControlState& state)
121 {
122     setValue(state[0]);
123 }
124
125 void HTMLTextAreaElement::childrenChanged(const ChildChange& change)
126 {
127     HTMLElement::childrenChanged(change);
128     setLastChangeWasNotUserEdit();
129     if (m_isDirty)
130         setInnerTextValue(value());
131     else
132         setNonDirtyValue(defaultValue());
133 }
134
135 bool HTMLTextAreaElement::isPresentationAttribute(const QualifiedName& name) const
136 {
137     if (name == alignAttr) {
138         // Don't map 'align' attribute.  This matches what Firefox, Opera and IE do.
139         // See http://bugs.webkit.org/show_bug.cgi?id=7075
140         return false;
141     }
142
143     if (name == wrapAttr)
144         return true;
145     return HTMLTextFormControlElement::isPresentationAttribute(name);
146 }
147
148 void HTMLTextAreaElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
149 {
150     if (name == wrapAttr) {
151         if (shouldWrapText()) {
152             addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValuePreWrap);
153             addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueBreakWord);
154         } else {
155             addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValuePre);
156             addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueNormal);
157         }
158     } else
159         HTMLTextFormControlElement::collectStyleForPresentationAttribute(name, value, style);
160 }
161
162 void HTMLTextAreaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
163 {
164     if (name == rowsAttr) {
165         unsigned rows = limitToOnlyHTMLNonNegativeNumbersGreaterThanZero(value, defaultRows);
166         if (m_rows != rows) {
167             m_rows = rows;
168             if (renderer())
169                 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
170         }
171     } else if (name == colsAttr) {
172         unsigned cols = limitToOnlyHTMLNonNegativeNumbersGreaterThanZero(value, defaultCols);
173         if (m_cols != cols) {
174             m_cols = cols;
175             if (renderer())
176                 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
177         }
178     } else if (name == wrapAttr) {
179         // The virtual/physical values were a Netscape extension of HTML 3.0, now deprecated.
180         // The soft/hard /off values are a recommendation for HTML 4 extension by IE and NS 4.
181         WrapMethod wrap;
182         if (equalLettersIgnoringASCIICase(value, "physical") || equalLettersIgnoringASCIICase(value, "hard") || equalLettersIgnoringASCIICase(value, "on"))
183             wrap = HardWrap;
184         else if (equalLettersIgnoringASCIICase(value, "off"))
185             wrap = NoWrap;
186         else
187             wrap = SoftWrap;
188         if (wrap != m_wrap) {
189             m_wrap = wrap;
190             if (renderer())
191                 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
192         }
193     } else if (name == accesskeyAttr) {
194         // ignore for the moment
195     } else if (name == maxlengthAttr)
196         maxLengthAttributeChanged(value);
197     else if (name == minlengthAttr)
198         minLengthAttributeChanged(value);
199     else
200         HTMLTextFormControlElement::parseAttribute(name, value);
201 }
202
203 void HTMLTextAreaElement::maxLengthAttributeChanged(const AtomicString& newValue)
204 {
205     internalSetMaxLength(parseHTMLNonNegativeInteger(newValue).value_or(-1));
206     updateValidity();
207 }
208
209 void HTMLTextAreaElement::minLengthAttributeChanged(const AtomicString& newValue)
210 {
211     internalSetMinLength(parseHTMLNonNegativeInteger(newValue).value_or(-1));
212     updateValidity();
213 }
214
215 RenderPtr<RenderElement> HTMLTextAreaElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
216 {
217     return createRenderer<RenderTextControlMultiLine>(*this, WTFMove(style));
218 }
219
220 bool HTMLTextAreaElement::appendFormData(DOMFormData& formData, bool)
221 {
222     if (name().isEmpty())
223         return false;
224
225     document().updateLayout();
226
227     formData.append(name(), m_wrap == HardWrap ? valueWithHardLineBreaks() : value());
228
229     auto& dirnameAttrValue = attributeWithoutSynchronization(dirnameAttr);
230     if (!dirnameAttrValue.isNull())
231         formData.append(dirnameAttrValue, directionForFormData());
232
233     return true;    
234 }
235
236 void HTMLTextAreaElement::reset()
237 {
238     setNonDirtyValue(defaultValue());
239 }
240
241 bool HTMLTextAreaElement::hasCustomFocusLogic() const
242 {
243     return true;
244 }
245
246 bool HTMLTextAreaElement::isKeyboardFocusable(KeyboardEvent&) const
247 {
248     // If a given text area can be focused at all, then it will always be keyboard focusable.
249     return isFocusable();
250 }
251
252 bool HTMLTextAreaElement::isMouseFocusable() const
253 {
254     return isFocusable();
255 }
256
257 void HTMLTextAreaElement::updateFocusAppearance(SelectionRestorationMode restorationMode, SelectionRevealMode revealMode)
258 {
259     if (restorationMode == SelectionRestorationMode::SetDefault || !hasCachedSelection()) {
260         // If this is the first focus, set a caret at the beginning of the text.  
261         // This matches some browsers' behavior; see bug 11746 Comment #15.
262         // http://bugs.webkit.org/show_bug.cgi?id=11746#c15
263         setSelectionRange(0, 0, SelectionHasNoDirection, Element::defaultFocusTextStateChangeIntent());
264     } else
265         restoreCachedSelection(Element::defaultFocusTextStateChangeIntent());
266
267     if (document().frame())
268         document().frame()->selection().revealSelection(revealMode);
269 }
270
271 void HTMLTextAreaElement::defaultEventHandler(Event& event)
272 {
273     if (renderer() && (event.isMouseEvent() || event.type() == eventNames().blurEvent))
274         forwardEvent(event);
275     else if (renderer() && is<BeforeTextInsertedEvent>(event))
276         handleBeforeTextInsertedEvent(downcast<BeforeTextInsertedEvent>(event));
277
278     HTMLTextFormControlElement::defaultEventHandler(event);
279 }
280
281 void HTMLTextAreaElement::subtreeHasChanged()
282 {
283     setChangedSinceLastFormControlChangeEvent(true);
284     setFormControlValueMatchesRenderer(false);
285     updateValidity();
286
287     if (!focused())
288         return;
289
290     if (RefPtr<Frame> frame = document().frame())
291         frame->editor().textDidChangeInTextArea(this);
292     // When typing in a textarea, childrenChanged is not called, so we need to force the directionality check.
293     calculateAndAdjustDirectionality();
294 }
295
296 void HTMLTextAreaElement::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent& event) const
297 {
298     ASSERT(renderer());
299     int signedMaxLength = effectiveMaxLength();
300     if (signedMaxLength < 0)
301         return;
302     unsigned unsignedMaxLength = static_cast<unsigned>(signedMaxLength);
303
304     const String& currentValue = innerTextValue();
305     unsigned numberOfLineBreaksInCurrentValue = numberOfLineBreaks(currentValue);
306     if (upperBoundForLengthForSubmission(currentValue, numberOfLineBreaksInCurrentValue)
307         + upperBoundForLengthForSubmission(event.text(), numberOfLineBreaks(event.text())) < unsignedMaxLength)
308         return;
309
310     unsigned currentLength = computeLengthForSubmission(currentValue, numberOfLineBreaksInCurrentValue);
311     // selectionLength represents the selection length of this text field to be
312     // removed by this insertion.
313     // If the text field has no focus, we don't need to take account of the
314     // selection length. The selection is the source of text drag-and-drop in
315     // that case, and nothing in the text field will be removed.
316     unsigned selectionLength = focused() ? computeLengthForSubmission(plainText(document().frame()->selection().selection().toNormalizedRange().get())) : 0;
317     ASSERT(currentLength >= selectionLength);
318     unsigned baseLength = currentLength - selectionLength;
319     unsigned appendableLength = unsignedMaxLength > baseLength ? unsignedMaxLength - baseLength : 0;
320     event.setText(sanitizeUserInputValue(event.text(), appendableLength));
321 }
322
323 String HTMLTextAreaElement::sanitizeUserInputValue(const String& proposedValue, unsigned maxLength)
324 {
325     return proposedValue.left(numCharactersInGraphemeClusters(proposedValue, maxLength));
326 }
327
328 RefPtr<TextControlInnerTextElement> HTMLTextAreaElement::innerTextElement() const
329 {
330     RefPtr<ShadowRoot> root = userAgentShadowRoot();
331     if (!root)
332         return nullptr;
333     
334     return childrenOfType<TextControlInnerTextElement>(*root).first();
335 }
336
337 void HTMLTextAreaElement::rendererWillBeDestroyed()
338 {
339     updateValue();
340 }
341
342 void HTMLTextAreaElement::updateValue() const
343 {
344     if (formControlValueMatchesRenderer())
345         return;
346
347     ASSERT(renderer());
348     m_value = innerTextValue();
349     const_cast<HTMLTextAreaElement*>(this)->setFormControlValueMatchesRenderer(true);
350     m_isDirty = true;
351     m_wasModifiedByUser = true;
352     const_cast<HTMLTextAreaElement*>(this)->updatePlaceholderVisibility();
353 }
354
355 String HTMLTextAreaElement::value() const
356 {
357     updateValue();
358     return m_value;
359 }
360
361 void HTMLTextAreaElement::setValue(const String& value)
362 {
363     setValueCommon(value);
364     m_isDirty = true;
365     updateValidity();
366 }
367
368 void HTMLTextAreaElement::setNonDirtyValue(const String& value)
369 {
370     setValueCommon(value);
371     m_isDirty = false;
372     updateValidity();
373 }
374
375 void HTMLTextAreaElement::setValueCommon(const String& newValue)
376 {
377     m_wasModifiedByUser = false;
378     // Code elsewhere normalizes line endings added by the user via the keyboard or pasting.
379     // We normalize line endings coming from JavaScript here.
380     String normalizedValue = newValue.isNull() ? emptyString() : newValue;
381     normalizedValue.replace("\r\n", "\n");
382     normalizedValue.replace('\r', '\n');
383
384     // Return early because we don't want to move the caret or trigger other side effects
385     // when the value isn't changing. This matches Firefox behavior, at least.
386     if (normalizedValue == value())
387         return;
388
389     m_value = normalizedValue;
390     setInnerTextValue(m_value);
391     setLastChangeWasNotUserEdit();
392     updatePlaceholderVisibility();
393     invalidateStyleForSubtree();
394     setFormControlValueMatchesRenderer(true);
395
396     // Set the caret to the end of the text value.
397     if (document().focusedElement() == this) {
398         unsigned endOfString = m_value.length();
399         setSelectionRange(endOfString, endOfString);
400     }
401
402     setTextAsOfLastFormControlChangeEvent(normalizedValue);
403 }
404
405 String HTMLTextAreaElement::defaultValue() const
406 {
407     return TextNodeTraversal::childTextContent(*this);
408 }
409
410 void HTMLTextAreaElement::setDefaultValue(const String& defaultValue)
411 {
412     setTextContent(defaultValue);
413 }
414
415 String HTMLTextAreaElement::validationMessage() const
416 {
417     if (!willValidate())
418         return String();
419
420     if (customError())
421         return customValidationMessage();
422
423     if (valueMissing())
424         return validationMessageValueMissingText();
425
426     if (tooShort())
427         return validationMessageTooShortText(computeLengthForSubmission(value()), minLength());
428
429     if (tooLong())
430         return validationMessageTooLongText(computeLengthForSubmission(value()), effectiveMaxLength());
431
432     return String();
433 }
434
435 bool HTMLTextAreaElement::valueMissing() const
436 {
437     return willValidate() && valueMissing(value());
438 }
439
440 bool HTMLTextAreaElement::tooShort() const
441 {
442     return willValidate() && tooShort(value(), CheckDirtyFlag);
443 }
444
445 bool HTMLTextAreaElement::tooShort(StringView value, NeedsToCheckDirtyFlag check) const
446 {
447     // Return false for the default value or value set by script even if it is
448     // shorter than minLength.
449     if (check == CheckDirtyFlag && !m_wasModifiedByUser)
450         return false;
451
452     int min = minLength();
453     if (min <= 0)
454         return false;
455
456     // The empty string is excluded from tooShort validation.
457     if (value.isEmpty())
458         return false;
459
460     // FIXME: The HTML specification says that the "number of characters" is measured using code-unit length and,
461     // in the case of textarea elements, with all line breaks normalized to a single character (as opposed to CRLF pairs).
462     unsigned unsignedMin = static_cast<unsigned>(min);
463     unsigned numberOfLineBreaksInValue = numberOfLineBreaks(value);
464     return upperBoundForLengthForSubmission(value, numberOfLineBreaksInValue) < unsignedMin
465         && computeLengthForSubmission(value, numberOfLineBreaksInValue) < unsignedMin;
466 }
467
468 bool HTMLTextAreaElement::tooLong() const
469 {
470     return willValidate() && tooLong(value(), CheckDirtyFlag);
471 }
472
473 bool HTMLTextAreaElement::tooLong(StringView value, NeedsToCheckDirtyFlag check) const
474 {
475     // Return false for the default value or value set by script even if it is
476     // longer than maxLength.
477     if (check == CheckDirtyFlag && !m_wasModifiedByUser)
478         return false;
479
480     int max = effectiveMaxLength();
481     if (max < 0)
482         return false;
483
484     // FIXME: The HTML specification says that the "number of characters" is measured using code-unit length and,
485     // in the case of textarea elements, with all line breaks normalized to a single character (as opposed to CRLF pairs).
486     unsigned unsignedMax = static_cast<unsigned>(max);
487     unsigned numberOfLineBreaksInValue = numberOfLineBreaks(value);
488     return upperBoundForLengthForSubmission(value, numberOfLineBreaksInValue) > unsignedMax
489         && computeLengthForSubmission(value, numberOfLineBreaksInValue) > unsignedMax;
490 }
491
492 bool HTMLTextAreaElement::isValidValue(const String& candidate) const
493 {
494     return !valueMissing(candidate) && !tooShort(candidate, IgnoreDirtyFlag) && !tooLong(candidate, IgnoreDirtyFlag);
495 }
496
497 void HTMLTextAreaElement::accessKeyAction(bool)
498 {
499     focus();
500 }
501
502 void HTMLTextAreaElement::setCols(unsigned cols)
503 {
504     setUnsignedIntegralAttribute(colsAttr, limitToOnlyHTMLNonNegativeNumbersGreaterThanZero(cols, defaultCols));
505 }
506
507 void HTMLTextAreaElement::setRows(unsigned rows)
508 {
509     setUnsignedIntegralAttribute(rowsAttr, limitToOnlyHTMLNonNegativeNumbersGreaterThanZero(rows, defaultRows));
510 }
511
512 bool HTMLTextAreaElement::shouldUseInputMethod()
513 {
514     return true;
515 }
516
517 HTMLElement* HTMLTextAreaElement::placeholderElement() const
518 {
519     return m_placeholder.get();
520 }
521
522 bool HTMLTextAreaElement::matchesReadWritePseudoClass() const
523 {
524     return !isDisabledOrReadOnly();
525 }
526
527 void HTMLTextAreaElement::updatePlaceholderText()
528 {
529     String placeholderText = strippedPlaceholder();
530     if (placeholderText.isEmpty()) {
531         if (m_placeholder) {
532             userAgentShadowRoot()->removeChild(*m_placeholder);
533             m_placeholder = nullptr;
534         }
535         return;
536     }
537     if (!m_placeholder) {
538         m_placeholder = TextControlPlaceholderElement::create(document());
539         userAgentShadowRoot()->insertBefore(*m_placeholder, innerTextElement()->nextSibling());
540     }
541     m_placeholder->setInnerText(placeholderText);
542 }
543
544 bool HTMLTextAreaElement::willRespondToMouseClickEvents()
545 {
546     return !isDisabledFormControl();
547 }
548
549 RenderStyle HTMLTextAreaElement::createInnerTextStyle(const RenderStyle& style) const
550 {
551     auto textBlockStyle = RenderStyle::create();
552     textBlockStyle.inheritFrom(style);
553     adjustInnerTextStyle(style, textBlockStyle);
554     textBlockStyle.setDisplay(BLOCK);
555
556 #if PLATFORM(IOS)
557     // We're adding three extra pixels of padding to line textareas up with text fields.  
558     textBlockStyle.setPaddingLeft(Length(3, Fixed));
559     textBlockStyle.setPaddingRight(Length(3, Fixed));
560 #endif
561
562     return textBlockStyle;
563 }
564
565 void HTMLTextAreaElement::copyNonAttributePropertiesFromElement(const Element& source)
566 {
567     auto& sourceElement = downcast<HTMLTextAreaElement>(source);
568
569     setValueCommon(sourceElement.value());
570     m_isDirty = sourceElement.m_isDirty;
571     HTMLTextFormControlElement::copyNonAttributePropertiesFromElement(source);
572
573     updateValidity();
574 }
575
576 } // namespace WebCore