• Home
  • History
  • Annotate
  • 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/editing/DOMSelection.h"
33 
34 #include "bindings/core/v8/ExceptionMessages.h"
35 #include "bindings/core/v8/ExceptionState.h"
36 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
37 #include "core/dom/Document.h"
38 #include "core/dom/ExceptionCode.h"
39 #include "core/dom/Node.h"
40 #include "core/dom/Range.h"
41 #include "core/dom/TreeScope.h"
42 #include "core/editing/FrameSelection.h"
43 #include "core/editing/TextIterator.h"
44 #include "core/editing/htmlediting.h"
45 #include "core/frame/LocalFrame.h"
46 #include "core/inspector/ConsoleMessage.h"
47 #include "wtf/text/WTFString.h"
48 
49 namespace blink {
50 
selectionShadowAncestor(LocalFrame * frame)51 static Node* selectionShadowAncestor(LocalFrame* frame)
52 {
53     Node* node = frame->selection().selection().base().anchorNode();
54     if (!node)
55         return 0;
56 
57     if (!node->isInShadowTree())
58         return 0;
59 
60     return frame->document()->ancestorInThisScope(node);
61 }
62 
DOMSelection(const TreeScope * treeScope)63 DOMSelection::DOMSelection(const TreeScope* treeScope)
64     : DOMWindowProperty(treeScope->rootNode().document().frame())
65     , m_treeScope(treeScope)
66 {
67 }
68 
clearTreeScope()69 void DOMSelection::clearTreeScope()
70 {
71     m_treeScope = nullptr;
72 }
73 
visibleSelection() const74 const VisibleSelection& DOMSelection::visibleSelection() const
75 {
76     ASSERT(m_frame);
77     return m_frame->selection().selection();
78 }
79 
anchorPosition(const VisibleSelection & selection)80 static Position anchorPosition(const VisibleSelection& selection)
81 {
82     Position anchor = selection.isBaseFirst() ? selection.start() : selection.end();
83     return anchor.parentAnchoredEquivalent();
84 }
85 
focusPosition(const VisibleSelection & selection)86 static Position focusPosition(const VisibleSelection& selection)
87 {
88     Position focus = selection.isBaseFirst() ? selection.end() : selection.start();
89     return focus.parentAnchoredEquivalent();
90 }
91 
basePosition(const VisibleSelection & selection)92 static Position basePosition(const VisibleSelection& selection)
93 {
94     return selection.base().parentAnchoredEquivalent();
95 }
96 
extentPosition(const VisibleSelection & selection)97 static Position extentPosition(const VisibleSelection& selection)
98 {
99     return selection.extent().parentAnchoredEquivalent();
100 }
101 
anchorNode() const102 Node* DOMSelection::anchorNode() const
103 {
104     if (!m_frame)
105         return 0;
106 
107     return shadowAdjustedNode(anchorPosition(visibleSelection()));
108 }
109 
anchorOffset() const110 int DOMSelection::anchorOffset() const
111 {
112     if (!m_frame)
113         return 0;
114 
115     return shadowAdjustedOffset(anchorPosition(visibleSelection()));
116 }
117 
focusNode() const118 Node* DOMSelection::focusNode() const
119 {
120     if (!m_frame)
121         return 0;
122 
123     return shadowAdjustedNode(focusPosition(visibleSelection()));
124 }
125 
focusOffset() const126 int DOMSelection::focusOffset() const
127 {
128     if (!m_frame)
129         return 0;
130 
131     return shadowAdjustedOffset(focusPosition(visibleSelection()));
132 }
133 
baseNode() const134 Node* DOMSelection::baseNode() const
135 {
136     if (!m_frame)
137         return 0;
138 
139     return shadowAdjustedNode(basePosition(visibleSelection()));
140 }
141 
baseOffset() const142 int DOMSelection::baseOffset() const
143 {
144     if (!m_frame)
145         return 0;
146 
147     return shadowAdjustedOffset(basePosition(visibleSelection()));
148 }
149 
extentNode() const150 Node* DOMSelection::extentNode() const
151 {
152     if (!m_frame)
153         return 0;
154 
155     return shadowAdjustedNode(extentPosition(visibleSelection()));
156 }
157 
extentOffset() const158 int DOMSelection::extentOffset() const
159 {
160     if (!m_frame)
161         return 0;
162 
163     return shadowAdjustedOffset(extentPosition(visibleSelection()));
164 }
165 
isCollapsed() const166 bool DOMSelection::isCollapsed() const
167 {
168     if (!m_frame || selectionShadowAncestor(m_frame))
169         return true;
170     return !m_frame->selection().isRange();
171 }
172 
type() const173 String DOMSelection::type() const
174 {
175     if (!m_frame)
176         return String();
177 
178     FrameSelection& selection = m_frame->selection();
179 
180     // This is a WebKit DOM extension, incompatible with an IE extension
181     // IE has this same attribute, but returns "none", "text" and "control"
182     // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx
183     if (selection.isNone())
184         return "None";
185     if (selection.isCaret())
186         return "Caret";
187     return "Range";
188 }
189 
rangeCount() const190 int DOMSelection::rangeCount() const
191 {
192     if (!m_frame)
193         return 0;
194     return m_frame->selection().isNone() ? 0 : 1;
195 }
196 
collapse(Node * node,int offset,ExceptionState & exceptionState)197 void DOMSelection::collapse(Node* node, int offset, ExceptionState& exceptionState)
198 {
199     if (!m_frame)
200         return;
201 
202     if (!node) {
203         m_frame->selection().clear();
204         return;
205     }
206 
207     if (offset < 0) {
208         exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset.");
209         return;
210     }
211 
212     if (!isValidForPosition(node))
213         return;
214     RefPtrWillBeRawPtr<Range> range = Range::create(node->document());
215     range->setStart(node, offset, exceptionState);
216     if (exceptionState.hadException())
217         return;
218     range->setEnd(node, offset, exceptionState);
219     if (exceptionState.hadException())
220         return;
221     m_frame->selection().setSelectedRange(range.get(), DOWNSTREAM, m_frame->selection().isDirectional() ? FrameSelection::Directional : FrameSelection::NonDirectional);
222 }
223 
collapseToEnd(ExceptionState & exceptionState)224 void DOMSelection::collapseToEnd(ExceptionState& exceptionState)
225 {
226     if (!m_frame)
227         return;
228 
229     const VisibleSelection& selection = m_frame->selection().selection();
230 
231     if (selection.isNone()) {
232         exceptionState.throwDOMException(InvalidStateError, "there is no selection.");
233         return;
234     }
235 
236     m_frame->selection().moveTo(VisiblePosition(selection.end(), DOWNSTREAM));
237 }
238 
collapseToStart(ExceptionState & exceptionState)239 void DOMSelection::collapseToStart(ExceptionState& exceptionState)
240 {
241     if (!m_frame)
242         return;
243 
244     const VisibleSelection& selection = m_frame->selection().selection();
245 
246     if (selection.isNone()) {
247         exceptionState.throwDOMException(InvalidStateError, "there is no selection.");
248         return;
249     }
250 
251     m_frame->selection().moveTo(VisiblePosition(selection.start(), DOWNSTREAM));
252 }
253 
empty()254 void DOMSelection::empty()
255 {
256     if (!m_frame)
257         return;
258     m_frame->selection().clear();
259 }
260 
setBaseAndExtent(Node * baseNode,int baseOffset,Node * extentNode,int extentOffset,ExceptionState & exceptionState)261 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionState& exceptionState)
262 {
263     if (!m_frame)
264         return;
265 
266     if (baseOffset < 0) {
267         exceptionState.throwDOMException(IndexSizeError, String::number(baseOffset) + " is not a valid base offset.");
268         return;
269     }
270 
271     if (extentOffset < 0) {
272         exceptionState.throwDOMException(IndexSizeError, String::number(extentOffset) + " is not a valid extent offset.");
273         return;
274     }
275 
276     if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
277         return;
278 
279     // FIXME: Eliminate legacy editing positions
280     VisiblePosition visibleBase = VisiblePosition(createLegacyEditingPosition(baseNode, baseOffset), DOWNSTREAM);
281     VisiblePosition visibleExtent = VisiblePosition(createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM);
282 
283     m_frame->selection().moveTo(visibleBase, visibleExtent);
284 }
285 
modify(const String & alterString,const String & directionString,const String & granularityString)286 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
287 {
288     if (!m_frame)
289         return;
290 
291     FrameSelection::EAlteration alter;
292     if (equalIgnoringCase(alterString, "extend"))
293         alter = FrameSelection::AlterationExtend;
294     else if (equalIgnoringCase(alterString, "move"))
295         alter = FrameSelection::AlterationMove;
296     else
297         return;
298 
299     SelectionDirection direction;
300     if (equalIgnoringCase(directionString, "forward"))
301         direction = DirectionForward;
302     else if (equalIgnoringCase(directionString, "backward"))
303         direction = DirectionBackward;
304     else if (equalIgnoringCase(directionString, "left"))
305         direction = DirectionLeft;
306     else if (equalIgnoringCase(directionString, "right"))
307         direction = DirectionRight;
308     else
309         return;
310 
311     TextGranularity granularity;
312     if (equalIgnoringCase(granularityString, "character"))
313         granularity = CharacterGranularity;
314     else if (equalIgnoringCase(granularityString, "word"))
315         granularity = WordGranularity;
316     else if (equalIgnoringCase(granularityString, "sentence"))
317         granularity = SentenceGranularity;
318     else if (equalIgnoringCase(granularityString, "line"))
319         granularity = LineGranularity;
320     else if (equalIgnoringCase(granularityString, "paragraph"))
321         granularity = ParagraphGranularity;
322     else if (equalIgnoringCase(granularityString, "lineboundary"))
323         granularity = LineBoundary;
324     else if (equalIgnoringCase(granularityString, "sentenceboundary"))
325         granularity = SentenceBoundary;
326     else if (equalIgnoringCase(granularityString, "paragraphboundary"))
327         granularity = ParagraphBoundary;
328     else if (equalIgnoringCase(granularityString, "documentboundary"))
329         granularity = DocumentBoundary;
330     else
331         return;
332 
333     m_frame->selection().modify(alter, direction, granularity);
334 }
335 
extend(Node * node,int offset,ExceptionState & exceptionState)336 void DOMSelection::extend(Node* node, int offset, ExceptionState& exceptionState)
337 {
338     ASSERT(node);
339 
340     if (!m_frame)
341         return;
342 
343     if (offset < 0) {
344         exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset.");
345         return;
346     }
347     if (offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->countChildren())) {
348         exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is larger than the given node's length.");
349         return;
350     }
351 
352     if (!isValidForPosition(node))
353         return;
354 
355     // FIXME: Eliminate legacy editing positions
356     m_frame->selection().setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
357 }
358 
getRangeAt(int index,ExceptionState & exceptionState)359 PassRefPtrWillBeRawPtr<Range> DOMSelection::getRangeAt(int index, ExceptionState& exceptionState)
360 {
361     if (!m_frame)
362         return nullptr;
363 
364     if (index < 0 || index >= rangeCount()) {
365         exceptionState.throwDOMException(IndexSizeError, String::number(index) + " is not a valid index.");
366         return nullptr;
367     }
368 
369     // If you're hitting this, you've added broken multi-range selection support
370     ASSERT(rangeCount() == 1);
371 
372     if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
373         ASSERT(!shadowAncestor->isShadowRoot());
374         ContainerNode* container = shadowAncestor->parentOrShadowHostNode();
375         int offset = shadowAncestor->nodeIndex();
376         return Range::create(shadowAncestor->document(), container, offset, container, offset);
377     }
378 
379     return m_frame->selection().firstRange();
380 }
381 
removeAllRanges()382 void DOMSelection::removeAllRanges()
383 {
384     if (!m_frame)
385         return;
386     m_frame->selection().clear();
387 }
388 
addRange(Range * newRange)389 void DOMSelection::addRange(Range* newRange)
390 {
391     if (!m_frame)
392         return;
393 
394     // FIXME: Should we throw DOMException for error cases below?
395     if (!newRange) {
396         addConsoleError("The given range is null.");
397         return;
398     }
399 
400     if (!newRange->startContainer()) {
401         addConsoleError("The given range has no container. Perhaps 'detach()' has been invoked on it?");
402         return;
403     }
404 
405     FrameSelection& selection = m_frame->selection();
406 
407     if (selection.isNone()) {
408         selection.setSelectedRange(newRange, VP_DEFAULT_AFFINITY);
409         return;
410     }
411 
412     RefPtrWillBeRawPtr<Range> originalRange = selection.firstRange();
413 
414     if (originalRange->startContainer()->document() != newRange->startContainer()->document()) {
415         addConsoleError("The given range does not belong to the current selection's document.");
416         return;
417     }
418     if (originalRange->startContainer()->treeScope() != newRange->startContainer()->treeScope()) {
419         addConsoleError("The given range and the current selection belong to two different document fragments.");
420         return;
421     }
422 
423     if (originalRange->compareBoundaryPoints(Range::START_TO_END, newRange, ASSERT_NO_EXCEPTION) < 0
424         || newRange->compareBoundaryPoints(Range::START_TO_END, originalRange.get(), ASSERT_NO_EXCEPTION) < 0) {
425         addConsoleError("Discontiguous selection is not supported.");
426         return;
427     }
428 
429     // FIXME: "Merge the ranges if they intersect" is Blink-specific behavior; other browsers supporting discontiguous
430     // selection (obviously) keep each Range added and return it in getRangeAt(). But it's unclear if we can really
431     // do the same, since we don't support discontiguous selection. Further discussions at
432     // <https://code.google.com/p/chromium/issues/detail?id=353069>.
433 
434     Range* start = originalRange->compareBoundaryPoints(Range::START_TO_START, newRange, ASSERT_NO_EXCEPTION) < 0 ? originalRange.get() : newRange;
435     Range* end = originalRange->compareBoundaryPoints(Range::END_TO_END, newRange, ASSERT_NO_EXCEPTION) < 0 ? newRange : originalRange.get();
436     RefPtrWillBeRawPtr<Range> merged = Range::create(originalRange->startContainer()->document(), start->startContainer(), start->startOffset(), end->endContainer(), end->endOffset());
437     EAffinity affinity = selection.selection().affinity();
438     selection.setSelectedRange(merged.get(), affinity);
439 }
440 
deleteFromDocument()441 void DOMSelection::deleteFromDocument()
442 {
443     if (!m_frame)
444         return;
445 
446     FrameSelection& selection = m_frame->selection();
447 
448     if (selection.isNone())
449         return;
450 
451     RefPtrWillBeRawPtr<Range> selectedRange = selection.selection().toNormalizedRange();
452     if (!selectedRange)
453         return;
454 
455     selectedRange->deleteContents(ASSERT_NO_EXCEPTION);
456 
457     setBaseAndExtent(selectedRange->startContainer(), selectedRange->startOffset(), selectedRange->startContainer(), selectedRange->startOffset(), ASSERT_NO_EXCEPTION);
458 }
459 
containsNode(const Node * n,bool allowPartial) const460 bool DOMSelection::containsNode(const Node* n, bool allowPartial) const
461 {
462     if (!m_frame)
463         return false;
464 
465     FrameSelection& selection = m_frame->selection();
466 
467     if (!n || m_frame->document() != n->document() || selection.isNone())
468         return false;
469 
470     unsigned nodeIndex = n->nodeIndex();
471     RefPtrWillBeRawPtr<Range> selectedRange = selection.selection().toNormalizedRange();
472 
473     ContainerNode* parentNode = n->parentNode();
474     if (!parentNode)
475         return false;
476 
477     TrackExceptionState exceptionState;
478     bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(), selectedRange->startOffset(), exceptionState) >= 0 && !exceptionState.hadException()
479         && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(), selectedRange->endOffset(), exceptionState) <= 0 && !exceptionState.hadException();
480     if (exceptionState.hadException())
481         return false;
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->countChildren(), exceptionState);
501 }
502 
toString()503 String DOMSelection::toString()
504 {
505     if (!m_frame)
506         return String();
507 
508     Position start, end;
509     if (m_frame->selection().selection().toNormalizedPositions(start, end))
510         return plainText(start, end);
511     return emptyString();
512 }
513 
shadowAdjustedNode(const Position & position) const514 Node* DOMSelection::shadowAdjustedNode(const Position& position) const
515 {
516     if (position.isNull())
517         return 0;
518 
519     Node* containerNode = position.containerNode();
520     Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
521 
522     if (!adjustedNode)
523         return 0;
524 
525     if (containerNode == adjustedNode)
526         return containerNode;
527 
528     ASSERT(!adjustedNode->isShadowRoot());
529     return adjustedNode->parentOrShadowHostNode();
530 }
531 
shadowAdjustedOffset(const Position & position) const532 int DOMSelection::shadowAdjustedOffset(const Position& position) const
533 {
534     if (position.isNull())
535         return 0;
536 
537     Node* containerNode = position.containerNode();
538     Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
539 
540     if (!adjustedNode)
541         return 0;
542 
543     if (containerNode == adjustedNode)
544         return position.computeOffsetInContainerNode();
545 
546     return adjustedNode->nodeIndex();
547 }
548 
isValidForPosition(Node * node) const549 bool DOMSelection::isValidForPosition(Node* node) const
550 {
551     ASSERT(m_frame);
552     if (!node)
553         return true;
554     return node->document() == m_frame->document();
555 }
556 
addConsoleError(const String & message)557 void DOMSelection::addConsoleError(const String& message)
558 {
559     if (m_treeScope)
560         m_treeScope->document().addConsoleMessage(ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, message));
561 }
562 
trace(Visitor * visitor)563 void DOMSelection::trace(Visitor* visitor)
564 {
565     visitor->trace(m_treeScope);
566     DOMWindowProperty::trace(visitor);
567 }
568 
569 } // namespace blink
570