diff options
Diffstat (limited to 'Source/WebCore/html/HTMLTextFormControlElement.cpp')
-rw-r--r-- | Source/WebCore/html/HTMLTextFormControlElement.cpp | 469 |
1 files changed, 311 insertions, 158 deletions
diff --git a/Source/WebCore/html/HTMLTextFormControlElement.cpp b/Source/WebCore/html/HTMLTextFormControlElement.cpp index af222e4ac..bc493c057 100644 --- a/Source/WebCore/html/HTMLTextFormControlElement.cpp +++ b/Source/WebCore/html/HTMLTextFormControlElement.cpp @@ -2,7 +2,7 @@ * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Dirk Mueller (mueller@kde.org) - * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2004-2017 Apple Inc. All rights reserved. * (C) 2006 Alexey Proskuryakov (ap@nypop.com) * * This library is free software; you can redistribute it and/or @@ -26,20 +26,23 @@ #include "HTMLTextFormControlElement.h" #include "AXObjectCache.h" -#include "Attribute.h" +#include "CSSPrimitiveValueMappings.h" #include "ChromeClient.h" #include "Document.h" #include "Event.h" #include "EventNames.h" -#include "FeatureObserver.h" +#include "ExceptionCode.h" #include "Frame.h" #include "FrameSelection.h" #include "HTMLBRElement.h" #include "HTMLFormElement.h" #include "HTMLInputElement.h" #include "HTMLNames.h" +#include "HTMLParserIdioms.h" +#include "Logging.h" +#include "NoEventDispatchAssertion.h" #include "NodeTraversal.h" -#include "RenderBlockFlow.h" +#include "Page.h" #include "RenderTextControlSingleLine.h" #include "RenderTheme.h" #include "ShadowRoot.h" @@ -52,12 +55,15 @@ namespace WebCore { using namespace HTMLNames; +static Position positionForIndex(TextControlInnerTextElement*, unsigned); + HTMLTextFormControlElement::HTMLTextFormControlElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form) : HTMLFormControlElementWithState(tagName, document, form) + , m_cachedSelectionDirection(SelectionHasNoDirection) , m_lastChangeWasUserEdit(false) + , m_isPlaceholderVisible(false) , m_cachedSelectionStart(-1) , m_cachedSelectionEnd(-1) - , m_cachedSelectionDirection(SelectionHasNoDirection) { } @@ -76,44 +82,45 @@ bool HTMLTextFormControlElement::childShouldCreateRenderer(const Node& child) co Node::InsertionNotificationRequest HTMLTextFormControlElement::insertedInto(ContainerNode& insertionPoint) { - HTMLFormControlElementWithState::insertedInto(insertionPoint); - if (!insertionPoint.inDocument()) - return InsertionDone; + InsertionNotificationRequest insertionNotificationRequest = HTMLFormControlElementWithState::insertedInto(insertionPoint); + if (!insertionPoint.isConnected()) + return insertionNotificationRequest; String initialValue = value(); setTextAsOfLastFormControlChangeEvent(initialValue.isNull() ? emptyString() : initialValue); - return InsertionDone; + return insertionNotificationRequest; } -void HTMLTextFormControlElement::dispatchFocusEvent(PassRefPtr<Element> oldFocusedElement, FocusDirection direction) +void HTMLTextFormControlElement::dispatchFocusEvent(RefPtr<Element>&& oldFocusedElement, FocusDirection direction) { if (supportsPlaceholder()) - updatePlaceholderVisibility(false); + updatePlaceholderVisibility(); handleFocusEvent(oldFocusedElement.get(), direction); - HTMLFormControlElementWithState::dispatchFocusEvent(oldFocusedElement, direction); + HTMLFormControlElementWithState::dispatchFocusEvent(WTFMove(oldFocusedElement), direction); } -void HTMLTextFormControlElement::dispatchBlurEvent(PassRefPtr<Element> newFocusedElement) +void HTMLTextFormControlElement::dispatchBlurEvent(RefPtr<Element>&& newFocusedElement) { if (supportsPlaceholder()) - updatePlaceholderVisibility(false); + updatePlaceholderVisibility(); + // Match the order in Document::setFocusedElement. handleBlurEvent(); - HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedElement); + HTMLFormControlElementWithState::dispatchBlurEvent(WTFMove(newFocusedElement)); } -void HTMLTextFormControlElement::defaultEventHandler(Event* event) +void HTMLTextFormControlElement::didEditInnerTextValue() { - if (event->type() == eventNames().webkitEditableContentChangedEvent && renderer() && renderer()->isTextControl()) { - m_lastChangeWasUserEdit = true; - subtreeHasChanged(); + if (!isTextFormControl()) return; - } - HTMLFormControlElementWithState::defaultEventHandler(event); + LOG(Editing, "HTMLTextFormControlElement %p didEditInnerTextValue", this); + + m_lastChangeWasUserEdit = true; + subtreeHasChanged(); } -void HTMLTextFormControlElement::forwardEvent(Event* event) +void HTMLTextFormControlElement::forwardEvent(Event& event) { - if (event->type() == eventNames().blurEvent || event->type() == eventNames().focusEvent) + if (event.type() == eventNames().blurEvent || event.type() == eventNames().focusEvent) return; innerTextElement()->defaultEventHandler(event); } @@ -122,7 +129,7 @@ String HTMLTextFormControlElement::strippedPlaceholder() const { // According to the HTML5 specification, we need to remove CR and LF from // the attribute value. - const AtomicString& attributeValue = fastGetAttribute(placeholderAttr); + const AtomicString& attributeValue = attributeWithoutSynchronization(placeholderAttr); if (!attributeValue.contains(newlineCharacter) && !attributeValue.contains(carriageReturn)) return attributeValue; @@ -142,30 +149,26 @@ static bool isNotLineBreak(UChar ch) { return ch != newlineCharacter && ch != ca bool HTMLTextFormControlElement::isPlaceholderEmpty() const { - const AtomicString& attributeValue = fastGetAttribute(placeholderAttr); + const AtomicString& attributeValue = attributeWithoutSynchronization(placeholderAttr); return attributeValue.string().find(isNotLineBreak) == notFound; } bool HTMLTextFormControlElement::placeholderShouldBeVisible() const { - return supportsPlaceholder() - && isEmptyValue() - && isEmptySuggestedValue() - && !isPlaceholderEmpty() - && (document().focusedElement() != this || (renderer() && renderer()->theme().shouldShowPlaceholderWhenFocused())) - && (!renderer() || renderer()->style().visibility() == VISIBLE); + // This function is used by the style resolver to match the :placeholder-shown pseudo class. + // Since it is used for styling, it must not use any value depending on the style. + return supportsPlaceholder() && isEmptyValue() && !isPlaceholderEmpty(); } -void HTMLTextFormControlElement::updatePlaceholderVisibility(bool placeholderValueChanged) +void HTMLTextFormControlElement::updatePlaceholderVisibility() { - if (!supportsPlaceholder()) - return; - if (!placeholderElement() || placeholderValueChanged) - updatePlaceholderText(); - HTMLElement* placeholder = placeholderElement(); - if (!placeholder) + bool placeHolderWasVisible = m_isPlaceholderVisible; + m_isPlaceholderVisible = placeholderShouldBeVisible(); + + if (placeHolderWasVisible == m_isPlaceholderVisible) return; - placeholder->setInlineStyleProperty(CSSPropertyVisibility, placeholderShouldBeVisible() ? CSSValueVisible : CSSValueHidden); + + invalidateStyleForSubtree(); } void HTMLTextFormControlElement::setSelectionStart(int start) @@ -183,15 +186,15 @@ void HTMLTextFormControlElement::setSelectionDirection(const String& direction) setSelectionRange(selectionStart(), selectionEnd(), direction); } -void HTMLTextFormControlElement::select() +void HTMLTextFormControlElement::select(const AXTextStateChangeIntent& intent) { // FIXME: We should abstract the selection behavior into an EditingBehavior function instead // of hardcoding the behavior using a macro define. #if PLATFORM(IOS) // We don't want to select all the text on iOS. Instead use the standard textfield behavior of going to the end of the line. - setSelectionRange(std::numeric_limits<int>::max(), std::numeric_limits<int>::max(), SelectionHasForwardDirection); + setSelectionRange(std::numeric_limits<int>::max(), std::numeric_limits<int>::max(), SelectionHasForwardDirection, intent); #else - setSelectionRange(0, std::numeric_limits<int>::max(), SelectionHasNoDirection); + setSelectionRange(0, std::numeric_limits<int>::max(), SelectionHasNoDirection, intent); #endif } @@ -211,22 +214,15 @@ void HTMLTextFormControlElement::dispatchFormControlChangeEvent() setChangedSinceLastFormControlChangeEvent(false); } -static inline bool hasVisibleTextArea(RenderElement& textControl, TextControlInnerTextElement* innerText) +ExceptionOr<void> HTMLTextFormControlElement::setRangeText(const String& replacement) { - return textControl.style().visibility() != HIDDEN && innerText && innerText->renderer() && innerText->renderBox()->height(); + return setRangeText(replacement, selectionStart(), selectionEnd(), String()); } -void HTMLTextFormControlElement::setRangeText(const String& replacement, ExceptionCode& ec) +ExceptionOr<void> HTMLTextFormControlElement::setRangeText(const String& replacement, unsigned start, unsigned end, const String& selectionMode) { - setRangeText(replacement, selectionStart(), selectionEnd(), String(), ec); -} - -void HTMLTextFormControlElement::setRangeText(const String& replacement, unsigned start, unsigned end, const String& selectionMode, ExceptionCode& ec) -{ - if (start > end) { - ec = INDEX_SIZE_ERR; - return; - } + if (start > end) + return Exception { INDEX_SIZE_ERR }; String text = innerTextValue(); unsigned textLength = text.length(); @@ -246,16 +242,16 @@ void HTMLTextFormControlElement::setRangeText(const String& replacement, unsigne // FIXME: What should happen to the value (as in value()) if there's no renderer? if (!renderer()) - return; + return { }; subtreeHasChanged(); - if (equalIgnoringCase(selectionMode, "select")) { + if (equalLettersIgnoringASCIICase(selectionMode, "select")) { newSelectionStart = start; newSelectionEnd = start + replacementLength; - } else if (equalIgnoringCase(selectionMode, "start")) + } else if (equalLettersIgnoringASCIICase(selectionMode, "start")) newSelectionStart = newSelectionEnd = start; - else if (equalIgnoringCase(selectionMode, "end")) + else if (equalLettersIgnoringASCIICase(selectionMode, "end")) newSelectionStart = newSelectionEnd = start + replacementLength; else { // Default is "preserve". @@ -273,9 +269,11 @@ void HTMLTextFormControlElement::setRangeText(const String& replacement, unsigne } setSelectionRange(newSelectionStart, newSelectionEnd, SelectionHasNoDirection); + + return { }; } -void HTMLTextFormControlElement::setSelectionRange(int start, int end, const String& directionString) +void HTMLTextFormControlElement::setSelectionRange(int start, int end, const String& directionString, const AXTextStateChangeIntent& intent) { TextFieldSelectionDirection direction = SelectionHasNoDirection; if (directionString == "forward") @@ -283,60 +281,66 @@ void HTMLTextFormControlElement::setSelectionRange(int start, int end, const Str else if (directionString == "backward") direction = SelectionHasBackwardDirection; - return setSelectionRange(start, end, direction); + return setSelectionRange(start, end, direction, intent); } -void HTMLTextFormControlElement::setSelectionRange(int start, int end, TextFieldSelectionDirection direction) +void HTMLTextFormControlElement::setSelectionRange(int start, int end, TextFieldSelectionDirection direction, const AXTextStateChangeIntent& intent) { - document().updateLayoutIgnorePendingStylesheets(); - - if (!renderer() || !renderer()->isTextControl()) + if (!isTextFormControl()) return; end = std::max(end, 0); start = std::min(std::max(start, 0), end); - if (!hasVisibleTextArea(*renderer(), innerTextElement())) { - cacheSelection(start, end, direction); - return; + TextControlInnerTextElement* innerText = innerTextElement(); + bool hasFocus = document().focusedElement() == this; + if (!hasFocus && innerText) { + // FIXME: Removing this synchronous layout requires fixing <https://webkit.org/b/128797> + document().updateLayoutIgnorePendingStylesheets(); + + // Double-check the state of innerTextElement after the layout. + innerText = innerTextElement(); + auto* rendererTextControl = renderer(); + + if (innerText && rendererTextControl) { + if (rendererTextControl->style().visibility() == HIDDEN || !innerText->renderBox()->height()) { + cacheSelection(start, end, direction); + return; + } + } } - VisiblePosition startPosition = visiblePositionForIndex(start); - VisiblePosition endPosition; + + Position startPosition = positionForIndex(innerText, start); + Position endPosition; if (start == end) endPosition = startPosition; - else - endPosition = visiblePositionForIndex(end); - -#if !PLATFORM(IOS) - // startPosition and endPosition can be null position for example when - // "-webkit-user-select: none" style attribute is specified. - if (startPosition.isNotNull() && endPosition.isNotNull()) { - ASSERT(startPosition.deepEquivalent().deprecatedNode()->shadowHost() == this - && endPosition.deepEquivalent().deprecatedNode()->shadowHost() == this); + else { + if (direction == SelectionHasBackwardDirection) { + endPosition = startPosition; + startPosition = positionForIndex(innerText, end); + } else + endPosition = positionForIndex(innerText, end); } -#endif - VisibleSelection newSelection; - if (direction == SelectionHasBackwardDirection) - newSelection = VisibleSelection(endPosition, startPosition); - else - newSelection = VisibleSelection(startPosition, endPosition); - newSelection.setIsDirectional(direction != SelectionHasNoDirection); if (Frame* frame = document().frame()) - frame->selection().setSelection(newSelection); + frame->selection().moveWithoutValidationTo(startPosition, endPosition, direction != SelectionHasNoDirection, !hasFocus, intent); } -int HTMLTextFormControlElement::indexForVisiblePosition(const VisiblePosition& pos) const +int HTMLTextFormControlElement::indexForVisiblePosition(const VisiblePosition& position) const { - if (enclosingTextFormControl(pos.deepEquivalent()) != this) + TextControlInnerTextElement* innerText = innerTextElement(); + if (!innerText || !innerText->contains(position.deepEquivalent().anchorNode())) return 0; - bool forSelectionPreservation = false; - return WebCore::indexForVisiblePosition(innerTextElement(), pos, forSelectionPreservation); + unsigned index = indexForPosition(position.deepEquivalent()); + ASSERT(VisiblePosition(positionForIndex(innerTextElement(), index)) == position); + return index; } VisiblePosition HTMLTextFormControlElement::visiblePositionForIndex(int index) const { - return visiblePositionForIndexUsingCharacterIterator(innerTextElement(), index); + VisiblePosition position = positionForIndex(innerTextElement(), index); + ASSERT(indexForVisiblePosition(position) == index); + return position; } int HTMLTextFormControlElement::selectionStart() const @@ -356,7 +360,7 @@ int HTMLTextFormControlElement::computeSelectionStart() const if (!frame) return 0; - return indexForVisiblePosition(frame->selection().start()); + return indexForPosition(frame->selection().selection().start()); } int HTMLTextFormControlElement::selectionEnd() const @@ -375,14 +379,14 @@ int HTMLTextFormControlElement::computeSelectionEnd() const if (!frame) return 0; - return indexForVisiblePosition(frame->selection().end()); + return indexForPosition(frame->selection().selection().end()); } static const AtomicString& directionString(TextFieldSelectionDirection direction) { - DEFINE_STATIC_LOCAL(const AtomicString, none, ("none", AtomicString::ConstructFromLiteral)); - DEFINE_STATIC_LOCAL(const AtomicString, forward, ("forward", AtomicString::ConstructFromLiteral)); - DEFINE_STATIC_LOCAL(const AtomicString, backward, ("backward", AtomicString::ConstructFromLiteral)); + static NeverDestroyed<const AtomicString> none("none", AtomicString::ConstructFromLiteral); + static NeverDestroyed<const AtomicString> forward("forward", AtomicString::ConstructFromLiteral); + static NeverDestroyed<const AtomicString> backward("backward", AtomicString::ConstructFromLiteral); switch (direction) { case SelectionHasNoDirection: @@ -402,7 +406,7 @@ const AtomicString& HTMLTextFormControlElement::selectionDirection() const if (!isTextFormControl()) return directionString(SelectionHasNoDirection); if (document().focusedElement() != this && hasCachedSelection()) - return directionString(m_cachedSelectionDirection); + return directionString(cachedSelectionDirection()); return directionString(computeSelectionDirection()); } @@ -425,14 +429,14 @@ static inline void setContainerAndOffsetForRange(Node* node, int offset, Node*& offsetInContainer = offset; } else { containerNode = node->parentNode(); - offsetInContainer = node->nodeIndex() + offset; + offsetInContainer = node->computeNodeIndex() + offset; } } -PassRefPtr<Range> HTMLTextFormControlElement::selection() const +RefPtr<Range> HTMLTextFormControlElement::selection() const { if (!renderer() || !isTextFormControl() || !hasCachedSelection()) - return 0; + return nullptr; int start = m_cachedSelectionStart; int end = m_cachedSelectionEnd; @@ -440,15 +444,15 @@ PassRefPtr<Range> HTMLTextFormControlElement::selection() const ASSERT(start <= end); TextControlInnerTextElement* innerText = innerTextElement(); if (!innerText) - return 0; + return nullptr; if (!innerText->firstChild()) return Range::create(document(), innerText, 0, innerText, 0); int offset = 0; - Node* startNode = 0; - Node* endNode = 0; - for (Node* node = innerText->firstChild(); node; node = NodeTraversal::next(node, innerText)) { + Node* startNode = nullptr; + Node* endNode = nullptr; + for (Node* node = innerText->firstChild(); node; node = NodeTraversal::next(*node, innerText)) { ASSERT(!node->firstChild()); ASSERT(node->isTextNode() || node->hasTagName(brTag)); int length = node->isTextNode() ? lastOffsetInNode(node) : 1; @@ -465,17 +469,17 @@ PassRefPtr<Range> HTMLTextFormControlElement::selection() const } if (!startNode || !endNode) - return 0; + return nullptr; return Range::create(document(), startNode, start, endNode, end); } -void HTMLTextFormControlElement::restoreCachedSelection() +void HTMLTextFormControlElement::restoreCachedSelection(const AXTextStateChangeIntent& intent) { - setSelectionRange(m_cachedSelectionStart, m_cachedSelectionEnd, m_cachedSelectionDirection); + setSelectionRange(m_cachedSelectionStart, m_cachedSelectionEnd, cachedSelectionDirection(), intent); } -void HTMLTextFormControlElement::selectionChanged(bool userTriggered) +void HTMLTextFormControlElement::selectionChanged(bool shouldFireSelectEvent) { if (!isTextFormControl()) return; @@ -483,22 +487,38 @@ void HTMLTextFormControlElement::selectionChanged(bool userTriggered) // FIXME: Don't re-compute selection start and end if this function was called inside setSelectionRange. // selectionStart() or selectionEnd() will return cached selection when this node doesn't have focus cacheSelection(computeSelectionStart(), computeSelectionEnd(), computeSelectionDirection()); - - if (Frame* frame = document().frame()) { - if (frame->selection().isRange() && userTriggered) - dispatchEvent(Event::create(eventNames().selectEvent, true, false)); - } + + if (shouldFireSelectEvent && m_cachedSelectionStart != m_cachedSelectionEnd) + dispatchEvent(Event::create(eventNames().selectEvent, true, false)); } void HTMLTextFormControlElement::parseAttribute(const QualifiedName& name, const AtomicString& value) { if (name == placeholderAttr) { - updatePlaceholderVisibility(true); - FeatureObserver::observe(&document(), FeatureObserver::PlaceholderAttribute); + updatePlaceholderText(); + updatePlaceholderVisibility(); } else HTMLFormControlElementWithState::parseAttribute(name, value); } +void HTMLTextFormControlElement::disabledStateChanged() +{ + HTMLFormControlElementWithState::disabledStateChanged(); + updateInnerTextElementEditability(); +} + +void HTMLTextFormControlElement::readOnlyAttributeChanged() +{ + HTMLFormControlElementWithState::disabledAttributeChanged(); + updateInnerTextElementEditability(); +} + +void HTMLTextFormControlElement::updateInnerTextElementEditability() +{ + if (TextControlInnerTextElement* innerText = innerTextElement()) + innerText->setAttributeWithoutSynchronization(contenteditableAttr, isDisabledOrReadOnly() ? "false" : "plaintext-only"); +} + bool HTMLTextFormControlElement::lastChangeWasUserEdit() const { if (!isTextFormControl()) @@ -506,70 +526,142 @@ bool HTMLTextFormControlElement::lastChangeWasUserEdit() const return m_lastChangeWasUserEdit; } +static void stripTrailingNewline(StringBuilder& result) +{ + // Remove one trailing newline; there's always one that's collapsed out by rendering. + size_t size = result.length(); + if (size && result[size - 1] == newlineCharacter) + result.resize(size - 1); +} + +static String innerTextValueFrom(TextControlInnerTextElement& innerText) +{ + StringBuilder result; + for (Node* node = innerText.firstChild(); node; node = NodeTraversal::next(*node, &innerText)) { + if (is<HTMLBRElement>(*node)) + result.append(newlineCharacter); + else if (is<Text>(*node)) + result.append(downcast<Text>(*node).data()); + } + stripTrailingNewline(result); + return result.toString(); +} + void HTMLTextFormControlElement::setInnerTextValue(const String& value) { - if (!isTextFormControl()) + TextControlInnerTextElement* innerText = innerTextElement(); + if (!innerText) return; - bool textIsChanged = value != innerTextValue(); - if (textIsChanged || !innerTextElement()->hasChildNodes()) { + ASSERT(isTextFormControl()); + String previousValue = innerTextValueFrom(*innerText); + bool textIsChanged = value != previousValue; + if (textIsChanged || !innerText->hasChildNodes()) { +#if HAVE(ACCESSIBILITY) && !PLATFORM(COCOA) if (textIsChanged && renderer()) { if (AXObjectCache* cache = document().existingAXObjectCache()) cache->postNotification(this, AXObjectCache::AXValueChanged, TargetObservableParent); } - innerTextElement()->setInnerText(value, ASSERT_NO_EXCEPTION); +#endif + + { + // Events dispatched on the inner text element cannot execute arbitrary author scripts. + NoEventDispatchAssertion::EventAllowedScope allowedScope(*userAgentShadowRoot()); + + innerText->setInnerText(value); - if (value.endsWith('\n') || value.endsWith('\r')) - innerTextElement()->appendChild(HTMLBRElement::create(document()), ASSERT_NO_EXCEPTION); + if (value.endsWith('\n') || value.endsWith('\r')) + innerText->appendChild(HTMLBRElement::create(document())); + } + +#if HAVE(ACCESSIBILITY) && PLATFORM(COCOA) + if (textIsChanged && renderer()) { + if (AXObjectCache* cache = document().existingAXObjectCache()) + cache->postTextReplacementNotification(this, AXTextEditTypeDelete, previousValue, AXTextEditTypeInsert, value, VisiblePosition(Position(this, Position::PositionIsBeforeAnchor))); + } +#endif } setFormControlValueMatchesRenderer(true); } -static String finishText(StringBuilder& result) +String HTMLTextFormControlElement::innerTextValue() const { - // Remove one trailing newline; there's always one that's collapsed out by rendering. - size_t size = result.length(); - if (size && result[size - 1] == '\n') - result.resize(--size); - return result.toString(); + TextControlInnerTextElement* innerText = innerTextElement(); + return innerText ? innerTextValueFrom(*innerText) : emptyString(); +} + +static Position positionForIndex(TextControlInnerTextElement* innerText, unsigned index) +{ + unsigned remainingCharactersToMoveForward = index; + Node* lastBrOrText = innerText; + for (Node* node = innerText; node; node = NodeTraversal::next(*node, innerText)) { + if (node->hasTagName(brTag)) { + if (!remainingCharactersToMoveForward) + return positionBeforeNode(node); + remainingCharactersToMoveForward--; + lastBrOrText = node; + } else if (is<Text>(*node)) { + Text& text = downcast<Text>(*node); + if (remainingCharactersToMoveForward < text.length()) + return Position(&text, remainingCharactersToMoveForward); + remainingCharactersToMoveForward -= text.length(); + lastBrOrText = node; + } + } + return lastPositionInOrAfterNode(lastBrOrText); } -String HTMLTextFormControlElement::innerTextValue() const +unsigned HTMLTextFormControlElement::indexForPosition(const Position& passedPosition) const { - if (!isTextFormControl()) - return emptyString(); - TextControlInnerTextElement* innerText = innerTextElement(); - if (!innerText) - return emptyString(); + if (!innerText || !innerText->contains(passedPosition.anchorNode()) || passedPosition.isNull()) + return 0; - StringBuilder result; - for (Node* node = innerText; node; node = NodeTraversal::next(node, innerText)) { - if (node->hasTagName(brTag)) - result.append(newlineCharacter); - else if (node->isTextNode()) - result.append(toText(node)->data()); + if (positionBeforeNode(innerText) == passedPosition) + return 0; + + unsigned index = 0; + Node* startNode = passedPosition.computeNodeBeforePosition(); + if (!startNode) + startNode = passedPosition.containerNode(); + ASSERT(startNode); + ASSERT(innerText->contains(startNode)); + + for (Node* node = startNode; node; node = NodeTraversal::previous(*node, innerText)) { + if (is<Text>(*node)) { + unsigned length = downcast<Text>(*node).length(); + if (node == passedPosition.containerNode()) + index += std::min<unsigned>(length, passedPosition.offsetInContainerNode()); + else + index += length; + } else if (is<HTMLBRElement>(*node)) + ++index; } - return finishText(result); + + unsigned length = innerTextValue().length(); + index = std::min(index, length); // FIXME: We shouldn't have to call innerTextValue() just to ignore the last LF. See finishText. +#ifndef ASSERT_DISABLED + VisiblePosition visiblePosition = passedPosition; + unsigned indexComputedByVisiblePosition = 0; + if (visiblePosition.isNotNull()) + indexComputedByVisiblePosition = WebCore::indexForVisiblePosition(innerText, visiblePosition, false /* forSelectionPreservation */); + ASSERT(index == indexComputedByVisiblePosition); +#endif + return index; } #if PLATFORM(IOS) void HTMLTextFormControlElement::hidePlaceholder() { - if (!supportsPlaceholder()) - return; - HTMLElement* placeholder = placeholderElement(); - if (!placeholder) { - updatePlaceholderText(); - return; - } - placeholder->setInlineStyleProperty(CSSPropertyVisibility, ASCIILiteral("hidden")); + if (HTMLElement* placeholder = placeholderElement()) + placeholder->setInlineStyleProperty(CSSPropertyVisibility, CSSValueHidden, true); } void HTMLTextFormControlElement::showPlaceholderIfNecessary() { - updatePlaceholderVisibility(false); + if (HTMLElement* placeholder = placeholderElement()) + placeholder->setInlineStyleProperty(CSSPropertyVisibility, CSSValueVisible, true); } #endif @@ -614,11 +706,11 @@ String HTMLTextFormControlElement::valueWithHardLineBreaks() const getNextSoftBreak(line, breakNode, breakOffset); StringBuilder result; - for (Node* node = innerText->firstChild(); node; node = NodeTraversal::next(node, innerText)) { - if (node->hasTagName(brTag)) + for (Node* node = innerText->firstChild(); node; node = NodeTraversal::next(*node, innerText)) { + if (is<HTMLBRElement>(*node)) result.append(newlineCharacter); - else if (node->isTextNode()) { - String data = toText(node)->data(); + else if (is<Text>(*node)) { + String data = downcast<Text>(*node).data(); unsigned length = data.length(); unsigned position = 0; while (breakNode == node && breakOffset <= length) { @@ -634,7 +726,8 @@ String HTMLTextFormControlElement::valueWithHardLineBreaks() const while (breakNode == node) getNextSoftBreak(line, breakNode, breakOffset); } - return finishText(result); + stripTrailingNewline(result); + return result.toString(); } HTMLTextFormControlElement* enclosingTextFormControl(const Position& position) @@ -647,7 +740,7 @@ HTMLTextFormControlElement* enclosingTextFormControl(const Position& position) if (!container) return nullptr; Element* ancestor = container->shadowHost(); - return ancestor && isHTMLTextFormControlElement(*ancestor) ? toHTMLTextFormControlElement(ancestor) : nullptr; + return ancestor && is<HTMLTextFormControlElement>(*ancestor) ? downcast<HTMLTextFormControlElement>(ancestor) : nullptr; } static const Element* parentHTMLElement(const Element* element) @@ -663,14 +756,14 @@ static const Element* parentHTMLElement(const Element* element) String HTMLTextFormControlElement::directionForFormData() const { for (const Element* element = this; element; element = parentHTMLElement(element)) { - const AtomicString& dirAttributeValue = element->fastGetAttribute(dirAttr); + const AtomicString& dirAttributeValue = element->attributeWithoutSynchronization(dirAttr); if (dirAttributeValue.isNull()) continue; - if (equalIgnoringCase(dirAttributeValue, "rtl") || equalIgnoringCase(dirAttributeValue, "ltr")) + if (equalLettersIgnoringASCIICase(dirAttributeValue, "rtl") || equalLettersIgnoringASCIICase(dirAttributeValue, "ltr")) return dirAttributeValue; - if (equalIgnoringCase(dirAttributeValue, "auto")) { + if (equalLettersIgnoringASCIICase(dirAttributeValue, "auto")) { bool isAuto; TextDirection textDirection = static_cast<const HTMLElement*>(element)->directionalityIfhasDirAutoAttribute(isAuto); return textDirection == RTL ? "rtl" : "ltr"; @@ -680,4 +773,64 @@ String HTMLTextFormControlElement::directionForFormData() const return "ltr"; } +ExceptionOr<void> HTMLTextFormControlElement::setMaxLength(int maxLength) +{ + if (maxLength < 0 || (m_minLength >= 0 && maxLength < m_minLength)) + return Exception { INDEX_SIZE_ERR }; + setIntegralAttribute(maxlengthAttr, maxLength); + return { }; +} + +ExceptionOr<void> HTMLTextFormControlElement::setMinLength(int minLength) +{ + if (minLength < 0 || (m_maxLength >= 0 && minLength > m_maxLength)) + return Exception { INDEX_SIZE_ERR }; + setIntegralAttribute(minlengthAttr, minLength); + return { }; +} + +void HTMLTextFormControlElement::adjustInnerTextStyle(const RenderStyle& parentStyle, RenderStyle& textBlockStyle) const +{ + // The inner block, if present, always has its direction set to LTR, + // so we need to inherit the direction and unicode-bidi style from the element. + textBlockStyle.setDirection(parentStyle.direction()); + textBlockStyle.setUnicodeBidi(parentStyle.unicodeBidi()); + + if (HTMLElement* innerText = innerTextElement()) { + if (const StyleProperties* properties = innerText->presentationAttributeStyle()) { + RefPtr<CSSValue> value = properties->getPropertyCSSValue(CSSPropertyWebkitUserModify); + if (is<CSSPrimitiveValue>(value.get())) + textBlockStyle.setUserModify(downcast<CSSPrimitiveValue>(*value)); + } + } + + if (isDisabledFormControl()) + textBlockStyle.setColor(document().page()->theme().disabledTextColor(textBlockStyle.visitedDependentColor(CSSPropertyColor), parentStyle.visitedDependentColor(CSSPropertyBackgroundColor))); +#if PLATFORM(IOS) + if (textBlockStyle.textSecurity() != TSNONE && !textBlockStyle.isLeftToRightDirection()) { + // Preserve the alignment but force the direction to LTR so that the last-typed, unmasked character + // (which cannot have RTL directionality) will appear to the right of the masked characters. See <rdar://problem/7024375>. + + switch (textBlockStyle.textAlign()) { + case TASTART: + case JUSTIFY: + textBlockStyle.setTextAlign(RIGHT); + break; + case TAEND: + textBlockStyle.setTextAlign(LEFT); + break; + case LEFT: + case RIGHT: + case CENTER: + case WEBKIT_LEFT: + case WEBKIT_RIGHT: + case WEBKIT_CENTER: + break; + } + + textBlockStyle.setDirection(LTR); + } +#endif +} + } // namespace Webcore |