unicode-bidi:-webkit-plaintext does not work on <textarea>
[WebKit-https.git] / Source / WebCore / rendering / RenderTextControl.cpp
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 "Editor.h"
27 #include "Frame.h"
28 #include "HTMLBRElement.h"
29 #include "HTMLInputElement.h"
30 #include "HTMLNames.h"
31 #include "HitTestResult.h"
32 #include "Position.h"
33 #include "RenderLayer.h"
34 #include "RenderText.h"
35 #include "ScrollbarTheme.h"
36 #include "Text.h"
37 #include "TextIterator.h"
38 #include <wtf/text/StringBuilder.h>
39 #include <wtf/unicode/CharacterNames.h>
40
41 using namespace std;
42
43 namespace WebCore {
44
45 using namespace HTMLNames;
46
47 // Value chosen by observation.  This can be tweaked.
48 static const int minColorContrastValue = 1300;
49
50 static Color disabledTextColor(const Color& textColor, const Color& backgroundColor)
51 {
52     // The explicit check for black is an optimization for the 99% case (black on white).
53     // This also means that black on black will turn into grey on black when disabled.
54     Color disabledColor;
55     if (textColor.rgb() == Color::black || differenceSquared(textColor, Color::white) > differenceSquared(backgroundColor, Color::white))
56         disabledColor = textColor.light();
57     else
58         disabledColor = textColor.dark();
59     
60     // If there's not very much contrast between the disabled color and the background color,
61     // just leave the text color alone.  We don't want to change a good contrast color scheme so that it has really bad contrast.
62     // If the the contrast was already poor, then it doesn't do any good to change it to a different poor contrast color scheme.
63     if (differenceSquared(disabledColor, backgroundColor) < minColorContrastValue)
64         return textColor;
65     
66     return disabledColor;
67 }
68
69 RenderTextControl::RenderTextControl(Node* node)
70     : RenderBlock(node)
71     , m_lastChangeWasUserEdit(false)
72 {
73     ASSERT(toTextFormControl(node));
74 }
75
76 RenderTextControl::~RenderTextControl()
77 {
78 }
79
80 HTMLTextFormControlElement* RenderTextControl::textFormControlElement() const
81 {
82     return static_cast<HTMLTextFormControlElement*>(node());
83 }
84
85 HTMLElement* RenderTextControl::innerTextElement() const
86 {
87     return textFormControlElement()->innerTextElement();
88 }
89
90 void RenderTextControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
91 {
92     RenderBlock::styleDidChange(diff, oldStyle);
93     Element* innerText = innerTextElement();
94     if (!innerText)
95         return;
96     RenderBlock* innerTextRenderer = toRenderBlock(innerText->renderer());
97     if (innerTextRenderer) {
98         // We may have set the width and the height in the old style in layout().
99         // Reset them now to avoid getting a spurious layout hint.
100         innerTextRenderer->style()->setHeight(Length());
101         innerTextRenderer->style()->setWidth(Length());
102         innerTextRenderer->setStyle(createInnerTextStyle(style()));
103         innerText->setNeedsStyleRecalc();
104     }
105     textFormControlElement()->updatePlaceholderVisibility(false);
106 }
107
108 static inline bool updateUserModifyProperty(Node* node, RenderStyle* style)
109 {
110     bool isEnabled = true;
111     bool isReadOnlyControl = false;
112
113     if (node->isElementNode()) {
114         Element* element = static_cast<Element*>(node);
115         isEnabled = element->isEnabledFormControl();
116         isReadOnlyControl = element->isReadOnlyFormControl();
117     }
118
119     style->setUserModify((isReadOnlyControl || !isEnabled) ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY);
120     return !isEnabled;
121 }
122
123 void RenderTextControl::adjustInnerTextStyle(const RenderStyle* startStyle, RenderStyle* textBlockStyle) const
124 {
125     // The inner block, if present, always has its direction set to LTR,
126     // so we need to inherit the direction and unicode-bidi style from the element.
127     textBlockStyle->setDirection(style()->direction());
128     textBlockStyle->setUnicodeBidi(style()->unicodeBidi());
129
130     bool disabled = updateUserModifyProperty(node(), textBlockStyle);
131     if (disabled)
132         textBlockStyle->setColor(disabledTextColor(textBlockStyle->visitedDependentColor(CSSPropertyColor), startStyle->visitedDependentColor(CSSPropertyBackgroundColor)));
133 }
134
135 int RenderTextControl::textBlockHeight() const
136 {
137     return height() - borderAndPaddingHeight();
138 }
139
140 int RenderTextControl::textBlockWidth() const
141 {
142     Element* innerText = innerTextElement();
143     ASSERT(innerText);
144     return width() - borderAndPaddingWidth() - innerText->renderBox()->paddingLeft() - innerText->renderBox()->paddingRight();
145 }
146
147 void RenderTextControl::updateFromElement()
148 {
149     Element* innerText = innerTextElement();
150     if (innerText)
151         updateUserModifyProperty(node(), innerText->renderer()->style());
152 }
153
154 void RenderTextControl::setInnerTextValue(const String& value)
155 {
156     bool textIsChanged = value != text();
157     if (textIsChanged || !innerTextElement()->hasChildNodes()) {
158         if (textIsChanged && document() && AXObjectCache::accessibilityEnabled())
159             document()->axObjectCache()->postNotification(this, AXObjectCache::AXValueChanged, false);
160
161         ExceptionCode ec = 0;
162         innerTextElement()->setInnerText(value, ec);
163         ASSERT(!ec);
164
165         if (value.endsWith("\n") || value.endsWith("\r")) {
166             innerTextElement()->appendChild(HTMLBRElement::create(document()), ec);
167             ASSERT(!ec);
168         }
169
170         // 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>.
171         m_lastChangeWasUserEdit = false;
172     }
173
174     static_cast<Element*>(node())->setFormControlValueMatchesRenderer(true);
175 }
176
177 VisiblePosition RenderTextControl::visiblePositionForIndex(int index) const
178 {
179     if (index <= 0)
180         return VisiblePosition(firstPositionInNode(innerTextElement()), DOWNSTREAM);
181     ExceptionCode ec = 0;
182     RefPtr<Range> range = Range::create(document());
183     range->selectNodeContents(innerTextElement(), ec);
184     ASSERT(!ec);
185     CharacterIterator it(range.get());
186     it.advance(index - 1);
187     return VisiblePosition(it.range()->endPosition(), UPSTREAM);
188 }
189
190 static String finishText(StringBuilder& result)
191 {
192     // Remove one trailing newline; there's always one that's collapsed out by rendering.
193     size_t size = result.length();
194     if (size && result[size - 1] == '\n')
195         result.resize(--size);
196     return result.toString();
197 }
198
199 String RenderTextControl::text()
200 {
201     HTMLElement* innerText = innerTextElement();
202     if (!innerText)
203         return emptyString();
204  
205     StringBuilder result;
206     for (Node* node = innerText; node; node = node->traverseNextNode(innerText)) {
207         if (node->hasTagName(brTag))
208             result.append(newlineCharacter);
209         else if (node->isTextNode())
210             result.append(static_cast<Text*>(node)->data());
211     }
212     return finishText(result);
213 }
214
215 static void getNextSoftBreak(RootInlineBox*& line, Node*& breakNode, unsigned& breakOffset)
216 {
217     RootInlineBox* next;
218     for (; line; line = next) {
219         next = line->nextRootBox();
220         if (next && !line->endsWithBreak()) {
221             ASSERT(line->lineBreakObj());
222             breakNode = line->lineBreakObj()->node();
223             breakOffset = line->lineBreakPos();
224             line = next;
225             return;
226         }
227     }
228     breakNode = 0;
229     breakOffset = 0;
230 }
231
232 String RenderTextControl::textWithHardLineBreaks()
233 {
234     HTMLElement* innerText = innerTextElement();
235     if (!innerText)
236         return emptyString();
237
238     RenderBlock* renderer = toRenderBlock(innerText->renderer());
239     if (!renderer)
240         return emptyString();
241
242     Node* breakNode;
243     unsigned breakOffset;
244     RootInlineBox* line = renderer->firstRootBox();
245     if (!line)
246         return emptyString();
247
248     getNextSoftBreak(line, breakNode, breakOffset);
249
250     StringBuilder result;
251     for (Node* node = innerText->firstChild(); node; node = node->traverseNextNode(innerText)) {
252         if (node->hasTagName(brTag))
253             result.append(newlineCharacter);
254         else if (node->isTextNode()) {
255             String data = static_cast<Text*>(node)->data();
256             unsigned length = data.length();
257             unsigned position = 0;
258             while (breakNode == node && breakOffset <= length) {
259                 if (breakOffset > position) {
260                     result.append(data.characters() + position, breakOffset - position);
261                     position = breakOffset;
262                     result.append(newlineCharacter);
263                 }
264                 getNextSoftBreak(line, breakNode, breakOffset);
265             }
266             result.append(data.characters() + position, length - position);
267         }
268         while (breakNode == node)
269             getNextSoftBreak(line, breakNode, breakOffset);
270     }
271     return finishText(result);
272 }
273
274 int RenderTextControl::scrollbarThickness() const
275 {
276     // FIXME: We should get the size of the scrollbar from the RenderTheme instead.
277     return ScrollbarTheme::nativeTheme()->scrollbarThickness();
278 }
279
280 void RenderTextControl::computeLogicalHeight()
281 {
282     HTMLElement* innerText = innerTextElement();
283     ASSERT(innerText);
284     RenderBox* innerTextRenderBox = innerText->renderBox();
285
286     setHeight(innerTextRenderBox->borderTop() + innerTextRenderBox->borderBottom() +
287               innerTextRenderBox->paddingTop() + innerTextRenderBox->paddingBottom() +
288               innerTextRenderBox->marginTop() + innerTextRenderBox->marginBottom());
289
290     adjustControlHeightBasedOnLineHeight(innerText->renderBox()->lineHeight(true, HorizontalLine, PositionOfInteriorLineBoxes));
291     setHeight(height() + borderAndPaddingHeight());
292
293     // We are able to have a horizontal scrollbar if the overflow style is scroll, or if its auto and there's no word wrap.
294     if (style()->overflowX() == OSCROLL ||  (style()->overflowX() == OAUTO && innerText->renderer()->style()->wordWrap() == NormalWordWrap))
295         setHeight(height() + scrollbarThickness());
296
297     RenderBlock::computeLogicalHeight();
298 }
299
300 void RenderTextControl::hitInnerTextElement(HitTestResult& result, const LayoutPoint& pointInContainer, const LayoutPoint& accumulatedOffset)
301 {
302     IntPoint adjustedLocation = accumulatedOffset + location();
303     HTMLElement* innerText = innerTextElement();
304     result.setInnerNode(innerText);
305     result.setInnerNonSharedNode(innerText);
306     result.setLocalPoint(pointInContainer - toSize(adjustedLocation + innerText->renderBox()->location()));
307 }
308
309 static const char* fontFamiliesWithInvalidCharWidth[] = {
310     "American Typewriter",
311     "Arial Hebrew",
312     "Chalkboard",
313     "Cochin",
314     "Corsiva Hebrew",
315     "Courier",
316     "Euphemia UCAS",
317     "Geneva",
318     "Gill Sans",
319     "Hei",
320     "Helvetica",
321     "Hoefler Text",
322     "InaiMathi",
323     "Kai",
324     "Lucida Grande",
325     "Marker Felt",
326     "Monaco",
327     "Mshtakan",
328     "New Peninim MT",
329     "Osaka",
330     "Raanana",
331     "STHeiti",
332     "Symbol",
333     "Times",
334     "Apple Braille",
335     "Apple LiGothic",
336     "Apple LiSung",
337     "Apple Symbols",
338     "AppleGothic",
339     "AppleMyungjo",
340     "#GungSeo",
341     "#HeadLineA",
342     "#PCMyungjo",
343     "#PilGi",
344 };
345
346 // For font families where any of the fonts don't have a valid entry in the OS/2 table
347 // for avgCharWidth, fallback to the legacy webkit behavior of getting the avgCharWidth
348 // from the width of a '0'. This only seems to apply to a fixed number of Mac fonts,
349 // but, in order to get similar rendering across platforms, we do this check for
350 // all platforms.
351 bool RenderTextControl::hasValidAvgCharWidth(AtomicString family)
352 {
353     static HashSet<AtomicString>* fontFamiliesWithInvalidCharWidthMap = 0;
354
355     if (!fontFamiliesWithInvalidCharWidthMap) {
356         fontFamiliesWithInvalidCharWidthMap = new HashSet<AtomicString>;
357
358         for (size_t i = 0; i < WTF_ARRAY_LENGTH(fontFamiliesWithInvalidCharWidth); ++i)
359             fontFamiliesWithInvalidCharWidthMap->add(AtomicString(fontFamiliesWithInvalidCharWidth[i]));
360     }
361
362     return !fontFamiliesWithInvalidCharWidthMap->contains(family);
363 }
364
365 float RenderTextControl::getAvgCharWidth(AtomicString family)
366 {
367     if (hasValidAvgCharWidth(family))
368         return roundf(style()->font().primaryFont()->avgCharWidth());
369
370     const UChar ch = '0';
371     const String str = String(&ch, 1);
372     const Font& font = style()->font();
373     TextRun textRun = constructTextRun(this, font, str, style(), TextRun::AllowTrailingExpansion);
374     textRun.disableRoundingHacks();
375     return font.width(textRun);
376 }
377
378 float RenderTextControl::scaleEmToUnits(int x) const
379 {
380     // This matches the unitsPerEm value for MS Shell Dlg and Courier New from the "head" font table.
381     float unitsPerEm = 2048.0f;
382     return roundf(style()->font().size() * x / unitsPerEm);
383 }
384
385 void RenderTextControl::computePreferredLogicalWidths()
386 {
387     ASSERT(preferredLogicalWidthsDirty());
388
389     m_minPreferredLogicalWidth = 0;
390     m_maxPreferredLogicalWidth = 0;
391
392     if (style()->width().isFixed() && style()->width().value() > 0)
393         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value());
394     else {
395         // Use average character width. Matches IE.
396         AtomicString family = style()->font().family().family();
397         RenderBox* innerTextRenderBox = innerTextElement()->renderBox();
398         m_maxPreferredLogicalWidth = preferredContentWidth(getAvgCharWidth(family)) + innerTextRenderBox->paddingLeft() + innerTextRenderBox->paddingRight();
399     }
400
401     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
402         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
403         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
404     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
405         m_minPreferredLogicalWidth = 0;
406     else
407         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
408
409     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
410         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
411         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
412     }
413
414     LayoutUnit toAdd = borderAndPaddingWidth();
415
416     m_minPreferredLogicalWidth += toAdd;
417     m_maxPreferredLogicalWidth += toAdd;
418
419     setPreferredLogicalWidthsDirty(false);
420 }
421
422 void RenderTextControl::addFocusRingRects(Vector<LayoutRect>& rects, const LayoutPoint& additionalOffset)
423 {
424     if (!size().isEmpty())
425         rects.append(LayoutRect(additionalOffset, size()));
426 }
427
428 RenderObject* RenderTextControl::layoutSpecialExcludedChild(bool relayoutChildren)
429 {
430     HTMLElement* placeholder = toTextFormControl(node())->placeholderElement();
431     RenderObject* placeholderRenderer = placeholder ? placeholder->renderer() : 0;
432     if (!placeholderRenderer)
433         return 0;
434     if (relayoutChildren)
435         placeholderRenderer->setNeedsLayout(true);
436     return placeholderRenderer;
437 }
438
439 } // namespace WebCore