VisiblePosition Frame::visiblePositionForPoint(const IntPoint& framePoint) { HitTestResult result = eventHandler()->hitTestResultAtPoint(framePoint, true); Node* node = result.innerNonSharedNode(); if (!node) return VisiblePosition(); RenderObject* renderer = node->renderer(); if (!renderer) return VisiblePosition(); VisiblePosition visiblePos = renderer->positionForPoint(result.localPoint()); if (visiblePos.isNull()) visiblePos = firstPositionInOrBeforeNode(node); return visiblePos; }
VisiblePosition AccessibilityObject::previousSentenceStartPosition(const VisiblePosition& visiblePos) const { // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer) // Related? <rdar://problem/3927736> Text selection broken in 8A336 if (visiblePos.isNull()) return VisiblePosition(); // make sure we move off of a sentence start VisiblePosition previousVisiblePos = visiblePos.previous(); if (previousVisiblePos.isNull()) return VisiblePosition(); // treat empty line as a separate sentence. VisiblePosition startPosition; String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get()); if (lineString.isEmpty()) startPosition = previousVisiblePos; else startPosition = startOfSentence(previousVisiblePos); return startPosition; }
RenderedPosition::RenderedPosition(const VisiblePosition& position) : m_layoutObject(nullptr) , m_inlineBox(nullptr) , m_offset(0) , m_prevLeafChild(uncachedInlineBox()) , m_nextLeafChild(uncachedInlineBox()) { if (position.isNull()) return; position.getInlineBoxAndOffset(m_inlineBox, m_offset); if (m_inlineBox) m_layoutObject = &m_inlineBox->layoutObject(); else m_layoutObject = layoutObjectFromPosition(position.deepEquivalent()); }
VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const { if (visiblePos.isNull()) return VisiblePosition(); // make sure we move off of a line start VisiblePosition prevVisiblePos = visiblePos.previous(); if (prevVisiblePos.isNull()) return VisiblePosition(); VisiblePosition startPosition = startOfLine(prevVisiblePos); // as long as the position hasn't reached the beginning of the doc, keep searching for a valid line start position // There are cases like when the position is next to a floating object that'll return null for start of line. This code will avoid returning null. if (startPosition.isNull()) { while (startPosition.isNull() && prevVisiblePos.isNotNull()) { prevVisiblePos = prevVisiblePos.previous(); startPosition = startOfLine(prevVisiblePos); } } else startPosition = updateAXLineStartForVisiblePosition(startPosition); return startPosition; }
VisiblePosition endOfWord(const VisiblePosition &c, EWordSide side) { VisiblePosition p = c; if (side == LeftWordIfOnBoundary) { if (isStartOfParagraph(c)) return c; p = c.previous(); if (p.isNull()) return c; } else if (isEndOfParagraph(c)) return c; return nextBoundary(p, endWordBoundary); }
RenderedPosition::RenderedPosition(const VisiblePosition& position) : m_renderer(0) , m_inlineBox(0) , m_offset(0) , m_prevLeafChild(uncachedInlineBox()) , m_nextLeafChild(uncachedInlineBox()) { if (position.isNull()) return; position.getInlineBoxAndOffset(m_inlineBox, m_offset); if (m_inlineBox) m_renderer = m_inlineBox->renderer(); else m_renderer = rendererFromPosition(position.deepEquivalent()); }
static VisiblePosition visiblePositionForPoint(Frame* frame, IntPoint outerPoint) { ASSERT(frame); HitTestResult result = frame->eventHandler()->hitTestResultAtPoint(outerPoint, true); Node* node = result.innerNode(); if (!node) return VisiblePosition(); RenderObject* renderer = node->renderer(); if (!renderer) return VisiblePosition(); VisiblePosition visiblePos = renderer->positionForCoordinates(result.localPoint().x(), result.localPoint().y()); if (visiblePos.isNull()) visiblePos = VisiblePosition(Position(node, 0)); return visiblePos; }
VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const { // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer) // Related? <rdar://problem/3927736> Text selection broken in 8A336 if (visiblePos.isNull()) return VisiblePosition(); // make sure we move off of a sentence end VisiblePosition nextVisiblePos = visiblePos.next(); if (nextVisiblePos.isNull()) return VisiblePosition(); // an empty line is considered a sentence. If it's skipped, then the sentence parser will not // see this empty line. Instead, return the end position of the empty line. VisiblePosition endPosition; String lineString = plainText(makeRange(startOfLine(nextVisiblePos), endOfLine(nextVisiblePos)).get()); if (lineString.isEmpty()) endPosition = nextVisiblePos; else endPosition = endOfSentence(nextVisiblePos); return endPosition; }
VisiblePosition startOfWord(const VisiblePosition &c, EWordSide side) { // FIXME: This returns a null VP for c at the start of the document // and side == LeftWordIfOnBoundary VisiblePosition p = c; if (side == RightWordIfOnBoundary) { // at paragraph end, the startofWord is the current position if (isEndOfParagraph(c)) return c; p = c.next(); if (p.isNull()) return c; } return previousBoundary(p, startWordBoundary); }
static VisiblePosition endPositionForLine(const VisiblePosition& c) { if (c.isNull()) return VisiblePosition(); RootInlineBox *rootBox = rootBoxForLine(c); if (!rootBox) { // There are VisiblePositions at offset 0 in blocks without // RootInlineBoxes, like empty editable blocks and bordered blocks. Position p = c.deepEquivalent(); if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.deprecatedEditingOffset() == 0) return c; return VisiblePosition(); } // Generated content (e.g. list markers and CSS :before and :after // pseudoelements) have no corresponding DOM element, and so cannot be // represented by a VisiblePosition. Use whatever precedes instead. Node *endNode; InlineBox *endBox = rootBox->lastLeafChild(); while (1) { if (!endBox) return VisiblePosition(); RenderObject *endRenderer = endBox->renderer(); if (!endRenderer) return VisiblePosition(); endNode = endRenderer->node(); if (endNode) break; endBox = endBox->prevLeafChild(); } int endOffset = 1; if (endNode->hasTagName(brTag)) { endOffset = 0; } else if (endBox->isInlineTextBox()) { InlineTextBox *endTextBox = static_cast<InlineTextBox *>(endBox); endOffset = endTextBox->start(); if (!endTextBox->isLineBreak()) endOffset += endTextBox->len(); } return VisiblePosition(endNode, endOffset, VP_UPSTREAM_IF_POSSIBLE); }
RenderedPosition::RenderedPosition(const VisiblePosition& position) : m_layoutObject(nullptr) , m_inlineBox(nullptr) , m_offset(0) , m_prevLeafChild(uncachedInlineBox()) , m_nextLeafChild(uncachedInlineBox()) { if (position.isNull()) return; InlineBoxPosition boxPosition = computeInlineBoxPosition(position); m_inlineBox = boxPosition.inlineBox; m_offset = boxPosition.offsetInBox; if (m_inlineBox) m_layoutObject = &m_inlineBox->layoutObject(); else m_layoutObject = layoutObjectFromPosition(position.deepEquivalent()); }
static VisiblePosition startPositionForLine(const VisiblePosition& c) { if (c.isNull()) return VisiblePosition(); RootInlineBox *rootBox = rootBoxForLine(c); if (!rootBox) { // There are VisiblePositions at offset 0 in blocks without // RootInlineBoxes, like empty editable blocks and bordered blocks. Position p = c.deepEquivalent(); if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.deprecatedEditingOffset() == 0) return positionAvoidingFirstPositionInTable(c); return VisiblePosition(); } // Generated content (e.g. list markers and CSS :before and :after // pseudoelements) have no corresponding DOM element, and so cannot be // represented by a VisiblePosition. Use whatever follows instead. InlineBox *startBox = rootBox->firstLeafChild(); Node *startNode; while (1) { if (!startBox) return VisiblePosition(); RenderObject *startRenderer = startBox->renderer(); if (!startRenderer) return VisiblePosition(); startNode = startRenderer->node(); if (startNode) break; startBox = startBox->nextLeafChild(); } int startOffset = 0; if (startBox->isInlineTextBox()) { InlineTextBox *startTextBox = static_cast<InlineTextBox *>(startBox); startOffset = startTextBox->start(); } VisiblePosition visPos = VisiblePosition(startNode, startOffset, DOWNSTREAM); return positionAvoidingFirstPositionInTable(visPos); }
// FIXME: indexForVisiblePosition and visiblePositionForIndex use TextIterators to convert between // VisiblePositions and indices. But TextIterator iteration using TextIteratorEmitsCharactersBetweenAllVisiblePositions // does not exactly match VisiblePosition iteration, so using them to preserve a selection during an editing // opertion is unreliable. TextIterator's TextIteratorEmitsCharactersBetweenAllVisiblePositions mode needs to be fixed, // or these functions need to be changed to iterate using actual VisiblePositions. // FIXME: Deploy these functions everywhere that TextIterators are used to convert between VisiblePositions and indices. int indexForVisiblePosition(const VisiblePosition& visiblePosition, RefPtr<ContainerNode>& scope) { if (visiblePosition.isNull()) return 0; Position p(visiblePosition.deepEquivalent()); Document* document = p.anchorNode()->document(); ShadowRoot* shadowRoot = p.anchorNode()->containingShadowRoot(); if (shadowRoot) scope = shadowRoot; else scope = document->documentElement(); RefPtr<Range> range = Range::create(document, firstPositionInNode(scope.get()), p.parentAnchoredEquivalent()); return TextIterator::rangeLength(range.get(), true); }
VisiblePosition endOfLine(const VisiblePosition& c) { VisiblePosition visPos = endPositionForLine(c); // Make sure the end of line is at the same line as the given input position. Else use the previous position to // obtain end of line. This condition happens when the input position is before the space character at the end // of a soft-wrapped non-editable line. In this scenario, endPositionForLine would incorrectly hand back a position // in the next line instead. This fix is to account for the discrepancy between lines with webkit-line-break:after-white-space style // versus lines without that style, which would break before a space by default. if (!inSameLine(c, visPos)) { visPos = c.previous(); if (visPos.isNull()) return VisiblePosition(); visPos = endPositionForLine(visPos); } return c.honorEditableBoundaryAtOrBefore(visPos); }
bool Editor::canDeleteRange(Range* range) const { Node* startContainer = range->startContainer(); Node* endContainer = range->endContainer(); if (!startContainer || !endContainer) return false; if (!startContainer->hasEditableStyle() || !endContainer->hasEditableStyle()) return false; if (range->collapsed()) { VisiblePosition start(range->startPosition(), DOWNSTREAM); VisiblePosition previous = start.previous(); // FIXME: We sometimes allow deletions at the start of editable roots, like when the caret is in an empty list item. if (previous.isNull() || previous.deepEquivalent().deprecatedNode()->rootEditableElement() != startContainer->rootEditableElement()) return false; } return true; }
// Abs x/y position of the caret ignoring transforms. // TODO(yosin) navigation with transforms should be smarter. static int lineDirectionPointForBlockDirectionNavigationOf(const VisiblePosition& visiblePosition) { if (visiblePosition.isNull()) return 0; LayoutObject* layoutObject; LayoutRect localRect = localCaretRectOfPosition(visiblePosition.toPositionWithAffinity(), layoutObject); if (localRect.isEmpty() || !layoutObject) return 0; // This ignores transforms on purpose, for now. Vertical navigation is done // without consulting transforms, so that 'up' in transformed text is 'up' // relative to the text, not absolute 'up'. FloatPoint caretPoint = layoutObject->localToAbsolute(FloatPoint(localRect.location())); LayoutObject* containingBlock = layoutObject->containingBlock(); if (!containingBlock) containingBlock = layoutObject; // Just use ourselves to determine the writing mode if we have no containing block. return containingBlock->isHorizontalWritingMode() ? caretPoint.x() : caretPoint.y(); }
VisiblePosition VisiblePosition::skipToStartOfEditingBoundary(const VisiblePosition &pos) const { if (pos.isNull()) return pos; ContainerNode* highestRoot = highestEditableRoot(deepEquivalent()); ContainerNode* highestRootOfPos = highestEditableRoot(pos.deepEquivalent()); // Return pos itself if the two are from the very same editable region, or both are non-editable. if (highestRootOfPos == highestRoot) return pos; // If this is not editable but |pos| has an editable root, skip to the start if (!highestRoot && highestRootOfPos) return VisiblePosition(previousVisuallyDistinctCandidate(Position(highestRootOfPos, PositionAnchorType::BeforeAnchor).parentAnchoredEquivalent())); // That must mean that |pos| is not editable. Return the last position before pos that is in the same editable region as this position return lastEditableVisiblePositionBeforePositionInRoot(pos.deepEquivalent(), highestRoot); }
VisiblePosition VisiblePosition::skipToEndOfEditingBoundary(const VisiblePosition &pos) const { if (pos.isNull()) return pos; ContainerNode* highestRoot = highestEditableRoot(deepEquivalent()); ContainerNode* highestRootOfPos = highestEditableRoot(pos.deepEquivalent()); // Return pos itself if the two are from the very same editable region, or both are non-editable. if (highestRootOfPos == highestRoot) return pos; // If this is not editable but |pos| has an editable root, skip to the end if (!highestRoot && highestRootOfPos) return VisiblePosition(Position(highestRootOfPos, PositionAnchorType::AfterAnchor).parentAnchoredEquivalent()); // That must mean that |pos| is not editable. Return the next position after pos that is in the same editable region as this position return firstEditableVisiblePositionAfterPositionInRoot(pos.deepEquivalent(), highestRoot); }
bool SelectionController::modify(EAlter alter, EDirection dir, TextGranularity granularity) { if (frame()) frame()->setSelectionGranularity(granularity); setModifyBias(alter, dir); VisiblePosition pos; switch (dir) { // EDIT FIXME: These need to handle bidi case RIGHT: case FORWARD: if (alter == EXTEND) pos = modifyExtendingRightForward(granularity); else pos = modifyMovingRightForward(granularity); break; case LEFT: case BACKWARD: if (alter == EXTEND) pos = modifyExtendingLeftBackward(granularity); else pos = modifyMovingLeftBackward(granularity); break; } if (pos.isNull()) return false; switch (alter) { case MOVE: moveTo(pos); break; case EXTEND: setExtent(pos); break; } setNeedsLayout(); return true; }
// FIXME: indexForVisiblePosition and visiblePositionForIndex use TextIterators to convert between // VisiblePositions and indices. But TextIterator iteration using TextIteratorEmitsCharactersBetweenAllVisiblePositions // does not exactly match VisiblePosition iteration, so using them to preserve a selection during an editing // opertion is unreliable. TextIterator's TextIteratorEmitsCharactersBetweenAllVisiblePositions mode needs to be fixed, // or these functions need to be changed to iterate using actual VisiblePositions. // FIXME: Deploy these functions everywhere that TextIterators are used to convert between VisiblePositions and indices. int indexForVisiblePosition(const VisiblePosition& visiblePosition, RefPtr<Element>& scope) { if (visiblePosition.isNull()) return 0; Position p(visiblePosition.deepEquivalent()); Document* document = p.anchorNode()->document(); Node* shadowRoot = p.anchorNode()->shadowTreeRootNode(); if (shadowRoot) { // Use the shadow root for form elements, since TextIterators will not enter shadow content. ASSERT(shadowRoot->isElementNode()); scope = static_cast<Element*>(shadowRoot); } else scope = document->documentElement(); RefPtr<Range> range = Range::create(document, firstPositionInNode(scope.get()), p.parentAnchoredEquivalent()); return TextIterator::rangeLength(range.get(), true); }
VisiblePosition LocalFrame::visiblePositionForPoint(const IntPoint& framePoint) { if (!contentRenderer() || !view() || !view()->didFirstLayout()) return VisiblePosition(); LayoutSize padding = LayoutSize(); HitTestResult result(framePoint, padding.height(), padding.width(), padding.height(), padding.width()); HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active); contentRenderer()->hitTest(request, result); Node* node = result.innerNonSharedNode(); if (!node) return VisiblePosition(); RenderObject* renderer = node->renderer(); if (!renderer) return VisiblePosition(); VisiblePosition visiblePos = VisiblePosition(renderer->positionForPoint(result.localPoint())); if (visiblePos.isNull()) visiblePos = VisiblePosition(firstPositionInOrBeforeNode(node)); return visiblePos; }
unsigned AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const { if (visiblePos.isNull()) return 0; unsigned lineCount = 0; VisiblePosition currentVisiblePos = visiblePos; VisiblePosition savedVisiblePos; // move up until we get to the top // FIXME: This only takes us to the top of the rootEditableElement, not the top of the // top document. while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos))) { lineCount += 1; savedVisiblePos = currentVisiblePos; VisiblePosition prevVisiblePos = previousLinePosition(visiblePos, 0); currentVisiblePos = prevVisiblePos; } return lineCount - 1; }
static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition) { // A line in the accessibility sense should include floating objects, such as aligned image, as part of a line. // So let's update the position to include that. VisiblePosition tempPosition; VisiblePosition startPosition = visiblePosition; while (true) { tempPosition = startPosition.previous(); if (tempPosition.isNull()) break; Position p = tempPosition.deepEquivalent(); RenderObject* renderer = p.deprecatedNode()->renderer(); if (!renderer || (renderer->isRenderBlock() && !p.deprecatedEditingOffset())) break; if (!RenderedPosition(tempPosition).isNull()) break; startPosition = tempPosition; } return startPosition; }
VisiblePosition startOfLine(const VisiblePosition& c) { VisiblePosition visPos = startPositionForLine(c); if (visPos.isNotNull()) { // Make sure the start of line is not greater than the given input position. Else use the previous position to // obtain start of line. This condition happens when the input position is before the space character at the end // of a soft-wrapped non-editable line. In this scenario, startPositionForLine would incorrectly hand back a position // greater than the input position. This fix is to account for the discrepancy between lines with webkit-line-break:after-white-space // style versus lines without that style, which would break before a space by default. Position p = visPos.deepEquivalent(); if (p.deprecatedEditingOffset() > c.deepEquivalent().deprecatedEditingOffset() && p.node()->isSameNode(c.deepEquivalent().node())) { visPos = c.previous(); if (visPos.isNull()) return VisiblePosition(); visPos = startPositionForLine(visPos); } } return c.honorEditableBoundaryAtOrAfter(visPos); }
// Returns true if at least one text node was split. bool IndentOutdentCommand::splitTextNodes(const VisiblePosition& start, int numParagraphs) { VisiblePosition currentParagraphStart = start; bool hasSplit = false; int paragraphCount; for (paragraphCount = 0; paragraphCount < numParagraphs; ++paragraphCount) { // If there are multiple paragraphs in a single text node, we split the text node into a separate node for each paragraph. if (currentParagraphStart.deepEquivalent().node()->isTextNode() && currentParagraphStart.deepEquivalent().node() == startOfParagraph(currentParagraphStart.previous()).deepEquivalent().node()) { Text* textNode = static_cast<Text *>(currentParagraphStart.deepEquivalent().node()); int offset = currentParagraphStart.deepEquivalent().offsetInContainerNode(); splitTextNode(textNode, offset); currentParagraphStart = VisiblePosition(textNode, 0, VP_DEFAULT_AFFINITY); hasSplit = true; } VisiblePosition nextParagraph = startOfParagraph(endOfParagraph(currentParagraphStart).next()); if (nextParagraph.isNull()) break; currentParagraphStart = nextParagraph; } return hasSplit; }
String DeleteSelectionCommand::originalStringForAutocorrectionAtBeginningOfSelection() { if (!m_selectionToDelete.isRange()) return String(); VisiblePosition startOfSelection = m_selectionToDelete.start(); if (!isStartOfWord(startOfSelection)) return String(); VisiblePosition nextPosition = startOfSelection.next(); if (nextPosition.isNull()) return String(); RefPtr<Range> rangeOfFirstCharacter = Range::create(document(), startOfSelection.deepEquivalent(), nextPosition.deepEquivalent()); Vector<DocumentMarker*> markers = document()->markers()->markersInRange(rangeOfFirstCharacter.get(), DocumentMarker::Autocorrected); for (size_t i = 0; i < markers.size(); ++i) { const DocumentMarker* marker = markers[i]; int startOffset = marker->startOffset(); if (startOffset == startOfSelection.deepEquivalent().offsetInContainerNode()) return marker->description(); } return String(); }
PassRefPtr<Range> Frame::rangeForPoint(const LayoutPoint& framePoint) { VisiblePosition position = visiblePositionForPoint(framePoint); if (position.isNull()) return 0; VisiblePosition previous = position.previous(); if (previous.isNotNull()) { RefPtr<Range> previousCharacterRange = makeRange(previous, position); LayoutRect rect = editor()->firstRectForRange(previousCharacterRange.get()); if (rect.contains(framePoint)) return previousCharacterRange.release(); } VisiblePosition next = position.next(); if (RefPtr<Range> nextCharacterRange = makeRange(position, next)) { LayoutRect rect = editor()->firstRectForRange(nextCharacterRange.get()); if (rect.contains(framePoint)) return nextCharacterRange.release(); } return 0; }
void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos) { // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence. // This also allows callers to check for failure by looking at textMarkerData upon return. memset(&textMarkerData, 0, sizeof(TextMarkerData)); if (visiblePos.isNull()) return; Position deepPos = visiblePos.deepEquivalent(); Node* domNode = deepPos.deprecatedNode(); ASSERT(domNode); if (!domNode) return; if (domNode->isHTMLElement()) { HTMLInputElement* inputElement = domNode->toInputElement(); if (inputElement && inputElement->isPasswordField()) return; } // locate the renderer, which must exist for a visible dom node RenderObject* renderer = domNode->renderer(); ASSERT(renderer); // find or create an accessibility object for this renderer AXObjectCache* cache = renderer->document()->axObjectCache(); RefPtr<AccessibilityObject> obj = cache->getOrCreate(renderer); textMarkerData.axID = obj.get()->axObjectID(); textMarkerData.node = domNode; textMarkerData.offset = deepPos.deprecatedEditingOffset(); textMarkerData.affinity = visiblePos.affinity(); cache->setNodeInUse(domNode); }
Position adjustedSelectionStartForStyleComputation(const VisibleSelection& selection) { // This function is used by range style computations to avoid bugs like: // <rdar://problem/4017641> REGRESSION (Mail): you can only bold/unbold a selection starting from end of line once // It is important to skip certain irrelevant content at the start of the selection, so we do not wind up // with a spurious "mixed" style. VisiblePosition visiblePosition = selection.start(); if (visiblePosition.isNull()) return Position(); // if the selection is a caret, just return the position, since the style // behind us is relevant if (selection.isCaret()) return visiblePosition.deepEquivalent(); // if the selection starts just before a paragraph break, skip over it if (isEndOfParagraph(visiblePosition)) return visiblePosition.next().deepEquivalent().downstream(); // otherwise, make sure to be at the start of the first selected node, // instead of possibly at the end of the last node before the selection return visiblePosition.deepEquivalent().downstream(); }
VisiblePosition VisiblePosition::honorEditingBoundaryAtOrAfter(const VisiblePosition &pos, bool* reachedBoundary) const { if (reachedBoundary) *reachedBoundary = false; if (pos.isNull()) return pos; auto* highestRoot = highestEditableRoot(deepEquivalent()); // Return empty position if pos is not somewhere inside the editable region containing this position if (highestRoot && !pos.deepEquivalent().deprecatedNode()->isDescendantOf(highestRoot)) { if (reachedBoundary) *reachedBoundary = true; return VisiblePosition(); } // Return pos itself if the two are from the very same editable region, or both are non-editable // FIXME: In the non-editable case, just because the new position is non-editable doesn't mean movement // to it is allowed. VisibleSelection::adjustForEditableContent has this problem too. if (highestEditableRoot(pos.deepEquivalent()) == highestRoot) { if (reachedBoundary) *reachedBoundary = *this == pos; return pos; } // Return empty position if this position is non-editable, but pos is editable // FIXME: Move to the next non-editable region. if (!highestRoot) { if (reachedBoundary) *reachedBoundary = true; return VisiblePosition(); } // Return the next position after pos that is in the same editable region as this position return firstEditablePositionAfterPositionInRoot(pos.deepEquivalent(), highestRoot); }