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