| 1 | /* |
| 2 | * Copyright (C) 2004, 2006, 2008 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| 14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #pragma once |
| 27 | |
| 28 | #include "ContainerNode.h" |
| 29 | #include "EditingBoundary.h" |
| 30 | #include "TextAffinity.h" |
| 31 | #include <wtf/Assertions.h> |
| 32 | #include <wtf/RefPtr.h> |
| 33 | |
| 34 | namespace WTF { |
| 35 | class TextStream; |
| 36 | } |
| 37 | |
| 38 | namespace WebCore { |
| 39 | |
| 40 | class CSSComputedStyleDeclaration; |
| 41 | class Element; |
| 42 | class InlineBox; |
| 43 | class Node; |
| 44 | class Range; |
| 45 | class RenderElement; |
| 46 | class RenderObject; |
| 47 | class Text; |
| 48 | |
| 49 | enum PositionMoveType { |
| 50 | CodePoint, // Move by a single code point. |
| 51 | Character, // Move to the next Unicode character break. |
| 52 | BackwardDeletion // Subject to platform conventions. |
| 53 | }; |
| 54 | |
| 55 | class Position { |
| 56 | public: |
| 57 | enum AnchorType { |
| 58 | PositionIsOffsetInAnchor, |
| 59 | PositionIsBeforeAnchor, |
| 60 | PositionIsAfterAnchor, |
| 61 | PositionIsBeforeChildren, |
| 62 | PositionIsAfterChildren, |
| 63 | }; |
| 64 | |
| 65 | Position() |
| 66 | : m_anchorType(PositionIsOffsetInAnchor) |
| 67 | , m_isLegacyEditingPosition(false) |
| 68 | { |
| 69 | } |
| 70 | |
| 71 | // For creating before/after positions: |
| 72 | WEBCORE_EXPORT Position(Node* anchorNode, AnchorType); |
| 73 | Position(Text* textNode, unsigned offset); |
| 74 | |
| 75 | // For creating offset positions: |
| 76 | // FIXME: This constructor should eventually go away. See bug 63040. |
| 77 | WEBCORE_EXPORT Position(Node* anchorNode, int offset, AnchorType); |
| 78 | |
| 79 | AnchorType anchorType() const { return static_cast<AnchorType>(m_anchorType); } |
| 80 | |
| 81 | void clear() { m_anchorNode = nullptr; m_offset = 0; m_anchorType = PositionIsOffsetInAnchor; m_isLegacyEditingPosition = false; } |
| 82 | |
| 83 | // These are always DOM compliant values. Editing positions like [img, 0] (aka [img, before]) |
| 84 | // will return img->parentNode() and img->computeNodeIndex() from these functions. |
| 85 | WEBCORE_EXPORT Node* containerNode() const; // null for a before/after position anchored to a node with no parent |
| 86 | Text* containerText() const; |
| 87 | |
| 88 | int computeOffsetInContainerNode() const; // O(n) for before/after-anchored positions, O(1) for parent-anchored positions |
| 89 | WEBCORE_EXPORT Position parentAnchoredEquivalent() const; // Convenience method for DOM positions that also fixes up some positions for editing |
| 90 | |
| 91 | // Inline O(1) access for Positions which callers know to be parent-anchored |
| 92 | int offsetInContainerNode() const |
| 93 | { |
| 94 | ASSERT(anchorType() == PositionIsOffsetInAnchor); |
| 95 | return m_offset; |
| 96 | } |
| 97 | |
| 98 | // New code should not use this function. |
| 99 | int deprecatedEditingOffset() const |
| 100 | { |
| 101 | if (m_isLegacyEditingPosition || (m_anchorType != PositionIsAfterAnchor && m_anchorType != PositionIsAfterChildren)) |
| 102 | return m_offset; |
| 103 | return offsetForPositionAfterAnchor(); |
| 104 | } |
| 105 | |
| 106 | RefPtr<Node> firstNode() const; |
| 107 | |
| 108 | // These are convenience methods which are smart about whether the position is neighbor anchored or parent anchored |
| 109 | Node* computeNodeBeforePosition() const; |
| 110 | Node* computeNodeAfterPosition() const; |
| 111 | |
| 112 | Node* anchorNode() const { return m_anchorNode.get(); } |
| 113 | |
| 114 | // FIXME: Callers should be moved off of node(), node() is not always the container for this position. |
| 115 | // For nodes which editingIgnoresContent(node()) returns true, positions like [ignoredNode, 0] |
| 116 | // will be treated as before ignoredNode (thus node() is really after the position, not containing it). |
| 117 | Node* deprecatedNode() const { return m_anchorNode.get(); } |
| 118 | |
| 119 | Document* document() const { return m_anchorNode ? &m_anchorNode->document() : nullptr; } |
| 120 | TreeScope* treeScope() const { return m_anchorNode ? &m_anchorNode->treeScope() : nullptr; } |
| 121 | Element* rootEditableElement() const |
| 122 | { |
| 123 | Node* container = containerNode(); |
| 124 | return container ? container->rootEditableElement() : nullptr; |
| 125 | } |
| 126 | |
| 127 | // These should only be used for PositionIsOffsetInAnchor positions, unless |
| 128 | // the position is a legacy editing position. |
| 129 | void moveToPosition(Node* anchorNode, int offset); |
| 130 | void moveToOffset(int offset); |
| 131 | |
| 132 | bool isNull() const { return !m_anchorNode; } |
| 133 | bool isNotNull() const { return m_anchorNode; } |
| 134 | bool isOrphan() const { return m_anchorNode && !m_anchorNode->isConnected(); } |
| 135 | |
| 136 | Element* element() const; |
| 137 | |
| 138 | // Move up or down the DOM by one position. |
| 139 | // Offsets are computed using render text for nodes that have renderers - but note that even when |
| 140 | // using composed characters, the result may be inside a single user-visible character if a ligature is formed. |
| 141 | WEBCORE_EXPORT Position previous(PositionMoveType = CodePoint) const; |
| 142 | WEBCORE_EXPORT Position next(PositionMoveType = CodePoint) const; |
| 143 | static int uncheckedPreviousOffset(const Node*, int current); |
| 144 | static int uncheckedPreviousOffsetForBackwardDeletion(const Node*, int current); |
| 145 | static int uncheckedNextOffset(const Node*, int current); |
| 146 | |
| 147 | // These can be either inside or just before/after the node, depending on |
| 148 | // if the node is ignored by editing or not. |
| 149 | // FIXME: These should go away. They only make sense for legacy positions. |
| 150 | bool atFirstEditingPositionForNode() const; |
| 151 | bool atLastEditingPositionForNode() const; |
| 152 | |
| 153 | // Returns true if the visually equivalent positions around have different editability |
| 154 | bool atEditingBoundary() const; |
| 155 | Node* parentEditingBoundary() const; |
| 156 | |
| 157 | bool atStartOfTree() const; |
| 158 | bool atEndOfTree() const; |
| 159 | |
| 160 | // FIXME: Make these non-member functions and put them somewhere in the editing directory. |
| 161 | // These aren't really basic "position" operations. More high level editing helper functions. |
| 162 | WEBCORE_EXPORT Position leadingWhitespacePosition(EAffinity, bool considerNonCollapsibleWhitespace = false) const; |
| 163 | WEBCORE_EXPORT Position trailingWhitespacePosition(EAffinity, bool considerNonCollapsibleWhitespace = false) const; |
| 164 | |
| 165 | // These return useful visually equivalent positions. |
| 166 | WEBCORE_EXPORT Position upstream(EditingBoundaryCrossingRule = CannotCrossEditingBoundary) const; |
| 167 | WEBCORE_EXPORT Position downstream(EditingBoundaryCrossingRule = CannotCrossEditingBoundary) const; |
| 168 | |
| 169 | bool isCandidate() const; |
| 170 | bool isRenderedCharacter() const; |
| 171 | bool rendersInDifferentPosition(const Position&) const; |
| 172 | |
| 173 | void getInlineBoxAndOffset(EAffinity, InlineBox*&, int& caretOffset) const; |
| 174 | void getInlineBoxAndOffset(EAffinity, TextDirection primaryDirection, InlineBox*&, int& caretOffset) const; |
| 175 | |
| 176 | TextDirection primaryDirection() const; |
| 177 | |
| 178 | // Returns the number of positions that exist between two positions. |
| 179 | static unsigned positionCountBetweenPositions(const Position&, const Position&); |
| 180 | |
| 181 | static bool hasRenderedNonAnonymousDescendantsWithHeight(const RenderElement&); |
| 182 | static bool nodeIsUserSelectNone(Node*); |
| 183 | #if ENABLE(USERSELECT_ALL) |
| 184 | static bool nodeIsUserSelectAll(const Node*); |
| 185 | static Node* rootUserSelectAllForNode(Node*); |
| 186 | #else |
| 187 | static bool nodeIsUserSelectAll(const Node*) { return false; } |
| 188 | static Node* rootUserSelectAllForNode(Node*) { return 0; } |
| 189 | #endif |
| 190 | |
| 191 | void debugPosition(const char* msg = "" ) const; |
| 192 | |
| 193 | #if ENABLE(TREE_DEBUGGING) |
| 194 | void formatForDebugger(char* buffer, unsigned length) const; |
| 195 | void showAnchorTypeAndOffset() const; |
| 196 | void showTreeForThis() const; |
| 197 | #endif |
| 198 | |
| 199 | // This is a tentative enhancement of operator== to account for different position types. |
| 200 | // FIXME: Combine this function with operator== |
| 201 | bool equals(const Position&) const; |
| 202 | |
| 203 | private: |
| 204 | // For creating legacy editing positions: (Anchor type will be determined from editingIgnoresContent(node)) |
| 205 | enum class LegacyEditingPositionFlag { On }; |
| 206 | WEBCORE_EXPORT Position(Node* anchorNode, unsigned offset, LegacyEditingPositionFlag); |
| 207 | friend Position createLegacyEditingPosition(Node*, unsigned offset); |
| 208 | |
| 209 | WEBCORE_EXPORT int offsetForPositionAfterAnchor() const; |
| 210 | |
| 211 | Position previousCharacterPosition(EAffinity) const; |
| 212 | Position nextCharacterPosition(EAffinity) const; |
| 213 | |
| 214 | static AnchorType anchorTypeForLegacyEditingPosition(Node* anchorNode, int offset); |
| 215 | |
| 216 | RefPtr<Node> m_anchorNode; |
| 217 | // m_offset can be the offset inside m_anchorNode, or if editingIgnoresContent(m_anchorNode) |
| 218 | // returns true, then other places in editing will treat m_offset == 0 as "before the anchor" |
| 219 | // and m_offset > 0 as "after the anchor node". See parentAnchoredEquivalent for more info. |
| 220 | int m_offset { 0 }; |
| 221 | unsigned m_anchorType : 3; |
| 222 | bool m_isLegacyEditingPosition : 1; |
| 223 | }; |
| 224 | |
| 225 | inline Position createLegacyEditingPosition(Node* node, unsigned offset) |
| 226 | { |
| 227 | return { node, offset, Position::LegacyEditingPositionFlag::On }; |
| 228 | } |
| 229 | |
| 230 | inline bool operator==(const Position& a, const Position& b) |
| 231 | { |
| 232 | // FIXME: In <div><img></div> [div, 0] != [img, 0] even though most of the |
| 233 | // editing code will treat them as identical. |
| 234 | return a.anchorNode() == b.anchorNode() && a.deprecatedEditingOffset() == b.deprecatedEditingOffset() && a.anchorType() == b.anchorType(); |
| 235 | } |
| 236 | |
| 237 | inline bool operator!=(const Position& a, const Position& b) |
| 238 | { |
| 239 | return !(a == b); |
| 240 | } |
| 241 | |
| 242 | inline bool operator<(const Position& a, const Position& b) |
| 243 | { |
| 244 | if (a.isNull() || b.isNull()) |
| 245 | return false; |
| 246 | if (a.anchorNode() == b.anchorNode()) |
| 247 | return a.deprecatedEditingOffset() < b.deprecatedEditingOffset(); |
| 248 | return b.anchorNode()->compareDocumentPosition(*a.anchorNode()) == Node::DOCUMENT_POSITION_PRECEDING; |
| 249 | } |
| 250 | |
| 251 | inline bool operator>(const Position& a, const Position& b) |
| 252 | { |
| 253 | return !a.isNull() && !b.isNull() && a != b && b < a; |
| 254 | } |
| 255 | |
| 256 | inline bool operator>=(const Position& a, const Position& b) |
| 257 | { |
| 258 | return !a.isNull() && !b.isNull() && (a == b || a > b); |
| 259 | } |
| 260 | |
| 261 | inline bool operator<=(const Position& a, const Position& b) |
| 262 | { |
| 263 | return !a.isNull() && !b.isNull() && (a == b || a < b); |
| 264 | } |
| 265 | |
| 266 | inline Position positionInParentBeforeNode(const Node* node) |
| 267 | { |
| 268 | ASSERT(node->parentNode()); |
| 269 | return Position(node->parentNode(), node->computeNodeIndex(), Position::PositionIsOffsetInAnchor); |
| 270 | } |
| 271 | |
| 272 | inline Position positionInParentAfterNode(const Node* node) |
| 273 | { |
| 274 | ASSERT(node->parentNode()); |
| 275 | return Position(node->parentNode(), node->computeNodeIndex() + 1, Position::PositionIsOffsetInAnchor); |
| 276 | } |
| 277 | |
| 278 | // positionBeforeNode and positionAfterNode return neighbor-anchored positions, construction is O(1) |
| 279 | inline Position positionBeforeNode(Node* anchorNode) |
| 280 | { |
| 281 | ASSERT(anchorNode); |
| 282 | return Position(anchorNode, Position::PositionIsBeforeAnchor); |
| 283 | } |
| 284 | |
| 285 | inline Position positionAfterNode(Node* anchorNode) |
| 286 | { |
| 287 | ASSERT(anchorNode); |
| 288 | return Position(anchorNode, Position::PositionIsAfterAnchor); |
| 289 | } |
| 290 | |
| 291 | inline int lastOffsetInNode(Node* node) |
| 292 | { |
| 293 | return node->isCharacterDataNode() ? node->maxCharacterOffset() : static_cast<int>(node->countChildNodes()); |
| 294 | } |
| 295 | |
| 296 | // firstPositionInNode and lastPositionInNode return parent-anchored positions, lastPositionInNode construction is O(n) due to countChildNodes() |
| 297 | inline Position firstPositionInNode(Node* anchorNode) |
| 298 | { |
| 299 | if (anchorNode->isTextNode()) |
| 300 | return Position(anchorNode, 0, Position::PositionIsOffsetInAnchor); |
| 301 | return Position(anchorNode, Position::PositionIsBeforeChildren); |
| 302 | } |
| 303 | |
| 304 | inline Position lastPositionInNode(Node* anchorNode) |
| 305 | { |
| 306 | if (anchorNode->isTextNode()) |
| 307 | return Position(anchorNode, lastOffsetInNode(anchorNode), Position::PositionIsOffsetInAnchor); |
| 308 | return Position(anchorNode, Position::PositionIsAfterChildren); |
| 309 | } |
| 310 | |
| 311 | inline int minOffsetForNode(Node* anchorNode, int offset) |
| 312 | { |
| 313 | if (anchorNode->isCharacterDataNode()) |
| 314 | return std::min(offset, anchorNode->maxCharacterOffset()); |
| 315 | |
| 316 | int newOffset = 0; |
| 317 | for (Node* node = anchorNode->firstChild(); node && newOffset < offset; node = node->nextSibling()) |
| 318 | newOffset++; |
| 319 | |
| 320 | return newOffset; |
| 321 | } |
| 322 | |
| 323 | inline bool offsetIsBeforeLastNodeOffset(int offset, Node* anchorNode) |
| 324 | { |
| 325 | if (anchorNode->isCharacterDataNode()) |
| 326 | return offset < anchorNode->maxCharacterOffset(); |
| 327 | |
| 328 | int currentOffset = 0; |
| 329 | for (Node* node = anchorNode->firstChild(); node && currentOffset < offset; node = node->nextSibling()) |
| 330 | currentOffset++; |
| 331 | |
| 332 | |
| 333 | return offset < currentOffset; |
| 334 | } |
| 335 | |
| 336 | RefPtr<Node> commonShadowIncludingAncestor(const Position&, const Position&); |
| 337 | |
| 338 | WTF::TextStream& operator<<(WTF::TextStream&, const Position&); |
| 339 | |
| 340 | } // namespace WebCore |
| 341 | |
| 342 | #if ENABLE(TREE_DEBUGGING) |
| 343 | // Outside the WebCore namespace for ease of invocation from the debugger. |
| 344 | void showTree(const WebCore::Position&); |
| 345 | void showTree(const WebCore::Position*); |
| 346 | #endif |
| 347 | |