void DOMSelection::extend(Node* node, int offset, ExceptionCode& ec) { if (!m_frame) return; if (!node) { ec = TYPE_MISMATCH_ERR; return; } if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) { ec = INDEX_SIZE_ERR; return; } SelectionController* selection = m_frame->selection(); selection->expandUsingGranularity(CharacterGranularity); selection->setExtent(VisiblePosition(node, offset, DOWNSTREAM)); }
static void sendAXTextChangedIgnoringLineBreaks(Node* node, AXTextEditType type) { if (!node) return; if (!AXObjectCache::accessibilityEnabled()) return; String text = node->nodeValue(); // Don't consider linebreaks in this command if (text == "\n") return; if (AXObjectCache* cache = node->document().existingAXObjectCache()) { Position position = is<Text>(node) ? Position(downcast<Text>(node), 0) : createLegacyEditingPosition(node, 0); cache->postTextStateChangeNotification(node, type, text, VisiblePosition(position)); } }
TEST_F(FrameSelectionTest, MoveRangeSelectionTest) { // "Foo Bar Baz," RefPtrWillBeRawPtr<Text> text = appendTextNode("Foo Bar Baz,"); // Itinitializes with "Foo B|a>r Baz," (| means start and > means end). selection().setSelection(VisibleSelection(Position(text, 5), Position(text, 6))); EXPECT_EQ_SELECTED_TEXT("a"); // "Foo B|ar B>az," with the Character granularity. selection().moveRangeSelection(VisiblePosition(Position(text, 5)), VisiblePosition(Position(text, 9)), CharacterGranularity); EXPECT_EQ_SELECTED_TEXT("ar B"); // "Foo B|ar B>az," with the Word granularity. selection().moveRangeSelection(VisiblePosition(Position(text, 5)), VisiblePosition(Position(text, 9)), WordGranularity); EXPECT_EQ_SELECTED_TEXT("Bar Baz"); // "Fo<o B|ar Baz," with the Character granularity. selection().moveRangeSelection(VisiblePosition(Position(text, 5)), VisiblePosition(Position(text, 2)), CharacterGranularity); EXPECT_EQ_SELECTED_TEXT("o B"); // "Fo<o B|ar Baz," with the Word granularity. selection().moveRangeSelection(VisiblePosition(Position(text, 5)), VisiblePosition(Position(text, 2)), WordGranularity); EXPECT_EQ_SELECTED_TEXT("Foo Bar"); }
void DOMSelection::extend(Node* node, int offset, ExceptionCode& ec) { if (!m_frame) return; if (!node) { ec = TYPE_MISMATCH_ERR; return; } if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) { ec = INDEX_SIZE_ERR; return; } if (!isValidForPosition(node)) return; m_frame->selection()->setExtent(VisiblePosition(node, offset, DOWNSTREAM)); }
Position InsertTextCommand::insertTab(const Position& pos) { Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent(); if (insertPos.isNull()) return pos; Node* node = insertPos.containerNode(); unsigned offset = node->isTextNode() ? insertPos.offsetInContainerNode() : 0; // keep tabs coalesced in tab span if (isTabSpanTextNode(node)) { RefPtrWillBeRawPtr<Text> textNode = toText(node); insertTextIntoNode(textNode, offset, "\t"); return Position(textNode.release(), offset + 1); } // create new tab span RefPtrWillBeRawPtr<Element> spanNode = createTabSpanElement(document()); // place it if (!node->isTextNode()) { insertNodeAt(spanNode.get(), insertPos); } else { RefPtrWillBeRawPtr<Text> textNode = toText(node); if (offset >= textNode->length()) insertNodeAfter(spanNode, textNode.release()); else { // split node to make room for the span // NOTE: splitTextNode uses textNode for the // second node in the split, so we need to // insert the span before it. if (offset > 0) splitTextNode(textNode, offset); insertNodeBefore(spanNode, textNode.release()); } } // return the position following the new tab return lastPositionInNode(spanNode.get()); }
void MoveSelectionCommand::doApply() { ASSERT(endingSelection().isNonOrphanedRange()); Position pos = m_position; if (pos.isNull()) return; // Update the position otherwise it may become invalid after the selection is deleted. Position selectionEnd = endingSelection().end(); if (pos.isOffsetInAnchor() && selectionEnd.isOffsetInAnchor() && selectionEnd.computeContainerNode() == pos.computeContainerNode() && selectionEnd.offsetInContainerNode() < pos.offsetInContainerNode()) { pos = Position(pos.computeContainerNode(), pos.offsetInContainerNode() - selectionEnd.offsetInContainerNode()); Position selectionStart = endingSelection().start(); if (selectionStart.isOffsetInAnchor() && selectionStart.computeContainerNode() == pos.computeContainerNode()) pos = Position(pos.computeContainerNode(), pos.offsetInContainerNode() + selectionStart.offsetInContainerNode()); } deleteSelection(m_smartDelete); // If the node for the destination has been removed as a result of the deletion, // set the destination to the ending point after the deletion. // Fixes: <rdar://problem/3910425> REGRESSION (Mail): Crash in ReplaceSelectionCommand; // selection is empty, leading to null deref if (!pos.inDocument()) pos = endingSelection().start(); cleanupAfterDeletion(VisiblePosition(pos)); setEndingSelection(VisibleSelection(pos, endingSelection().affinity(), endingSelection().isDirectional())); if (!pos.inDocument()) { // Document was modified out from under us. return; } ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::PreventNesting; if (m_smartInsert) options |= ReplaceSelectionCommand::SmartReplace; applyCommandToComposite(ReplaceSelectionCommand::create(document(), m_fragment, options)); }
void DOMSelection::extend(Node* node, int offset, ExceptionState& exceptionState) { ASSERT(node); if (!m_frame) return; if (offset < 0) { exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset."); return; } if (offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->countChildren())) { exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is larger than the given node's length."); return; } if (!isValidForPosition(node)) return; // FIXME: Eliminate legacy editing positions m_frame->selection().setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM)); }
void DOMSelection::extend(Node* node, int offset, ExceptionState& exceptionState) { ASSERT(node); if (!m_frame) return; if (offset < 0) { exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset."); return; } if (static_cast<unsigned>(offset) > node->lengthOfContents()) { exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is larger than the given node's length."); return; } if (!isValidForPosition(node)) return; // FIXME: Eliminate legacy editing positions m_frame->selection().setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM)); }
Position InsertTextCommand::insertTab(const Position& pos) { Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent(); Node *node = insertPos.node(); unsigned int offset = insertPos.offset(); // keep tabs coalesced in tab span if (isTabSpanTextNode(node)) { insertTextIntoNode(static_cast<Text *>(node), offset, "\t"); return Position(node, offset + 1); } // create new tab span RefPtr<Element> spanNode = createTabSpanElement(document()); // place it if (!node->isTextNode()) { insertNodeAt(spanNode.get(), node, offset); } else { Text *textNode = static_cast<Text *>(node); if (offset >= textNode->length()) { insertNodeAfter(spanNode.get(), textNode); } else { // split node to make room for the span // NOTE: splitTextNode uses textNode for the // second node in the split, so we need to // insert the span before it. if (offset > 0) splitTextNode(textNode, offset); insertNodeBefore(spanNode.get(), textNode); } } // return the position following the new tab return Position(spanNode->lastChild(), spanNode->lastChild()->caretMaxOffset()); }
void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(TextGranularity granularity) { if (m_baseIsFirst) { m_start = m_base; m_end = m_extent; } else { m_start = m_extent; m_end = m_base; } switch (granularity) { case CharacterGranularity: // Don't do any expansion. break; case WordGranularity: { // General case: Select the word the caret is positioned inside of, or at the start of (RightWordIfOnBoundary). // Edge case: If the caret is after the last word in a soft-wrapped line or the last word in // the document, select that last word (LeftWordIfOnBoundary). // Edge case: If the caret is after the last word in a paragraph, select from the the end of the // last word to the line break (also RightWordIfOnBoundary); VisiblePosition start = VisiblePosition(m_start, m_affinity); VisiblePosition originalEnd(m_end, m_affinity); EWordSide side = RightWordIfOnBoundary; if (isEndOfDocument(start) || (isEndOfLine(start) && !isStartOfLine(start) && !isEndOfParagraph(start))) side = LeftWordIfOnBoundary; m_start = startOfWord(start, side).deepEquivalent(); side = RightWordIfOnBoundary; if (isEndOfDocument(originalEnd) || (isEndOfLine(originalEnd) && !isStartOfLine(originalEnd) && !isEndOfParagraph(originalEnd))) side = LeftWordIfOnBoundary; VisiblePosition wordEnd(endOfWord(originalEnd, side)); VisiblePosition end(wordEnd); if (isEndOfParagraph(originalEnd) && !isEmptyTableCell(m_start.deprecatedNode())) { // Select the paragraph break (the space from the end of a paragraph to the start of // the next one) to match TextEdit. end = wordEnd.next(); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table. if (isBlock(table)) end = end.next(CannotCrossEditingBoundary); else end = wordEnd; } if (end.isNull()) end = wordEnd; } m_end = end.deepEquivalent(); //added this to select only letters for dictionary viewing if( m_selectOnlyLetters && m_start.anchorNode()->isTextNode() && m_end.anchorNode()->isTextNode() ) { CharacterData* startText = static_cast<CharacterData*>(m_start.anchorNode()); String startStr = startText->data(); CharacterData* endText = static_cast<CharacterData*>(m_end.anchorNode()); String endStr = endText->data(); int n1 = m_start.offsetInContainerNode(); int n2 = m_end.offsetInContainerNode() - 1; //unhandled corner case: at beginning of sentence, m_start may refer //to the Node of the last sentence, this causes the space at the beginning //of a sentence to be part of the selection. Assigning m_end to m_start and //adjusting the offset does not fix this. QChar c1 = startStr[n1]; QChar c2 = endStr[n2]; while(!c1.isLetter() && n1 <= n2) { c1 = startStr[++n1]; m_start = m_start.next(Character); } while(!c2.isLetter() && n2 >= n1) { c2 = endStr[--n2]; m_end = m_end.previous(Character); } } break; } case SentenceGranularity: { m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; } case LineGranularity: { m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); VisiblePosition end = endOfLine(VisiblePosition(m_end, m_affinity)); // If the end of this line is at the end of a paragraph, include the space // after the end of the line in the selection. if (isEndOfParagraph(end)) { VisiblePosition next = end.next(); if (next.isNotNull()) end = next; } m_end = end.deepEquivalent(); break; } case LineBoundary: m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfLine(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphGranularity: { VisiblePosition pos(m_start, m_affinity); if (isStartOfLine(pos) && isEndOfDocument(pos)) pos = pos.previous(); m_start = startOfParagraph(pos).deepEquivalent(); VisiblePosition visibleParagraphEnd = endOfParagraph(VisiblePosition(m_end, m_affinity)); // Include the "paragraph break" (the space from the end of this paragraph to the start // of the next one) in the selection. VisiblePosition end(visibleParagraphEnd.next()); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table, not at the position just after the table. if (isBlock(table)) end = end.next(CannotCrossEditingBoundary); // There is no parargraph break after the last paragraph in the last cell of an inline table. else end = visibleParagraphEnd; } if (end.isNull()) end = visibleParagraphEnd; m_end = end.deepEquivalent(); break; } case DocumentBoundary: m_start = startOfDocument(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfDocument(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphBoundary: m_start = startOfParagraph(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfParagraph(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case SentenceBoundary: m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case WebKitVisualWordGranularity: break; } // Make sure we do not have a dangling start or end. if (m_start.isNull()) m_start = m_end; if (m_end.isNull()) m_end = m_start; }
PassRefPtr<Range> trimWhitespaceFromRange(PassRefPtr<Range> range) { return trimWhitespaceFromRange(VisiblePosition(range->startPosition()), VisiblePosition(range->endPosition())); }
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::validate() { // Move the selection to rendered positions, if possible. bool baseAndExtentEqual = m_base == m_extent; if (m_base.isNotNull()) { m_base = VisiblePosition(m_base, m_affinity).deepEquivalent(); if (baseAndExtentEqual) m_extent = m_base; } if (m_extent.isNotNull() && !baseAndExtentEqual) m_extent = VisiblePosition(m_extent, m_affinity).deepEquivalent(); // Make sure we do not have a dangling base or extent. if (m_base.isNull() && m_extent.isNull()) m_baseIsFirst = true; else if (m_base.isNull()) { m_base = m_extent; m_baseIsFirst = true; } else if (m_extent.isNull()) { m_extent = m_base; m_baseIsFirst = true; } else { m_baseIsFirst = comparePositions(m_base, m_extent) <= 0; } if (m_baseIsFirst) { m_start = m_base; m_end = m_extent; } else { m_start = m_extent; m_end = m_base; } // Expand the selection if requested. switch (m_granularity) { case CharacterGranularity: // Don't do any expansion. break; case WordGranularity: { // General case: Select the word the caret is positioned inside of, or at the start of (RightWordIfOnBoundary). // Edge case: If the caret is after the last word in a soft-wrapped line or the last word in // the document, select that last word (LeftWordIfOnBoundary). // Edge case: If the caret is after the last word in a paragraph, select from the the end of the // last word to the line break (also RightWordIfOnBoundary); VisiblePosition start = VisiblePosition(m_start, m_affinity); VisiblePosition originalEnd(m_end, m_affinity); EWordSide side = RightWordIfOnBoundary; if (isEndOfDocument(start) || (isEndOfLine(start) && !isStartOfLine(start) && !isEndOfParagraph(start))) side = LeftWordIfOnBoundary; m_start = startOfWord(start, side).deepEquivalent(); side = RightWordIfOnBoundary; if (isEndOfDocument(originalEnd) || (isEndOfLine(originalEnd) && !isStartOfLine(originalEnd) && !isEndOfParagraph(originalEnd))) side = LeftWordIfOnBoundary; VisiblePosition wordEnd(endOfWord(originalEnd, side)); VisiblePosition end(wordEnd); if (isEndOfParagraph(originalEnd)) { // Select the paragraph break (the space from the end of a paragraph to the start of // the next one) to match TextEdit. end = wordEnd.next(); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table. if (isBlock(table)) end = end.next(true); else end = wordEnd; } if (end.isNull()) end = wordEnd; } m_end = end.deepEquivalent(); break; } case SentenceGranularity: { m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; } case LineGranularity: { m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); VisiblePosition end = endOfLine(VisiblePosition(m_end, m_affinity)); // If the end of this line is at the end of a paragraph, include the space // after the end of the line in the selection. if (isEndOfParagraph(end)) { VisiblePosition next = end.next(); if (next.isNotNull()) end = next; } m_end = end.deepEquivalent(); break; } case LineBoundary: m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfLine(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphGranularity: { VisiblePosition pos(m_start, m_affinity); if (isStartOfLine(pos) && isEndOfDocument(pos)) pos = pos.previous(); m_start = startOfParagraph(pos).deepEquivalent(); VisiblePosition visibleParagraphEnd = endOfParagraph(VisiblePosition(m_end, m_affinity)); // Include the "paragraph break" (the space from the end of this paragraph to the start // of the next one) in the selection. VisiblePosition end(visibleParagraphEnd.next()); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table, not at the position just after the table. if (isBlock(table)) end = end.next(true); // There is no parargraph break after the last paragraph in the last cell of an inline table. else end = visibleParagraphEnd; } if (end.isNull()) end = visibleParagraphEnd; m_end = end.deepEquivalent(); break; } case DocumentBoundary: m_start = startOfDocument(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfDocument(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphBoundary: m_start = startOfParagraph(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfParagraph(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case SentenceBoundary: m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; } // Make sure we do not have a dangling start or end. if (m_start.isNull()) m_start = m_end; if (m_end.isNull()) m_end = m_start; adjustForEditableContent(); // adjust the state if (m_start.isNull()) { ASSERT(m_end.isNull()); m_state = NONE; // enforce downstream affinity if not caret, as affinity only // makes sense for caret m_affinity = DOWNSTREAM; } else if (m_start == m_end || m_start.upstream() == m_end.upstream()) { m_state = CARET; } else { m_state = RANGE; // enforce downstream affinity if not caret, as affinity only // makes sense for caret m_affinity = DOWNSTREAM; // "Constrain" the selection to be the smallest equivalent range of nodes. // This is a somewhat arbitrary choice, but experience shows that it is // useful to make to make the selection "canonical" (if only for // purposes of comparing selections). This is an ideal point of the code // to do this operation, since all selection changes that result in a RANGE // come through here before anyone uses it. m_start = m_start.downstream(); m_end = m_end.upstream(); } }
VisiblePosition RenderContainer::positionForCoordinates(int x, int y) { // no children...return this render object's element, if there is one, and offset 0 if (!m_firstChild) return VisiblePosition(element(), 0, DOWNSTREAM); if (isTable() && element()) { int right = contentWidth() + borderRight() + paddingRight() + borderLeft() + paddingLeft(); int bottom = contentHeight() + borderTop() + paddingTop() + borderBottom() + paddingBottom(); if (x < 0 || x > right || y < 0 || y > bottom) { if (x <= right / 2) return VisiblePosition(Position(element(), 0)); else return VisiblePosition(Position(element(), maxDeepOffset(element()))); } } // Pass off to the closest child. int minDist = INT_MAX; RenderObject* closestRenderer = 0; int newX = x; int newY = y; if (isTableRow()) { newX += xPos(); newY += yPos(); } for (RenderObject* renderer = m_firstChild; renderer; renderer = renderer->nextSibling()) { if (!renderer->firstChild() && !renderer->isInline() && !renderer->isBlockFlow() || renderer->style()->visibility() != VISIBLE) continue; int top = borderTop() + paddingTop() + (isTableRow() ? 0 : renderer->yPos()); int bottom = top + renderer->contentHeight(); int left = borderLeft() + paddingLeft() + (isTableRow() ? 0 : renderer->xPos()); int right = left + renderer->contentWidth(); if (x <= right && x >= left && y <= top && y >= bottom) { if (renderer->isTableRow()) return renderer->positionForCoordinates(x + newX - renderer->xPos(), y + newY - renderer->yPos()); return renderer->positionForCoordinates(x - renderer->xPos(), y - renderer->yPos()); } // Find the distance from (x, y) to the box. Split the space around the box into 8 pieces // and use a different compare depending on which piece (x, y) is in. IntPoint cmp; if (x > right) { if (y < top) cmp = IntPoint(right, top); else if (y > bottom) cmp = IntPoint(right, bottom); else cmp = IntPoint(right, y); } else if (x < left) { if (y < top) cmp = IntPoint(left, top); else if (y > bottom) cmp = IntPoint(left, bottom); else cmp = IntPoint(left, y); } else { if (y < top) cmp = IntPoint(x, top); else cmp = IntPoint(x, bottom); } int x1minusx2 = cmp.x() - x; int y1minusy2 = cmp.y() - y; int dist = x1minusx2 * x1minusx2 + y1minusy2 * y1minusy2; if (dist < minDist) { closestRenderer = renderer; minDist = dist; } } if (closestRenderer) return closestRenderer->positionForCoordinates(newX - closestRenderer->xPos(), newY - closestRenderer->yPos()); return VisiblePosition(element(), 0, DOWNSTREAM); }
VisiblePosition startVisiblePosition(const Range &r, EAffinity affinity) { return VisiblePosition(r.startContainer().handle(), r.startOffset(), affinity); }
void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(TextGranularity granularity) { if (m_baseIsFirst) { m_start = m_base; m_end = m_extent; } else { m_start = m_extent; m_end = m_base; } switch (granularity) { case CharacterGranularity: // Don't do any expansion. break; case WordGranularity: { // General case: Select the word the caret is positioned inside of, or at the start of (RightWordIfOnBoundary). // Edge case: If the caret is after the last word in a soft-wrapped line or the last word in // the document, select that last word (LeftWordIfOnBoundary). // Edge case: If the caret is after the last word in a paragraph, select from the the end of the // last word to the line break (also RightWordIfOnBoundary); VisiblePosition start = VisiblePosition(m_start, m_affinity); VisiblePosition originalEnd(m_end, m_affinity); EWordSide side = RightWordIfOnBoundary; if (isEndOfEditableOrNonEditableContent(start) || (isEndOfLine(start) && !isStartOfLine(start) && !isEndOfParagraph(start))) side = LeftWordIfOnBoundary; m_start = startOfWord(start, side).deepEquivalent(); side = RightWordIfOnBoundary; if (isEndOfEditableOrNonEditableContent(originalEnd) || (isEndOfLine(originalEnd) && !isStartOfLine(originalEnd) && !isEndOfParagraph(originalEnd))) side = LeftWordIfOnBoundary; VisiblePosition wordEnd(endOfWord(originalEnd, side)); VisiblePosition end(wordEnd); if (isEndOfParagraph(originalEnd) && !isEmptyTableCell(m_start.deprecatedNode())) { // Select the paragraph break (the space from the end of a paragraph to the start of // the next one) to match TextEdit. end = wordEnd.next(); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table. if (isBlock(table)) end = end.next(CannotCrossEditingBoundary); else end = wordEnd; } if (end.isNull()) end = wordEnd; } m_end = end.deepEquivalent(); // End must not be before start. if (m_start.deprecatedNode() == m_end.deprecatedNode() && m_start.deprecatedEditingOffset() > m_end.deprecatedEditingOffset()) { Position swap(m_start); m_start = m_end; m_end = swap; } break; } case SentenceGranularity: { m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; } case LineGranularity: { m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); VisiblePosition end = endOfLine(VisiblePosition(m_end, m_affinity)); // If the end of this line is at the end of a paragraph, include the space // after the end of the line in the selection. if (isEndOfParagraph(end)) { VisiblePosition next = end.next(); if (next.isNotNull()) end = next; } m_end = end.deepEquivalent(); break; } case LineBoundary: m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfLine(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphGranularity: { VisiblePosition pos(m_start, m_affinity); if (isStartOfLine(pos) && isEndOfEditableOrNonEditableContent(pos)) pos = pos.previous(); m_start = startOfParagraph(pos).deepEquivalent(); VisiblePosition visibleParagraphEnd = endOfParagraph(VisiblePosition(m_end, m_affinity)); // Include the "paragraph break" (the space from the end of this paragraph to the start // of the next one) in the selection. VisiblePosition end(visibleParagraphEnd.next()); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table, not at the position just after the table. if (isBlock(table)) end = end.next(CannotCrossEditingBoundary); // There is no parargraph break after the last paragraph in the last cell of an inline table. else end = visibleParagraphEnd; } if (end.isNull()) end = visibleParagraphEnd; m_end = end.deepEquivalent(); break; } case DocumentBoundary: m_start = startOfDocument(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfDocument(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphBoundary: m_start = startOfParagraph(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfParagraph(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case SentenceBoundary: m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case DocumentGranularity: ASSERT_NOT_REACHED(); break; } // Make sure we do not have a dangling start or end. if (m_start.isNull()) m_start = m_end; if (m_end.isNull()) m_end = m_start; }
VisiblePosition RenderBR::positionForCoordinates(int /*x*/, int /*y*/) { return VisiblePosition(element(), 0, DOWNSTREAM); }
void TextCheckingParagraph::expandRangeToNextEnd() { ASSERT(m_checkingRange); setEnd(paragraphRange().get(), endOfParagraph(startOfNextParagraph(VisiblePosition(paragraphRange()->startPosition())))); invalidateParagraphRangeValues(); }
void InsertTextCommand::input(const String &text, bool selectInsertedText) { assert(text.find('\n') == -1); if (endingSelection().isNone()) return; // Delete the current selection. if (endingSelection().isRange()) deleteSelection(false, true, true, false); // Insert the character at the leftmost candidate. Position startPosition = endingSelection().start().upstream(); deleteInsignificantText(startPosition.upstream(), startPosition.downstream()); if (!startPosition.isCandidate()) startPosition = startPosition.downstream(); // FIXME: This typing around anchor behavior doesn't exactly match TextEdit. In TextEdit, // you won't be placed inside a link when typing after it if you've just placed the caret // there with the mouse. startPosition = positionAvoidingSpecialElementBoundary(startPosition, false); 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 (text != " ") rebalanceWhitespaceAt(startPosition); m_charactersAdded += text.length(); } setEndingSelection(Selection(startPosition, endPosition, DOWNSTREAM)); // Handle the case where there is a typing style. // FIXME: Improve typing style. // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement CSSMutableStyleDeclaration* typingStyle = document()->frame()->typingStyle(); RefPtr<CSSComputedStyleDeclaration> endingStyle = endPosition.computedStyle(); endingStyle->diff(typingStyle); if (typingStyle && typingStyle->length() > 0) applyStyle(typingStyle); if (!selectInsertedText) setEndingSelection(Selection(endingSelection().end(), endingSelection().affinity())); }
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())); }
void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(TextGranularity granularity) { if (m_baseIsFirst) { m_start = m_base; m_end = m_extent; } else { m_start = m_extent; m_end = m_base; } switch (granularity) { case CharacterGranularity: // Don't do any expansion. break; case WordGranularity: { // General case: Select the word the caret is positioned inside of, or at the start of (RightWordIfOnBoundary). // Edge case: If the caret is after the last word in a soft-wrapped line or the last word in // the document, select that last word (LeftWordIfOnBoundary). // Edge case: If the caret is after the last word in a paragraph, select from the the end of the // last word to the line break (also RightWordIfOnBoundary); VisiblePosition start = VisiblePosition(m_start, m_affinity); VisiblePosition originalEnd(m_end, m_affinity); EWordSide side = RightWordIfOnBoundary; //SAMSUNG - Text Selection >> Document *doc = NULL; if (m_start.anchorNode()) doc = m_start.anchorNode()->document(); if (doc && doc->settings() && doc->settings()->advancedSelectionEnabled()) { bool endOfDocument; if ( (endOfDocument = isEndOfDocument(start)) || ( (isEndOfLine(start) || isStartOfSpace(start)) && !isStartOfLine(start))) { side = LeftWordIfOnBoundary; if (!endOfDocument && isEndOfParagraph(start)) { originalEnd = start; } } m_start = startOfWord(start, side).deepEquivalent(); side = RightWordIfOnBoundary; if (isStartOfSpace(start)) { side = LeftWordIfOnBoundary; } if (isEndOfDocument(originalEnd) || (isEndOfLine(originalEnd) && !isStartOfLine(originalEnd))) side = LeftWordIfOnBoundary; VisiblePosition wordEnd(endOfWord(originalEnd, side)); VisiblePosition end(wordEnd); m_end = end.deepEquivalent(); } else { //SAMSUNG - Text Selection << if (isEndOfDocument(start) || (isEndOfLine(start) && !isStartOfLine(start) && !isEndOfParagraph(start))) side = LeftWordIfOnBoundary; m_start = startOfWord(start, side).deepEquivalent(); side = RightWordIfOnBoundary; if (isEndOfDocument(originalEnd) || (isEndOfLine(originalEnd) && !isStartOfLine(originalEnd) && !isEndOfParagraph(originalEnd))) side = LeftWordIfOnBoundary; VisiblePosition wordEnd(endOfWord(originalEnd, side)); VisiblePosition end(wordEnd); if (isEndOfParagraph(originalEnd) && !isEmptyTableCell(m_start.deprecatedNode())) { // Select the paragraph break (the space from the end of a paragraph to the start of // the next one) to match TextEdit. end = wordEnd.next(); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table. if (isBlock(table)) end = end.next(CannotCrossEditingBoundary); else end = wordEnd; } if (end.isNull()) end = wordEnd; } m_end = end.deepEquivalent(); //SAMSUNG - Text Selection >> } //SAMSUNG - Text Selection << break; } case SentenceGranularity: { m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; } case LineGranularity: { m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); VisiblePosition end = endOfLine(VisiblePosition(m_end, m_affinity)); // If the end of this line is at the end of a paragraph, include the space // after the end of the line in the selection. if (isEndOfParagraph(end)) { VisiblePosition next = end.next(); if (next.isNotNull()) end = next; } m_end = end.deepEquivalent(); break; } case LineBoundary: m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfLine(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphGranularity: { VisiblePosition pos(m_start, m_affinity); if (isStartOfLine(pos) && isEndOfDocument(pos)) pos = pos.previous(); m_start = startOfParagraph(pos).deepEquivalent(); VisiblePosition visibleParagraphEnd = endOfParagraph(VisiblePosition(m_end, m_affinity)); // Include the "paragraph break" (the space from the end of this paragraph to the start // of the next one) in the selection. //SAMSUNG - Text Selection >> // Here it tries to extend the end point from current paragraph to beginning of next paragraph. So somehow it is // selecting the next paragraph also and it looks wrong when we do backward paragraph selection. // Even though user is trying to extend selection in backward direction, because of this condition it extends selection // in both directions. So commented the below code to avoid wrong backward text selection // WAS: VisiblePosition end(visibleParagraphEnd.next()); VisiblePosition end(visibleParagraphEnd); //SAMSUNG - Text Selection << if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table, not at the position just after the table. if (isBlock(table)) end = end.next(CannotCrossEditingBoundary); // There is no parargraph break after the last paragraph in the last cell of an inline table. else end = visibleParagraphEnd; } if (end.isNull()) end = visibleParagraphEnd; m_end = end.deepEquivalent(); break; } case DocumentBoundary: m_start = startOfDocument(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfDocument(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphBoundary: m_start = startOfParagraph(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfParagraph(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case SentenceBoundary: m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case WebKitVisualWordGranularity: break; } // Make sure we do not have a dangling start or end. if (m_start.isNull()) m_start = m_end; if (m_end.isNull()) m_end = m_start; }
PassRefPtrWillBeRawPtr<Range> PlainTextRange::createRangeFor(const ContainerNode& scope, GetRangeFor getRangeFor) const { ASSERT(isNotNull()); RefPtrWillBeRawPtr<Range> resultRange = scope.document().createRange(); size_t docTextPosition = 0; bool startRangeFound = false; Position textRunStartPosition; Position textRunEndPosition; TextIteratorBehaviorFlags behaviorFlags = TextIteratorEmitsObjectReplacementCharacter; if (getRangeFor == ForSelection) behaviorFlags |= TextIteratorEmitsCharactersBetweenAllVisiblePositions; auto range = rangeOfContents(const_cast<ContainerNode*>(&scope)); TextIterator it(range->startPosition(), range->endPosition(), behaviorFlags); // FIXME: the atEnd() check shouldn't be necessary, workaround for <http://bugs.webkit.org/show_bug.cgi?id=6289>. if (!start() && !length() && it.atEnd()) { resultRange->setStart(it.currentContainer(), 0, ASSERT_NO_EXCEPTION); resultRange->setEnd(it.currentContainer(), 0, ASSERT_NO_EXCEPTION); return resultRange.release(); } for (; !it.atEnd(); it.advance()) { int len = it.length(); textRunStartPosition = it.startPositionInCurrentContainer(); textRunEndPosition = it.endPositionInCurrentContainer(); bool foundStart = start() >= docTextPosition && start() <= docTextPosition + len; bool foundEnd = end() >= docTextPosition && end() <= docTextPosition + len; // Fix textRunRange->endPosition(), but only if foundStart || foundEnd, because it is only // in those cases that textRunRange is used. if (foundEnd) { // FIXME: This is a workaround for the fact that the end of a run // is often at the wrong position for emitted '\n's or if the // renderer of the current node is a replaced element. if (len == 1 && (it.characterAt(0) == '\n' || it.isInsideReplacedElement())) { it.advance(); if (!it.atEnd()) { textRunEndPosition = it.startPositionInCurrentContainer(); } else { Position runEnd = VisiblePosition(textRunStartPosition).next().deepEquivalent(); if (runEnd.isNotNull()) textRunEndPosition = createLegacyEditingPosition(runEnd.containerNode(), runEnd.computeOffsetInContainerNode()); } } } if (foundStart) { startRangeFound = true; if (textRunStartPosition.containerNode()->isTextNode()) { int offset = start() - docTextPosition; resultRange->setStart(textRunStartPosition.containerNode(), offset + textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } else { if (start() == docTextPosition) resultRange->setStart(textRunStartPosition.containerNode(), textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION); else resultRange->setStart(textRunEndPosition.containerNode(), textRunEndPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } } if (foundEnd) { if (textRunStartPosition.containerNode()->isTextNode()) { int offset = end() - docTextPosition; resultRange->setEnd(textRunStartPosition.containerNode(), offset + textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } else { if (end() == docTextPosition) resultRange->setEnd(textRunStartPosition.containerNode(), textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION); else resultRange->setEnd(textRunEndPosition.containerNode(), textRunEndPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } docTextPosition += len; break; } docTextPosition += len; } if (!startRangeFound) return nullptr; if (length() && end() > docTextPosition) { // end() is out of bounds resultRange->setEnd(textRunEndPosition.containerNode(), textRunEndPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } return resultRange.release(); }
void HTMLTextFormControlElement::setInnerTextValue(const String& value) { TextControlInnerTextElement* innerText = innerTextElement(); if (!innerText) return; ASSERT(isTextFormControl()); String previousValue = innerTextValueFrom(*innerText); bool textIsChanged = value != previousValue; if (textIsChanged || !innerText->hasChildNodes()) { #if HAVE(ACCESSIBILITY) && !PLATFORM(COCOA) if (textIsChanged && renderer()) { if (AXObjectCache* cache = document().existingAXObjectCache()) cache->postNotification(this, AXObjectCache::AXValueChanged, TargetObservableParent); } #endif innerText->setInnerText(value, ASSERT_NO_EXCEPTION); if (value.endsWith('\n') || value.endsWith('\r')) innerText->appendChild(HTMLBRElement::create(document()), ASSERT_NO_EXCEPTION); #if HAVE(ACCESSIBILITY) && PLATFORM(COCOA) if (textIsChanged && renderer()) { if (AXObjectCache* cache = document().existingAXObjectCache()) cache->postTextReplacementNotification(this, AXTextEditTypeDelete, previousValue, AXTextEditTypeInsert, value, VisiblePosition(Position(this, Position::PositionIsBeforeAnchor))); } #endif } setFormControlValueMatchesRenderer(true); }
TEST_F(VisiblePositionTest, NullIsValid) { EXPECT_TRUE(VisiblePosition().isValid()); }
String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail) { if (!unifiedTextCheckerEnabled()) return ""; String firstFoundItem; String misspelledWord; String badGrammarPhrase; // Initialize out parameters; these will be updated if we find something to return. outIsSpelling = true; outFirstFoundOffset = 0; outGrammarDetail.location = -1; outGrammarDetail.length = 0; outGrammarDetail.guesses.clear(); outGrammarDetail.userDescription = ""; // Expand the search range to encompass entire paragraphs, since text checking needs that much context. // Determine the character offset from the start of the paragraph to the start of the original search range, // since we will want to ignore results in this area. Position paragraphStart = startOfParagraph(VisiblePosition(m_start)).toParentAnchoredPosition(); Position paragraphEnd = m_end; int totalRangeLength = TextIterator::rangeLength(paragraphStart, paragraphEnd); paragraphEnd = endOfParagraph(VisiblePosition(m_start)).toParentAnchoredPosition(); int rangeStartOffset = TextIterator::rangeLength(paragraphStart, m_start); int totalLengthProcessed = 0; bool firstIteration = true; bool lastIteration = false; while (totalLengthProcessed < totalRangeLength) { // Iterate through the search range by paragraphs, checking each one for spelling and grammar. int currentLength = TextIterator::rangeLength(paragraphStart, paragraphEnd); int currentStartOffset = firstIteration ? rangeStartOffset : 0; int currentEndOffset = currentLength; if (inSameParagraph(VisiblePosition(paragraphStart), VisiblePosition(m_end))) { // Determine the character offset from the end of the original search range to the end of the paragraph, // since we will want to ignore results in this area. currentEndOffset = TextIterator::rangeLength(paragraphStart, m_end); lastIteration = true; } if (currentStartOffset < currentEndOffset) { String paragraphString = plainText(paragraphStart, paragraphEnd); if (paragraphString.length() > 0) { bool foundGrammar = false; int spellingLocation = 0; int grammarPhraseLocation = 0; int grammarDetailLocation = 0; unsigned grammarDetailIndex = 0; Vector<TextCheckingResult> results; TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling; checkTextOfParagraph(m_client->textChecker(), paragraphString, checkingTypes, results); for (unsigned i = 0; i < results.size(); i++) { const TextCheckingResult* result = &results[i]; if (result->decoration == TextDecorationTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) { ASSERT(result->length > 0 && result->location >= 0); spellingLocation = result->location; misspelledWord = paragraphString.substring(result->location, result->length); ASSERT(misspelledWord.length()); break; } if (checkGrammar && result->decoration == TextDecorationTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) { ASSERT(result->length > 0 && result->location >= 0); // We can't stop after the first grammar result, since there might still be a spelling result after // it begins but before the first detail in it, but we can stop if we find a second grammar result. if (foundGrammar) break; for (unsigned j = 0; j < result->details.size(); j++) { const GrammarDetail* detail = &result->details[j]; ASSERT(detail->length > 0 && detail->location >= 0); if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) { grammarDetailIndex = j; grammarDetailLocation = result->location + detail->location; foundGrammar = true; } } if (foundGrammar) { grammarPhraseLocation = result->location; outGrammarDetail = result->details[grammarDetailIndex]; badGrammarPhrase = paragraphString.substring(result->location, result->length); ASSERT(badGrammarPhrase.length()); } } } if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) { int spellingOffset = spellingLocation - currentStartOffset; if (!firstIteration) spellingOffset += TextIterator::rangeLength(m_start, paragraphStart); outIsSpelling = true; outFirstFoundOffset = spellingOffset; firstFoundItem = misspelledWord; break; } if (checkGrammar && !badGrammarPhrase.isEmpty()) { int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset; if (!firstIteration) grammarPhraseOffset += TextIterator::rangeLength(m_start, paragraphStart); outIsSpelling = false; outFirstFoundOffset = grammarPhraseOffset; firstFoundItem = badGrammarPhrase; break; } } } if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength) break; VisiblePosition newParagraphStart = startOfNextParagraph(VisiblePosition(paragraphEnd)); paragraphStart = newParagraphStart.toParentAnchoredPosition(); paragraphEnd = endOfParagraph(newParagraphStart).toParentAnchoredPosition(); firstIteration = false; totalLengthProcessed += currentLength; } return firstFoundItem; }
VisiblePosition VisiblePosition::next() const { VisiblePosition result = VisiblePosition(nextVisiblePosition(m_deepPosition), m_affinity); setAffinityUsingLinePosition(result); return result; }
void InsertNodeBeforeCommand::doApply() { ContainerNode* parent = m_refChild->parentNode(); if (!parent || (m_shouldAssumeContentIsAlwaysEditable == DoNotAssumeContentIsAlwaysEditable && !isEditableNode(*parent))) return; ASSERT(isEditableNode(*parent)); parent->insertBefore(*m_insertChild, m_refChild.get(), IGNORE_EXCEPTION); if (shouldPostAccessibilityNotification()) { Position position = is<Text>(m_insertChild.get()) ? Position(downcast<Text>(m_insertChild.get()), 0) : createLegacyEditingPosition(m_insertChild.get(), 0); notifyAccessibilityForTextChange(m_insertChild.get(), applyEditType(), m_insertChild->nodeValue(), VisiblePosition(position)); } }
VisiblePosition endVisiblePosition(const Range &r, EAffinity affinity) { return VisiblePosition(r.endContainer().handle(), r.endOffset(), affinity); }
void InsertNodeBeforeCommand::doUnapply() { if (!isEditableNode(*m_insertChild)) return; // Need to notify this before actually deleting the text if (shouldPostAccessibilityNotification()) { Position position = is<Text>(m_insertChild.get()) ? Position(downcast<Text>(m_insertChild.get()), 0) : createLegacyEditingPosition(m_insertChild.get(), 0); notifyAccessibilityForTextChange(m_insertChild.get(), unapplyEditType(), m_insertChild->nodeValue(), VisiblePosition(position)); } m_insertChild->remove(IGNORE_EXCEPTION); }