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
34namespace WTF {
35class TextStream;
36}
37
38namespace WebCore {
39
40class CSSComputedStyleDeclaration;
41class Element;
42class InlineBox;
43class Node;
44class Range;
45class RenderElement;
46class RenderObject;
47class Text;
48
49enum 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
55class Position {
56public:
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
203private:
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
225inline Position createLegacyEditingPosition(Node* node, unsigned offset)
226{
227 return { node, offset, Position::LegacyEditingPositionFlag::On };
228}
229
230inline 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
237inline bool operator!=(const Position& a, const Position& b)
238{
239 return !(a == b);
240}
241
242inline 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
251inline bool operator>(const Position& a, const Position& b)
252{
253 return !a.isNull() && !b.isNull() && a != b && b < a;
254}
255
256inline bool operator>=(const Position& a, const Position& b)
257{
258 return !a.isNull() && !b.isNull() && (a == b || a > b);
259}
260
261inline bool operator<=(const Position& a, const Position& b)
262{
263 return !a.isNull() && !b.isNull() && (a == b || a < b);
264}
265
266inline Position positionInParentBeforeNode(const Node* node)
267{
268 ASSERT(node->parentNode());
269 return Position(node->parentNode(), node->computeNodeIndex(), Position::PositionIsOffsetInAnchor);
270}
271
272inline 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)
279inline Position positionBeforeNode(Node* anchorNode)
280{
281 ASSERT(anchorNode);
282 return Position(anchorNode, Position::PositionIsBeforeAnchor);
283}
284
285inline Position positionAfterNode(Node* anchorNode)
286{
287 ASSERT(anchorNode);
288 return Position(anchorNode, Position::PositionIsAfterAnchor);
289}
290
291inline 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()
297inline Position firstPositionInNode(Node* anchorNode)
298{
299 if (anchorNode->isTextNode())
300 return Position(anchorNode, 0, Position::PositionIsOffsetInAnchor);
301 return Position(anchorNode, Position::PositionIsBeforeChildren);
302}
303
304inline 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
311inline 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
323inline 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
336RefPtr<Node> commonShadowIncludingAncestor(const Position&, const Position&);
337
338WTF::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.
344void showTree(const WebCore::Position&);
345void showTree(const WebCore::Position*);
346#endif
347