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