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 "core/rendering/RenderTextControlSingleLine.h" 26 27 #include "CSSValueKeywords.h" 28 #include "core/dom/shadow/ShadowRoot.h" 29 #include "core/editing/FrameSelection.h" 30 #include "core/html/shadow/ShadowElementNames.h" 31 #include "core/frame/Frame.h" 32 #include "core/rendering/HitTestResult.h" 33 #include "core/rendering/LayoutRectRecorder.h" 34 #include "core/rendering/RenderLayer.h" 35 #include "core/rendering/RenderTheme.h" 36 #include "platform/PlatformKeyboardEvent.h" 37 #include "platform/fonts/SimpleFontData.h" 38 39 using namespace std; 40 41 namespace WebCore { 42 43 using namespace HTMLNames; 44 45 RenderTextControlSingleLine::RenderTextControlSingleLine(HTMLInputElement* element) 46 : RenderTextControl(element) 47 , m_shouldDrawCapsLockIndicator(false) 48 , m_desiredInnerTextLogicalHeight(-1) 49 { 50 ASSERT(element->hasTagName(inputTag)); 51 } 52 53 RenderTextControlSingleLine::~RenderTextControlSingleLine() 54 { 55 } 56 57 inline Element* RenderTextControlSingleLine::containerElement() const 58 { 59 return inputElement()->userAgentShadowRoot()->getElementById(ShadowElementNames::textFieldContainer()); 60 } 61 62 inline Element* RenderTextControlSingleLine::editingViewPortElement() const 63 { 64 return inputElement()->userAgentShadowRoot()->getElementById(ShadowElementNames::editingViewPort()); 65 } 66 67 inline HTMLElement* RenderTextControlSingleLine::innerSpinButtonElement() const 68 { 69 return toHTMLElement(inputElement()->userAgentShadowRoot()->getElementById(ShadowElementNames::spinButton())); 70 } 71 72 void RenderTextControlSingleLine::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 73 { 74 RenderTextControl::paint(paintInfo, paintOffset); 75 76 if (paintInfo.phase == PaintPhaseBlockBackground && m_shouldDrawCapsLockIndicator) { 77 LayoutRect contentsRect = contentBoxRect(); 78 79 // Center in the block progression direction. 80 if (isHorizontalWritingMode()) 81 contentsRect.setY((height() - contentsRect.height()) / 2); 82 else 83 contentsRect.setX((width() - contentsRect.width()) / 2); 84 85 // Convert the rect into the coords used for painting the content 86 contentsRect.moveBy(paintOffset + location()); 87 RenderTheme::theme().paintCapsLockIndicator(this, paintInfo, pixelSnappedIntRect(contentsRect)); 88 } 89 } 90 91 LayoutUnit RenderTextControlSingleLine::computeLogicalHeightLimit() const 92 { 93 return containerElement() ? contentLogicalHeight() : logicalHeight(); 94 } 95 96 void RenderTextControlSingleLine::layout() 97 { 98 LayoutRectRecorder recorder(*this); 99 SubtreeLayoutScope layoutScope(this); 100 101 // FIXME: We should remove the height-related hacks in layout() and 102 // styleDidChange(). We need them because 103 // - Center the inner elements vertically if the input height is taller than 104 // the intrinsic height of the inner elements. 105 // - Shrink the inner elment heights if the input height is samller than the 106 // intrinsic heights of the inner elements. 107 108 // We don't honor paddings and borders for textfields without decorations 109 // and type=search if the text height is taller than the contentHeight() 110 // because of compability. 111 112 RenderBox* innerTextRenderer = innerTextElement()->renderBox(); 113 RenderBox* viewPortRenderer = editingViewPortElement() ? editingViewPortElement()->renderBox() : 0; 114 115 // To ensure consistency between layouts, we need to reset any conditionally overriden height. 116 if (innerTextRenderer && !innerTextRenderer->style()->logicalHeight().isAuto()) { 117 innerTextRenderer->style()->setLogicalHeight(Length(Auto)); 118 layoutScope.setNeedsLayout(innerTextRenderer); 119 } 120 if (viewPortRenderer && !viewPortRenderer->style()->logicalHeight().isAuto()) { 121 viewPortRenderer->style()->setLogicalHeight(Length(Auto)); 122 layoutScope.setNeedsLayout(viewPortRenderer); 123 } 124 125 RenderBlockFlow::layoutBlock(false); 126 127 Element* container = containerElement(); 128 RenderBox* containerRenderer = container ? container->renderBox() : 0; 129 130 // Set the text block height 131 LayoutUnit desiredLogicalHeight = textBlockLogicalHeight(); 132 LayoutUnit logicalHeightLimit = computeLogicalHeightLimit(); 133 if (innerTextRenderer && innerTextRenderer->logicalHeight() > logicalHeightLimit) { 134 if (desiredLogicalHeight != innerTextRenderer->logicalHeight()) 135 layoutScope.setNeedsLayout(this); 136 137 m_desiredInnerTextLogicalHeight = desiredLogicalHeight; 138 139 innerTextRenderer->style()->setLogicalHeight(Length(desiredLogicalHeight, Fixed)); 140 layoutScope.setNeedsLayout(innerTextRenderer); 141 if (viewPortRenderer) { 142 viewPortRenderer->style()->setLogicalHeight(Length(desiredLogicalHeight, Fixed)); 143 layoutScope.setNeedsLayout(viewPortRenderer); 144 } 145 } 146 // The container might be taller because of decoration elements. 147 if (containerRenderer) { 148 containerRenderer->layoutIfNeeded(); 149 LayoutUnit containerLogicalHeight = containerRenderer->logicalHeight(); 150 if (containerLogicalHeight > logicalHeightLimit) { 151 containerRenderer->style()->setLogicalHeight(Length(logicalHeightLimit, Fixed)); 152 layoutScope.setNeedsLayout(this); 153 } else if (containerRenderer->logicalHeight() < contentLogicalHeight()) { 154 containerRenderer->style()->setLogicalHeight(Length(contentLogicalHeight(), Fixed)); 155 layoutScope.setNeedsLayout(this); 156 } else 157 containerRenderer->style()->setLogicalHeight(Length(containerLogicalHeight, Fixed)); 158 } 159 160 // If we need another layout pass, we have changed one of children's height so we need to relayout them. 161 if (needsLayout()) 162 RenderBlockFlow::layoutBlock(true); 163 164 // Center the child block in the block progression direction (vertical centering for horizontal text fields). 165 if (!container && innerTextRenderer && innerTextRenderer->height() != contentLogicalHeight()) { 166 LayoutUnit logicalHeightDiff = innerTextRenderer->logicalHeight() - contentLogicalHeight(); 167 innerTextRenderer->setLogicalTop(innerTextRenderer->logicalTop() - (logicalHeightDiff / 2 + layoutMod(logicalHeightDiff, 2))); 168 } else 169 centerContainerIfNeeded(containerRenderer); 170 171 // Ignores the paddings for the inner spin button. 172 if (RenderBox* innerSpinBox = innerSpinButtonElement() ? innerSpinButtonElement()->renderBox() : 0) { 173 RenderBox* parentBox = innerSpinBox->parentBox(); 174 if (containerRenderer && !containerRenderer->style()->isLeftToRightDirection()) 175 innerSpinBox->setLogicalLocation(LayoutPoint(-paddingLogicalLeft(), -paddingBefore())); 176 else 177 innerSpinBox->setLogicalLocation(LayoutPoint(parentBox->logicalWidth() - innerSpinBox->logicalWidth() + paddingLogicalRight(), -paddingBefore())); 178 innerSpinBox->setLogicalHeight(logicalHeight() - borderBefore() - borderAfter()); 179 } 180 181 HTMLElement* placeholderElement = inputElement()->placeholderElement(); 182 if (RenderBox* placeholderBox = placeholderElement ? placeholderElement->renderBox() : 0) { 183 LayoutSize innerTextSize; 184 if (innerTextRenderer) 185 innerTextSize = innerTextRenderer->size(); 186 placeholderBox->style()->setWidth(Length(innerTextSize.width() - placeholderBox->borderAndPaddingWidth(), Fixed)); 187 placeholderBox->style()->setHeight(Length(innerTextSize.height() - placeholderBox->borderAndPaddingHeight(), Fixed)); 188 bool neededLayout = placeholderBox->needsLayout(); 189 bool placeholderBoxHadLayout = placeholderBox->everHadLayout(); 190 placeholderBox->layoutIfNeeded(); 191 LayoutPoint textOffset; 192 if (innerTextRenderer) 193 textOffset = innerTextRenderer->location(); 194 if (editingViewPortElement() && editingViewPortElement()->renderBox()) 195 textOffset += toLayoutSize(editingViewPortElement()->renderBox()->location()); 196 if (containerRenderer) 197 textOffset += toLayoutSize(containerRenderer->location()); 198 placeholderBox->setLocation(textOffset); 199 200 if (!placeholderBoxHadLayout && placeholderBox->checkForRepaintDuringLayout()) { 201 // This assumes a shadow tree without floats. If floats are added, the 202 // logic should be shared with RenderBlock::layoutBlockChild. 203 placeholderBox->repaint(); 204 } 205 // The placeholder gets layout last, after the parent text control and its other children, 206 // so in order to get the correct overflow from the placeholder we need to recompute it now. 207 if (neededLayout) 208 computeOverflow(clientLogicalBottom()); 209 } 210 } 211 212 bool RenderTextControlSingleLine::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction) 213 { 214 if (!RenderTextControl::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction)) 215 return false; 216 217 // Say that we hit the inner text element if 218 // - we hit a node inside the inner text element, 219 // - we hit the <input> element (e.g. we're over the border or padding), or 220 // - we hit regions not in any decoration buttons. 221 Element* container = containerElement(); 222 if (result.innerNode()->isDescendantOf(innerTextElement()) || result.innerNode() == node() || (container && container == result.innerNode())) { 223 LayoutPoint pointInParent = locationInContainer.point(); 224 if (container && editingViewPortElement()) { 225 if (editingViewPortElement()->renderBox()) 226 pointInParent -= toLayoutSize(editingViewPortElement()->renderBox()->location()); 227 if (container->renderBox()) 228 pointInParent -= toLayoutSize(container->renderBox()->location()); 229 } 230 hitInnerTextElement(result, pointInParent, accumulatedOffset); 231 } 232 return true; 233 } 234 235 void RenderTextControlSingleLine::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 236 { 237 m_desiredInnerTextLogicalHeight = -1; 238 RenderTextControl::styleDidChange(diff, oldStyle); 239 240 // We may have set the width and the height in the old style in layout(). 241 // Reset them now to avoid getting a spurious layout hint. 242 Element* viewPort = editingViewPortElement(); 243 if (RenderObject* viewPortRenderer = viewPort ? viewPort->renderer() : 0) { 244 viewPortRenderer->style()->setHeight(Length()); 245 viewPortRenderer->style()->setWidth(Length()); 246 } 247 Element* container = containerElement(); 248 if (RenderObject* containerRenderer = container ? container->renderer() : 0) { 249 containerRenderer->style()->setHeight(Length()); 250 containerRenderer->style()->setWidth(Length()); 251 } 252 RenderObject* innerTextRenderer = innerTextElement()->renderer(); 253 if (innerTextRenderer && diff == StyleDifferenceLayout) 254 innerTextRenderer->setNeedsLayout(); 255 if (HTMLElement* placeholder = inputElement()->placeholderElement()) 256 placeholder->setInlineStyleProperty(CSSPropertyTextOverflow, textShouldBeTruncated() ? CSSValueEllipsis : CSSValueClip); 257 setHasOverflowClip(false); 258 } 259 260 void RenderTextControlSingleLine::capsLockStateMayHaveChanged() 261 { 262 if (!node()) 263 return; 264 265 // Only draw the caps lock indicator if these things are true: 266 // 1) The field is a password field 267 // 2) The frame is active 268 // 3) The element is focused 269 // 4) The caps lock is on 270 bool shouldDrawCapsLockIndicator = false; 271 272 if (Frame* frame = document().frame()) 273 shouldDrawCapsLockIndicator = inputElement()->isPasswordField() && frame->selection().isFocusedAndActive() && document().focusedElement() == node() && PlatformKeyboardEvent::currentCapsLockState(); 274 275 if (shouldDrawCapsLockIndicator != m_shouldDrawCapsLockIndicator) { 276 m_shouldDrawCapsLockIndicator = shouldDrawCapsLockIndicator; 277 repaint(); 278 } 279 } 280 281 bool RenderTextControlSingleLine::hasControlClip() const 282 { 283 // Apply control clip for text fields with decorations. 284 return !!containerElement(); 285 } 286 287 LayoutRect RenderTextControlSingleLine::controlClipRect(const LayoutPoint& additionalOffset) const 288 { 289 ASSERT(hasControlClip()); 290 LayoutRect clipRect = contentBoxRect(); 291 if (containerElement()->renderBox()) 292 clipRect = unionRect(clipRect, containerElement()->renderBox()->frameRect()); 293 clipRect.moveBy(additionalOffset); 294 return clipRect; 295 } 296 297 float RenderTextControlSingleLine::getAvgCharWidth(AtomicString family) 298 { 299 // Since Lucida Grande is the default font, we want this to match the width 300 // of MS Shell Dlg, the default font for textareas in Firefox, Safari Win and 301 // IE for some encodings (in IE, the default font is encoding specific). 302 // 901 is the avgCharWidth value in the OS/2 table for MS Shell Dlg. 303 if (family == "Lucida Grande") 304 return scaleEmToUnits(901); 305 306 return RenderTextControl::getAvgCharWidth(family); 307 } 308 309 LayoutUnit RenderTextControlSingleLine::preferredContentLogicalWidth(float charWidth) const 310 { 311 int factor; 312 bool includesDecoration = inputElement()->sizeShouldIncludeDecoration(factor); 313 if (factor <= 0) 314 factor = 20; 315 316 LayoutUnit result = LayoutUnit::fromFloatCeil(charWidth * factor); 317 318 float maxCharWidth = 0.f; 319 AtomicString family = style()->font().family().family(); 320 // Since Lucida Grande is the default font, we want this to match the width 321 // of MS Shell Dlg, the default font for textareas in Firefox, Safari Win and 322 // IE for some encodings (in IE, the default font is encoding specific). 323 // 4027 is the (xMax - xMin) value in the "head" font table for MS Shell Dlg. 324 if (family == "Lucida Grande") 325 maxCharWidth = scaleEmToUnits(4027); 326 else if (hasValidAvgCharWidth(family)) 327 maxCharWidth = roundf(style()->font().primaryFont()->maxCharWidth()); 328 329 // For text inputs, IE adds some extra width. 330 if (maxCharWidth > 0.f) 331 result += maxCharWidth - charWidth; 332 333 if (includesDecoration) { 334 HTMLElement* spinButton = innerSpinButtonElement(); 335 if (RenderBox* spinRenderer = spinButton ? spinButton->renderBox() : 0) { 336 result += spinRenderer->borderAndPaddingLogicalWidth(); 337 // Since the width of spinRenderer is not calculated yet, spinRenderer->logicalWidth() returns 0. 338 // So computedStyle()->logicalWidth() is used instead. 339 result += spinButton->computedStyle()->logicalWidth().value(); 340 } 341 } 342 343 return result; 344 } 345 346 LayoutUnit RenderTextControlSingleLine::computeControlLogicalHeight(LayoutUnit lineHeight, LayoutUnit nonContentHeight) const 347 { 348 return lineHeight + nonContentHeight; 349 } 350 351 void RenderTextControlSingleLine::updateFromElement() 352 { 353 RenderTextControl::updateFromElement(); 354 } 355 356 PassRefPtr<RenderStyle> RenderTextControlSingleLine::createInnerTextStyle(const RenderStyle* startStyle) const 357 { 358 RefPtr<RenderStyle> textBlockStyle = RenderStyle::create(); 359 textBlockStyle->inheritFrom(startStyle); 360 adjustInnerTextStyle(textBlockStyle.get()); 361 362 textBlockStyle->setWhiteSpace(PRE); 363 textBlockStyle->setOverflowWrap(NormalOverflowWrap); 364 textBlockStyle->setOverflowX(OHIDDEN); 365 textBlockStyle->setOverflowY(OHIDDEN); 366 textBlockStyle->setTextOverflow(textShouldBeTruncated() ? TextOverflowEllipsis : TextOverflowClip); 367 368 if (m_desiredInnerTextLogicalHeight >= 0) 369 textBlockStyle->setLogicalHeight(Length(m_desiredInnerTextLogicalHeight, Fixed)); 370 // Do not allow line-height to be smaller than our default. 371 if (textBlockStyle->fontMetrics().lineSpacing() > lineHeight(true, HorizontalLine, PositionOfInteriorLineBoxes)) 372 textBlockStyle->setLineHeight(RenderStyle::initialLineHeight()); 373 374 textBlockStyle->setDisplay(BLOCK); 375 textBlockStyle->setUnique(); 376 377 return textBlockStyle.release(); 378 } 379 380 bool RenderTextControlSingleLine::textShouldBeTruncated() const 381 { 382 return document().focusedElement() != node() && style()->textOverflow() == TextOverflowEllipsis; 383 } 384 385 void RenderTextControlSingleLine::autoscroll(const IntPoint& position) 386 { 387 RenderBox* renderer = innerTextElement()->renderBox(); 388 if (!renderer) 389 return; 390 391 renderer->autoscroll(position); 392 } 393 394 int RenderTextControlSingleLine::scrollWidth() const 395 { 396 if (innerTextElement()) 397 return innerTextElement()->scrollWidth(); 398 return RenderBlock::scrollWidth(); 399 } 400 401 int RenderTextControlSingleLine::scrollHeight() const 402 { 403 if (innerTextElement()) 404 return innerTextElement()->scrollHeight(); 405 return RenderBlock::scrollHeight(); 406 } 407 408 int RenderTextControlSingleLine::scrollLeft() const 409 { 410 if (innerTextElement()) 411 return innerTextElement()->scrollLeft(); 412 return RenderBlock::scrollLeft(); 413 } 414 415 int RenderTextControlSingleLine::scrollTop() const 416 { 417 if (innerTextElement()) 418 return innerTextElement()->scrollTop(); 419 return RenderBlock::scrollTop(); 420 } 421 422 void RenderTextControlSingleLine::setScrollLeft(int newLeft) 423 { 424 if (innerTextElement()) 425 innerTextElement()->setScrollLeft(newLeft); 426 } 427 428 void RenderTextControlSingleLine::setScrollTop(int newTop) 429 { 430 if (innerTextElement()) 431 innerTextElement()->setScrollTop(newTop); 432 } 433 434 HTMLInputElement* RenderTextControlSingleLine::inputElement() const 435 { 436 return toHTMLInputElement(node()); 437 } 438 439 } 440