EphemeralRange LocalFrame::rangeForPoint(const IntPoint& framePoint) { const PositionWithAffinity positionWithAffinity = positionForPoint(framePoint); if (positionWithAffinity.isNull()) return EphemeralRange(); VisiblePosition position = createVisiblePosition(positionWithAffinity); VisiblePosition previous = previousPositionOf(position); if (previous.isNotNull()) { const EphemeralRange previousCharacterRange = makeRange(previous, position); IntRect rect = editor().firstRectForRange(previousCharacterRange); if (rect.contains(framePoint)) return EphemeralRange(previousCharacterRange); } VisiblePosition next = nextPositionOf(position); const EphemeralRange nextCharacterRange = makeRange(position, next); if (nextCharacterRange.isNotNull()) { IntRect rect = editor().firstRectForRange(nextCharacterRange); if (rect.contains(framePoint)) return EphemeralRange(nextCharacterRange); } return EphemeralRange(); }
Dart_Handle Paragraph::getPositionForOffset(double dx, double dy) { LayoutPoint point(dx, dy); PositionWithAffinity position = m_renderView->positionForPoint(point); Dart_Handle result = Dart_NewList(2); Dart_ListSetAt(result, 0, ToDart(absoluteOffsetForPosition(position))); Dart_ListSetAt(result, 1, ToDart(static_cast<int>(position.affinity()))); return result; }
// FIXME: We should merge this function with // ApplyBlockElementCommand::formatSelection void IndentOutdentCommand::outdentRegion( const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection, EditingState* editingState) { VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection); VisiblePosition endOfLastParagraph = endOfParagraph(endOfSelection); if (endOfCurrentParagraph.deepEquivalent() == endOfLastParagraph.deepEquivalent()) { outdentParagraph(editingState); return; } Position originalSelectionEnd = endingSelection().end(); Position endAfterSelection = endOfParagraph(nextPositionOf(endOfLastParagraph)).deepEquivalent(); while (endOfCurrentParagraph.deepEquivalent() != endAfterSelection) { PositionWithAffinity endOfNextParagraph = endOfParagraph(nextPositionOf(endOfCurrentParagraph)) .toPositionWithAffinity(); if (endOfCurrentParagraph.deepEquivalent() == endOfLastParagraph.deepEquivalent()) { SelectionInDOMTree::Builder builder; if (originalSelectionEnd.isNotNull()) builder.collapse(originalSelectionEnd); setEndingSelection(builder.build()); } else { setEndingSelection(SelectionInDOMTree::Builder() .collapse(endOfCurrentParagraph.deepEquivalent()) .build()); } outdentParagraph(editingState); if (editingState->isAborted()) return; // outdentParagraph could move more than one paragraph if the paragraph // is in a list item. As a result, endAfterSelection and endOfNextParagraph // could refer to positions no longer in the document. if (endAfterSelection.isNotNull() && !endAfterSelection.isConnected()) break; document().updateStyleAndLayoutIgnorePendingStylesheets(); if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.isConnected()) { endOfCurrentParagraph = createVisiblePosition(endingSelection().end()); endOfNextParagraph = endOfParagraph(nextPositionOf(endOfCurrentParagraph)) .toPositionWithAffinity(); } endOfCurrentParagraph = createVisiblePosition(endOfNextParagraph); } }
PositionWithAffinity LocalFrame::positionForPoint(const IntPoint& framePoint) { HitTestResult result = eventHandler().hitTestResultAtPoint(framePoint); Node* node = result.innerNodeOrImageMapImage(); if (!node) return PositionWithAffinity(); LayoutObject* layoutObject = node->layoutObject(); if (!layoutObject) return PositionWithAffinity(); const PositionWithAffinity position = layoutObject->positionForPoint(result.localPoint()); if (position.isNull()) return PositionWithAffinity(firstPositionInOrBeforeNode(node)); return position; }
int Paragraph::absoluteOffsetForPosition(const PositionWithAffinity& position) { DCHECK(position.renderer()); unsigned offset = 0; for (RenderObject* object = m_renderView.get(); object; object = object->nextInPreOrder()) { if (object == position.renderer()) return offset + position.offset(); if (object->isText()) { RenderText* text = toRenderText(object); offset += text->textLength(); } } DCHECK(false); return 0; }
PassRefPtr<Range> Document::caretRangeFromPoint(int x, int y) { if (!renderView()) return nullptr; HitTestResult result = hitTestInDocument(this, x, y); RenderObject* renderer = result.renderer(); if (!renderer) return nullptr; PositionWithAffinity positionWithAffinity = result.position(); if (positionWithAffinity.position().isNull()) return nullptr; Position rangeCompliantPosition = positionWithAffinity.position().parentAnchoredEquivalent(); return Range::create(*this, rangeCompliantPosition, rangeCompliantPosition); }
void FrameCaret::invalidateCaretRect(bool forceInvalidation) { if (!m_caretRectDirty) return; m_caretRectDirty = false; DCHECK(caretPositionIsValidForDocument(*m_frame->document())); LayoutObject* layoutObject = nullptr; LayoutRect newRect; PositionWithAffinity currentCaretPosition = caretPosition(); if (isActive()) newRect = localCaretRectOfPosition(currentCaretPosition, layoutObject); Node* newNode = layoutObject ? layoutObject->node() : nullptr; // The current selected node |newNode| could be a child multiple levels below // its associated "anchor node" ancestor, so we reference and keep around the // anchor node for checking editability. // TODO(wkorman): Consider storing previous Position, rather than Node, and // making use of EditingUtilies::isEditablePosition() directly. Node* newAnchorNode = currentCaretPosition.position().parentAnchoredEquivalent().anchorNode(); if (newNode && newAnchorNode && newNode != newAnchorNode && newAnchorNode->layoutObject() && newAnchorNode->layoutObject()->isBox()) { newNode->layoutObject()->mapToVisualRectInAncestorSpace( toLayoutBoxModelObject(newAnchorNode->layoutObject()), newRect); } // It's possible for the timer to be inactive even though we want to // invalidate the caret. For example, when running as a layout test the // caret blink interval could be zero and thus |m_caretBlinkTimer| will // never be started. We provide |forceInvalidation| for use by paint // invalidation internals where we need to invalidate the caret regardless // of timer state. if (!forceInvalidation && !m_caretBlinkTimer.isActive() && newNode == m_previousCaretNode && newRect == m_previousCaretRect && m_caretVisibility == m_previousCaretVisibility) return; if (m_previousCaretAnchorNode && shouldRepaintCaret(*m_previousCaretAnchorNode)) { invalidateLocalCaretRect(m_previousCaretAnchorNode.get(), m_previousCaretRect); } if (newAnchorNode && shouldRepaintCaret(*newAnchorNode)) invalidateLocalCaretRect(newAnchorNode, newRect); m_previousCaretNode = newNode; m_previousCaretAnchorNode = newAnchorNode; m_previousCaretRect = newRect; m_previousCaretVisibility = m_caretVisibility; }
void CaretBase::updateCaretRect(const PositionWithAffinity& caretPosition) { m_caretLocalRect = LayoutRect(); if (caretPosition.isNull()) return; DCHECK(caretPosition.anchorNode()->layoutObject()); // First compute a rect local to the layoutObject at the selection start. LayoutObject* layoutObject; m_caretLocalRect = localCaretRectOfPosition(caretPosition, layoutObject); // Get the layoutObject that will be responsible for painting the caret // (which is either the layoutObject we just found, or one of its containers). LayoutBlockItem caretPainterItem = LayoutBlockItem(caretLayoutObject(caretPosition.anchorNode())); mapCaretRectToCaretPainter(LayoutItem(layoutObject), caretPainterItem, m_caretLocalRect); }
bool CaretBase::updateCaretRect(const PositionWithAffinity& caretPosition) { m_caretLocalRect = LayoutRect(); if (caretPosition.position().isNull()) return false; ASSERT(caretPosition.position().anchorNode()->layoutObject()); // First compute a rect local to the layoutObject at the selection start. LayoutObject* layoutObject; m_caretLocalRect = localCaretRectOfPosition(caretPosition, layoutObject); // Get the layoutObject that will be responsible for painting the caret // (which is either the layoutObject we just found, or one of its containers). LayoutBlock* caretPainter = caretLayoutObject(caretPosition.position().anchorNode()); mapCaretRectToCaretPainter(layoutObject, caretPainter, m_caretLocalRect); return true; }
PassRefPtr<Range> Document::caretRangeFromPoint(int x, int y) { if (!renderView()) return nullptr; HitTestResult result = hitTestInDocument(this, x, y); RenderObject* renderer = result.renderer(); if (!renderer) return nullptr; Node* node = renderer->node(); Node* shadowAncestorNode = ancestorInThisScope(node); if (shadowAncestorNode != node) { unsigned offset = shadowAncestorNode->nodeIndex(); ContainerNode* container = shadowAncestorNode->parentNode(); return Range::create(*this, container, offset, container, offset); } PositionWithAffinity positionWithAffinity = result.position(); if (positionWithAffinity.position().isNull()) return nullptr; Position rangeCompliantPosition = positionWithAffinity.position().parentAnchoredEquivalent(); return Range::create(*this, rangeCompliantPosition, rangeCompliantPosition); }
bool CaretBase::updateCaretRect(Document* document, const PositionWithAffinity& caretPosition) { m_caretLocalRect = LayoutRect(); m_caretRectNeedsUpdate = false; if (caretPosition.position().isNull()) return false; ASSERT(caretPosition.position().deprecatedNode()->renderer()); // First compute a rect local to the renderer at the selection start. RenderObject* renderer; LayoutRect localRect = localCaretRectOfPosition(caretPosition, renderer); // Get the renderer that will be responsible for painting the caret // (which is either the renderer we just found, or one of its containers). RenderBlock* caretPainter = caretRenderer(caretPosition.position().deprecatedNode()); // Compute an offset between the renderer and the caretPainter. bool unrooted = false; while (renderer != caretPainter) { RenderObject* containerObject = renderer->container(); if (!containerObject) { unrooted = true; break; } localRect.move(renderer->offsetFromContainer(containerObject, localRect.location())); renderer = containerObject; } if (!unrooted) m_caretLocalRect = localRect; return true; }
void InsertListCommand::doApply(EditingState* editingState) { // Only entry points are Editor::Command::execute and // IndentOutdentCommand::outdentParagraph, both of which ensure clean layout. DCHECK(!document().needsLayoutTreeUpdate()); if (!endingSelection().isNonOrphanedCaretOrRange()) return; if (!endingSelection().rootEditableElement()) return; VisiblePosition visibleEnd = endingSelection().visibleEnd(); VisiblePosition visibleStart = endingSelection().visibleStart(); // When a selection ends at the start of a paragraph, we rarely paint // the selection gap before that paragraph, because there often is no gap. // In a case like this, it's not obvious to the user that the selection // ends "inside" that paragraph, so it would be confusing if // InsertUn{Ordered}List operated on that paragraph. // FIXME: We paint the gap before some paragraphs that are indented with left // margin/padding, but not others. We should make the gap painting more // consistent and then use a left margin/padding rule here. if (visibleEnd.deepEquivalent() != visibleStart.deepEquivalent() && isStartOfParagraph(visibleEnd, CanSkipOverEditingBoundary)) { setEndingSelection(createVisibleSelection( visibleStart, previousPositionOf(visibleEnd, CannotCrossEditingBoundary), endingSelection().isDirectional())); if (!endingSelection().rootEditableElement()) return; } const HTMLQualifiedName& listTag = (m_type == OrderedList) ? olTag : ulTag; if (endingSelection().isRange()) { bool forceListCreation = false; VisibleSelection selection = selectionForParagraphIteration(endingSelection()); DCHECK(selection.isRange()); VisiblePosition visibleStartOfSelection = selection.visibleStart(); VisiblePosition visibleEndOfSelection = selection.visibleEnd(); PositionWithAffinity startOfSelection = visibleStartOfSelection.toPositionWithAffinity(); PositionWithAffinity endOfSelection = visibleEndOfSelection.toPositionWithAffinity(); Position startOfLastParagraph = startOfParagraph(visibleEndOfSelection, CanSkipOverEditingBoundary) .deepEquivalent(); Range* currentSelection = firstRangeOf(endingSelection()); ContainerNode* scopeForStartOfSelection = nullptr; ContainerNode* scopeForEndOfSelection = nullptr; // FIXME: This is an inefficient way to keep selection alive because // indexForVisiblePosition walks from the beginning of the document to the // visibleEndOfSelection everytime this code is executed. But not using // index is hard because there are so many ways we can lose selection inside // doApplyForSingleParagraph. int indexForStartOfSelection = indexForVisiblePosition( visibleStartOfSelection, scopeForStartOfSelection); int indexForEndOfSelection = indexForVisiblePosition(visibleEndOfSelection, scopeForEndOfSelection); if (startOfParagraph(visibleStartOfSelection, CanSkipOverEditingBoundary) .deepEquivalent() != startOfLastParagraph) { forceListCreation = !selectionHasListOfType(selection, listTag); VisiblePosition startOfCurrentParagraph = visibleStartOfSelection; while (inSameTreeAndOrdered(startOfCurrentParagraph.deepEquivalent(), startOfLastParagraph) && !inSameParagraph(startOfCurrentParagraph, createVisiblePosition(startOfLastParagraph), CanCrossEditingBoundary)) { // doApply() may operate on and remove the last paragraph of the // selection from the document if it's in the same list item as // startOfCurrentParagraph. Return early to avoid an infinite loop and // because there is no more work to be done. // FIXME(<rdar://problem/5983974>): The endingSelection() may be // incorrect here. Compute the new location of visibleEndOfSelection // and use it as the end of the new selection. if (!startOfLastParagraph.isConnected()) return; setEndingSelection(startOfCurrentParagraph); // Save and restore visibleEndOfSelection and startOfLastParagraph when // necessary since moveParagraph and movePragraphWithClones can remove // nodes. bool singleParagraphResult = doApplyForSingleParagraph( forceListCreation, listTag, *currentSelection, editingState); if (editingState->isAborted()) return; if (!singleParagraphResult) break; document().updateStyleAndLayoutIgnorePendingStylesheets(); // Make |visibleEndOfSelection| valid again. if (!endOfSelection.isConnected() || !startOfLastParagraph.isConnected()) { visibleEndOfSelection = visiblePositionForIndex( indexForEndOfSelection, scopeForEndOfSelection); endOfSelection = visibleEndOfSelection.toPositionWithAffinity(); // If visibleEndOfSelection is null, then some contents have been // deleted from the document. This should never happen and if it did, // exit early immediately because we've lost the loop invariant. DCHECK(visibleEndOfSelection.isNotNull()); if (visibleEndOfSelection.isNull() || !rootEditableElementOf(visibleEndOfSelection)) return; startOfLastParagraph = startOfParagraph(visibleEndOfSelection, CanSkipOverEditingBoundary) .deepEquivalent(); } else { visibleEndOfSelection = createVisiblePosition(endOfSelection); } startOfCurrentParagraph = startOfNextParagraph(endingSelection().visibleStart()); } setEndingSelection(visibleEndOfSelection); } doApplyForSingleParagraph(forceListCreation, listTag, *currentSelection, editingState); if (editingState->isAborted()) return; document().updateStyleAndLayoutIgnorePendingStylesheets(); // Fetch the end of the selection, for the reason mentioned above. if (!endOfSelection.isConnected()) { visibleEndOfSelection = visiblePositionForIndex(indexForEndOfSelection, scopeForEndOfSelection); if (visibleEndOfSelection.isNull()) return; } else { visibleEndOfSelection = createVisiblePosition(endOfSelection); } if (!startOfSelection.isConnected()) { visibleStartOfSelection = visiblePositionForIndex( indexForStartOfSelection, scopeForStartOfSelection); if (visibleStartOfSelection.isNull()) return; } else { visibleStartOfSelection = createVisiblePosition(startOfSelection); } setEndingSelection( createVisibleSelection(visibleStartOfSelection, visibleEndOfSelection, endingSelection().isDirectional())); return; } DCHECK(firstRangeOf(endingSelection())); doApplyForSingleParagraph(false, listTag, *firstRangeOf(endingSelection()), editingState); }
VisiblePosition::VisiblePosition(const PositionWithAffinity& positionWithAffinity) { init(positionWithAffinity.position(), positionWithAffinity.affinity()); }