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())) { if (position.isAfterAnchor()) return Position(position.anchorNode(), PositionAnchorType::BeforeAnchor); if (position.isBeforeAnchor()) return previousIfPositionIsAfterLineBreak(endOfPrevious(*position.anchorNode(), innerEditor), innerEditor); // We don't place caret into BR element, since well-formed BR element // doesn't have child nodes. ASSERT_NOT_REACHED(); return position; } if (!position.anchorNode()->isTextNode()) return position; 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; }
void HTMLTextAreaElement::subtreeHasChanged() { #if DCHECK_IS_ON() // The innerEditor should have either Text nodes or a placeholder break // element. If we see other nodes, it's a bug in editing code and we should // fix it. Element* innerEditor = innerEditorElement(); for (Node& node : NodeTraversal::descendantsOf(*innerEditor)) { if (node.isTextNode()) continue; DCHECK(isHTMLBRElement(node)); DCHECK_EQ(&node, innerEditor->lastChild()); } #endif addPlaceholderBreakElementIfNecessary(); setChangedSinceLastFormControlChangeEvent(true); m_valueIsUpToDate = false; setNeedsValidityCheck(); setAutofilled(false); updatePlaceholderVisibility(); if (!isFocused()) return; // When typing in a textarea, childrenChanged is not called, so we need to // force the directionality check. calculateAndAdjustDirectionality(); DCHECK(document().isActive()); document().frameHost()->chromeClient().didChangeValueInTextField(*this); }
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 (isHTMLBRElement(node) && (!isPivotNode || pivotPosition.isAfterAnchor())) return Position(node, PositionAnchorType::AfterAnchor); if (node->isTextNode()) { Text* textNode = toText(node); size_t lastLineBreak = textNode->data().substring(0, isPivotNode ? pivotPosition.offsetInContainerNode() : textNode->length()).reverseFind('\n'); if (lastLineBreak != kNotFound) return Position(textNode, lastLineBreak + 1); } } return startOfInnerText(textFormControl); }
void HTMLTextFormControlElement::setInnerEditorValue(const String& value) { ASSERT(!openShadowRoot()); if (!isTextFormControl() || openShadowRoot()) return; bool textIsChanged = value != innerEditorValue(); HTMLElement* innerEditor = innerEditorElement(); if (!textIsChanged && innerEditor->hasChildren()) return; // If the last child is a trailing <br> that's appended below, remove it // first so as to enable setInnerText() fast path of updating a text node. if (isHTMLBRElement(innerEditor->lastChild())) innerEditor->removeChild(innerEditor->lastChild(), ASSERT_NO_EXCEPTION); // We don't use setTextContent. It triggers unnecessary paint. if (value.isEmpty()) innerEditor->removeChildren(); else replaceChildrenWithText(innerEditor, value, ASSERT_NO_EXCEPTION); // Add <br> so that we can put the caret at the next line of the last // newline. addPlaceholderBreakElementIfNecessary(); if (textIsChanged && layoutObject()) { if (AXObjectCache* cache = document().existingAXObjectCache()) cache->handleTextFormControlChanged(this); } }
Position HTMLTextFormControlElement::endOfSentence(const Position& position) { HTMLTextFormControlElement* textFormControl = enclosingTextFormControl(position); ASSERT(textFormControl); HTMLElement* innerEditor = textFormControl->innerEditorElement(); if (innerEditor->childNodes()->length() == 0) 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 (isHTMLBRElement(node)) return Position(node, PositionAnchorType::AfterAnchor); if (node->isTextNode()) { Text* textNode = toText(node); size_t firstLineBreak = textNode->data().find('\n', isPivotNode ? pivotPosition.offsetInContainerNode() : 0); if (firstLineBreak != kNotFound) return Position(textNode, firstLineBreak + 1); } } return endOfInnerText(textFormControl); }
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 Position endOfPrevious(const Node& node, HTMLElement* innerEditor) { Node* previousNode = NodeTraversal::previous(node, innerEditor); if (!previousNode) return Position(); if (isHTMLBRElement(previousNode)) return Position(previousNode, PositionAnchorType::AfterAnchor); if (previousNode->isTextNode()) return Position(toText(previousNode), toText(previousNode)->length()); return Position(); }
String HTMLTextFormControlElement::innerEditorValue() const { ASSERT(!hasAuthorShadowRoot()); HTMLElement* innerEditor = innerEditorElement(); if (!innerEditor || !isTextFormControl()) return emptyString(); StringBuilder result; for (Node* node = innerEditor; node; node = NodeTraversal::next(*node, innerEditor)) { if (isHTMLBRElement(*node)) result.append(newlineCharacter); else if (node->isTextNode()) result.append(toText(node)->data()); } return finishText(result); }
String HTMLTextFormControlElement::innerEditorValue() const { ASSERT(!hasOpenShadowRoot()); HTMLElement* innerEditor = innerEditorElement(); if (!innerEditor || !isTextFormControl()) return emptyString(); StringBuilder result; for (Node& node : NodeTraversal::inclusiveDescendantsOf(*innerEditor)) { if (isHTMLBRElement(node)) result.append(newlineCharacter); else if (node.isTextNode()) result.append(toText(node).data()); } return finishText(result); }
String HTMLTextFormControlElement::valueWithHardLineBreaks() const { // FIXME: It's not acceptable to ignore the HardWrap setting when there is no layoutObject. // While we have no evidence this has ever been a practical problem, it would be best to fix it some day. HTMLElement* innerText = innerEditorElement(); if (!innerText || !isTextFormControl()) return value(); LayoutBlockFlow* layoutObject = toLayoutBlockFlow(innerText->layoutObject()); if (!layoutObject) return value(); Node* breakNode; unsigned breakOffset; RootInlineBox* line = layoutObject->firstRootBox(); if (!line) return value(); getNextSoftBreak(line, breakNode, breakOffset); StringBuilder result; for (Node& node : NodeTraversal::descendantsOf(*innerText)) { if (isHTMLBRElement(node)) { ASSERT(&node == innerText->lastChild()); if (&node != innerText->lastChild()) result.append(newlineCharacter); } else if (node.isTextNode()) { String data = toText(node).data(); unsigned length = data.length(); unsigned position = 0; while (breakNode == node && breakOffset <= length) { if (breakOffset > position) { result.append(data, position, breakOffset - position); position = breakOffset; result.append(newlineCharacter); } getNextSoftBreak(line, breakNode, breakOffset); } result.append(data, position, length - position); } while (breakNode == node) getNextSoftBreak(line, breakNode, breakOffset); } return result.toString(); }
String HTMLTextFormControlElement::valueWithHardLineBreaks() const { // FIXME: It's not acceptable to ignore the HardWrap setting when there is no renderer. // While we have no evidence this has ever been a practical problem, it would be best to fix it some day. HTMLElement* innerText = innerEditorElement(); if (!innerText || !isTextFormControl()) return value(); RenderBlock* renderer = toRenderBlock(innerText->renderer()); if (!renderer) return value(); Node* breakNode; unsigned breakOffset; RootInlineBox* line = renderer->firstRootBox(); if (!line) return value(); getNextSoftBreak(line, breakNode, breakOffset); StringBuilder result; for (Node* node = innerText->firstChild(); node; node = NodeTraversal::next(*node, innerText)) { if (isHTMLBRElement(*node)) result.append(newlineCharacter); else if (node->isTextNode()) { String data = toText(node)->data(); unsigned length = data.length(); unsigned position = 0; while (breakNode == node && breakOffset <= length) { if (breakOffset > position) { result.append(data, position, breakOffset - position); position = breakOffset; result.append(newlineCharacter); } getNextSoftBreak(line, breakNode, breakOffset); } result.append(data, position, length - position); } while (breakNode == node) getNextSoftBreak(line, breakNode, breakOffset); } return finishText(result); }
PassRefPtrWillBeRawPtr<Range> HTMLTextFormControlElement::selection() const { if (!renderer() || !isTextFormControl()) return nullptr; int start = m_cachedSelectionStart; int end = m_cachedSelectionEnd; ASSERT(start <= end); HTMLElement* innerText = innerEditorElement(); if (!innerText) return nullptr; if (!innerText->firstChild()) return Range::create(document(), innerText, 0, innerText, 0); int offset = 0; Node* startNode = 0; Node* endNode = 0; for (Node* node = innerText->firstChild(); node; node = NodeTraversal::next(*node, innerText)) { ASSERT(!node->firstChild()); ASSERT(node->isTextNode() || isHTMLBRElement(*node)); int length = node->isTextNode() ? lastOffsetInNode(node) : 1; if (offset <= start && start <= offset + length) setContainerAndOffsetForRange(node, start - offset, startNode, start); if (offset <= end && end <= offset + length) { setContainerAndOffsetForRange(node, end - offset, endNode, end); break; } offset += length; } if (!startNode || !endNode) return nullptr; return Range::create(document(), startNode, start, endNode, end); }
Range* HTMLTextFormControlElement::selection() const { if (!layoutObject() || !isTextFormControl()) return nullptr; int start = m_cachedSelectionStart; int end = m_cachedSelectionEnd; ASSERT(start <= end); HTMLElement* innerText = innerEditorElement(); if (!innerText) return nullptr; if (!innerText->hasChildren()) return Range::create(document(), innerText, 0, innerText, 0); int offset = 0; Node* startNode = 0; Node* endNode = 0; for (Node& node : NodeTraversal::descendantsOf(*innerText)) { ASSERT(!node.hasChildren()); ASSERT(node.isTextNode() || isHTMLBRElement(node)); int length = node.isTextNode() ? lastOffsetInNode(&node) : 1; if (offset <= start && start <= offset + length) setContainerAndOffsetForRange(&node, start - offset, startNode, start); if (offset <= end && end <= offset + length) { setContainerAndOffsetForRange(&node, end - offset, endNode, end); break; } offset += length; } if (!startNode || !endNode) return nullptr; return Range::create(document(), startNode, start, endNode, end); }