1 /*
2 * Copyright (C) 2007, 2009 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 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29
30 #include "config.h"
31 #include "DOMSelection.h"
32
33 #include "ExceptionCode.h"
34 #include "Frame.h"
35 #include "Node.h"
36 #include "PlatformString.h"
37 #include "Range.h"
38 #include "SelectionController.h"
39 #include "TextIterator.h"
40 #include "htmlediting.h"
41
42 namespace WebCore {
43
selectionShadowAncestor(Frame * frame)44 static Node* selectionShadowAncestor(Frame* frame)
45 {
46 Node* node = frame->selection()->selection().base().anchorNode();
47 if (!node)
48 return 0;
49 Node* shadowAncestor = node->shadowAncestorNode();
50 if (shadowAncestor == node)
51 return 0;
52 return shadowAncestor;
53 }
54
DOMSelection(Frame * frame)55 DOMSelection::DOMSelection(Frame* frame)
56 : m_frame(frame)
57 {
58 }
59
frame() const60 Frame* DOMSelection::frame() const
61 {
62 return m_frame;
63 }
64
disconnectFrame()65 void DOMSelection::disconnectFrame()
66 {
67 m_frame = 0;
68 }
69
visibleSelection() const70 const VisibleSelection& DOMSelection::visibleSelection() const
71 {
72 ASSERT(m_frame);
73 return m_frame->selection()->selection();
74 }
75
anchorPosition(const VisibleSelection & selection)76 static Position anchorPosition(const VisibleSelection& selection)
77 {
78 Position anchor = selection.isBaseFirst() ? selection.start() : selection.end();
79 return rangeCompliantEquivalent(anchor);
80 }
81
focusPosition(const VisibleSelection & selection)82 static Position focusPosition(const VisibleSelection& selection)
83 {
84 Position focus = selection.isBaseFirst() ? selection.end() : selection.start();
85 return rangeCompliantEquivalent(focus);
86 }
87
basePosition(const VisibleSelection & selection)88 static Position basePosition(const VisibleSelection& selection)
89 {
90 return rangeCompliantEquivalent(selection.base());
91 }
92
extentPosition(const VisibleSelection & selection)93 static Position extentPosition(const VisibleSelection& selection)
94 {
95 return rangeCompliantEquivalent(selection.extent());
96 }
97
anchorNode() const98 Node* DOMSelection::anchorNode() const
99 {
100 if (!m_frame)
101 return 0;
102 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
103 return shadowAncestor->parentNode();
104 return anchorPosition(visibleSelection()).node();
105 }
106
anchorOffset() const107 int DOMSelection::anchorOffset() const
108 {
109 if (!m_frame)
110 return 0;
111 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
112 return shadowAncestor->nodeIndex();
113 return anchorPosition(visibleSelection()).deprecatedEditingOffset();
114 }
115
focusNode() const116 Node* DOMSelection::focusNode() const
117 {
118 if (!m_frame)
119 return 0;
120 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
121 return shadowAncestor->parentNode();
122 return focusPosition(visibleSelection()).node();
123 }
124
focusOffset() const125 int DOMSelection::focusOffset() const
126 {
127 if (!m_frame)
128 return 0;
129 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
130 return shadowAncestor->nodeIndex();
131 return focusPosition(visibleSelection()).deprecatedEditingOffset();
132 }
133
baseNode() const134 Node* DOMSelection::baseNode() const
135 {
136 if (!m_frame)
137 return 0;
138 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
139 return shadowAncestor->parentNode();
140 return basePosition(visibleSelection()).node();
141 }
142
baseOffset() const143 int DOMSelection::baseOffset() const
144 {
145 if (!m_frame)
146 return 0;
147 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
148 return shadowAncestor->nodeIndex();
149 return basePosition(visibleSelection()).deprecatedEditingOffset();
150 }
151
extentNode() const152 Node* DOMSelection::extentNode() const
153 {
154 if (!m_frame)
155 return 0;
156 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
157 return shadowAncestor->parentNode();
158 return extentPosition(visibleSelection()).node();
159 }
160
extentOffset() const161 int DOMSelection::extentOffset() const
162 {
163 if (!m_frame)
164 return 0;
165 if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
166 return shadowAncestor->nodeIndex();
167 return extentPosition(visibleSelection()).deprecatedEditingOffset();
168 }
169
isCollapsed() const170 bool DOMSelection::isCollapsed() const
171 {
172 if (!m_frame || selectionShadowAncestor(m_frame))
173 return true;
174 return !m_frame->selection()->isRange();
175 }
176
type() const177 String DOMSelection::type() const
178 {
179 if (!m_frame)
180 return String();
181
182 SelectionController* selection = m_frame->selection();
183
184 // This is a WebKit DOM extension, incompatible with an IE extension
185 // IE has this same attribute, but returns "none", "text" and "control"
186 // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx
187 if (selection->isNone())
188 return "None";
189 if (selection->isCaret())
190 return "Caret";
191 return "Range";
192 }
193
rangeCount() const194 int DOMSelection::rangeCount() const
195 {
196 if (!m_frame)
197 return 0;
198 return m_frame->selection()->isNone() ? 0 : 1;
199 }
200
collapse(Node * node,int offset,ExceptionCode & ec)201 void DOMSelection::collapse(Node* node, int offset, ExceptionCode& ec)
202 {
203 if (!m_frame)
204 return;
205
206 if (offset < 0) {
207 ec = INDEX_SIZE_ERR;
208 return;
209 }
210 m_frame->selection()->moveTo(VisiblePosition(node, offset, DOWNSTREAM));
211 }
212
collapseToEnd()213 void DOMSelection::collapseToEnd()
214 {
215 if (!m_frame)
216 return;
217
218 const VisibleSelection& selection = m_frame->selection()->selection();
219 m_frame->selection()->moveTo(VisiblePosition(selection.end(), DOWNSTREAM));
220 }
221
collapseToStart()222 void DOMSelection::collapseToStart()
223 {
224 if (!m_frame)
225 return;
226
227 const VisibleSelection& selection = m_frame->selection()->selection();
228 m_frame->selection()->moveTo(VisiblePosition(selection.start(), DOWNSTREAM));
229 }
230
empty()231 void DOMSelection::empty()
232 {
233 if (!m_frame)
234 return;
235 m_frame->selection()->clear();
236 }
237
setBaseAndExtent(Node * baseNode,int baseOffset,Node * extentNode,int extentOffset,ExceptionCode & ec)238 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionCode& ec)
239 {
240 if (!m_frame)
241 return;
242
243 if (baseOffset < 0 || extentOffset < 0) {
244 ec = INDEX_SIZE_ERR;
245 return;
246 }
247 VisiblePosition visibleBase = VisiblePosition(baseNode, baseOffset, DOWNSTREAM);
248 VisiblePosition visibleExtent = VisiblePosition(extentNode, extentOffset, DOWNSTREAM);
249
250 m_frame->selection()->moveTo(visibleBase, visibleExtent);
251 }
252
setPosition(Node * node,int offset,ExceptionCode & ec)253 void DOMSelection::setPosition(Node* node, int offset, ExceptionCode& ec)
254 {
255 if (!m_frame)
256 return;
257 if (offset < 0) {
258 ec = INDEX_SIZE_ERR;
259 return;
260 }
261 m_frame->selection()->moveTo(VisiblePosition(node, offset, DOWNSTREAM));
262 }
263
modify(const String & alterString,const String & directionString,const String & granularityString)264 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
265 {
266 if (!m_frame)
267 return;
268
269 SelectionController::EAlteration alter;
270 if (equalIgnoringCase(alterString, "extend"))
271 alter = SelectionController::EXTEND;
272 else if (equalIgnoringCase(alterString, "move"))
273 alter = SelectionController::MOVE;
274 else
275 return;
276
277 SelectionController::EDirection direction;
278 if (equalIgnoringCase(directionString, "forward"))
279 direction = SelectionController::FORWARD;
280 else if (equalIgnoringCase(directionString, "backward"))
281 direction = SelectionController::BACKWARD;
282 else if (equalIgnoringCase(directionString, "left"))
283 direction = SelectionController::LEFT;
284 else if (equalIgnoringCase(directionString, "right"))
285 direction = SelectionController::RIGHT;
286 else
287 return;
288
289 TextGranularity granularity;
290 if (equalIgnoringCase(granularityString, "character"))
291 granularity = CharacterGranularity;
292 else if (equalIgnoringCase(granularityString, "word"))
293 granularity = WordGranularity;
294 else if (equalIgnoringCase(granularityString, "sentence"))
295 granularity = SentenceGranularity;
296 else if (equalIgnoringCase(granularityString, "line"))
297 granularity = LineGranularity;
298 else if (equalIgnoringCase(granularityString, "paragraph"))
299 granularity = ParagraphGranularity;
300 else if (equalIgnoringCase(granularityString, "lineboundary"))
301 granularity = LineBoundary;
302 else if (equalIgnoringCase(granularityString, "sentenceboundary"))
303 granularity = SentenceBoundary;
304 else if (equalIgnoringCase(granularityString, "paragraphboundary"))
305 granularity = ParagraphBoundary;
306 else if (equalIgnoringCase(granularityString, "documentboundary"))
307 granularity = DocumentBoundary;
308 else
309 return;
310
311 m_frame->selection()->modify(alter, direction, granularity, false);
312 }
313
extend(Node * node,int offset,ExceptionCode & ec)314 void DOMSelection::extend(Node* node, int offset, ExceptionCode& ec)
315 {
316 if (!m_frame)
317 return;
318
319 if (!node) {
320 ec = TYPE_MISMATCH_ERR;
321 return;
322 }
323 if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) {
324 ec = INDEX_SIZE_ERR;
325 return;
326 }
327
328 SelectionController* selection = m_frame->selection();
329 selection->expandUsingGranularity(CharacterGranularity);
330 selection->setExtent(VisiblePosition(node, offset, DOWNSTREAM));
331 }
332
getRangeAt(int index,ExceptionCode & ec)333 PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionCode& ec)
334 {
335 if (!m_frame)
336 return 0;
337
338 if (index < 0 || index >= rangeCount()) {
339 ec = INDEX_SIZE_ERR;
340 return 0;
341 }
342
343 // If you're hitting this, you've added broken multi-range selection support
344 ASSERT(rangeCount() == 1);
345
346 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
347 Node* container = shadowAncestor->parentNode();
348 int offset = shadowAncestor->nodeIndex();
349 return Range::create(shadowAncestor->document(), container, offset, container, offset);
350 }
351
352 const VisibleSelection& selection = m_frame->selection()->selection();
353 return selection.firstRange();
354 }
355
removeAllRanges()356 void DOMSelection::removeAllRanges()
357 {
358 if (!m_frame)
359 return;
360 m_frame->selection()->clear();
361 }
362
addRange(Range * r)363 void DOMSelection::addRange(Range* r)
364 {
365 if (!m_frame)
366 return;
367 if (!r)
368 return;
369
370 SelectionController* selection = m_frame->selection();
371
372 if (selection->isNone()) {
373 selection->setSelection(VisibleSelection(r));
374 return;
375 }
376
377 RefPtr<Range> range = selection->selection().toNormalizedRange();
378 ExceptionCode ec = 0;
379 if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), ec) == -1) {
380 // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
381 if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), ec) > -1) {
382 if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1)
383 // The original range and r intersect.
384 selection->setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM));
385 else
386 // r contains the original range.
387 selection->setSelection(VisibleSelection(r));
388 }
389 } else {
390 // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
391 if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), ec) < 1) {
392 if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1)
393 // The original range contains r.
394 selection->setSelection(VisibleSelection(range.get()));
395 else
396 // The original range and r intersect.
397 selection->setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM));
398 }
399 }
400 }
401
deleteFromDocument()402 void DOMSelection::deleteFromDocument()
403 {
404 if (!m_frame)
405 return;
406
407 SelectionController* selection = m_frame->selection();
408
409 if (selection->isNone())
410 return;
411
412 if (isCollapsed())
413 selection->modify(SelectionController::EXTEND, SelectionController::BACKWARD, CharacterGranularity);
414
415 RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
416
417 ExceptionCode ec = 0;
418 selectedRange->deleteContents(ec);
419 ASSERT(!ec);
420
421 setBaseAndExtent(selectedRange->startContainer(ec), selectedRange->startOffset(ec), selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec);
422 ASSERT(!ec);
423 }
424
containsNode(const Node * n,bool allowPartial) const425 bool DOMSelection::containsNode(const Node* n, bool allowPartial) const
426 {
427 if (!m_frame)
428 return false;
429
430 SelectionController* selection = m_frame->selection();
431
432 if (!n || selection->isNone())
433 return false;
434
435 Node* parentNode = n->parentNode();
436 unsigned nodeIndex = n->nodeIndex();
437 RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
438
439 if (!parentNode)
440 return false;
441
442 ExceptionCode ec = 0;
443 bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(ec), selectedRange->startOffset(ec)) >= 0
444 && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(ec), selectedRange->endOffset(ec)) <= 0;
445 ASSERT(!ec);
446 if (nodeFullySelected)
447 return true;
448
449 bool nodeFullyUnselected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(ec), selectedRange->endOffset(ec)) > 0
450 || Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(ec), selectedRange->startOffset(ec)) < 0;
451 ASSERT(!ec);
452 if (nodeFullyUnselected)
453 return false;
454
455 return allowPartial || n->isTextNode();
456 }
457
selectAllChildren(Node * n,ExceptionCode & ec)458 void DOMSelection::selectAllChildren(Node* n, ExceptionCode& ec)
459 {
460 if (!n)
461 return;
462
463 // This doesn't (and shouldn't) select text node characters.
464 setBaseAndExtent(n, 0, n, n->childNodeCount(), ec);
465 }
466
toString()467 String DOMSelection::toString()
468 {
469 if (!m_frame)
470 return String();
471
472 return plainText(m_frame->selection()->selection().toNormalizedRange().get());
473 }
474
475 } // namespace WebCore
476