VisiblePosition SelectionModifier::modifyMovingForward( TextGranularity granularity) { VisiblePosition pos; // FIXME: Stay in editable content for the less common granularities. switch (granularity) { case CharacterGranularity: if (m_selection.isRange()) pos = createVisiblePosition(m_selection.end(), m_selection.affinity()); else pos = nextPositionOf( createVisiblePosition(m_selection.extent(), m_selection.affinity()), CanSkipOverEditingBoundary); break; case WordGranularity: pos = nextWordPositionForPlatform( createVisiblePosition(m_selection.extent(), m_selection.affinity())); break; case SentenceGranularity: pos = nextSentencePosition( createVisiblePosition(m_selection.extent(), m_selection.affinity())); break; case LineGranularity: { // down-arrowing from a range selection that ends at the start of a line // needs to leave the selection at that line start (no need to call // nextLinePosition!) pos = endForPlatform(); if (!m_selection.isRange() || !isStartOfLine(pos)) pos = nextLinePosition( pos, lineDirectionPointForBlockDirectionNavigation(START)); break; } case ParagraphGranularity: pos = nextParagraphPosition( endForPlatform(), lineDirectionPointForBlockDirectionNavigation(START)); break; case SentenceBoundary: pos = endOfSentence(endForPlatform()); break; case LineBoundary: pos = logicalEndOfLine(endForPlatform()); break; case ParagraphBoundary: pos = endOfParagraph(endForPlatform()); break; case DocumentBoundary: pos = endForPlatform(); if (isEditablePosition(pos.deepEquivalent())) pos = endOfEditableContent(pos); else pos = endOfDocument(pos); break; } return pos; }
VisiblePosition SelectionModifier::modifyMovingBackward( TextGranularity granularity) { VisiblePosition pos; switch (granularity) { case CharacterGranularity: if (m_selection.isRange()) pos = createVisiblePosition(m_selection.start(), m_selection.affinity()); else pos = previousPositionOf( createVisiblePosition(m_selection.extent(), m_selection.affinity()), CanSkipOverEditingBoundary); break; case WordGranularity: pos = previousWordPosition( createVisiblePosition(m_selection.extent(), m_selection.affinity())); break; case SentenceGranularity: pos = previousSentencePosition( createVisiblePosition(m_selection.extent(), m_selection.affinity())); break; case LineGranularity: pos = previousLinePosition( startForPlatform(), lineDirectionPointForBlockDirectionNavigation(START)); break; case ParagraphGranularity: pos = previousParagraphPosition( startForPlatform(), lineDirectionPointForBlockDirectionNavigation(START)); break; case SentenceBoundary: pos = startOfSentence(startForPlatform()); break; case LineBoundary: pos = logicalStartOfLine(startForPlatform()); break; case ParagraphBoundary: pos = startOfParagraph(startForPlatform()); break; case DocumentBoundary: pos = startForPlatform(); if (isEditablePosition(pos.deepEquivalent())) pos = startOfEditableContent(pos); else pos = startOfDocument(pos); break; } return pos; }
VisiblePosition SelectionModifier::modifyExtendingBackward( TextGranularity granularity) { VisiblePosition pos = createVisiblePosition(m_selection.extent(), m_selection.affinity()); // Extending a selection backward by word or character from just after a table // selects the table. This "makes sense" from the user perspective, esp. when // deleting. It was done here instead of in VisiblePosition because we want // VPs to iterate over everything. switch (granularity) { case CharacterGranularity: pos = previousPositionOf(pos, CanSkipOverEditingBoundary); break; case WordGranularity: pos = previousWordPosition(pos); break; case SentenceGranularity: pos = previousSentencePosition(pos); break; case LineGranularity: pos = previousLinePosition( pos, lineDirectionPointForBlockDirectionNavigation(EXTENT)); break; case ParagraphGranularity: pos = previousParagraphPosition( pos, lineDirectionPointForBlockDirectionNavigation(EXTENT)); break; case SentenceBoundary: pos = startOfSentence(startForPlatform()); break; case LineBoundary: pos = logicalStartOfLine(startForPlatform()); break; case ParagraphBoundary: pos = startOfParagraph(startForPlatform()); break; case DocumentBoundary: pos = startForPlatform(); if (isEditablePosition(pos.deepEquivalent())) pos = startOfEditableContent(pos); else pos = startOfDocument(pos); break; } adjustPositionForUserSelectAll(pos, !(directionOfEnclosingBlock() == LTR)); return pos; }
VisiblePosition SelectionModifier::modifyExtendingForward( TextGranularity granularity) { VisiblePosition pos = createVisiblePosition(m_selection.extent(), m_selection.affinity()); switch (granularity) { case CharacterGranularity: pos = nextPositionOf(pos, CanSkipOverEditingBoundary); break; case WordGranularity: pos = nextWordPositionForPlatform(pos); break; case SentenceGranularity: pos = nextSentencePosition(pos); break; case LineGranularity: pos = nextLinePosition( pos, lineDirectionPointForBlockDirectionNavigation(EXTENT)); break; case ParagraphGranularity: pos = nextParagraphPosition( pos, lineDirectionPointForBlockDirectionNavigation(EXTENT)); break; case SentenceBoundary: pos = endOfSentence(endForPlatform()); break; case LineBoundary: pos = logicalEndOfLine(endForPlatform()); break; case ParagraphBoundary: pos = endOfParagraph(endForPlatform()); break; case DocumentBoundary: pos = endForPlatform(); if (isEditablePosition(pos.deepEquivalent())) pos = endOfEditableContent(pos); else pos = endOfDocument(pos); break; } adjustPositionForUserSelectAll(pos, directionOfEnclosingBlock() == LTR); return pos; }
bool VisibleSelection::isContentEditable() const { return isEditablePosition(start()); }
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; }
bool VisibleSelection::rendererIsEditable() const { return isEditablePosition(start(), ContentIsEditable, DoNotUpdateStyle); }
void SpellChecker::advanceToNextMisspelling(bool startBeforeSelection) { // The basic approach is to search in two phases - from the selection end to the end of the doc, and // then we wrap and search from the doc start to (approximately) where we started. // Start at the end of the selection, search to edge of document. Starting at the selection end makes // repeated "check spelling" commands work. VisibleSelection selection(m_frame.selection().selection()); RefPtr<Range> spellingSearchRange(rangeOfContents(m_frame.document())); bool startedWithSelection = false; if (selection.start().deprecatedNode()) { startedWithSelection = true; if (startBeforeSelection) { VisiblePosition start(selection.visibleStart()); // We match AppKit's rule: Start 1 character before the selection. VisiblePosition oneBeforeStart = start.previous(); setStart(spellingSearchRange.get(), oneBeforeStart.isNotNull() ? oneBeforeStart : start); } else { setStart(spellingSearchRange.get(), selection.visibleEnd()); } } Position position = spellingSearchRange->startPosition(); if (!isEditablePosition(position)) { // This shouldn't happen in very often because the Spelling menu items aren't enabled unless the // selection is editable. // This can happen in Mail for a mix of non-editable and editable content (like Stationary), // when spell checking the whole document before sending the message. // In that case the document might not be editable, but there are editable pockets that need to be spell checked. position = firstEditableVisiblePositionAfterPositionInRoot(position, m_frame.document()).deepEquivalent(); if (position.isNull()) return; Position rangeCompliantPosition = position.parentAnchoredEquivalent(); spellingSearchRange->setStart(rangeCompliantPosition.deprecatedNode(), rangeCompliantPosition.deprecatedEditingOffset(), IGNORE_EXCEPTION); startedWithSelection = false; // won't need to wrap } // topNode defines the whole range we want to operate on ContainerNode* topNode = highestEditableRoot(position); // FIXME: lastOffsetForEditing() is wrong here if editingIgnoresContent(highestEditableRoot()) returns true (e.g. a <table>) spellingSearchRange->setEnd(topNode, lastOffsetForEditing(topNode), IGNORE_EXCEPTION); // If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking // at a word boundary. Going back by one char and then forward by a word does the trick. if (startedWithSelection) { VisiblePosition oneBeforeStart = startVisiblePosition(spellingSearchRange.get(), DOWNSTREAM).previous(); if (oneBeforeStart.isNotNull()) setStart(spellingSearchRange.get(), endOfWord(oneBeforeStart)); // else we were already at the start of the editable node } if (spellingSearchRange->collapsed()) return; // nothing to search in // We go to the end of our first range instead of the start of it, just to be sure // we don't get foiled by any word boundary problems at the start. It means we might // do a tiny bit more searching. Node* searchEndNodeAfterWrap = spellingSearchRange->endContainer(); int searchEndOffsetAfterWrap = spellingSearchRange->endOffset(); int misspellingOffset = 0; GrammarDetail grammarDetail; int grammarPhraseOffset = 0; RefPtr<Range> grammarSearchRange = nullptr; String badGrammarPhrase; String misspelledWord; bool isSpelling = true; int foundOffset = 0; String foundItem; RefPtr<Range> firstMisspellingRange = nullptr; if (unifiedTextCheckerEnabled()) { grammarSearchRange = spellingSearchRange->cloneRange(); foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); if (isSpelling) { misspelledWord = foundItem; misspellingOffset = foundOffset; } else { badGrammarPhrase = foundItem; grammarPhraseOffset = foundOffset; } } else { misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); grammarSearchRange = spellingSearchRange->cloneRange(); if (!misspelledWord.isEmpty()) { // Stop looking at start of next misspelled word CharacterIterator chars(grammarSearchRange.get()); chars.advance(misspellingOffset); grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset(), IGNORE_EXCEPTION); } if (isGrammarCheckingEnabled()) badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false); } // If we found neither bad grammar nor a misspelled word, wrap and try again (but don't bother if we started at the beginning of the // block rather than at a selection). if (startedWithSelection && !misspelledWord && !badGrammarPhrase) { spellingSearchRange->setStart(topNode, 0, IGNORE_EXCEPTION); // going until the end of the very first chunk we tested is far enough spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap, IGNORE_EXCEPTION); if (unifiedTextCheckerEnabled()) { grammarSearchRange = spellingSearchRange->cloneRange(); foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); if (isSpelling) { misspelledWord = foundItem; misspellingOffset = foundOffset; } else { badGrammarPhrase = foundItem; grammarPhraseOffset = foundOffset; } } else { misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); grammarSearchRange = spellingSearchRange->cloneRange(); if (!misspelledWord.isEmpty()) { // Stop looking at start of next misspelled word CharacterIterator chars(grammarSearchRange.get()); chars.advance(misspellingOffset); grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset(), IGNORE_EXCEPTION); } if (isGrammarCheckingEnabled()) badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false); } } if (!badGrammarPhrase.isEmpty()) { // We found bad grammar. Since we only searched for bad grammar up to the first misspelled word, the bad grammar // takes precedence and we ignore any potential misspelled word. Select the grammar detail, update the spelling // panel, and store a marker so we draw the green squiggle later. ASSERT(badGrammarPhrase.length() > 0); ASSERT(grammarDetail.location != -1 && grammarDetail.length > 0); // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarSearchRange.get(), grammarPhraseOffset + grammarDetail.location, grammarDetail.length); m_frame.selection().setSelection(VisibleSelection(badGrammarRange.get(), SEL_DEFAULT_AFFINITY)); m_frame.selection().revealSelection(); m_frame.document()->markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, grammarDetail.userDescription); } else if (!misspelledWord.isEmpty()) { // We found a misspelling, but not any earlier bad grammar. Select the misspelling, update the spelling panel, and store // a marker so we draw the red squiggle later. RefPtr<Range> misspellingRange = TextIterator::subrange(spellingSearchRange.get(), misspellingOffset, misspelledWord.length()); m_frame.selection().setSelection(VisibleSelection(misspellingRange.get(), DOWNSTREAM)); m_frame.selection().revealSelection(); spellCheckerClient().updateSpellingUIWithMisspelledWord(misspelledWord); m_frame.document()->markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling); } }
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; }