--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/webengine/osswebengine/WebCore/rendering/RenderTextControl.cpp Mon Mar 30 12:54:55 2009 +0300
@@ -0,0 +1,1210 @@
+/**
+ * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
+ *
+ * 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 "RenderTextControl.h"
+
+#include "CharacterNames.h"
+#include "Document.h"
+#include "Editor.h"
+#include "EditorClient.h"
+#include "Event.h"
+#include "EventNames.h"
+#include "Frame.h"
+#include "HTMLBRElement.h"
+#include "HTMLInputElement.h"
+#include "HTMLNames.h"
+#include "HTMLTextAreaElement.h"
+#include "HTMLTextFieldInnerElement.h"
+#include "HitTestResult.h"
+#include "LocalizedStrings.h"
+#include "MouseEvent.h"
+#include "PlatformScrollBar.h"
+#include "RenderTheme.h"
+#include "SearchPopupMenu.h"
+#include "SelectionController.h"
+#include "Settings.h"
+#include "Text.h"
+#include "TextIterator.h"
+#include "TextStyle.h"
+#include "htmlediting.h"
+#include "visible_units.h"
+#include <math.h>
+#if PLATFORM(SYMBIAN)
+#include "RenderView.h"
+#include "WebCoreFrameBridge.h"
+#endif
+
+using namespace std;
+
+namespace WebCore {
+
+using namespace EventNames;
+using namespace HTMLNames;
+
+class RenderTextControlInnerBlock : public RenderBlock {
+public:
+ RenderTextControlInnerBlock(Node* node) : RenderBlock(node) { }
+
+ virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction);
+};
+
+bool RenderTextControlInnerBlock::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction)
+{
+ RenderTextControl* renderer = static_cast<RenderTextControl*>(node()->shadowAncestorNode()->renderer());
+
+ return RenderBlock::nodeAtPoint(request, result, x, y, tx, ty, renderer->placeholderIsVisible() ? HitTestBlockBackground : hitTestAction);
+}
+
+RenderTextControl::RenderTextControl(Node* node, bool multiLine)
+ : RenderBlock(node)
+ , m_dirty(false)
+ , m_multiLine(multiLine)
+ , m_placeholderVisible(false)
+ , m_userEdited(false)
+ , m_searchPopup(0)
+ , m_searchPopupIsVisible(false)
+ , m_searchEventTimer(this, &RenderTextControl::searchEventTimerFired)
+{
+}
+
+RenderTextControl::~RenderTextControl()
+{
+ if (m_searchPopup) {
+ m_searchPopup->disconnectClient();
+ m_searchPopup = 0;
+ }
+ if (m_multiLine && node())
+ static_cast<HTMLTextAreaElement*>(node())->rendererWillBeDestroyed();
+ // The children renderers have already been destroyed by destroyLeftoverChildren
+ if (m_innerBlock)
+ m_innerBlock->detach();
+ else if (m_innerText)
+ m_innerText->detach();
+}
+
+void RenderTextControl::setStyle(RenderStyle* style)
+{
+ RenderBlock::setStyle(style);
+ if (m_innerBlock) {
+ // We may have set the width and the height in the old style in layout(). Reset them now to avoid
+ // getting a spurious layout hint.
+ m_innerBlock->renderer()->style()->setHeight(Length());
+ m_innerBlock->renderer()->style()->setWidth(Length());
+ m_innerBlock->renderer()->setStyle(createInnerBlockStyle(style));
+ }
+
+ if (m_innerText) {
+ RenderBlock* textBlockRenderer = static_cast<RenderBlock*>(m_innerText->renderer());
+ RenderStyle* textBlockStyle = createInnerTextStyle(style);
+ // We may have set the width and the height in the old style in layout(). Reset them now to avoid
+ // getting a spurious layout hint.
+ textBlockRenderer->style()->setHeight(Length());
+ textBlockRenderer->style()->setWidth(Length());
+ textBlockRenderer->setStyle(textBlockStyle);
+ for (Node* n = m_innerText->firstChild(); n; n = n->traverseNextNode(m_innerText.get())) {
+ if (n->renderer())
+ n->renderer()->setStyle(textBlockStyle);
+ }
+ }
+ if (m_resultsButton)
+ m_resultsButton->renderer()->setStyle(createResultsButtonStyle(style));
+
+ if (m_cancelButton)
+ m_cancelButton->renderer()->setStyle(createCancelButtonStyle(style));
+
+ setHasOverflowClip(false);
+ setReplaced(isInline());
+}
+
+static Color disabledTextColor(const Color& textColor, const Color& backgroundColor)
+{
+ // The explcit check for black is an optimization for the 99% case (black on white).
+ // This also means that black on black will turn into grey on black when disabled.
+ if (textColor.rgb() == Color::black || differenceSquared(textColor, Color::white) > differenceSquared(backgroundColor, Color::white))
+ return textColor.light();
+ return textColor.dark();
+}
+
+RenderStyle* RenderTextControl::createInnerBlockStyle(RenderStyle* startStyle)
+{
+ RenderStyle* innerBlockStyle = new (renderArena()) RenderStyle();
+
+ innerBlockStyle->inheritFrom(startStyle);
+ innerBlockStyle->setDisplay(BLOCK);
+ innerBlockStyle->setDirection(LTR);
+ // We don't want the shadow dom to be editable, so we set this block to read-only in case the input itself is editable.
+ innerBlockStyle->setUserModify(READ_ONLY);
+
+ return innerBlockStyle;
+}
+
+RenderStyle* RenderTextControl::createInnerTextStyle(RenderStyle* startStyle)
+{
+ RenderStyle* textBlockStyle = new (renderArena()) RenderStyle();
+ HTMLGenericFormElement* element = static_cast<HTMLGenericFormElement*>(node());
+
+ textBlockStyle->inheritFrom(startStyle);
+ // The inner block, if present, always has its direction set to LTR,
+ // so we need to inherit the direction from the element.
+ textBlockStyle->setDirection(style()->direction());
+ textBlockStyle->setUserModify(element->isReadOnlyControl() || element->disabled() ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY);
+ if (m_innerBlock)
+ textBlockStyle->setDisplay(INLINE_BLOCK);
+ else
+ textBlockStyle->setDisplay(BLOCK);
+
+ if (m_multiLine) {
+ // Forward overflow properties.
+ textBlockStyle->setOverflowX(startStyle->overflowX() == OVISIBLE ? OAUTO : startStyle->overflowX());
+ textBlockStyle->setOverflowY(startStyle->overflowY() == OVISIBLE ? OAUTO : startStyle->overflowY());
+
+ // Set word wrap property based on wrap attribute
+ if (static_cast<HTMLTextAreaElement*>(element)->wrap() == HTMLTextAreaElement::ta_NoWrap) {
+ textBlockStyle->setWhiteSpace(PRE);
+ textBlockStyle->setWordWrap(NormalWordWrap);
+ } else {
+ textBlockStyle->setWhiteSpace(PRE_WRAP);
+ textBlockStyle->setWordWrap(BreakWordWrap);
+ }
+ } else {
+ textBlockStyle->setWhiteSpace(PRE);
+ textBlockStyle->setWordWrap(NormalWordWrap);
+ textBlockStyle->setOverflowX(OHIDDEN);
+ textBlockStyle->setOverflowY(OHIDDEN);
+
+ // Do not allow line-height to be smaller than our default.
+ if (textBlockStyle->font().lineSpacing() > lineHeight(true, true))
+ textBlockStyle->setLineHeight(Length(-100.0f, Percent));
+ }
+
+ if (!m_multiLine) {
+ // We're adding one extra pixel of padding to match WinIE.
+ textBlockStyle->setPaddingLeft(Length(1, Fixed));
+ textBlockStyle->setPaddingRight(Length(1, Fixed));
+ } else {
+ // We're adding three extra pixels of padding to line textareas up with text fields.
+ textBlockStyle->setPaddingLeft(Length(3, Fixed));
+ textBlockStyle->setPaddingRight(Length(3, Fixed));
+ }
+
+ if (!element->isEnabled())
+ textBlockStyle->setColor(disabledTextColor(startStyle->color(), startStyle->backgroundColor()));
+
+ return textBlockStyle;
+}
+
+RenderStyle* RenderTextControl::createResultsButtonStyle(RenderStyle* startStyle)
+{
+ ASSERT(!m_multiLine);
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
+ RenderStyle* resultsBlockStyle;
+ if (input->maxResults() < 0)
+ resultsBlockStyle = getPseudoStyle(RenderStyle::SEARCH_DECORATION);
+ else if (!input->maxResults())
+ resultsBlockStyle = getPseudoStyle(RenderStyle::SEARCH_RESULTS_DECORATION);
+ else
+ resultsBlockStyle = getPseudoStyle(RenderStyle::SEARCH_RESULTS_BUTTON);
+
+ if (!resultsBlockStyle)
+ resultsBlockStyle = new (renderArena()) RenderStyle();
+
+ if (startStyle)
+ resultsBlockStyle->inheritFrom(startStyle);
+
+ resultsBlockStyle->setDisplay(INLINE_BLOCK);
+
+ return resultsBlockStyle;
+}
+
+RenderStyle* RenderTextControl::createCancelButtonStyle(RenderStyle* startStyle)
+{
+ RenderStyle* cancelBlockStyle;
+
+ if (RenderStyle* pseudoStyle = getPseudoStyle(RenderStyle::SEARCH_CANCEL_BUTTON))
+ // We may be sharing style with another search field, but we must not share the cancel button style.
+ cancelBlockStyle = new (renderArena()) RenderStyle(*pseudoStyle);
+ else
+ cancelBlockStyle = new (renderArena()) RenderStyle();
+
+ if (startStyle)
+ cancelBlockStyle->inheritFrom(startStyle);
+
+ cancelBlockStyle->setDisplay(INLINE_BLOCK);
+
+ updateCancelButtonVisibility(cancelBlockStyle);
+
+ return cancelBlockStyle;
+}
+
+void RenderTextControl::updatePlaceholder()
+{
+ bool oldPlaceholderVisible = m_placeholderVisible;
+
+ String placeholder;
+ if (!m_multiLine) {
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
+ if (input->value().isEmpty() && document()->focusedNode() != node())
+ placeholder = input->getAttribute(placeholderAttr);
+ }
+
+ if (!placeholder.isEmpty() || m_placeholderVisible) {
+ ExceptionCode ec = 0;
+ m_innerText->setInnerText(placeholder, ec);
+ m_placeholderVisible = !placeholder.isEmpty();
+ }
+
+ Color color;
+ if (!placeholder.isEmpty())
+ color = Color::darkGray;
+ else if (node()->isEnabled())
+ color = style()->color();
+ else
+ color = disabledTextColor(style()->color(), style()->backgroundColor());
+
+ RenderObject* renderer = m_innerText->renderer();
+ RenderStyle* innerStyle = renderer->style();
+ if (innerStyle->color() != color) {
+ innerStyle->setColor(color);
+ renderer->repaint();
+ }
+
+ // temporary disable textSecurity if placeholder is visible
+ if (style()->textSecurity() != TSNONE && oldPlaceholderVisible != m_placeholderVisible) {
+ RenderStyle* newInnerStyle = new (renderArena()) RenderStyle(*innerStyle);
+ newInnerStyle->setTextSecurity(m_placeholderVisible ? TSNONE : style()->textSecurity());
+ renderer->setStyle(newInnerStyle);
+ for (Node* n = m_innerText->firstChild(); n; n = n->traverseNextNode(m_innerText.get())) {
+ if (n->renderer())
+ n->renderer()->setStyle(newInnerStyle);
+ }
+ }
+}
+
+void RenderTextControl::createSubtreeIfNeeded()
+{
+ // When adding these elements, create the renderer & style first before adding to the DOM.
+ // Otherwise, the render tree will create some anonymous blocks that will mess up our layout.
+ bool isSearchField = !m_multiLine && static_cast<HTMLInputElement*>(node())->isSearchField();
+ if (isSearchField && !m_innerBlock) {
+ // Create the inner block element and give it a parent, renderer, and style
+ m_innerBlock = new HTMLTextFieldInnerElement(document(), node());
+ RenderBlock* innerBlockRenderer = new (renderArena()) RenderBlock(m_innerBlock.get());
+ m_innerBlock->setRenderer(innerBlockRenderer);
+ m_innerBlock->setAttached();
+ m_innerBlock->setInDocument(true);
+ innerBlockRenderer->setStyle(createInnerBlockStyle(style()));
+
+ // Add inner block renderer to Render tree
+ RenderBlock::addChild(innerBlockRenderer);
+ }
+ if (isSearchField && !m_resultsButton) {
+ // Create the results block element and give it a parent, renderer, and style
+ m_resultsButton = new HTMLSearchFieldResultsButtonElement(document());
+ RenderBlock* resultsBlockRenderer = new (renderArena()) RenderBlock(m_resultsButton.get());
+ m_resultsButton->setRenderer(resultsBlockRenderer);
+ m_resultsButton->setAttached();
+ m_resultsButton->setInDocument(true);
+
+ RenderStyle* resultsBlockStyle = createResultsButtonStyle(m_innerBlock->renderer()->style());
+ resultsBlockRenderer->setStyle(resultsBlockStyle);
+
+ // Add results renderer to DOM & Render tree
+ m_innerBlock->renderer()->addChild(resultsBlockRenderer);
+ ExceptionCode ec = 0;
+ m_innerBlock->appendChild(m_resultsButton, ec);
+ }
+ if (!m_innerText) {
+ // Create the text block element and give it a parent, renderer, and style
+ // For non-search fields, there is no intermediate m_innerBlock as the shadow node.
+ // m_innerText will be the shadow node in that case.
+ m_innerText = new HTMLTextFieldInnerTextElement(document(), m_innerBlock ? 0 : node());
+ RenderTextControlInnerBlock* textBlockRenderer = new (renderArena()) RenderTextControlInnerBlock(m_innerText.get());
+ m_innerText->setRenderer(textBlockRenderer);
+ m_innerText->setAttached();
+ m_innerText->setInDocument(true);
+
+ RenderStyle* parentStyle = style();
+ if (m_innerBlock)
+ parentStyle = m_innerBlock->renderer()->style();
+ RenderStyle* textBlockStyle = createInnerTextStyle(parentStyle);
+ textBlockRenderer->setStyle(textBlockStyle);
+
+ // Add text block renderer to Render tree
+ if (m_innerBlock) {
+ m_innerBlock->renderer()->addChild(textBlockRenderer);
+ ExceptionCode ec = 0;
+ // Add text block to the DOM
+ m_innerBlock->appendChild(m_innerText, ec);
+ } else
+ RenderBlock::addChild(textBlockRenderer);
+ }
+ if (isSearchField && !m_cancelButton) {
+ // Create the close block element and give it a parent, renderer, and style
+ m_cancelButton = new HTMLSearchFieldCancelButtonElement(document());
+ RenderBlock* closeBlockRenderer = new (renderArena()) RenderBlock(m_cancelButton.get());
+ m_cancelButton->setRenderer(closeBlockRenderer);
+ m_cancelButton->setAttached();
+ m_cancelButton->setInDocument(true);
+
+ RenderStyle* closeBlockStyle = createCancelButtonStyle(m_innerBlock->renderer()->style());
+ closeBlockRenderer->setStyle(closeBlockStyle);
+
+ // Add close block renderer to DOM & Render tree
+ m_innerBlock->renderer()->addChild(closeBlockRenderer);
+ ExceptionCode ec = 0;
+ m_innerBlock->appendChild(m_cancelButton, ec);
+ }
+}
+
+void RenderTextControl::updateFromElement()
+{
+ HTMLGenericFormElement* element = static_cast<HTMLGenericFormElement*>(node());
+
+ createSubtreeIfNeeded();
+
+ if (m_cancelButton)
+ updateCancelButtonVisibility(m_cancelButton->renderer()->style());
+
+ updatePlaceholder();
+
+ m_innerText->renderer()->style()->setUserModify(element->isReadOnlyControl() || element->disabled() ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY);
+
+ if ((!element->valueMatchesRenderer() || m_multiLine) && !m_placeholderVisible) {
+ String value;
+ if (m_multiLine)
+ value = static_cast<HTMLTextAreaElement*>(element)->value();
+ else
+ value = static_cast<HTMLInputElement*>(element)->value();
+ if (value.isNull())
+ value = "";
+ else
+ value = value.replace('\\', backslashAsCurrencySymbol());
+ if (value != text() || !m_innerText->hasChildNodes()) {
+ ExceptionCode ec = 0;
+#if PLATFORM(SYMBIAN)
+ if (value.length() > 8192)
+ value.truncate(8192);
+#endif
+ m_innerText->setInnerText(value, ec);
+ if (value.endsWith("\n") || value.endsWith("\r"))
+ m_innerText->appendChild(new HTMLBRElement(document()), ec);
+ if (Frame* frame = document()->frame())
+ frame->editor()->clearUndoRedoOperations();
+ m_dirty = false;
+ m_userEdited = false;
+ }
+ element->setValueMatchesRenderer();
+ }
+
+ if (m_searchPopupIsVisible)
+ m_searchPopup->updateFromElement();
+}
+
+int RenderTextControl::selectionStart()
+{
+ Frame* frame = document()->frame();
+ if (!frame)
+ return 0;
+ return indexForVisiblePosition(frame->selectionController()->start());
+}
+
+int RenderTextControl::selectionEnd()
+{
+ Frame* frame = document()->frame();
+ if (!frame)
+ return 0;
+ return indexForVisiblePosition(frame->selectionController()->end());
+}
+
+void RenderTextControl::setSelectionStart(int start)
+{
+ setSelectionRange(start, max(start, selectionEnd()));
+}
+
+void RenderTextControl::setSelectionEnd(int end)
+{
+ setSelectionRange(min(end, selectionStart()), end);
+}
+
+void RenderTextControl::select()
+{
+ setSelectionRange(0, text().length());
+}
+
+void RenderTextControl::setSelectionRange(int start, int end)
+{
+ end = max(end, 0);
+ start = min(max(start, 0), end);
+
+ document()->updateLayout();
+
+ if (style()->visibility() == HIDDEN || !m_innerText || !m_innerText->renderer() || !m_innerText->renderer()->height()) {
+ if (m_multiLine)
+ static_cast<HTMLTextAreaElement*>(node())->cacheSelection(start, end);
+ else
+ static_cast<HTMLInputElement*>(node())->cacheSelection(start, end);
+ return;
+ }
+ VisiblePosition startPosition = visiblePositionForIndex(start);
+ VisiblePosition endPosition;
+ if (start == end)
+ endPosition = startPosition;
+ else
+ endPosition = visiblePositionForIndex(end);
+
+ ASSERT(startPosition.isNotNull() && endPosition.isNotNull());
+ ASSERT(startPosition.deepEquivalent().node()->shadowAncestorNode() == node() && endPosition.deepEquivalent().node()->shadowAncestorNode() == node());
+
+ Selection newSelection = Selection(startPosition, endPosition);
+
+ if (Frame* frame = document()->frame())
+ frame->selectionController()->setSelection(newSelection);
+
+ // FIXME: Granularity is stored separately on the frame, but also in the selection controller.
+ // The granularity in the selection controller should be used, and then this line of code would not be needed.
+ if (Frame* frame = document()->frame())
+ frame->setSelectionGranularity(CharacterGranularity);
+}
+
+Selection RenderTextControl::selection(int start, int end) const
+{
+ return Selection(VisiblePosition(m_innerText.get(), start, VP_DEFAULT_AFFINITY),
+ VisiblePosition(m_innerText.get(), end, VP_DEFAULT_AFFINITY));
+}
+
+VisiblePosition RenderTextControl::visiblePositionForIndex(int index)
+{
+ if (index <= 0)
+ return VisiblePosition(m_innerText.get(), 0, DOWNSTREAM);
+ ExceptionCode ec = 0;
+ RefPtr<Range> range = new Range(document());
+ range->selectNodeContents(m_innerText.get(), ec);
+ CharacterIterator it(range.get());
+ it.advance(index - 1);
+ return VisiblePosition(it.range()->endContainer(ec), it.range()->endOffset(ec), UPSTREAM);
+}
+
+int RenderTextControl::indexForVisiblePosition(const VisiblePosition& pos)
+{
+ Position indexPosition = pos.deepEquivalent();
+ if (!indexPosition.node() || indexPosition.node()->rootEditableElement() != m_innerText)
+ return 0;
+ ExceptionCode ec = 0;
+ RefPtr<Range> range = new Range(document());
+ range->setStart(m_innerText.get(), 0, ec);
+ range->setEnd(indexPosition.node(), indexPosition.offset(), ec);
+ return TextIterator::rangeLength(range.get());
+}
+
+void RenderTextControl::updateCancelButtonVisibility(RenderStyle* style)
+{
+ ASSERT(!m_multiLine);
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
+ if (input->value().isEmpty())
+ style->setVisibility(HIDDEN);
+ else
+ style->setVisibility(VISIBLE);
+}
+
+void RenderTextControl::subtreeHasChanged()
+{
+ bool wasDirty = m_dirty;
+ m_dirty = true;
+ m_userEdited = true;
+ HTMLGenericFormElement* element = static_cast<HTMLGenericFormElement*>(node());
+ if (m_multiLine) {
+ element->setValueMatchesRenderer(false);
+ if (element->focused())
+ if (Frame* frame = document()->frame())
+ frame->textDidChangeInTextArea(element);
+ } else {
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(element);
+ input->setValueFromRenderer(input->constrainValue(text()));
+ if (m_cancelButton)
+ updateCancelButtonVisibility(m_cancelButton->renderer()->style());
+
+ // If the incremental attribute is set, then dispatch the search event
+ if (!input->getAttribute(incrementalAttr).isNull())
+ startSearchEventTimer();
+
+ if (!wasDirty) {
+ if (input->focused())
+ if (Frame* frame = document()->frame())
+ frame->textFieldDidBeginEditing(input);
+ }
+ if (input->focused())
+ if (Frame* frame = document()->frame())
+ frame->textDidChangeInTextField(input);
+ }
+}
+
+String RenderTextControl::finishText(Vector<UChar>& result) const
+{
+ UChar symbol = backslashAsCurrencySymbol();
+ if (symbol != '\\') {
+ size_t size = result.size();
+ for (size_t i = 0; i < size; ++i)
+ if (result[i] == '\\')
+ result[i] = symbol;
+ }
+
+ return String::adopt(result);
+}
+
+String RenderTextControl::text()
+{
+ if (!m_innerText)
+ return "";
+
+ Frame* frame = document()->frame();
+ Text* compositionNode = frame ? frame->editor()->compositionNode() : 0;
+
+ Vector<UChar> result;
+
+ for (Node* n = m_innerText.get(); n; n = n->traverseNextNode(m_innerText.get())) {
+ if (n->isTextNode()) {
+ Text* text = static_cast<Text*>(n);
+ String data = text->data();
+ unsigned length = data.length();
+ if (text != compositionNode)
+ result.append(data.characters(), length);
+ else {
+ unsigned compositionStart = min(frame->editor()->compositionStart(), length);
+ unsigned compositionEnd = min(max(compositionStart, frame->editor()->compositionEnd()), length);
+ result.append(data.characters(), compositionStart);
+ result.append(data.characters() + compositionEnd, length - compositionEnd);
+ }
+ }
+ }
+
+ 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;
+}
+
+String RenderTextControl::textWithHardLineBreaks()
+{
+ if (!m_innerText)
+ return "";
+ Node* firstChild = m_innerText->firstChild();
+ if (!firstChild)
+ return "";
+
+ document()->updateLayout();
+
+ RenderObject* renderer = firstChild->renderer();
+ if (!renderer)
+ return "";
+
+ InlineBox* box = renderer->inlineBox(0, DOWNSTREAM);
+ if (!box)
+ return "";
+
+ Frame* frame = document()->frame();
+ Text* compositionNode = frame ? frame->editor()->compositionNode() : 0;
+
+ Node* breakNode;
+ unsigned breakOffset;
+ RootInlineBox* line = box->root();
+ getNextSoftBreak(line, breakNode, breakOffset);
+
+ Vector<UChar> result;
+
+ for (Node* n = firstChild; n; n = n->traverseNextNode(m_innerText.get())) {
+ if (n->hasTagName(brTag))
+ result.append(&newlineCharacter, 1);
+ else if (n->isTextNode()) {
+ Text* text = static_cast<Text*>(n);
+ String data = text->data();
+ unsigned length = data.length();
+ unsigned compositionStart = (text == compositionNode)
+ ? min(frame->editor()->compositionStart(), length) : 0;
+ unsigned compositionEnd = (text == compositionNode)
+ ? min(max(compositionStart, frame->editor()->compositionEnd()), length) : 0;
+ unsigned position = 0;
+ while (breakNode == n && breakOffset < compositionStart) {
+ result.append(data.characters() + position, breakOffset - position);
+ position = breakOffset;
+ result.append(&newlineCharacter, 1);
+ getNextSoftBreak(line, breakNode, breakOffset);
+ }
+ result.append(data.characters() + position, compositionStart - position);
+ position = compositionEnd;
+ while (breakNode == n && breakOffset <= length) {
+ if (breakOffset > position) {
+ result.append(data.characters() + position, breakOffset - position);
+ position = breakOffset;
+ result.append(&newlineCharacter, 1);
+ }
+ getNextSoftBreak(line, breakNode, breakOffset);
+ }
+ result.append(data.characters() + position, length - position);
+ }
+ while (breakNode == n)
+ getNextSoftBreak(line, breakNode, breakOffset);
+ }
+
+ return finishText(result);
+}
+
+void RenderTextControl::calcHeight()
+{
+ int rows = 1;
+ if (m_multiLine)
+ rows = static_cast<HTMLTextAreaElement*>(node())->rows();
+
+ int line = m_innerText->renderer()->lineHeight(true, true);
+ int toAdd = paddingTop() + paddingBottom() + borderTop() + borderBottom();
+
+ int innerToAdd = m_innerText->renderer()->borderTop() + m_innerText->renderer()->borderBottom() +
+ m_innerText->renderer()->paddingTop() + m_innerText->renderer()->paddingBottom() +
+ m_innerText->renderer()->marginTop() + m_innerText->renderer()->marginBottom();
+
+ if (m_resultsButton) {
+ static_cast<RenderBlock*>(m_resultsButton->renderer())->calcHeight();
+ innerToAdd = max(innerToAdd,
+ m_resultsButton->renderer()->borderTop() + m_resultsButton->renderer()->borderBottom() +
+ m_resultsButton->renderer()->paddingTop() + m_resultsButton->renderer()->paddingBottom() +
+ m_resultsButton->renderer()->marginTop() + m_resultsButton->renderer()->marginBottom());
+ line = max(line, m_resultsButton->renderer()->height());
+ }
+ if (m_cancelButton) {
+ static_cast<RenderBlock*>(m_cancelButton->renderer())->calcHeight();
+ innerToAdd = max(innerToAdd,
+ m_cancelButton->renderer()->borderTop() + m_cancelButton->renderer()->borderBottom() +
+ m_cancelButton->renderer()->paddingTop() + m_cancelButton->renderer()->paddingBottom() +
+ m_cancelButton->renderer()->marginTop() + m_cancelButton->renderer()->marginBottom());
+ line = max(line, m_cancelButton->renderer()->height());
+ }
+ toAdd += innerToAdd;
+
+ // FIXME: We should get the size of the scrollbar from the RenderTheme instead.
+ int scrollbarSize = 0;
+ // We are able to have a horizontal scrollbar if the overflow style is scroll, or if its auto and there's no word wrap.
+ if (m_innerText->renderer()->style()->overflowX() == OSCROLL || (m_innerText->renderer()->style()->overflowX() == OAUTO && m_innerText->renderer()->style()->wordWrap() == NormalWordWrap))
+ scrollbarSize = PlatformScrollbar::horizontalScrollbarHeight();
+
+ m_height = line * rows + toAdd + scrollbarSize;
+
+ RenderBlock::calcHeight();
+}
+
+short RenderTextControl::baselinePosition(bool b, bool isRootLineBox) const
+{
+ if (m_multiLine)
+ return height() + marginTop() + marginBottom();
+ return RenderBlock::baselinePosition(b, isRootLineBox);
+}
+
+bool RenderTextControl::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction)
+{
+ // If we're within the text control, we want to act as if we've hit the inner text block element, in case the point
+ // was on the control but not on the inner element (see Radar 4617841).
+
+ // In a search field, we want to act as if we've hit the results block if we're to the left of the inner text block,
+ // and act as if we've hit the close block if we're to the right of the inner text block.
+
+ if (RenderBlock::nodeAtPoint(request, result, x, y, tx, ty, hitTestAction) &&
+ (result.innerNode() == element() || result.innerNode() == m_innerBlock)) {
+ IntPoint localPoint = IntPoint(x - tx - m_x, y - ty - m_y);
+ if (m_innerBlock) {
+ int textLeft = tx + m_x + m_innerBlock->renderer()->xPos() + m_innerText->renderer()->xPos();
+ int textRight = textLeft + m_innerText->renderer()->width();
+ if (m_resultsButton && x < textLeft) {
+ result.setInnerNode(m_resultsButton.get());
+ result.setLocalPoint(IntPoint(localPoint.x() - m_innerText->renderer()->xPos() - m_innerBlock->renderer()->xPos() - m_resultsButton->renderer()->xPos(),
+ localPoint.y() - m_innerText->renderer()->yPos() - m_innerBlock->renderer()->yPos() - m_resultsButton->renderer()->yPos()));
+ return true;
+ }
+ if (m_cancelButton && x > textRight) {
+ result.setInnerNode(m_cancelButton.get());
+ result.setLocalPoint(IntPoint(localPoint.x() - m_innerText->renderer()->xPos() - m_innerBlock->renderer()->xPos() - m_cancelButton->renderer()->xPos(),
+ localPoint.y() - m_innerText->renderer()->yPos() - m_innerBlock->renderer()->yPos() - m_cancelButton->renderer()->yPos()));
+ return true;
+ }
+ }
+
+ // Hit the inner text block.
+ result.setInnerNode(m_innerText.get());
+ result.setLocalPoint(IntPoint(localPoint.x() - m_innerText->renderer()->xPos() - (m_innerBlock.get() ? m_innerBlock->renderer()->xPos() : 0),
+ localPoint.y() - m_innerText->renderer()->yPos() - (m_innerBlock.get() ? m_innerBlock->renderer()->yPos() : 0)));
+
+ return true;
+ }
+
+ return false;
+}
+
+IntRect RenderTextControl::controlClipRect(int tx, int ty) const
+{
+ IntRect clipRect = contentBox();
+ clipRect.move(tx, ty);
+ return clipRect;
+}
+
+void RenderTextControl::layout()
+{
+ int oldHeight = m_height;
+ calcHeight();
+ bool relayoutChildren = oldHeight != m_height;
+
+ // Set the text block's height
+ int textBlockHeight = m_height - paddingTop() - paddingBottom() - borderTop() - borderBottom();
+ int currentTextBlockHeight = m_innerText->renderer()->height();
+ if (m_multiLine || m_innerBlock || currentTextBlockHeight > m_height) {
+ if (textBlockHeight != currentTextBlockHeight)
+ relayoutChildren = true;
+ m_innerText->renderer()->style()->setHeight(Length(textBlockHeight, Fixed));
+ }
+ if (m_innerBlock) {
+ if (textBlockHeight != m_innerBlock->renderer()->height())
+ relayoutChildren = true;
+ m_innerBlock->renderer()->style()->setHeight(Length(textBlockHeight, Fixed));
+ }
+
+ int oldWidth = m_width;
+ calcWidth();
+ if (oldWidth != m_width)
+ relayoutChildren = true;
+
+ int searchExtrasWidth = 0;
+ if (m_resultsButton) {
+ m_resultsButton->renderer()->calcWidth();
+ searchExtrasWidth += m_resultsButton->renderer()->width();
+ }
+ if (m_cancelButton) {
+ m_cancelButton->renderer()->calcWidth();
+ searchExtrasWidth += m_cancelButton->renderer()->width();
+ }
+
+ // Set the text block's width
+ int textBlockWidth = m_width - paddingLeft() - paddingRight() - borderLeft() - borderRight() -
+ m_innerText->renderer()->paddingLeft() - m_innerText->renderer()->paddingRight() - searchExtrasWidth;
+ if (textBlockWidth != m_innerText->renderer()->width())
+ relayoutChildren = true;
+ m_innerText->renderer()->style()->setWidth(Length(textBlockWidth, Fixed));
+ if (m_innerBlock) {
+ int innerBlockWidth = m_width - paddingLeft() - paddingRight() - borderLeft() - borderRight();
+ if (innerBlockWidth != m_innerBlock->renderer()->width())
+ relayoutChildren = true;
+ m_innerBlock->renderer()->style()->setWidth(Length(innerBlockWidth, Fixed));
+ }
+
+ RenderBlock::layoutBlock(relayoutChildren);
+
+ // For text fields, center the inner text vertically
+ // Don't do this for search fields, since we don't honor height for them
+ if (!m_multiLine) {
+ currentTextBlockHeight = m_innerText->renderer()->height();
+ if (!m_innerBlock && currentTextBlockHeight < m_height)
+ m_innerText->renderer()->setPos(m_innerText->renderer()->xPos(), (m_height - currentTextBlockHeight) / 2);
+ }
+}
+
+void RenderTextControl::calcPrefWidths()
+{
+ ASSERT(prefWidthsDirty());
+
+ m_minPrefWidth = 0;
+ m_maxPrefWidth = 0;
+
+ if (style()->width().isFixed() && style()->width().value() > 0)
+ m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
+ else {
+ // Figure out how big a text control needs to be for a given number of characters
+ // (using "0" as the nominal character).
+ const UChar ch = '0';
+ float charWidth = style()->font().floatWidth(TextRun(&ch, 1), TextStyle(0, 0, 0, false, false, false));
+ int factor;
+ int scrollbarSize = 0;
+ if (m_multiLine) {
+ factor = static_cast<HTMLTextAreaElement*>(node())->cols();
+ // FIXME: We should get the size of the scrollbar from the RenderTheme instead.
+ if (m_innerText->renderer()->style()->overflowY() != OHIDDEN)
+ scrollbarSize = PlatformScrollbar::verticalScrollbarWidth();
+ } else {
+ factor = static_cast<HTMLInputElement*>(node())->size();
+ if (factor <= 0)
+ factor = 20;
+ }
+ m_maxPrefWidth = static_cast<int>(ceilf(charWidth * factor)) + scrollbarSize +
+ m_innerText->renderer()->paddingLeft() + m_innerText->renderer()->paddingRight();
+
+ if (m_resultsButton)
+ m_maxPrefWidth += m_resultsButton->renderer()->borderLeft() + m_resultsButton->renderer()->borderRight() +
+ m_resultsButton->renderer()->paddingLeft() + m_resultsButton->renderer()->paddingRight();
+ if (m_cancelButton)
+ m_maxPrefWidth += m_cancelButton->renderer()->borderLeft() + m_cancelButton->renderer()->borderRight() +
+ m_cancelButton->renderer()->paddingLeft() + m_cancelButton->renderer()->paddingRight();
+ }
+
+ if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
+ m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
+ m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
+ } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
+ m_minPrefWidth = 0;
+ else
+ m_minPrefWidth = m_maxPrefWidth;
+
+ if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
+ m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
+ m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
+ }
+
+ int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
+
+ m_minPrefWidth += toAdd;
+ m_maxPrefWidth += toAdd;
+
+#if PLATFORM(SYMBIAN)
+ // force control to fit the view width
+ RenderView* v = view();
+ if (!style()->width().isFixed() && v && v->frameView() && v->frameView()->frame() && v->frameView()->frame()->bridge()) {
+ int maxControlWidth = v->frameView()->frame()->bridge()->maxBidiWidth();
+ m_minPrefWidth = min(maxControlWidth, m_minPrefWidth);
+ m_maxPrefWidth = min(maxControlWidth, m_maxPrefWidth);
+ }
+#endif
+ setPrefWidthsDirty(false);
+}
+
+void RenderTextControl::forwardEvent(Event* evt)
+{
+ if (evt->type() == blurEvent) {
+ RenderObject* innerRenderer = m_innerText->renderer();
+ if (innerRenderer) {
+ RenderLayer* innerLayer = innerRenderer->layer();
+#if PLATFORM(SYMBIAN)
+ if (innerLayer)
+#else
+ if (innerLayer && !m_multiLine)
+#endif
+ innerLayer->scrollToOffset(style()->direction() == RTL ? innerLayer->scrollWidth() : 0, 0);
+ }
+ updatePlaceholder();
+ } else if (evt->type() == focusEvent)
+ updatePlaceholder();
+ else {
+ if (evt->isMouseEvent() && m_resultsButton && static_cast<MouseEvent*>(evt)->x() < m_innerText->renderer()->absoluteBoundingBoxRect().x())
+ m_resultsButton->defaultEventHandler(evt);
+ else if (evt->isMouseEvent() && m_cancelButton && static_cast<MouseEvent*>(evt)->x() > m_innerText->renderer()->absoluteBoundingBoxRect().right())
+ m_cancelButton->defaultEventHandler(evt);
+ else
+ m_innerText->defaultEventHandler(evt);
+ }
+}
+
+void RenderTextControl::selectionChanged(bool userTriggered)
+{
+ HTMLGenericFormElement* element = static_cast<HTMLGenericFormElement*>(node());
+ if (m_multiLine)
+ static_cast<HTMLTextAreaElement*>(element)->cacheSelection(selectionStart(), selectionEnd());
+ else
+ static_cast<HTMLInputElement*>(element)->cacheSelection(selectionStart(), selectionEnd());
+ if (Frame* frame = document()->frame())
+ if (frame->selectionController()->isRange() && userTriggered)
+ element->dispatchHTMLEvent(selectEvent, true, false);
+}
+
+void RenderTextControl::autoscroll()
+{
+ RenderLayer* layer = m_innerText->renderer()->layer();
+ if (layer)
+ layer->autoscroll();
+}
+
+int RenderTextControl::scrollWidth() const
+{
+ if (m_innerText)
+ return m_innerText->scrollWidth();
+ return RenderBlock::scrollWidth();
+}
+
+int RenderTextControl::scrollHeight() const
+{
+ if (m_innerText)
+ return m_innerText->scrollHeight();
+ return RenderBlock::scrollHeight();
+}
+
+int RenderTextControl::scrollLeft() const
+{
+ if (m_innerText)
+ return m_innerText->scrollLeft();
+ return RenderBlock::scrollLeft();
+}
+
+int RenderTextControl::scrollTop() const
+{
+ if (m_innerText)
+ return m_innerText->scrollTop();
+ return RenderBlock::scrollTop();
+}
+
+void RenderTextControl::setScrollLeft(int newLeft)
+{
+ if (m_innerText)
+ m_innerText->setScrollLeft(newLeft);
+}
+
+void RenderTextControl::setScrollTop(int newTop)
+{
+ if (m_innerText)
+ m_innerText->setScrollTop(newTop);
+}
+
+const AtomicString& RenderTextControl::autosaveName() const
+{
+ return static_cast<Element*>(node())->getAttribute(autosaveAttr);
+}
+
+void RenderTextControl::addSearchResult()
+{
+ ASSERT(!m_multiLine);
+
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
+ if (input->maxResults() <= 0)
+ return;
+
+ String value = input->value();
+ if (value.isEmpty())
+ return;
+
+ Settings* settings = document()->settings();
+ if (!settings || settings->privateBrowsingEnabled())
+ return;
+
+ int size = static_cast<int>(m_recentSearches.size());
+ for (int i = size - 1; i >= 0; --i)
+ if (m_recentSearches[i] == value)
+ m_recentSearches.remove(i);
+
+ m_recentSearches.insert(0, value);
+ while (static_cast<int>(m_recentSearches.size()) > input->maxResults())
+ m_recentSearches.removeLast();
+
+ const AtomicString& name = autosaveName();
+ if (!m_searchPopup)
+ m_searchPopup = SearchPopupMenu::create(this);
+ m_searchPopup->saveRecentSearches(name, m_recentSearches);
+}
+
+void RenderTextControl::showPopup()
+{
+ if (m_searchPopupIsVisible)
+ return;
+
+ if (!m_searchPopup)
+ m_searchPopup = SearchPopupMenu::create(this);
+
+ if (!m_searchPopup->enabled())
+ return;
+
+ m_searchPopupIsVisible = true;
+
+ const AtomicString& name = autosaveName();
+ m_searchPopup->loadRecentSearches(name, m_recentSearches);
+
+ // Trim the recent searches list if the maximum size has changed since we last saved.
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
+ if (static_cast<int>(m_recentSearches.size()) > input->maxResults()) {
+ do
+ m_recentSearches.removeLast();
+ while (static_cast<int>(m_recentSearches.size()) > input->maxResults());
+ m_searchPopup->saveRecentSearches(name, m_recentSearches);
+ }
+
+ m_searchPopup->show(absoluteBoundingBoxRect(), document()->view(), -1);
+}
+
+void RenderTextControl::hidePopup()
+{
+ if (m_searchPopup)
+ m_searchPopup->hide();
+ m_searchPopupIsVisible = false;
+}
+
+void RenderTextControl::valueChanged(unsigned listIndex, bool fireEvents)
+{
+ ASSERT(static_cast<int>(listIndex) < listSize());
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
+ if (static_cast<int>(listIndex) == (listSize() - 1)) {
+ if (fireEvents) {
+ m_recentSearches.clear();
+ const AtomicString& name = autosaveName();
+ if (!name.isEmpty()) {
+ if (!m_searchPopup)
+ m_searchPopup = SearchPopupMenu::create(this);
+ m_searchPopup->saveRecentSearches(name, m_recentSearches);
+ }
+ }
+ } else {
+ input->setValue(itemText(listIndex));
+ if (fireEvents)
+ input->onSearch();
+ input->select();
+ }
+}
+
+String RenderTextControl::itemText(unsigned listIndex) const
+{
+ int size = listSize();
+ if (size == 1) {
+ ASSERT(!listIndex);
+ return searchMenuNoRecentSearchesText();
+ }
+ if (!listIndex)
+ return searchMenuRecentSearchesText();
+ if (itemIsSeparator(listIndex))
+ return String();
+ if (static_cast<int>(listIndex) == (size - 1))
+ return searchMenuClearRecentSearchesText();
+ return m_recentSearches[listIndex - 1];
+}
+
+bool RenderTextControl::itemIsEnabled(unsigned listIndex) const
+{
+ if (!listIndex || itemIsSeparator(listIndex))
+ return false;
+ return true;
+}
+
+RenderStyle* RenderTextControl::itemStyle(unsigned listIndex) const
+{
+ return style();
+}
+
+Color RenderTextControl::itemBackgroundColor(unsigned listIndex) const
+{
+ return style()->backgroundColor();
+}
+
+RenderStyle* RenderTextControl::clientStyle() const
+{
+ return style();
+}
+
+Document* RenderTextControl::clientDocument() const
+{
+ return document();
+}
+
+int RenderTextControl::clientPaddingLeft() const
+{
+ return paddingLeft() + m_resultsButton->renderer()->width();
+}
+
+int RenderTextControl::clientPaddingRight() const
+{
+ return paddingRight() + m_cancelButton->renderer()->width();
+}
+
+int RenderTextControl::listSize() const
+{
+ // If there are no recent searches, then our menu will have 1 "No recent searches" item.
+ if (!m_recentSearches.size())
+ return 1;
+ // Otherwise, leave room in the menu for a header, a separator, and the "Clear recent searches" item.
+ return m_recentSearches.size() + 3;
+}
+
+int RenderTextControl::selectedIndex() const
+{
+ return -1;
+}
+
+bool RenderTextControl::itemIsSeparator(unsigned listIndex) const
+{
+ // The separator will be the second to last item in our list.
+ return static_cast<int>(listIndex) == (listSize() - 2);
+}
+
+bool RenderTextControl::itemIsLabel(unsigned listIndex) const
+{
+ return listIndex == 0;
+}
+
+bool RenderTextControl::itemIsSelected(unsigned listIndex) const
+{
+ return false;
+}
+
+void RenderTextControl::setTextFromItem(unsigned listIndex)
+{
+ static_cast<HTMLInputElement*>(node())->setValue(itemText(listIndex));
+}
+
+bool RenderTextControl::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
+{
+ RenderLayer* layer = m_innerText->renderer()->layer();
+ if (layer && layer->scroll(direction, granularity, multiplier))
+ return true;
+ return RenderObject::scroll(direction, granularity, multiplier);
+}
+
+void RenderTextControl::searchEventTimerFired(Timer<RenderTextControl>*)
+{
+ static_cast<HTMLInputElement*>(node())->onSearch();
+}
+
+void RenderTextControl::stopSearchEventTimer()
+{
+ m_searchEventTimer.stop();
+}
+
+void RenderTextControl::startSearchEventTimer()
+{
+ unsigned length = text().length();
+
+ // If there's no text, fire the event right away.
+ if (!length) {
+ m_searchEventTimer.stop();
+ static_cast<HTMLInputElement*>(node())->onSearch();
+ return;
+ }
+
+ // After typing the first key, we wait 0.5 seconds.
+ // After the second key, 0.4 seconds, then 0.3, then 0.2 from then on.
+ m_searchEventTimer.startOneShot(max(0.2, 0.6 - 0.1 * length));
+}
+
+bool RenderTextControl::isScrollable() const
+{
+ if (m_innerText && m_innerText->renderer()->isScrollable())
+ return true;
+ return RenderObject::isScrollable();
+}
+
+} // namespace WebCore