blob: f0ea86080a7dd1e9e301bbcf43ea7f5bdaf50658 [file] [log] [blame]
/**
* Copyright (C) 2006, 2007, 2010 Apple Inc. All rights reserved.
* (C) 2008 Torch Mobile Inc. All rights reserved.
* (http://www.torchmobile.com/)
* Copyright (C) 2010 Google Inc. All rights reserved.
* Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
*
* 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 "core/layout/LayoutTextControlSingleLine.h"
#include "core/CSSValueKeywords.h"
#include "core/InputTypeNames.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/editing/FrameSelection.h"
#include "core/frame/LocalFrame.h"
#include "core/html/shadow/ShadowElementNames.h"
#include "core/input/KeyboardEventManager.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutAnalyzer.h"
#include "core/layout/LayoutTheme.h"
#include "core/paint/LayoutObjectDrawingRecorder.h"
#include "core/paint/PaintInfo.h"
#include "core/paint/PaintLayer.h"
#include "core/paint/ThemePainter.h"
#include "platform/fonts/SimpleFontData.h"
namespace blink {
using namespace HTMLNames;
LayoutTextControlSingleLine::LayoutTextControlSingleLine(
HTMLInputElement* element)
: LayoutTextControl(element), m_shouldDrawCapsLockIndicator(false) {}
LayoutTextControlSingleLine::~LayoutTextControlSingleLine() {}
inline Element* LayoutTextControlSingleLine::containerElement() const {
return inputElement()->userAgentShadowRoot()->getElementById(
ShadowElementNames::textFieldContainer());
}
inline Element* LayoutTextControlSingleLine::editingViewPortElement() const {
return inputElement()->userAgentShadowRoot()->getElementById(
ShadowElementNames::editingViewPort());
}
inline HTMLElement* LayoutTextControlSingleLine::innerSpinButtonElement()
const {
return toHTMLElement(inputElement()->userAgentShadowRoot()->getElementById(
ShadowElementNames::spinButton()));
}
void LayoutTextControlSingleLine::paint(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) const {
LayoutTextControl::paint(paintInfo, paintOffset);
if (shouldPaintSelfBlockBackground(paintInfo.phase) &&
m_shouldDrawCapsLockIndicator) {
if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(
paintInfo.context, *this, paintInfo.phase))
return;
LayoutRect contentsRect = contentBoxRect();
// Center in the block progression direction.
if (isHorizontalWritingMode())
contentsRect.setY((size().height() - contentsRect.height()) / 2);
else
contentsRect.setX((size().width() - contentsRect.width()) / 2);
// Convert the rect into the coords used for painting the content
contentsRect.moveBy(paintOffset + location());
IntRect snappedRect = pixelSnappedIntRect(contentsRect);
LayoutObjectDrawingRecorder recorder(paintInfo.context, *this,
paintInfo.phase, snappedRect);
LayoutTheme::theme().painter().paintCapsLockIndicator(*this, paintInfo,
snappedRect);
}
}
void LayoutTextControlSingleLine::layout() {
LayoutAnalyzer::Scope analyzer(*this);
LayoutBlockFlow::layoutBlock(true);
LayoutBox* innerEditorLayoutObject = innerEditorElement()->layoutBox();
Element* container = containerElement();
LayoutBox* containerLayoutObject =
container ? container->layoutBox() : nullptr;
// Center the child block in the block progression direction (vertical
// centering for horizontal text fields).
if (!container && innerEditorLayoutObject &&
innerEditorLayoutObject->size().height() != contentLogicalHeight()) {
LayoutUnit logicalHeightDiff =
innerEditorLayoutObject->logicalHeight() - contentLogicalHeight();
innerEditorLayoutObject->setLogicalTop(
innerEditorLayoutObject->logicalTop() -
(logicalHeightDiff / 2 + layoutMod(logicalHeightDiff, 2)));
} else if (container && containerLayoutObject &&
containerLayoutObject->size().height() != contentLogicalHeight()) {
LayoutUnit logicalHeightDiff =
containerLayoutObject->logicalHeight() - contentLogicalHeight();
containerLayoutObject->setLogicalTop(
containerLayoutObject->logicalTop() -
(logicalHeightDiff / 2 + layoutMod(logicalHeightDiff, 2)));
}
HTMLElement* placeholderElement = inputElement()->placeholderElement();
if (LayoutBox* placeholderBox =
placeholderElement ? placeholderElement->layoutBox() : 0) {
LayoutSize innerEditorSize;
if (innerEditorLayoutObject)
innerEditorSize = innerEditorLayoutObject->size();
placeholderBox->mutableStyleRef().setWidth(Length(
innerEditorSize.width() - placeholderBox->borderAndPaddingWidth(),
Fixed));
bool neededLayout = placeholderBox->needsLayout();
placeholderBox->layoutIfNeeded();
LayoutPoint textOffset;
if (innerEditorLayoutObject)
textOffset = innerEditorLayoutObject->location();
if (editingViewPortElement() && editingViewPortElement()->layoutBox())
textOffset +=
toLayoutSize(editingViewPortElement()->layoutBox()->location());
if (containerLayoutObject)
textOffset += toLayoutSize(containerLayoutObject->location());
if (innerEditorLayoutObject) {
// We use inlineBlockBaseline() for innerEditor because it has no
// inline boxes when we show the placeholder.
int innerEditorBaseline =
innerEditorLayoutObject->inlineBlockBaseline(HorizontalLine);
// We use firstLineBoxBaseline() for placeholder.
// TODO(tkent): It's inconsistent with innerEditorBaseline. However
// placeholderBox->inlineBlockBase() is unexpectedly larger.
int placeholderBaseline = placeholderBox->firstLineBoxBaseline();
textOffset += LayoutSize(0, innerEditorBaseline - placeholderBaseline);
}
placeholderBox->setLocation(textOffset);
// The placeholder gets layout last, after the parent text control and its
// other children, so in order to get the correct overflow from the
// placeholder we need to recompute it now.
if (neededLayout)
computeOverflow(clientLogicalBottom());
}
}
bool LayoutTextControlSingleLine::nodeAtPoint(
HitTestResult& result,
const HitTestLocation& locationInContainer,
const LayoutPoint& accumulatedOffset,
HitTestAction hitTestAction) {
if (!LayoutTextControl::nodeAtPoint(result, locationInContainer,
accumulatedOffset, hitTestAction))
return false;
// Say that we hit the inner text element if
// - we hit a node inside the inner text element,
// - we hit the <input> element (e.g. we're over the border or padding), or
// - we hit regions not in any decoration buttons.
Element* container = containerElement();
if (result.innerNode()->isDescendantOf(innerEditorElement()) ||
result.innerNode() == node() ||
(container && container == result.innerNode())) {
LayoutPoint pointInParent = locationInContainer.point();
if (container && editingViewPortElement()) {
if (editingViewPortElement()->layoutBox())
pointInParent -=
toLayoutSize(editingViewPortElement()->layoutBox()->location());
if (container->layoutBox())
pointInParent -= toLayoutSize(container->layoutBox()->location());
}
hitInnerEditorElement(result, pointInParent, accumulatedOffset);
}
return true;
}
void LayoutTextControlSingleLine::styleDidChange(
StyleDifference diff,
const ComputedStyle* oldStyle) {
LayoutTextControl::styleDidChange(diff, oldStyle);
if (HTMLElement* placeholder = inputElement()->placeholderElement())
placeholder->setInlineStyleProperty(
CSSPropertyTextOverflow,
textShouldBeTruncated() ? CSSValueEllipsis : CSSValueClip);
}
void LayoutTextControlSingleLine::capsLockStateMayHaveChanged() {
if (!node())
return;
// Only draw the caps lock indicator if these things are true:
// 1) The field is a password field
// 2) The frame is active
// 3) The element is focused
// 4) The caps lock is on
bool shouldDrawCapsLockIndicator = false;
if (LocalFrame* frame = document().frame())
shouldDrawCapsLockIndicator =
inputElement()->type() == InputTypeNames::password &&
frame->selection().isFocusedAndActive() &&
document().focusedElement() == node() &&
KeyboardEventManager::currentCapsLockState();
if (shouldDrawCapsLockIndicator != m_shouldDrawCapsLockIndicator) {
m_shouldDrawCapsLockIndicator = shouldDrawCapsLockIndicator;
setShouldDoFullPaintInvalidation();
}
}
bool LayoutTextControlSingleLine::hasControlClip() const {
return true;
}
LayoutRect LayoutTextControlSingleLine::controlClipRect(
const LayoutPoint& additionalOffset) const {
LayoutRect clipRect = paddingBoxRect();
clipRect.moveBy(additionalOffset);
return clipRect;
}
float LayoutTextControlSingleLine::getAvgCharWidth(
const AtomicString& family) const {
// Match the default system font to the width of MS Shell Dlg, the default
// font for textareas in Firefox, Safari Win and IE for some encodings (in
// IE, the default font is encoding specific). 901 is the avgCharWidth value
// in the OS/2 table for MS Shell Dlg.
if (LayoutTheme::theme().needsHackForTextControlWithFontFamily(family))
return scaleEmToUnits(901);
return LayoutTextControl::getAvgCharWidth(family);
}
LayoutUnit LayoutTextControlSingleLine::preferredContentLogicalWidth(
float charWidth) const {
int factor;
bool includesDecoration = inputElement()->sizeShouldIncludeDecoration(factor);
if (factor <= 0)
factor = 20;
LayoutUnit result = LayoutUnit::fromFloatCeil(charWidth * factor);
float maxCharWidth = 0.f;
const Font& font = style()->font();
AtomicString family = font.getFontDescription().family().family();
// Match the default system font to the width of MS Shell Dlg, the default
// font for textareas in Firefox, Safari Win and IE for some encodings (in
// IE, the default font is encoding specific). 4027 is the (xMax - xMin)
// value in the "head" font table for MS Shell Dlg.
if (LayoutTheme::theme().needsHackForTextControlWithFontFamily(family))
maxCharWidth = scaleEmToUnits(4027);
else if (hasValidAvgCharWidth(font.primaryFont(), family))
maxCharWidth = roundf(font.primaryFont()->maxCharWidth());
// For text inputs, IE adds some extra width.
if (maxCharWidth > 0.f)
result += maxCharWidth - charWidth;
if (includesDecoration) {
HTMLElement* spinButton = innerSpinButtonElement();
if (LayoutBox* spinLayoutObject =
spinButton ? spinButton->layoutBox() : 0) {
result += spinLayoutObject->borderAndPaddingLogicalWidth();
// Since the width of spinLayoutObject is not calculated yet,
// spinLayoutObject->logicalWidth() returns 0.
// So ensureComputedStyle()->logicalWidth() is used instead.
result += spinButton->ensureComputedStyle()->logicalWidth().value();
}
}
return result;
}
LayoutUnit LayoutTextControlSingleLine::computeControlLogicalHeight(
LayoutUnit lineHeight,
LayoutUnit nonContentHeight) const {
return lineHeight + nonContentHeight;
}
PassRefPtr<ComputedStyle> LayoutTextControlSingleLine::createInnerEditorStyle(
const ComputedStyle& startStyle) const {
RefPtr<ComputedStyle> textBlockStyle = ComputedStyle::create();
textBlockStyle->inheritFrom(startStyle);
adjustInnerEditorStyle(*textBlockStyle);
textBlockStyle->setWhiteSpace(EWhiteSpace::kPre);
textBlockStyle->setOverflowWrap(NormalOverflowWrap);
textBlockStyle->setTextOverflow(textShouldBeTruncated() ? TextOverflowEllipsis
: TextOverflowClip);
int computedLineHeight =
lineHeight(true, HorizontalLine, PositionOfInteriorLineBoxes).toInt();
// Do not allow line-height to be smaller than our default.
if (textBlockStyle->fontSize() >= computedLineHeight)
textBlockStyle->setLineHeight(ComputedStyle::initialLineHeight());
// We'd like to remove line-height if it's unnecessary because
// overflow:scroll clips editing text by line-height.
Length logicalHeight = startStyle.logicalHeight();
// Here, we remove line-height if the INPUT fixed height is taller than the
// line-height. It's not the precise condition because logicalHeight
// includes border and padding if box-sizing:border-box, and there are cases
// in which we don't want to remove line-height with percent or calculated
// length.
// TODO(tkent): This should be done during layout.
if (logicalHeight.isPercentOrCalc() ||
(logicalHeight.isFixed() &&
logicalHeight.getFloatValue() > computedLineHeight))
textBlockStyle->setLineHeight(ComputedStyle::initialLineHeight());
textBlockStyle->setDisplay(EDisplay::Block);
textBlockStyle->setUnique();
if (inputElement()->shouldRevealPassword())
textBlockStyle->setTextSecurity(TSNONE);
textBlockStyle->setOverflowX(EOverflow::kScroll);
// overflow-y:visible doesn't work because overflow-x:scroll makes a layer.
textBlockStyle->setOverflowY(EOverflow::kScroll);
RefPtr<ComputedStyle> noScrollbarStyle = ComputedStyle::create();
noScrollbarStyle->setStyleType(PseudoIdScrollbar);
noScrollbarStyle->setDisplay(EDisplay::None);
textBlockStyle->addCachedPseudoStyle(noScrollbarStyle);
textBlockStyle->setHasPseudoStyle(PseudoIdScrollbar);
return textBlockStyle.release();
}
bool LayoutTextControlSingleLine::textShouldBeTruncated() const {
return document().focusedElement() != node() &&
styleRef().getTextOverflow() == TextOverflowEllipsis;
}
void LayoutTextControlSingleLine::autoscroll(const IntPoint& position) {
LayoutBox* layoutObject = innerEditorElement()->layoutBox();
if (!layoutObject)
return;
layoutObject->autoscroll(position);
}
LayoutUnit LayoutTextControlSingleLine::scrollWidth() const {
if (LayoutBox* inner =
innerEditorElement() ? innerEditorElement()->layoutBox() : 0) {
// Adjust scrollWidth to inculde input element horizontal paddings and
// decoration width
LayoutUnit adjustment = clientWidth() - inner->clientWidth();
return inner->scrollWidth() + adjustment;
}
return LayoutBlockFlow::scrollWidth();
}
LayoutUnit LayoutTextControlSingleLine::scrollHeight() const {
if (LayoutBox* inner =
innerEditorElement() ? innerEditorElement()->layoutBox() : 0) {
// Adjust scrollHeight to include input element vertical paddings and
// decoration height
LayoutUnit adjustment = clientHeight() - inner->clientHeight();
return inner->scrollHeight() + adjustment;
}
return LayoutBlockFlow::scrollHeight();
}
LayoutUnit LayoutTextControlSingleLine::scrollLeft() const {
if (innerEditorElement())
return LayoutUnit(innerEditorElement()->scrollLeft());
return LayoutBlockFlow::scrollLeft();
}
LayoutUnit LayoutTextControlSingleLine::scrollTop() const {
if (innerEditorElement())
return LayoutUnit(innerEditorElement()->scrollTop());
return LayoutBlockFlow::scrollTop();
}
void LayoutTextControlSingleLine::setScrollLeft(LayoutUnit newLeft) {
if (innerEditorElement())
innerEditorElement()->setScrollLeft(newLeft);
}
void LayoutTextControlSingleLine::setScrollTop(LayoutUnit newTop) {
if (innerEditorElement())
innerEditorElement()->setScrollTop(newTop);
}
void LayoutTextControlSingleLine::addOverflowFromChildren() {
// If the INPUT content height is smaller than the font height, the
// inner-editor element overflows the INPUT box intentionally, however it
// shouldn't affect outside of the INPUT box. So we ignore child overflow.
}
HTMLInputElement* LayoutTextControlSingleLine::inputElement() const {
return toHTMLInputElement(node());
}
} // namespace blink