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-2018 Apple Inc. All rights reserved. |
6 | * (C) 2006 Alexey Proskuryakov (ap@nypop.com) |
7 | * |
8 | * This library is free software; you can redistribute it and/or |
9 | * modify it under the terms of the GNU Library General Public |
10 | * License as published by the Free Software Foundation; either |
11 | * version 2 of the License, or (at your option) any later version. |
12 | * |
13 | * This library is distributed in the hope that it will be useful, |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | * Library General Public License for more details. |
17 | * |
18 | * You should have received a copy of the GNU Library General Public License |
19 | * along with this library; see the file COPYING.LIB. If not, write to |
20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
21 | * Boston, MA 02110-1301, USA. |
22 | * |
23 | */ |
24 | |
25 | #include "config.h" |
26 | #include "HTMLTextFormControlElement.h" |
27 | |
28 | #include "AXObjectCache.h" |
29 | #include "CSSPrimitiveValueMappings.h" |
30 | #include "ChromeClient.h" |
31 | #include "Document.h" |
32 | #include "Editing.h" |
33 | #include "Event.h" |
34 | #include "EventNames.h" |
35 | #include "Frame.h" |
36 | #include "FrameSelection.h" |
37 | #include "HTMLBRElement.h" |
38 | #include "HTMLFormElement.h" |
39 | #include "HTMLInputElement.h" |
40 | #include "HTMLNames.h" |
41 | #include "HTMLParserIdioms.h" |
42 | #include "LayoutDisallowedScope.h" |
43 | #include "Logging.h" |
44 | #include "NodeTraversal.h" |
45 | #include "Page.h" |
46 | #include "RenderTextControlSingleLine.h" |
47 | #include "RenderTheme.h" |
48 | #include "ScriptDisallowedScope.h" |
49 | #include "ShadowRoot.h" |
50 | #include "Text.h" |
51 | #include "TextControlInnerElements.h" |
52 | #include <wtf/IsoMallocInlines.h> |
53 | #include <wtf/text/StringBuilder.h> |
54 | |
55 | namespace WebCore { |
56 | |
57 | WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLTextFormControlElement); |
58 | |
59 | using namespace HTMLNames; |
60 | |
61 | static Position positionForIndex(TextControlInnerTextElement*, unsigned); |
62 | |
63 | HTMLTextFormControlElement::HTMLTextFormControlElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form) |
64 | : HTMLFormControlElementWithState(tagName, document, form) |
65 | , m_cachedSelectionDirection(SelectionHasNoDirection) |
66 | , m_lastChangeWasUserEdit(false) |
67 | , m_isPlaceholderVisible(false) |
68 | , m_cachedSelectionStart(-1) |
69 | , m_cachedSelectionEnd(-1) |
70 | { |
71 | } |
72 | |
73 | HTMLTextFormControlElement::~HTMLTextFormControlElement() = default; |
74 | |
75 | bool HTMLTextFormControlElement::childShouldCreateRenderer(const Node& child) const |
76 | { |
77 | // FIXME: We shouldn't force the pseudo elements down into the shadow, but |
78 | // this perserves the current behavior of WebKit. |
79 | if (child.isPseudoElement()) |
80 | return HTMLFormControlElementWithState::childShouldCreateRenderer(child); |
81 | return hasShadowRootParent(child) && HTMLFormControlElementWithState::childShouldCreateRenderer(child); |
82 | } |
83 | |
84 | Node::InsertedIntoAncestorResult HTMLTextFormControlElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
85 | { |
86 | InsertedIntoAncestorResult InsertedIntoAncestorResult = HTMLFormControlElementWithState::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
87 | if (insertionType.connectedToDocument) { |
88 | String initialValue = value(); |
89 | setTextAsOfLastFormControlChangeEvent(initialValue.isNull() ? emptyString() : initialValue); |
90 | } |
91 | return InsertedIntoAncestorResult; |
92 | } |
93 | |
94 | void HTMLTextFormControlElement::dispatchFocusEvent(RefPtr<Element>&& oldFocusedElement, FocusDirection direction) |
95 | { |
96 | if (supportsPlaceholder()) |
97 | updatePlaceholderVisibility(); |
98 | handleFocusEvent(oldFocusedElement.get(), direction); |
99 | HTMLFormControlElementWithState::dispatchFocusEvent(WTFMove(oldFocusedElement), direction); |
100 | } |
101 | |
102 | void HTMLTextFormControlElement::dispatchBlurEvent(RefPtr<Element>&& newFocusedElement) |
103 | { |
104 | if (supportsPlaceholder()) |
105 | updatePlaceholderVisibility(); |
106 | // Match the order in Document::setFocusedElement. |
107 | handleBlurEvent(); |
108 | HTMLFormControlElementWithState::dispatchBlurEvent(WTFMove(newFocusedElement)); |
109 | } |
110 | |
111 | void HTMLTextFormControlElement::didEditInnerTextValue() |
112 | { |
113 | if (!renderer() || !isTextField()) |
114 | return; |
115 | |
116 | LOG(Editing, "HTMLTextFormControlElement %p didEditInnerTextValue" , this); |
117 | |
118 | m_lastChangeWasUserEdit = true; |
119 | subtreeHasChanged(); |
120 | } |
121 | |
122 | void HTMLTextFormControlElement::forwardEvent(Event& event) |
123 | { |
124 | if (event.type() == eventNames().blurEvent || event.type() == eventNames().focusEvent) |
125 | return; |
126 | innerTextElement()->defaultEventHandler(event); |
127 | } |
128 | |
129 | String HTMLTextFormControlElement::strippedPlaceholder() const |
130 | { |
131 | // According to the HTML5 specification, we need to remove CR and LF from |
132 | // the attribute value. |
133 | const AtomicString& attributeValue = attributeWithoutSynchronization(placeholderAttr); |
134 | if (!attributeValue.contains(newlineCharacter) && !attributeValue.contains(carriageReturn)) |
135 | return attributeValue; |
136 | |
137 | StringBuilder stripped; |
138 | unsigned length = attributeValue.length(); |
139 | stripped.reserveCapacity(length); |
140 | for (unsigned i = 0; i < length; ++i) { |
141 | UChar character = attributeValue[i]; |
142 | if (character == newlineCharacter || character == carriageReturn) |
143 | continue; |
144 | stripped.append(character); |
145 | } |
146 | return stripped.toString(); |
147 | } |
148 | |
149 | static bool isNotLineBreak(UChar ch) { return ch != newlineCharacter && ch != carriageReturn; } |
150 | |
151 | bool HTMLTextFormControlElement::isPlaceholderEmpty() const |
152 | { |
153 | const AtomicString& attributeValue = attributeWithoutSynchronization(placeholderAttr); |
154 | return attributeValue.string().find(isNotLineBreak) == notFound; |
155 | } |
156 | |
157 | bool HTMLTextFormControlElement::placeholderShouldBeVisible() const |
158 | { |
159 | // This function is used by the style resolver to match the :placeholder-shown pseudo class. |
160 | // Since it is used for styling, it must not use any value depending on the style. |
161 | return supportsPlaceholder() && isEmptyValue() && !isPlaceholderEmpty(); |
162 | } |
163 | |
164 | void HTMLTextFormControlElement::updatePlaceholderVisibility() |
165 | { |
166 | bool placeHolderWasVisible = m_isPlaceholderVisible; |
167 | m_isPlaceholderVisible = placeholderShouldBeVisible(); |
168 | |
169 | if (placeHolderWasVisible == m_isPlaceholderVisible) |
170 | return; |
171 | |
172 | invalidateStyleForSubtree(); |
173 | } |
174 | |
175 | void HTMLTextFormControlElement::setSelectionStart(int start) |
176 | { |
177 | setSelectionRange(start, std::max(start, selectionEnd()), selectionDirection()); |
178 | } |
179 | |
180 | void HTMLTextFormControlElement::setSelectionEnd(int end) |
181 | { |
182 | setSelectionRange(std::min(end, selectionStart()), end, selectionDirection()); |
183 | } |
184 | |
185 | void HTMLTextFormControlElement::setSelectionDirection(const String& direction) |
186 | { |
187 | setSelectionRange(selectionStart(), selectionEnd(), direction); |
188 | } |
189 | |
190 | void HTMLTextFormControlElement::select(SelectionRevealMode revealMode, const AXTextStateChangeIntent& intent) |
191 | { |
192 | setSelectionRange(0, std::numeric_limits<int>::max(), SelectionHasNoDirection, revealMode, intent); |
193 | } |
194 | |
195 | String HTMLTextFormControlElement::selectedText() const |
196 | { |
197 | if (!isTextField()) |
198 | return String(); |
199 | return value().substring(selectionStart(), selectionEnd() - selectionStart()); |
200 | } |
201 | |
202 | void HTMLTextFormControlElement::dispatchFormControlChangeEvent() |
203 | { |
204 | if (m_textAsOfLastFormControlChangeEvent != value()) { |
205 | dispatchChangeEvent(); |
206 | setTextAsOfLastFormControlChangeEvent(value()); |
207 | } |
208 | setChangedSinceLastFormControlChangeEvent(false); |
209 | } |
210 | |
211 | ExceptionOr<void> HTMLTextFormControlElement::setRangeText(const String& replacement) |
212 | { |
213 | return setRangeText(replacement, selectionStart(), selectionEnd(), String()); |
214 | } |
215 | |
216 | ExceptionOr<void> HTMLTextFormControlElement::setRangeText(const String& replacement, unsigned start, unsigned end, const String& selectionMode) |
217 | { |
218 | if (start > end) |
219 | return Exception { IndexSizeError }; |
220 | |
221 | String text = innerTextValue(); |
222 | unsigned textLength = text.length(); |
223 | unsigned replacementLength = replacement.length(); |
224 | unsigned newSelectionStart = selectionStart(); |
225 | unsigned newSelectionEnd = selectionEnd(); |
226 | |
227 | start = std::min(start, textLength); |
228 | end = std::min(end, textLength); |
229 | |
230 | if (start < end) |
231 | text.replace(start, end - start, replacement); |
232 | else |
233 | text.insert(replacement, start); |
234 | |
235 | setInnerTextValue(text); |
236 | |
237 | // FIXME: What should happen to the value (as in value()) if there's no renderer? |
238 | if (!renderer()) |
239 | return { }; |
240 | |
241 | subtreeHasChanged(); |
242 | |
243 | if (equalLettersIgnoringASCIICase(selectionMode, "select" )) { |
244 | newSelectionStart = start; |
245 | newSelectionEnd = start + replacementLength; |
246 | } else if (equalLettersIgnoringASCIICase(selectionMode, "start" )) |
247 | newSelectionStart = newSelectionEnd = start; |
248 | else if (equalLettersIgnoringASCIICase(selectionMode, "end" )) |
249 | newSelectionStart = newSelectionEnd = start + replacementLength; |
250 | else { |
251 | // Default is "preserve". |
252 | long delta = replacementLength - (end - start); |
253 | |
254 | if (newSelectionStart > end) |
255 | newSelectionStart += delta; |
256 | else if (newSelectionStart > start) |
257 | newSelectionStart = start; |
258 | |
259 | if (newSelectionEnd > end) |
260 | newSelectionEnd += delta; |
261 | else if (newSelectionEnd > start) |
262 | newSelectionEnd = start + replacementLength; |
263 | } |
264 | |
265 | setSelectionRange(newSelectionStart, newSelectionEnd, SelectionHasNoDirection); |
266 | |
267 | return { }; |
268 | } |
269 | |
270 | void HTMLTextFormControlElement::setSelectionRange(int start, int end, const String& directionString, const AXTextStateChangeIntent& intent) |
271 | { |
272 | TextFieldSelectionDirection direction = SelectionHasNoDirection; |
273 | if (directionString == "forward" ) |
274 | direction = SelectionHasForwardDirection; |
275 | else if (directionString == "backward" ) |
276 | direction = SelectionHasBackwardDirection; |
277 | |
278 | return setSelectionRange(start, end, direction, SelectionRevealMode::DoNotReveal, intent); |
279 | } |
280 | |
281 | void HTMLTextFormControlElement::setSelectionRange(int start, int end, TextFieldSelectionDirection direction, SelectionRevealMode revealMode, const AXTextStateChangeIntent& intent) |
282 | { |
283 | if (!isTextField()) |
284 | return; |
285 | |
286 | end = std::max(end, 0); |
287 | start = std::min(std::max(start, 0), end); |
288 | |
289 | auto innerText = innerTextElement(); |
290 | bool hasFocus = document().focusedElement() == this; |
291 | if (!hasFocus && innerText) { |
292 | // FIXME: Removing this synchronous layout requires fixing setSelectionWithoutUpdatingAppearance not needing up-to-date style. |
293 | document().updateLayoutIgnorePendingStylesheets(); |
294 | |
295 | // Double-check the state of innerTextElement after the layout. |
296 | innerText = innerTextElement(); |
297 | auto* rendererTextControl = renderer(); |
298 | |
299 | if (innerText && rendererTextControl) { |
300 | if (rendererTextControl->style().visibility() == Visibility::Hidden || !innerText->renderBox() || !innerText->renderBox()->height()) { |
301 | cacheSelection(start, end, direction); |
302 | return; |
303 | } |
304 | } |
305 | } |
306 | |
307 | Position startPosition = positionForIndex(innerText.get(), start); |
308 | Position endPosition; |
309 | if (start == end) |
310 | endPosition = startPosition; |
311 | else { |
312 | if (direction == SelectionHasBackwardDirection) { |
313 | endPosition = startPosition; |
314 | startPosition = positionForIndex(innerText.get(), end); |
315 | } else |
316 | endPosition = positionForIndex(innerText.get(), end); |
317 | } |
318 | |
319 | if (RefPtr<Frame> frame = document().frame()) |
320 | frame->selection().moveWithoutValidationTo(startPosition, endPosition, direction != SelectionHasNoDirection, !hasFocus, revealMode, intent); |
321 | } |
322 | |
323 | int HTMLTextFormControlElement::indexForVisiblePosition(const VisiblePosition& position) const |
324 | { |
325 | auto innerText = innerTextElement(); |
326 | if (!innerText || !innerText->contains(position.deepEquivalent().anchorNode())) |
327 | return 0; |
328 | unsigned index = indexForPosition(position.deepEquivalent()); |
329 | ASSERT(VisiblePosition(positionForIndex(innerTextElement().get(), index)) == position); |
330 | return index; |
331 | } |
332 | |
333 | VisiblePosition HTMLTextFormControlElement::visiblePositionForIndex(int index) const |
334 | { |
335 | VisiblePosition position = positionForIndex(innerTextElement().get(), index); |
336 | ASSERT(indexForVisiblePosition(position) == index); |
337 | return position; |
338 | } |
339 | |
340 | int HTMLTextFormControlElement::selectionStart() const |
341 | { |
342 | if (!isTextField()) |
343 | return 0; |
344 | if (document().focusedElement() != this && hasCachedSelection()) |
345 | return m_cachedSelectionStart; |
346 | |
347 | return computeSelectionStart(); |
348 | } |
349 | |
350 | int HTMLTextFormControlElement::computeSelectionStart() const |
351 | { |
352 | ASSERT(isTextField()); |
353 | RefPtr<Frame> frame = document().frame(); |
354 | if (!frame) |
355 | return 0; |
356 | |
357 | return indexForPosition(frame->selection().selection().start()); |
358 | } |
359 | |
360 | int HTMLTextFormControlElement::selectionEnd() const |
361 | { |
362 | if (!isTextField()) |
363 | return 0; |
364 | if (document().focusedElement() != this && hasCachedSelection()) |
365 | return m_cachedSelectionEnd; |
366 | return computeSelectionEnd(); |
367 | } |
368 | |
369 | int HTMLTextFormControlElement::computeSelectionEnd() const |
370 | { |
371 | ASSERT(isTextField()); |
372 | RefPtr<Frame> frame = document().frame(); |
373 | if (!frame) |
374 | return 0; |
375 | |
376 | return indexForPosition(frame->selection().selection().end()); |
377 | } |
378 | |
379 | static const AtomicString& directionString(TextFieldSelectionDirection direction) |
380 | { |
381 | static NeverDestroyed<const AtomicString> none("none" , AtomicString::ConstructFromLiteral); |
382 | static NeverDestroyed<const AtomicString> forward("forward" , AtomicString::ConstructFromLiteral); |
383 | static NeverDestroyed<const AtomicString> backward("backward" , AtomicString::ConstructFromLiteral); |
384 | |
385 | switch (direction) { |
386 | case SelectionHasNoDirection: |
387 | return none; |
388 | case SelectionHasForwardDirection: |
389 | return forward; |
390 | case SelectionHasBackwardDirection: |
391 | return backward; |
392 | } |
393 | |
394 | ASSERT_NOT_REACHED(); |
395 | return none; |
396 | } |
397 | |
398 | const AtomicString& HTMLTextFormControlElement::selectionDirection() const |
399 | { |
400 | if (!isTextField()) |
401 | return directionString(SelectionHasNoDirection); |
402 | if (document().focusedElement() != this && hasCachedSelection()) |
403 | return directionString(cachedSelectionDirection()); |
404 | |
405 | return directionString(computeSelectionDirection()); |
406 | } |
407 | |
408 | TextFieldSelectionDirection HTMLTextFormControlElement::computeSelectionDirection() const |
409 | { |
410 | ASSERT(isTextField()); |
411 | RefPtr<Frame> frame = document().frame(); |
412 | if (!frame) |
413 | return SelectionHasNoDirection; |
414 | |
415 | const VisibleSelection& selection = frame->selection().selection(); |
416 | return selection.isDirectional() ? (selection.isBaseFirst() ? SelectionHasForwardDirection : SelectionHasBackwardDirection) : SelectionHasNoDirection; |
417 | } |
418 | |
419 | static inline void setContainerAndOffsetForRange(Node* node, int offset, Node*& containerNode, int& offsetInContainer) |
420 | { |
421 | if (node->isTextNode()) { |
422 | containerNode = node; |
423 | offsetInContainer = offset; |
424 | } else { |
425 | containerNode = node->parentNode(); |
426 | offsetInContainer = node->computeNodeIndex() + offset; |
427 | } |
428 | } |
429 | |
430 | RefPtr<Range> HTMLTextFormControlElement::selection() const |
431 | { |
432 | if (!renderer() || !isTextField() || !hasCachedSelection()) |
433 | return nullptr; |
434 | |
435 | int start = m_cachedSelectionStart; |
436 | int end = m_cachedSelectionEnd; |
437 | |
438 | ASSERT(start <= end); |
439 | auto innerText = innerTextElement(); |
440 | if (!innerText) |
441 | return nullptr; |
442 | |
443 | if (!innerText->firstChild()) |
444 | return Range::create(document(), innerText, 0, innerText, 0); |
445 | |
446 | int offset = 0; |
447 | Node* startNode = nullptr; |
448 | Node* endNode = nullptr; |
449 | for (RefPtr<Node> node = innerText->firstChild(); node; node = NodeTraversal::next(*node, innerText.get())) { |
450 | ASSERT(!node->firstChild()); |
451 | ASSERT(node->isTextNode() || node->hasTagName(brTag)); |
452 | int length = node->isTextNode() ? lastOffsetInNode(node.get()) : 1; |
453 | |
454 | if (offset <= start && start <= offset + length) |
455 | setContainerAndOffsetForRange(node.get(), start - offset, startNode, start); |
456 | |
457 | if (offset <= end && end <= offset + length) { |
458 | setContainerAndOffsetForRange(node.get(), end - offset, endNode, end); |
459 | break; |
460 | } |
461 | |
462 | offset += length; |
463 | } |
464 | |
465 | if (!startNode || !endNode) |
466 | return nullptr; |
467 | |
468 | return Range::create(document(), startNode, start, endNode, end); |
469 | } |
470 | |
471 | void HTMLTextFormControlElement::restoreCachedSelection(SelectionRevealMode revealMode, const AXTextStateChangeIntent& intent) |
472 | { |
473 | setSelectionRange(m_cachedSelectionStart, m_cachedSelectionEnd, cachedSelectionDirection(), revealMode, intent); |
474 | } |
475 | |
476 | void HTMLTextFormControlElement::selectionChanged(bool shouldFireSelectEvent) |
477 | { |
478 | if (!isTextField()) |
479 | return; |
480 | |
481 | // FIXME: Don't re-compute selection start and end if this function was called inside setSelectionRange. |
482 | // selectionStart() or selectionEnd() will return cached selection when this node doesn't have focus |
483 | cacheSelection(computeSelectionStart(), computeSelectionEnd(), computeSelectionDirection()); |
484 | |
485 | if (shouldFireSelectEvent && m_cachedSelectionStart != m_cachedSelectionEnd) |
486 | dispatchEvent(Event::create(eventNames().selectEvent, Event::CanBubble::Yes, Event::IsCancelable::No)); |
487 | } |
488 | |
489 | void HTMLTextFormControlElement::parseAttribute(const QualifiedName& name, const AtomicString& value) |
490 | { |
491 | if (name == placeholderAttr) { |
492 | updatePlaceholderText(); |
493 | updatePlaceholderVisibility(); |
494 | } else |
495 | HTMLFormControlElementWithState::parseAttribute(name, value); |
496 | } |
497 | |
498 | void HTMLTextFormControlElement::disabledStateChanged() |
499 | { |
500 | HTMLFormControlElementWithState::disabledStateChanged(); |
501 | updateInnerTextElementEditability(); |
502 | } |
503 | |
504 | void HTMLTextFormControlElement::readOnlyStateChanged() |
505 | { |
506 | HTMLFormControlElementWithState::readOnlyStateChanged(); |
507 | updateInnerTextElementEditability(); |
508 | } |
509 | |
510 | bool HTMLTextFormControlElement::isInnerTextElementEditable() const |
511 | { |
512 | return !isDisabledOrReadOnly(); |
513 | } |
514 | |
515 | void HTMLTextFormControlElement::updateInnerTextElementEditability() |
516 | { |
517 | if (auto innerText = innerTextElement()) { |
518 | auto value = isInnerTextElementEditable() ? AtomicString { "plaintext-only" , AtomicString::ConstructFromLiteral } : AtomicString { "false" , AtomicString::ConstructFromLiteral }; |
519 | innerText->setAttributeWithoutSynchronization(contenteditableAttr, value); |
520 | } |
521 | } |
522 | |
523 | bool HTMLTextFormControlElement::lastChangeWasUserEdit() const |
524 | { |
525 | if (!isTextField()) |
526 | return false; |
527 | return m_lastChangeWasUserEdit; |
528 | } |
529 | |
530 | static void stripTrailingNewline(StringBuilder& result) |
531 | { |
532 | // Remove one trailing newline; there's always one that's collapsed out by rendering. |
533 | size_t size = result.length(); |
534 | if (size && result[size - 1] == newlineCharacter) |
535 | result.resize(size - 1); |
536 | } |
537 | |
538 | static String innerTextValueFrom(TextControlInnerTextElement& innerText) |
539 | { |
540 | StringBuilder result; |
541 | for (RefPtr<Node> node = innerText.firstChild(); node; node = NodeTraversal::next(*node, &innerText)) { |
542 | if (is<HTMLBRElement>(*node)) |
543 | result.append(newlineCharacter); |
544 | else if (is<Text>(*node)) |
545 | result.append(downcast<Text>(*node).data()); |
546 | } |
547 | stripTrailingNewline(result); |
548 | return result.toString(); |
549 | } |
550 | |
551 | void HTMLTextFormControlElement::setInnerTextValue(const String& value) |
552 | { |
553 | LayoutDisallowedScope layoutDisallowedScope(LayoutDisallowedScope::Reason::PerformanceOptimization); |
554 | auto innerText = innerTextElement(); |
555 | if (!innerText) |
556 | return; |
557 | |
558 | ASSERT(isTextField()); |
559 | String previousValue = innerTextValueFrom(*innerText); |
560 | bool textIsChanged = value != previousValue; |
561 | if (textIsChanged || !innerText->hasChildNodes()) { |
562 | #if HAVE(ACCESSIBILITY) && !PLATFORM(COCOA) |
563 | if (textIsChanged && renderer()) { |
564 | if (AXObjectCache* cache = document().existingAXObjectCache()) |
565 | cache->postNotification(this, AXObjectCache::AXValueChanged, TargetObservableParent); |
566 | } |
567 | #endif |
568 | |
569 | { |
570 | // Events dispatched on the inner text element cannot execute arbitrary author scripts. |
571 | ScriptDisallowedScope::EventAllowedScope allowedScope(*userAgentShadowRoot()); |
572 | |
573 | innerText->setInnerText(value); |
574 | |
575 | if (value.endsWith('\n') || value.endsWith('\r')) |
576 | innerText->appendChild(HTMLBRElement::create(document())); |
577 | } |
578 | |
579 | #if HAVE(ACCESSIBILITY) && PLATFORM(COCOA) |
580 | if (textIsChanged && renderer()) { |
581 | if (AXObjectCache* cache = document().existingAXObjectCache()) |
582 | cache->deferTextReplacementNotificationForTextControl(*this, previousValue); |
583 | } |
584 | #endif |
585 | } |
586 | |
587 | setFormControlValueMatchesRenderer(true); |
588 | } |
589 | |
590 | String HTMLTextFormControlElement::innerTextValue() const |
591 | { |
592 | auto innerText = innerTextElement(); |
593 | return innerText ? innerTextValueFrom(*innerText) : emptyString(); |
594 | } |
595 | |
596 | static Position positionForIndex(TextControlInnerTextElement* innerText, unsigned index) |
597 | { |
598 | unsigned remainingCharactersToMoveForward = index; |
599 | RefPtr<Node> lastBrOrText = innerText; |
600 | for (RefPtr<Node> node = innerText; node; node = NodeTraversal::next(*node, innerText)) { |
601 | if (node->hasTagName(brTag)) { |
602 | if (!remainingCharactersToMoveForward) |
603 | return positionBeforeNode(node.get()); |
604 | remainingCharactersToMoveForward--; |
605 | lastBrOrText = node; |
606 | } else if (is<Text>(*node)) { |
607 | Text& text = downcast<Text>(*node); |
608 | if (remainingCharactersToMoveForward < text.length()) |
609 | return Position(&text, remainingCharactersToMoveForward); |
610 | remainingCharactersToMoveForward -= text.length(); |
611 | lastBrOrText = node; |
612 | } |
613 | } |
614 | return lastPositionInOrAfterNode(lastBrOrText.get()); |
615 | } |
616 | |
617 | unsigned HTMLTextFormControlElement::indexForPosition(const Position& passedPosition) const |
618 | { |
619 | auto innerText = innerTextElement(); |
620 | if (!innerText || !innerText->contains(passedPosition.anchorNode()) || passedPosition.isNull()) |
621 | return 0; |
622 | |
623 | if (positionBeforeNode(innerText.get()) == passedPosition) |
624 | return 0; |
625 | |
626 | unsigned index = 0; |
627 | RefPtr<Node> startNode = passedPosition.computeNodeBeforePosition(); |
628 | if (!startNode) |
629 | startNode = passedPosition.containerNode(); |
630 | ASSERT(startNode); |
631 | ASSERT(innerText->contains(startNode.get())); |
632 | |
633 | for (RefPtr<Node> node = startNode; node; node = NodeTraversal::previous(*node, innerText.get())) { |
634 | if (is<Text>(*node)) { |
635 | unsigned length = downcast<Text>(*node).length(); |
636 | if (node == passedPosition.containerNode()) |
637 | index += std::min<unsigned>(length, passedPosition.offsetInContainerNode()); |
638 | else |
639 | index += length; |
640 | } else if (is<HTMLBRElement>(*node)) |
641 | ++index; |
642 | } |
643 | |
644 | unsigned length = innerTextValue().length(); |
645 | index = std::min(index, length); // FIXME: We shouldn't have to call innerTextValue() just to ignore the last LF. See finishText. |
646 | #ifndef ASSERT_DISABLED |
647 | VisiblePosition visiblePosition = passedPosition; |
648 | unsigned indexComputedByVisiblePosition = 0; |
649 | if (visiblePosition.isNotNull()) |
650 | indexComputedByVisiblePosition = WebCore::indexForVisiblePosition(innerText, visiblePosition, false /* forSelectionPreservation */); |
651 | ASSERT(index == indexComputedByVisiblePosition); |
652 | #endif |
653 | return index; |
654 | } |
655 | |
656 | #if PLATFORM(IOS_FAMILY) |
657 | void HTMLTextFormControlElement::hidePlaceholder() |
658 | { |
659 | if (RefPtr<HTMLElement> placeholder = placeholderElement()) |
660 | placeholder->setInlineStyleProperty(CSSPropertyVisibility, CSSValueHidden, true); |
661 | } |
662 | |
663 | void HTMLTextFormControlElement::showPlaceholderIfNecessary() |
664 | { |
665 | if (RefPtr<HTMLElement> placeholder = placeholderElement()) |
666 | placeholder->setInlineStyleProperty(CSSPropertyVisibility, CSSValueVisible, true); |
667 | } |
668 | #endif |
669 | |
670 | static void getNextSoftBreak(RootInlineBox*& line, Node*& breakNode, unsigned& breakOffset) |
671 | { |
672 | RootInlineBox* next; |
673 | for (; line; line = next) { |
674 | next = line->nextRootBox(); |
675 | if (next && !line->endsWithBreak()) { |
676 | ASSERT(line->lineBreakObj()); |
677 | breakNode = line->lineBreakObj()->node(); |
678 | breakOffset = line->lineBreakPos(); |
679 | line = next; |
680 | return; |
681 | } |
682 | } |
683 | breakNode = 0; |
684 | breakOffset = 0; |
685 | } |
686 | |
687 | String HTMLTextFormControlElement::valueWithHardLineBreaks() const |
688 | { |
689 | // FIXME: It's not acceptable to ignore the HardWrap setting when there is no renderer. |
690 | // While we have no evidence this has ever been a practical problem, it would be best to fix it some day. |
691 | if (!isTextField()) |
692 | return value(); |
693 | |
694 | auto innerText = innerTextElement(); |
695 | if (!innerText) |
696 | return value(); |
697 | |
698 | RenderTextControlInnerBlock* renderer = innerText->renderer(); |
699 | if (!renderer) |
700 | return value(); |
701 | |
702 | Node* breakNode; |
703 | unsigned breakOffset; |
704 | RootInlineBox* line = renderer->firstRootBox(); |
705 | if (!line) |
706 | return value(); |
707 | |
708 | getNextSoftBreak(line, breakNode, breakOffset); |
709 | |
710 | StringBuilder result; |
711 | for (RefPtr<Node> node = innerText->firstChild(); node; node = NodeTraversal::next(*node, innerText.get())) { |
712 | if (is<HTMLBRElement>(*node)) |
713 | result.append(newlineCharacter); |
714 | else if (is<Text>(*node)) { |
715 | String data = downcast<Text>(*node).data(); |
716 | unsigned length = data.length(); |
717 | unsigned position = 0; |
718 | while (breakNode == node && breakOffset <= length) { |
719 | if (breakOffset > position) { |
720 | result.append(data, position, breakOffset - position); |
721 | position = breakOffset; |
722 | result.append(newlineCharacter); |
723 | } |
724 | getNextSoftBreak(line, breakNode, breakOffset); |
725 | } |
726 | result.append(data, position, length - position); |
727 | } |
728 | while (breakNode == node) |
729 | getNextSoftBreak(line, breakNode, breakOffset); |
730 | } |
731 | stripTrailingNewline(result); |
732 | return result.toString(); |
733 | } |
734 | |
735 | HTMLTextFormControlElement* enclosingTextFormControl(const Position& position) |
736 | { |
737 | ASSERT(position.isNull() || position.anchorType() == Position::PositionIsOffsetInAnchor |
738 | || position.containerNode() || !position.anchorNode()->shadowHost() |
739 | || hasShadowRootParent(*position.anchorNode())); |
740 | |
741 | RefPtr<Node> container = position.containerNode(); |
742 | if (!container) |
743 | return nullptr; |
744 | RefPtr<Element> ancestor = container->shadowHost(); |
745 | return ancestor && ancestor->isTextField() ? downcast<HTMLTextFormControlElement>(ancestor.get()) : nullptr; |
746 | } |
747 | |
748 | static const Element* parentHTMLElement(const Element* element) |
749 | { |
750 | while (element) { |
751 | element = element->parentElement(); |
752 | if (element && element->isHTMLElement()) |
753 | return element; |
754 | } |
755 | return 0; |
756 | } |
757 | |
758 | String HTMLTextFormControlElement::directionForFormData() const |
759 | { |
760 | for (const Element* element = this; element; element = parentHTMLElement(element)) { |
761 | const AtomicString& dirAttributeValue = element->attributeWithoutSynchronization(dirAttr); |
762 | if (dirAttributeValue.isNull()) |
763 | continue; |
764 | |
765 | if (equalLettersIgnoringASCIICase(dirAttributeValue, "rtl" ) || equalLettersIgnoringASCIICase(dirAttributeValue, "ltr" )) |
766 | return dirAttributeValue; |
767 | |
768 | if (equalLettersIgnoringASCIICase(dirAttributeValue, "auto" )) { |
769 | bool isAuto; |
770 | TextDirection textDirection = static_cast<const HTMLElement*>(element)->directionalityIfhasDirAutoAttribute(isAuto); |
771 | return textDirection == TextDirection::RTL ? "rtl" : "ltr" ; |
772 | } |
773 | } |
774 | |
775 | return "ltr" ; |
776 | } |
777 | |
778 | ExceptionOr<void> HTMLTextFormControlElement::setMaxLength(int maxLength) |
779 | { |
780 | if (maxLength < 0 || (m_minLength >= 0 && maxLength < m_minLength)) |
781 | return Exception { IndexSizeError }; |
782 | setIntegralAttribute(maxlengthAttr, maxLength); |
783 | return { }; |
784 | } |
785 | |
786 | ExceptionOr<void> HTMLTextFormControlElement::setMinLength(int minLength) |
787 | { |
788 | if (minLength < 0 || (m_maxLength >= 0 && minLength > m_maxLength)) |
789 | return Exception { IndexSizeError }; |
790 | setIntegralAttribute(minlengthAttr, minLength); |
791 | return { }; |
792 | } |
793 | |
794 | void HTMLTextFormControlElement::adjustInnerTextStyle(const RenderStyle& parentStyle, RenderStyle& textBlockStyle) const |
795 | { |
796 | // The inner block, if present, always has its direction set to LTR, |
797 | // so we need to inherit the direction and unicode-bidi style from the element. |
798 | textBlockStyle.setDirection(parentStyle.direction()); |
799 | textBlockStyle.setUnicodeBidi(parentStyle.unicodeBidi()); |
800 | |
801 | if (auto innerText = innerTextElement()) { |
802 | if (const StyleProperties* properties = innerText->presentationAttributeStyle()) { |
803 | RefPtr<CSSValue> value = properties->getPropertyCSSValue(CSSPropertyWebkitUserModify); |
804 | if (is<CSSPrimitiveValue>(value)) |
805 | textBlockStyle.setUserModify(downcast<CSSPrimitiveValue>(*value)); |
806 | } |
807 | } |
808 | |
809 | if (isDisabledFormControl()) |
810 | textBlockStyle.setColor(RenderTheme::singleton().disabledTextColor(textBlockStyle.visitedDependentColorWithColorFilter(CSSPropertyColor), parentStyle.visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor))); |
811 | #if PLATFORM(IOS_FAMILY) |
812 | if (textBlockStyle.textSecurity() != TextSecurity::None && !textBlockStyle.isLeftToRightDirection()) { |
813 | // Preserve the alignment but force the direction to LTR so that the last-typed, unmasked character |
814 | // (which cannot have RTL directionality) will appear to the right of the masked characters. See <rdar://problem/7024375>. |
815 | |
816 | switch (textBlockStyle.textAlign()) { |
817 | case TextAlignMode::Start: |
818 | case TextAlignMode::Justify: |
819 | textBlockStyle.setTextAlign(TextAlignMode::Right); |
820 | break; |
821 | case TextAlignMode::End: |
822 | textBlockStyle.setTextAlign(TextAlignMode::Left); |
823 | break; |
824 | case TextAlignMode::Left: |
825 | case TextAlignMode::Right: |
826 | case TextAlignMode::Center: |
827 | case TextAlignMode::WebKitLeft: |
828 | case TextAlignMode::WebKitRight: |
829 | case TextAlignMode::WebKitCenter: |
830 | break; |
831 | } |
832 | |
833 | textBlockStyle.setDirection(TextDirection::LTR); |
834 | } |
835 | #endif |
836 | } |
837 | |
838 | } // namespace Webcore |
839 | |