bool selectionBelongsToObject(AccessibilityObject* coreObject, VisibleSelection& selection) { if (!coreObject || !coreObject->isAccessibilityRenderObject()) return false; if (selection.isNone()) return false; RefPtr<Range> range = selection.toNormalizedRange(); if (!range) return false; // We want to check that both the selection intersects the node // AND that the selection is not just "touching" one of the // boundaries for the selected node. We want to check whether the // node is actually inside the region, at least partially. auto& node = *coreObject->node(); auto* lastDescendant = node.lastDescendant(); unsigned lastOffset = lastOffsetInNode(lastDescendant); auto intersectsResult = range->intersectsNode(node); return !intersectsResult.hasException() && intersectsResult.releaseReturnValue() && (&range->endContainer() != &node || range->endOffset()) && (&range->startContainer() != lastDescendant || range->startOffset() != lastOffset); }
// Updates |selectionInFlatTree| to match with |selection|. void SelectionAdjuster::adjustSelectionInFlatTree( VisibleSelectionInFlatTree* selectionInFlatTree, const VisibleSelection& selection) { if (selection.isNone()) { *selectionInFlatTree = VisibleSelectionInFlatTree(); return; } const PositionInFlatTree& base = toPositionInFlatTree(selection.base()); const PositionInFlatTree& extent = toPositionInFlatTree(selection.extent()); const PositionInFlatTree& position1 = toPositionInFlatTree(selection.start()); const PositionInFlatTree& position2 = toPositionInFlatTree(selection.end()); position1.anchorNode()->updateDistribution(); position2.anchorNode()->updateDistribution(); selectionInFlatTree->m_base = base; selectionInFlatTree->m_extent = extent; selectionInFlatTree->m_affinity = selection.m_affinity; selectionInFlatTree->m_isDirectional = selection.m_isDirectional; selectionInFlatTree->m_granularity = selection.m_granularity; selectionInFlatTree->m_hasTrailingWhitespace = selection.m_hasTrailingWhitespace; selectionInFlatTree->m_baseIsFirst = base.isNull() || base.compareTo(extent) <= 0; if (position1.compareTo(position2) <= 0) { selectionInFlatTree->m_start = position1; selectionInFlatTree->m_end = position2; } else { selectionInFlatTree->m_start = position2; selectionInFlatTree->m_end = position1; } selectionInFlatTree->updateSelectionType(); }
static gint webkit_accessible_text_get_n_selections(AtkText* text) { AccessibilityObject* coreObject = core(text); VisibleSelection selection = coreObject->selection(); // We don't support multiple selections for now, so there's only // two possibilities // Also, we don't want to do anything if the selection does not // belong to the currently selected object. We have to check since // there's no way to get the selection for a given object, only // the global one (the API is a bit confusing) return !selectionBelongsToObject(coreObject, selection) || selection.isNone() ? 0 : 1; }
// This needs to be static so it can be called by canIncreaseSelectionListLevel and canDecreaseSelectionListLevel static bool getStartEndListChildren(const VisibleSelection& selection, Node*& start, Node*& end) { if (selection.isNone()) return false; // start must be in a list child Node* startListChild = enclosingListChild(selection.start().anchorNode()); if (!startListChild) return false; // end must be in a list child Node* endListChild = selection.isRange() ? enclosingListChild(selection.end().anchorNode()) : startListChild; if (!endListChild) return false; // For a range selection we want the following behavior: // - the start and end must be within the same overall list // - the start must be at or above the level of the rest of the range // - if the end is anywhere in a sublist lower than start, the whole sublist gets moved // In terms of this function, this means: // - endListChild must start out being be a sibling of startListChild, or be in a // sublist of startListChild or a sibling // - if endListChild is in a sublist of startListChild or a sibling, it must be adjusted // to be the ancestor that is startListChild or its sibling while (startListChild->parentNode() != endListChild->parentNode()) { endListChild = endListChild->parentNode(); if (!endListChild) return false; } // if the selection ends on a list item with a sublist, include the entire sublist if (endListChild->renderer()->isListItem()) { RenderObject* r = endListChild->renderer()->nextSibling(); if (r && isListHTMLElement(r->node())) endListChild = r->node(); } start = startListChild; end = endListChild; return true; }
VisibleSelection avoidIntersectionWithNode(const VisibleSelection& selection, Node* node) { if (selection.isNone()) return VisibleSelection(selection); VisibleSelection updatedSelection(selection); Node* base = selection.base().deprecatedNode(); Node* extent = selection.extent().deprecatedNode(); ASSERT(base); ASSERT(extent); if (base == node || base->isDescendantOf(node)) { ASSERT(node->parentNode()); updatedSelection.setBase(positionInParentBeforeNode(node)); } if (extent == node || extent->isDescendantOf(node)) { ASSERT(node->parentNode()); updatedSelection.setExtent(positionInParentBeforeNode(node)); } return updatedSelection; }
void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing) { Frame* frame = document()->frame(); if (!frame) return; frame->editor()->updateMarkersForWordsAffectedByEditing(false); VisibleSelection selectionToDelete; VisibleSelection selectionAfterUndo; switch (endingSelection().selectionType()) { case VisibleSelection::RangeSelection: selectionToDelete = endingSelection(); selectionAfterUndo = selectionToDelete; break; case VisibleSelection::CaretSelection: { m_smartDelete = false; // Handle delete at beginning-of-block case. // Do nothing in the case that the caret is at the start of a // root editable element or at the start of a document. FrameSelection selection; selection.setSelection(endingSelection()); selection.modify(FrameSelection::AlterationExtend, DirectionForward, granularity); if (killRing && selection.isCaret() && granularity != CharacterGranularity) selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity); Position downstreamEnd = endingSelection().end().downstream(); VisiblePosition visibleEnd = endingSelection().visibleEnd(); Node* enclosingTableCell = enclosingNodeOfType(visibleEnd.deepEquivalent(), &isTableCell); if (enclosingTableCell && visibleEnd == lastPositionInNode(enclosingTableCell)) return; if (visibleEnd == endOfParagraph(visibleEnd)) downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream(); // When deleting tables: Select the table first, then perform the deletion if (downstreamEnd.containerNode() && downstreamEnd.containerNode()->renderer() && downstreamEnd.containerNode()->renderer()->isTable() && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(downstreamEnd.containerNode())) { setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.containerNode()), DOWNSTREAM, endingSelection().isDirectional())); typingAddedToOpenCommand(ForwardDeleteKey); return; } // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any) if (granularity == ParagraphBoundary && selection.selection().isCaret() && isEndOfParagraph(selection.selection().visibleEnd())) selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity); selectionToDelete = selection.selection(); if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start()) selectionAfterUndo = selectionToDelete; else { // It's a little tricky to compute what the starting selection would have been in the original document. // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on // the current state of the document and we'll get the wrong result. Position extent = startingSelection().end(); if (extent.containerNode() != selectionToDelete.end().containerNode()) extent = selectionToDelete.extent(); else { int extraCharacters; if (selectionToDelete.start().containerNode() == selectionToDelete.end().containerNode()) extraCharacters = selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode(); else extraCharacters = selectionToDelete.end().computeOffsetInContainerNode(); extent = Position(extent.containerNode(), extent.computeOffsetInContainerNode() + extraCharacters, Position::PositionIsOffsetInAnchor); } selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent); } break; } case VisibleSelection::NoSelection: ASSERT_NOT_REACHED(); break; } ASSERT(!selectionToDelete.isNone()); if (selectionToDelete.isNone()) return; if (selectionToDelete.isCaret() || !frame->selection()->shouldDeleteSelection(selectionToDelete)) return; if (killRing) frame->editor()->addToKillRing(selectionToDelete.toNormalizedRange().get(), false); // make undo select what was deleted setStartingSelection(selectionAfterUndo); CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); setSmartDelete(false); typingAddedToOpenCommand(ForwardDeleteKey); }
void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) { Frame* frame = document()->frame(); if (!frame) return; frame->editor()->updateMarkersForWordsAffectedByEditing(false); VisibleSelection selectionToDelete; VisibleSelection selectionAfterUndo; switch (endingSelection().selectionType()) { case VisibleSelection::RangeSelection: selectionToDelete = endingSelection(); selectionAfterUndo = selectionToDelete; break; case VisibleSelection::CaretSelection: { // After breaking out of an empty mail blockquote, we still want continue with the deletion // so actual content will get deleted, and not just the quote style. if (breakOutOfEmptyMailBlockquotedParagraph()) typingAddedToOpenCommand(DeleteKey); m_smartDelete = false; FrameSelection selection; selection.setSelection(endingSelection()); selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity); if (killRing && selection.isCaret() && granularity != CharacterGranularity) selection.modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity); if (endingSelection().visibleStart().previous(CannotCrossEditingBoundary).isNull()) { // When the caret is at the start of the editable area in an empty list item, break out of the list item. if (breakOutOfEmptyListItem()) { typingAddedToOpenCommand(DeleteKey); return; } // When there are no visible positions in the editing root, delete its entire contents. if (endingSelection().visibleStart().next(CannotCrossEditingBoundary).isNull() && makeEditableRootEmpty()) { typingAddedToOpenCommand(DeleteKey); return; } } VisiblePosition visibleStart(endingSelection().visibleStart()); // If we have a caret selection at the beginning of a cell, we have nothing to do. Node* enclosingTableCell = enclosingNodeOfType(visibleStart.deepEquivalent(), &isTableCell); if (enclosingTableCell && visibleStart == firstPositionInNode(enclosingTableCell)) return; // If the caret is at the start of a paragraph after a table, move content into the last table cell. if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(CannotCrossEditingBoundary))) { // Unless the caret is just before a table. We don't want to move a table into the last table cell. if (isLastPositionBeforeTable(visibleStart)) return; // Extend the selection backward into the last cell, then deletion will handle the move. selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity); // If the caret is just after a table, select the table and don't delete anything. } else if (Node* table = isFirstPositionAfterTable(visibleStart)) { setEndingSelection(VisibleSelection(positionBeforeNode(table), endingSelection().start(), DOWNSTREAM, endingSelection().isDirectional())); typingAddedToOpenCommand(DeleteKey); return; } selectionToDelete = selection.selection(); if (granularity == CharacterGranularity && selectionToDelete.end().containerNode() == selectionToDelete.start().containerNode() && selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode() > 1) { // If there are multiple Unicode code points to be deleted, adjust the range to match platform conventions. selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(BackwardDeletion)); } if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start()) selectionAfterUndo = selectionToDelete; else // It's a little tricky to compute what the starting selection would have been in the original document. // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on // the current state of the document and we'll get the wrong result. selectionAfterUndo.setWithoutValidation(startingSelection().end(), selectionToDelete.extent()); break; } case VisibleSelection::NoSelection: ASSERT_NOT_REACHED(); break; } ASSERT(!selectionToDelete.isNone()); if (selectionToDelete.isNone()) return; if (selectionToDelete.isCaret() || !frame->selection()->shouldDeleteSelection(selectionToDelete)) return; if (killRing) frame->editor()->addToKillRing(selectionToDelete.toNormalizedRange().get(), false); // Make undo select everything that has been deleted, unless an undo will undo more than just this deletion. // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete // more text than you insert. In that case all of the text that was around originally should be selected. if (m_openedByBackwardDelete) setStartingSelection(selectionAfterUndo); CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); setSmartDelete(false); typingAddedToOpenCommand(DeleteKey); }
static void getSelectionOffsetsForObject(AccessibilityObject* coreObject, VisibleSelection& selection, gint& startOffset, gint& endOffset) { // Default values, unless the contrary is proved. startOffset = 0; endOffset = 0; Node* node = getNodeForAccessibilityObject(coreObject); if (!node) return; if (selection.isNone()) return; // We need to limit our search to positions that fall inside the domain of the current object. Position firstValidPosition = firstPositionInOrBeforeNode(node->firstDescendant()); Position lastValidPosition = lastPositionInOrAfterNode(node->lastDescendant()); // Early return with proper values if the selection falls entirely out of the object. if (!selectionBelongsToObject(coreObject, selection)) { startOffset = comparePositions(selection.start(), firstValidPosition) < 0 ? 0 : accessibilityObjectLength(coreObject); endOffset = startOffset; return; } // Find the proper range for the selection that falls inside the object. Position nodeRangeStart = selection.start(); if (comparePositions(nodeRangeStart, firstValidPosition) < 0) nodeRangeStart = firstValidPosition; Position nodeRangeEnd = selection.end(); if (comparePositions(nodeRangeEnd, lastValidPosition) > 0) nodeRangeEnd = lastValidPosition; // Calculate position of the selected range inside the object. Position parentFirstPosition = firstPositionInOrBeforeNode(node); RefPtr<Range> rangeInParent = Range::create(&node->document(), parentFirstPosition, nodeRangeStart); // Set values for start offsets and calculate initial range length. // These values might be adjusted later to cover special cases. startOffset = webCoreOffsetToAtkOffset(coreObject, TextIterator::rangeLength(rangeInParent.get(), true)); RefPtr<Range> nodeRange = Range::create(&node->document(), nodeRangeStart, nodeRangeEnd); int rangeLength = TextIterator::rangeLength(nodeRange.get(), true); // Special cases that are only relevant when working with *_END boundaries. if (selection.affinity() == UPSTREAM) { VisiblePosition visibleStart(nodeRangeStart, UPSTREAM); VisiblePosition visibleEnd(nodeRangeEnd, UPSTREAM); // We need to adjust offsets when finding wrapped lines so the position at the end // of the line is properly taking into account when calculating the offsets. if (isEndOfLine(visibleStart) && !lineBreakExistsAtVisiblePosition(visibleStart)) { if (isStartOfLine(visibleStart.next())) rangeLength++; if (!isEndOfBlock(visibleStart)) startOffset = std::max(startOffset - 1, 0); } if (isEndOfLine(visibleEnd) && !lineBreakExistsAtVisiblePosition(visibleEnd) && !isEndOfBlock(visibleEnd)) rangeLength--; } endOffset = std::min(startOffset + rangeLength, static_cast<int>(accessibilityObjectLength(coreObject))); }
bool InPageSearchManager::findNextString(const String& text, FindOptions findOptions, bool wrap, bool highlightAllMatches, bool selectActiveMatchOnClear) { bool highlightAllMatchesStateChanged = m_highlightAllMatches != highlightAllMatches; m_highlightAllMatches = highlightAllMatches; if (!text.length()) { clearTextMatches(selectActiveMatchOnClear); cancelPendingScopingEffort(); m_activeSearchString = String(); m_webPage->m_client->updateFindStringResult(m_activeMatchCount, m_activeMatchIndex); return false; } if (!shouldSearchForText(text)) { m_activeSearchString = text; m_webPage->m_client->updateFindStringResult(m_activeMatchCount, m_activeMatchIndex); return false; } // Validate the range in case any node has been removed since last search. if (m_activeMatch && !m_activeMatch->boundaryPointsValid()) m_activeMatch = 0; ExceptionCode ec = 0; RefPtr<Range> searchStartingPoint = m_activeMatch ? m_activeMatch->cloneRange(ec) : 0; bool newSearch = highlightAllMatchesStateChanged || (m_activeSearchString != text); bool forward = !(findOptions & WebCore::Backwards); if (newSearch) { // Start a new search. m_activeSearchString = text; cancelPendingScopingEffort(); m_scopingCaseInsensitive = findOptions & CaseInsensitive; m_webPage->m_page->unmarkAllTextMatches(); } else { // Searching for same string should start from the end of last match. if (m_activeMatch) { if (forward) searchStartingPoint->setStart(searchStartingPoint->endPosition()); else searchStartingPoint->setEnd(searchStartingPoint->startPosition()); } } // If there is any active selection, new search should start from the beginning of it. bool startFromSelection = false; VisibleSelection selection = m_webPage->focusedOrMainFrame()->selection().selection(); if (!selection.isNone()) { searchStartingPoint = selection.firstRange().get(); m_webPage->focusedOrMainFrame()->selection().clear(); startFromSelection = true; } Frame* currentActiveMatchFrame = selection.isNone() && m_activeMatch ? m_activeMatch->ownerDocument().frame() : m_webPage->focusedOrMainFrame(); if (findAndMarkText(text, searchStartingPoint.get(), currentActiveMatchFrame, findOptions, newSearch, startFromSelection)) return true; Frame* startFrame = currentActiveMatchFrame; do { currentActiveMatchFrame = DOMSupport::incrementFrame(currentActiveMatchFrame, forward, wrap); if (!currentActiveMatchFrame) { // We should only ever have a null frame if we haven't found any // matches and we're not wrapping. We have searched every frame. ASSERT(!wrap); m_webPage->m_client->updateFindStringResult(m_activeMatchCount, m_activeMatchIndex); return false; } if (findAndMarkText(text, 0, currentActiveMatchFrame, findOptions, newSearch, startFromSelection)) return true; } while (startFrame != currentActiveMatchFrame); clearTextMatches(); m_webPage->m_client->updateFindStringResult(m_activeMatchCount, m_activeMatchIndex); return false; }
void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing) { VisibleSelection selectionToDelete; VisibleSelection selectionAfterUndo; switch (endingSelection().selectionType()) { case VisibleSelection::RangeSelection: selectionToDelete = endingSelection(); selectionAfterUndo = selectionToDelete; break; case VisibleSelection::CaretSelection: { m_smartDelete = false; // Handle delete at beginning-of-block case. // Do nothing in the case that the caret is at the start of a // root editable element or at the start of a document. SelectionController selection; selection.setSelection(endingSelection()); selection.modify(SelectionController::EXTEND, SelectionController::FORWARD, granularity); if (killRing && selection.isCaret() && granularity != CharacterGranularity) selection.modify(SelectionController::EXTEND, SelectionController::FORWARD, CharacterGranularity); Position downstreamEnd = endingSelection().end().downstream(); VisiblePosition visibleEnd = endingSelection().visibleEnd(); if (visibleEnd == endOfParagraph(visibleEnd)) downstreamEnd = visibleEnd.next(true).deepEquivalent().downstream(); // When deleting tables: Select the table first, then perform the deletion if (downstreamEnd.node() && downstreamEnd.node()->renderer() && downstreamEnd.node()->renderer()->isTable() && downstreamEnd.deprecatedEditingOffset() == 0) { setEndingSelection(VisibleSelection(endingSelection().end(), lastDeepEditingPositionForNode(downstreamEnd.node()), DOWNSTREAM)); typingAddedToOpenCommand(ForwardDeleteKey); return; } // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any) if (granularity == ParagraphBoundary && selection.selection().isCaret() && isEndOfParagraph(selection.selection().visibleEnd())) selection.modify(SelectionController::EXTEND, SelectionController::FORWARD, CharacterGranularity); selectionToDelete = selection.selection(); if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start()) selectionAfterUndo = selectionToDelete; else { // It's a little tricky to compute what the starting selection would have been in the original document. // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on // the current state of the document and we'll get the wrong result. Position extent = startingSelection().end(); if (extent.node() != selectionToDelete.end().node()) extent = selectionToDelete.extent(); else { int extraCharacters; if (selectionToDelete.start().node() == selectionToDelete.end().node()) extraCharacters = selectionToDelete.end().deprecatedEditingOffset() - selectionToDelete.start().deprecatedEditingOffset(); else extraCharacters = selectionToDelete.end().deprecatedEditingOffset(); extent = Position(extent.node(), extent.deprecatedEditingOffset() + extraCharacters); } selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent); } break; } case VisibleSelection::NoSelection: ASSERT_NOT_REACHED(); break; } ASSERT(!selectionToDelete.isNone()); if (selectionToDelete.isNone()) return; if (selectionToDelete.isCaret() || !document()->frame()->shouldDeleteSelection(selectionToDelete)) return; if (killRing) document()->frame()->editor()->addToKillRing(selectionToDelete.toNormalizedRange().get(), false); // make undo select what was deleted setStartingSelection(selectionAfterUndo); CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); setSmartDelete(false); typingAddedToOpenCommand(ForwardDeleteKey); }
bool InPageSearchManager::findNextString(const String& text, bool forward) { if (!text.length()) { clearTextMatches(); cancelPendingScopingEffort(); m_activeSearchString = String(); return false; } if (!shouldSearchForText(text)) { m_activeSearchString = text; return false; } // Validate the range in case any node has been removed since last search. if (m_activeMatch && !m_activeMatch->boundaryPointsValid()) m_activeMatch = 0; RefPtr<Range> searchStartingPoint(m_activeMatch); bool newSearch = m_activeSearchString != text; if (newSearch) { // Start a new search. m_activeSearchString = text; cancelPendingScopingEffort(); m_webPage->m_page->unmarkAllTextMatches(); } else { // Search same string for next occurrence. setMarkerActive(m_activeMatch.get(), false /* active */); // Searching for same string should start from the end of last match. if (m_activeMatch) { if (forward) searchStartingPoint->setStart(searchStartingPoint->endPosition()); else searchStartingPoint->setEnd(searchStartingPoint->startPosition()); } } // If there is any active selection, new search should start from the beginning of it. VisibleSelection selection = m_webPage->focusedOrMainFrame()->selection()->selection(); if (!selection.isNone()) { searchStartingPoint = selection.firstRange().get(); m_webPage->focusedOrMainFrame()->selection()->clear(); } Frame* currentActiveMatchFrame = selection.isNone() && m_activeMatch ? m_activeMatch->ownerDocument()->frame() : m_webPage->focusedOrMainFrame(); const FindOptions findOptions = (forward ? 0 : Backwards) | CaseInsensitive | StartInSelection; if (findAndMarkText(text, searchStartingPoint.get(), currentActiveMatchFrame, findOptions, newSearch)) return true; Frame* startFrame = currentActiveMatchFrame; do { currentActiveMatchFrame = DOMSupport::incrementFrame(currentActiveMatchFrame, forward, true /* wrapFlag */); if (findAndMarkText(text, 0, currentActiveMatchFrame, findOptions, newSearch)) return true; } while (currentActiveMatchFrame && startFrame != currentActiveMatchFrame); clearTextMatches(); // FIXME: We need to notify client here. return false; }
static bool isSpellCheckingEnabledFor(const VisibleSelection& selection) { if (selection.isNone()) return false; return isSpellCheckingEnabledFor(selection.start()); }