1 /**
2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
3 * (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 *
20 */
21
22 #include "config.h"
23 #include "RenderTextControl.h"
24
25 #include "AXObjectCache.h"
26 #include "CharacterNames.h"
27 #include "Editor.h"
28 #include "Event.h"
29 #include "EventNames.h"
30 #include "Frame.h"
31 #include "HTMLBRElement.h"
32 #include "HTMLNames.h"
33 #include "HitTestResult.h"
34 #include "RenderLayer.h"
35 #include "RenderText.h"
36 #include "ScrollbarTheme.h"
37 #include "SelectionController.h"
38 #include "Text.h"
39 #include "TextControlInnerElements.h"
40 #include "TextIterator.h"
41
42 using namespace std;
43
44 namespace WebCore {
45
46 using namespace HTMLNames;
47
48 // Value chosen by observation. This can be tweaked.
49 static const int minColorContrastValue = 1300;
50
disabledTextColor(const Color & textColor,const Color & backgroundColor)51 static Color disabledTextColor(const Color& textColor, const Color& backgroundColor)
52 {
53 // The explicit check for black is an optimization for the 99% case (black on white).
54 // This also means that black on black will turn into grey on black when disabled.
55 Color disabledColor;
56 if (textColor.rgb() == Color::black || differenceSquared(textColor, Color::white) > differenceSquared(backgroundColor, Color::white))
57 disabledColor = textColor.light();
58 else
59 disabledColor = textColor.dark();
60
61 // If there's not very much contrast between the disabled color and the background color,
62 // just leave the text color alone. We don't want to change a good contrast color scheme so that it has really bad contrast.
63 // If the the contrast was already poor, then it doesn't do any good to change it to a different poor contrast color scheme.
64 if (differenceSquared(disabledColor, backgroundColor) < minColorContrastValue)
65 return textColor;
66
67 return disabledColor;
68 }
69
RenderTextControl(Node * node,bool placeholderVisible)70 RenderTextControl::RenderTextControl(Node* node, bool placeholderVisible)
71 : RenderBlock(node)
72 , m_placeholderVisible(placeholderVisible)
73 , m_wasChangedSinceLastChangeEvent(false)
74 , m_lastChangeWasUserEdit(false)
75 {
76 }
77
~RenderTextControl()78 RenderTextControl::~RenderTextControl()
79 {
80 // The children renderers have already been destroyed by destroyLeftoverChildren
81 if (m_innerText)
82 m_innerText->detach();
83 }
84
styleDidChange(StyleDifference diff,const RenderStyle * oldStyle)85 void RenderTextControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
86 {
87 RenderBlock::styleDidChange(diff, oldStyle);
88
89 if (m_innerText) {
90 RenderBlock* textBlockRenderer = toRenderBlock(m_innerText->renderer());
91 RefPtr<RenderStyle> textBlockStyle = createInnerTextStyle(style());
92 // We may have set the width and the height in the old style in layout().
93 // Reset them now to avoid getting a spurious layout hint.
94 textBlockRenderer->style()->setHeight(Length());
95 textBlockRenderer->style()->setWidth(Length());
96 setInnerTextStyle(textBlockStyle);
97 }
98
99 setReplaced(isInline());
100 }
101
setInnerTextStyle(PassRefPtr<RenderStyle> style)102 void RenderTextControl::setInnerTextStyle(PassRefPtr<RenderStyle> style)
103 {
104 if (m_innerText) {
105 RefPtr<RenderStyle> textStyle = style;
106 m_innerText->renderer()->setStyle(textStyle);
107 for (Node* n = m_innerText->firstChild(); n; n = n->traverseNextNode(m_innerText.get())) {
108 if (n->renderer())
109 n->renderer()->setStyle(textStyle);
110 }
111 }
112 }
113
updateUserModifyProperty(Node * node,RenderStyle * style)114 static inline bool updateUserModifyProperty(Node* node, RenderStyle* style)
115 {
116 bool isEnabled = true;
117 bool isReadOnlyControl = false;
118
119 if (node->isElementNode()) {
120 Element* element = static_cast<Element*>(node);
121 isEnabled = element->isEnabledFormControl();
122 isReadOnlyControl = element->isReadOnlyFormControl();
123 }
124
125 style->setUserModify((isReadOnlyControl || !isEnabled) ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY);
126 return !isEnabled;
127 }
128
adjustInnerTextStyle(const RenderStyle * startStyle,RenderStyle * textBlockStyle) const129 void RenderTextControl::adjustInnerTextStyle(const RenderStyle* startStyle, RenderStyle* textBlockStyle) const
130 {
131 // The inner block, if present, always has its direction set to LTR,
132 // so we need to inherit the direction from the element.
133 textBlockStyle->setDirection(style()->direction());
134
135 bool disabled = updateUserModifyProperty(node(), textBlockStyle);
136 if (disabled)
137 textBlockStyle->setColor(disabledTextColor(textBlockStyle->color(), startStyle->backgroundColor()));
138 }
139
createSubtreeIfNeeded(TextControlInnerElement * innerBlock)140 void RenderTextControl::createSubtreeIfNeeded(TextControlInnerElement* innerBlock)
141 {
142 if (!m_innerText) {
143 // Create the text block element
144 // For non-search fields, there is no intermediate innerBlock as the shadow node.
145 // m_innerText will be the shadow node in that case.
146 RenderStyle* parentStyle = innerBlock ? innerBlock->renderer()->style() : style();
147 m_innerText = new TextControlInnerTextElement(document(), innerBlock ? 0 : node());
148 m_innerText->attachInnerElement(innerBlock ? innerBlock : node(), createInnerTextStyle(parentStyle), renderArena());
149 }
150 }
151
textBlockHeight() const152 int RenderTextControl::textBlockHeight() const
153 {
154 return height() - paddingTop() - paddingBottom() - borderTop() - borderBottom();
155 }
156
textBlockWidth() const157 int RenderTextControl::textBlockWidth() const
158 {
159 return width() - paddingLeft() - paddingRight() - borderLeft() - borderRight()
160 - m_innerText->renderBox()->paddingLeft() - m_innerText->renderBox()->paddingRight();
161 }
162
updateFromElement()163 void RenderTextControl::updateFromElement()
164 {
165 updateUserModifyProperty(node(), m_innerText->renderer()->style());
166 }
167
setInnerTextValue(const String & innerTextValue)168 void RenderTextControl::setInnerTextValue(const String& innerTextValue)
169 {
170 String value;
171
172 if (innerTextValue.isNull())
173 value = "";
174 else {
175 value = innerTextValue;
176 value = document()->displayStringModifiedByEncoding(value);
177 }
178
179 if (value != text() || !m_innerText->hasChildNodes()) {
180 if (value != text()) {
181 if (Frame* frame = document()->frame()) {
182 frame->editor()->clearUndoRedoOperations();
183
184 if (AXObjectCache::accessibilityEnabled())
185 document()->axObjectCache()->postNotification(this, AXObjectCache::AXValueChanged, false);
186 }
187 }
188
189 ExceptionCode ec = 0;
190 m_innerText->setInnerText(value, ec);
191 ASSERT(!ec);
192
193 if (value.endsWith("\n") || value.endsWith("\r")) {
194 m_innerText->appendChild(new HTMLBRElement(brTag, document()), ec);
195 ASSERT(!ec);
196 }
197
198 // We set m_lastChangeWasUserEdit to false since this change was not explicitly made by the user (say, via typing on the keyboard), see <rdar://problem/5359921>.
199 m_lastChangeWasUserEdit = false;
200 }
201
202 static_cast<Element*>(node())->setFormControlValueMatchesRenderer(true);
203 }
204
setLastChangeWasUserEdit(bool lastChangeWasUserEdit)205 void RenderTextControl::setLastChangeWasUserEdit(bool lastChangeWasUserEdit)
206 {
207 m_lastChangeWasUserEdit = lastChangeWasUserEdit;
208 document()->setIgnoreAutofocus(lastChangeWasUserEdit);
209 }
210
selectionStart()211 int RenderTextControl::selectionStart()
212 {
213 Frame* frame = document()->frame();
214 if (!frame)
215 return 0;
216 return indexForVisiblePosition(frame->selection()->start());
217 }
218
selectionEnd()219 int RenderTextControl::selectionEnd()
220 {
221 Frame* frame = document()->frame();
222 if (!frame)
223 return 0;
224 return indexForVisiblePosition(frame->selection()->end());
225 }
226
setSelectionStart(int start)227 void RenderTextControl::setSelectionStart(int start)
228 {
229 setSelectionRange(start, max(start, selectionEnd()));
230 }
231
setSelectionEnd(int end)232 void RenderTextControl::setSelectionEnd(int end)
233 {
234 setSelectionRange(min(end, selectionStart()), end);
235 }
236
select()237 void RenderTextControl::select()
238 {
239 setSelectionRange(0, text().length());
240 }
241
setSelectionRange(int start,int end)242 void RenderTextControl::setSelectionRange(int start, int end)
243 {
244 end = max(end, 0);
245 start = min(max(start, 0), end);
246
247 ASSERT(!document()->childNeedsAndNotInStyleRecalc());
248
249 if (style()->visibility() == HIDDEN || !m_innerText || !m_innerText->renderer() || !m_innerText->renderBox()->height()) {
250 cacheSelection(start, end);
251 return;
252 }
253 VisiblePosition startPosition = visiblePositionForIndex(start);
254 VisiblePosition endPosition;
255 if (start == end)
256 endPosition = startPosition;
257 else
258 endPosition = visiblePositionForIndex(end);
259
260 // startPosition and endPosition can be null position for example when
261 // "-webkit-user-select: none" style attribute is specified.
262 if (startPosition.isNotNull() && endPosition.isNotNull()) {
263 ASSERT(startPosition.deepEquivalent().node()->shadowAncestorNode() == node() && endPosition.deepEquivalent().node()->shadowAncestorNode() == node());
264 }
265 VisibleSelection newSelection = VisibleSelection(startPosition, endPosition);
266
267 if (Frame* frame = document()->frame())
268 frame->selection()->setSelection(newSelection);
269
270 // FIXME: Granularity is stored separately on the frame, but also in the selection controller.
271 // The granularity in the selection controller should be used, and then this line of code would not be needed.
272 if (Frame* frame = document()->frame())
273 frame->setSelectionGranularity(CharacterGranularity);
274 }
275
selection(int start,int end) const276 VisibleSelection RenderTextControl::selection(int start, int end) const
277 {
278 return VisibleSelection(VisiblePosition(m_innerText.get(), start, VP_DEFAULT_AFFINITY),
279 VisiblePosition(m_innerText.get(), end, VP_DEFAULT_AFFINITY));
280 }
281
visiblePositionForIndex(int index)282 VisiblePosition RenderTextControl::visiblePositionForIndex(int index)
283 {
284 if (index <= 0)
285 return VisiblePosition(m_innerText.get(), 0, DOWNSTREAM);
286 ExceptionCode ec = 0;
287 RefPtr<Range> range = Range::create(document());
288 range->selectNodeContents(m_innerText.get(), ec);
289 ASSERT(!ec);
290 CharacterIterator it(range.get());
291 it.advance(index - 1);
292 Node* endContainer = it.range()->endContainer(ec);
293 ASSERT(!ec);
294 int endOffset = it.range()->endOffset(ec);
295 ASSERT(!ec);
296 return VisiblePosition(endContainer, endOffset, UPSTREAM);
297 }
298
indexForVisiblePosition(const VisiblePosition & pos)299 int RenderTextControl::indexForVisiblePosition(const VisiblePosition& pos)
300 {
301 Position indexPosition = pos.deepEquivalent();
302 if (!indexPosition.node() || indexPosition.node()->rootEditableElement() != m_innerText)
303 return 0;
304 ExceptionCode ec = 0;
305 RefPtr<Range> range = Range::create(document());
306 range->setStart(m_innerText.get(), 0, ec);
307 ASSERT(!ec);
308 range->setEnd(indexPosition.node(), indexPosition.deprecatedEditingOffset(), ec);
309 ASSERT(!ec);
310 return TextIterator::rangeLength(range.get());
311 }
312
subtreeHasChanged()313 void RenderTextControl::subtreeHasChanged()
314 {
315 m_wasChangedSinceLastChangeEvent = true;
316 m_lastChangeWasUserEdit = true;
317 }
318
finishText(Vector<UChar> & result) const319 String RenderTextControl::finishText(Vector<UChar>& result) const
320 {
321 // Remove one trailing newline; there's always one that's collapsed out by rendering.
322 size_t size = result.size();
323 if (size && result[size - 1] == '\n')
324 result.shrink(--size);
325
326 // Convert backslash to currency symbol.
327 document()->displayBufferModifiedByEncoding(result.data(), result.size());
328
329 return String::adopt(result);
330 }
331
text()332 String RenderTextControl::text()
333 {
334 if (!m_innerText)
335 return "";
336
337 Vector<UChar> result;
338
339 for (Node* n = m_innerText.get(); n; n = n->traverseNextNode(m_innerText.get())) {
340 if (n->hasTagName(brTag))
341 result.append(&newlineCharacter, 1);
342 else if (n->isTextNode()) {
343 String data = static_cast<Text*>(n)->data();
344 result.append(data.characters(), data.length());
345 }
346 }
347
348 return finishText(result);
349 }
350
getNextSoftBreak(RootInlineBox * & line,Node * & breakNode,unsigned & breakOffset)351 static void getNextSoftBreak(RootInlineBox*& line, Node*& breakNode, unsigned& breakOffset)
352 {
353 RootInlineBox* next;
354 for (; line; line = next) {
355 next = line->nextRootBox();
356 if (next && !line->endsWithBreak()) {
357 ASSERT(line->lineBreakObj());
358 breakNode = line->lineBreakObj()->node();
359 breakOffset = line->lineBreakPos();
360 line = next;
361 return;
362 }
363 }
364 breakNode = 0;
365 breakOffset = 0;
366 }
367
textWithHardLineBreaks()368 String RenderTextControl::textWithHardLineBreaks()
369 {
370 if (!m_innerText)
371 return "";
372 Node* firstChild = m_innerText->firstChild();
373 if (!firstChild)
374 return "";
375
376 document()->updateLayout();
377
378 RenderObject* renderer = firstChild->renderer();
379 if (!renderer)
380 return "";
381
382 InlineBox* box = renderer->isText() ? toRenderText(renderer)->firstTextBox() : toRenderBox(renderer)->inlineBoxWrapper();
383 if (!box)
384 return "";
385
386 Node* breakNode;
387 unsigned breakOffset;
388 RootInlineBox* line = box->root();
389 getNextSoftBreak(line, breakNode, breakOffset);
390
391 Vector<UChar> result;
392
393 for (Node* n = firstChild; n; n = n->traverseNextNode(m_innerText.get())) {
394 if (n->hasTagName(brTag))
395 result.append(&newlineCharacter, 1);
396 else if (n->isTextNode()) {
397 Text* text = static_cast<Text*>(n);
398 String data = text->data();
399 unsigned length = data.length();
400 unsigned position = 0;
401 while (breakNode == n && breakOffset <= length) {
402 if (breakOffset > position) {
403 result.append(data.characters() + position, breakOffset - position);
404 position = breakOffset;
405 result.append(&newlineCharacter, 1);
406 }
407 getNextSoftBreak(line, breakNode, breakOffset);
408 }
409 result.append(data.characters() + position, length - position);
410 }
411 while (breakNode == n)
412 getNextSoftBreak(line, breakNode, breakOffset);
413 }
414
415 return finishText(result);
416 }
417
scrollbarThickness() const418 int RenderTextControl::scrollbarThickness() const
419 {
420 // FIXME: We should get the size of the scrollbar from the RenderTheme instead.
421 return ScrollbarTheme::nativeTheme()->scrollbarThickness();
422 }
423
calcHeight()424 void RenderTextControl::calcHeight()
425 {
426 setHeight(m_innerText->renderBox()->borderTop() + m_innerText->renderBox()->borderBottom() +
427 m_innerText->renderBox()->paddingTop() + m_innerText->renderBox()->paddingBottom() +
428 m_innerText->renderBox()->marginTop() + m_innerText->renderBox()->marginBottom());
429
430 adjustControlHeightBasedOnLineHeight(m_innerText->renderer()->lineHeight(true, true));
431 setHeight(height() + paddingTop() + paddingBottom() + borderTop() + borderBottom());
432
433 // We are able to have a horizontal scrollbar if the overflow style is scroll, or if its auto and there's no word wrap.
434 if (style()->overflowX() == OSCROLL || (style()->overflowX() == OAUTO && m_innerText->renderer()->style()->wordWrap() == NormalWordWrap))
435 setHeight(height() + scrollbarThickness());
436
437 RenderBlock::calcHeight();
438 }
439
hitInnerTextElement(HitTestResult & result,int xPos,int yPos,int tx,int ty)440 void RenderTextControl::hitInnerTextElement(HitTestResult& result, int xPos, int yPos, int tx, int ty)
441 {
442 result.setInnerNode(m_innerText.get());
443 result.setInnerNonSharedNode(m_innerText.get());
444 result.setLocalPoint(IntPoint(xPos - tx - x() - m_innerText->renderBox()->x(),
445 yPos - ty - y() - m_innerText->renderBox()->y()));
446 }
447
forwardEvent(Event * event)448 void RenderTextControl::forwardEvent(Event* event)
449 {
450 if (event->type() == eventNames().blurEvent || event->type() == eventNames().focusEvent)
451 return;
452 m_innerText->defaultEventHandler(event);
453 }
454
controlClipRect(int tx,int ty) const455 IntRect RenderTextControl::controlClipRect(int tx, int ty) const
456 {
457 IntRect clipRect = contentBoxRect();
458 clipRect.move(tx, ty);
459 return clipRect;
460 }
461
calcPrefWidths()462 void RenderTextControl::calcPrefWidths()
463 {
464 ASSERT(prefWidthsDirty());
465
466 m_minPrefWidth = 0;
467 m_maxPrefWidth = 0;
468
469 if (style()->width().isFixed() && style()->width().value() > 0)
470 m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
471 else {
472 // Use average character width. Matches IE.
473 float charWidth = style()->font().primaryFont()->avgCharWidth();
474 m_maxPrefWidth = preferredContentWidth(charWidth) + m_innerText->renderBox()->paddingLeft() + m_innerText->renderBox()->paddingRight();
475 }
476
477 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
478 m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
479 m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
480 } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
481 m_minPrefWidth = 0;
482 else
483 m_minPrefWidth = m_maxPrefWidth;
484
485 if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
486 m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
487 m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
488 }
489
490 int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
491
492 m_minPrefWidth += toAdd;
493 m_maxPrefWidth += toAdd;
494
495 setPrefWidthsDirty(false);
496 }
497
selectionChanged(bool userTriggered)498 void RenderTextControl::selectionChanged(bool userTriggered)
499 {
500 cacheSelection(selectionStart(), selectionEnd());
501
502 if (Frame* frame = document()->frame()) {
503 if (frame->selection()->isRange() && userTriggered)
504 node()->dispatchEvent(Event::create(eventNames().selectEvent, true, false));
505 }
506 }
507
addFocusRingRects(Vector<IntRect> & rects,int tx,int ty)508 void RenderTextControl::addFocusRingRects(Vector<IntRect>& rects, int tx, int ty)
509 {
510 if (width() && height())
511 rects.append(IntRect(tx, ty, width(), height()));
512 }
513
innerTextElement() const514 HTMLElement* RenderTextControl::innerTextElement() const
515 {
516 return m_innerText.get();
517 }
518
updatePlaceholderVisibility(bool placeholderShouldBeVisible,bool placeholderValueChanged)519 void RenderTextControl::updatePlaceholderVisibility(bool placeholderShouldBeVisible, bool placeholderValueChanged)
520 {
521 bool oldPlaceholderVisible = m_placeholderVisible;
522 m_placeholderVisible = placeholderShouldBeVisible;
523 if (oldPlaceholderVisible != m_placeholderVisible || placeholderValueChanged) {
524 // Sets the inner text style to the normal style or :placeholder style.
525 setInnerTextStyle(createInnerTextStyle(textBaseStyle()));
526
527 // updateFromElement() of the subclasses updates the text content
528 // to the element's value(), placeholder(), or the empty string.
529 updateFromElement();
530 }
531 }
532
533 } // namespace WebCore
534