| Index: third_party/WebKit/WebCore/rendering/RenderTextControl.cpp
|
| ===================================================================
|
| --- third_party/WebKit/WebCore/rendering/RenderTextControl.cpp (revision 9310)
|
| +++ third_party/WebKit/WebCore/rendering/RenderTextControl.cpp (working copy)
|
| @@ -1,591 +1,591 @@
|
| -/**
|
| - * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
|
| - * (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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 "RenderTextControl.h"
|
| -
|
| -#include "CharacterNames.h"
|
| -#include "Editor.h"
|
| -#include "Event.h"
|
| -#include "EventNames.h"
|
| -#include "Frame.h"
|
| -#include "HTMLBRElement.h"
|
| -#include "HTMLFormControlElement.h"
|
| -#include "HTMLNames.h"
|
| -#include "HitTestResult.h"
|
| -#include "RenderText.h"
|
| -#include "ScrollbarTheme.h"
|
| -#include "SelectionController.h"
|
| -#include "SimpleFontData.h"
|
| -#include "TextControlInnerElements.h"
|
| -#include "Text.h"
|
| -#include "TextIterator.h"
|
| -
|
| -using namespace std;
|
| -
|
| -namespace WebCore {
|
| -
|
| -using namespace HTMLNames;
|
| -
|
| -// Value chosen by observation. This can be tweaked.
|
| -static const int minColorContrastValue = 1300;
|
| -
|
| -static Color disabledTextColor(const Color& textColor, const Color& backgroundColor)
|
| -{
|
| - // The explicit 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.
|
| - Color disabledColor;
|
| - if (textColor.rgb() == Color::black || differenceSquared(textColor, Color::white) > differenceSquared(backgroundColor, Color::white))
|
| - disabledColor = textColor.light();
|
| - else
|
| - disabledColor = textColor.dark();
|
| -
|
| - // If there's not very much contrast between the disabled color and the background color,
|
| - // just leave the text color alone. We don't want to change a good contrast color scheme so that it has really bad contrast.
|
| - // If the the contrast was already poor, then it doesn't do any good to change it to a different poor contrast color scheme.
|
| - if (differenceSquared(disabledColor, backgroundColor) < minColorContrastValue)
|
| - return textColor;
|
| -
|
| - return disabledColor;
|
| -}
|
| -
|
| -RenderTextControl::RenderTextControl(Node* node)
|
| - : RenderBlock(node)
|
| - , m_edited(false)
|
| - , m_userEdited(false)
|
| -{
|
| -}
|
| -
|
| -RenderTextControl::~RenderTextControl()
|
| -{
|
| - // The children renderers have already been destroyed by destroyLeftoverChildren
|
| - if (m_innerText)
|
| - m_innerText->detach();
|
| -}
|
| -
|
| -void RenderTextControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
|
| -{
|
| - RenderBlock::styleDidChange(diff, oldStyle);
|
| -
|
| - if (m_innerText) {
|
| - RenderBlock* textBlockRenderer = toRenderBlock(m_innerText->renderer());
|
| - RefPtr<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);
|
| - }
|
| - }
|
| -
|
| - setReplaced(isInline());
|
| -}
|
| -
|
| -static inline bool updateUserModifyProperty(Node* node, RenderStyle* style)
|
| -{
|
| - bool isEnabled = true;
|
| - bool isReadOnlyControl = false;
|
| -
|
| - if (node->isElementNode()) {
|
| - FormControlElement* formControlElement = toFormControlElement(static_cast<Element*>(node));
|
| - ASSERT(formControlElement);
|
| -
|
| - isEnabled = formControlElement->isEnabled();
|
| - isReadOnlyControl = formControlElement->isReadOnlyControl();
|
| - }
|
| -
|
| - style->setUserModify((isReadOnlyControl || !isEnabled) ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY);
|
| - return !isEnabled;
|
| -}
|
| -
|
| -void RenderTextControl::adjustInnerTextStyle(const RenderStyle* startStyle, RenderStyle* textBlockStyle) const
|
| -{
|
| - // 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());
|
| -
|
| - bool disabled = updateUserModifyProperty(node(), textBlockStyle);
|
| - if (disabled)
|
| - textBlockStyle->setColor(disabledTextColor(textBlockStyle->color(), startStyle->backgroundColor()));
|
| -}
|
| -
|
| -void RenderTextControl::createSubtreeIfNeeded(TextControlInnerElement* innerBlock)
|
| -{
|
| - if (!m_innerText) {
|
| - // Create the text block element
|
| - // For non-search fields, there is no intermediate innerBlock as the shadow node.
|
| - // m_innerText will be the shadow node in that case.
|
| - RenderStyle* parentStyle = innerBlock ? innerBlock->renderer()->style() : style();
|
| - m_innerText = new TextControlInnerTextElement(document(), innerBlock ? 0 : node());
|
| - m_innerText->attachInnerElement(innerBlock ? innerBlock : node(), createInnerTextStyle(parentStyle), renderArena());
|
| - }
|
| -}
|
| -
|
| -int RenderTextControl::textBlockHeight() const
|
| -{
|
| - return height() - paddingTop() - paddingBottom() - borderTop() - borderBottom();
|
| -}
|
| -
|
| -int RenderTextControl::textBlockWidth() const
|
| -{
|
| - return width() - paddingLeft() - paddingRight() - borderLeft() - borderRight()
|
| - - m_innerText->renderBox()->paddingLeft() - m_innerText->renderBox()->paddingRight();
|
| -}
|
| -
|
| -void RenderTextControl::updateFromElement()
|
| -{
|
| - updateUserModifyProperty(node(), m_innerText->renderer()->style());
|
| -}
|
| -
|
| -void RenderTextControl::setInnerTextValue(const String& innerTextValue)
|
| -{
|
| - String value;
|
| -
|
| - if (innerTextValue.isNull())
|
| - value = "";
|
| - else {
|
| - value = innerTextValue;
|
| - value = document()->displayStringModifiedByEncoding(value);
|
| - }
|
| -
|
| - if (value != text() || !m_innerText->hasChildNodes()) {
|
| - if (value != text()) {
|
| - if (Frame* frame = document()->frame())
|
| - frame->editor()->clearUndoRedoOperations();
|
| - }
|
| -
|
| - ExceptionCode ec = 0;
|
| - m_innerText->setInnerText(value, ec);
|
| - ASSERT(!ec);
|
| -
|
| - if (value.endsWith("\n") || value.endsWith("\r")) {
|
| - m_innerText->appendChild(new HTMLBRElement(brTag, document()), ec);
|
| - ASSERT(!ec);
|
| - }
|
| -
|
| - m_edited = false;
|
| - m_userEdited = false;
|
| - }
|
| -
|
| - formControlElement()->setValueMatchesRenderer();
|
| -}
|
| -
|
| -void RenderTextControl::setUserEdited(bool isUserEdited)
|
| -{
|
| - m_userEdited = isUserEdited;
|
| - document()->setIgnoreAutofocus(isUserEdited);
|
| -}
|
| -
|
| -int RenderTextControl::selectionStart()
|
| -{
|
| - Frame* frame = document()->frame();
|
| - if (!frame)
|
| - return 0;
|
| - return indexForVisiblePosition(frame->selection()->start());
|
| -}
|
| -
|
| -int RenderTextControl::selectionEnd()
|
| -{
|
| - Frame* frame = document()->frame();
|
| - if (!frame)
|
| - return 0;
|
| - return indexForVisiblePosition(frame->selection()->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->renderBox()->height()) {
|
| - 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->selection()->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 = Range::create(document());
|
| - range->selectNodeContents(m_innerText.get(), ec);
|
| - ASSERT(!ec);
|
| - CharacterIterator it(range.get());
|
| - it.advance(index - 1);
|
| - Node* endContainer = it.range()->endContainer(ec);
|
| - ASSERT(!ec);
|
| - int endOffset = it.range()->endOffset(ec);
|
| - ASSERT(!ec);
|
| - return VisiblePosition(endContainer, endOffset, 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 = Range::create(document());
|
| - range->setStart(m_innerText.get(), 0, ec);
|
| - ASSERT(!ec);
|
| - range->setEnd(indexPosition.node(), indexPosition.offset(), ec);
|
| - ASSERT(!ec);
|
| - return TextIterator::rangeLength(range.get());
|
| -}
|
| -
|
| -void RenderTextControl::subtreeHasChanged()
|
| -{
|
| - m_edited = true;
|
| - m_userEdited = true;
|
| -}
|
| -
|
| -String RenderTextControl::finishText(Vector<UChar>& result) const
|
| -{
|
| - // Remove one trailing newline; there's always one that's collapsed out by rendering.
|
| - size_t size = result.size();
|
| - if (size && result[size - 1] == '\n')
|
| - result.shrink(--size);
|
| -
|
| - // Convert backslash to currency symbol.
|
| - document()->displayBufferModifiedByEncoding(result.data(), result.size());
|
| -
|
| - 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->hasTagName(brTag))
|
| - result.append(&newlineCharacter, 1);
|
| - else 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->isText() ? toRenderText(renderer)->firstTextBox() : renderer->inlineBoxWrapper();
|
| - 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);
|
| -}
|
| -
|
| -int RenderTextControl::scrollbarThickness() const
|
| -{
|
| - // FIXME: We should get the size of the scrollbar from the RenderTheme instead.
|
| - return ScrollbarTheme::nativeTheme()->scrollbarThickness();
|
| -}
|
| -
|
| -void RenderTextControl::calcHeight()
|
| -{
|
| - setHeight(m_innerText->renderBox()->borderTop() + m_innerText->renderBox()->borderBottom() +
|
| - m_innerText->renderBox()->paddingTop() + m_innerText->renderBox()->paddingBottom() +
|
| - m_innerText->renderBox()->marginTop() + m_innerText->renderBox()->marginBottom());
|
| -
|
| - adjustControlHeightBasedOnLineHeight(m_innerText->renderer()->lineHeight(true, true));
|
| - setHeight(height() + paddingTop() + paddingBottom() + borderTop() + borderBottom());
|
| -
|
| - // 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 (style()->overflowX() == OSCROLL || (style()->overflowX() == OAUTO && m_innerText->renderer()->style()->wordWrap() == NormalWordWrap))
|
| - setHeight(height() + scrollbarThickness());
|
| -
|
| - RenderBlock::calcHeight();
|
| -}
|
| -
|
| -void RenderTextControl::hitInnerTextBlock(HitTestResult& result, int xPos, int yPos, int tx, int ty)
|
| -{
|
| - result.setInnerNode(m_innerText.get());
|
| - result.setInnerNonSharedNode(m_innerText.get());
|
| - result.setLocalPoint(IntPoint(xPos - tx - x() - m_innerText->renderBox()->x(),
|
| - yPos - ty - y() - m_innerText->renderBox()->y()));
|
| -}
|
| -
|
| -void RenderTextControl::forwardEvent(Event* event)
|
| -{
|
| - if (event->type() == eventNames().blurEvent || event->type() == eventNames().focusEvent)
|
| - return;
|
| - m_innerText->defaultEventHandler(event);
|
| -}
|
| -
|
| -IntRect RenderTextControl::controlClipRect(int tx, int ty) const
|
| -{
|
| - IntRect clipRect = contentBoxRect();
|
| - clipRect.move(tx, ty);
|
| - return clipRect;
|
| -}
|
| -
|
| -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 {
|
| - // Use average character width. Matches IE.
|
| - int charWidth = style()->font().primaryFont()->avgCharWidth();
|
| - m_maxPrefWidth = preferredContentWidth(charWidth) + m_innerText->renderBox()->paddingLeft() + m_innerText->renderBox()->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;
|
| -
|
| - setPrefWidthsDirty(false);
|
| -}
|
| -
|
| -void RenderTextControl::selectionChanged(bool userTriggered)
|
| -{
|
| - cacheSelection(selectionStart(), selectionEnd());
|
| -
|
| - if (Frame* frame = document()->frame()) {
|
| - if (frame->selection()->isRange() && userTriggered)
|
| - static_cast<EventTargetNode*>(node())->dispatchEventForType(eventNames().selectEvent, true, false);
|
| - }
|
| -}
|
| -
|
| -void RenderTextControl::addFocusRingRects(GraphicsContext* graphicsContext, int tx, int ty)
|
| -{
|
| - graphicsContext->addFocusRingRect(IntRect(tx, ty, width(), height()));
|
| -}
|
| -
|
| -void RenderTextControl::autoscroll()
|
| -{
|
| - RenderLayer* layer = m_innerText->renderBox()->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);
|
| -}
|
| -
|
| -bool RenderTextControl::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
|
| -{
|
| - RenderLayer* layer = m_innerText->renderBox()->layer();
|
| - if (layer && layer->scroll(direction, granularity, multiplier))
|
| - return true;
|
| - return RenderBlock::scroll(direction, granularity, multiplier);
|
| -}
|
| -
|
| -HTMLElement* RenderTextControl::innerTextElement() const
|
| -{
|
| - return m_innerText.get();
|
| -}
|
| -
|
| -FormControlElement* RenderTextControl::formControlElement() const
|
| -{
|
| - return toFormControlElement(static_cast<Element*>(node()));
|
| -}
|
| -
|
| -} // namespace WebCore
|
| +/**
|
| + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
|
| + * (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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 "RenderTextControl.h"
|
| +
|
| +#include "CharacterNames.h"
|
| +#include "Editor.h"
|
| +#include "Event.h"
|
| +#include "EventNames.h"
|
| +#include "Frame.h"
|
| +#include "HTMLBRElement.h"
|
| +#include "HTMLFormControlElement.h"
|
| +#include "HTMLNames.h"
|
| +#include "HitTestResult.h"
|
| +#include "RenderText.h"
|
| +#include "ScrollbarTheme.h"
|
| +#include "SelectionController.h"
|
| +#include "SimpleFontData.h"
|
| +#include "TextControlInnerElements.h"
|
| +#include "Text.h"
|
| +#include "TextIterator.h"
|
| +
|
| +using namespace std;
|
| +
|
| +namespace WebCore {
|
| +
|
| +using namespace HTMLNames;
|
| +
|
| +// Value chosen by observation. This can be tweaked.
|
| +static const int minColorContrastValue = 1300;
|
| +
|
| +static Color disabledTextColor(const Color& textColor, const Color& backgroundColor)
|
| +{
|
| + // The explicit 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.
|
| + Color disabledColor;
|
| + if (textColor.rgb() == Color::black || differenceSquared(textColor, Color::white) > differenceSquared(backgroundColor, Color::white))
|
| + disabledColor = textColor.light();
|
| + else
|
| + disabledColor = textColor.dark();
|
| +
|
| + // If there's not very much contrast between the disabled color and the background color,
|
| + // just leave the text color alone. We don't want to change a good contrast color scheme so that it has really bad contrast.
|
| + // If the the contrast was already poor, then it doesn't do any good to change it to a different poor contrast color scheme.
|
| + if (differenceSquared(disabledColor, backgroundColor) < minColorContrastValue)
|
| + return textColor;
|
| +
|
| + return disabledColor;
|
| +}
|
| +
|
| +RenderTextControl::RenderTextControl(Node* node)
|
| + : RenderBlock(node)
|
| + , m_edited(false)
|
| + , m_userEdited(false)
|
| +{
|
| +}
|
| +
|
| +RenderTextControl::~RenderTextControl()
|
| +{
|
| + // The children renderers have already been destroyed by destroyLeftoverChildren
|
| + if (m_innerText)
|
| + m_innerText->detach();
|
| +}
|
| +
|
| +void RenderTextControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
|
| +{
|
| + RenderBlock::styleDidChange(diff, oldStyle);
|
| +
|
| + if (m_innerText) {
|
| + RenderBlock* textBlockRenderer = toRenderBlock(m_innerText->renderer());
|
| + RefPtr<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);
|
| + }
|
| + }
|
| +
|
| + setReplaced(isInline());
|
| +}
|
| +
|
| +static inline bool updateUserModifyProperty(Node* node, RenderStyle* style)
|
| +{
|
| + bool isEnabled = true;
|
| + bool isReadOnlyControl = false;
|
| +
|
| + if (node->isElementNode()) {
|
| + FormControlElement* formControlElement = toFormControlElement(static_cast<Element*>(node));
|
| + ASSERT(formControlElement);
|
| +
|
| + isEnabled = formControlElement->isEnabled();
|
| + isReadOnlyControl = formControlElement->isReadOnlyControl();
|
| + }
|
| +
|
| + style->setUserModify((isReadOnlyControl || !isEnabled) ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY);
|
| + return !isEnabled;
|
| +}
|
| +
|
| +void RenderTextControl::adjustInnerTextStyle(const RenderStyle* startStyle, RenderStyle* textBlockStyle) const
|
| +{
|
| + // 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());
|
| +
|
| + bool disabled = updateUserModifyProperty(node(), textBlockStyle);
|
| + if (disabled)
|
| + textBlockStyle->setColor(disabledTextColor(textBlockStyle->color(), startStyle->backgroundColor()));
|
| +}
|
| +
|
| +void RenderTextControl::createSubtreeIfNeeded(TextControlInnerElement* innerBlock)
|
| +{
|
| + if (!m_innerText) {
|
| + // Create the text block element
|
| + // For non-search fields, there is no intermediate innerBlock as the shadow node.
|
| + // m_innerText will be the shadow node in that case.
|
| + RenderStyle* parentStyle = innerBlock ? innerBlock->renderer()->style() : style();
|
| + m_innerText = new TextControlInnerTextElement(document(), innerBlock ? 0 : node());
|
| + m_innerText->attachInnerElement(innerBlock ? innerBlock : node(), createInnerTextStyle(parentStyle), renderArena());
|
| + }
|
| +}
|
| +
|
| +int RenderTextControl::textBlockHeight() const
|
| +{
|
| + return height() - paddingTop() - paddingBottom() - borderTop() - borderBottom();
|
| +}
|
| +
|
| +int RenderTextControl::textBlockWidth() const
|
| +{
|
| + return width() - paddingLeft() - paddingRight() - borderLeft() - borderRight()
|
| + - m_innerText->renderBox()->paddingLeft() - m_innerText->renderBox()->paddingRight();
|
| +}
|
| +
|
| +void RenderTextControl::updateFromElement()
|
| +{
|
| + updateUserModifyProperty(node(), m_innerText->renderer()->style());
|
| +}
|
| +
|
| +void RenderTextControl::setInnerTextValue(const String& innerTextValue)
|
| +{
|
| + String value;
|
| +
|
| + if (innerTextValue.isNull())
|
| + value = "";
|
| + else {
|
| + value = innerTextValue;
|
| + value = document()->displayStringModifiedByEncoding(value);
|
| + }
|
| +
|
| + if (value != text() || !m_innerText->hasChildNodes()) {
|
| + if (value != text()) {
|
| + if (Frame* frame = document()->frame())
|
| + frame->editor()->clearUndoRedoOperations();
|
| + }
|
| +
|
| + ExceptionCode ec = 0;
|
| + m_innerText->setInnerText(value, ec);
|
| + ASSERT(!ec);
|
| +
|
| + if (value.endsWith("\n") || value.endsWith("\r")) {
|
| + m_innerText->appendChild(new HTMLBRElement(brTag, document()), ec);
|
| + ASSERT(!ec);
|
| + }
|
| +
|
| + m_edited = false;
|
| + m_userEdited = false;
|
| + }
|
| +
|
| + formControlElement()->setValueMatchesRenderer();
|
| +}
|
| +
|
| +void RenderTextControl::setUserEdited(bool isUserEdited)
|
| +{
|
| + m_userEdited = isUserEdited;
|
| + document()->setIgnoreAutofocus(isUserEdited);
|
| +}
|
| +
|
| +int RenderTextControl::selectionStart()
|
| +{
|
| + Frame* frame = document()->frame();
|
| + if (!frame)
|
| + return 0;
|
| + return indexForVisiblePosition(frame->selection()->start());
|
| +}
|
| +
|
| +int RenderTextControl::selectionEnd()
|
| +{
|
| + Frame* frame = document()->frame();
|
| + if (!frame)
|
| + return 0;
|
| + return indexForVisiblePosition(frame->selection()->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->renderBox()->height()) {
|
| + 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->selection()->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 = Range::create(document());
|
| + range->selectNodeContents(m_innerText.get(), ec);
|
| + ASSERT(!ec);
|
| + CharacterIterator it(range.get());
|
| + it.advance(index - 1);
|
| + Node* endContainer = it.range()->endContainer(ec);
|
| + ASSERT(!ec);
|
| + int endOffset = it.range()->endOffset(ec);
|
| + ASSERT(!ec);
|
| + return VisiblePosition(endContainer, endOffset, 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 = Range::create(document());
|
| + range->setStart(m_innerText.get(), 0, ec);
|
| + ASSERT(!ec);
|
| + range->setEnd(indexPosition.node(), indexPosition.offset(), ec);
|
| + ASSERT(!ec);
|
| + return TextIterator::rangeLength(range.get());
|
| +}
|
| +
|
| +void RenderTextControl::subtreeHasChanged()
|
| +{
|
| + m_edited = true;
|
| + m_userEdited = true;
|
| +}
|
| +
|
| +String RenderTextControl::finishText(Vector<UChar>& result) const
|
| +{
|
| + // Remove one trailing newline; there's always one that's collapsed out by rendering.
|
| + size_t size = result.size();
|
| + if (size && result[size - 1] == '\n')
|
| + result.shrink(--size);
|
| +
|
| + // Convert backslash to currency symbol.
|
| + document()->displayBufferModifiedByEncoding(result.data(), result.size());
|
| +
|
| + 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->hasTagName(brTag))
|
| + result.append(&newlineCharacter, 1);
|
| + else 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->isText() ? toRenderText(renderer)->firstTextBox() : renderer->inlineBoxWrapper();
|
| + 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);
|
| +}
|
| +
|
| +int RenderTextControl::scrollbarThickness() const
|
| +{
|
| + // FIXME: We should get the size of the scrollbar from the RenderTheme instead.
|
| + return ScrollbarTheme::nativeTheme()->scrollbarThickness();
|
| +}
|
| +
|
| +void RenderTextControl::calcHeight()
|
| +{
|
| + setHeight(m_innerText->renderBox()->borderTop() + m_innerText->renderBox()->borderBottom() +
|
| + m_innerText->renderBox()->paddingTop() + m_innerText->renderBox()->paddingBottom() +
|
| + m_innerText->renderBox()->marginTop() + m_innerText->renderBox()->marginBottom());
|
| +
|
| + adjustControlHeightBasedOnLineHeight(m_innerText->renderer()->lineHeight(true, true));
|
| + setHeight(height() + paddingTop() + paddingBottom() + borderTop() + borderBottom());
|
| +
|
| + // 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 (style()->overflowX() == OSCROLL || (style()->overflowX() == OAUTO && m_innerText->renderer()->style()->wordWrap() == NormalWordWrap))
|
| + setHeight(height() + scrollbarThickness());
|
| +
|
| + RenderBlock::calcHeight();
|
| +}
|
| +
|
| +void RenderTextControl::hitInnerTextBlock(HitTestResult& result, int xPos, int yPos, int tx, int ty)
|
| +{
|
| + result.setInnerNode(m_innerText.get());
|
| + result.setInnerNonSharedNode(m_innerText.get());
|
| + result.setLocalPoint(IntPoint(xPos - tx - x() - m_innerText->renderBox()->x(),
|
| + yPos - ty - y() - m_innerText->renderBox()->y()));
|
| +}
|
| +
|
| +void RenderTextControl::forwardEvent(Event* event)
|
| +{
|
| + if (event->type() == eventNames().blurEvent || event->type() == eventNames().focusEvent)
|
| + return;
|
| + m_innerText->defaultEventHandler(event);
|
| +}
|
| +
|
| +IntRect RenderTextControl::controlClipRect(int tx, int ty) const
|
| +{
|
| + IntRect clipRect = contentBoxRect();
|
| + clipRect.move(tx, ty);
|
| + return clipRect;
|
| +}
|
| +
|
| +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 {
|
| + // Use average character width. Matches IE.
|
| + int charWidth = style()->font().primaryFont()->avgCharWidth();
|
| + m_maxPrefWidth = preferredContentWidth(charWidth) + m_innerText->renderBox()->paddingLeft() + m_innerText->renderBox()->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;
|
| +
|
| + setPrefWidthsDirty(false);
|
| +}
|
| +
|
| +void RenderTextControl::selectionChanged(bool userTriggered)
|
| +{
|
| + cacheSelection(selectionStart(), selectionEnd());
|
| +
|
| + if (Frame* frame = document()->frame()) {
|
| + if (frame->selection()->isRange() && userTriggered)
|
| + node()->dispatchEventForType(eventNames().selectEvent, true, false);
|
| + }
|
| +}
|
| +
|
| +void RenderTextControl::addFocusRingRects(GraphicsContext* graphicsContext, int tx, int ty)
|
| +{
|
| + graphicsContext->addFocusRingRect(IntRect(tx, ty, width(), height()));
|
| +}
|
| +
|
| +void RenderTextControl::autoscroll()
|
| +{
|
| + RenderLayer* layer = m_innerText->renderBox()->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);
|
| +}
|
| +
|
| +bool RenderTextControl::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
|
| +{
|
| + RenderLayer* layer = m_innerText->renderBox()->layer();
|
| + if (layer && layer->scroll(direction, granularity, multiplier))
|
| + return true;
|
| + return RenderBlock::scroll(direction, granularity, multiplier);
|
| +}
|
| +
|
| +HTMLElement* RenderTextControl::innerTextElement() const
|
| +{
|
| + return m_innerText.get();
|
| +}
|
| +
|
| +FormControlElement* RenderTextControl::formControlElement() const
|
| +{
|
| + return toFormControlElement(static_cast<Element*>(node()));
|
| +}
|
| +
|
| +} // namespace WebCore
|
|
|