summaryrefslogtreecommitdiff
path: root/Source/WebCore/html/HTMLTextFormControlElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/html/HTMLTextFormControlElement.cpp')
-rw-r--r--Source/WebCore/html/HTMLTextFormControlElement.cpp469
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