2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
22 #include "RenderTextControl.h"
24 #include "CharacterNames.h"
25 #include "CSSStyleSelector.h"
28 #include "EditorClient.h"
30 #include "EventNames.h"
31 #include "FontSelector.h"
33 #include "FrameView.h"
34 #include "HTMLBRElement.h"
35 #include "HTMLInputElement.h"
36 #include "HTMLNames.h"
37 #include "HTMLTextAreaElement.h"
38 #include "HitTestResult.h"
39 #include "LocalizedStrings.h"
40 #include "MouseEvent.h"
41 #include "PlatformKeyboardEvent.h"
42 #include "RenderScrollbar.h"
43 #include "RenderTheme.h"
44 #include "ScrollbarTheme.h"
45 #include "SearchPopupMenu.h"
46 #include "SelectionController.h"
49 #include "TextControlInnerElements.h"
50 #include "TextIterator.h"
51 #include "htmlediting.h"
52 #include "visible_units.h"
59 using namespace HTMLNames
;
61 // Value chosen by observation. This can be tweaked.
62 static const int minColorContrastValue
= 1300;
64 static Color
disabledTextColor(const Color
& textColor
, const Color
& backgroundColor
)
66 // The explicit check for black is an optimization for the 99% case (black on white).
67 // This also means that black on black will turn into grey on black when disabled.
69 if (textColor
.rgb() == Color::black
|| differenceSquared(textColor
, Color::white
) > differenceSquared(backgroundColor
, Color::white
))
70 disabledColor
= textColor
.light();
72 disabledColor
= textColor
.dark();
74 // If there's not very much contrast between the disabled color and the background color,
75 // just leave the text color alone. We don't want to change a good contrast color scheme so that it has really bad contrast.
76 // If the the contrast was already poor, then it doesn't do any good to change it to a different poor contrast color scheme.
77 if (differenceSquared(disabledColor
, backgroundColor
) < minColorContrastValue
)
83 RenderTextControl::RenderTextControl(Node
* node
, bool multiLine
)
86 , m_multiLine(multiLine
)
87 , m_placeholderVisible(false)
89 , m_shouldDrawCapsLockIndicator(false)
91 , m_searchPopupIsVisible(false)
92 , m_searchEventTimer(this, &RenderTextControl::searchEventTimerFired
)
96 RenderTextControl::~RenderTextControl()
99 m_searchPopup
->disconnectClient();
102 if (m_multiLine
&& node())
103 static_cast<HTMLTextAreaElement
*>(node())->rendererWillBeDestroyed();
104 // The children renderers have already been destroyed by destroyLeftoverChildren
106 m_innerBlock
->detach();
107 else if (m_innerText
)
108 m_innerText
->detach();
111 void RenderTextControl::styleDidChange(RenderStyle::Diff diff
, const RenderStyle
* oldStyle
)
113 RenderBlock::styleDidChange(diff
, oldStyle
);
115 // We may have set the width and the height in the old style in layout(). Reset them now to avoid
116 // getting a spurious layout hint.
117 m_innerBlock
->renderer()->style()->setHeight(Length());
118 m_innerBlock
->renderer()->style()->setWidth(Length());
119 m_innerBlock
->renderer()->setStyle(createInnerBlockStyle(style()));
123 RenderBlock
* textBlockRenderer
= static_cast<RenderBlock
*>(m_innerText
->renderer());
124 RefPtr
<RenderStyle
> textBlockStyle
= createInnerTextStyle(style());
125 // We may have set the width and the height in the old style in layout(). Reset them now to avoid
126 // getting a spurious layout hint.
127 textBlockRenderer
->style()->setHeight(Length());
128 textBlockRenderer
->style()->setWidth(Length());
129 textBlockRenderer
->setStyle(textBlockStyle
);
130 for (Node
* n
= m_innerText
->firstChild(); n
; n
= n
->traverseNextNode(m_innerText
.get())) {
132 n
->renderer()->setStyle(textBlockStyle
);
135 if (m_resultsButton
&& m_resultsButton
->renderer())
136 m_resultsButton
->renderer()->setStyle(createResultsButtonStyle(style()));
138 if (m_cancelButton
&& m_cancelButton
->renderer())
139 m_cancelButton
->renderer()->setStyle(createCancelButtonStyle(style()));
141 setHasOverflowClip(false);
142 setReplaced(isInline());
145 PassRefPtr
<RenderStyle
> RenderTextControl::createInnerBlockStyle(const RenderStyle
* startStyle
)
147 RefPtr
<RenderStyle
> innerBlockStyle
= RenderStyle::create();
149 innerBlockStyle
->inheritFrom(startStyle
);
150 innerBlockStyle
->setDisplay(BLOCK
);
151 innerBlockStyle
->setDirection(LTR
);
152 // 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.
153 innerBlockStyle
->setUserModify(READ_ONLY
);
155 return innerBlockStyle
.release();
158 PassRefPtr
<RenderStyle
> RenderTextControl::createInnerTextStyle(const RenderStyle
* startStyle
)
160 HTMLFormControlElement
* element
= static_cast<HTMLFormControlElement
*>(node());
161 bool placeholderShouldBeVisible
= !m_multiLine
&& static_cast<HTMLInputElement
*>(element
)->placeholderShouldBeVisible();
163 RefPtr
<RenderStyle
> textBlockStyle
;
164 if (placeholderShouldBeVisible
) {
165 RenderStyle
* pseudoStyle
= getCachedPseudoStyle(RenderStyle::INPUT_PLACEHOLDER
);
166 textBlockStyle
= RenderStyle::clone(pseudoStyle
);
168 textBlockStyle
= RenderStyle::create();
169 textBlockStyle
->inheritFrom(startStyle
);
172 // The inner block, if present, always has its direction set to LTR,
173 // so we need to inherit the direction from the element.
174 textBlockStyle
->setDirection(style()->direction());
175 textBlockStyle
->setUserModify(element
->isReadOnlyControl() || element
->disabled() ? READ_ONLY
: READ_WRITE_PLAINTEXT_ONLY
);
177 textBlockStyle
->setDisplay(INLINE_BLOCK
);
179 textBlockStyle
->setDisplay(BLOCK
);
182 // Forward overflow properties.
183 textBlockStyle
->setOverflowX(startStyle
->overflowX() == OVISIBLE
? OAUTO
: startStyle
->overflowX());
184 textBlockStyle
->setOverflowY(startStyle
->overflowY() == OVISIBLE
? OAUTO
: startStyle
->overflowY());
186 // Set word wrap property based on wrap attribute.
187 if (!static_cast<HTMLTextAreaElement
*>(element
)->shouldWrapText()) {
188 textBlockStyle
->setWhiteSpace(PRE
);
189 textBlockStyle
->setWordWrap(NormalWordWrap
);
191 textBlockStyle
->setWhiteSpace(PRE_WRAP
);
192 textBlockStyle
->setWordWrap(BreakWordWrap
);
195 textBlockStyle
->setWhiteSpace(PRE
);
196 textBlockStyle
->setWordWrap(NormalWordWrap
);
197 textBlockStyle
->setOverflowX(OHIDDEN
);
198 textBlockStyle
->setOverflowY(OHIDDEN
);
200 // Do not allow line-height to be smaller than our default.
201 if (textBlockStyle
->font().lineSpacing() > lineHeight(true, true))
202 textBlockStyle
->setLineHeight(Length(-100.0f
, Percent
));
206 // We're adding one extra pixel of padding to match WinIE.
207 textBlockStyle
->setPaddingLeft(Length(1, Fixed
));
208 textBlockStyle
->setPaddingRight(Length(1, Fixed
));
210 // We're adding three extra pixels of padding to line textareas up with text fields.
211 textBlockStyle
->setPaddingLeft(Length(3, Fixed
));
212 textBlockStyle
->setPaddingRight(Length(3, Fixed
));
215 // When the placeholder is going to be displayed, temporarily override the text security to be "none".
216 // After this, updateFromElement will immediately update the text displayed.
217 // When the placeholder is no longer visible, updatePlaceholderVisiblity will reset the style,
218 // and the text security mode will be set back to the computed value correctly.
219 if (!m_multiLine
&& static_cast<HTMLInputElement
*>(element
)->placeholderShouldBeVisible())
220 textBlockStyle
->setTextSecurity(TSNONE
);
222 if (!element
->isEnabled())
223 textBlockStyle
->setColor(disabledTextColor(textBlockStyle
->color(), startStyle
->backgroundColor()));
225 return textBlockStyle
.release();
228 PassRefPtr
<RenderStyle
> RenderTextControl::createResultsButtonStyle(const RenderStyle
* startStyle
)
230 ASSERT(!m_multiLine
);
231 HTMLInputElement
* input
= static_cast<HTMLInputElement
*>(node());
232 RefPtr
<RenderStyle
> resultsBlockStyle
;
233 if (input
->maxResults() < 0)
234 resultsBlockStyle
= getCachedPseudoStyle(RenderStyle::SEARCH_DECORATION
);
235 else if (!input
->maxResults())
236 resultsBlockStyle
= getCachedPseudoStyle(RenderStyle::SEARCH_RESULTS_DECORATION
);
238 resultsBlockStyle
= getCachedPseudoStyle(RenderStyle::SEARCH_RESULTS_BUTTON
);
240 if (!resultsBlockStyle
)
241 resultsBlockStyle
= RenderStyle::create();
244 resultsBlockStyle
->inheritFrom(startStyle
);
246 return resultsBlockStyle
.release();
249 PassRefPtr
<RenderStyle
> RenderTextControl::createCancelButtonStyle(const RenderStyle
* startStyle
)
251 RefPtr
<RenderStyle
> cancelBlockStyle
;
253 if (RefPtr
<RenderStyle
> pseudoStyle
= getCachedPseudoStyle(RenderStyle::SEARCH_CANCEL_BUTTON
))
254 // We may be sharing style with another search field, but we must not share the cancel button style.
255 cancelBlockStyle
= RenderStyle::clone(pseudoStyle
.get());
257 cancelBlockStyle
= RenderStyle::create();
260 cancelBlockStyle
->inheritFrom(startStyle
);
262 updateCancelButtonVisibility(cancelBlockStyle
.get());
264 return cancelBlockStyle
.release();
267 void RenderTextControl::createSubtreeIfNeeded()
269 bool isSearchField
= !m_multiLine
&& static_cast<HTMLInputElement
*>(node())->isSearchField();
270 if (isSearchField
&& !m_innerBlock
) {
271 // Create the inner block element
272 m_innerBlock
= new TextControlInnerElement(document(), node());
273 m_innerBlock
->attachInnerElement(node(), createInnerBlockStyle(style()), renderArena());
275 if (isSearchField
&& !m_resultsButton
) {
276 // Create the search results button element
277 m_resultsButton
= new SearchFieldResultsButtonElement(document());
278 m_resultsButton
->attachInnerElement(m_innerBlock
.get(), createResultsButtonStyle(m_innerBlock
->renderer()->style()), renderArena());
281 // Create the text block element
282 // For non-search fields, there is no intermediate m_innerBlock as the shadow node.
283 // m_innerText will be the shadow node in that case.
285 RenderStyle
* parentStyle
= style();
287 parentStyle
= m_innerBlock
->renderer()->style();
288 m_innerText
= new TextControlInnerTextElement(document(), m_innerBlock
? 0 : node());
289 m_innerText
->attachInnerElement(m_innerBlock
? m_innerBlock
.get() : node(), createInnerTextStyle(parentStyle
), renderArena());
291 if (isSearchField
&& !m_cancelButton
) {
292 // Create the cancel button element
293 m_cancelButton
= new SearchFieldCancelButtonElement(document());
294 m_cancelButton
->attachInnerElement(m_innerBlock
.get(), createCancelButtonStyle(m_innerBlock
->renderer()->style()), renderArena());
298 void RenderTextControl::updateFromElement()
300 HTMLFormControlElement
* element
= static_cast<HTMLFormControlElement
*>(node());
302 bool placeholderShouldBeVisible
= !m_multiLine
&& static_cast<HTMLInputElement
*>(element
)->placeholderShouldBeVisible();
303 bool placeholderVisibilityShouldChange
= m_placeholderVisible
!= placeholderShouldBeVisible
;
304 m_placeholderVisible
= placeholderShouldBeVisible
;
306 createSubtreeIfNeeded();
308 if (m_cancelButton
&& m_cancelButton
->renderer())
309 updateCancelButtonVisibility(m_cancelButton
->renderer()->style());
311 m_innerText
->renderer()->style()->setUserModify(element
->isReadOnlyControl() || element
->disabled() ? READ_ONLY
: READ_WRITE_PLAINTEXT_ONLY
);
313 if (m_placeholderVisible
) {
315 m_innerText
->setInnerText(static_cast<HTMLInputElement
*>(element
)->getAttribute(placeholderAttr
), ec
);
316 } else if (!element
->valueMatchesRenderer() || m_multiLine
|| placeholderVisibilityShouldChange
) {
319 value
= static_cast<HTMLTextAreaElement
*>(element
)->value();
321 value
= static_cast<HTMLInputElement
*>(element
)->value();
325 value
= value
.replace('\\', backslashAsCurrencySymbol());
326 if (value
!= text() || !m_innerText
->hasChildNodes()) {
327 if (value
!= text()) {
328 if (Frame
* frame
= document()->frame())
329 frame
->editor()->clearUndoRedoOperations();
331 ExceptionCode ec
= 0;
332 m_innerText
->setInnerText(value
, ec
);
333 if (value
.endsWith("\n") || value
.endsWith("\r"))
334 m_innerText
->appendChild(new HTMLBRElement(document()), ec
);
336 m_userEdited
= false;
338 element
->setValueMatchesRenderer();
341 if (m_searchPopupIsVisible
)
342 m_searchPopup
->updateFromElement();
345 void RenderTextControl::setUserEdited(bool isUserEdited
)
347 m_userEdited
= isUserEdited
;
348 document()->setIgnoreAutofocus(isUserEdited
);
351 int RenderTextControl::selectionStart()
353 Frame
* frame
= document()->frame();
356 return indexForVisiblePosition(frame
->selection()->start());
359 int RenderTextControl::selectionEnd()
361 Frame
* frame
= document()->frame();
364 return indexForVisiblePosition(frame
->selection()->end());
367 void RenderTextControl::setSelectionStart(int start
)
369 setSelectionRange(start
, max(start
, selectionEnd()));
372 void RenderTextControl::setSelectionEnd(int end
)
374 setSelectionRange(min(end
, selectionStart()), end
);
377 void RenderTextControl::select()
379 setSelectionRange(0, text().length());
382 void RenderTextControl::setSelectionRange(int start
, int end
)
385 start
= min(max(start
, 0), end
);
387 document()->updateLayout();
389 if (style()->visibility() == HIDDEN
|| !m_innerText
|| !m_innerText
->renderer() || !m_innerText
->renderer()->height()) {
391 static_cast<HTMLTextAreaElement
*>(node())->cacheSelection(start
, end
);
393 static_cast<HTMLInputElement
*>(node())->cacheSelection(start
, end
);
396 VisiblePosition startPosition
= visiblePositionForIndex(start
);
397 VisiblePosition endPosition
;
399 endPosition
= startPosition
;
401 endPosition
= visiblePositionForIndex(end
);
403 ASSERT(startPosition
.isNotNull() && endPosition
.isNotNull());
404 ASSERT(startPosition
.deepEquivalent().node()->shadowAncestorNode() == node() && endPosition
.deepEquivalent().node()->shadowAncestorNode() == node());
406 Selection newSelection
= Selection(startPosition
, endPosition
);
408 if (Frame
* frame
= document()->frame())
409 frame
->selection()->setSelection(newSelection
);
411 // FIXME: Granularity is stored separately on the frame, but also in the selection controller.
412 // The granularity in the selection controller should be used, and then this line of code would not be needed.
413 if (Frame
* frame
= document()->frame())
414 frame
->setSelectionGranularity(CharacterGranularity
);
417 Selection
RenderTextControl::selection(int start
, int end
) const
419 return Selection(VisiblePosition(m_innerText
.get(), start
, VP_DEFAULT_AFFINITY
),
420 VisiblePosition(m_innerText
.get(), end
, VP_DEFAULT_AFFINITY
));
423 VisiblePosition
RenderTextControl::visiblePositionForIndex(int index
)
426 return VisiblePosition(m_innerText
.get(), 0, DOWNSTREAM
);
427 ExceptionCode ec
= 0;
428 RefPtr
<Range
> range
= Range::create(document());
429 range
->selectNodeContents(m_innerText
.get(), ec
);
430 CharacterIterator
it(range
.get());
431 it
.advance(index
- 1);
432 return VisiblePosition(it
.range()->endContainer(ec
), it
.range()->endOffset(ec
), UPSTREAM
);
435 int RenderTextControl::indexForVisiblePosition(const VisiblePosition
& pos
)
437 Position indexPosition
= pos
.deepEquivalent();
438 if (!indexPosition
.node() || indexPosition
.node()->rootEditableElement() != m_innerText
)
440 ExceptionCode ec
= 0;
441 RefPtr
<Range
> range
= Range::create(document());
442 range
->setStart(m_innerText
.get(), 0, ec
);
443 range
->setEnd(indexPosition
.node(), indexPosition
.offset(), ec
);
444 return TextIterator::rangeLength(range
.get());
447 void RenderTextControl::updateCancelButtonVisibility(RenderStyle
* style
)
449 ASSERT(!m_multiLine
);
450 HTMLInputElement
* input
= static_cast<HTMLInputElement
*>(node());
451 if (input
->value().isEmpty())
452 style
->setVisibility(HIDDEN
);
454 style
->setVisibility(VISIBLE
);
457 void RenderTextControl::subtreeHasChanged()
459 bool wasDirty
= m_dirty
;
462 HTMLFormControlElement
* element
= static_cast<HTMLFormControlElement
*>(node());
464 element
->setValueMatchesRenderer(false);
465 if (element
->focused())
466 if (Frame
* frame
= document()->frame())
467 frame
->textDidChangeInTextArea(element
);
469 HTMLInputElement
* input
= static_cast<HTMLInputElement
*>(element
);
470 input
->setValueFromRenderer(input
->constrainValue(text()));
471 if (m_cancelButton
&& m_cancelButton
->renderer())
472 updateCancelButtonVisibility(m_cancelButton
->renderer()->style());
474 // If the incremental attribute is set, then dispatch the search event
475 if (!input
->getAttribute(incrementalAttr
).isNull())
476 startSearchEventTimer();
479 if (input
->focused())
480 if (Frame
* frame
= document()->frame())
481 frame
->textFieldDidBeginEditing(input
);
483 if (input
->focused())
484 if (Frame
* frame
= document()->frame())
485 frame
->textDidChangeInTextField(input
);
489 String
RenderTextControl::finishText(Vector
<UChar
>& result
) const
491 // Remove one trailing newline; there's always one that's collapsed out by rendering.
492 size_t size
= result
.size();
493 if (size
&& result
[size
- 1] == '\n')
494 result
.shrink(--size
);
496 // Convert backslash to currency symbol.
497 UChar symbol
= backslashAsCurrencySymbol();
498 if (symbol
!= '\\') {
499 for (size_t i
= 0; i
< size
; ++i
) {
500 if (result
[i
] == '\\')
505 return String::adopt(result
);
508 HTMLElement
* RenderTextControl::innerTextElement() const
510 return m_innerText
.get();
513 String
RenderTextControl::text()
518 Frame
* frame
= document()->frame();
519 Text
* compositionNode
= frame
? frame
->editor()->compositionNode() : 0;
521 Vector
<UChar
> result
;
523 for (Node
* n
= m_innerText
.get(); n
; n
= n
->traverseNextNode(m_innerText
.get())) {
524 if (n
->isTextNode()) {
525 Text
* text
= static_cast<Text
*>(n
);
526 String data
= text
->data();
527 unsigned length
= data
.length();
528 if (text
!= compositionNode
)
529 result
.append(data
.characters(), length
);
531 unsigned compositionStart
= min(frame
->editor()->compositionStart(), length
);
532 unsigned compositionEnd
= min(max(compositionStart
, frame
->editor()->compositionEnd()), length
);
533 result
.append(data
.characters(), compositionStart
);
534 result
.append(data
.characters() + compositionEnd
, length
- compositionEnd
);
539 return finishText(result
);
542 static void getNextSoftBreak(RootInlineBox
*& line
, Node
*& breakNode
, unsigned& breakOffset
)
545 for (; line
; line
= next
) {
546 next
= line
->nextRootBox();
547 if (next
&& !line
->endsWithBreak()) {
548 ASSERT(line
->lineBreakObj());
549 breakNode
= line
->lineBreakObj()->node();
550 breakOffset
= line
->lineBreakPos();
558 String
RenderTextControl::textWithHardLineBreaks()
562 Node
* firstChild
= m_innerText
->firstChild();
566 document()->updateLayout();
568 RenderObject
* renderer
= firstChild
->renderer();
572 InlineBox
* box
= renderer
->isText() ? static_cast<RenderText
*>(renderer
)->firstTextBox() : renderer
->inlineBoxWrapper();
576 Frame
* frame
= document()->frame();
577 Text
* compositionNode
= frame
? frame
->editor()->compositionNode() : 0;
580 unsigned breakOffset
;
581 RootInlineBox
* line
= box
->root();
582 getNextSoftBreak(line
, breakNode
, breakOffset
);
584 Vector
<UChar
> result
;
586 for (Node
* n
= firstChild
; n
; n
= n
->traverseNextNode(m_innerText
.get())) {
587 if (n
->hasTagName(brTag
))
588 result
.append(&newlineCharacter
, 1);
589 else if (n
->isTextNode()) {
590 Text
* text
= static_cast<Text
*>(n
);
591 String data
= text
->data();
592 unsigned length
= data
.length();
593 unsigned compositionStart
= (text
== compositionNode
)
594 ? min(frame
->editor()->compositionStart(), length
) : 0;
595 unsigned compositionEnd
= (text
== compositionNode
)
596 ? min(max(compositionStart
, frame
->editor()->compositionEnd()), length
) : 0;
597 unsigned position
= 0;
598 while (breakNode
== n
&& breakOffset
< compositionStart
) {
599 result
.append(data
.characters() + position
, breakOffset
- position
);
600 position
= breakOffset
;
601 result
.append(&newlineCharacter
, 1);
602 getNextSoftBreak(line
, breakNode
, breakOffset
);
604 result
.append(data
.characters() + position
, compositionStart
- position
);
605 position
= compositionEnd
;
606 while (breakNode
== n
&& breakOffset
<= length
) {
607 if (breakOffset
> position
) {
608 result
.append(data
.characters() + position
, breakOffset
- position
);
609 position
= breakOffset
;
610 result
.append(&newlineCharacter
, 1);
612 getNextSoftBreak(line
, breakNode
, breakOffset
);
614 result
.append(data
.characters() + position
, length
- position
);
616 while (breakNode
== n
)
617 getNextSoftBreak(line
, breakNode
, breakOffset
);
620 return finishText(result
);
623 void RenderTextControl::calcHeight()
627 rows
= static_cast<HTMLTextAreaElement
*>(node())->rows();
629 int line
= m_innerText
->renderer()->lineHeight(true, true);
630 int toAdd
= paddingTop() + paddingBottom() + borderTop() + borderBottom();
632 int innerToAdd
= m_innerText
->renderer()->borderTop() + m_innerText
->renderer()->borderBottom() +
633 m_innerText
->renderer()->paddingTop() + m_innerText
->renderer()->paddingBottom() +
634 m_innerText
->renderer()->marginTop() + m_innerText
->renderer()->marginBottom();
636 if (m_resultsButton
&& m_resultsButton
->renderer()) {
637 static_cast<RenderBlock
*>(m_resultsButton
->renderer())->calcHeight();
638 innerToAdd
= max(innerToAdd
,
639 m_resultsButton
->renderer()->borderTop() + m_resultsButton
->renderer()->borderBottom() +
640 m_resultsButton
->renderer()->paddingTop() + m_resultsButton
->renderer()->paddingBottom() +
641 m_resultsButton
->renderer()->marginTop() + m_resultsButton
->renderer()->marginBottom());
642 line
= max(line
, m_resultsButton
->renderer()->height());
644 if (m_cancelButton
&& m_cancelButton
->renderer()) {
645 static_cast<RenderBlock
*>(m_cancelButton
->renderer())->calcHeight();
646 innerToAdd
= max(innerToAdd
,
647 m_cancelButton
->renderer()->borderTop() + m_cancelButton
->renderer()->borderBottom() +
648 m_cancelButton
->renderer()->paddingTop() + m_cancelButton
->renderer()->paddingBottom() +
649 m_cancelButton
->renderer()->marginTop() + m_cancelButton
->renderer()->marginBottom());
650 line
= max(line
, m_cancelButton
->renderer()->height());
654 // FIXME: We should get the size of the scrollbar from the RenderTheme instead.
655 int scrollbarSize
= 0;
656 // We are able to have a horizontal scrollbar if the overflow style is scroll, or if its auto and there's no word wrap.
657 if (m_innerText
->renderer()->style()->overflowX() == OSCROLL
|| (m_innerText
->renderer()->style()->overflowX() == OAUTO
&& m_innerText
->renderer()->style()->wordWrap() == NormalWordWrap
))
658 scrollbarSize
= ScrollbarTheme::nativeTheme()->scrollbarThickness();
660 m_height
= line
* rows
+ toAdd
+ scrollbarSize
;
662 RenderBlock::calcHeight();
665 int RenderTextControl::baselinePosition(bool b
, bool isRootLineBox
) const
668 return height() + marginTop() + marginBottom();
669 return RenderBlock::baselinePosition(b
, isRootLineBox
);
672 bool RenderTextControl::nodeAtPoint(const HitTestRequest
& request
, HitTestResult
& result
, int x
, int y
, int tx
, int ty
, HitTestAction hitTestAction
)
674 // If we're within the text control, we want to act as if we've hit the inner text block element, in case the point
675 // was on the control but not on the inner element (see Radar 4617841).
677 // In a search field, we want to act as if we've hit the results block if we're to the left of the inner text block,
678 // and act as if we've hit the close block if we're to the right of the inner text block.
680 if (RenderBlock::nodeAtPoint(request
, result
, x
, y
, tx
, ty
, hitTestAction
) &&
681 (result
.innerNode() == element() || result
.innerNode() == m_innerBlock
)) {
682 IntPoint localPoint
= IntPoint(x
- tx
- m_x
, y
- ty
- m_y
);
684 int textLeft
= tx
+ m_x
+ m_innerBlock
->renderer()->xPos() + m_innerText
->renderer()->xPos();
685 int textRight
= textLeft
+ m_innerText
->renderer()->width();
686 if (m_resultsButton
&& m_resultsButton
->renderer() && x
< textLeft
) {
687 result
.setInnerNode(m_resultsButton
.get());
688 result
.setLocalPoint(IntPoint(localPoint
.x() - m_innerText
->renderer()->xPos() - m_innerBlock
->renderer()->xPos() - m_resultsButton
->renderer()->xPos(),
689 localPoint
.y() - m_innerText
->renderer()->yPos() - m_innerBlock
->renderer()->yPos() - m_resultsButton
->renderer()->yPos()));
692 if (m_cancelButton
&& m_cancelButton
->renderer() && x
> textRight
) {
693 result
.setInnerNode(m_cancelButton
.get());
694 result
.setLocalPoint(IntPoint(localPoint
.x() - m_innerText
->renderer()->xPos() - m_innerBlock
->renderer()->xPos() - m_cancelButton
->renderer()->xPos(),
695 localPoint
.y() - m_innerText
->renderer()->yPos() - m_innerBlock
->renderer()->yPos() - m_cancelButton
->renderer()->yPos()));
700 // Hit the inner text block.
701 result
.setInnerNode(m_innerText
.get());
702 result
.setLocalPoint(IntPoint(localPoint
.x() - m_innerText
->renderer()->xPos() - (m_innerBlock
.get() ? m_innerBlock
->renderer()->xPos() : 0),
703 localPoint
.y() - m_innerText
->renderer()->yPos() - (m_innerBlock
.get() ? m_innerBlock
->renderer()->yPos() : 0)));
711 IntRect
RenderTextControl::controlClipRect(int tx
, int ty
) const
713 IntRect clipRect
= contentBox();
714 clipRect
.move(tx
, ty
);
718 void RenderTextControl::layout()
720 int oldHeight
= m_height
;
722 bool relayoutChildren
= oldHeight
!= m_height
;
724 // Set the text block's height
725 int textBlockHeight
= m_height
- paddingTop() - paddingBottom() - borderTop() - borderBottom();
726 int currentTextBlockHeight
= m_innerText
->renderer()->height();
727 if (m_multiLine
|| m_innerBlock
|| currentTextBlockHeight
> m_height
) {
728 if (textBlockHeight
!= currentTextBlockHeight
)
729 relayoutChildren
= true;
730 m_innerText
->renderer()->style()->setHeight(Length(textBlockHeight
, Fixed
));
733 if (textBlockHeight
!= m_innerBlock
->renderer()->height())
734 relayoutChildren
= true;
735 m_innerBlock
->renderer()->style()->setHeight(Length(textBlockHeight
, Fixed
));
738 int oldWidth
= m_width
;
740 if (oldWidth
!= m_width
)
741 relayoutChildren
= true;
743 int searchExtrasWidth
= 0;
744 if (m_resultsButton
&& m_resultsButton
->renderer()) {
745 m_resultsButton
->renderer()->calcWidth();
746 searchExtrasWidth
+= m_resultsButton
->renderer()->width();
748 if (m_cancelButton
&& m_cancelButton
->renderer()) {
749 m_cancelButton
->renderer()->calcWidth();
750 searchExtrasWidth
+= m_cancelButton
->renderer()->width();
753 // Set the text block's width
754 int textBlockWidth
= m_width
- paddingLeft() - paddingRight() - borderLeft() - borderRight() -
755 m_innerText
->renderer()->paddingLeft() - m_innerText
->renderer()->paddingRight() - searchExtrasWidth
;
756 if (textBlockWidth
!= m_innerText
->renderer()->width())
757 relayoutChildren
= true;
758 m_innerText
->renderer()->style()->setWidth(Length(textBlockWidth
, Fixed
));
760 int innerBlockWidth
= m_width
- paddingLeft() - paddingRight() - borderLeft() - borderRight();
761 if (innerBlockWidth
!= m_innerBlock
->renderer()->width())
762 relayoutChildren
= true;
763 m_innerBlock
->renderer()->style()->setWidth(Length(innerBlockWidth
, Fixed
));
766 RenderBlock::layoutBlock(relayoutChildren
);
768 // For text fields, center the inner text vertically
769 // Don't do this for search fields, since we don't honor height for them
771 currentTextBlockHeight
= m_innerText
->renderer()->height();
772 if (!m_innerBlock
&& currentTextBlockHeight
< m_height
)
773 m_innerText
->renderer()->setPos(m_innerText
->renderer()->xPos(), (m_height
- currentTextBlockHeight
) / 2);
777 void RenderTextControl::paint(PaintInfo
& paintInfo
, int tx
, int ty
)
779 RenderBlock::paint(paintInfo
, tx
, ty
);
780 if (paintInfo
.phase
== PaintPhaseBlockBackground
&& m_shouldDrawCapsLockIndicator
)
781 theme()->paintCapsLockIndicator(this, paintInfo
, absoluteContentBox());
784 void RenderTextControl::calcPrefWidths()
786 ASSERT(prefWidthsDirty());
791 if (style()->width().isFixed() && style()->width().value() > 0)
792 m_minPrefWidth
= m_maxPrefWidth
= calcContentBoxWidth(style()->width().value());
794 // Figure out how big a text control needs to be for a given number of characters
795 // (using "0" as the nominal character).
796 const UChar ch
= '0';
797 float charWidth
= style()->font().floatWidth(TextRun(&ch
, 1, false, 0, 0, false, false, false));
799 int scrollbarSize
= 0;
801 factor
= static_cast<HTMLTextAreaElement
*>(node())->cols();
802 // FIXME: We should get the size of the scrollbar from the RenderTheme instead.
803 if (m_innerText
->renderer()->style()->overflowY() != OHIDDEN
)
804 scrollbarSize
= ScrollbarTheme::nativeTheme()->scrollbarThickness();
806 factor
= static_cast<HTMLInputElement
*>(node())->size();
810 m_maxPrefWidth
= static_cast<int>(ceilf(charWidth
* factor
)) + scrollbarSize
+
811 m_innerText
->renderer()->paddingLeft() + m_innerText
->renderer()->paddingRight();
813 if (m_resultsButton
&& m_resultsButton
->renderer())
814 m_maxPrefWidth
+= m_resultsButton
->renderer()->borderLeft() + m_resultsButton
->renderer()->borderRight() +
815 m_resultsButton
->renderer()->paddingLeft() + m_resultsButton
->renderer()->paddingRight();
816 if (m_cancelButton
&& m_cancelButton
->renderer())
817 m_maxPrefWidth
+= m_cancelButton
->renderer()->borderLeft() + m_cancelButton
->renderer()->borderRight() +
818 m_cancelButton
->renderer()->paddingLeft() + m_cancelButton
->renderer()->paddingRight();
821 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
822 m_maxPrefWidth
= max(m_maxPrefWidth
, calcContentBoxWidth(style()->minWidth().value()));
823 m_minPrefWidth
= max(m_minPrefWidth
, calcContentBoxWidth(style()->minWidth().value()));
824 } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
827 m_minPrefWidth
= m_maxPrefWidth
;
829 if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength
) {
830 m_maxPrefWidth
= min(m_maxPrefWidth
, calcContentBoxWidth(style()->maxWidth().value()));
831 m_minPrefWidth
= min(m_minPrefWidth
, calcContentBoxWidth(style()->maxWidth().value()));
834 int toAdd
= paddingLeft() + paddingRight() + borderLeft() + borderRight();
836 m_minPrefWidth
+= toAdd
;
837 m_maxPrefWidth
+= toAdd
;
839 setPrefWidthsDirty(false);
842 void RenderTextControl::forwardEvent(Event
* evt
)
844 if (evt
->type() == eventNames().blurEvent
) {
845 RenderObject
* innerRenderer
= m_innerText
->renderer();
847 RenderLayer
* innerLayer
= innerRenderer
->layer();
848 if (innerLayer
&& !m_multiLine
)
849 innerLayer
->scrollToOffset(style()->direction() == RTL
? innerLayer
->scrollWidth() : 0, 0);
851 capsLockStateMayHaveChanged();
852 } else if (evt
->type() == eventNames().focusEvent
)
853 capsLockStateMayHaveChanged();
855 if (evt
->isMouseEvent() && m_resultsButton
&& static_cast<MouseEvent
*>(evt
)->x() < m_innerText
->renderer()->absoluteBoundingBoxRect().x())
856 m_resultsButton
->defaultEventHandler(evt
);
857 else if (evt
->isMouseEvent() && m_cancelButton
&& static_cast<MouseEvent
*>(evt
)->x() > m_innerText
->renderer()->absoluteBoundingBoxRect().right())
858 m_cancelButton
->defaultEventHandler(evt
);
860 m_innerText
->defaultEventHandler(evt
);
864 void RenderTextControl::selectionChanged(bool userTriggered
)
866 HTMLFormControlElement
* element
= static_cast<HTMLFormControlElement
*>(node());
868 static_cast<HTMLTextAreaElement
*>(element
)->cacheSelection(selectionStart(), selectionEnd());
870 static_cast<HTMLInputElement
*>(element
)->cacheSelection(selectionStart(), selectionEnd());
871 if (Frame
* frame
= document()->frame())
872 if (frame
->selection()->isRange() && userTriggered
)
873 element
->dispatchEventForType(eventNames().selectEvent
, true, false);
876 void RenderTextControl::autoscroll()
878 RenderLayer
* layer
= m_innerText
->renderer()->layer();
883 int RenderTextControl::scrollWidth() const
886 return m_innerText
->scrollWidth();
887 return RenderBlock::scrollWidth();
890 int RenderTextControl::scrollHeight() const
893 return m_innerText
->scrollHeight();
894 return RenderBlock::scrollHeight();
897 int RenderTextControl::scrollLeft() const
900 return m_innerText
->scrollLeft();
901 return RenderBlock::scrollLeft();
904 int RenderTextControl::scrollTop() const
907 return m_innerText
->scrollTop();
908 return RenderBlock::scrollTop();
911 void RenderTextControl::setScrollLeft(int newLeft
)
914 m_innerText
->setScrollLeft(newLeft
);
917 void RenderTextControl::setScrollTop(int newTop
)
920 m_innerText
->setScrollTop(newTop
);
923 const AtomicString
& RenderTextControl::autosaveName() const
925 return static_cast<Element
*>(node())->getAttribute(autosaveAttr
);
928 void RenderTextControl::addSearchResult()
930 ASSERT(!m_multiLine
);
932 HTMLInputElement
* input
= static_cast<HTMLInputElement
*>(node());
933 if (input
->maxResults() <= 0)
936 String value
= input
->value();
940 Settings
* settings
= document()->settings();
941 if (!settings
|| settings
->privateBrowsingEnabled())
944 int size
= static_cast<int>(m_recentSearches
.size());
945 for (int i
= size
- 1; i
>= 0; --i
)
946 if (m_recentSearches
[i
] == value
)
947 m_recentSearches
.remove(i
);
949 m_recentSearches
.insert(0, value
);
950 while (static_cast<int>(m_recentSearches
.size()) > input
->maxResults())
951 m_recentSearches
.removeLast();
953 const AtomicString
& name
= autosaveName();
955 m_searchPopup
= SearchPopupMenu::create(this);
956 m_searchPopup
->saveRecentSearches(name
, m_recentSearches
);
959 void RenderTextControl::showPopup()
961 if (m_searchPopupIsVisible
)
965 m_searchPopup
= SearchPopupMenu::create(this);
967 if (!m_searchPopup
->enabled())
970 m_searchPopupIsVisible
= true;
972 const AtomicString
& name
= autosaveName();
973 m_searchPopup
->loadRecentSearches(name
, m_recentSearches
);
975 // Trim the recent searches list if the maximum size has changed since we last saved.
976 HTMLInputElement
* input
= static_cast<HTMLInputElement
*>(node());
977 if (static_cast<int>(m_recentSearches
.size()) > input
->maxResults()) {
979 m_recentSearches
.removeLast();
980 while (static_cast<int>(m_recentSearches
.size()) > input
->maxResults());
981 m_searchPopup
->saveRecentSearches(name
, m_recentSearches
);
984 m_searchPopup
->show(absoluteBoundingBoxRect(), document()->view(), -1);
987 void RenderTextControl::hidePopup()
990 m_searchPopup
->hide();
991 m_searchPopupIsVisible
= false;
994 void RenderTextControl::valueChanged(unsigned listIndex
, bool fireEvents
)
996 ASSERT(static_cast<int>(listIndex
) < listSize());
997 HTMLInputElement
* input
= static_cast<HTMLInputElement
*>(node());
998 if (static_cast<int>(listIndex
) == (listSize() - 1)) {
1000 m_recentSearches
.clear();
1001 const AtomicString
& name
= autosaveName();
1002 if (!name
.isEmpty()) {
1004 m_searchPopup
= SearchPopupMenu::create(this);
1005 m_searchPopup
->saveRecentSearches(name
, m_recentSearches
);
1009 input
->setValue(itemText(listIndex
));
1016 String
RenderTextControl::itemText(unsigned listIndex
) const
1018 int size
= listSize();
1021 return searchMenuNoRecentSearchesText();
1024 return searchMenuRecentSearchesText();
1025 if (itemIsSeparator(listIndex
))
1027 if (static_cast<int>(listIndex
) == (size
- 1))
1028 return searchMenuClearRecentSearchesText();
1029 return m_recentSearches
[listIndex
- 1];
1032 bool RenderTextControl::itemIsEnabled(unsigned listIndex
) const
1034 if (!listIndex
|| itemIsSeparator(listIndex
))
1039 PopupMenuStyle
RenderTextControl::itemStyle(unsigned listIndex
) const
1044 PopupMenuStyle
RenderTextControl::menuStyle() const
1046 return PopupMenuStyle(style()->color(), style()->backgroundColor(), style()->font(), style()->visibility() == VISIBLE
);
1049 HostWindow
* RenderTextControl::hostWindow() const
1051 return document()->view()->hostWindow();
1054 PassRefPtr
<Scrollbar
> RenderTextControl::createScrollbar(ScrollbarClient
* client
, ScrollbarOrientation orientation
, ScrollbarControlSize controlSize
)
1056 RefPtr
<Scrollbar
> widget
;
1057 bool hasCustomScrollbarStyle
= style()->hasPseudoStyle(RenderStyle::SCROLLBAR
);
1058 if (hasCustomScrollbarStyle
)
1059 widget
= RenderScrollbar::createCustomScrollbar(client
, orientation
, this);
1061 widget
= Scrollbar::createNativeScrollbar(client
, orientation
, controlSize
);
1062 return widget
.release();
1065 int RenderTextControl::clientInsetLeft() const
1067 // Inset the menu by the radius of the cap on the left so that
1068 // it only runs along the straight part of the bezel.
1069 return height() / 2;
1072 int RenderTextControl::clientInsetRight() const
1074 // Inset the menu by the radius of the cap on the right so that
1075 // it only runs along the straight part of the bezel (unless it needs
1077 return height() / 2;
1080 int RenderTextControl::clientPaddingLeft() const
1082 int padding
= paddingLeft();
1083 if (m_resultsButton
->renderer())
1084 padding
+= m_resultsButton
->renderer()->width();
1088 int RenderTextControl::clientPaddingRight() const
1090 int padding
= paddingRight();
1091 if (m_cancelButton
->renderer())
1092 padding
+= m_cancelButton
->renderer()->width();
1096 int RenderTextControl::listSize() const
1098 // If there are no recent searches, then our menu will have 1 "No recent searches" item.
1099 if (!m_recentSearches
.size())
1101 // Otherwise, leave room in the menu for a header, a separator, and the "Clear recent searches" item.
1102 return m_recentSearches
.size() + 3;
1105 int RenderTextControl::selectedIndex() const
1110 bool RenderTextControl::itemIsSeparator(unsigned listIndex
) const
1112 // The separator will be the second to last item in our list.
1113 return static_cast<int>(listIndex
) == (listSize() - 2);
1116 bool RenderTextControl::itemIsLabel(unsigned listIndex
) const
1118 return listIndex
== 0;
1121 bool RenderTextControl::itemIsSelected(unsigned listIndex
) const
1126 void RenderTextControl::setTextFromItem(unsigned listIndex
)
1128 static_cast<HTMLInputElement
*>(node())->setValue(itemText(listIndex
));
1131 bool RenderTextControl::scroll(ScrollDirection direction
, ScrollGranularity granularity
, float multiplier
)
1133 RenderLayer
* layer
= m_innerText
->renderer()->layer();
1134 if (layer
&& layer
->scroll(direction
, granularity
, multiplier
))
1136 return RenderObject::scroll(direction
, granularity
, multiplier
);
1139 void RenderTextControl::searchEventTimerFired(Timer
<RenderTextControl
>*)
1141 static_cast<HTMLInputElement
*>(node())->onSearch();
1144 void RenderTextControl::stopSearchEventTimer()
1146 m_searchEventTimer
.stop();
1149 void RenderTextControl::startSearchEventTimer()
1151 unsigned length
= text().length();
1153 // If there's no text, fire the event right away.
1155 m_searchEventTimer
.stop();
1156 static_cast<HTMLInputElement
*>(node())->onSearch();
1160 // After typing the first key, we wait 0.5 seconds.
1161 // After the second key, 0.4 seconds, then 0.3, then 0.2 from then on.
1162 m_searchEventTimer
.startOneShot(max(0.2, 0.6 - 0.1 * length
));
1165 bool RenderTextControl::isScrollable() const
1167 if (m_innerText
&& m_innerText
->renderer()->isScrollable())
1169 return RenderObject::isScrollable();
1172 FontSelector
* RenderTextControl::fontSelector() const
1174 return document()->styleSelector()->fontSelector();
1177 void RenderTextControl::updatePlaceholderVisibility()
1179 RenderStyle
* parentStyle
= m_innerBlock
? m_innerBlock
->renderer()->style() : style();
1180 RefPtr
<RenderStyle
> textBlockStyle
= createInnerTextStyle(parentStyle
);
1181 m_innerText
->renderer()->setStyle(textBlockStyle
);
1182 for (Node
* n
= m_innerText
->firstChild(); n
; n
= n
->traverseNextNode(m_innerText
.get())) {
1184 n
->renderer()->setStyle(textBlockStyle
);
1186 updateFromElement();
1189 void RenderTextControl::capsLockStateMayHaveChanged()
1191 // Only draw the caps lock indicator if these things are true:
1192 // 1) The field is a password field
1193 // 2) The frame is active
1194 // 3) The element is focused
1195 // 4) The caps lock is on
1197 bool shouldDrawCapsLockIndicator
= false;
1198 if (Node
* n
= node())
1199 if (Document
* d
= document())
1200 if (Frame
* f
= d
->frame())
1201 shouldDrawCapsLockIndicator
= !m_multiLine
&& static_cast<HTMLInputElement
*>(n
)->inputType() == HTMLInputElement::PASSWORD
&&
1202 f
->selection()->isFocusedAndActive() && d
->focusedNode() == n
&& PlatformKeyboardEvent::currentCapsLockState();
1204 if (shouldDrawCapsLockIndicator
!= m_shouldDrawCapsLockIndicator
) {
1205 m_shouldDrawCapsLockIndicator
= shouldDrawCapsLockIndicator
;
1210 } // namespace WebCore