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 COMPUTER, 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 COMPUTER, 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 #ifndef Position_h 27 #define Position_h 28 29 #include "core/dom/ContainerNode.h" 30 #include "core/editing/EditingBoundary.h" 31 #include "core/editing/TextAffinity.h" 32 #include "platform/text/TextDirection.h" 33 #include "wtf/Assertions.h" 34 #include "wtf/PassRefPtr.h" 35 #include "wtf/RefPtr.h" 36 37 namespace WebCore { 38 39 class CSSComputedStyleDeclaration; 40 class Element; 41 class InlineBox; 42 class Node; 43 class Range; 44 class RenderObject; 45 class Text; 46 47 enum PositionMoveType { 48 CodePoint, // Move by a single code point. 49 Character, // Move to the next Unicode character break. 50 BackwardDeletion // Subject to platform conventions. 51 }; 52 53 class Position { 54 public: 55 enum AnchorType { 56 PositionIsOffsetInAnchor, 57 PositionIsBeforeAnchor, 58 PositionIsAfterAnchor, 59 PositionIsBeforeChildren, 60 PositionIsAfterChildren, 61 }; 62 63 Position() 64 : m_offset(0) 65 , m_anchorType(PositionIsOffsetInAnchor) 66 , m_isLegacyEditingPosition(false) 67 { 68 } 69 70 // For creating legacy editing positions: (Anchor type will be determined from editingIgnoresContent(node)) 71 class LegacyEditingOffset { 72 public: 73 int value() const { return m_offset; } 74 75 private: 76 explicit LegacyEditingOffset(int offset) : m_offset(offset) { } 77 78 friend Position createLegacyEditingPosition(PassRefPtr<Node>, int offset); 79 80 int m_offset; 81 }; 82 Position(PassRefPtr<Node> anchorNode, LegacyEditingOffset); 83 84 // For creating before/after positions: 85 Position(PassRefPtr<Node> anchorNode, AnchorType); 86 Position(PassRefPtr<Text> textNode, unsigned offset); 87 88 // For creating offset positions: 89 // FIXME: This constructor should eventually go away. See bug 63040. 90 Position(PassRefPtr<Node> anchorNode, int offset, AnchorType); 91 92 AnchorType anchorType() const { return static_cast<AnchorType>(m_anchorType); } 93 94 void clear() { m_anchorNode.clear(); m_offset = 0; m_anchorType = PositionIsOffsetInAnchor; m_isLegacyEditingPosition = false; } 95 96 // These are always DOM compliant values. Editing positions like [img, 0] (aka [img, before]) 97 // will return img->parentNode() and img->nodeIndex() from these functions. 98 Node* containerNode() const; // NULL for a before/after position anchored to a node with no parent 99 Text* containerText() const; 100 101 int computeOffsetInContainerNode() const; // O(n) for before/after-anchored positions, O(1) for parent-anchored positions 102 Position parentAnchoredEquivalent() const; // Convenience method for DOM positions that also fixes up some positions for editing 103 104 // Inline O(1) access for Positions which callers know to be parent-anchored 105 int offsetInContainerNode() const 106 { 107 ASSERT(anchorType() == PositionIsOffsetInAnchor); 108 return m_offset; 109 } 110 111 // New code should not use this function. 112 int deprecatedEditingOffset() const 113 { 114 if (m_isLegacyEditingPosition || (m_anchorType != PositionIsAfterAnchor && m_anchorType != PositionIsAfterChildren)) 115 return m_offset; 116 return offsetForPositionAfterAnchor(); 117 } 118 119 // These are convenience methods which are smart about whether the position is neighbor anchored or parent anchored 120 Node* computeNodeBeforePosition() const; 121 Node* computeNodeAfterPosition() const; 122 123 Node* anchorNode() const { return m_anchorNode.get(); } 124 125 // FIXME: Callers should be moved off of node(), node() is not always the container for this position. 126 // For nodes which editingIgnoresContent(node()) returns true, positions like [ignoredNode, 0] 127 // will be treated as before ignoredNode (thus node() is really after the position, not containing it). 128 Node* deprecatedNode() const { return m_anchorNode.get(); } 129 130 Document* document() const { return m_anchorNode ? &m_anchorNode->document() : 0; } 131 bool inDocument() const { return m_anchorNode && m_anchorNode->inDocument(); } 132 Element* rootEditableElement() const 133 { 134 Node* container = containerNode(); 135 return container ? container->rootEditableElement() : 0; 136 } 137 138 // These should only be used for PositionIsOffsetInAnchor positions, unless 139 // the position is a legacy editing position. 140 void moveToPosition(PassRefPtr<Node> anchorNode, int offset); 141 void moveToOffset(int offset); 142 143 bool isNull() const { return !m_anchorNode; } 144 bool isNotNull() const { return m_anchorNode; } 145 bool isOrphan() const { return m_anchorNode && !m_anchorNode->inDocument(); } 146 147 Element* element() const; 148 PassRefPtr<CSSComputedStyleDeclaration> computedStyle() const; 149 150 // Move up or down the DOM by one position. 151 // Offsets are computed using render text for nodes that have renderers - but note that even when 152 // using composed characters, the result may be inside a single user-visible character if a ligature is formed. 153 Position previous(PositionMoveType = CodePoint) const; 154 Position next(PositionMoveType = CodePoint) const; 155 static int uncheckedPreviousOffset(const Node*, int current); 156 static int uncheckedPreviousOffsetForBackwardDeletion(const Node*, int current); 157 static int uncheckedNextOffset(const Node*, int current); 158 159 // These can be either inside or just before/after the node, depending on 160 // if the node is ignored by editing or not. 161 // FIXME: These should go away. They only make sense for legacy positions. 162 bool atFirstEditingPositionForNode() const; 163 bool atLastEditingPositionForNode() const; 164 165 // Returns true if the visually equivalent positions around have different editability 166 bool atEditingBoundary() const; 167 Node* parentEditingBoundary() const; 168 169 bool atStartOfTree() const; 170 bool atEndOfTree() const; 171 172 // FIXME: Make these non-member functions and put them somewhere in the editing directory. 173 // These aren't really basic "position" operations. More high level editing helper functions. 174 Position leadingWhitespacePosition(EAffinity, bool considerNonCollapsibleWhitespace = false) const; 175 Position trailingWhitespacePosition(EAffinity, bool considerNonCollapsibleWhitespace = false) const; 176 177 // These return useful visually equivalent positions. 178 Position upstream(EditingBoundaryCrossingRule = CannotCrossEditingBoundary) const; 179 Position downstream(EditingBoundaryCrossingRule = CannotCrossEditingBoundary) const; 180 181 bool isCandidate() const; 182 bool inRenderedText() const; 183 bool isRenderedCharacter() const; 184 bool rendersInDifferentPosition(const Position&) const; 185 186 void getInlineBoxAndOffset(EAffinity, InlineBox*&, int& caretOffset) const; 187 void getInlineBoxAndOffset(EAffinity, TextDirection primaryDirection, InlineBox*&, int& caretOffset) const; 188 189 TextDirection primaryDirection() const; 190 191 static bool hasRenderedNonAnonymousDescendantsWithHeight(RenderObject*); 192 static bool nodeIsUserSelectNone(Node*); 193 static bool nodeIsUserSelectAll(const Node*); 194 static Node* rootUserSelectAllForNode(Node*); 195 196 static ContainerNode* findParent(const Node*); 197 198 void debugPosition(const char* msg = "") const; 199 200 #ifndef NDEBUG 201 void formatForDebugger(char* buffer, unsigned length) const; 202 void showAnchorTypeAndOffset() const; 203 void showTreeForThis() const; 204 #endif 205 206 private: 207 int offsetForPositionAfterAnchor() const; 208 209 int renderedOffset() const; 210 211 212 Position previousCharacterPosition(EAffinity) const; 213 Position nextCharacterPosition(EAffinity) const; 214 215 static AnchorType anchorTypeForLegacyEditingPosition(Node* anchorNode, int offset); 216 217 RefPtr<Node> m_anchorNode; 218 // m_offset can be the offset inside m_anchorNode, or if editingIgnoresContent(m_anchorNode) 219 // returns true, then other places in editing will treat m_offset == 0 as "before the anchor" 220 // and m_offset > 0 as "after the anchor node". See parentAnchoredEquivalent for more info. 221 int m_offset; 222 unsigned m_anchorType : 3; 223 bool m_isLegacyEditingPosition : 1; 224 }; 225 226 inline Position createLegacyEditingPosition(PassRefPtr<Node> node, int offset) 227 { 228 return Position(node, Position::LegacyEditingOffset(offset)); 229 } 230 231 inline bool operator==(const Position& a, const Position& b) 232 { 233 // FIXME: In <div><img></div> [div, 0] != [img, 0] even though most of the 234 // editing code will treat them as identical. 235 return a.anchorNode() == b.anchorNode() && a.deprecatedEditingOffset() == b.deprecatedEditingOffset() && a.anchorType() == b.anchorType(); 236 } 237 238 inline bool operator!=(const Position& a, const Position& b) 239 { 240 return !(a == b); 241 } 242 243 // We define position creation functions to make callsites more readable. 244 // These are inline to prevent ref-churn when returning a Position object. 245 // If we ever add a PassPosition we can make these non-inline. 246 247 inline Position positionInParentBeforeNode(const Node* node) 248 { 249 // FIXME: This should ASSERT(node->parentNode()) 250 // At least one caller currently hits this ASSERT though, which indicates 251 // that the caller is trying to make a position relative to a disconnected node (which is likely an error) 252 // Specifically, editing/deleting/delete-ligature-001.html crashes with ASSERT(node->parentNode()) 253 return Position(node->parentNode(), node->nodeIndex(), Position::PositionIsOffsetInAnchor); 254 } 255 256 inline Position positionInParentAfterNode(const Node* node) 257 { 258 ASSERT(node->parentNode()); 259 return Position(node->parentNode(), node->nodeIndex() + 1, Position::PositionIsOffsetInAnchor); 260 } 261 262 // positionBeforeNode and positionAfterNode return neighbor-anchored positions, construction is O(1) 263 inline Position positionBeforeNode(Node* anchorNode) 264 { 265 ASSERT(anchorNode); 266 return Position(anchorNode, Position::PositionIsBeforeAnchor); 267 } 268 269 inline Position positionAfterNode(Node* anchorNode) 270 { 271 ASSERT(anchorNode); 272 return Position(anchorNode, Position::PositionIsAfterAnchor); 273 } 274 275 inline int lastOffsetInNode(Node* node) 276 { 277 return node->offsetInCharacters() ? node->maxCharacterOffset() : static_cast<int>(node->childNodeCount()); 278 } 279 280 // firstPositionInNode and lastPositionInNode return parent-anchored positions, lastPositionInNode construction is O(n) due to childNodeCount() 281 inline Position firstPositionInNode(Node* anchorNode) 282 { 283 if (anchorNode->isTextNode()) 284 return Position(anchorNode, 0, Position::PositionIsOffsetInAnchor); 285 return Position(anchorNode, Position::PositionIsBeforeChildren); 286 } 287 288 inline Position lastPositionInNode(Node* anchorNode) 289 { 290 if (anchorNode->isTextNode()) 291 return Position(anchorNode, lastOffsetInNode(anchorNode), Position::PositionIsOffsetInAnchor); 292 return Position(anchorNode, Position::PositionIsAfterChildren); 293 } 294 295 inline int minOffsetForNode(Node* anchorNode, int offset) 296 { 297 if (anchorNode->offsetInCharacters()) 298 return std::min(offset, anchorNode->maxCharacterOffset()); 299 300 int newOffset = 0; 301 for (Node* node = anchorNode->firstChild(); node && newOffset < offset; node = node->nextSibling()) 302 newOffset++; 303 304 return newOffset; 305 } 306 307 inline bool offsetIsBeforeLastNodeOffset(int offset, Node* anchorNode) 308 { 309 if (anchorNode->offsetInCharacters()) 310 return offset < anchorNode->maxCharacterOffset(); 311 312 int currentOffset = 0; 313 for (Node* node = anchorNode->firstChild(); node && currentOffset < offset; node = node->nextSibling()) 314 currentOffset++; 315 316 317 return offset < currentOffset; 318 } 319 320 class PositionWithAffinity { 321 public: 322 PositionWithAffinity() 323 : m_affinity(DOWNSTREAM) 324 { 325 } 326 327 PositionWithAffinity(const Position& position, EAffinity affinity = DOWNSTREAM) 328 : m_position(position) 329 , m_affinity(affinity) 330 { 331 } 332 333 EAffinity affinity() const { return m_affinity; } 334 const Position& position() const { return m_position; } 335 336 private: 337 Position m_position; 338 EAffinity m_affinity; 339 }; 340 341 } // namespace WebCore 342 343 #ifndef NDEBUG 344 // Outside the WebCore namespace for ease of invocation from gdb. 345 void showTree(const WebCore::Position&); 346 void showTree(const WebCore::Position*); 347 #endif 348 349 #endif // Position_h 350