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 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 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 78 RenderTextControl::~RenderTextControl() 79 { 80 // The children renderers have already been destroyed by destroyLeftoverChildren 81 if (m_innerText) 82 m_innerText->detach(); 83 } 84 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 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 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 129 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 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 152 int RenderTextControl::textBlockHeight() const 153 { 154 return height() - paddingTop() - paddingBottom() - borderTop() - borderBottom(); 155 } 156 157 int RenderTextControl::textBlockWidth() const 158 { 159 return width() - paddingLeft() - paddingRight() - borderLeft() - borderRight() 160 - m_innerText->renderBox()->paddingLeft() - m_innerText->renderBox()->paddingRight(); 161 } 162 163 void RenderTextControl::updateFromElement() 164 { 165 updateUserModifyProperty(node(), m_innerText->renderer()->style()); 166 } 167 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 205 void RenderTextControl::setLastChangeWasUserEdit(bool lastChangeWasUserEdit) 206 { 207 m_lastChangeWasUserEdit = lastChangeWasUserEdit; 208 document()->setIgnoreAutofocus(lastChangeWasUserEdit); 209 } 210 211 int RenderTextControl::selectionStart() 212 { 213 Frame* frame = document()->frame(); 214 if (!frame) 215 return 0; 216 return indexForVisiblePosition(frame->selection()->start()); 217 } 218 219 int RenderTextControl::selectionEnd() 220 { 221 Frame* frame = document()->frame(); 222 if (!frame) 223 return 0; 224 return indexForVisiblePosition(frame->selection()->end()); 225 } 226 227 void RenderTextControl::setSelectionStart(int start) 228 { 229 setSelectionRange(start, max(start, selectionEnd())); 230 } 231 232 void RenderTextControl::setSelectionEnd(int end) 233 { 234 setSelectionRange(min(end, selectionStart()), end); 235 } 236 237 void RenderTextControl::select() 238 { 239 setSelectionRange(0, text().length()); 240 } 241 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 276 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 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 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 313 void RenderTextControl::subtreeHasChanged() 314 { 315 m_wasChangedSinceLastChangeEvent = true; 316 m_lastChangeWasUserEdit = true; 317 } 318 319 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 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 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 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 418 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 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 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 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 455 IntRect RenderTextControl::controlClipRect(int tx, int ty) const 456 { 457 IntRect clipRect = contentBoxRect(); 458 clipRect.move(tx, ty); 459 return clipRect; 460 } 461 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 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 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 514 HTMLElement* RenderTextControl::innerTextElement() const 515 { 516 return m_innerText.get(); 517 } 518 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