OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 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 #include "core/editing/SelectionAdjuster.h" | |
27 | |
28 #include "core/editing/EditingUtilities.h" | |
29 | |
30 namespace blink { | |
31 | |
32 namespace { | |
33 | |
34 Node* enclosingShadowHost(Node* node) | |
35 { | |
36 for (Node* runner = node; runner; runner = ComposedTreeTraversal::parent(*ru
nner)) { | |
37 if (isShadowHost(runner)) | |
38 return runner; | |
39 } | |
40 return nullptr; | |
41 } | |
42 | |
43 bool isEnclosedBy(const PositionInComposedTree& position, const Node& node) | |
44 { | |
45 ASSERT(position.isNotNull()); | |
46 Node* anchorNode = position.anchorNode(); | |
47 if (anchorNode == node) | |
48 return !position.isAfterAnchor() && !position.isBeforeAnchor(); | |
49 | |
50 return ComposedTreeTraversal::isDescendantOf(*anchorNode, node); | |
51 } | |
52 | |
53 bool isSelectionBoundary(const Node& node) | |
54 { | |
55 return isHTMLTextAreaElement(node) || isHTMLInputElement(node) || isHTMLSele
ctElement(node); | |
56 } | |
57 | |
58 Node* enclosingShadowHostForStart(const PositionInComposedTree& position) | |
59 { | |
60 Node* node = position.nodeAsRangeFirstNode(); | |
61 if (!node) | |
62 return nullptr; | |
63 Node* shadowHost = enclosingShadowHost(node); | |
64 if (!shadowHost) | |
65 return nullptr; | |
66 if (!isEnclosedBy(position, *shadowHost)) | |
67 return nullptr; | |
68 return isSelectionBoundary(*shadowHost) ? shadowHost : nullptr; | |
69 } | |
70 | |
71 Node* enclosingShadowHostForEnd(const PositionInComposedTree& position) | |
72 { | |
73 Node* node = position.nodeAsRangeLastNode(); | |
74 if (!node) | |
75 return nullptr; | |
76 Node* shadowHost = enclosingShadowHost(node); | |
77 if (!shadowHost) | |
78 return nullptr; | |
79 if (!isEnclosedBy(position, *shadowHost)) | |
80 return nullptr; | |
81 return isSelectionBoundary(*shadowHost) ? shadowHost : nullptr; | |
82 } | |
83 | |
84 PositionInComposedTree adjustPositionInComposedTreeForStart(const PositionInComp
osedTree& position, Node* shadowHost) | |
85 { | |
86 if (isEnclosedBy(position, *shadowHost)) { | |
87 if (position.isBeforeChildren()) | |
88 return PositionInComposedTree::beforeNode(shadowHost); | |
89 return PositionInComposedTree::afterNode(shadowHost); | |
90 } | |
91 | |
92 // We use |firstChild|'s after instead of beforeAllChildren for backward | |
93 // compatibility. The positions are same but the anchors would be different, | |
94 // and selection painting uses anchor nodes. | |
95 if (Node* firstChild = ComposedTreeTraversal::firstChild(*shadowHost)) | |
96 return PositionInComposedTree::beforeNode(firstChild); | |
97 return PositionInComposedTree(); | |
98 } | |
99 | |
100 Position adjustPositionForEnd(const Position& currentPosition, Node* startContai
nerNode) | |
101 { | |
102 TreeScope& treeScope = startContainerNode->treeScope(); | |
103 | |
104 ASSERT(currentPosition.computeContainerNode()->treeScope() != treeScope); | |
105 | |
106 if (Node* ancestor = treeScope.ancestorInThisScope(currentPosition.computeCo
ntainerNode())) { | |
107 if (ancestor->contains(startContainerNode)) | |
108 return positionAfterNode(ancestor); | |
109 return positionBeforeNode(ancestor); | |
110 } | |
111 | |
112 if (Node* lastChild = treeScope.rootNode().lastChild()) | |
113 return positionAfterNode(lastChild); | |
114 | |
115 return Position(); | |
116 } | |
117 | |
118 PositionInComposedTree adjustPositionInComposedTreeForEnd(const PositionInCompos
edTree& position, Node* shadowHost) | |
119 { | |
120 if (isEnclosedBy(position, *shadowHost)) { | |
121 if (position.isAfterChildren()) | |
122 return PositionInComposedTree::afterNode(shadowHost); | |
123 return PositionInComposedTree::beforeNode(shadowHost); | |
124 } | |
125 | |
126 // We use |lastChild|'s after instead of afterAllChildren for backward | |
127 // compatibility. The positions are same but the anchors would be different, | |
128 // and selection painting uses anchor nodes. | |
129 if (Node* lastChild = ComposedTreeTraversal::lastChild(*shadowHost)) | |
130 return PositionInComposedTree::afterNode(lastChild); | |
131 return PositionInComposedTree(); | |
132 } | |
133 | |
134 Position adjustPositionForStart(const Position& currentPosition, Node* endContai
nerNode) | |
135 { | |
136 TreeScope& treeScope = endContainerNode->treeScope(); | |
137 | |
138 ASSERT(currentPosition.computeContainerNode()->treeScope() != treeScope); | |
139 | |
140 if (Node* ancestor = treeScope.ancestorInThisScope(currentPosition.computeCo
ntainerNode())) { | |
141 if (ancestor->contains(endContainerNode)) | |
142 return positionBeforeNode(ancestor); | |
143 return positionAfterNode(ancestor); | |
144 } | |
145 | |
146 if (Node* firstChild = treeScope.rootNode().firstChild()) | |
147 return positionBeforeNode(firstChild); | |
148 | |
149 return Position(); | |
150 } | |
151 | |
152 } // namespace | |
153 | |
154 // Updates |selectionInComposedTree| to match with |selection|. | |
155 void SelectionAdjuster::adjustSelectionInComposedTree(VisibleSelectionInComposed
Tree* selectionInComposedTree, const VisibleSelection& selection) | |
156 { | |
157 if (selection.isNone()) { | |
158 *selectionInComposedTree = VisibleSelectionInComposedTree(); | |
159 return; | |
160 } | |
161 | |
162 const PositionInComposedTree& base = toPositionInComposedTree(selection.base
()); | |
163 const PositionInComposedTree& extent = toPositionInComposedTree(selection.ex
tent()); | |
164 const PositionInComposedTree& position1 = toPositionInComposedTree(selection
.start()); | |
165 const PositionInComposedTree& position2 = toPositionInComposedTree(selection
.end()); | |
166 position1.anchorNode()->updateDistribution(); | |
167 position2.anchorNode()->updateDistribution(); | |
168 selectionInComposedTree->m_base = base; | |
169 selectionInComposedTree->m_extent = extent; | |
170 selectionInComposedTree->m_affinity = selection.m_affinity; | |
171 selectionInComposedTree->m_isDirectional = selection.m_isDirectional; | |
172 selectionInComposedTree->m_baseIsFirst = base.isNull() || base.compareTo(ext
ent) <= 0; | |
173 if (position1.compareTo(position2) <= 0) { | |
174 selectionInComposedTree->m_start = position1; | |
175 selectionInComposedTree->m_end = position2; | |
176 } else { | |
177 selectionInComposedTree->m_start = position2; | |
178 selectionInComposedTree->m_end = position1; | |
179 } | |
180 selectionInComposedTree->updateSelectionType(); | |
181 } | |
182 | |
183 static bool isCrossingShadowBoundaries(const VisibleSelectionInComposedTree& sel
ection) | |
184 { | |
185 if (!selection.isRange()) | |
186 return false; | |
187 TreeScope& treeScope = selection.base().anchorNode()->treeScope(); | |
188 return selection.extent().anchorNode()->treeScope() != treeScope | |
189 || selection.start().anchorNode()->treeScope() != treeScope | |
190 || selection.end().anchorNode()->treeScope() != treeScope; | |
191 } | |
192 | |
193 void SelectionAdjuster::adjustSelectionInDOMTree(VisibleSelection* selection, co
nst VisibleSelectionInComposedTree& selectionInComposedTree) | |
194 { | |
195 if (selectionInComposedTree.isNone()) { | |
196 *selection = VisibleSelection(); | |
197 return; | |
198 } | |
199 | |
200 const Position& base = toPositionInDOMTree(selectionInComposedTree.base()); | |
201 const Position& extent = toPositionInDOMTree(selectionInComposedTree.extent(
)); | |
202 | |
203 if (isCrossingShadowBoundaries(selectionInComposedTree)) { | |
204 *selection = VisibleSelection(base, extent); | |
205 return; | |
206 } | |
207 | |
208 const Position& position1 = toPositionInDOMTree(selectionInComposedTree.star
t()); | |
209 const Position& position2 = toPositionInDOMTree(selectionInComposedTree.end(
)); | |
210 selection->m_base = base; | |
211 selection->m_extent = extent; | |
212 selection->m_affinity = selectionInComposedTree.m_affinity; | |
213 selection->m_isDirectional = selectionInComposedTree.m_isDirectional; | |
214 selection->m_baseIsFirst = base.isNull() || base.compareTo(extent) <= 0; | |
215 if (position1.compareTo(position2) <= 0) { | |
216 selection->m_start = position1; | |
217 selection->m_end = position2; | |
218 } else { | |
219 selection->m_start = position2; | |
220 selection->m_end = position1; | |
221 } | |
222 selection->updateSelectionType(); | |
223 } | |
224 | |
225 void SelectionAdjuster::adjustSelectionToAvoidCrossingShadowBoundaries(VisibleSe
lection* selection) | |
226 { | |
227 // Note: |m_selectionType| isn't computed yet. | |
228 ASSERT(selection->base().isNotNull()); | |
229 ASSERT(selection->extent().isNotNull()); | |
230 ASSERT(selection->start().isNotNull()); | |
231 ASSERT(selection->end().isNotNull()); | |
232 | |
233 // TODO(hajimehoshi): Checking treeScope is wrong when a node is | |
234 // distributed, but we leave it as it is for backward compatibility. | |
235 if (selection->start().anchorNode()->treeScope() == selection->end().anchorN
ode()->treeScope()) | |
236 return; | |
237 | |
238 if (selection->isBaseFirst()) { | |
239 const Position& newEnd = adjustPositionForEnd(selection->end(), selectio
n->start().computeContainerNode()); | |
240 selection->m_extent = newEnd; | |
241 selection->m_end = newEnd; | |
242 return; | |
243 } | |
244 | |
245 const Position& newStart = adjustPositionForStart(selection->start(), select
ion->end().computeContainerNode()); | |
246 selection->m_extent = newStart; | |
247 selection->m_start = newStart; | |
248 } | |
249 | |
250 // This function is called twice. The first is called when |m_start| and |m_end| | |
251 // or |m_extent| are same, and the second when |m_start| and |m_end| are changed | |
252 // after downstream/upstream. | |
253 void SelectionAdjuster::adjustSelectionToAvoidCrossingShadowBoundaries(VisibleSe
lectionInComposedTree* selection) | |
254 { | |
255 Node* const shadowHostStart = enclosingShadowHostForStart(selection->start()
); | |
256 Node* const shadowHostEnd = enclosingShadowHostForEnd(selection->end()); | |
257 if (shadowHostStart == shadowHostEnd) | |
258 return; | |
259 | |
260 if (selection->isBaseFirst()) { | |
261 Node* const shadowHost = shadowHostStart ? shadowHostStart : shadowHostE
nd; | |
262 const PositionInComposedTree& newEnd = adjustPositionInComposedTreeForEn
d(selection->end(), shadowHost); | |
263 selection->m_extent = newEnd; | |
264 selection->m_end = newEnd; | |
265 return; | |
266 } | |
267 Node* const shadowHost = shadowHostEnd ? shadowHostEnd : shadowHostStart; | |
268 const PositionInComposedTree& newStart = adjustPositionInComposedTreeForStar
t(selection->start(), shadowHost); | |
269 selection->m_extent = newStart; | |
270 selection->m_start = newStart; | |
271 } | |
272 | |
273 } // namespace blink | |
OLD | NEW |