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 "DOMSelection.h"
33
34#include "Document.h"
35#include "ExceptionCode.h"
36#include "Frame.h"
37#include "FrameSelection.h"
38#include "Node.h"
39#include "Range.h"
40#include "TextIterator.h"
41#include "TreeScope.h"
42#include "htmlediting.h"
43#include <wtf/text/WTFString.h>
44
45namespace WebCore {
46
47static Node* selectionShadowAncestor(Frame* frame)
48{
49    Node* node = frame->selection()->selection().base().anchorNode();
50    if (!node)
51        return 0;
52
53    if (!node->isInShadowTree())
54        return 0;
55
56    return frame->document()->ancestorInThisScope(node);
57}
58
59DOMSelection::DOMSelection(const TreeScope* treeScope)
60    : DOMWindowProperty(treeScope->rootNode()->document()->frame())
61    , m_treeScope(treeScope)
62{
63}
64
65void DOMSelection::clearTreeScope()
66{
67    m_treeScope = 0;
68}
69
70const VisibleSelection& DOMSelection::visibleSelection() const
71{
72    ASSERT(m_frame);
73    return m_frame->selection()->selection();
74}
75
76static Position anchorPosition(const VisibleSelection& selection)
77{
78    Position anchor = selection.isBaseFirst() ? selection.start() : selection.end();
79    return anchor.parentAnchoredEquivalent();
80}
81
82static Position focusPosition(const VisibleSelection& selection)
83{
84    Position focus = selection.isBaseFirst() ? selection.end() : selection.start();
85    return focus.parentAnchoredEquivalent();
86}
87
88static Position basePosition(const VisibleSelection& selection)
89{
90    return selection.base().parentAnchoredEquivalent();
91}
92
93static Position extentPosition(const VisibleSelection& selection)
94{
95    return selection.extent().parentAnchoredEquivalent();
96}
97
98Node* DOMSelection::anchorNode() const
99{
100    if (!m_frame)
101        return 0;
102
103    return shadowAdjustedNode(anchorPosition(visibleSelection()));
104}
105
106int DOMSelection::anchorOffset() const
107{
108    if (!m_frame)
109        return 0;
110
111    return shadowAdjustedOffset(anchorPosition(visibleSelection()));
112}
113
114Node* DOMSelection::focusNode() const
115{
116    if (!m_frame)
117        return 0;
118
119    return shadowAdjustedNode(focusPosition(visibleSelection()));
120}
121
122int DOMSelection::focusOffset() const
123{
124    if (!m_frame)
125        return 0;
126
127    return shadowAdjustedOffset(focusPosition(visibleSelection()));
128}
129
130Node* DOMSelection::baseNode() const
131{
132    if (!m_frame)
133        return 0;
134
135    return shadowAdjustedNode(basePosition(visibleSelection()));
136}
137
138int DOMSelection::baseOffset() const
139{
140    if (!m_frame)
141        return 0;
142
143    return shadowAdjustedOffset(basePosition(visibleSelection()));
144}
145
146Node* DOMSelection::extentNode() const
147{
148    if (!m_frame)
149        return 0;
150
151    return shadowAdjustedNode(extentPosition(visibleSelection()));
152}
153
154int DOMSelection::extentOffset() const
155{
156    if (!m_frame)
157        return 0;
158
159    return shadowAdjustedOffset(extentPosition(visibleSelection()));
160}
161
162bool DOMSelection::isCollapsed() const
163{
164    if (!m_frame || selectionShadowAncestor(m_frame))
165        return true;
166    return !m_frame->selection()->isRange();
167}
168
169String DOMSelection::type() const
170{
171    if (!m_frame)
172        return String();
173
174    FrameSelection* selection = m_frame->selection();
175
176    // This is a WebKit DOM extension, incompatible with an IE extension
177    // IE has this same attribute, but returns "none", "text" and "control"
178    // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx
179    if (selection->isNone())
180        return "None";
181    if (selection->isCaret())
182        return "Caret";
183    return "Range";
184}
185
186int DOMSelection::rangeCount() const
187{
188    if (!m_frame)
189        return 0;
190    return m_frame->selection()->isNone() ? 0 : 1;
191}
192
193void DOMSelection::collapse(Node* node, int offset, ExceptionCode& ec)
194{
195    if (!m_frame)
196        return;
197
198    if (offset < 0) {
199        ec = INDEX_SIZE_ERR;
200        return;
201    }
202
203    if (!isValidForPosition(node))
204        return;
205
206    // FIXME: Eliminate legacy editing positions
207    m_frame->selection()->moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
208}
209
210void DOMSelection::collapseToEnd(ExceptionCode& ec)
211{
212    if (!m_frame)
213        return;
214
215    const VisibleSelection& selection = m_frame->selection()->selection();
216
217    if (selection.isNone()) {
218        ec = INVALID_STATE_ERR;
219        return;
220    }
221
222    m_frame->selection()->moveTo(VisiblePosition(selection.end(), DOWNSTREAM));
223}
224
225void DOMSelection::collapseToStart(ExceptionCode& ec)
226{
227    if (!m_frame)
228        return;
229
230    const VisibleSelection& selection = m_frame->selection()->selection();
231
232    if (selection.isNone()) {
233        ec = INVALID_STATE_ERR;
234        return;
235    }
236
237    m_frame->selection()->moveTo(VisiblePosition(selection.start(), DOWNSTREAM));
238}
239
240void DOMSelection::empty()
241{
242    if (!m_frame)
243        return;
244    m_frame->selection()->clear();
245}
246
247void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionCode& ec)
248{
249    if (!m_frame)
250        return;
251
252    if (baseOffset < 0 || extentOffset < 0) {
253        ec = INDEX_SIZE_ERR;
254        return;
255    }
256
257    if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
258        return;
259
260    // FIXME: Eliminate legacy editing positions
261    VisiblePosition visibleBase = VisiblePosition(createLegacyEditingPosition(baseNode, baseOffset), DOWNSTREAM);
262    VisiblePosition visibleExtent = VisiblePosition(createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM);
263
264    m_frame->selection()->moveTo(visibleBase, visibleExtent);
265}
266
267void DOMSelection::setPosition(Node* node, int offset, ExceptionCode& ec)
268{
269    if (!m_frame)
270        return;
271    if (offset < 0) {
272        ec = INDEX_SIZE_ERR;
273        return;
274    }
275
276    if (!isValidForPosition(node))
277        return;
278
279    // FIXME: Eliminate legacy editing positions
280    m_frame->selection()->moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
281}
282
283void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
284{
285    if (!m_frame)
286        return;
287
288    FrameSelection::EAlteration alter;
289    if (equalIgnoringCase(alterString, "extend"))
290        alter = FrameSelection::AlterationExtend;
291    else if (equalIgnoringCase(alterString, "move"))
292        alter = FrameSelection::AlterationMove;
293    else
294        return;
295
296    SelectionDirection direction;
297    if (equalIgnoringCase(directionString, "forward"))
298        direction = DirectionForward;
299    else if (equalIgnoringCase(directionString, "backward"))
300        direction = DirectionBackward;
301    else if (equalIgnoringCase(directionString, "left"))
302        direction = DirectionLeft;
303    else if (equalIgnoringCase(directionString, "right"))
304        direction = DirectionRight;
305    else
306        return;
307
308    TextGranularity granularity;
309    if (equalIgnoringCase(granularityString, "character"))
310        granularity = CharacterGranularity;
311    else if (equalIgnoringCase(granularityString, "word"))
312        granularity = WordGranularity;
313    else if (equalIgnoringCase(granularityString, "sentence"))
314        granularity = SentenceGranularity;
315    else if (equalIgnoringCase(granularityString, "line"))
316        granularity = LineGranularity;
317    else if (equalIgnoringCase(granularityString, "paragraph"))
318        granularity = ParagraphGranularity;
319    else if (equalIgnoringCase(granularityString, "lineboundary"))
320        granularity = LineBoundary;
321    else if (equalIgnoringCase(granularityString, "sentenceboundary"))
322        granularity = SentenceBoundary;
323    else if (equalIgnoringCase(granularityString, "paragraphboundary"))
324        granularity = ParagraphBoundary;
325    else if (equalIgnoringCase(granularityString, "documentboundary"))
326        granularity = DocumentBoundary;
327    else
328        return;
329
330    m_frame->selection()->modify(alter, direction, granularity);
331}
332
333void DOMSelection::extend(Node* node, int offset, ExceptionCode& ec)
334{
335    if (!m_frame)
336        return;
337
338    if (!node) {
339        ec = TYPE_MISMATCH_ERR;
340        return;
341    }
342
343    if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) {
344        ec = INDEX_SIZE_ERR;
345        return;
346    }
347
348    if (!isValidForPosition(node))
349        return;
350
351    // FIXME: Eliminate legacy editing positions
352    m_frame->selection()->setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
353}
354
355PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionCode& ec)
356{
357    if (!m_frame)
358        return 0;
359
360    if (index < 0 || index >= rangeCount()) {
361        ec = INDEX_SIZE_ERR;
362        return 0;
363    }
364
365    // If you're hitting this, you've added broken multi-range selection support
366    ASSERT(rangeCount() == 1);
367
368    if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
369        ContainerNode* container = shadowAncestor->parentNodeGuaranteedHostFree();
370        int offset = shadowAncestor->nodeIndex();
371        return Range::create(shadowAncestor->document(), container, offset, container, offset);
372    }
373
374    const VisibleSelection& selection = m_frame->selection()->selection();
375    return selection.firstRange();
376}
377
378void DOMSelection::removeAllRanges()
379{
380    if (!m_frame)
381        return;
382    m_frame->selection()->clear();
383}
384
385void DOMSelection::addRange(Range* r)
386{
387    if (!m_frame)
388        return;
389    if (!r)
390        return;
391
392    FrameSelection* selection = m_frame->selection();
393
394    if (selection->isNone()) {
395        selection->setSelection(VisibleSelection(r));
396        return;
397    }
398
399    RefPtr<Range> range = selection->selection().toNormalizedRange();
400    if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), IGNORE_EXCEPTION) == -1) {
401        // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
402        if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), IGNORE_EXCEPTION) > -1) {
403            if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), IGNORE_EXCEPTION) == -1)
404                // The original range and r intersect.
405                selection->setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM));
406            else
407                // r contains the original range.
408                selection->setSelection(VisibleSelection(r));
409        }
410    } else {
411        // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
412        ExceptionCode ec = 0;
413        if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), ec) < 1 && !ec) {
414            if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), IGNORE_EXCEPTION) == -1)
415                // The original range contains r.
416                selection->setSelection(VisibleSelection(range.get()));
417            else
418                // The original range and r intersect.
419                selection->setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM));
420        }
421    }
422}
423
424void DOMSelection::deleteFromDocument()
425{
426    if (!m_frame)
427        return;
428
429    FrameSelection* selection = m_frame->selection();
430
431    if (selection->isNone())
432        return;
433
434    if (isCollapsed())
435        selection->modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity);
436
437    RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
438    if (!selectedRange)
439        return;
440
441    selectedRange->deleteContents(ASSERT_NO_EXCEPTION);
442
443    setBaseAndExtent(selectedRange->startContainer(ASSERT_NO_EXCEPTION), selectedRange->startOffset(), selectedRange->startContainer(), selectedRange->startOffset(), ASSERT_NO_EXCEPTION);
444}
445
446bool DOMSelection::containsNode(Node* n, bool allowPartial) const
447{
448    if (!m_frame)
449        return false;
450
451    FrameSelection* selection = m_frame->selection();
452
453    if (!n || m_frame->document() != n->document() || selection->isNone())
454        return false;
455
456    RefPtr<Node> node = n;
457    RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
458
459    ContainerNode* parentNode = node->parentNode();
460    if (!parentNode || !parentNode->inDocument())
461        return false;
462    unsigned nodeIndex = node->nodeIndex();
463
464    ExceptionCode ec = 0;
465    bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(), selectedRange->startOffset(), ec) >= 0 && !ec
466        && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(), selectedRange->endOffset(), ec) <= 0 && !ec;
467    ASSERT(!ec);
468    if (nodeFullySelected)
469        return true;
470
471    bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(), selectedRange->endOffset(), ec) > 0 && !ec)
472        || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(), selectedRange->startOffset(), ec) < 0 && !ec);
473    ASSERT(!ec);
474    if (nodeFullyUnselected)
475        return false;
476
477    return allowPartial || node->isTextNode();
478}
479
480void DOMSelection::selectAllChildren(Node* n, ExceptionCode& ec)
481{
482    if (!n)
483        return;
484
485    // This doesn't (and shouldn't) select text node characters.
486    setBaseAndExtent(n, 0, n, n->childNodeCount(), ec);
487}
488
489String DOMSelection::toString()
490{
491    if (!m_frame)
492        return String();
493
494    return plainText(m_frame->selection()->selection().toNormalizedRange().get());
495}
496
497Node* DOMSelection::shadowAdjustedNode(const Position& position) const
498{
499    if (position.isNull())
500        return 0;
501
502    Node* containerNode = position.containerNode();
503    Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
504
505    if (!adjustedNode)
506        return 0;
507
508    if (containerNode == adjustedNode)
509        return containerNode;
510
511    return adjustedNode->parentNodeGuaranteedHostFree();
512}
513
514int DOMSelection::shadowAdjustedOffset(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 position.computeOffsetInContainerNode();
527
528    return adjustedNode->nodeIndex();
529}
530
531bool DOMSelection::isValidForPosition(Node* node) const
532{
533    ASSERT(m_frame);
534    if (!node)
535        return true;
536    return node->document() == m_frame->document();
537}
538
539} // namespace WebCore
540