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.cpp603
1 files changed, 603 insertions, 0 deletions
diff --git a/Source/WebCore/html/HTMLTextFormControlElement.cpp b/Source/WebCore/html/HTMLTextFormControlElement.cpp
new file mode 100644
index 000000000..04c926c88
--- /dev/null
+++ b/Source/WebCore/html/HTMLTextFormControlElement.cpp
@@ -0,0 +1,603 @@
+/*
+ * 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.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+#include "HTMLTextFormControlElement.h"
+
+#include "AXObjectCache.h"
+#include "Attribute.h"
+#include "Chrome.h"
+#include "ChromeClient.h"
+#include "Document.h"
+#include "Event.h"
+#include "EventNames.h"
+#include "Frame.h"
+#include "HTMLBRElement.h"
+#include "HTMLFormElement.h"
+#include "HTMLInputElement.h"
+#include "HTMLNames.h"
+#include "Page.h"
+#include "RenderBox.h"
+#include "RenderTextControl.h"
+#include "RenderTheme.h"
+#include "ScriptEventListener.h"
+#include "Text.h"
+#include "TextIterator.h"
+#include <wtf/text/StringBuilder.h>
+
+namespace WebCore {
+
+using namespace HTMLNames;
+using namespace std;
+
+HTMLTextFormControlElement::HTMLTextFormControlElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* form)
+ : HTMLFormControlElementWithState(tagName, doc, form)
+ , m_lastChangeWasUserEdit(false)
+ , m_cachedSelectionStart(-1)
+ , m_cachedSelectionEnd(-1)
+ , m_cachedSelectionDirection(SelectionHasNoDirection)
+{
+}
+
+HTMLTextFormControlElement::~HTMLTextFormControlElement()
+{
+}
+
+void HTMLTextFormControlElement::insertedIntoDocument()
+{
+ HTMLFormControlElement::insertedIntoDocument();
+ String initialValue = value();
+ setTextAsOfLastFormControlChangeEvent(initialValue.isNull() ? emptyString() : initialValue);
+}
+
+void HTMLTextFormControlElement::dispatchFocusEvent(PassRefPtr<Node> oldFocusedNode)
+{
+ if (supportsPlaceholder())
+ updatePlaceholderVisibility(false);
+ handleFocusEvent();
+ HTMLFormControlElementWithState::dispatchFocusEvent(oldFocusedNode);
+}
+
+void HTMLTextFormControlElement::dispatchBlurEvent(PassRefPtr<Node> newFocusedNode)
+{
+ if (supportsPlaceholder())
+ updatePlaceholderVisibility(false);
+ handleBlurEvent();
+ HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedNode);
+}
+
+void HTMLTextFormControlElement::defaultEventHandler(Event* event)
+{
+ if (event->type() == eventNames().webkitEditableContentChangedEvent && renderer() && renderer()->isTextControl()) {
+ m_lastChangeWasUserEdit = true;
+ subtreeHasChanged();
+ return;
+ }
+
+ HTMLFormControlElementWithState::defaultEventHandler(event);
+}
+
+void HTMLTextFormControlElement::forwardEvent(Event* event)
+{
+ if (event->type() == eventNames().blurEvent || event->type() == eventNames().focusEvent)
+ return;
+ innerTextElement()->defaultEventHandler(event);
+}
+
+String HTMLTextFormControlElement::strippedPlaceholder() const
+{
+ // According to the HTML5 specification, we need to remove CR and LF from
+ // the attribute value.
+ const AtomicString& attributeValue = getAttribute(placeholderAttr);
+ if (!attributeValue.contains(newlineCharacter) && !attributeValue.contains(carriageReturn))
+ return attributeValue;
+
+ StringBuilder stripped;
+ unsigned length = attributeValue.length();
+ stripped.reserveCapacity(length);
+ for (unsigned i = 0; i < length; ++i) {
+ UChar character = attributeValue[i];
+ if (character == newlineCharacter || character == carriageReturn)
+ continue;
+ stripped.append(character);
+ }
+ return stripped.toString();
+}
+
+static bool isNotLineBreak(UChar ch) { return ch != newlineCharacter && ch != carriageReturn; }
+
+bool HTMLTextFormControlElement::isPlaceholderEmpty() const
+{
+ const AtomicString& attributeValue = getAttribute(placeholderAttr);
+ return attributeValue.string().find(isNotLineBreak) == notFound;
+}
+
+bool HTMLTextFormControlElement::placeholderShouldBeVisible() const
+{
+ return supportsPlaceholder()
+ && isEmptyValue()
+ && isEmptySuggestedValue()
+ && !isPlaceholderEmpty()
+ && (document()->focusedNode() != this || (renderer() && renderer()->theme()->shouldShowPlaceholderWhenFocused()))
+ && (!renderer() || renderer()->style()->visibility() == VISIBLE);
+}
+
+void HTMLTextFormControlElement::updatePlaceholderVisibility(bool placeholderValueChanged)
+{
+ if (!supportsPlaceholder())
+ return;
+ if (!placeholderElement() || placeholderValueChanged)
+ updatePlaceholderText();
+ HTMLElement* placeholder = placeholderElement();
+ if (!placeholder)
+ return;
+ ExceptionCode ec = 0;
+ placeholder->ensureInlineStyleDecl()->setProperty(CSSPropertyVisibility, placeholderShouldBeVisible() ? "visible" : "hidden", ec);
+ ASSERT(!ec);
+}
+
+RenderTextControl* HTMLTextFormControlElement::textRendererAfterUpdateLayout()
+{
+ if (!isTextFormControl())
+ return 0;
+ document()->updateLayoutIgnorePendingStylesheets();
+ return toRenderTextControl(renderer());
+}
+
+void HTMLTextFormControlElement::setSelectionStart(int start)
+{
+ setSelectionRange(start, max(start, selectionEnd()), selectionDirection());
+}
+
+void HTMLTextFormControlElement::setSelectionEnd(int end)
+{
+ setSelectionRange(min(end, selectionStart()), end, selectionDirection());
+}
+
+void HTMLTextFormControlElement::setSelectionDirection(const String& direction)
+{
+ setSelectionRange(selectionStart(), selectionEnd(), direction);
+}
+
+void HTMLTextFormControlElement::select()
+{
+ setSelectionRange(0, numeric_limits<int>::max(), SelectionHasNoDirection);
+}
+
+String HTMLTextFormControlElement::selectedText() const
+{
+ if (!isTextFormControl())
+ return String();
+ return value().substring(selectionStart(), selectionEnd() - selectionStart());
+}
+
+void HTMLTextFormControlElement::dispatchFormControlChangeEvent()
+{
+ if (m_textAsOfLastFormControlChangeEvent != value()) {
+ HTMLElement::dispatchChangeEvent();
+ setTextAsOfLastFormControlChangeEvent(value());
+ }
+ setChangedSinceLastFormControlChangeEvent(false);
+}
+
+static inline bool hasVisibleTextArea(RenderTextControl* textControl, HTMLElement* innerText)
+{
+ ASSERT(textControl);
+ return textControl->style()->visibility() != HIDDEN && innerText && innerText->renderer() && innerText->renderBox()->height();
+}
+
+void HTMLTextFormControlElement::setSelectionRange(int start, int end, const String& directionString)
+{
+ TextFieldSelectionDirection direction = SelectionHasNoDirection;
+ if (directionString == "forward")
+ direction = SelectionHasForwardDirection;
+ else if (directionString == "backward")
+ direction = SelectionHasBackwardDirection;
+
+ return setSelectionRange(start, end, direction);
+}
+
+void HTMLTextFormControlElement::setSelectionRange(int start, int end, TextFieldSelectionDirection direction)
+{
+ document()->updateLayoutIgnorePendingStylesheets();
+
+ if (!renderer() || !renderer()->isTextControl())
+ return;
+
+ end = max(end, 0);
+ start = min(max(start, 0), end);
+
+ RenderTextControl* control = toRenderTextControl(renderer());
+ if (!hasVisibleTextArea(control, innerTextElement())) {
+ cacheSelection(start, end, direction);
+ return;
+ }
+ VisiblePosition startPosition = control->visiblePositionForIndex(start);
+ VisiblePosition endPosition;
+ if (start == end)
+ endPosition = startPosition;
+ else
+ endPosition = control->visiblePositionForIndex(end);
+
+ // 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()->shadowAncestorNode() == this
+ && endPosition.deepEquivalent().deprecatedNode()->shadowAncestorNode() == this);
+ }
+ 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);
+}
+
+int HTMLTextFormControlElement::indexForVisiblePosition(const VisiblePosition& pos) const
+{
+ Position indexPosition = pos.deepEquivalent().parentAnchoredEquivalent();
+ if (enclosingTextFormControl(indexPosition) != this)
+ return 0;
+ ExceptionCode ec = 0;
+ RefPtr<Range> range = Range::create(indexPosition.document());
+ range->setStart(innerTextElement(), 0, ec);
+ ASSERT(!ec);
+ range->setEnd(indexPosition.containerNode(), indexPosition.offsetInContainerNode(), ec);
+ ASSERT(!ec);
+ return TextIterator::rangeLength(range.get());
+}
+
+int HTMLTextFormControlElement::selectionStart() const
+{
+ if (!isTextFormControl())
+ return 0;
+ if (document()->focusedNode() != this && hasCachedSelection())
+ return m_cachedSelectionStart;
+
+ return computeSelectionStart();
+}
+
+int HTMLTextFormControlElement::computeSelectionStart() const
+{
+ ASSERT(isTextFormControl());
+ Frame* frame = document()->frame();
+ if (!frame)
+ return 0;
+
+ return indexForVisiblePosition(frame->selection()->start());
+}
+
+int HTMLTextFormControlElement::selectionEnd() const
+{
+ if (!isTextFormControl())
+ return 0;
+ if (document()->focusedNode() != this && hasCachedSelection())
+ return m_cachedSelectionEnd;
+ return computeSelectionEnd();
+}
+
+int HTMLTextFormControlElement::computeSelectionEnd() const
+{
+ ASSERT(isTextFormControl());
+ Frame* frame = document()->frame();
+ if (!frame)
+ return 0;
+
+ return indexForVisiblePosition(frame->selection()->end());
+}
+
+static const AtomicString& directionString(TextFieldSelectionDirection direction)
+{
+ DEFINE_STATIC_LOCAL(const AtomicString, none, ("none"));
+ DEFINE_STATIC_LOCAL(const AtomicString, forward, ("forward"));
+ DEFINE_STATIC_LOCAL(const AtomicString, backward, ("backward"));
+
+ switch (direction) {
+ case SelectionHasNoDirection:
+ return none;
+ case SelectionHasForwardDirection:
+ return forward;
+ case SelectionHasBackwardDirection:
+ return backward;
+ }
+
+ ASSERT_NOT_REACHED();
+ return none;
+}
+
+const AtomicString& HTMLTextFormControlElement::selectionDirection() const
+{
+ if (!isTextFormControl())
+ return directionString(SelectionHasNoDirection);
+ if (document()->focusedNode() != this && hasCachedSelection())
+ return directionString(m_cachedSelectionDirection);
+
+ return directionString(computeSelectionDirection());
+}
+
+TextFieldSelectionDirection HTMLTextFormControlElement::computeSelectionDirection() const
+{
+ ASSERT(isTextFormControl());
+ Frame* frame = document()->frame();
+ if (!frame)
+ return SelectionHasNoDirection;
+
+ const VisibleSelection& selection = frame->selection()->selection();
+ return selection.isDirectional() ? (selection.isBaseFirst() ? SelectionHasForwardDirection : SelectionHasBackwardDirection) : SelectionHasNoDirection;
+}
+
+static inline void setContainerAndOffsetForRange(Node* node, int offset, Node*& containerNode, int& offsetInContainer)
+{
+ if (node->isTextNode()) {
+ containerNode = node;
+ offsetInContainer = offset;
+ } else {
+ containerNode = node->parentNode();
+ offsetInContainer = node->nodeIndex() + offset;
+ }
+}
+
+PassRefPtr<Range> HTMLTextFormControlElement::selection() const
+{
+ if (!renderer() || !isTextFormControl() || !hasCachedSelection())
+ return 0;
+
+ int start = m_cachedSelectionStart;
+ int end = m_cachedSelectionEnd;
+
+ ASSERT(start <= end);
+ HTMLElement* innerText = innerTextElement();
+ if (!innerText)
+ return 0;
+
+ 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 = node->traverseNextNode(innerText)) {
+ ASSERT(!node->firstChild());
+ ASSERT(node->isTextNode() || node->hasTagName(brTag));
+ int length = node->isTextNode() ? lastOffsetInNode(node) : 1;
+
+ if (offset <= start && start <= offset + length)
+ setContainerAndOffsetForRange(node, start - offset, startNode, start);
+
+ if (offset <= end && end <= offset + length) {
+ setContainerAndOffsetForRange(node, end - offset, endNode, end);
+ break;
+ }
+
+ offset += length;
+ }
+
+ if (!startNode || !endNode)
+ return 0;
+
+ return Range::create(document(), startNode, start, endNode, end);
+}
+
+void HTMLTextFormControlElement::restoreCachedSelection()
+{
+ setSelectionRange(m_cachedSelectionStart, m_cachedSelectionEnd, m_cachedSelectionDirection);
+}
+
+void HTMLTextFormControlElement::selectionChanged(bool userTriggered)
+{
+ if (!renderer() || !isTextFormControl())
+ return;
+
+ // 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));
+ }
+}
+
+void HTMLTextFormControlElement::parseMappedAttribute(Attribute* attr)
+{
+ if (attr->name() == placeholderAttr)
+ updatePlaceholderVisibility(true);
+ else if (attr->name() == onselectAttr)
+ setAttributeEventListener(eventNames().selectEvent, createAttributeEventListener(this, attr));
+ else if (attr->name() == onchangeAttr)
+ setAttributeEventListener(eventNames().changeEvent, createAttributeEventListener(this, attr));
+ else
+ HTMLFormControlElementWithState::parseMappedAttribute(attr);
+}
+
+void HTMLTextFormControlElement::notifyFormStateChanged()
+{
+ Frame* frame = document()->frame();
+ if (!frame)
+ return;
+
+ if (Page* page = frame->page())
+ page->chrome()->client()->formStateDidChange(this);
+}
+
+bool HTMLTextFormControlElement::lastChangeWasUserEdit() const
+{
+ if (!isTextFormControl())
+ return false;
+ return m_lastChangeWasUserEdit;
+}
+
+void HTMLTextFormControlElement::setInnerTextValue(const String& value)
+{
+ if (!isTextFormControl())
+ return;
+
+ bool textIsChanged = value != innerTextValue();
+ if (textIsChanged || !innerTextElement()->hasChildNodes()) {
+ if (textIsChanged && document() && renderer() && AXObjectCache::accessibilityEnabled())
+ document()->axObjectCache()->postNotification(renderer(), AXObjectCache::AXValueChanged, false);
+
+ ExceptionCode ec = 0;
+ innerTextElement()->setInnerText(value, ec);
+ ASSERT(!ec);
+
+ if (value.endsWith("\n") || value.endsWith("\r")) {
+ innerTextElement()->appendChild(HTMLBRElement::create(document()), ec);
+ ASSERT(!ec);
+ }
+ }
+
+ setFormControlValueMatchesRenderer(true);
+}
+
+static String finishText(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] == '\n')
+ result.resize(--size);
+ return result.toString();
+}
+
+String HTMLTextFormControlElement::innerTextValue() const
+{
+ HTMLElement* innerText = innerTextElement();
+ if (!innerText || !isTextFormControl())
+ return emptyString();
+
+ StringBuilder result;
+ for (Node* node = innerText; node; node = node->traverseNextNode(innerText)) {
+ if (node->hasTagName(brTag))
+ result.append(newlineCharacter);
+ else if (node->isTextNode())
+ result.append(static_cast<Text*>(node)->data());
+ }
+ return finishText(result);
+}
+
+static void getNextSoftBreak(RootInlineBox*& line, Node*& breakNode, unsigned& breakOffset)
+{
+ RootInlineBox* next;
+ for (; line; line = next) {
+ next = line->nextRootBox();
+ if (next && !line->endsWithBreak()) {
+ ASSERT(line->lineBreakObj());
+ breakNode = line->lineBreakObj()->node();
+ breakOffset = line->lineBreakPos();
+ line = next;
+ return;
+ }
+ }
+ breakNode = 0;
+ breakOffset = 0;
+}
+
+String HTMLTextFormControlElement::valueWithHardLineBreaks() const
+{
+ // FIXME: It's not acceptable to ignore the HardWrap setting when there is no renderer.
+ // While we have no evidence this has ever been a practical problem, it would be best to fix it some day.
+ HTMLElement* innerText = innerTextElement();
+ if (!innerText || !isTextFormControl())
+ return value();
+
+ RenderBlock* renderer = toRenderBlock(innerText->renderer());
+ if (!renderer)
+ return value();
+
+ Node* breakNode;
+ unsigned breakOffset;
+ RootInlineBox* line = renderer->firstRootBox();
+ if (!line)
+ return value();
+
+ getNextSoftBreak(line, breakNode, breakOffset);
+
+ StringBuilder result;
+ for (Node* node = innerText->firstChild(); node; node = node->traverseNextNode(innerText)) {
+ if (node->hasTagName(brTag))
+ result.append(newlineCharacter);
+ else if (node->isTextNode()) {
+ String data = static_cast<Text*>(node)->data();
+ unsigned length = data.length();
+ unsigned position = 0;
+ while (breakNode == node && breakOffset <= length) {
+ if (breakOffset > position) {
+ result.append(data.characters() + position, breakOffset - position);
+ position = breakOffset;
+ result.append(newlineCharacter);
+ }
+ getNextSoftBreak(line, breakNode, breakOffset);
+ }
+ result.append(data.characters() + position, length - position);
+ }
+ while (breakNode == node)
+ getNextSoftBreak(line, breakNode, breakOffset);
+ }
+ return finishText(result);
+}
+
+HTMLTextFormControlElement* enclosingTextFormControl(const Position& position)
+{
+ ASSERT(position.isNull() || position.anchorType() == Position::PositionIsOffsetInAnchor
+ || position.containerNode() || !position.anchorNode()->shadowAncestorNode());
+ Node* container = position.containerNode();
+ if (!container)
+ return 0;
+ Node* ancestor = container->shadowAncestorNode();
+ return ancestor != container ? toTextFormControl(ancestor) : 0;
+}
+
+static const Element* parentHTMLElement(const Element* element)
+{
+ while (element) {
+ element = element->parentElement();
+ if (element && element->isHTMLElement())
+ return element;
+ }
+ return 0;
+}
+
+String HTMLTextFormControlElement::directionForFormData() const
+{
+ for (const Element* element = this; element; element = parentHTMLElement(element)) {
+ const AtomicString& dirAttributeValue = element->fastGetAttribute(dirAttr);
+ if (dirAttributeValue.isNull())
+ continue;
+
+ if (equalIgnoringCase(dirAttributeValue, "rtl") || equalIgnoringCase(dirAttributeValue, "ltr"))
+ return dirAttributeValue;
+
+ if (equalIgnoringCase(dirAttributeValue, "auto")) {
+ bool isAuto;
+ TextDirection textDirection = static_cast<const HTMLElement*>(element)->directionalityIfhasDirAutoAttribute(isAuto);
+ return textDirection == RTL ? "rtl" : "ltr";
+ }
+ }
+
+ return "ltr";
+}
+
+} // namespace Webcore