static void clearSelectionIfNeeded(Frame* oldFocusedFrame, Frame* newFocusedFrame, Node* newFocusedNode) { if (!oldFocusedFrame || !newFocusedFrame) return; if (oldFocusedFrame->document() != newFocusedFrame->document()) return; SelectionController* s = oldFocusedFrame->selection(); if (s->isNone()) return; bool caretBrowsing = oldFocusedFrame->settings()->caretBrowsingEnabled(); if (caretBrowsing) return; Node* selectionStartNode = s->selection().start().node(); if (selectionStartNode == newFocusedNode || selectionStartNode->isDescendantOf(newFocusedNode) || selectionStartNode->shadowAncestorNode() == newFocusedNode) return; if (Node* mousePressNode = newFocusedFrame->eventHandler()->mousePressNode()) if (mousePressNode->renderer() && !mousePressNode->canStartSelection()) if (Node* root = s->rootEditableElement()) if (Node* shadowAncestorNode = root->shadowAncestorNode()) // Don't do this for textareas and text fields, when they lose focus their selections should be cleared // and then restored when they regain focus, to match other browsers. if (!shadowAncestorNode->hasTagName(inputTag) && !shadowAncestorNode->hasTagName(textareaTag)) return; s->clear(); }
WebCore::IntRect SelectionHandler::selectionEndCaretRect() { if (!m_selectionActive) return IntRect(); ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection()); WebCore::IntRect caretLocation(m_webPage->focusedOrMainFrame()->selection()->selection().visibleEnd().absoluteCaretBounds()); SelectionController nextCaretSelection; nextCaretSelection.setSelection(VisibleSelection(m_webPage->focusedOrMainFrame()->selection()->selection().visibleEnd())); nextCaretSelection.modify(SelectionController::AlterationMove, SelectionController::DirectionForward, CharacterGranularity); WebCore::IntRect nextCaretLocation(nextCaretSelection.selection().visibleStart().absoluteCaretBounds()); if (nextCaretLocation.y() == caretLocation.y()) { // The next caret position is on this line, use the middle of the character // as the active selection point. caretLocation.move((nextCaretLocation.x() - caretLocation.x()) / 2, 0); } else { // Caret position is at the end of a line, advance 1 pixel to ensure // overlap with character. caretLocation.move(1, 0); } if (m_webPage->focusedOrMainFrame()->ownerRenderer()) { WebCore::IntPoint frameOffset = m_webPage->focusedOrMainFrame()->ownerRenderer()->absoluteContentBox().location(); caretLocation.move(frameOffset.x(), frameOffset.y()); } return caretLocation; }
bool DOMSelection::containsNode(const Node* n, bool allowPartial) const { if (!m_frame) return false; SelectionController* selection = m_frame->selection(); if (!n || selection->isNone()) return false; Node* parentNode = n->parentNode(); unsigned nodeIndex = n->nodeIndex(); RefPtr<Range> selectedRange = selection->selection().toNormalizedRange(); if (!parentNode) return false; ExceptionCode ec = 0; bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(ec), selectedRange->startOffset(ec)) >= 0 && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(ec), selectedRange->endOffset(ec)) <= 0; ASSERT(!ec); if (nodeFullySelected) return true; bool nodeFullyUnselected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(ec), selectedRange->endOffset(ec)) > 0 || Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(ec), selectedRange->startOffset(ec)) < 0; ASSERT(!ec); if (nodeFullyUnselected) return false; return allowPartial || n->isTextNode(); }
void DOMSelection::deleteFromDocument() { if (!m_frame) return; SelectionController* selection = m_frame->selection(); if (selection->isNone()) return; if (isCollapsed()) selection->modify(SelectionController::EXTEND, SelectionController::BACKWARD, CharacterGranularity); RefPtr<Range> selectedRange = selection->selection().toNormalizedRange(); ExceptionCode ec = 0; selectedRange->deleteContents(ec); ASSERT(!ec); setBaseAndExtent(selectedRange->startContainer(ec), selectedRange->startOffset(ec), selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec); ASSERT(!ec); }
void DOMSelection::addRange(Range* r) { if (!m_frame) return; if (!r) return; SelectionController* selection = m_frame->selection(); if (selection->isNone()) { selection->setSelection(VisibleSelection(r)); return; } RefPtr<Range> range = selection->selection().toNormalizedRange(); ExceptionCode ec = 0; if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), ec) == -1) { // We don't support discontiguous selection. We don't do anything if r and range don't intersect. if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), ec) > -1) { if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1) // The original range and r intersect. selection->setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM)); else // r contains the original range. selection->setSelection(VisibleSelection(r)); } } else { // We don't support discontiguous selection. We don't do anything if r and range don't intersect. if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), ec) < 1) { if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1) // The original range contains r. selection->setSelection(VisibleSelection(range.get())); else // The original range and r intersect. selection->setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM)); } } }
static void clearSelectionIfNeeded(Frame* oldFocusedFrame, Frame* newFocusedFrame, Node* newFocusedNode) { if (!oldFocusedFrame || !newFocusedFrame) return; if (oldFocusedFrame->document() != newFocusedFrame->document()) return; SelectionController* s = oldFocusedFrame->selection(); if (s->isNone()) return; bool caretBrowsing = oldFocusedFrame->settings()->caretBrowsingEnabled(); if (caretBrowsing) return; Node* selectionStartNode = s->selection().start().deprecatedNode(); if (selectionStartNode == newFocusedNode || selectionStartNode->isDescendantOf(newFocusedNode) || selectionStartNode->shadowAncestorNode() == newFocusedNode) return; if (Node* mousePressNode = newFocusedFrame->eventHandler()->mousePressNode()) { if (mousePressNode->renderer() && !mousePressNode->canStartSelection()) { // Don't clear the selection for contentEditable elements, but do clear it for input and textarea. See bug 38696. Node * root = s->rootEditableElement(); if (!root) return; if (Node* shadowAncestorNode = root->shadowAncestorNode()) { if (!shadowAncestorNode->hasTagName(inputTag) && !shadowAncestorNode->hasTagName(textareaTag)) return; } } } s->clear(); }
void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing) { #if REMOVE_MARKERS_UPON_EDITING document()->frame()->editor()->removeSpellAndCorrectionMarkersFromWordsToBeEdited(false); #endif 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::AlterationExtend, DirectionForward, granularity); if (killRing && selection.isCaret() && granularity != CharacterGranularity) selection.modify(SelectionController::AlterationExtend, DirectionForward, 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()) { 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::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.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, 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() || !document()->frame()->selection()->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); }
void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) { #if REMOVE_MARKERS_UPON_EDITING document()->frame()->editor()->removeSpellAndCorrectionMarkersFromWordsToBeEdited(false); #endif 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; SelectionController selection; selection.setSelection(endingSelection()); selection.modify(SelectionController::AlterationExtend, DirectionBackward, granularity); if (killRing && selection.isCaret() && granularity != CharacterGranularity) selection.modify(SelectionController::AlterationExtend, DirectionBackward, CharacterGranularity); if (endingSelection().visibleStart().previous(true).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(true).isNull() && makeEditableRootEmpty()) { typingAddedToOpenCommand(DeleteKey); return; } } VisiblePosition visibleStart(endingSelection().visibleStart()); // If we have a caret selection on an empty cell, we have nothing to do. if (isEmptyTableCell(visibleStart.deepEquivalent().node())) 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(true))) { // 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(SelectionController::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(positionAfterNode(table), endingSelection().start(), DOWNSTREAM)); typingAddedToOpenCommand(DeleteKey); return; } selectionToDelete = selection.selection(); if (granularity == CharacterGranularity && selectionToDelete.end().node() == selectionToDelete.start().node() && selectionToDelete.end().deprecatedEditingOffset() - selectionToDelete.start().deprecatedEditingOffset() > 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() || !document()->frame()->selection()->shouldDeleteSelection(selectionToDelete)) return; if (killRing) document()->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); }
void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity) { Selection selectionToDelete; Selection selectionAfterUndo; switch (endingSelection().state()) { case Selection::RANGE: selectionToDelete = endingSelection(); selectionAfterUndo = selectionToDelete; break; case Selection::CARET: { 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); 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.offset() == 0) { setEndingSelection(Selection(endingSelection().end(), Position(downstreamEnd.node(), maxDeepOffset(downstreamEnd.node())), DOWNSTREAM)); typingAddedToOpenCommand(); 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 Selection 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().offset() - selectionToDelete.start().offset(); else extraCharacters = selectionToDelete.end().offset(); extent = Position(extent.node(), extent.offset() + extraCharacters); } selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent); } break; } case Selection::NONE: ASSERT_NOT_REACHED(); break; } if (selectionToDelete.isCaretOrRange() && document()->frame()->shouldDeleteSelection(selectionToDelete)) { // make undo select what was deleted setStartingSelection(selectionAfterUndo); CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); setSmartDelete(false); typingAddedToOpenCommand(); } }
void TypingCommand::deleteKeyPressed(TextGranularity granularity) { Selection selectionToDelete; Selection selectionAfterUndo; switch (endingSelection().state()) { case Selection::RANGE: selectionToDelete = endingSelection(); selectionAfterUndo = selectionToDelete; break; case Selection::CARET: { m_smartDelete = false; SelectionController selection; selection.setSelection(endingSelection()); selection.modify(SelectionController::EXTEND, SelectionController::BACKWARD, granularity); // When the caret is at the start of the editable area in an empty list item, break out of the list item. if (endingSelection().visibleStart().previous(true).isNull()) { if (breakOutOfEmptyListItem()) { typingAddedToOpenCommand(); return; } } VisiblePosition visibleStart(endingSelection().visibleStart()); // 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(true))) { // 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(SelectionController::EXTEND, SelectionController::BACKWARD, granularity); // If the caret is just after a table, select the table and don't delete anything. } else if (Node* table = isFirstPositionAfterTable(visibleStart)) { setEndingSelection(Selection(Position(table, 0), endingSelection().start(), DOWNSTREAM)); typingAddedToOpenCommand(); return; } 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 Selection 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 Selection::NONE: ASSERT_NOT_REACHED(); break; } if (selectionToDelete.isCaretOrRange() && document()->frame()->shouldDeleteSelection(selectionToDelete)) { // 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(); } }