static int indexForPosition(HTMLElement* innerEditor, const Position& passedPosition) { if (!innerEditor || !innerEditor->contains(passedPosition.anchorNode()) || passedPosition.isNull()) return 0; if (positionBeforeNode(innerEditor) == passedPosition) return 0; int index = 0; Node* startNode = passedPosition.computeNodeBeforePosition(); if (!startNode) startNode = passedPosition.computeContainerNode(); ASSERT(startNode); ASSERT(innerEditor->contains(startNode)); for (Node* node = startNode; node; node = NodeTraversal::previous(*node, innerEditor)) { if (node->isTextNode()) { int length = toText(*node).length(); if (node == passedPosition.computeContainerNode()) index += std::min(length, passedPosition.offsetInContainerNode()); else index += length; } else if (node->hasTagName(brTag)) { ++index; } } ASSERT(index >= 0); return index; }
static Position positionForIndex(HTMLElement* innerEditor, int index) { ASSERT(index >= 0); if (index == 0) { Node* node = NodeTraversal::next(*innerEditor, innerEditor); if (node && node->isTextNode()) return Position(node, 0); return Position(innerEditor, 0); } int remainingCharactersToMoveForward = index; Node* lastBrOrText = innerEditor; for (Node& node : NodeTraversal::descendantsOf(*innerEditor)) { ASSERT(remainingCharactersToMoveForward >= 0); if (node.hasTagName(brTag)) { if (remainingCharactersToMoveForward == 0) return positionBeforeNode(&node); --remainingCharactersToMoveForward; lastBrOrText = &node; continue; } if (node.isTextNode()) { Text& text = toText(node); if (remainingCharactersToMoveForward < static_cast<int>(text.length())) return Position(&text, remainingCharactersToMoveForward); remainingCharactersToMoveForward -= text.length(); lastBrOrText = &node; continue; } ASSERT_NOT_REACHED(); } return lastPositionInOrAfterNode(lastBrOrText); }
static Position adjustPositionForStart(const Position& currentPosition, Node* endContainerNode) { TreeScope& treeScope = endContainerNode->treeScope(); ASSERT(¤tPosition.containerNode()->treeScope() != &treeScope); if (Node* ancestor = treeScope.ancestorInThisScope(currentPosition.containerNode())) { if (ancestor->contains(endContainerNode)) return positionBeforeNode(ancestor); return positionAfterNode(ancestor); } if (Node* firstChild = treeScope.rootNode().firstChild()) return positionBeforeNode(firstChild); return Position(); }
static Position adjustPositionForEnd(const Position& currentPosition, Node* startContainerNode) { TreeScope& treeScope = startContainerNode->treeScope(); ASSERT(currentPosition.containerNode()->treeScope() != treeScope); if (Node* ancestor = treeScope.ancestorInThisScope(currentPosition.containerNode())) { if (ancestor->contains(startContainerNode)) return positionAfterNode(ancestor); return positionBeforeNode(ancestor); } if (Node* lastChild = treeScope.rootNode().lastChild()) return positionAfterNode(lastChild); return Position(); }
void CreateLinkCommand::doApply() { if (endingSelection().isNone()) return; RefPtr<HTMLAnchorElement> anchorElement = new HTMLAnchorElement(document()); anchorElement->setHref(m_url); if (endingSelection().isRange()) { pushPartiallySelectedAnchorElementsDown(); applyStyledElement(anchorElement.get()); } else { insertNodeAt(anchorElement.get(), endingSelection().start()); RefPtr<Text> textNode = new Text(document(), m_url); appendNode(textNode.get(), anchorElement.get()); setEndingSelection(Selection(positionBeforeNode(anchorElement.get()), positionAfterNode(anchorElement.get()), DOWNSTREAM)); } }
static Position positionForIndex(TextControlInnerTextElement* innerText, unsigned index) { unsigned remainingCharactersToMoveForward = index; Node* lastBrOrText = innerText; for (Node* node = innerText; node; node = NodeTraversal::next(*node, innerText)) { if (node->hasTagName(brTag)) { if (!remainingCharactersToMoveForward) return positionBeforeNode(node); remainingCharactersToMoveForward--; lastBrOrText = node; } else if (is<Text>(*node)) { Text& text = downcast<Text>(*node); if (remainingCharactersToMoveForward < text.length()) return Position(&text, remainingCharactersToMoveForward); remainingCharactersToMoveForward -= text.length(); lastBrOrText = node; } } return lastPositionInOrAfterNode(lastBrOrText); }
void VisibleSelection::adjustSelectionToAvoidCrossingShadowBoundaries() { if (m_base.isNull() || m_start.isNull() || m_end.isNull()) return; Node* startRootNode = m_start.anchorNode()->shadowTreeRootNode(); Node* endRootNode = m_end.anchorNode()->shadowTreeRootNode(); if (!startRootNode && !endRootNode) return; if (startRootNode == endRootNode) return; if (m_baseIsFirst) { m_extent = startRootNode ? lastPositionInNode(startRootNode) : positionBeforeNode(endRootNode->shadowHost()); m_end = m_extent; } else { m_extent = endRootNode ? firstPositionInNode(endRootNode) : positionAfterNode(startRootNode->shadowHost()); m_start = m_extent; } }
unsigned HTMLTextFormControlElement::indexForPosition(const Position& passedPosition) const { TextControlInnerTextElement* innerText = innerTextElement(); if (!innerText || !innerText->contains(passedPosition.anchorNode()) || passedPosition.isNull()) return 0; if (positionBeforeNode(innerText) == passedPosition) return 0; unsigned index = 0; Node* startNode = passedPosition.computeNodeBeforePosition(); if (!startNode) startNode = passedPosition.containerNode(); ASSERT(startNode); ASSERT(innerText->contains(startNode)); for (Node* node = startNode; node; node = NodeTraversal::previous(*node, innerText)) { if (is<Text>(*node)) { unsigned length = downcast<Text>(*node).length(); if (node == passedPosition.containerNode()) index += std::min<unsigned>(length, passedPosition.offsetInContainerNode()); else index += length; } else if (is<HTMLBRElement>(*node)) ++index; } unsigned length = innerTextValue().length(); index = std::min(index, length); // FIXME: We shouldn't have to call innerTextValue() just to ignore the last LF. See finishText. #ifndef ASSERT_DISABLED VisiblePosition visiblePosition = passedPosition; unsigned indexComputedByVisiblePosition = 0; if (visiblePosition.isNotNull()) indexComputedByVisiblePosition = WebCore::indexForVisiblePosition(innerText, visiblePosition, false /* forSelectionPreservation */); ASSERT(index == indexComputedByVisiblePosition); #endif return index; }
Position toPositionInDOMTree(const PositionInComposedTree& position) { if (position.isNull()) return Position(); Node* anchorNode = position.anchorNode(); switch (position.anchorType()) { case PositionAnchorType::AfterChildren: // FIXME: When anchorNode is <img>, assertion fails in the constructor. return Position(anchorNode, PositionAnchorType::AfterChildren); case PositionAnchorType::AfterAnchor: return positionAfterNode(anchorNode); case PositionAnchorType::BeforeChildren: return Position(anchorNode, PositionAnchorType::BeforeChildren); case PositionAnchorType::BeforeAnchor: return positionBeforeNode(anchorNode); case PositionAnchorType::OffsetInAnchor: { int offset = position.offsetInContainerNode(); if (anchorNode->offsetInCharacters()) return Position(anchorNode, offset); Node* child = ComposedTreeTraversal::childAt(*anchorNode, offset); if (child) return Position(child->parentNode(), child->nodeIndex()); if (!position.offsetInContainerNode()) return Position(anchorNode, PositionAnchorType::BeforeChildren); // |child| is null when the position is at the end of the children. // <div>foo|</div> return Position(anchorNode, PositionAnchorType::AfterChildren); } default: ASSERT_NOT_REACHED(); return Position(); } }
void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries() { if (m_base.isNull() || m_start.isNull() || m_end.isNull()) return; // Early return in the caret case (the state hasn't actually been set yet, so we can't use isCaret()) to avoid the // expense of computing highestEditableRoot. if (m_base == m_start && m_base == m_end) return; Node* baseRoot = highestEditableRoot(m_base); Node* startRoot = highestEditableRoot(m_start); Node* endRoot = highestEditableRoot(m_end); Node* baseEditableAncestor = lowestEditableAncestor(m_base.containerNode()); // The base, start and end are all in the same region. No adjustment necessary. if (baseRoot == startRoot && baseRoot == endRoot) return; // The selection is based in editable content. if (baseRoot) { // If the start is outside the base's editable root, cap it at the start of that root. // If the start is in non-editable content that is inside the base's editable root, put it // at the first editable position after start inside the base's editable root. if (startRoot != baseRoot) { VisiblePosition first = firstEditablePositionAfterPositionInRoot(m_start, baseRoot); m_start = first.deepEquivalent(); if (m_start.isNull()) { ASSERT_NOT_REACHED(); m_start = m_end; } } // If the end is outside the base's editable root, cap it at the end of that root. // If the end is in non-editable content that is inside the base's root, put it // at the last editable position before the end inside the base's root. if (endRoot != baseRoot) { VisiblePosition last = lastEditablePositionBeforePositionInRoot(m_end, baseRoot); m_end = last.deepEquivalent(); if (m_end.isNull()) m_end = m_start; } // The selection is based in non-editable content. } else { // FIXME: Non-editable pieces inside editable content should be atomic, in the same way that editable // pieces in non-editable content are atomic. // The selection ends in editable content or non-editable content inside a different editable ancestor, // move backward until non-editable content inside the same lowest editable ancestor is reached. Node* endEditableAncestor = lowestEditableAncestor(m_end.containerNode()); if (endRoot || endEditableAncestor != baseEditableAncestor) { Position p = previousVisuallyDistinctCandidate(m_end); Node* shadowAncestor = endRoot ? endRoot->shadowHost() : 0; if (p.isNull() && shadowAncestor) p = positionAfterNode(shadowAncestor); while (p.isNotNull() && !(lowestEditableAncestor(p.containerNode()) == baseEditableAncestor && !isEditablePosition(p))) { Node* root = editableRootForPosition(p); shadowAncestor = root ? root->shadowHost() : 0; p = isAtomicNode(p.containerNode()) ? positionInParentBeforeNode(p.containerNode()) : previousVisuallyDistinctCandidate(p); if (p.isNull() && shadowAncestor) p = positionAfterNode(shadowAncestor); } VisiblePosition previous(p); if (previous.isNull()) { // The selection crosses an Editing boundary. This is a // programmer error in the editing code. Happy debugging! ASSERT_NOT_REACHED(); m_base = Position(); m_extent = Position(); validate(); return; } m_end = previous.deepEquivalent(); } // The selection starts in editable content or non-editable content inside a different editable ancestor, // move forward until non-editable content inside the same lowest editable ancestor is reached. Node* startEditableAncestor = lowestEditableAncestor(m_start.containerNode()); if (startRoot || startEditableAncestor != baseEditableAncestor) { Position p = nextVisuallyDistinctCandidate(m_start); Node* shadowAncestor = startRoot ? startRoot->shadowHost() : 0; if (p.isNull() && shadowAncestor) p = positionBeforeNode(shadowAncestor); while (p.isNotNull() && !(lowestEditableAncestor(p.containerNode()) == baseEditableAncestor && !isEditablePosition(p))) { Node* root = editableRootForPosition(p); shadowAncestor = root ? root->shadowHost() : 0; p = isAtomicNode(p.containerNode()) ? positionInParentAfterNode(p.containerNode()) : nextVisuallyDistinctCandidate(p); if (p.isNull() && shadowAncestor) p = positionBeforeNode(shadowAncestor); } VisiblePosition next(p); if (next.isNull()) { // The selection crosses an Editing boundary. This is a // programmer error in the editing code. Happy debugging! ASSERT_NOT_REACHED(); m_base = Position(); m_extent = Position(); validate(); return; } m_start = next.deepEquivalent(); } } // Correct the extent if necessary. if (baseEditableAncestor != lowestEditableAncestor(m_extent.containerNode())) m_extent = m_baseIsFirst ? m_end : m_start; }
void InsertParagraphSeparatorCommand::doApply() { if (!endingSelection().isNonOrphanedCaretOrRange()) return; Position insertionPosition = endingSelection().start(); EAffinity affinity = endingSelection().affinity(); // Delete the current selection. if (endingSelection().isRange()) { calculateStyleBeforeInsertion(insertionPosition); deleteSelection(false, true); insertionPosition = endingSelection().start(); affinity = endingSelection().affinity(); } // FIXME: The parentAnchoredEquivalent conversion needs to be moved into enclosingBlock. RefPtr<Element> startBlock = enclosingBlock(insertionPosition.parentAnchoredEquivalent().containerNode()); Position canonicalPos = VisiblePosition(insertionPosition).deepEquivalent(); if (!startBlock || !startBlock->nonShadowBoundaryParentNode() // FIXME: If the node is hidden, we don't have a canonical position so we will do the wrong thing for tables and <hr>. https://bugs.webkit.org/show_bug.cgi?id=40342 || (!canonicalPos.isNull() && isRenderedTableElement(canonicalPos.deprecatedNode()))) { applyCommandToComposite(InsertLineBreakCommand::create(document())); return; } // Use the leftmost candidate. insertionPosition = insertionPosition.upstream(); if (!insertionPosition.isCandidate()) insertionPosition = insertionPosition.downstream(); // Adjust the insertion position after the delete insertionPosition = positionAvoidingSpecialElementBoundary(insertionPosition); VisiblePosition visiblePos(insertionPosition, affinity); calculateStyleBeforeInsertion(insertionPosition); //--------------------------------------------------------------------- // Handle special case of typing return on an empty list item if (breakOutOfEmptyListItem()) return; //--------------------------------------------------------------------- // Prepare for more general cases. bool isFirstInBlock = isStartOfBlock(visiblePos); bool isLastInBlock = isEndOfBlock(visiblePos); bool nestNewBlock = false; // Create block to be inserted. RefPtr<Element> blockToInsert = nullptr; if (startBlock->isRootEditableElement()) { blockToInsert = createDefaultParagraphElement(document()); nestNewBlock = true; } else if (shouldUseDefaultParagraphElement(startBlock.get())) { blockToInsert = createDefaultParagraphElement(document()); } else { blockToInsert = startBlock->cloneElementWithoutChildren(); } //--------------------------------------------------------------------- // Handle case when position is in the last visible position in its block, // including when the block is empty. if (isLastInBlock) { if (nestNewBlock) { if (isFirstInBlock && !lineBreakExistsAtVisiblePosition(visiblePos)) { // The block is empty. Create an empty block to // represent the paragraph that we're leaving. RefPtr<HTMLElement> extraBlock = createDefaultParagraphElement(document()); appendNode(extraBlock, startBlock); } appendNode(blockToInsert, startBlock); } else { // Most of the time we want to stay at the nesting level of the startBlock (e.g., when nesting within lists). However, // for div nodes, this can result in nested div tags that are hard to break out of. Element* siblingElement = startBlock.get(); insertNodeAfter(blockToInsert, siblingElement); } // Recreate the same structure in the new paragraph. Vector<RefPtr<Element> > ancestors; getAncestorsInsideBlock(positionOutsideTabSpan(insertionPosition).deprecatedNode(), startBlock.get(), ancestors); RefPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert); setEndingSelection(VisibleSelection(firstPositionInNode(parent.get()), DOWNSTREAM, endingSelection().isDirectional())); return; } //--------------------------------------------------------------------- // Handle case when position is in the first visible position in its block, and // similar case where previous position is in another, presumeably nested, block. if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) { Node* refNode = 0; insertionPosition = positionOutsideTabSpan(insertionPosition); if (isFirstInBlock && !nestNewBlock) { refNode = startBlock.get(); } else if (isFirstInBlock && nestNewBlock) { // startBlock should always have children, otherwise isLastInBlock would be true and it's handled above. ASSERT(startBlock->hasChildren()); refNode = startBlock->firstChild(); } else if (insertionPosition.deprecatedNode() == startBlock && nestNewBlock) { refNode = NodeTraversal::childAt(*startBlock, insertionPosition.deprecatedEditingOffset()); ASSERT(refNode); // must be true or we'd be in the end of block case } else refNode = insertionPosition.deprecatedNode(); // find ending selection position easily before inserting the paragraph insertionPosition = insertionPosition.downstream(); if (refNode) insertNodeBefore(blockToInsert, refNode); // Recreate the same structure in the new paragraph. Vector<RefPtr<Element> > ancestors; getAncestorsInsideBlock(positionAvoidingSpecialElementBoundary(positionOutsideTabSpan(insertionPosition)).deprecatedNode(), startBlock.get(), ancestors); // In this case, we need to set the new ending selection. setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM, endingSelection().isDirectional())); return; } //--------------------------------------------------------------------- // Handle the (more complicated) general case, // Move downstream. Typing style code will take care of carrying along the // style of the upstream position. insertionPosition = insertionPosition.downstream(); // At this point, the insertionPosition's node could be a container, and we want to make sure we include // all of the correct nodes when building the ancestor list. So this needs to be the deepest representation of the position // before we walk the DOM tree. insertionPosition = positionOutsideTabSpan(VisiblePosition(insertionPosition).deepEquivalent()); // If the returned position lies either at the end or at the start of an element that is ignored by editing // we should move to its upstream or downstream position. if (editingIgnoresContent(insertionPosition.deprecatedNode())) { if (insertionPosition.atLastEditingPositionForNode()) insertionPosition = insertionPosition.downstream(); else if (insertionPosition.atFirstEditingPositionForNode()) insertionPosition = insertionPosition.upstream(); } // Make sure we do not cause a rendered space to become unrendered. // FIXME: We need the affinity for pos, but pos.downstream() does not give it Position leadingWhitespace = leadingWhitespacePosition(insertionPosition, VP_DEFAULT_AFFINITY); // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions // after the preserved newline, causing the newline to be turned into a nbsp. if (leadingWhitespace.isNotNull() && leadingWhitespace.deprecatedNode()->isTextNode()) { Text* textNode = toText(leadingWhitespace.deprecatedNode()); ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace()); replaceTextInNodePreservingMarkers(textNode, leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); } // Split at pos if in the middle of a text node. Position positionAfterSplit; if (insertionPosition.anchorType() == Position::PositionIsOffsetInAnchor && insertionPosition.containerNode()->isTextNode()) { RefPtr<Text> textNode = toText(insertionPosition.containerNode()); bool atEnd = static_cast<unsigned>(insertionPosition.offsetInContainerNode()) >= textNode->length(); if (insertionPosition.deprecatedEditingOffset() > 0 && !atEnd) { splitTextNode(textNode, insertionPosition.offsetInContainerNode()); positionAfterSplit = firstPositionInNode(textNode.get()); insertionPosition.moveToPosition(textNode->previousSibling(), insertionPosition.offsetInContainerNode()); visiblePos = VisiblePosition(insertionPosition); } } // If we got detached due to mutation events, just bail out. if (!startBlock->parentNode()) return; // Put the added block in the tree. if (nestNewBlock) { appendNode(blockToInsert.get(), startBlock); } else { insertNodeAfter(blockToInsert.get(), startBlock); } document().updateLayoutIgnorePendingStylesheets(); // Move the start node and the siblings of the start node. if (VisiblePosition(insertionPosition) != VisiblePosition(positionBeforeNode(blockToInsert.get()))) { Node* n; if (insertionPosition.containerNode() == startBlock) n = insertionPosition.computeNodeAfterPosition(); else { Node* splitTo = insertionPosition.containerNode(); if (splitTo->isTextNode() && insertionPosition.offsetInContainerNode() >= caretMaxOffset(splitTo)) splitTo = NodeTraversal::next(*splitTo, startBlock.get()); ASSERT(splitTo); splitTreeToNode(splitTo, startBlock.get()); for (n = startBlock->firstChild(); n; n = n->nextSibling()) { VisiblePosition beforeNodePosition(positionBeforeNode(n)); if (!beforeNodePosition.isNull() && comparePositions(VisiblePosition(insertionPosition), beforeNodePosition) <= 0) break; } } moveRemainingSiblingsToNewParent(n, blockToInsert.get(), blockToInsert); } // Handle whitespace that occurs after the split if (positionAfterSplit.isNotNull()) { document().updateLayoutIgnorePendingStylesheets(); if (!positionAfterSplit.isRenderedCharacter()) { // Clear out all whitespace and insert one non-breaking space ASSERT(!positionAfterSplit.containerNode()->renderer() || positionAfterSplit.containerNode()->renderer()->style()->collapseWhiteSpace()); deleteInsignificantTextDownstream(positionAfterSplit); if (positionAfterSplit.deprecatedNode()->isTextNode()) insertTextIntoNode(toText(positionAfterSplit.containerNode()), 0, nonBreakingSpaceString()); } } setEndingSelection(VisibleSelection(firstPositionInNode(blockToInsert.get()), DOWNSTREAM, endingSelection().isDirectional())); }
void Selection::adjustForEditableContent() { if (m_base.isNull() || m_start.isNull() || m_end.isNull()) return; Node* baseRoot = highestEditableRoot(m_base); Node* startRoot = highestEditableRoot(m_start); Node* endRoot = highestEditableRoot(m_end); Node* baseEditableAncestor = lowestEditableAncestor(m_base.node()); // The base, start and end are all in the same region. No adjustment necessary. if (baseRoot == startRoot && baseRoot == endRoot) return; // The selection is based in editable content. if (baseRoot) { // If the start is outside the base's editable root, cap it at the start of that root. // If the start is in non-editable content that is inside the base's editable root, put it // at the first editable position after start inside the base's editable root. if (startRoot != baseRoot) { VisiblePosition first = firstEditablePositionAfterPositionInRoot(m_start, baseRoot); m_start = first.deepEquivalent(); if (m_start.isNull()) { ASSERT_NOT_REACHED(); m_start = m_end; } } // If the end is outside the base's editable root, cap it at the end of that root. // If the end is in non-editable content that is inside the base's root, put it // at the last editable position before the end inside the base's root. if (endRoot != baseRoot) { VisiblePosition last = lastEditablePositionBeforePositionInRoot(m_end, baseRoot); m_end = last.deepEquivalent(); if (m_end.isNull()) { ASSERT_NOT_REACHED(); m_end = m_start; } } // The selection is based in non-editable content. } else { // FIXME: Non-editable pieces inside editable content should be atomic, in the same way that editable // pieces in non-editable content are atomic. // The selection ends in editable content or non-editable content inside a different editable ancestor, // move backward until non-editable content inside the same lowest editable ancestor is reached. Node* endEditableAncestor = lowestEditableAncestor(m_end.node()); if (endRoot || endEditableAncestor != baseEditableAncestor) { Position p = previousVisuallyDistinctCandidate(m_end); Node* shadowAncestor = endRoot ? endRoot->shadowAncestorNode() : 0; if (p.isNull() && endRoot && (shadowAncestor != endRoot)) p = Position(shadowAncestor, maxDeepOffset(shadowAncestor)); while (p.isNotNull() && !(lowestEditableAncestor(p.node()) == baseEditableAncestor && !isEditablePosition(p))) { Node* root = editableRootForPosition(p); shadowAncestor = root ? root->shadowAncestorNode() : 0; p = isAtomicNode(p.node()) ? positionBeforeNode(p.node()) : previousVisuallyDistinctCandidate(p); if (p.isNull() && (shadowAncestor != root)) p = Position(shadowAncestor, maxDeepOffset(shadowAncestor)); } VisiblePosition previous(p); if (previous.isNull()) { ASSERT_NOT_REACHED(); m_base = Position(); m_extent = Position(); validate(); return; } m_end = previous.deepEquivalent(); } // The selection starts in editable content or non-editable content inside a different editable ancestor, // move forward until non-editable content inside the same lowest editable ancestor is reached. Node* startEditableAncestor = lowestEditableAncestor(m_start.node()); if (startRoot || startEditableAncestor != baseEditableAncestor) { Position p = nextVisuallyDistinctCandidate(m_start); Node* shadowAncestor = startRoot ? startRoot->shadowAncestorNode() : 0; if (p.isNull() && startRoot && (shadowAncestor != startRoot)) p = Position(shadowAncestor, 0); while (p.isNotNull() && !(lowestEditableAncestor(p.node()) == baseEditableAncestor && !isEditablePosition(p))) { Node* root = editableRootForPosition(p); shadowAncestor = root ? root->shadowAncestorNode() : 0; p = isAtomicNode(p.node()) ? positionAfterNode(p.node()) : nextVisuallyDistinctCandidate(p); if (p.isNull() && (shadowAncestor != root)) p = Position(shadowAncestor, 0); } VisiblePosition next(p); if (next.isNull()) { ASSERT_NOT_REACHED(); m_base = Position(); m_extent = Position(); validate(); return; } m_start = next.deepEquivalent(); } } // Correct the extent if necessary. if (baseEditableAncestor != lowestEditableAncestor(m_extent.node())) m_extent = m_baseIsFirst ? m_end : m_start; }
static void adjustPositionForUserSelectAll(VisiblePosition& pos, bool isForward) { if (Node* rootUserSelectAll = EditingStrategy::rootUserSelectAllForNode(pos.deepEquivalent().anchorNode())) pos = createVisiblePosition(isForward ? mostForwardCaretPosition(positionAfterNode(rootUserSelectAll), CanCrossEditingBoundary) : mostBackwardCaretPosition(positionBeforeNode(rootUserSelectAll), CanCrossEditingBoundary)); }
void InsertTextCommand::input(const String& originalText, bool selectInsertedText) { String text = originalText; ASSERT(text.find('\n') == -1); if (endingSelection().isNone()) return; if (RenderObject* renderer = endingSelection().start().node()->renderer()) if (renderer->style()->collapseWhiteSpace()) // Turn all spaces into non breaking spaces, to make sure that they are treated // literally, and aren't collapsed after insertion. They will be rebalanced // (turned into a sequence of regular and non breaking spaces) below. text.replace(' ', noBreakSpace); // Delete the current selection. // FIXME: This delete operation blows away the typing style. if (endingSelection().isRange()) { if (performTrivialReplace(text, selectInsertedText)) return; deleteSelection(false, true, true, false); } // Insert the character at the leftmost candidate. Position startPosition = endingSelection().start().upstream(); // It is possible for the node that contains startPosition to contain only unrendered whitespace, // and so deleteInsignificantText could remove it. Save the position before the node in case that happens. Position positionBeforeStartNode(positionBeforeNode(startPosition.node())); deleteInsignificantText(startPosition.upstream(), startPosition.downstream()); if (!startPosition.node()->inDocument()) startPosition = positionBeforeStartNode; if (!startPosition.isCandidate()) startPosition = startPosition.downstream(); startPosition = positionAvoidingSpecialElementBoundary(startPosition); Position endPosition; if (text == "\t") { endPosition = insertTab(startPosition); startPosition = endPosition.previous(); removePlaceholderAt(VisiblePosition(startPosition)); m_charactersAdded += 1; } else { // Make sure the document is set up to receive text startPosition = prepareForTextInsertion(startPosition); removePlaceholderAt(VisiblePosition(startPosition)); Text *textNode = static_cast<Text *>(startPosition.node()); int offset = startPosition.offset(); insertTextIntoNode(textNode, offset, text); endPosition = Position(textNode, offset + text.length()); // The insertion may require adjusting adjacent whitespace, if it is present. rebalanceWhitespaceAt(endPosition); // Rebalancing on both sides isn't necessary if we've inserted a space. if (originalText != " ") rebalanceWhitespaceAt(startPosition); m_charactersAdded += text.length(); } // We could have inserted a part of composed character sequence, // so we are basically treating ending selection as a range to avoid validation. // <http://bugs.webkit.org/show_bug.cgi?id=15781> Selection forcedEndingSelection; forcedEndingSelection.setWithoutValidation(startPosition, endPosition); setEndingSelection(forcedEndingSelection); // Handle the case where there is a typing style. CSSMutableStyleDeclaration* typingStyle = document()->frame()->typingStyle(); RefPtr<CSSComputedStyleDeclaration> endingStyle = endPosition.computedStyle(); RefPtr<CSSValue> unicodeBidi; RefPtr<CSSValue> direction; if (typingStyle) { unicodeBidi = typingStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi); direction = typingStyle->getPropertyCSSValue(CSSPropertyDirection); } endingStyle->diff(typingStyle); if (typingStyle && unicodeBidi) { ASSERT(unicodeBidi->isPrimitiveValue()); typingStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent()); if (direction) { ASSERT(direction->isPrimitiveValue()); typingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent()); } } if (typingStyle && typingStyle->length()) applyStyle(typingStyle); if (!selectInsertedText) setEndingSelection(Selection(endingSelection().end(), endingSelection().affinity())); }