Move dispatching of focus-related events from Node to Element.
[WebKit-https.git] / Source / WebCore / html / HTMLTextFormControlElement.cpp
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
51 namespace WebCore {
52
53 using namespace HTMLNames;
54 using namespace std;
55
56 HTMLTextFormControlElement::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
65 HTMLTextFormControlElement::~HTMLTextFormControlElement()
66 {
67 }
68
69 bool 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
78 Node::InsertionNotificationRequest HTMLTextFormControlElement::insertedInto(ContainerNode* insertionPoint)
79 {
80     HTMLFormControlElement::insertedInto(insertionPoint);
81     if (!insertionPoint->inDocument())
82         return InsertionDone;
83     String initialValue = value();
84     setTextAsOfLastFormControlChangeEvent(initialValue.isNull() ? emptyString() : initialValue);
85     return InsertionDone;
86 }
87
88 void 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
96 void HTMLTextFormControlElement::dispatchBlurEvent(PassRefPtr<Element> newFocusedElement)
97 {
98     if (supportsPlaceholder())
99         updatePlaceholderVisibility(false);
100     handleBlurEvent();
101     HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedElement);
102 }
103
104 void 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
115 void HTMLTextFormControlElement::forwardEvent(Event* event)
116 {
117     if (event->type() == eventNames().blurEvent || event->type() == eventNames().focusEvent)
118         return;
119     innerTextElement()->defaultEventHandler(event);
120 }
121
122 String 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
142 static bool isNotLineBreak(UChar ch) { return ch != newlineCharacter && ch != carriageReturn; }
143
144 bool HTMLTextFormControlElement::isPlaceholderEmpty() const
145 {
146     const AtomicString& attributeValue = fastGetAttribute(placeholderAttr);
147     return attributeValue.string().find(isNotLineBreak) == notFound;
148 }
149
150 bool 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
160 void 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
172 void 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
190 RenderTextControl* HTMLTextFormControlElement::textRendererAfterUpdateLayout()
191 {
192     if (!isTextFormControl())
193         return 0;
194     document()->updateLayoutIgnorePendingStylesheets();
195     return toRenderTextControl(renderer());
196 }
197
198 void HTMLTextFormControlElement::setSelectionStart(int start)
199 {
200     setSelectionRange(start, max(start, selectionEnd()), selectionDirection());
201 }
202
203 void HTMLTextFormControlElement::setSelectionEnd(int end)
204 {
205     setSelectionRange(min(end, selectionStart()), end, selectionDirection());
206 }
207
208 void HTMLTextFormControlElement::setSelectionDirection(const String& direction)
209 {
210     setSelectionRange(selectionStart(), selectionEnd(), direction);
211 }
212
213 void HTMLTextFormControlElement::select()
214 {
215     setSelectionRange(0, numeric_limits<int>::max(), SelectionHasNoDirection);
216 }
217
218 String HTMLTextFormControlElement::selectedText() const
219 {
220     if (!isTextFormControl())
221         return String();
222     return value().substring(selectionStart(), selectionEnd() - selectionStart());
223 }
224
225 void HTMLTextFormControlElement::dispatchFormControlChangeEvent()
226 {
227     if (m_textAsOfLastFormControlChangeEvent != value()) {
228         HTMLElement::dispatchChangeEvent();
229         setTextAsOfLastFormControlChangeEvent(value());
230     }
231     setChangedSinceLastFormControlChangeEvent(false);
232 }
233
234 static 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
241 void HTMLTextFormControlElement::setRangeText(const String& replacement, ExceptionCode& ec)
242 {
243     setRangeText(replacement, selectionStart(), selectionEnd(), String(), ec);
244 }
245
246 void 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
300 void 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
311 void 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
350 int 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
361 int 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
371 int 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
381 int 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
390 int 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
400 static 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
419 const 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
429 TextFieldSelectionDirection 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
440 static 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
451 PassRefPtr<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
492 void HTMLTextFormControlElement::restoreCachedSelection()
493 {
494     setSelectionRange(m_cachedSelectionStart, m_cachedSelectionEnd, m_cachedSelectionDirection);
495 }
496
497 void 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
511 void 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
520 bool HTMLTextFormControlElement::lastChangeWasUserEdit() const
521 {
522     if (!isTextFormControl())
523         return false;
524     return m_lastChangeWasUserEdit;
525 }
526
527 void 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
547 static 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
556 String 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
572 static 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
589 String 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
633 HTMLTextFormControlElement* 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
646 static 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
656 String 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