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 | |