diff options
Diffstat (limited to 'Source/WebCore/rendering/RenderTextControl.cpp')
-rw-r--r-- | Source/WebCore/rendering/RenderTextControl.cpp | 629 |
1 files changed, 629 insertions, 0 deletions
diff --git a/Source/WebCore/rendering/RenderTextControl.cpp b/Source/WebCore/rendering/RenderTextControl.cpp new file mode 100644 index 0000000..0192ee4 --- /dev/null +++ b/Source/WebCore/rendering/RenderTextControl.cpp @@ -0,0 +1,629 @@ +/** + * 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 "AXObjectCache.h" +#include "CharacterNames.h" +#include "Editor.h" +#include "Event.h" +#include "EventNames.h" +#include "Frame.h" +#include "HTMLBRElement.h" +#include "HTMLFormControlElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "HitTestResult.h" +#include "RenderLayer.h" +#include "RenderText.h" +#include "ScrollbarTheme.h" +#include "SelectionController.h" +#include "Text.h" +#include "TextControlInnerElements.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, bool placeholderVisible) + : RenderBlock(node) + , m_placeholderVisible(placeholderVisible) + , m_wasChangedSinceLastChangeEvent(false) + , m_lastChangeWasUserEdit(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()); + setInnerTextStyle(textBlockStyle); + } + + setReplaced(isInline()); +} + +void RenderTextControl::setInnerTextStyle(PassRefPtr<RenderStyle> style) +{ + if (m_innerText) { + RefPtr<RenderStyle> textStyle = style; + m_innerText->renderer()->setStyle(textStyle); + for (Node* n = m_innerText->firstChild(); n; n = n->traverseNextNode(m_innerText.get())) { + if (n->renderer()) + n->renderer()->setStyle(textStyle); + } + } +} + +static inline bool updateUserModifyProperty(Node* node, RenderStyle* style) +{ + bool isEnabled = true; + bool isReadOnlyControl = false; + + if (node->isElementNode()) { + Element* element = static_cast<Element*>(node); + isEnabled = element->isEnabledFormControl(); + isReadOnlyControl = element->isReadOnlyFormControl(); + } + + 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->visitedDependentColor(CSSPropertyColor), startStyle->visitedDependentColor(CSSPropertyBackgroundColor))); +} + +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 = TextControlInnerTextElement::create(document(), innerBlock ? 0 : static_cast<HTMLElement*>(node())); + m_innerText->attachInnerElement(innerBlock ? innerBlock : node(), createInnerTextStyle(parentStyle), renderArena()); + } +} + +int RenderTextControl::textBlockHeight() const +{ + return height() - borderAndPaddingHeight(); +} + +int RenderTextControl::textBlockWidth() const +{ + return width() - borderAndPaddingWidth() - 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 = innerTextValue; + if (value != text() || !m_innerText->hasChildNodes()) { + if (value != text()) { + if (Frame* frame = this->frame()) { + frame->editor()->clearUndoRedoOperations(); + + if (AXObjectCache::accessibilityEnabled()) + document()->axObjectCache()->postNotification(this, AXObjectCache::AXValueChanged, false); + } + } + + ExceptionCode ec = 0; + m_innerText->setInnerText(value, ec); + ASSERT(!ec); + + if (value.endsWith("\n") || value.endsWith("\r")) { + m_innerText->appendChild(HTMLBRElement::create(document()), ec); + ASSERT(!ec); + } + + // We set m_lastChangeWasUserEdit to false since this change was not explicitly made by the user (say, via typing on the keyboard), see <rdar://problem/5359921>. + m_lastChangeWasUserEdit = false; + } + + static_cast<Element*>(node())->setFormControlValueMatchesRenderer(true); +} + +void RenderTextControl::setLastChangeWasUserEdit(bool lastChangeWasUserEdit) +{ + m_lastChangeWasUserEdit = lastChangeWasUserEdit; + document()->setIgnoreAutofocus(lastChangeWasUserEdit); +} + +int RenderTextControl::selectionStart() const +{ + Frame* frame = this->frame(); + if (!frame) + return 0; + return indexForVisiblePosition(frame->selection()->start()); +} + +int RenderTextControl::selectionEnd() const +{ + Frame* frame = this->frame(); + if (!frame) + return 0; + return indexForVisiblePosition(frame->selection()->end()); +} + +bool RenderTextControl::hasVisibleTextArea() const +{ + return style()->visibility() == HIDDEN || !m_innerText || !m_innerText->renderer() || !m_innerText->renderBox()->height(); +} + +void setSelectionRange(Node* node, int start, int end) +{ + ASSERT(node); + node->document()->updateLayoutIgnorePendingStylesheets(); + + if (!node->renderer() || !node->renderer()->isTextControl()) + return; + + end = max(end, 0); + start = min(max(start, 0), end); + + RenderTextControl* control = toRenderTextControl(node->renderer()); + + if (control->hasVisibleTextArea()) { + control->cacheSelection(start, end); + 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().node()->shadowAncestorNode() == node && endPosition.deepEquivalent().node()->shadowAncestorNode() == node); + } + VisibleSelection newSelection = VisibleSelection(startPosition, endPosition); + + if (Frame* frame = node->document()->frame()) + frame->selection()->setSelection(newSelection); +} + +bool RenderTextControl::isSelectableElement(Node* node) const +{ + if (!node || !m_innerText) + return false; + + if (node->rootEditableElement() == m_innerText) + return true; + + if (!m_innerText->contains(node)) + return false; + + Node* shadowAncestor = node->shadowAncestorNode(); + return shadowAncestor && (shadowAncestor->hasTagName(textareaTag) + || (shadowAncestor->hasTagName(inputTag) && static_cast<HTMLInputElement*>(shadowAncestor)->isTextField())); +} + +PassRefPtr<Range> RenderTextControl::selection(int start, int end) const +{ + if (!m_innerText) + return 0; + + return Range::create(document(), m_innerText, start, m_innerText, end); +} + +VisiblePosition RenderTextControl::visiblePositionForIndex(int index) const +{ + 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) const +{ + Position indexPosition = pos.deepEquivalent(); + if (!isSelectableElement(indexPosition.node())) + 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.deprecatedEditingOffset(), ec); + ASSERT(!ec); + return TextIterator::rangeLength(range.get()); +} + +void RenderTextControl::subtreeHasChanged() +{ + m_wasChangedSinceLastChangeEvent = true; + m_lastChangeWasUserEdit = 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); + + return String::adopt(result); +} + +String RenderTextControl::text() +{ + if (!m_innerText) + return ""; + + 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()) { + String data = static_cast<Text*>(n)->data(); + result.append(data.characters(), data.length()); + } + } + + 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 RenderTextControl::textWithHardLineBreaks() +{ + if (!m_innerText) + return ""; + + RenderBlock* renderer = toRenderBlock(m_innerText->renderer()); + if (!renderer) + return ""; + + Node* breakNode; + unsigned breakOffset; + RootInlineBox* line = renderer->firstRootBox(); + if (!line) + return ""; + + getNextSoftBreak(line, breakNode, breakOffset); + + Vector<UChar> result; + + for (Node* n = m_innerText->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 position = 0; + 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::computeLogicalHeight() +{ + 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->renderBox()->lineHeight(true, HorizontalLine, PositionOfInteriorLineBoxes)); + setHeight(height() + borderAndPaddingHeight()); + + // 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::computeLogicalHeight(); +} + +void RenderTextControl::hitInnerTextElement(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); +} + +static const char* fontFamiliesWithInvalidCharWidth[] = { + "American Typewriter", + "Arial Hebrew", + "Chalkboard", + "Cochin", + "Corsiva Hebrew", + "Courier", + "Euphemia UCAS", + "Geneva", + "Gill Sans", + "Hei", + "Helvetica", + "Hoefler Text", + "InaiMathi", + "Kai", + "Lucida Grande", + "Marker Felt", + "Monaco", + "Mshtakan", + "New Peninim MT", + "Osaka", + "Raanana", + "STHeiti", + "Symbol", + "Times", + "Apple Braille", + "Apple LiGothic", + "Apple LiSung", + "Apple Symbols", + "AppleGothic", + "AppleMyungjo", + "#GungSeo", + "#HeadLineA", + "#PCMyungjo", + "#PilGi", +}; + +// For font families where any of the fonts don't have a valid entry in the OS/2 table +// for avgCharWidth, fallback to the legacy webkit behavior of getting the avgCharWidth +// from the width of a '0'. This only seems to apply to a fixed number of Mac fonts, +// but, in order to get similar rendering across platforms, we do this check for +// all platforms. +bool RenderTextControl::hasValidAvgCharWidth(AtomicString family) +{ + static HashSet<AtomicString>* fontFamiliesWithInvalidCharWidthMap = 0; + + if (!fontFamiliesWithInvalidCharWidthMap) { + fontFamiliesWithInvalidCharWidthMap = new HashSet<AtomicString>; + + for (size_t i = 0; i < WTF_ARRAY_LENGTH(fontFamiliesWithInvalidCharWidth); ++i) + fontFamiliesWithInvalidCharWidthMap->add(AtomicString(fontFamiliesWithInvalidCharWidth[i])); + } + + return !fontFamiliesWithInvalidCharWidthMap->contains(family); +} + +float RenderTextControl::getAvgCharWidth(AtomicString family) +{ + if (hasValidAvgCharWidth(family)) + return roundf(style()->font().primaryFont()->avgCharWidth()); + + const UChar ch = '0'; + return style()->font().floatWidth(TextRun(&ch, 1, false, 0, 0, false, false, false)); +} + +float RenderTextControl::scaleEmToUnits(int x) const +{ + // This matches the unitsPerEm value for MS Shell Dlg and Courier New from the "head" font table. + float unitsPerEm = 2048.0f; + return roundf(style()->font().size() * x / unitsPerEm); +} + +void RenderTextControl::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + m_minPreferredLogicalWidth = 0; + m_maxPreferredLogicalWidth = 0; + + if (style()->width().isFixed() && style()->width().value() > 0) + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value()); + else { + // Use average character width. Matches IE. + AtomicString family = style()->font().family().family(); + m_maxPreferredLogicalWidth = preferredContentWidth(getAvgCharWidth(family)) + m_innerText->renderBox()->paddingLeft() + m_innerText->renderBox()->paddingRight(); + } + + if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { + m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); + } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) + m_minPreferredLogicalWidth = 0; + else + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; + + if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) { + m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); + } + + int toAdd = borderAndPaddingWidth(); + + m_minPreferredLogicalWidth += toAdd; + m_maxPreferredLogicalWidth += toAdd; + + setPreferredLogicalWidthsDirty(false); +} + +void RenderTextControl::selectionChanged(bool userTriggered) +{ + cacheSelection(selectionStart(), selectionEnd()); + + if (Frame* frame = this->frame()) { + if (frame->selection()->isRange() && userTriggered) + node()->dispatchEvent(Event::create(eventNames().selectEvent, true, false)); + } +} + +void RenderTextControl::addFocusRingRects(Vector<IntRect>& rects, int tx, int ty) +{ + if (width() && height()) + rects.append(IntRect(tx, ty, width(), height())); +} + +HTMLElement* RenderTextControl::innerTextElement() const +{ + return m_innerText.get(); +} + +void RenderTextControl::updatePlaceholderVisibility(bool placeholderShouldBeVisible, bool placeholderValueChanged) +{ + bool oldPlaceholderVisible = m_placeholderVisible; + m_placeholderVisible = placeholderShouldBeVisible; + if (oldPlaceholderVisible != m_placeholderVisible || placeholderValueChanged) + repaint(); +} + +void RenderTextControl::paintPlaceholder(PaintInfo& paintInfo, int tx, int ty) +{ + if (style()->visibility() != VISIBLE) + return; + + IntRect clipRect(tx + borderLeft(), ty + borderTop(), width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop()); + if (clipRect.isEmpty()) + return; + + paintInfo.context->save(); + + paintInfo.context->clip(clipRect); + + RefPtr<RenderStyle> placeholderStyle = getCachedPseudoStyle(INPUT_PLACEHOLDER); + if (!placeholderStyle) + placeholderStyle = style(); + + paintInfo.context->setFillColor(placeholderStyle->visitedDependentColor(CSSPropertyColor), placeholderStyle->colorSpace()); + + String placeholderText = static_cast<HTMLTextFormControlElement*>(node())->strippedPlaceholder(); + TextRun textRun(placeholderText.characters(), placeholderText.length(), 0, 0, 0, !placeholderStyle->isLeftToRightDirection(), placeholderStyle->unicodeBidi() == Override, false, false); + + RenderBox* textRenderer = innerTextElement() ? innerTextElement()->renderBox() : 0; + if (textRenderer) { + IntPoint textPoint; + textPoint.setY(ty + textBlockInsetTop() + placeholderStyle->font().ascent()); + if (placeholderStyle->isLeftToRightDirection()) + textPoint.setX(tx + textBlockInsetLeft()); + else + textPoint.setX(tx + width() - textBlockInsetRight() - style()->font().width(textRun)); + + paintInfo.context->drawBidiText(placeholderStyle->font(), textRun, textPoint); + } + paintInfo.context->restore(); +} + +void RenderTextControl::paintObject(PaintInfo& paintInfo, int tx, int ty) +{ + if (m_placeholderVisible && paintInfo.phase == PaintPhaseForeground) + paintPlaceholder(paintInfo, tx, ty); + + RenderBlock::paintObject(paintInfo, tx, ty); +} + +} // namespace WebCore |