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