• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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