1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2001 Dirk Mueller (mueller (at) kde.org) 5 * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. 6 * (C) 2006 Alexey Proskuryakov (ap (at) nypop.com) 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Library General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Library General Public License for more details. 17 * 18 * You should have received a copy of the GNU Library General Public License 19 * along with this library; see the file COPYING.LIB. If not, write to 20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA. 22 * 23 */ 24 25 #include "config.h" 26 #include "core/html/HTMLTextFormControlElement.h" 27 28 #include "HTMLNames.h" 29 #include "bindings/v8/ExceptionState.h" 30 #include "bindings/v8/ExceptionStatePlaceholder.h" 31 #include "core/accessibility/AXObjectCache.h" 32 #include "core/dom/Document.h" 33 #include "core/dom/Event.h" 34 #include "core/dom/EventNames.h" 35 #include "core/dom/NodeRenderingContext.h" 36 #include "core/dom/NodeTraversal.h" 37 #include "core/dom/Text.h" 38 #include "core/editing/FrameSelection.h" 39 #include "core/editing/TextIterator.h" 40 #include "core/html/HTMLBRElement.h" 41 #include "core/page/Frame.h" 42 #include "core/page/UseCounter.h" 43 #include "core/rendering/RenderBox.h" 44 #include "core/rendering/RenderTextControl.h" 45 #include "core/rendering/RenderTheme.h" 46 #include "wtf/text/StringBuilder.h" 47 48 namespace WebCore { 49 50 using namespace HTMLNames; 51 using namespace std; 52 53 HTMLTextFormControlElement::HTMLTextFormControlElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* form) 54 : HTMLFormControlElementWithState(tagName, doc, form) 55 , m_lastChangeWasUserEdit(false) 56 , m_cachedSelectionStart(-1) 57 , m_cachedSelectionEnd(-1) 58 , m_cachedSelectionDirection(SelectionHasNoDirection) 59 { 60 } 61 62 HTMLTextFormControlElement::~HTMLTextFormControlElement() 63 { 64 } 65 66 Node::InsertionNotificationRequest HTMLTextFormControlElement::insertedInto(ContainerNode* insertionPoint) 67 { 68 HTMLFormControlElementWithState::insertedInto(insertionPoint); 69 if (!insertionPoint->inDocument()) 70 return InsertionDone; 71 String initialValue = value(); 72 setTextAsOfLastFormControlChangeEvent(initialValue.isNull() ? emptyString() : initialValue); 73 return InsertionDone; 74 } 75 76 void HTMLTextFormControlElement::dispatchFocusEvent(Element* oldFocusedElement, FocusDirection direction) 77 { 78 if (supportsPlaceholder()) 79 updatePlaceholderVisibility(false); 80 handleFocusEvent(oldFocusedElement, direction); 81 HTMLFormControlElementWithState::dispatchFocusEvent(oldFocusedElement, direction); 82 } 83 84 void HTMLTextFormControlElement::dispatchBlurEvent(Element* newFocusedElement) 85 { 86 if (supportsPlaceholder()) 87 updatePlaceholderVisibility(false); 88 handleBlurEvent(); 89 HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedElement); 90 } 91 92 void HTMLTextFormControlElement::defaultEventHandler(Event* event) 93 { 94 if (event->type() == eventNames().webkitEditableContentChangedEvent && renderer() && renderer()->isTextControl()) { 95 m_lastChangeWasUserEdit = true; 96 subtreeHasChanged(); 97 return; 98 } 99 100 HTMLFormControlElementWithState::defaultEventHandler(event); 101 } 102 103 void HTMLTextFormControlElement::forwardEvent(Event* event) 104 { 105 if (event->type() == eventNames().blurEvent || event->type() == eventNames().focusEvent) 106 return; 107 innerTextElement()->defaultEventHandler(event); 108 } 109 110 String HTMLTextFormControlElement::strippedPlaceholder() const 111 { 112 // According to the HTML5 specification, we need to remove CR and LF from 113 // the attribute value. 114 const AtomicString& attributeValue = fastGetAttribute(placeholderAttr); 115 if (!attributeValue.contains(newlineCharacter) && !attributeValue.contains(carriageReturn)) 116 return attributeValue; 117 118 StringBuilder stripped; 119 unsigned length = attributeValue.length(); 120 stripped.reserveCapacity(length); 121 for (unsigned i = 0; i < length; ++i) { 122 UChar character = attributeValue[i]; 123 if (character == newlineCharacter || character == carriageReturn) 124 continue; 125 stripped.append(character); 126 } 127 return stripped.toString(); 128 } 129 130 static bool isNotLineBreak(UChar ch) { return ch != newlineCharacter && ch != carriageReturn; } 131 132 bool HTMLTextFormControlElement::isPlaceholderEmpty() const 133 { 134 const AtomicString& attributeValue = fastGetAttribute(placeholderAttr); 135 return attributeValue.string().find(isNotLineBreak) == notFound; 136 } 137 138 bool HTMLTextFormControlElement::placeholderShouldBeVisible() const 139 { 140 return supportsPlaceholder() 141 && isEmptyValue() 142 && isEmptySuggestedValue() 143 && !isPlaceholderEmpty() 144 && (document()->focusedElement() != this || (renderer() && renderer()->theme()->shouldShowPlaceholderWhenFocused())) 145 && (!renderer() || renderer()->style()->visibility() == VISIBLE); 146 } 147 148 void HTMLTextFormControlElement::updatePlaceholderVisibility(bool placeholderValueChanged) 149 { 150 if (!supportsPlaceholder()) 151 return; 152 if (!placeholderElement() || placeholderValueChanged) 153 updatePlaceholderText(); 154 HTMLElement* placeholder = placeholderElement(); 155 if (!placeholder) 156 return; 157 placeholder->setInlineStyleProperty(CSSPropertyVisibility, placeholderShouldBeVisible() ? CSSValueVisible : CSSValueHidden); 158 } 159 160 void HTMLTextFormControlElement::fixPlaceholderRenderer(HTMLElement* placeholder, HTMLElement* siblingElement) 161 { 162 // FIXME: We should change the order of DOM nodes. But it makes an assertion 163 // failure in editing code. 164 if (!placeholder || !placeholder->renderer()) 165 return; 166 RenderObject* placeholderRenderer = placeholder->renderer(); 167 RenderObject* siblingRenderer = siblingElement->renderer(); 168 if (!siblingRenderer) 169 return; 170 if (placeholderRenderer->nextSibling() == siblingRenderer) 171 return; 172 RenderObject* parentRenderer = placeholderRenderer->parent(); 173 ASSERT(siblingRenderer->parent() == parentRenderer); 174 parentRenderer->removeChild(placeholderRenderer); 175 parentRenderer->addChild(placeholderRenderer, siblingRenderer); 176 } 177 178 void HTMLTextFormControlElement::setSelectionStart(int start) 179 { 180 setSelectionRange(start, max(start, selectionEnd()), selectionDirection()); 181 } 182 183 void HTMLTextFormControlElement::setSelectionEnd(int end) 184 { 185 setSelectionRange(min(end, selectionStart()), end, selectionDirection()); 186 } 187 188 void HTMLTextFormControlElement::setSelectionDirection(const String& direction) 189 { 190 setSelectionRange(selectionStart(), selectionEnd(), direction); 191 } 192 193 void HTMLTextFormControlElement::select() 194 { 195 setSelectionRange(0, numeric_limits<int>::max(), SelectionHasNoDirection); 196 } 197 198 String HTMLTextFormControlElement::selectedText() const 199 { 200 if (!isTextFormControl()) 201 return String(); 202 return value().substring(selectionStart(), selectionEnd() - selectionStart()); 203 } 204 205 void HTMLTextFormControlElement::dispatchFormControlChangeEvent() 206 { 207 if (m_textAsOfLastFormControlChangeEvent != value()) { 208 HTMLElement::dispatchChangeEvent(); 209 setTextAsOfLastFormControlChangeEvent(value()); 210 } 211 setChangedSinceLastFormControlChangeEvent(false); 212 } 213 214 static inline bool hasVisibleTextArea(RenderTextControl* textControl, HTMLElement* innerText) 215 { 216 ASSERT(textControl); 217 return textControl->style()->visibility() != HIDDEN && innerText && innerText->renderer() && innerText->renderBox()->height(); 218 } 219 220 221 void HTMLTextFormControlElement::setRangeText(const String& replacement, ExceptionState& es) 222 { 223 setRangeText(replacement, selectionStart(), selectionEnd(), String(), es); 224 } 225 226 void HTMLTextFormControlElement::setRangeText(const String& replacement, unsigned start, unsigned end, const String& selectionMode, ExceptionState& es) 227 { 228 if (start > end) { 229 es.throwDOMException(IndexSizeError); 230 return; 231 } 232 233 String text = innerTextValue(); 234 unsigned textLength = text.length(); 235 unsigned replacementLength = replacement.length(); 236 unsigned newSelectionStart = selectionStart(); 237 unsigned newSelectionEnd = selectionEnd(); 238 239 start = std::min(start, textLength); 240 end = std::min(end, textLength); 241 242 if (start < end) 243 text.replace(start, end - start, replacement); 244 else 245 text.insert(replacement, start); 246 247 setInnerTextValue(text); 248 249 // FIXME: What should happen to the value (as in value()) if there's no renderer? 250 if (!renderer()) 251 return; 252 253 subtreeHasChanged(); 254 255 if (equalIgnoringCase(selectionMode, "select")) { 256 newSelectionStart = start; 257 newSelectionEnd = start + replacementLength; 258 } else if (equalIgnoringCase(selectionMode, "start")) 259 newSelectionStart = newSelectionEnd = start; 260 else if (equalIgnoringCase(selectionMode, "end")) 261 newSelectionStart = newSelectionEnd = start + replacementLength; 262 else { 263 // Default is "preserve". 264 long delta = replacementLength - (end - start); 265 266 if (newSelectionStart > end) 267 newSelectionStart += delta; 268 else if (newSelectionStart > start) 269 newSelectionStart = start; 270 271 if (newSelectionEnd > end) 272 newSelectionEnd += delta; 273 else if (newSelectionEnd > start) 274 newSelectionEnd = start + replacementLength; 275 } 276 277 setSelectionRange(newSelectionStart, newSelectionEnd, SelectionHasNoDirection); 278 } 279 280 void HTMLTextFormControlElement::setSelectionRange(int start, int end, const String& directionString) 281 { 282 TextFieldSelectionDirection direction = SelectionHasNoDirection; 283 if (directionString == "forward") 284 direction = SelectionHasForwardDirection; 285 else if (directionString == "backward") 286 direction = SelectionHasBackwardDirection; 287 288 return setSelectionRange(start, end, direction); 289 } 290 291 void HTMLTextFormControlElement::setSelectionRange(int start, int end, TextFieldSelectionDirection direction) 292 { 293 document()->updateLayoutIgnorePendingStylesheets(); 294 295 if (!renderer() || !renderer()->isTextControl()) 296 return; 297 298 end = max(end, 0); 299 start = min(max(start, 0), end); 300 301 if (!hasVisibleTextArea(toRenderTextControl(renderer()), innerTextElement())) { 302 cacheSelection(start, end, direction); 303 return; 304 } 305 VisiblePosition startPosition = visiblePositionForIndex(start); 306 VisiblePosition endPosition; 307 if (start == end) 308 endPosition = startPosition; 309 else 310 endPosition = visiblePositionForIndex(end); 311 312 // startPosition and endPosition can be null position for example when 313 // "-webkit-user-select: none" style attribute is specified. 314 if (startPosition.isNotNull() && endPosition.isNotNull()) { 315 ASSERT(startPosition.deepEquivalent().deprecatedNode()->shadowHost() == this 316 && endPosition.deepEquivalent().deprecatedNode()->shadowHost() == this); 317 } 318 VisibleSelection newSelection; 319 if (direction == SelectionHasBackwardDirection) 320 newSelection = VisibleSelection(endPosition, startPosition); 321 else 322 newSelection = VisibleSelection(startPosition, endPosition); 323 newSelection.setIsDirectional(direction != SelectionHasNoDirection); 324 325 if (Frame* frame = document()->frame()) 326 frame->selection()->setSelection(newSelection); 327 } 328 329 VisiblePosition HTMLTextFormControlElement::visiblePositionForIndex(int index) const 330 { 331 if (index <= 0) 332 return VisiblePosition(firstPositionInNode(innerTextElement()), DOWNSTREAM); 333 RefPtr<Range> range = Range::create(document()); 334 range->selectNodeContents(innerTextElement(), ASSERT_NO_EXCEPTION); 335 CharacterIterator it(range.get()); 336 it.advance(index - 1); 337 return VisiblePosition(it.range()->endPosition(), UPSTREAM); 338 } 339 340 int HTMLTextFormControlElement::indexForVisiblePosition(const VisiblePosition& pos) const 341 { 342 Position indexPosition = pos.deepEquivalent().parentAnchoredEquivalent(); 343 if (enclosingTextFormControl(indexPosition) != this) 344 return 0; 345 RefPtr<Range> range = Range::create(indexPosition.document()); 346 range->setStart(innerTextElement(), 0, ASSERT_NO_EXCEPTION); 347 range->setEnd(indexPosition.containerNode(), indexPosition.offsetInContainerNode(), ASSERT_NO_EXCEPTION); 348 return TextIterator::rangeLength(range.get()); 349 } 350 351 int HTMLTextFormControlElement::selectionStart() const 352 { 353 if (!isTextFormControl()) 354 return 0; 355 if (document()->focusedElement() != this && hasCachedSelection()) 356 return m_cachedSelectionStart; 357 358 return computeSelectionStart(); 359 } 360 361 int HTMLTextFormControlElement::computeSelectionStart() const 362 { 363 ASSERT(isTextFormControl()); 364 Frame* frame = document()->frame(); 365 if (!frame) 366 return 0; 367 368 return indexForVisiblePosition(frame->selection()->start()); 369 } 370 371 int HTMLTextFormControlElement::selectionEnd() const 372 { 373 if (!isTextFormControl()) 374 return 0; 375 if (document()->focusedElement() != this && hasCachedSelection()) 376 return m_cachedSelectionEnd; 377 return computeSelectionEnd(); 378 } 379 380 int HTMLTextFormControlElement::computeSelectionEnd() const 381 { 382 ASSERT(isTextFormControl()); 383 Frame* frame = document()->frame(); 384 if (!frame) 385 return 0; 386 387 return indexForVisiblePosition(frame->selection()->end()); 388 } 389 390 static const AtomicString& directionString(TextFieldSelectionDirection direction) 391 { 392 DEFINE_STATIC_LOCAL(const AtomicString, none, ("none", AtomicString::ConstructFromLiteral)); 393 DEFINE_STATIC_LOCAL(const AtomicString, forward, ("forward", AtomicString::ConstructFromLiteral)); 394 DEFINE_STATIC_LOCAL(const AtomicString, backward, ("backward", AtomicString::ConstructFromLiteral)); 395 396 switch (direction) { 397 case SelectionHasNoDirection: 398 return none; 399 case SelectionHasForwardDirection: 400 return forward; 401 case SelectionHasBackwardDirection: 402 return backward; 403 } 404 405 ASSERT_NOT_REACHED(); 406 return none; 407 } 408 409 const AtomicString& HTMLTextFormControlElement::selectionDirection() const 410 { 411 if (!isTextFormControl()) 412 return directionString(SelectionHasNoDirection); 413 if (document()->focusedElement() != this && hasCachedSelection()) 414 return directionString(m_cachedSelectionDirection); 415 416 return directionString(computeSelectionDirection()); 417 } 418 419 TextFieldSelectionDirection HTMLTextFormControlElement::computeSelectionDirection() const 420 { 421 ASSERT(isTextFormControl()); 422 Frame* frame = document()->frame(); 423 if (!frame) 424 return SelectionHasNoDirection; 425 426 const VisibleSelection& selection = frame->selection()->selection(); 427 return selection.isDirectional() ? (selection.isBaseFirst() ? SelectionHasForwardDirection : SelectionHasBackwardDirection) : SelectionHasNoDirection; 428 } 429 430 static inline void setContainerAndOffsetForRange(Node* node, int offset, Node*& containerNode, int& offsetInContainer) 431 { 432 if (node->isTextNode()) { 433 containerNode = node; 434 offsetInContainer = offset; 435 } else { 436 containerNode = node->parentNode(); 437 offsetInContainer = node->nodeIndex() + offset; 438 } 439 } 440 441 PassRefPtr<Range> HTMLTextFormControlElement::selection() const 442 { 443 if (!renderer() || !isTextFormControl() || !hasCachedSelection()) 444 return 0; 445 446 int start = m_cachedSelectionStart; 447 int end = m_cachedSelectionEnd; 448 449 ASSERT(start <= end); 450 HTMLElement* innerText = innerTextElement(); 451 if (!innerText) 452 return 0; 453 454 if (!innerText->firstChild()) 455 return Range::create(document(), innerText, 0, innerText, 0); 456 457 int offset = 0; 458 Node* startNode = 0; 459 Node* endNode = 0; 460 for (Node* node = innerText->firstChild(); node; node = NodeTraversal::next(node, innerText)) { 461 ASSERT(!node->firstChild()); 462 ASSERT(node->isTextNode() || node->hasTagName(brTag)); 463 int length = node->isTextNode() ? lastOffsetInNode(node) : 1; 464 465 if (offset <= start && start <= offset + length) 466 setContainerAndOffsetForRange(node, start - offset, startNode, start); 467 468 if (offset <= end && end <= offset + length) { 469 setContainerAndOffsetForRange(node, end - offset, endNode, end); 470 break; 471 } 472 473 offset += length; 474 } 475 476 if (!startNode || !endNode) 477 return 0; 478 479 return Range::create(document(), startNode, start, endNode, end); 480 } 481 482 void HTMLTextFormControlElement::restoreCachedSelection() 483 { 484 setSelectionRange(m_cachedSelectionStart, m_cachedSelectionEnd, m_cachedSelectionDirection); 485 } 486 487 void HTMLTextFormControlElement::selectionChanged(bool userTriggered) 488 { 489 if (!renderer() || !isTextFormControl()) 490 return; 491 492 // selectionStart() or selectionEnd() will return cached selection when this node doesn't have focus 493 cacheSelection(computeSelectionStart(), computeSelectionEnd(), computeSelectionDirection()); 494 495 if (Frame* frame = document()->frame()) { 496 if (frame->selection()->isRange() && userTriggered) 497 dispatchEvent(Event::create(eventNames().selectEvent, true, false)); 498 } 499 } 500 501 void HTMLTextFormControlElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 502 { 503 if (name == placeholderAttr) { 504 updatePlaceholderVisibility(true); 505 UseCounter::count(document(), UseCounter::PlaceholderAttribute); 506 } else 507 HTMLFormControlElementWithState::parseAttribute(name, value); 508 } 509 510 bool HTMLTextFormControlElement::lastChangeWasUserEdit() const 511 { 512 if (!isTextFormControl()) 513 return false; 514 return m_lastChangeWasUserEdit; 515 } 516 517 void HTMLTextFormControlElement::setInnerTextValue(const String& value) 518 { 519 if (!isTextFormControl()) 520 return; 521 522 bool textIsChanged = value != innerTextValue(); 523 if (textIsChanged || !innerTextElement()->hasChildNodes()) { 524 if (textIsChanged && document() && renderer()) { 525 if (AXObjectCache* cache = document()->existingAXObjectCache()) 526 cache->postNotification(this, AXObjectCache::AXValueChanged, false); 527 } 528 innerTextElement()->setInnerText(value, ASSERT_NO_EXCEPTION); 529 530 if (value.endsWith('\n') || value.endsWith('\r')) 531 innerTextElement()->appendChild(HTMLBRElement::create(document()), ASSERT_NO_EXCEPTION, AttachLazily); 532 } 533 534 setFormControlValueMatchesRenderer(true); 535 } 536 537 static String finishText(StringBuilder& result) 538 { 539 // Remove one trailing newline; there's always one that's collapsed out by rendering. 540 size_t size = result.length(); 541 if (size && result[size - 1] == '\n') 542 result.resize(--size); 543 return result.toString(); 544 } 545 546 String HTMLTextFormControlElement::innerTextValue() const 547 { 548 HTMLElement* innerText = innerTextElement(); 549 if (!innerText || !isTextFormControl()) 550 return emptyString(); 551 552 StringBuilder result; 553 for (Node* node = innerText; node; node = NodeTraversal::next(node, innerText)) { 554 if (node->hasTagName(brTag)) 555 result.append(newlineCharacter); 556 else if (node->isTextNode()) 557 result.append(toText(node)->data()); 558 } 559 return finishText(result); 560 } 561 562 static void getNextSoftBreak(RootInlineBox*& line, Node*& breakNode, unsigned& breakOffset) 563 { 564 RootInlineBox* next; 565 for (; line; line = next) { 566 next = line->nextRootBox(); 567 if (next && !line->endsWithBreak()) { 568 ASSERT(line->lineBreakObj()); 569 breakNode = line->lineBreakObj()->node(); 570 breakOffset = line->lineBreakPos(); 571 line = next; 572 return; 573 } 574 } 575 breakNode = 0; 576 breakOffset = 0; 577 } 578 579 String HTMLTextFormControlElement::valueWithHardLineBreaks() const 580 { 581 // FIXME: It's not acceptable to ignore the HardWrap setting when there is no renderer. 582 // While we have no evidence this has ever been a practical problem, it would be best to fix it some day. 583 HTMLElement* innerText = innerTextElement(); 584 if (!innerText || !isTextFormControl()) 585 return value(); 586 587 RenderBlock* renderer = toRenderBlock(innerText->renderer()); 588 if (!renderer) 589 return value(); 590 591 Node* breakNode; 592 unsigned breakOffset; 593 RootInlineBox* line = renderer->firstRootBox(); 594 if (!line) 595 return value(); 596 597 getNextSoftBreak(line, breakNode, breakOffset); 598 599 StringBuilder result; 600 for (Node* node = innerText->firstChild(); node; node = NodeTraversal::next(node, innerText)) { 601 if (node->hasTagName(brTag)) 602 result.append(newlineCharacter); 603 else if (node->isTextNode()) { 604 String data = toText(node)->data(); 605 unsigned length = data.length(); 606 unsigned position = 0; 607 while (breakNode == node && breakOffset <= length) { 608 if (breakOffset > position) { 609 result.append(data, position, breakOffset - position); 610 position = breakOffset; 611 result.append(newlineCharacter); 612 } 613 getNextSoftBreak(line, breakNode, breakOffset); 614 } 615 result.append(data, position, length - position); 616 } 617 while (breakNode == node) 618 getNextSoftBreak(line, breakNode, breakOffset); 619 } 620 return finishText(result); 621 } 622 623 HTMLTextFormControlElement* enclosingTextFormControl(const Position& position) 624 { 625 ASSERT(position.isNull() || position.anchorType() == Position::PositionIsOffsetInAnchor 626 || position.containerNode() || !position.anchorNode()->shadowHost() 627 || (position.anchorNode()->parentNode() && position.anchorNode()->parentNode()->isShadowRoot())); 628 629 Node* container = position.containerNode(); 630 if (!container) 631 return 0; 632 Element* ancestor = container->shadowHost(); 633 return ancestor && isHTMLTextFormControlElement(ancestor) ? toHTMLTextFormControlElement(ancestor) : 0; 634 } 635 636 static const Element* parentHTMLElement(const Element* element) 637 { 638 while (element) { 639 element = element->parentElement(); 640 if (element && element->isHTMLElement()) 641 return element; 642 } 643 return 0; 644 } 645 646 String HTMLTextFormControlElement::directionForFormData() const 647 { 648 for (const Element* element = this; element; element = parentHTMLElement(element)) { 649 const AtomicString& dirAttributeValue = element->fastGetAttribute(dirAttr); 650 if (dirAttributeValue.isNull()) 651 continue; 652 653 if (equalIgnoringCase(dirAttributeValue, "rtl") || equalIgnoringCase(dirAttributeValue, "ltr")) 654 return dirAttributeValue; 655 656 if (equalIgnoringCase(dirAttributeValue, "auto")) { 657 bool isAuto; 658 TextDirection textDirection = static_cast<const HTMLElement*>(element)->directionalityIfhasDirAutoAttribute(isAuto); 659 return textDirection == RTL ? "rtl" : "ltr"; 660 } 661 } 662 663 return "ltr"; 664 } 665 666 } // namespace Webcore 667