← Chapter 6: When VisiblePosition Is Triggered | Home | Chapter 8: Special Handling →
This appendix documents every function involved in the VisiblePosition computation pipeline, including private and utility functions. Organized by source file.
- Args: None
- Returns: Default (null) VisiblePosition
- Does: Initializes with null position, zero version numbers (DCHECK mode)
VisiblePositionTemplate<Strategy>::VisiblePositionTemplate(const PositionWithAffinityTemplate<Strategy>&)
- Args:
position_with_affinity— the canonical position + affinity - Returns: VisiblePosition wrapping the given position
- Does: Private constructor. Stores the position_with_affinity and captures DOM/style versions for validity checking
- Visibility: Private — only called from
Create()
- Args:
position_with_affinity— raw position with affinity - Returns:
VisiblePositionTemplate<Strategy>— the canonical visible position - Does: The main factory method. Canonicalizes the position, resolves affinity. See Chapter 2 for full algorithm. DCHECKs: connected, valid, no layout update needed. Uses
DocumentLifecycle::DisallowTransitionScope.
- Args: None
- Returns:
PositionTemplate<Strategy>— the canonical position - Does: Returns the position from the stored position_with_affinity
- Args: None
- Returns:
PositionTemplate<Strategy>— parent-anchored equivalent - Does: Calls
DeepEquivalent().ParentAnchoredEquivalent()
- Args: None
- Returns:
PositionWithAffinityTemplate<Strategy> - Does: Returns the stored position_with_affinity
- Args: None
- Returns:
TextAffinity— UPSTREAM or DOWNSTREAM - Does: Returns the affinity from the stored position_with_affinity
- Args: None
- Returns:
bool - Does: In DCHECK builds, checks DOM tree version and style version haven't changed since creation, and no layout update is needed. Always returns true in release.
- Args:
document— the document to check against - Returns:
bool - Does: Delegates to
position_with_affinity_.IsValidFor(document)
- Args: None
- Returns:
bool - Does: Returns true if position_with_affinity is null
- Args: None
- Returns:
bool - Does: Returns true if position_with_affinity is not null
- Args: None
- Returns:
bool - Does: Returns
DeepEquivalent().IsOrphan()— non-null but disconnected
Static Factory: AfterNode(const Node&), BeforeNode(const Node&), FirstPositionInNode(const Node&), LastPositionInNode(const Node&), InParentAfterNode(const Node&), InParentBeforeNode(const Node&)
- Args: A DOM node
- Returns:
VisiblePositionTemplate<Strategy> - Does: Creates a
PositionWithAffinityfrom the correspondingPosition::XxxNode()factory and callsCreate()
- Args:
position— raw DOM position,affinity— defaults tokDefault - Returns:
VisiblePosition - Does: Wraps position in
PositionWithAffinityand callsVisiblePosition::Create()
- Args:
position_with_affinity - Returns:
VisiblePosition - Does: Directly calls
VisiblePosition::Create()
- Args: Two
PositionWithAffinityTemplate<Strategy> - Returns:
bool - Does: Returns true if positions are on different lines but in the same inline formatting context. Used in
Create()to detect when canonicalization jumped to a previous line.
- Args: A raw position
- Returns: The canonical position (leftmost valid candidate)
- Does: Delegates to
CanonicalPosition<Strategy>(). See Chapter 2.3.
- Args: A raw position
- Returns: Canonical position
- Does:
- Try
MostBackwardCaretPosition→ check ifIsVisuallyEquivalentCandidate - Try
MostForwardCaretPosition→ check if candidate - Search wider with
NextCandidate/PreviousCandidate - Apply editable root constraints
- Apply same-block preference
- Try
- Bugs: crbug.com/472258, issues.chromium.org/40890187
- FIXME: FIXME (9535) — leftmost bias causes caret painting issues
- Args: A candidate position (must be
IsVisuallyEquivalentCandidate) - Returns: The upstream equivalent if also a candidate, otherwise the original
- Does: Tries
MostBackwardCaretPositionon the candidate; returns whichever is valid
- Args:
original_node,new_position_node - Returns:
bool - Does: Checks if two nodes are in the same block flow element. Special handling: if both editable, checks DOM hierarchy. If
new_position_nodeis descendant oforiginal_node, returns true. - Referenced tests:
editing/execCommand/indent-pre-list.html,editing/execCommand/indent-pre.html
- Args:
position,rule(default:kCannotCrossEditingBoundary),client(default:kOthers) - Returns: The most upstream visually equivalent caret position
- Does: Wrapper that converts to flat tree, calls the flat tree algorithm, then adjusts for shadow boundaries. See Chapter 5.8.
- FIXME: PositionIterator should respect Before/After positions
- Bug: crbug.com/1248744
- Args: Same as above
- Returns: Flat tree position
- Does: The core backward iteration algorithm. See Chapter 2.6 for full flowchart.
- Args: Same as backward
- Returns: The most downstream visually equivalent caret position
- Does: Wrapper + flat tree algorithm, mirror of backward
- Args: Same
- Returns: Flat tree position
- Does: Core forward iteration algorithm
- Args: DOM position + flat tree algorithm function pointer
- Returns: DOM position
- Does: Converts to flat tree, calls the algorithm, converts back, adjusts for shadow boundaries
- Special handling: Fast path when no shadow DOM involved
- Args: A position
- Returns: Whether this is a valid caret position
- Does: Delegates to
IsVisuallyEquivalentCandidateAlgorithm. See Chapter 2.8.
- Args: A position
- Returns:
bool - Does: Full candidate check: layout object, visibility, display lock, BR, text, SVG, table, editing boundary.
- TODO(leviw): Condition for BR should be
kBeforeAnchor
- Args: A DOM node
- Returns: Whether start and end of node are visually distinct
- Does: True for non-inline,
<marquee>, empty inline-blocks. False for inline tables, null. - —
<marquee>elements are moving so assume always distinct
- Args: A node
- Returns: The nearest ancestor where ends are visually distinct
- Does: Walks up
Strategy::ParentuntilEndsOfNodeAreVisuallyDistinctPositionsreturns true
- Args: A position iterator
- Returns:
bool - Does: True if at null node, atomic node, or start of node
- Args: A position
- Returns: Adjusted position for backward iteration
- Does: For
kAfterAnchor+user-select:contain→ToOffsetInAnchor(). Otherwise →EditingPositionOf(node, CaretMaxOffset).
- Args: A node
- Returns:
bool - Does: True for non-SVG, SVG
<text>(crbug.com/891908), SVG<foreignObject>(crbug.com/1348816). False for other SVG elements.
- Args: A node
- Returns:
bool - Does: True if the node is a text node with a first-letter pseudo-element that is invisible
- Args: A position
- Returns:
bool - Does: True if position is in rendered (non-collapsed) text. Checks: text node, valid layout object,
ContainsCaretOffset, grapheme boundary. - TODO(editing-dev): Should use
offset_in_nodeinstead oftext_offset
- Args: A position
- Returns:
bool - Does: True if position is at an editing boundary (adjacent to non-editable content)
- Args: A layout object
- Returns: Whether it has non-anonymous descendants with height
- Does: Skips display-locked, anonymous, pseudo elements. Checks text, boxes, inline objects.
- TODO(editing-dev): Semantics wrong for one-letter first-letter blocks
- Args:
PositionWithAffinity,EditingBoundaryCrossingRule - Returns:
VisiblePositionTemplate<Strategy> - Does: Calls
NextVisuallyDistinctCandidate, creates VisiblePosition, applies boundary rule
- Args:
Position,EditingBoundaryCrossingRule - Returns:
VisiblePositionTemplate<Strategy> - Does: Calls
PreviousVisuallyDistinctCandidate, creates VisiblePosition, applies boundary rule
- Public API wrappers calling the Algorithm versions
- Args: A visible position
- Returns:
UChar32 - Does: Gets
MostForwardCaretPosition, reads character from text node at that offset
- Args: A visible position
- Returns:
UChar32 - Does:
CharacterAfter(PreviousPositionOf(visible_position))
- Args: Candidate position, original anchor
- Returns: Adjusted position
- Does: If different editable roots, skips to end of the editable boundary
- Args: Candidate position, original anchor
- Returns: Adjusted position
- Does: Mirror — skips to start of editing boundary
AdjustBackwardPositionToAvoidCrossingEditingBoundariesTemplate<Strategy>(pos, anchor) — Static template
- Args:
PositionWithAffinity,Positionanchor - Returns:
PositionWithAffinity - Does: Returns pos if same editable root as anchor. Returns empty if pos is outside root. Returns
LastEditablePositionBeforePositionInRoot()otherwise.
AdjustForwardPositionToAvoidCrossingEditingBoundariesTemplate<Strategy>(pos, anchor) — Static template
- Args:
PositionWithAffinity,Positionanchor - Returns:
PositionWithAffinity - Does: Mirror of backward. Returns
FirstEditablePositionAfterPositionInRoot().
- Args: A position
- Returns:
Node* - Does: Walks up ancestors until editability changes (crossing the editing boundary)
- Args: A position
- Returns: Position at
FirstPositionInNode(*documentElement)
- Args: A visible position
- Returns: VisiblePosition at
LastPositionInNode(*documentElement)
- Args: Two positions
- Returns: Whether they render at different visual locations
- Does: Compares
LocalCaretRectOfPosition→LocalToAbsoluteQuadOf
- Args: A point in contents coordinates, a frame
- Returns: Hit-tested position respecting editing boundaries
- Does:
HitTestResultwith move/readonly/active/ignore-clipping flags
- Args: A position
- Returns: Position after whitespace
- Does: Iterates forward skipping spaces (but not newlines)
- TODO(editing-dev): Should consider U+20E3 (COMBINING ENCLOSING KEYCAP)
- Args: A position
- Returns:
EphemeralRangefrom pos to end of enclosing block - Does: Helper for whitespace skipping
- Args: A node
- Returns: True if node has no content for editing purposes
- Does:
!node.CanContainRangeEndPoint() || IsEmptyNonEditableNodeInEditable(node) - TODO(yosin): Should not use
IsEmptyNonEditableNodeInEditable()since it requires clean layout
- Args: A node
- Returns: True if no children or content ignored for editing
- Does:
!node->hasChildren() || EditingIgnoresContent(*node)
- Args: A node
- Returns: Same as IsAtomicNode but uses
FlatTreeTraversal::HasChildren()
- Args: A node
- Returns: True if node is content-editable
- Does: Checks
ComputedStyle::UsedUserModify()up the ancestor chain - TODO: crbug.com/667681 — shouldn't check in inactive documents
- Args: A node
- Returns: True if node is richly editable (not plaintext-only)
- Does: Same as IsEditable but excludes
read-write-plaintext-only
- Args: A position
- Returns: True if position is editable
- Does: Gets container node, adjusts for tables, calls
IsEditable()
- Args: A position
- Returns: True if position is richly editable
- Does: Same as IsEditablePosition but calls
IsRichlyEditable()
- Args: A node
- Returns: Highest editable ancestor Element, stopping at body
- Does: Walks ancestors while editable, tracks last Element
- Args: A position
- Returns: Root editable element for the position
- Does: Gets container, adjusts for tables, calls
RootEditableElement()
- Args: A position
- Returns: Highest editable root (may be Document for designMode)
- Does: Searches ancestors for highest editable, ignoring editing boundaries
- Args: A node
- Returns: True if node is a root editable element
- Does: Is editable, is Element, parent not editable or is body
- Args: A node
- Returns: True if empty, non-editable, within editable parent
- Does: No children + !IsEditable + parent IsEditable
- Bug: crbug.com/428986
- Args: A position
- Returns: Next visually equivalent candidate
- Does: Iterates forward with PositionIterator, returns first
IsVisuallyEquivalentCandidate
- Args: A position
- Returns: Previous visually equivalent candidate
- Does: Iterates backward
- Args: Position + boundary rule
- Returns: Next candidate whose forward/backward caret positions differ from the start's
- Does: Skips candidates that are visually equivalent to the starting position
- Flag:
SkipNonEditableInAtomicMoveEnabled— can skip non-editable regions
- Args: Position + boundary rule
- Returns: Mirror of NextVisuallyDistinctCandidate
- Args: Position + highest root node
- Returns: First editable position after the given position within root
- Does: Handles shadow trees, uses NextCandidate/NextVisuallyDistinctCandidate
- Bugs: crbug.com/571420, crbug.com/1334557, crbug.com/1406207
- Args: Position + highest root node
- Returns: Last editable position before the given position within root
- Does: Mirror of First...
- Args: Position + move type (CodeUnit/BackwardDeletion/GraphemeCluster)
- Returns: Previous position
- Does: Handles EditingIgnoresContent, text decrements, parent walking
- Args: Position + move type
- Returns: Next position
- Does: Mirror of PreviousPositionOf
- Args: A node
- Returns: Enclosing block flow element (deprecated — use EnclosingBlock)
- Does: Walks ancestors looking for IsBlockFlowElement or body
- Args: Node + boundary rule
- Returns: Enclosing block element
- Does: Uses
EnclosingNodeOfTypewithIsEnclosingBlock
- Args: A node
- Returns: True if node is an
<table>with layout object
- Args: A node
- Returns: True if layout object is table cell
- Args: A node
- Returns: True if not text and can contain range endpoints
- Args: A node
- Returns: True for textarea, input, select
- TODO: Should check CSS user-select property
- Args: Node + offset + side (for first-letter boundary)
- Returns: The layout object associated with the position
- Does: Handles first-letter pseudo-element boundaries
- Args: A position
- Returns: Adjusted position that respects editing boundaries
- Does: Moves positions out of non-editable into adjacent editable
- Args: Selection start + hit test result
- Returns: Position adjusted for editing boundary and user-select:contain
- Bugs: crbug.com/185089, crbug.com/658129
- Args: A position + node being removed
- Returns: Adjusted position after the node removal
- Does: Handles all PositionAnchorType variants
- Args: Position or Node
- Returns: Whether layout tree needs update
- Does: Checks document needsLayoutTreeUpdate and view needsLayout
- TODO(yosin): document::needsLayoutTreeUpdate should check LayoutView::needsLayout
- Args: A DOM position
- Returns: The NG inline formatting context (LayoutBlockFlow) containing the position
- Does: Looks up the offset mapping for the position
- Args: A flat tree position
- Returns: Same, converts to DOM tree first
- Args: Two flat tree positions with affinity
- Returns: Whether they're in the same NG line box
- Does: Converts to DOM tree, calls DOM version
- Fields:
layout_object(LayoutObject*),rect(PhysicalRect),root_box_fragment - Does: Represents a caret rectangle in local coordinates
LocalCaretRectOfPosition(const PositionWithAffinity&, CaretShape, EditingBoundaryCrossingRule) → LocalCaretRect
- Args: Position with affinity + caret shape + boundary rule
- Returns: Local-coordinates caret rect
AbsoluteCaretBoundsOf(const PositionWithAffinity&, CaretShape, EditingBoundaryCrossingRule) → gfx::Rect
- Args: Same
- Returns: Absolute-coordinates bounding rect
- Does: Gets LocalCaretRect, converts via LocalToAbsoluteQuadOf
- Args: A local caret rect
- Returns: Absolute quad
- Does: Converts via layout_object's local-to-absolute mapping
- Args:
SelectionTemplate,TextGranularity,WordInclusion - Returns: Adjusted
SelectionTemplate - Does: Expands to word/sentence/line/paragraph/document boundaries
- Args:
SelectionTemplate - Returns: Adjusted
SelectionTemplate - Does: Prevents selection spanning shadow DOM boundaries
- Args:
SelectionTemplate - Returns: Adjusted
SelectionTemplate - Does: Prevents selection spanning editable/non-editable boundaries
- Args:
SelectionTemplate - Returns: Type-adjusted
SelectionTemplate - Does: If positions equal → caret; otherwise → range
| Value | Description |
|---|---|
kCanCrossEditingBoundary |
Allow crossing between editable and non-editable |
kCannotCrossEditingBoundary |
Stop at editing boundary |
kCanSkipOverEditingBoundary |
Skip over non-editable regions entirely |
| Value | Description |
|---|---|
kDefault |
Same as kDownstream |
kDownstream |
Caret at start of next line (after wrap) |
kUpstream |
Caret at end of current line (before wrap) |
- Tree traversal for DOM tree. Uses
NodeTraversalfor parent/child/sibling operations.
- Tree traversal for flat tree. Uses
FlatTreeTraversalfor parent/child/sibling operations, resolving shadow DOM.
- Does: Iterates through all positions in document order
- Key methods:
Increment(),Decrement(),GetNode(),OffsetInTextNode(),AtStart(),AtEnd(),AtStartOfNode(),AtEndOfNode(),ComputePosition(),DeprecatedComputePosition() - Used by
MostBackwardCaretPositionandMostForwardCaretPositionfor iteration
- Args: A selection
- Returns: Canonicalized selection with visible positions
- Does: Calls
CreateVisiblePosition()on anchor and focus, builds new selection from theirDeepEquivalent()positions
- Args:
SelectionTemplate,TextGranularity,WordInclusion - Returns: Adjusted
SelectionTemplate - Does: The 5-step pipeline: canonicalize → granularity → shadow → editing → type
- Args: A selection
- Returns:
EphemeralRange - Does: For carets:
MostBackwardCaretPosition(start).ParentAnchoredEquivalent(). For ranges: normalized min range.