1/** 2 * Copyright (C) 2006, 2007, 2010 Apple Inc. All rights reserved. 3 * (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) 4 * Copyright (C) 2010 Google Inc. All rights reserved. 5 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Library General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Library General Public License for more details. 16 * 17 * You should have received a copy of the GNU Library General Public License 18 * along with this library; see the file COPYING.LIB. If not, write to 19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 20 * Boston, MA 02110-1301, USA. 21 * 22 */ 23 24#include "config.h" 25#include "RenderTextControlSingleLine.h" 26 27#include "CSSFontSelector.h" 28#include "CSSValueKeywords.h" 29#include "Chrome.h" 30#include "Frame.h" 31#include "FrameSelection.h" 32#include "FrameView.h" 33#include "HTMLNames.h" 34#include "HitTestResult.h" 35#include "LocalizedStrings.h" 36#include "Page.h" 37#include "PlatformKeyboardEvent.h" 38#include "RenderLayer.h" 39#include "RenderScrollbar.h" 40#include "RenderTheme.h" 41#include "Settings.h" 42#include "SimpleFontData.h" 43#include "StyleResolver.h" 44#include "TextControlInnerElements.h" 45#include <wtf/StackStats.h> 46 47using namespace std; 48 49namespace WebCore { 50 51using namespace HTMLNames; 52 53RenderTextControlSingleLine::RenderTextControlSingleLine(Element* element) 54 : RenderTextControl(element) 55 , m_shouldDrawCapsLockIndicator(false) 56 , m_desiredInnerTextLogicalHeight(-1) 57{ 58 ASSERT(element->isHTMLElement()); 59 ASSERT(element->toInputElement()); 60} 61 62RenderTextControlSingleLine::~RenderTextControlSingleLine() 63{ 64} 65 66inline HTMLElement* RenderTextControlSingleLine::innerSpinButtonElement() const 67{ 68 return inputElement()->innerSpinButtonElement(); 69} 70 71RenderStyle* RenderTextControlSingleLine::textBaseStyle() const 72{ 73 HTMLElement* innerBlock = innerBlockElement(); 74 return innerBlock ? innerBlock->renderer()->style() : style(); 75} 76 77void RenderTextControlSingleLine::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 78{ 79 RenderTextControl::paint(paintInfo, paintOffset); 80 81 if (paintInfo.phase == PaintPhaseBlockBackground && m_shouldDrawCapsLockIndicator) { 82 LayoutRect contentsRect = contentBoxRect(); 83 84 // Center in the block progression direction. 85 if (isHorizontalWritingMode()) 86 contentsRect.setY((height() - contentsRect.height()) / 2); 87 else 88 contentsRect.setX((width() - contentsRect.width()) / 2); 89 90 // Convert the rect into the coords used for painting the content 91 contentsRect.moveBy(paintOffset + location()); 92 theme()->paintCapsLockIndicator(this, paintInfo, pixelSnappedIntRect(contentsRect)); 93 } 94} 95 96LayoutUnit RenderTextControlSingleLine::computeLogicalHeightLimit() const 97{ 98 return containerElement() ? contentLogicalHeight() : logicalHeight(); 99} 100 101static void setNeedsLayoutOnAncestors(RenderObject* start, RenderObject* ancestor) 102{ 103 ASSERT(start != ancestor); 104 for (RenderObject* renderer = start; renderer != ancestor; renderer = renderer->parent()) { 105 ASSERT(renderer); 106 renderer->setNeedsLayout(true, MarkOnlyThis); 107 } 108} 109 110void RenderTextControlSingleLine::layout() 111{ 112 StackStats::LayoutCheckPoint layoutCheckPoint; 113 114 // FIXME: We should remove the height-related hacks in layout() and 115 // styleDidChange(). We need them because 116 // - Center the inner elements vertically if the input height is taller than 117 // the intrinsic height of the inner elements. 118 // - Shrink the inner elment heights if the input height is samller than the 119 // intrinsic heights of the inner elements. 120 121 // We don't honor paddings and borders for textfields without decorations 122 // and type=search if the text height is taller than the contentHeight() 123 // because of compability. 124 125 RenderBox* innerTextRenderer = innerTextElement()->renderBox(); 126 RenderBox* innerBlockRenderer = innerBlockElement() ? innerBlockElement()->renderBox() : 0; 127 128 // To ensure consistency between layouts, we need to reset any conditionally overriden height. 129 if (innerTextRenderer && !innerTextRenderer->style()->logicalHeight().isAuto()) { 130 innerTextRenderer->style()->setLogicalHeight(Length(Auto)); 131 setNeedsLayoutOnAncestors(innerTextRenderer, this); 132 } 133 if (innerBlockRenderer && !innerBlockRenderer->style()->logicalHeight().isAuto()) { 134 innerBlockRenderer->style()->setLogicalHeight(Length(Auto)); 135 setNeedsLayoutOnAncestors(innerBlockRenderer, this); 136 } 137 138 RenderBlock::layoutBlock(false); 139 140 HTMLElement* container = containerElement(); 141 RenderBox* containerRenderer = container ? container->renderBox() : 0; 142 143 // Set the text block height 144 LayoutUnit desiredLogicalHeight = textBlockLogicalHeight(); 145 LayoutUnit logicalHeightLimit = computeLogicalHeightLimit(); 146 if (innerTextRenderer && innerTextRenderer->logicalHeight() > logicalHeightLimit) { 147 if (desiredLogicalHeight != innerTextRenderer->logicalHeight()) 148 setNeedsLayout(true, MarkOnlyThis); 149 150 m_desiredInnerTextLogicalHeight = desiredLogicalHeight; 151 152 innerTextRenderer->style()->setLogicalHeight(Length(desiredLogicalHeight, Fixed)); 153 innerTextRenderer->setNeedsLayout(true, MarkOnlyThis); 154 if (innerBlockRenderer) { 155 innerBlockRenderer->style()->setLogicalHeight(Length(desiredLogicalHeight, Fixed)); 156 innerBlockRenderer->setNeedsLayout(true, MarkOnlyThis); 157 } 158 } 159 // The container might be taller because of decoration elements. 160 if (containerRenderer) { 161 containerRenderer->layoutIfNeeded(); 162 LayoutUnit containerLogicalHeight = containerRenderer->logicalHeight(); 163 if (containerLogicalHeight > logicalHeightLimit) { 164 containerRenderer->style()->setLogicalHeight(Length(logicalHeightLimit, Fixed)); 165 setNeedsLayout(true, MarkOnlyThis); 166 } else if (containerRenderer->logicalHeight() < contentLogicalHeight()) { 167 containerRenderer->style()->setLogicalHeight(Length(contentLogicalHeight(), Fixed)); 168 setNeedsLayout(true, MarkOnlyThis); 169 } else 170 containerRenderer->style()->setLogicalHeight(Length(containerLogicalHeight, Fixed)); 171 } 172 173 // If we need another layout pass, we have changed one of children's height so we need to relayout them. 174 if (needsLayout()) 175 RenderBlock::layoutBlock(true); 176 177 // Center the child block in the block progression direction (vertical centering for horizontal text fields). 178 if (!container && innerTextRenderer && innerTextRenderer->height() != contentLogicalHeight()) { 179 LayoutUnit logicalHeightDiff = innerTextRenderer->logicalHeight() - contentLogicalHeight(); 180 innerTextRenderer->setLogicalTop(innerTextRenderer->logicalTop() - (logicalHeightDiff / 2 + layoutMod(logicalHeightDiff, 2))); 181 } else 182 centerContainerIfNeeded(containerRenderer); 183 184 // Ignores the paddings for the inner spin button. 185 if (RenderBox* innerSpinBox = innerSpinButtonElement() ? innerSpinButtonElement()->renderBox() : 0) { 186 RenderBox* parentBox = innerSpinBox->parentBox(); 187 if (containerRenderer && !containerRenderer->style()->isLeftToRightDirection()) 188 innerSpinBox->setLogicalLocation(LayoutPoint(-paddingLogicalLeft(), -paddingBefore())); 189 else 190 innerSpinBox->setLogicalLocation(LayoutPoint(parentBox->logicalWidth() - innerSpinBox->logicalWidth() + paddingLogicalRight(), -paddingBefore())); 191 innerSpinBox->setLogicalHeight(logicalHeight() - borderBefore() - borderAfter()); 192 } 193 194 HTMLElement* placeholderElement = inputElement()->placeholderElement(); 195 if (RenderBox* placeholderBox = placeholderElement ? placeholderElement->renderBox() : 0) { 196 LayoutSize innerTextSize; 197 if (innerTextRenderer) 198 innerTextSize = innerTextRenderer->size(); 199 placeholderBox->style()->setWidth(Length(innerTextSize.width() - placeholderBox->borderAndPaddingWidth(), Fixed)); 200 placeholderBox->style()->setHeight(Length(innerTextSize.height() - placeholderBox->borderAndPaddingHeight(), Fixed)); 201 bool neededLayout = placeholderBox->needsLayout(); 202 bool placeholderBoxHadLayout = placeholderBox->everHadLayout(); 203 placeholderBox->layoutIfNeeded(); 204 LayoutPoint textOffset; 205 if (innerTextRenderer) 206 textOffset = innerTextRenderer->location(); 207 if (innerBlockElement() && innerBlockElement()->renderBox()) 208 textOffset += toLayoutSize(innerBlockElement()->renderBox()->location()); 209 if (containerRenderer) 210 textOffset += toLayoutSize(containerRenderer->location()); 211 placeholderBox->setLocation(textOffset); 212 213 if (!placeholderBoxHadLayout && placeholderBox->checkForRepaintDuringLayout()) { 214 // This assumes a shadow tree without floats. If floats are added, the 215 // logic should be shared with RenderBlock::layoutBlockChild. 216 placeholderBox->repaint(); 217 } 218 // The placeholder gets layout last, after the parent text control and its other children, 219 // so in order to get the correct overflow from the placeholder we need to recompute it now. 220 if (neededLayout) 221 computeOverflow(clientLogicalBottom()); 222 } 223} 224 225bool RenderTextControlSingleLine::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction) 226{ 227 if (!RenderTextControl::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction)) 228 return false; 229 230 // Say that we hit the inner text element if 231 // - we hit a node inside the inner text element, 232 // - we hit the <input> element (e.g. we're over the border or padding), or 233 // - we hit regions not in any decoration buttons. 234 HTMLElement* container = containerElement(); 235 if (result.innerNode()->isDescendantOf(innerTextElement()) || result.innerNode() == node() || (container && container == result.innerNode())) { 236 LayoutPoint pointInParent = locationInContainer.point(); 237 if (container && innerBlockElement()) { 238 if (innerBlockElement()->renderBox()) 239 pointInParent -= toLayoutSize(innerBlockElement()->renderBox()->location()); 240 if (container->renderBox()) 241 pointInParent -= toLayoutSize(container->renderBox()->location()); 242 } 243 hitInnerTextElement(result, pointInParent, accumulatedOffset); 244 } 245 return true; 246} 247 248void RenderTextControlSingleLine::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 249{ 250 m_desiredInnerTextLogicalHeight = -1; 251 RenderTextControl::styleDidChange(diff, oldStyle); 252 253 // We may have set the width and the height in the old style in layout(). 254 // Reset them now to avoid getting a spurious layout hint. 255 HTMLElement* innerBlock = innerBlockElement(); 256 if (RenderObject* innerBlockRenderer = innerBlock ? innerBlock->renderer() : 0) { 257 innerBlockRenderer->style()->setHeight(Length()); 258 innerBlockRenderer->style()->setWidth(Length()); 259 } 260 HTMLElement* container = containerElement(); 261 if (RenderObject* containerRenderer = container ? container->renderer() : 0) { 262 containerRenderer->style()->setHeight(Length()); 263 containerRenderer->style()->setWidth(Length()); 264 } 265 RenderObject* innerTextRenderer = innerTextElement()->renderer(); 266 if (innerTextRenderer && diff == StyleDifferenceLayout) 267 innerTextRenderer->setNeedsLayout(true, MarkContainingBlockChain); 268 if (HTMLElement* placeholder = inputElement()->placeholderElement()) 269 placeholder->setInlineStyleProperty(CSSPropertyTextOverflow, textShouldBeTruncated() ? CSSValueEllipsis : CSSValueClip); 270 setHasOverflowClip(false); 271} 272 273void RenderTextControlSingleLine::capsLockStateMayHaveChanged() 274{ 275 if (!node() || !document()) 276 return; 277 278 // Only draw the caps lock indicator if these things are true: 279 // 1) The field is a password field 280 // 2) The frame is active 281 // 3) The element is focused 282 // 4) The caps lock is on 283 bool shouldDrawCapsLockIndicator = false; 284 285 if (Frame* frame = document()->frame()) 286 shouldDrawCapsLockIndicator = inputElement()->isPasswordField() 287 && frame->selection()->isFocusedAndActive() 288 && document()->focusedElement() == node() 289 && PlatformKeyboardEvent::currentCapsLockState(); 290 291 if (shouldDrawCapsLockIndicator != m_shouldDrawCapsLockIndicator) { 292 m_shouldDrawCapsLockIndicator = shouldDrawCapsLockIndicator; 293 repaint(); 294 } 295} 296 297bool RenderTextControlSingleLine::hasControlClip() const 298{ 299 // Apply control clip for text fields with decorations. 300 return !!containerElement(); 301} 302 303LayoutRect RenderTextControlSingleLine::controlClipRect(const LayoutPoint& additionalOffset) const 304{ 305 ASSERT(hasControlClip()); 306 LayoutRect clipRect = contentBoxRect(); 307 if (containerElement()->renderBox()) 308 clipRect = unionRect(clipRect, containerElement()->renderBox()->frameRect()); 309 clipRect.moveBy(additionalOffset); 310 return clipRect; 311} 312 313float RenderTextControlSingleLine::getAvgCharWidth(AtomicString family) 314{ 315 // Since Lucida Grande is the default font, we want this to match the width 316 // of MS Shell Dlg, the default font for textareas in Firefox, Safari Win and 317 // IE for some encodings (in IE, the default font is encoding specific). 318 // 901 is the avgCharWidth value in the OS/2 table for MS Shell Dlg. 319 if (family == "Lucida Grande") 320 return scaleEmToUnits(901); 321 322 return RenderTextControl::getAvgCharWidth(family); 323} 324 325LayoutUnit RenderTextControlSingleLine::preferredContentLogicalWidth(float charWidth) const 326{ 327 int factor; 328 bool includesDecoration = inputElement()->sizeShouldIncludeDecoration(factor); 329 if (factor <= 0) 330 factor = 20; 331 332 LayoutUnit result = static_cast<LayoutUnit>(ceiledLayoutUnit(charWidth * factor)); 333 334 float maxCharWidth = 0.f; 335 const AtomicString& family = style()->font().firstFamily(); 336 // Since Lucida Grande is the default font, we want this to match the width 337 // of MS Shell Dlg, the default font for textareas in Firefox, Safari Win and 338 // IE for some encodings (in IE, the default font is encoding specific). 339 // 4027 is the (xMax - xMin) value in the "head" font table for MS Shell Dlg. 340 if (family == "Lucida Grande") 341 maxCharWidth = scaleEmToUnits(4027); 342 else if (hasValidAvgCharWidth(family)) 343 maxCharWidth = roundf(style()->font().primaryFont()->maxCharWidth()); 344 345 // For text inputs, IE adds some extra width. 346 if (maxCharWidth > 0.f) 347 result += maxCharWidth - charWidth; 348 349 if (includesDecoration) { 350 HTMLElement* spinButton = innerSpinButtonElement(); 351 if (RenderBox* spinRenderer = spinButton ? spinButton->renderBox() : 0) { 352 result += spinRenderer->borderAndPaddingLogicalWidth(); 353 // Since the width of spinRenderer is not calculated yet, spinRenderer->logicalWidth() returns 0. 354 // So computedStyle()->logicalWidth() is used instead. 355 result += spinButton->computedStyle()->logicalWidth().value(); 356 } 357 } 358 359 return result; 360} 361 362LayoutUnit RenderTextControlSingleLine::computeControlLogicalHeight(LayoutUnit lineHeight, LayoutUnit nonContentHeight) const 363{ 364 return lineHeight + nonContentHeight; 365} 366 367void RenderTextControlSingleLine::updateFromElement() 368{ 369 RenderTextControl::updateFromElement(); 370} 371 372PassRefPtr<RenderStyle> RenderTextControlSingleLine::createInnerTextStyle(const RenderStyle* startStyle) const 373{ 374 RefPtr<RenderStyle> textBlockStyle = RenderStyle::create(); 375 textBlockStyle->inheritFrom(startStyle); 376 adjustInnerTextStyle(startStyle, textBlockStyle.get()); 377 378 textBlockStyle->setWhiteSpace(PRE); 379 textBlockStyle->setOverflowWrap(NormalOverflowWrap); 380 textBlockStyle->setOverflowX(OHIDDEN); 381 textBlockStyle->setOverflowY(OHIDDEN); 382 textBlockStyle->setTextOverflow(textShouldBeTruncated() ? TextOverflowEllipsis : TextOverflowClip); 383 384 if (m_desiredInnerTextLogicalHeight >= 0) 385 textBlockStyle->setLogicalHeight(Length(m_desiredInnerTextLogicalHeight, Fixed)); 386 // Do not allow line-height to be smaller than our default. 387 if (textBlockStyle->fontMetrics().lineSpacing() > lineHeight(true, HorizontalLine, PositionOfInteriorLineBoxes)) 388 textBlockStyle->setLineHeight(RenderStyle::initialLineHeight()); 389 390 textBlockStyle->setDisplay(BLOCK); 391 392 return textBlockStyle.release(); 393} 394 395PassRefPtr<RenderStyle> RenderTextControlSingleLine::createInnerBlockStyle(const RenderStyle* startStyle) const 396{ 397 RefPtr<RenderStyle> innerBlockStyle = RenderStyle::create(); 398 innerBlockStyle->inheritFrom(startStyle); 399 400 innerBlockStyle->setFlexGrow(1); 401 // min-width: 0; is needed for correct shrinking. 402 // FIXME: Remove this line when https://bugs.webkit.org/show_bug.cgi?id=111790 is fixed. 403 innerBlockStyle->setMinWidth(Length(0, Fixed)); 404 innerBlockStyle->setDisplay(BLOCK); 405 innerBlockStyle->setDirection(LTR); 406 407 // 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. 408 innerBlockStyle->setUserModify(READ_ONLY); 409 410 return innerBlockStyle.release(); 411} 412 413bool RenderTextControlSingleLine::textShouldBeTruncated() const 414{ 415 return document()->focusedElement() != node() 416 && style()->textOverflow() == TextOverflowEllipsis; 417} 418 419void RenderTextControlSingleLine::autoscroll(const IntPoint& position) 420{ 421 RenderBox* renderer = innerTextElement()->renderBox(); 422 if (!renderer) 423 return; 424 RenderLayer* layer = renderer->layer(); 425 if (layer) 426 layer->autoscroll(position); 427} 428 429int RenderTextControlSingleLine::scrollWidth() const 430{ 431 if (innerTextElement()) 432 return innerTextElement()->scrollWidth(); 433 return RenderBlock::scrollWidth(); 434} 435 436int RenderTextControlSingleLine::scrollHeight() const 437{ 438 if (innerTextElement()) 439 return innerTextElement()->scrollHeight(); 440 return RenderBlock::scrollHeight(); 441} 442 443int RenderTextControlSingleLine::scrollLeft() const 444{ 445 if (innerTextElement()) 446 return innerTextElement()->scrollLeft(); 447 return RenderBlock::scrollLeft(); 448} 449 450int RenderTextControlSingleLine::scrollTop() const 451{ 452 if (innerTextElement()) 453 return innerTextElement()->scrollTop(); 454 return RenderBlock::scrollTop(); 455} 456 457void RenderTextControlSingleLine::setScrollLeft(int newLeft) 458{ 459 if (innerTextElement()) 460 innerTextElement()->setScrollLeft(newLeft); 461} 462 463void RenderTextControlSingleLine::setScrollTop(int newTop) 464{ 465 if (innerTextElement()) 466 innerTextElement()->setScrollTop(newTop); 467} 468 469bool RenderTextControlSingleLine::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier, Node** stopNode) 470{ 471 RenderBox* renderer = innerTextElement()->renderBox(); 472 if (!renderer) 473 return false; 474 RenderLayer* layer = renderer->layer(); 475 if (layer && layer->scroll(direction, granularity, multiplier)) 476 return true; 477 return RenderBlock::scroll(direction, granularity, multiplier, stopNode); 478} 479 480bool RenderTextControlSingleLine::logicalScroll(ScrollLogicalDirection direction, ScrollGranularity granularity, float multiplier, Node** stopNode) 481{ 482 RenderLayer* layer = innerTextElement()->renderBox()->layer(); 483 if (layer && layer->scroll(logicalToPhysical(direction, style()->isHorizontalWritingMode(), style()->isFlippedBlocksWritingMode()), granularity, multiplier)) 484 return true; 485 return RenderBlock::logicalScroll(direction, granularity, multiplier, stopNode); 486} 487 488HTMLInputElement* RenderTextControlSingleLine::inputElement() const 489{ 490 return node()->toInputElement(); 491} 492 493} 494