Chromium Code Reviews| Index: Source/core/html/HTMLTextFormControlElement.cpp |
| diff --git a/Source/core/html/HTMLTextFormControlElement.cpp b/Source/core/html/HTMLTextFormControlElement.cpp |
| index 0434728f24a557dcb6aa4a94af75ce08ceaccc60..34d175ee5f0b302ff158a3dd9fdc08a3daa90022 100644 |
| --- a/Source/core/html/HTMLTextFormControlElement.cpp |
| +++ b/Source/core/html/HTMLTextFormControlElement.cpp |
| @@ -30,6 +30,7 @@ |
| #include "core/HTMLNames.h" |
| #include "core/accessibility/AXObjectCache.h" |
| #include "core/dom/Document.h" |
| +#include "core/dom/NodeList.h" |
| #include "core/dom/NodeTraversal.h" |
| #include "core/dom/Text.h" |
| #include "core/dom/shadow/ShadowRoot.h" |
| @@ -43,6 +44,7 @@ |
| #include "core/rendering/RenderBlock.h" |
| #include "core/rendering/RenderTheme.h" |
| #include "platform/heap/Handle.h" |
| +#include "platform/text/TextBoundaries.h" |
| #include "wtf/text/StringBuilder.h" |
| namespace WebCore { |
| @@ -659,4 +661,246 @@ HTMLElement* HTMLTextFormControlElement::innerEditorElement() const |
| return toHTMLElement(userAgentShadowRoot()->getElementById(ShadowElementNames::innerEditor())); |
| } |
| +static Position innerNodePosition(const Position& innerPosition) |
| +{ |
| + HTMLElement* element = toHTMLElement(innerPosition.anchorNode()); |
|
Yuta Kitamura
2014/07/03 08:39:27
What if the anchor node isn't an HTMLElement?
yoichio
2014/07/04 01:29:44
This function is called with only innerEditorEleme
Yuta Kitamura
2014/07/04 06:12:34
Then that should be ASSERTed.
|
| + RefPtrWillBeRawPtr<NodeList> childNodes = element->childNodes(); |
| + if (!childNodes->length()) |
| + return Position(element, 0, Position::PositionIsOffsetInAnchor); |
| + |
| + unsigned offset = 0; |
| + |
| + switch (innerPosition.anchorType()) { |
| + case Position::PositionIsOffsetInAnchor: |
| + offset = std::max(0, std::min(innerPosition.computeOffsetInContainerNode(), (int)childNodes->length())); |
|
Yuta Kitamura
2014/07/03 08:39:27
1. Can the second argument of max() become negativ
yoichio
2014/07/04 01:29:43
Sorry, This must be offsetInContainerNode(), which
|
| + break; |
| + case Position::PositionIsAfterChildren: |
| + offset = childNodes->length(); |
| + break; |
| + default: |
|
Yuta Kitamura
2014/07/03 08:39:27
What about other Position types?
yoichio
2014/07/04 01:29:43
PositionIsBeforeChildren means offset is 0.
In ou
Yuta Kitamura
2014/07/04 05:23:34
Then you should ASSERT that. It's not okay to have
yoichio
2014/07/04 05:44:41
Done.
|
| + break; |
| + } |
| + |
| + if (offset == childNodes->length()) |
| + return Position(childNodes->item(childNodes->length() - 1), Position::PositionIsAfterAnchor); |
|
Yuta Kitamura
2014/07/03 08:39:27
item(...) can be lastChild().
yoichio
2014/07/04 01:29:44
Done.
|
| + |
| + Node* node = childNodes->item(offset); |
| + if (node->isTextNode()) |
| + return Position(toText(node), 0); |
| + |
| + return Position(node, Position::PositionIsBeforeAnchor); |
| +} |
| + |
| +static Position findWordBoundary(const HTMLElement* innerEditor, const Position& startPosition, const Position endPosition, bool findStart) |
|
Yuta Kitamura
2014/07/03 08:39:27
The type of the third argument should be "const Po
yoichio
2014/07/04 01:29:43
Done.
|
| +{ |
| + Vector<UChar> characters; |
| + Vector<unsigned> lengthList; |
| + Vector<Text*> textList; |
| + // Trace text nodes. |
| + for (Node* node = startPosition.anchorNode(); node; node = NodeTraversal::next(*node, innerEditor)) { |
|
Yuta Kitamura
2014/07/03 08:39:27
If startPosition is of type AfterAnchor or AfterCh
yoichio
2014/07/04 01:29:44
A goal of this loop is to collect all strings in t
|
| + bool isStartNode = node == startPosition.anchorNode(); |
| + bool isEndNode = node == endPosition.anchorNode(); |
|
Yuta Kitamura
2014/07/03 08:39:27
These booleans are smelly, too, if they are of typ
yoichio
2014/07/04 01:29:44
ditto
|
| + if (node->isTextNode()) { |
| + Text* text = toText(node); |
| + const unsigned start = isStartNode ? startPosition.computeOffsetInContainerNode() : 0; |
| + const unsigned end = isEndNode ? endPosition.computeOffsetInContainerNode() : text->data().length(); |
| + for (unsigned offset = start; offset < end; ++offset) |
| + characters.append(text->data()[offset]); |
|
Yuta Kitamura
2014/07/03 08:39:27
This loop looks error-prone.
If you use StringBui
yoichio
2014/07/04 01:29:44
Since findWordBoundary takes UChar*, we need to co
Yuta Kitamura
2014/07/04 05:23:34
It's silly to write this kind of loop by hand. You
yoichio
2014/07/04 05:44:41
Done.
Thanks! I have not gotten the way :)
|
| + const unsigned length = end - start; |
| + |
| + lengthList.append(length); |
| + textList.append(text); |
| + } |
| + |
| + if (isEndNode) |
| + break; |
| + } |
| + |
| + if (!characters.size()) |
| + return startPosition; |
| + |
| + int start, end; |
| + if (!findStart && characters.data()[0] == '\n') { |
| + // findWordBoundary("\ntext", 0, &start, &end) assigns 1 to |end| but we expect 0 at the case. |
| + start = 0; |
| + end = 0; |
| + } else { |
| + findWordBoundary(characters.data(), characters.size(), findStart ? characters.size() : 0, &start, &end); |
| + } |
| + ASSERT(start >= 0); |
| + ASSERT(end >= 0); |
| + unsigned restOffset = findStart ? start : end; |
|
Yuta Kitamura
2014/07/03 08:39:27
nit: "rest" isn't an adjective. It's a noun. You p
yoichio
2014/07/04 01:29:44
Done.
|
| + // Find position. |
| + for (unsigned i = 0; i < lengthList.size(); ++i) { |
| + if (restOffset <= lengthList[i]) |
| + return Position(textList[i], (textList[i] == startPosition.anchorNode()) ? restOffset + startPosition.computeOffsetInContainerNode() : restOffset); |
| + restOffset -= lengthList[i]; |
| + } |
| + |
| + ASSERT_NOT_REACHED(); |
| + return Position(); |
| +} |
| + |
| +Position HTMLTextFormControlElement::startOfWord(const Position& position) |
| +{ |
| + const HTMLTextFormControlElement* textFormControl = enclosingTextFormControl(position); |
| + ASSERT(textFormControl); |
| + HTMLElement* innerEditor = textFormControl->innerEditorElement(); |
| + |
| + const Position startPosition = startOfSentence(position); |
| + if (startPosition == position) |
| + return position; |
| + const Position endPosition = (position.anchorNode() == innerEditor) ? innerNodePosition(position) : position; |
| + |
| + return findWordBoundary(innerEditor, startPosition, endPosition, true); |
| +} |
| + |
| +Position HTMLTextFormControlElement::endOfWord(const Position& position) |
| +{ |
| + const HTMLTextFormControlElement* textFormControl = enclosingTextFormControl(position); |
| + ASSERT(textFormControl); |
| + HTMLElement* innerEditor = textFormControl->innerEditorElement(); |
| + |
| + |
| + const Position endPosition = endOfSentence(position); |
| + if (endPosition == position) |
| + return position; |
| + const Position startPosition = (position.anchorNode() == innerEditor) ? innerNodePosition(position) : position; |
| + |
| + return findWordBoundary(innerEditor, startPosition, endPosition, false); |
| +} |
| + |
| +static int findLastLineBreak(const Text& text, int offset) |
| +{ |
| + for (; offset >= 0; --offset) { |
| + if (text.data()[offset] == '\n') |
| + return offset; |
| + } |
| + return -1; |
| +} |
| + |
| +static Position endOfPrevious(const Node& node, HTMLElement* innerEditor) |
| +{ |
| + Node* prev = NodeTraversal::previous(node, innerEditor); |
| + if (!prev) |
| + return Position(); |
| + |
| + if (prev->hasTagName(brTag)) |
| + return Position(prev, Position::PositionIsAfterAnchor); |
| + |
| + if (prev->isTextNode()) { |
| + Text* textNode = toText(prev); |
| + return Position(textNode, textNode->length()); |
| + } |
| + |
| + return Position(); |
| +} |
| + |
| +static Position previousIfPositionIsAfterLineBreak(const Position& position, HTMLElement* innerEditor) |
| +{ |
| + if (position.isNull()) |
| + return Position(); |
| + |
| + // Move back if position is just after line break. |
| + if (isHTMLBRElement(*position.anchorNode())) { |
| + switch (position.anchorType()) { |
| + case Position::PositionIsAfterAnchor: |
| + return Position(position.anchorNode(), Position::PositionIsBeforeAnchor); |
| + case Position::PositionIsBeforeAnchor: |
| + return previousIfPositionIsAfterLineBreak(endOfPrevious(*position.anchorNode(), innerEditor), innerEditor); |
| + default: |
| + ASSERT_NOT_REACHED(); |
| + } |
| + } else if (position.anchorNode()->isTextNode()) { |
| + Text* textNode = toText(position.anchorNode()); |
| + unsigned offset = position.offsetInContainerNode(); |
| + if (textNode->length() == 0 || offset <= 0) { |
| + return previousIfPositionIsAfterLineBreak(endOfPrevious(*position.anchorNode(), innerEditor), innerEditor); |
| + } |
| + |
| + if (offset <= textNode->length() && textNode->data()[offset - 1] == '\n') { |
| + return Position(textNode, offset - 1); |
| + } |
| + } |
| + |
| + return position; |
| +} |
| + |
| +static inline Position startOfInnerText(const HTMLTextFormControlElement* textFormControl) |
| +{ |
| + return Position(textFormControl->innerEditorElement(), 0, Position::PositionIsOffsetInAnchor); |
| +} |
| + |
| +Position HTMLTextFormControlElement::startOfSentence(const Position& position) |
| +{ |
| + HTMLTextFormControlElement* textFormControl = enclosingTextFormControl(position); |
| + ASSERT(textFormControl); |
| + |
| + HTMLElement* innerEditor = textFormControl->innerEditorElement(); |
| + if (!innerEditor->childNodes()->length()) |
| + return startOfInnerText(textFormControl); |
| + |
| + const Position innerPosition = position.anchorNode() == innerEditor ? innerNodePosition(position) : position; |
| + const Position pivotPosition = previousIfPositionIsAfterLineBreak(innerPosition, innerEditor); |
| + if (pivotPosition.isNull()) |
| + return startOfInnerText(textFormControl); |
| + |
| + for (Node* node = pivotPosition.anchorNode(); node; node = NodeTraversal::previous(*node, innerEditor)) { |
| + bool isPivotNode = (node == pivotPosition.anchorNode()); |
| + |
| + if (node->isTextNode()) { |
| + Text* textNode = toText(node); |
| + int lastLineBreak = findLastLineBreak(*textNode, isPivotNode ? pivotPosition.offsetInContainerNode() - 1 : (int)textNode->length() - 1); |
| + if (lastLineBreak >= 0) |
| + return Position(textNode, lastLineBreak + 1); |
| + } else if (isHTMLBRElement(node) && (!isPivotNode || pivotPosition.anchorType() == Position::PositionIsAfterAnchor)) { |
| + return Position(node, Position::PositionIsAfterAnchor); |
| + } |
| + } |
| + return startOfInnerText(textFormControl); |
| +} |
| + |
| +static int findFirstLineBreak(const Text& text, int offset) |
| +{ |
| + for (; (unsigned)offset < text.length(); ++offset) { |
| + if (text.data()[offset] == '\n') |
| + return offset; |
| + } |
| + return -1; |
| +} |
| + |
| +static Position endOfInnerText(const HTMLTextFormControlElement* textFormControl) |
| +{ |
| + HTMLElement* innerEditor = textFormControl->innerEditorElement(); |
| + return Position(innerEditor, innerEditor->childNodes()->length(), Position::PositionIsOffsetInAnchor); |
| +} |
| + |
| +Position HTMLTextFormControlElement::endOfSentence(const Position& position) |
| +{ |
| + HTMLTextFormControlElement* textFormControl = enclosingTextFormControl(position); |
| + ASSERT(textFormControl); |
| + |
| + HTMLElement* innerEditor = textFormControl->innerEditorElement(); |
| + if (!innerEditor->childNodes()->length()) |
| + return startOfInnerText(textFormControl); |
| + |
| + const Position pivotPosition = position.anchorNode() == innerEditor ? innerNodePosition(position) : position; |
| + if (pivotPosition.isNull()) |
| + return startOfInnerText(textFormControl); |
| + |
| + for (Node* node = pivotPosition.anchorNode(); node; node = NodeTraversal::next(*node, innerEditor)) { |
| + bool isPivotNode = (node == pivotPosition.anchorNode()); |
| + |
| + if (node->isTextNode()) { |
| + Text* textNode = toText(node); |
| + int firstLineBreak = findFirstLineBreak(*textNode, isPivotNode ? pivotPosition.offsetInContainerNode() : 0); |
| + if (firstLineBreak >= 0) |
| + return Position(textNode, firstLineBreak + 1); |
| + } else if (isHTMLBRElement(node)) { |
| + return Position(node, Position::PositionIsAfterAnchor); |
| + } |
| + } |
| + return endOfInnerText(textFormControl); |
| +} |
| + |
| } // namespace Webcore |