1 /*
2 * Copyright (C) 2007, 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2012 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 * its contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30
31 #include "config.h"
32 #include "core/page/DOMSelection.h"
33
34 #include "bindings/v8/ExceptionState.h"
35 #include "bindings/v8/ExceptionStatePlaceholder.h"
36 #include "core/dom/Document.h"
37 #include "core/dom/ExceptionCode.h"
38 #include "core/dom/Node.h"
39 #include "core/dom/Range.h"
40 #include "core/dom/TreeScope.h"
41 #include "core/editing/FrameSelection.h"
42 #include "core/editing/TextIterator.h"
43 #include "core/editing/htmlediting.h"
44 #include "core/frame/Frame.h"
45 #include "wtf/text/WTFString.h"
46
47 namespace WebCore {
48
selectionShadowAncestor(Frame * frame)49 static Node* selectionShadowAncestor(Frame* frame)
50 {
51 Node* node = frame->selection().selection().base().anchorNode();
52 if (!node)
53 return 0;
54
55 if (!node->isInShadowTree())
56 return 0;
57
58 return frame->document()->ancestorInThisScope(node);
59 }
60
DOMSelection(const TreeScope * treeScope)61 DOMSelection::DOMSelection(const TreeScope* treeScope)
62 : DOMWindowProperty(treeScope->rootNode()->document().frame())
63 , m_treeScope(treeScope)
64 {
65 ScriptWrappable::init(this);
66 }
67
clearTreeScope()68 void DOMSelection::clearTreeScope()
69 {
70 m_treeScope = 0;
71 }
72
visibleSelection() const73 const VisibleSelection& DOMSelection::visibleSelection() const
74 {
75 ASSERT(m_frame);
76 return m_frame->selection().selection();
77 }
78
anchorPosition(const VisibleSelection & selection)79 static Position anchorPosition(const VisibleSelection& selection)
80 {
81 Position anchor = selection.isBaseFirst() ? selection.start() : selection.end();
82 return anchor.parentAnchoredEquivalent();
83 }
84
focusPosition(const VisibleSelection & selection)85 static Position focusPosition(const VisibleSelection& selection)
86 {
87 Position focus = selection.isBaseFirst() ? selection.end() : selection.start();
88 return focus.parentAnchoredEquivalent();
89 }
90
basePosition(const VisibleSelection & selection)91 static Position basePosition(const VisibleSelection& selection)
92 {
93 return selection.base().parentAnchoredEquivalent();
94 }
95
extentPosition(const VisibleSelection & selection)96 static Position extentPosition(const VisibleSelection& selection)
97 {
98 return selection.extent().parentAnchoredEquivalent();
99 }
100
anchorNode() const101 Node* DOMSelection::anchorNode() const
102 {
103 if (!m_frame)
104 return 0;
105
106 return shadowAdjustedNode(anchorPosition(visibleSelection()));
107 }
108
anchorOffset() const109 int DOMSelection::anchorOffset() const
110 {
111 if (!m_frame)
112 return 0;
113
114 return shadowAdjustedOffset(anchorPosition(visibleSelection()));
115 }
116
focusNode() const117 Node* DOMSelection::focusNode() const
118 {
119 if (!m_frame)
120 return 0;
121
122 return shadowAdjustedNode(focusPosition(visibleSelection()));
123 }
124
focusOffset() const125 int DOMSelection::focusOffset() const
126 {
127 if (!m_frame)
128 return 0;
129
130 return shadowAdjustedOffset(focusPosition(visibleSelection()));
131 }
132
baseNode() const133 Node* DOMSelection::baseNode() const
134 {
135 if (!m_frame)
136 return 0;
137
138 return shadowAdjustedNode(basePosition(visibleSelection()));
139 }
140
baseOffset() const141 int DOMSelection::baseOffset() const
142 {
143 if (!m_frame)
144 return 0;
145
146 return shadowAdjustedOffset(basePosition(visibleSelection()));
147 }
148
extentNode() const149 Node* DOMSelection::extentNode() const
150 {
151 if (!m_frame)
152 return 0;
153
154 return shadowAdjustedNode(extentPosition(visibleSelection()));
155 }
156
extentOffset() const157 int DOMSelection::extentOffset() const
158 {
159 if (!m_frame)
160 return 0;
161
162 return shadowAdjustedOffset(extentPosition(visibleSelection()));
163 }
164
isCollapsed() const165 bool DOMSelection::isCollapsed() const
166 {
167 if (!m_frame || selectionShadowAncestor(m_frame))
168 return true;
169 return !m_frame->selection().isRange();
170 }
171
type() const172 String DOMSelection::type() const
173 {
174 if (!m_frame)
175 return String();
176
177 FrameSelection& selection = m_frame->selection();
178
179 // This is a WebKit DOM extension, incompatible with an IE extension
180 // IE has this same attribute, but returns "none", "text" and "control"
181 // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx
182 if (selection.isNone())
183 return "None";
184 if (selection.isCaret())
185 return "Caret";
186 return "Range";
187 }
188
rangeCount() const189 int DOMSelection::rangeCount() const
190 {
191 if (!m_frame)
192 return 0;
193 return m_frame->selection().isNone() ? 0 : 1;
194 }
195
collapse(Node * node,int offset,ExceptionState & exceptionState)196 void DOMSelection::collapse(Node* node, int offset, ExceptionState& exceptionState)
197 {
198 if (!m_frame)
199 return;
200
201 if (offset < 0) {
202 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset.");
203 return;
204 }
205
206 if (!isValidForPosition(node))
207 return;
208
209 // FIXME: Eliminate legacy editing positions
210 m_frame->selection().moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
211 }
212
collapseToEnd(ExceptionState & exceptionState)213 void DOMSelection::collapseToEnd(ExceptionState& exceptionState)
214 {
215 if (!m_frame)
216 return;
217
218 const VisibleSelection& selection = m_frame->selection().selection();
219
220 if (selection.isNone()) {
221 exceptionState.throwDOMException(InvalidStateError, "there is no selection.");
222 return;
223 }
224
225 m_frame->selection().moveTo(VisiblePosition(selection.end(), DOWNSTREAM));
226 }
227
collapseToStart(ExceptionState & exceptionState)228 void DOMSelection::collapseToStart(ExceptionState& exceptionState)
229 {
230 if (!m_frame)
231 return;
232
233 const VisibleSelection& selection = m_frame->selection().selection();
234
235 if (selection.isNone()) {
236 exceptionState.throwDOMException(InvalidStateError, "there is no selection.");
237 return;
238 }
239
240 m_frame->selection().moveTo(VisiblePosition(selection.start(), DOWNSTREAM));
241 }
242
empty()243 void DOMSelection::empty()
244 {
245 if (!m_frame)
246 return;
247 m_frame->selection().clear();
248 }
249
setBaseAndExtent(Node * baseNode,int baseOffset,Node * extentNode,int extentOffset,ExceptionState & exceptionState)250 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionState& exceptionState)
251 {
252 if (!m_frame)
253 return;
254
255 if (baseOffset < 0) {
256 exceptionState.throwDOMException(IndexSizeError, String::number(baseOffset) + " is not a valid base offset.");
257 return;
258 }
259
260 if (extentOffset < 0) {
261 exceptionState.throwDOMException(IndexSizeError, String::number(extentOffset) + " is not a valid extent offset.");
262 return;
263 }
264
265 if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
266 return;
267
268 // FIXME: Eliminate legacy editing positions
269 VisiblePosition visibleBase = VisiblePosition(createLegacyEditingPosition(baseNode, baseOffset), DOWNSTREAM);
270 VisiblePosition visibleExtent = VisiblePosition(createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM);
271
272 m_frame->selection().moveTo(visibleBase, visibleExtent);
273 }
274
setPosition(Node * node,int offset,ExceptionState & exceptionState)275 void DOMSelection::setPosition(Node* node, int offset, ExceptionState& exceptionState)
276 {
277 if (!m_frame)
278 return;
279 if (offset < 0) {
280 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset.");
281 return;
282 }
283
284 if (!isValidForPosition(node))
285 return;
286
287 // FIXME: Eliminate legacy editing positions
288 m_frame->selection().moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
289 }
290
modify(const String & alterString,const String & directionString,const String & granularityString)291 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
292 {
293 if (!m_frame)
294 return;
295
296 FrameSelection::EAlteration alter;
297 if (equalIgnoringCase(alterString, "extend"))
298 alter = FrameSelection::AlterationExtend;
299 else if (equalIgnoringCase(alterString, "move"))
300 alter = FrameSelection::AlterationMove;
301 else
302 return;
303
304 SelectionDirection direction;
305 if (equalIgnoringCase(directionString, "forward"))
306 direction = DirectionForward;
307 else if (equalIgnoringCase(directionString, "backward"))
308 direction = DirectionBackward;
309 else if (equalIgnoringCase(directionString, "left"))
310 direction = DirectionLeft;
311 else if (equalIgnoringCase(directionString, "right"))
312 direction = DirectionRight;
313 else
314 return;
315
316 TextGranularity granularity;
317 if (equalIgnoringCase(granularityString, "character"))
318 granularity = CharacterGranularity;
319 else if (equalIgnoringCase(granularityString, "word"))
320 granularity = WordGranularity;
321 else if (equalIgnoringCase(granularityString, "sentence"))
322 granularity = SentenceGranularity;
323 else if (equalIgnoringCase(granularityString, "line"))
324 granularity = LineGranularity;
325 else if (equalIgnoringCase(granularityString, "paragraph"))
326 granularity = ParagraphGranularity;
327 else if (equalIgnoringCase(granularityString, "lineboundary"))
328 granularity = LineBoundary;
329 else if (equalIgnoringCase(granularityString, "sentenceboundary"))
330 granularity = SentenceBoundary;
331 else if (equalIgnoringCase(granularityString, "paragraphboundary"))
332 granularity = ParagraphBoundary;
333 else if (equalIgnoringCase(granularityString, "documentboundary"))
334 granularity = DocumentBoundary;
335 else
336 return;
337
338 m_frame->selection().modify(alter, direction, granularity);
339 }
340
extend(Node * node,int offset,ExceptionState & exceptionState)341 void DOMSelection::extend(Node* node, int offset, ExceptionState& exceptionState)
342 {
343 if (!m_frame)
344 return;
345
346 if (!node) {
347 exceptionState.throwDOMException(TypeMismatchError, "The node provided is invalid.");
348 return;
349 }
350
351 if (offset < 0) {
352 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset.");
353 return;
354 }
355 if (offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) {
356 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is larger than the given node's length.");
357 return;
358 }
359
360 if (!isValidForPosition(node))
361 return;
362
363 // FIXME: Eliminate legacy editing positions
364 m_frame->selection().setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
365 }
366
getRangeAt(int index,ExceptionState & exceptionState)367 PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionState& exceptionState)
368 {
369 if (!m_frame)
370 return 0;
371
372 if (index < 0 || index >= rangeCount()) {
373 exceptionState.throwDOMException(IndexSizeError, String::number(index) + " is not a valid index.");
374 return 0;
375 }
376
377 // If you're hitting this, you've added broken multi-range selection support
378 ASSERT(rangeCount() == 1);
379
380 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
381 ASSERT(!shadowAncestor->isShadowRoot());
382 ContainerNode* container = shadowAncestor->parentOrShadowHostNode();
383 int offset = shadowAncestor->nodeIndex();
384 return Range::create(shadowAncestor->document(), container, offset, container, offset);
385 }
386
387 const VisibleSelection& selection = m_frame->selection().selection();
388 return selection.firstRange();
389 }
390
removeAllRanges()391 void DOMSelection::removeAllRanges()
392 {
393 if (!m_frame)
394 return;
395 m_frame->selection().clear();
396 }
397
addRange(Range * r)398 void DOMSelection::addRange(Range* r)
399 {
400 if (!m_frame)
401 return;
402 if (!r)
403 return;
404
405 FrameSelection& selection = m_frame->selection();
406
407 if (selection.isNone()) {
408 selection.setSelection(VisibleSelection(r));
409 return;
410 }
411
412 RefPtr<Range> range = selection.selection().toNormalizedRange();
413 if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), IGNORE_EXCEPTION) == -1) {
414 // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
415 if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), IGNORE_EXCEPTION) > -1) {
416 if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), IGNORE_EXCEPTION) == -1) {
417 // The original range and r intersect.
418 selection.setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM));
419 } else {
420 // r contains the original range.
421 selection.setSelection(VisibleSelection(r));
422 }
423 }
424 } else {
425 // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
426 TrackExceptionState exceptionState;
427 if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), exceptionState) < 1 && !exceptionState.hadException()) {
428 if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), IGNORE_EXCEPTION) == -1) {
429 // The original range contains r.
430 selection.setSelection(VisibleSelection(range.get()));
431 } else {
432 // The original range and r intersect.
433 selection.setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM));
434 }
435 }
436 }
437 }
438
deleteFromDocument()439 void DOMSelection::deleteFromDocument()
440 {
441 if (!m_frame)
442 return;
443
444 FrameSelection& selection = m_frame->selection();
445
446 if (selection.isNone())
447 return;
448
449 if (isCollapsed())
450 selection.modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity);
451
452 RefPtr<Range> selectedRange = selection.selection().toNormalizedRange();
453 if (!selectedRange)
454 return;
455
456 selectedRange->deleteContents(ASSERT_NO_EXCEPTION);
457
458 setBaseAndExtent(selectedRange->startContainer(ASSERT_NO_EXCEPTION), selectedRange->startOffset(), selectedRange->startContainer(), selectedRange->startOffset(), ASSERT_NO_EXCEPTION);
459 }
460
containsNode(const Node * n,bool allowPartial) const461 bool DOMSelection::containsNode(const Node* n, bool allowPartial) const
462 {
463 if (!m_frame)
464 return false;
465
466 FrameSelection& selection = m_frame->selection();
467
468 if (!n || m_frame->document() != n->document() || selection.isNone())
469 return false;
470
471 unsigned nodeIndex = n->nodeIndex();
472 RefPtr<Range> selectedRange = selection.selection().toNormalizedRange();
473
474 ContainerNode* parentNode = n->parentNode();
475 if (!parentNode)
476 return false;
477
478 TrackExceptionState exceptionState;
479 bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(), selectedRange->startOffset(), exceptionState) >= 0 && !exceptionState.hadException()
480 && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(), selectedRange->endOffset(), exceptionState) <= 0 && !exceptionState.hadException();
481 ASSERT(!exceptionState.hadException());
482 if (nodeFullySelected)
483 return true;
484
485 bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(), selectedRange->endOffset(), exceptionState) > 0 && !exceptionState.hadException())
486 || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(), selectedRange->startOffset(), exceptionState) < 0 && !exceptionState.hadException());
487 ASSERT(!exceptionState.hadException());
488 if (nodeFullyUnselected)
489 return false;
490
491 return allowPartial || n->isTextNode();
492 }
493
selectAllChildren(Node * n,ExceptionState & exceptionState)494 void DOMSelection::selectAllChildren(Node* n, ExceptionState& exceptionState)
495 {
496 if (!n)
497 return;
498
499 // This doesn't (and shouldn't) select text node characters.
500 setBaseAndExtent(n, 0, n, n->childNodeCount(), exceptionState);
501 }
502
toString()503 String DOMSelection::toString()
504 {
505 if (!m_frame)
506 return String();
507
508 return plainText(m_frame->selection().selection().toNormalizedRange().get());
509 }
510
shadowAdjustedNode(const Position & position) const511 Node* DOMSelection::shadowAdjustedNode(const Position& position) const
512 {
513 if (position.isNull())
514 return 0;
515
516 Node* containerNode = position.containerNode();
517 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
518
519 if (!adjustedNode)
520 return 0;
521
522 if (containerNode == adjustedNode)
523 return containerNode;
524
525 ASSERT(!adjustedNode->isShadowRoot());
526 return adjustedNode->parentOrShadowHostNode();
527 }
528
shadowAdjustedOffset(const Position & position) const529 int DOMSelection::shadowAdjustedOffset(const Position& position) const
530 {
531 if (position.isNull())
532 return 0;
533
534 Node* containerNode = position.containerNode();
535 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
536
537 if (!adjustedNode)
538 return 0;
539
540 if (containerNode == adjustedNode)
541 return position.computeOffsetInContainerNode();
542
543 return adjustedNode->nodeIndex();
544 }
545
isValidForPosition(Node * node) const546 bool DOMSelection::isValidForPosition(Node* node) const
547 {
548 ASSERT(m_frame);
549 if (!node)
550 return true;
551 return node->document() == m_frame->document();
552 }
553
554 } // namespace WebCore
555