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 "Font.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 "RenderLayer.h"
37#include "RenderScrollbar.h"
38#include "RenderTheme.h"
39#include "RenderView.h"
40#include "StyleResolver.h"
41#include "TextControlInnerElements.h"
42#include <wtf/IsoMallocInlines.h>
43#include <wtf/StackStats.h>
44
45#if PLATFORM(IOS_FAMILY)
46#include "RenderThemeIOS.h"
47#endif
48
49namespace WebCore {
50
51using namespace HTMLNames;
52
53WTF_MAKE_ISO_ALLOCATED_IMPL(RenderTextControlSingleLine);
54WTF_MAKE_ISO_ALLOCATED_IMPL(RenderTextControlInnerBlock);
55
56RenderTextControlSingleLine::RenderTextControlSingleLine(HTMLInputElement& element, RenderStyle&& style)
57 : RenderTextControl(element, WTFMove(style))
58{
59}
60
61RenderTextControlSingleLine::~RenderTextControlSingleLine() = default;
62
63inline HTMLElement* RenderTextControlSingleLine::innerSpinButtonElement() const
64{
65 return inputElement().innerSpinButtonElement();
66}
67
68void RenderTextControlSingleLine::centerRenderer(RenderBox& renderer) const
69{
70 LayoutUnit logicalHeightDiff = renderer.logicalHeight() - contentLogicalHeight();
71 renderer.setLogicalTop(renderer.logicalTop() - logicalHeightDiff / 2);
72}
73
74static void resetOverriddenHeight(RenderBox* box, const RenderObject* ancestor)
75{
76 ASSERT(box != ancestor);
77 if (!box || box->style().logicalHeight().isAuto())
78 return; // Null box or its height was not overridden.
79 box->mutableStyle().setLogicalHeight(Length { Auto });
80 for (RenderObject* renderer = box; renderer != ancestor; renderer = renderer->parent()) {
81 ASSERT(renderer);
82 renderer->setNeedsLayout(MarkOnlyThis);
83 }
84}
85
86void RenderTextControlSingleLine::layout()
87{
88 StackStats::LayoutCheckPoint layoutCheckPoint;
89
90 // FIXME: We should remove the height-related hacks in layout() and
91 // styleDidChange(). We need them because we want to:
92 // - Center the inner elements vertically if the input height is taller than
93 // the intrinsic height of the inner elements.
94 // - Shrink the heights of the inner elements if the input height is smaller
95 // than the intrinsic heights of the inner elements.
96 // - Make the height of the container element equal to the intrinsic height of
97 // the inner elements when the field has a strong password button.
98 //
99 // We don't honor paddings and borders for textfields without decorations
100 // and type=search if the text height is taller than the contentHeight()
101 // because of compability.
102
103 RenderTextControlInnerBlock* innerTextRenderer = innerTextElement()->renderer();
104 RenderBox* innerBlockRenderer = innerBlockElement() ? innerBlockElement()->renderBox() : nullptr;
105 HTMLElement* container = containerElement();
106 RenderBox* containerRenderer = container ? container->renderBox() : nullptr;
107
108 // To ensure consistency between layouts, we need to reset any conditionally overridden height.
109 resetOverriddenHeight(innerTextRenderer, this);
110 resetOverriddenHeight(innerBlockRenderer, this);
111 resetOverriddenHeight(containerRenderer, this);
112
113 // Save the old size of the inner text (if we have one) as we will need to layout the placeholder
114 // and update selection if it changes. One way the size may change is if text decorations are
115 // toggled. For example, hiding and showing the caps lock indicator will cause a size change.
116 LayoutSize oldInnerTextSize;
117 if (innerTextRenderer)
118 oldInnerTextSize = innerTextRenderer->size();
119
120 RenderBlockFlow::layoutBlock(false);
121
122 // Set the text block height
123 LayoutUnit desiredLogicalHeight = textBlockLogicalHeight();
124 LayoutUnit logicalHeightLimit = logicalHeight();
125 LayoutUnit innerTextLogicalHeight;
126 if (innerTextRenderer)
127 innerTextLogicalHeight = innerTextRenderer->logicalHeight();
128 if (innerTextRenderer && innerTextLogicalHeight > logicalHeightLimit) {
129 if (desiredLogicalHeight != innerTextLogicalHeight)
130 setNeedsLayout(MarkOnlyThis);
131
132 innerTextRenderer->mutableStyle().setLogicalHeight(Length(desiredLogicalHeight, Fixed));
133 innerTextRenderer->setNeedsLayout(MarkOnlyThis);
134 if (innerBlockRenderer) {
135 innerBlockRenderer->mutableStyle().setLogicalHeight(Length(desiredLogicalHeight, Fixed));
136 innerBlockRenderer->setNeedsLayout(MarkOnlyThis);
137 }
138 innerTextLogicalHeight = desiredLogicalHeight;
139 }
140 // The container might be taller because of decoration elements.
141 LayoutUnit oldContainerLogicalTop;
142 if (containerRenderer) {
143 containerRenderer->layoutIfNeeded();
144 oldContainerLogicalTop = containerRenderer->logicalTop();
145 LayoutUnit containerLogicalHeight = containerRenderer->logicalHeight();
146 if (inputElement().hasAutoFillStrongPasswordButton() && innerTextRenderer && containerLogicalHeight != innerTextLogicalHeight) {
147 containerRenderer->mutableStyle().setLogicalHeight(Length { innerTextLogicalHeight, Fixed });
148 setNeedsLayout(MarkOnlyThis);
149 } else if (containerLogicalHeight > logicalHeightLimit) {
150 containerRenderer->mutableStyle().setLogicalHeight(Length(logicalHeightLimit, Fixed));
151 setNeedsLayout(MarkOnlyThis);
152 } else if (containerRenderer->logicalHeight() < contentLogicalHeight()) {
153 containerRenderer->mutableStyle().setLogicalHeight(Length(contentLogicalHeight(), Fixed));
154 setNeedsLayout(MarkOnlyThis);
155 } else
156 containerRenderer->mutableStyle().setLogicalHeight(Length(containerLogicalHeight, Fixed));
157 }
158
159 // If we need another layout pass, we have changed one of children's height so we need to relayout them.
160 if (needsLayout())
161 RenderBlockFlow::layoutBlock(true);
162
163 // Fix up the y-position of the container as it may have been flexed when the strong password or strong
164 // confirmation password button wraps to the next line.
165 if (inputElement().hasAutoFillStrongPasswordButton() && containerRenderer)
166 containerRenderer->setLogicalTop(oldContainerLogicalTop);
167
168 // Center the child block in the block progression direction (vertical centering for horizontal text fields).
169 if (!container && innerTextRenderer && innerTextRenderer->height() != contentLogicalHeight())
170 centerRenderer(*innerTextRenderer);
171 else if (container && containerRenderer && containerRenderer->height() != contentLogicalHeight())
172 centerRenderer(*containerRenderer);
173
174 bool innerTextSizeChanged = innerTextRenderer && innerTextRenderer->size() != oldInnerTextSize;
175
176 HTMLElement* placeholderElement = inputElement().placeholderElement();
177 if (RenderBox* placeholderBox = placeholderElement ? placeholderElement->renderBox() : 0) {
178 LayoutSize innerTextSize;
179 if (innerTextRenderer)
180 innerTextSize = innerTextRenderer->size();
181 placeholderBox->mutableStyle().setWidth(Length(innerTextSize.width() - placeholderBox->horizontalBorderAndPaddingExtent(), Fixed));
182 placeholderBox->mutableStyle().setHeight(Length(innerTextSize.height() - placeholderBox->verticalBorderAndPaddingExtent(), Fixed));
183 bool neededLayout = placeholderBox->needsLayout();
184 bool placeholderBoxHadLayout = placeholderBox->everHadLayout();
185 if (innerTextSizeChanged) {
186 // The caps lock indicator was hidden. Layout the placeholder. Its layout does not affect its parent.
187 placeholderBox->setChildNeedsLayout(MarkOnlyThis);
188 }
189 placeholderBox->layoutIfNeeded();
190 LayoutPoint textOffset;
191 if (innerTextRenderer)
192 textOffset = innerTextRenderer->location();
193 if (innerBlockElement() && innerBlockElement()->renderBox())
194 textOffset += toLayoutSize(innerBlockElement()->renderBox()->location());
195 if (containerRenderer)
196 textOffset += toLayoutSize(containerRenderer->location());
197 placeholderBox->setLocation(textOffset);
198
199 if (!placeholderBoxHadLayout && placeholderBox->checkForRepaintDuringLayout()) {
200 // This assumes a shadow tree without floats. If floats are added, the
201 // logic should be shared with RenderBlock::layoutBlockChild.
202 placeholderBox->repaint();
203 }
204 // The placeholder gets layout last, after the parent text control and its other children,
205 // so in order to get the correct overflow from the placeholder we need to recompute it now.
206 if (neededLayout)
207 computeOverflow(clientLogicalBottom());
208 }
209
210#if PLATFORM(IOS_FAMILY)
211 // FIXME: We should not be adjusting styles during layout. <rdar://problem/7675493>
212 if (inputElement().isSearchField())
213 RenderThemeIOS::adjustRoundBorderRadius(mutableStyle(), *this);
214#endif
215 if (innerTextSizeChanged && frame().selection().isFocusedAndActive() && document().focusedElement() == &inputElement()) {
216 // The caps lock indicator was hidden or shown. If it is now visible then it may be occluding
217 // the current selection (say, the caret was after the last character in the text field).
218 // Schedule an update and reveal of the current selection.
219 frame().selection().setNeedsSelectionUpdate(FrameSelection::RevealSelectionAfterUpdate::Forced);
220 }
221}
222
223bool RenderTextControlSingleLine::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
224{
225 if (!RenderTextControl::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction))
226 return false;
227
228 // Say that we hit the inner text element if
229 // - we hit a node inside the inner text element,
230 // - we hit the <input> element (e.g. we're over the border or padding), or
231 // - we hit regions not in any decoration buttons.
232 HTMLElement* container = containerElement();
233 if (result.innerNode()->isDescendantOf(innerTextElement().get()) || result.innerNode() == &inputElement() || (container && container == result.innerNode())) {
234 LayoutPoint pointInParent = locationInContainer.point();
235 if (container && innerBlockElement()) {
236 if (innerBlockElement()->renderBox())
237 pointInParent -= toLayoutSize(innerBlockElement()->renderBox()->location());
238 if (container->renderBox())
239 pointInParent -= toLayoutSize(container->renderBox()->location());
240 }
241 hitInnerTextElement(result, pointInParent, accumulatedOffset);
242 }
243 return true;
244}
245
246void RenderTextControlSingleLine::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
247{
248 RenderTextControl::styleDidChange(diff, oldStyle);
249
250 // We may have set the width and the height in the old style in layout().
251 // Reset them now to avoid getting a spurious layout hint.
252 HTMLElement* innerBlock = innerBlockElement();
253 if (auto* innerBlockRenderer = innerBlock ? innerBlock->renderer() : nullptr) {
254 innerBlockRenderer->mutableStyle().setHeight(Length());
255 innerBlockRenderer->mutableStyle().setWidth(Length());
256 }
257 HTMLElement* container = containerElement();
258 if (auto* containerRenderer = container ? container->renderer() : nullptr) {
259 containerRenderer->mutableStyle().setHeight(Length());
260 containerRenderer->mutableStyle().setWidth(Length());
261 }
262 if (diff == StyleDifference::Layout) {
263 if (auto innerTextRenderer = innerTextElement()->renderer())
264 innerTextRenderer->setNeedsLayout(MarkContainingBlockChain);
265 if (auto* placeholder = inputElement().placeholderElement()) {
266 if (placeholder->renderer())
267 placeholder->renderer()->setNeedsLayout(MarkContainingBlockChain);
268 }
269 }
270 setHasOverflowClip(false);
271}
272
273bool RenderTextControlSingleLine::hasControlClip() const
274{
275 // Apply control clip for text fields with decorations.
276 return !!containerElement();
277}
278
279LayoutRect RenderTextControlSingleLine::controlClipRect(const LayoutPoint& additionalOffset) const
280{
281 ASSERT(hasControlClip());
282 LayoutRect clipRect = contentBoxRect();
283 if (containerElement()->renderBox())
284 clipRect = unionRect(clipRect, containerElement()->renderBox()->frameRect());
285 clipRect.moveBy(additionalOffset);
286 return clipRect;
287}
288
289float RenderTextControlSingleLine::getAverageCharWidth()
290{
291#if !PLATFORM(IOS_FAMILY)
292 // Since Lucida Grande is the default font, we want this to match the width
293 // of MS Shell Dlg, the default font for textareas in Firefox, Safari Win and
294 // IE for some encodings (in IE, the default font is encoding specific).
295 // 901 is the avgCharWidth value in the OS/2 table for MS Shell Dlg.
296 if (style().fontCascade().firstFamily() == "Lucida Grande")
297 return scaleEmToUnits(901);
298#endif
299
300 return RenderTextControl::getAverageCharWidth();
301}
302
303LayoutUnit RenderTextControlSingleLine::preferredContentLogicalWidth(float charWidth) const
304{
305 int factor;
306 bool includesDecoration = inputElement().sizeShouldIncludeDecoration(factor);
307 if (factor <= 0)
308 factor = 20;
309
310 LayoutUnit result = LayoutUnit::fromFloatCeil(charWidth * factor);
311
312 float maxCharWidth = 0.f;
313
314#if !PLATFORM(IOS_FAMILY)
315 const AtomicString& family = style().fontCascade().firstFamily();
316 // Since Lucida Grande is the default font, we want this to match the width
317 // of MS Shell Dlg, the default font for textareas in Firefox, Safari Win and
318 // IE for some encodings (in IE, the default font is encoding specific).
319 // 4027 is the (xMax - xMin) value in the "head" font table for MS Shell Dlg.
320 if (family == "Lucida Grande")
321 maxCharWidth = scaleEmToUnits(4027);
322 else if (style().fontCascade().hasValidAverageCharWidth())
323 maxCharWidth = roundf(style().fontCascade().primaryFont().maxCharWidth());
324#endif
325
326 // For text inputs, IE adds some extra width.
327 if (maxCharWidth > 0.f)
328 result += maxCharWidth - charWidth;
329
330 if (includesDecoration)
331 result += inputElement().decorationWidth();
332
333 return result;
334}
335
336LayoutUnit RenderTextControlSingleLine::computeControlLogicalHeight(LayoutUnit lineHeight, LayoutUnit nonContentHeight) const
337{
338 return lineHeight + nonContentHeight;
339}
340
341void RenderTextControlSingleLine::autoscroll(const IntPoint& position)
342{
343 RenderTextControlInnerBlock* renderer = innerTextElement()->renderer();
344 if (!renderer)
345 return;
346 RenderLayer* layer = renderer->layer();
347 if (layer)
348 layer->autoscroll(position);
349}
350
351int RenderTextControlSingleLine::scrollWidth() const
352{
353 if (innerTextElement())
354 return innerTextElement()->scrollWidth();
355 return RenderBlockFlow::scrollWidth();
356}
357
358int RenderTextControlSingleLine::scrollHeight() const
359{
360 if (innerTextElement())
361 return innerTextElement()->scrollHeight();
362 return RenderBlockFlow::scrollHeight();
363}
364
365int RenderTextControlSingleLine::scrollLeft() const
366{
367 if (innerTextElement())
368 return innerTextElement()->scrollLeft();
369 return RenderBlockFlow::scrollLeft();
370}
371
372int RenderTextControlSingleLine::scrollTop() const
373{
374 if (innerTextElement())
375 return innerTextElement()->scrollTop();
376 return RenderBlockFlow::scrollTop();
377}
378
379void RenderTextControlSingleLine::setScrollLeft(int newLeft, ScrollType, ScrollClamping)
380{
381 if (innerTextElement())
382 innerTextElement()->setScrollLeft(newLeft);
383}
384
385void RenderTextControlSingleLine::setScrollTop(int newTop, ScrollType, ScrollClamping)
386{
387 if (innerTextElement())
388 innerTextElement()->setScrollTop(newTop);
389}
390
391bool RenderTextControlSingleLine::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier, Element** stopElement, RenderBox* startBox, const IntPoint& wheelEventAbsolutePoint)
392{
393 RenderTextControlInnerBlock* renderer = innerTextElement()->renderer();
394 if (!renderer)
395 return false;
396 RenderLayer* layer = renderer->layer();
397 if (layer && layer->scroll(direction, granularity, multiplier))
398 return true;
399 return RenderBlockFlow::scroll(direction, granularity, multiplier, stopElement, startBox, wheelEventAbsolutePoint);
400}
401
402bool RenderTextControlSingleLine::logicalScroll(ScrollLogicalDirection direction, ScrollGranularity granularity, float multiplier, Element** stopElement)
403{
404 RenderLayer* layer = innerTextElement()->renderer()->layer();
405 if (layer && layer->scroll(logicalToPhysical(direction, style().isHorizontalWritingMode(), style().isFlippedBlocksWritingMode()), granularity, multiplier))
406 return true;
407 return RenderBlockFlow::logicalScroll(direction, granularity, multiplier, stopElement);
408}
409
410HTMLInputElement& RenderTextControlSingleLine::inputElement() const
411{
412 return downcast<HTMLInputElement>(RenderTextControl::textFormControlElement());
413}
414
415}
416