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
55namespace WebCore {
56
57WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLTextFormControlElement);
58
59using namespace HTMLNames;
60
61static Position positionForIndex(TextControlInnerTextElement*, unsigned);
62
63HTMLTextFormControlElement::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
73HTMLTextFormControlElement::~HTMLTextFormControlElement() = default;
74
75bool 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
84Node::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
94void 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
102void 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
111void 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
122void HTMLTextFormControlElement::forwardEvent(Event& event)
123{
124 if (event.type() == eventNames().blurEvent || event.type() == eventNames().focusEvent)
125 return;
126 innerTextElement()->defaultEventHandler(event);
127}
128
129String 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
149static bool isNotLineBreak(UChar ch) { return ch != newlineCharacter && ch != carriageReturn; }
150
151bool HTMLTextFormControlElement::isPlaceholderEmpty() const
152{
153 const AtomicString& attributeValue = attributeWithoutSynchronization(placeholderAttr);
154 return attributeValue.string().find(isNotLineBreak) == notFound;
155}
156
157bool 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
164void 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
175void HTMLTextFormControlElement::setSelectionStart(int start)
176{
177 setSelectionRange(start, std::max(start, selectionEnd()), selectionDirection());
178}
179
180void HTMLTextFormControlElement::setSelectionEnd(int end)
181{
182 setSelectionRange(std::min(end, selectionStart()), end, selectionDirection());
183}
184
185void HTMLTextFormControlElement::setSelectionDirection(const String& direction)
186{
187 setSelectionRange(selectionStart(), selectionEnd(), direction);
188}
189
190void HTMLTextFormControlElement::select(SelectionRevealMode revealMode, const AXTextStateChangeIntent& intent)
191{
192 setSelectionRange(0, std::numeric_limits<int>::max(), SelectionHasNoDirection, revealMode, intent);
193}
194
195String HTMLTextFormControlElement::selectedText() const
196{
197 if (!isTextField())
198 return String();
199 return value().substring(selectionStart(), selectionEnd() - selectionStart());
200}
201
202void HTMLTextFormControlElement::dispatchFormControlChangeEvent()
203{
204 if (m_textAsOfLastFormControlChangeEvent != value()) {
205 dispatchChangeEvent();
206 setTextAsOfLastFormControlChangeEvent(value());
207 }
208 setChangedSinceLastFormControlChangeEvent(false);
209}
210
211ExceptionOr<void> HTMLTextFormControlElement::setRangeText(const String& replacement)
212{
213 return setRangeText(replacement, selectionStart(), selectionEnd(), String());
214}
215
216ExceptionOr<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
270void 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
281void 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
323int 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
333VisiblePosition HTMLTextFormControlElement::visiblePositionForIndex(int index) const
334{
335 VisiblePosition position = positionForIndex(innerTextElement().get(), index);
336 ASSERT(indexForVisiblePosition(position) == index);
337 return position;
338}
339
340int 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
350int 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
360int 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
369int 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
379static 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
398const 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
408TextFieldSelectionDirection 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
419static 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
430RefPtr<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
471void HTMLTextFormControlElement::restoreCachedSelection(SelectionRevealMode revealMode, const AXTextStateChangeIntent& intent)
472{
473 setSelectionRange(m_cachedSelectionStart, m_cachedSelectionEnd, cachedSelectionDirection(), revealMode, intent);
474}
475
476void 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
489void 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
498void HTMLTextFormControlElement::disabledStateChanged()
499{
500 HTMLFormControlElementWithState::disabledStateChanged();
501 updateInnerTextElementEditability();
502}
503
504void HTMLTextFormControlElement::readOnlyStateChanged()
505{
506 HTMLFormControlElementWithState::readOnlyStateChanged();
507 updateInnerTextElementEditability();
508}
509
510bool HTMLTextFormControlElement::isInnerTextElementEditable() const
511{
512 return !isDisabledOrReadOnly();
513}
514
515void 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
523bool HTMLTextFormControlElement::lastChangeWasUserEdit() const
524{
525 if (!isTextField())
526 return false;
527 return m_lastChangeWasUserEdit;
528}
529
530static 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
538static 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
551void 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
590String HTMLTextFormControlElement::innerTextValue() const
591{
592 auto innerText = innerTextElement();
593 return innerText ? innerTextValueFrom(*innerText) : emptyString();
594}
595
596static 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
617unsigned 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)
657void HTMLTextFormControlElement::hidePlaceholder()
658{
659 if (RefPtr<HTMLElement> placeholder = placeholderElement())
660 placeholder->setInlineStyleProperty(CSSPropertyVisibility, CSSValueHidden, true);
661}
662
663void HTMLTextFormControlElement::showPlaceholderIfNecessary()
664{
665 if (RefPtr<HTMLElement> placeholder = placeholderElement())
666 placeholder->setInlineStyleProperty(CSSPropertyVisibility, CSSValueVisible, true);
667}
668#endif
669
670static 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
687String 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
735HTMLTextFormControlElement* 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
748static 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
758String 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
778ExceptionOr<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
786ExceptionOr<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
794void 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