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/page/Frame.h"
45#include "wtf/text/WTFString.h"
46
47namespace WebCore {
48
49static 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
61DOMSelection::DOMSelection(const TreeScope* treeScope)
62    : DOMWindowProperty(treeScope->rootNode()->document()->frame())
63    , m_treeScope(treeScope)
64{
65    ScriptWrappable::init(this);
66}
67
68void DOMSelection::clearTreeScope()
69{
70    m_treeScope = 0;
71}
72
73const VisibleSelection& DOMSelection::visibleSelection() const
74{
75    ASSERT(m_frame);
76    return m_frame->selection()->selection();
77}
78
79static Position anchorPosition(const VisibleSelection& selection)
80{
81    Position anchor = selection.isBaseFirst() ? selection.start() : selection.end();
82    return anchor.parentAnchoredEquivalent();
83}
84
85static Position focusPosition(const VisibleSelection& selection)
86{
87    Position focus = selection.isBaseFirst() ? selection.end() : selection.start();
88    return focus.parentAnchoredEquivalent();
89}
90
91static Position basePosition(const VisibleSelection& selection)
92{
93    return selection.base().parentAnchoredEquivalent();
94}
95
96static Position extentPosition(const VisibleSelection& selection)
97{
98    return selection.extent().parentAnchoredEquivalent();
99}
100
101Node* DOMSelection::anchorNode() const
102{
103    if (!m_frame)
104        return 0;
105
106    return shadowAdjustedNode(anchorPosition(visibleSelection()));
107}
108
109int DOMSelection::anchorOffset() const
110{
111    if (!m_frame)
112        return 0;
113
114    return shadowAdjustedOffset(anchorPosition(visibleSelection()));
115}
116
117Node* DOMSelection::focusNode() const
118{
119    if (!m_frame)
120        return 0;
121
122    return shadowAdjustedNode(focusPosition(visibleSelection()));
123}
124
125int DOMSelection::focusOffset() const
126{
127    if (!m_frame)
128        return 0;
129
130    return shadowAdjustedOffset(focusPosition(visibleSelection()));
131}
132
133Node* DOMSelection::baseNode() const
134{
135    if (!m_frame)
136        return 0;
137
138    return shadowAdjustedNode(basePosition(visibleSelection()));
139}
140
141int DOMSelection::baseOffset() const
142{
143    if (!m_frame)
144        return 0;
145
146    return shadowAdjustedOffset(basePosition(visibleSelection()));
147}
148
149Node* DOMSelection::extentNode() const
150{
151    if (!m_frame)
152        return 0;
153
154    return shadowAdjustedNode(extentPosition(visibleSelection()));
155}
156
157int DOMSelection::extentOffset() const
158{
159    if (!m_frame)
160        return 0;
161
162    return shadowAdjustedOffset(extentPosition(visibleSelection()));
163}
164
165bool DOMSelection::isCollapsed() const
166{
167    if (!m_frame || selectionShadowAncestor(m_frame))
168        return true;
169    return !m_frame->selection()->isRange();
170}
171
172String 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
189int DOMSelection::rangeCount() const
190{
191    if (!m_frame)
192        return 0;
193    return m_frame->selection()->isNone() ? 0 : 1;
194}
195
196void DOMSelection::collapse(Node* node, int offset, ExceptionState& es)
197{
198    if (!m_frame)
199        return;
200
201    if (offset < 0) {
202        es.throwDOMException(IndexSizeError);
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
213void DOMSelection::collapseToEnd(ExceptionState& es)
214{
215    if (!m_frame)
216        return;
217
218    const VisibleSelection& selection = m_frame->selection()->selection();
219
220    if (selection.isNone()) {
221        es.throwDOMException(InvalidStateError);
222        return;
223    }
224
225    m_frame->selection()->moveTo(VisiblePosition(selection.end(), DOWNSTREAM));
226}
227
228void DOMSelection::collapseToStart(ExceptionState& es)
229{
230    if (!m_frame)
231        return;
232
233    const VisibleSelection& selection = m_frame->selection()->selection();
234
235    if (selection.isNone()) {
236        es.throwDOMException(InvalidStateError);
237        return;
238    }
239
240    m_frame->selection()->moveTo(VisiblePosition(selection.start(), DOWNSTREAM));
241}
242
243void DOMSelection::empty()
244{
245    if (!m_frame)
246        return;
247    m_frame->selection()->clear();
248}
249
250void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionState& es)
251{
252    if (!m_frame)
253        return;
254
255    if (baseOffset < 0 || extentOffset < 0) {
256        es.throwDOMException(IndexSizeError);
257        return;
258    }
259
260    if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
261        return;
262
263    // FIXME: Eliminate legacy editing positions
264    VisiblePosition visibleBase = VisiblePosition(createLegacyEditingPosition(baseNode, baseOffset), DOWNSTREAM);
265    VisiblePosition visibleExtent = VisiblePosition(createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM);
266
267    m_frame->selection()->moveTo(visibleBase, visibleExtent);
268}
269
270void DOMSelection::setPosition(Node* node, int offset, ExceptionState& es)
271{
272    if (!m_frame)
273        return;
274    if (offset < 0) {
275        es.throwDOMException(IndexSizeError);
276        return;
277    }
278
279    if (!isValidForPosition(node))
280        return;
281
282    // FIXME: Eliminate legacy editing positions
283    m_frame->selection()->moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
284}
285
286void 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
336void DOMSelection::extend(Node* node, int offset, ExceptionState& es)
337{
338    if (!m_frame)
339        return;
340
341    if (!node) {
342        es.throwDOMException(TypeMismatchError);
343        return;
344    }
345
346    if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) {
347        es.throwDOMException(IndexSizeError);
348        return;
349    }
350
351    if (!isValidForPosition(node))
352        return;
353
354    // FIXME: Eliminate legacy editing positions
355    m_frame->selection()->setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
356}
357
358PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionState& es)
359{
360    if (!m_frame)
361        return 0;
362
363    if (index < 0 || index >= rangeCount()) {
364        es.throwDOMException(IndexSizeError);
365        return 0;
366    }
367
368    // If you're hitting this, you've added broken multi-range selection support
369    ASSERT(rangeCount() == 1);
370
371    if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
372        ContainerNode* container = shadowAncestor->parentNodeGuaranteedHostFree();
373        int offset = shadowAncestor->nodeIndex();
374        return Range::create(shadowAncestor->document(), container, offset, container, offset);
375    }
376
377    const VisibleSelection& selection = m_frame->selection()->selection();
378    return selection.firstRange();
379}
380
381void DOMSelection::removeAllRanges()
382{
383    if (!m_frame)
384        return;
385    m_frame->selection()->clear();
386}
387
388void DOMSelection::addRange(Range* r)
389{
390    if (!m_frame)
391        return;
392    if (!r)
393        return;
394
395    FrameSelection* selection = m_frame->selection();
396
397    if (selection->isNone()) {
398        selection->setSelection(VisibleSelection(r));
399        return;
400    }
401
402    RefPtr<Range> range = selection->selection().toNormalizedRange();
403    if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), IGNORE_EXCEPTION) == -1) {
404        // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
405        if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), IGNORE_EXCEPTION) > -1) {
406            if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), IGNORE_EXCEPTION) == -1)
407                // The original range and r intersect.
408                selection->setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM));
409            else
410                // r contains the original range.
411                selection->setSelection(VisibleSelection(r));
412        }
413    } else {
414        // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
415        TrackExceptionState es;
416        if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), es) < 1 && !es.hadException()) {
417            if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), IGNORE_EXCEPTION) == -1)
418                // The original range contains r.
419                selection->setSelection(VisibleSelection(range.get()));
420            else
421                // The original range and r intersect.
422                selection->setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM));
423        }
424    }
425}
426
427void DOMSelection::deleteFromDocument()
428{
429    if (!m_frame)
430        return;
431
432    FrameSelection* selection = m_frame->selection();
433
434    if (selection->isNone())
435        return;
436
437    if (isCollapsed())
438        selection->modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity);
439
440    RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
441    if (!selectedRange)
442        return;
443
444    selectedRange->deleteContents(ASSERT_NO_EXCEPTION);
445
446    setBaseAndExtent(selectedRange->startContainer(ASSERT_NO_EXCEPTION), selectedRange->startOffset(), selectedRange->startContainer(), selectedRange->startOffset(), ASSERT_NO_EXCEPTION);
447}
448
449bool DOMSelection::containsNode(const Node* n, bool allowPartial) const
450{
451    if (!m_frame)
452        return false;
453
454    FrameSelection* selection = m_frame->selection();
455
456    if (!n || m_frame->document() != n->document() || selection->isNone())
457        return false;
458
459    unsigned nodeIndex = n->nodeIndex();
460    RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
461
462    ContainerNode* parentNode = n->parentNode();
463    if (!parentNode)
464        return false;
465
466    TrackExceptionState es;
467    bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(), selectedRange->startOffset(), es) >= 0 && !es.hadException()
468        && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(), selectedRange->endOffset(), es) <= 0 && !es.hadException();
469    ASSERT(!es.hadException());
470    if (nodeFullySelected)
471        return true;
472
473    bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(), selectedRange->endOffset(), es) > 0 && !es.hadException())
474        || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(), selectedRange->startOffset(), es) < 0 && !es.hadException());
475    ASSERT(!es.hadException());
476    if (nodeFullyUnselected)
477        return false;
478
479    return allowPartial || n->isTextNode();
480}
481
482void DOMSelection::selectAllChildren(Node* n, ExceptionState& es)
483{
484    if (!n)
485        return;
486
487    // This doesn't (and shouldn't) select text node characters.
488    setBaseAndExtent(n, 0, n, n->childNodeCount(), es);
489}
490
491String DOMSelection::toString()
492{
493    if (!m_frame)
494        return String();
495
496    return plainText(m_frame->selection()->selection().toNormalizedRange().get());
497}
498
499Node* DOMSelection::shadowAdjustedNode(const Position& position) const
500{
501    if (position.isNull())
502        return 0;
503
504    Node* containerNode = position.containerNode();
505    Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
506
507    if (!adjustedNode)
508        return 0;
509
510    if (containerNode == adjustedNode)
511        return containerNode;
512
513    return adjustedNode->parentNodeGuaranteedHostFree();
514}
515
516int DOMSelection::shadowAdjustedOffset(const Position& position) const
517{
518    if (position.isNull())
519        return 0;
520
521    Node* containerNode = position.containerNode();
522    Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
523
524    if (!adjustedNode)
525        return 0;
526
527    if (containerNode == adjustedNode)
528        return position.computeOffsetInContainerNode();
529
530    return adjustedNode->nodeIndex();
531}
532
533bool DOMSelection::isValidForPosition(Node* node) const
534{
535    ASSERT(m_frame);
536    if (!node)
537        return true;
538    return node->document() == m_frame->document();
539}
540
541} // namespace WebCore
542