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