1/*
2 * Copyright (C) 2007, 2009 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29
30#include "config.h"
31#include "DOMSelection.h"
32
33#include "ExceptionCode.h"
34#include "Frame.h"
35#include "Node.h"
36#include "PlatformString.h"
37#include "Range.h"
38#include "SelectionController.h"
39#include "TextIterator.h"
40#include "htmlediting.h"
41
42namespace WebCore {
43
44static Node* selectionShadowAncestor(Frame* frame)
45{
46    Node* node = frame->selection()->selection().base().anchorNode();
47    if (!node)
48        return 0;
49    Node* shadowAncestor = node->shadowAncestorNode();
50    if (shadowAncestor == node)
51        return 0;
52    return shadowAncestor;
53}
54
55DOMSelection::DOMSelection(Frame* frame)
56    : m_frame(frame)
57{
58}
59
60Frame* DOMSelection::frame() const
61{
62    return m_frame;
63}
64
65void DOMSelection::disconnectFrame()
66{
67    m_frame = 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    if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
103        return shadowAncestor->parentNodeGuaranteedHostFree();
104    return anchorPosition(visibleSelection()).containerNode();
105}
106
107int DOMSelection::anchorOffset() const
108{
109    if (!m_frame)
110        return 0;
111    if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
112        return shadowAncestor->nodeIndex();
113    return anchorPosition(visibleSelection()).offsetInContainerNode();
114}
115
116Node* DOMSelection::focusNode() const
117{
118    if (!m_frame)
119        return 0;
120    if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
121        return shadowAncestor->parentNodeGuaranteedHostFree();
122    return focusPosition(visibleSelection()).containerNode();
123}
124
125int DOMSelection::focusOffset() const
126{
127    if (!m_frame)
128        return 0;
129    if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
130        return shadowAncestor->nodeIndex();
131    return focusPosition(visibleSelection()).offsetInContainerNode();
132}
133
134Node* DOMSelection::baseNode() const
135{
136    if (!m_frame)
137        return 0;
138    if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
139        return shadowAncestor->parentNodeGuaranteedHostFree();
140    return basePosition(visibleSelection()).containerNode();
141}
142
143int DOMSelection::baseOffset() const
144{
145    if (!m_frame)
146        return 0;
147    if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
148        return shadowAncestor->nodeIndex();
149    return basePosition(visibleSelection()).offsetInContainerNode();
150}
151
152Node* DOMSelection::extentNode() const
153{
154    if (!m_frame)
155        return 0;
156    if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
157        return shadowAncestor->parentNodeGuaranteedHostFree();
158    return extentPosition(visibleSelection()).containerNode();
159}
160
161int DOMSelection::extentOffset() const
162{
163    if (!m_frame)
164        return 0;
165    if (Node* shadowAncestor = selectionShadowAncestor(m_frame))
166        return shadowAncestor->nodeIndex();
167    return extentPosition(visibleSelection()).offsetInContainerNode();
168}
169
170bool DOMSelection::isCollapsed() const
171{
172    if (!m_frame || selectionShadowAncestor(m_frame))
173        return true;
174    return !m_frame->selection()->isRange();
175}
176
177String DOMSelection::type() const
178{
179    if (!m_frame)
180        return String();
181
182    SelectionController* selection = m_frame->selection();
183
184    // This is a WebKit DOM extension, incompatible with an IE extension
185    // IE has this same attribute, but returns "none", "text" and "control"
186    // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx
187    if (selection->isNone())
188        return "None";
189    if (selection->isCaret())
190        return "Caret";
191    return "Range";
192}
193
194int DOMSelection::rangeCount() const
195{
196    if (!m_frame)
197        return 0;
198    return m_frame->selection()->isNone() ? 0 : 1;
199}
200
201void DOMSelection::collapse(Node* node, int offset, ExceptionCode& ec)
202{
203    if (!m_frame)
204        return;
205
206    if (offset < 0) {
207        ec = INDEX_SIZE_ERR;
208        return;
209    }
210
211    if (!isValidForPosition(node))
212        return;
213
214    // FIXME: Eliminate legacy editing positions
215    m_frame->selection()->moveTo(VisiblePosition(Position(node, offset), DOWNSTREAM));
216}
217
218void DOMSelection::collapseToEnd(ExceptionCode& ec)
219{
220    if (!m_frame)
221        return;
222
223    const VisibleSelection& selection = m_frame->selection()->selection();
224
225    if (selection.isNone()) {
226        ec = INVALID_STATE_ERR;
227        return;
228    }
229
230    m_frame->selection()->moveTo(VisiblePosition(selection.end(), DOWNSTREAM));
231}
232
233void DOMSelection::collapseToStart(ExceptionCode& ec)
234{
235    if (!m_frame)
236        return;
237
238    const VisibleSelection& selection = m_frame->selection()->selection();
239
240    if (selection.isNone()) {
241        ec = INVALID_STATE_ERR;
242        return;
243    }
244
245    m_frame->selection()->moveTo(VisiblePosition(selection.start(), DOWNSTREAM));
246}
247
248void DOMSelection::empty()
249{
250    if (!m_frame)
251        return;
252    m_frame->selection()->clear();
253}
254
255void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionCode& ec)
256{
257    if (!m_frame)
258        return;
259
260    if (baseOffset < 0 || extentOffset < 0) {
261        ec = INDEX_SIZE_ERR;
262        return;
263    }
264
265    if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
266        return;
267
268    // FIXME: Eliminate legacy editing positions
269    VisiblePosition visibleBase = VisiblePosition(Position(baseNode, baseOffset), DOWNSTREAM);
270    VisiblePosition visibleExtent = VisiblePosition(Position(extentNode, extentOffset), DOWNSTREAM);
271
272    m_frame->selection()->moveTo(visibleBase, visibleExtent);
273}
274
275void DOMSelection::setPosition(Node* node, int offset, ExceptionCode& ec)
276{
277    if (!m_frame)
278        return;
279    if (offset < 0) {
280        ec = INDEX_SIZE_ERR;
281        return;
282    }
283
284    if (!isValidForPosition(node))
285        return;
286
287    // FIXME: Eliminate legacy editing positions
288    m_frame->selection()->moveTo(VisiblePosition(Position(node, offset), DOWNSTREAM));
289}
290
291void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
292{
293    if (!m_frame)
294        return;
295
296    SelectionController::EAlteration alter;
297    if (equalIgnoringCase(alterString, "extend"))
298        alter = SelectionController::AlterationExtend;
299    else if (equalIgnoringCase(alterString, "move"))
300        alter = SelectionController::AlterationMove;
301    else
302        return;
303
304    SelectionDirection direction;
305    if (equalIgnoringCase(directionString, "forward"))
306        direction = DirectionForward;
307    else if (equalIgnoringCase(directionString, "backward"))
308        direction = DirectionBackward;
309    else if (equalIgnoringCase(directionString, "left"))
310        direction = DirectionLeft;
311    else if (equalIgnoringCase(directionString, "right"))
312        direction = DirectionRight;
313    else
314        return;
315
316    TextGranularity granularity;
317    if (equalIgnoringCase(granularityString, "character"))
318        granularity = CharacterGranularity;
319    else if (equalIgnoringCase(granularityString, "word"))
320        granularity = WordGranularity;
321    else if (equalIgnoringCase(granularityString, "sentence"))
322        granularity = SentenceGranularity;
323    else if (equalIgnoringCase(granularityString, "line"))
324        granularity = LineGranularity;
325    else if (equalIgnoringCase(granularityString, "paragraph"))
326        granularity = ParagraphGranularity;
327    else if (equalIgnoringCase(granularityString, "lineboundary"))
328        granularity = LineBoundary;
329    else if (equalIgnoringCase(granularityString, "sentenceboundary"))
330        granularity = SentenceBoundary;
331    else if (equalIgnoringCase(granularityString, "paragraphboundary"))
332        granularity = ParagraphBoundary;
333    else if (equalIgnoringCase(granularityString, "documentboundary"))
334        granularity = DocumentBoundary;
335    else if (equalIgnoringCase(granularityString, "-webkit-visual-word"))
336        granularity = WebKitVisualWordGranularity;
337    else
338        return;
339
340    m_frame->selection()->modify(alter, direction, granularity, false);
341}
342
343void DOMSelection::extend(Node* node, int offset, ExceptionCode& ec)
344{
345    if (!m_frame)
346        return;
347
348    if (!node) {
349        ec = TYPE_MISMATCH_ERR;
350        return;
351    }
352
353    if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) {
354        ec = INDEX_SIZE_ERR;
355        return;
356    }
357
358    if (!isValidForPosition(node))
359        return;
360
361    // FIXME: Eliminate legacy editing positions
362    m_frame->selection()->setExtent(VisiblePosition(Position(node, offset), DOWNSTREAM));
363}
364
365PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionCode& ec)
366{
367    if (!m_frame)
368        return 0;
369
370    if (index < 0 || index >= rangeCount()) {
371        ec = INDEX_SIZE_ERR;
372        return 0;
373    }
374
375    // If you're hitting this, you've added broken multi-range selection support
376    ASSERT(rangeCount() == 1);
377
378    if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
379        ContainerNode* container = shadowAncestor->parentNodeGuaranteedHostFree();
380        int offset = shadowAncestor->nodeIndex();
381        return Range::create(shadowAncestor->document(), container, offset, container, offset);
382    }
383
384    const VisibleSelection& selection = m_frame->selection()->selection();
385    return selection.firstRange();
386}
387
388void DOMSelection::removeAllRanges()
389{
390    if (!m_frame)
391        return;
392    m_frame->selection()->clear();
393}
394
395void DOMSelection::addRange(Range* r)
396{
397    if (!m_frame)
398        return;
399    if (!r)
400        return;
401
402    SelectionController* selection = m_frame->selection();
403
404    if (selection->isNone()) {
405        selection->setSelection(VisibleSelection(r));
406        return;
407    }
408
409    RefPtr<Range> range = selection->selection().toNormalizedRange();
410    ExceptionCode ec = 0;
411    if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), ec) == -1) {
412        // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
413        if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), ec) > -1) {
414            if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1)
415                // The original range and r intersect.
416                selection->setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM));
417            else
418                // r contains the original range.
419                selection->setSelection(VisibleSelection(r));
420        }
421    } else {
422        // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
423        if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), ec) < 1) {
424            if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1)
425                // The original range contains r.
426                selection->setSelection(VisibleSelection(range.get()));
427            else
428                // The original range and r intersect.
429                selection->setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM));
430        }
431    }
432}
433
434void DOMSelection::deleteFromDocument()
435{
436    if (!m_frame)
437        return;
438
439    SelectionController* selection = m_frame->selection();
440
441    if (selection->isNone())
442        return;
443
444    if (isCollapsed())
445        selection->modify(SelectionController::AlterationExtend, DirectionBackward, CharacterGranularity);
446
447    RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
448    if (!selectedRange)
449        return;
450
451    ExceptionCode ec = 0;
452    selectedRange->deleteContents(ec);
453    ASSERT(!ec);
454
455    setBaseAndExtent(selectedRange->startContainer(ec), selectedRange->startOffset(ec), selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec);
456    ASSERT(!ec);
457}
458
459bool DOMSelection::containsNode(const Node* n, bool allowPartial) const
460{
461    if (!m_frame)
462        return false;
463
464    SelectionController* selection = m_frame->selection();
465
466    if (!n || m_frame->document() != n->document() || selection->isNone())
467        return false;
468
469    ContainerNode* parentNode = n->parentNode();
470    unsigned nodeIndex = n->nodeIndex();
471    RefPtr<Range> selectedRange = selection->selection().toNormalizedRange();
472
473    if (!parentNode)
474        return false;
475
476    ExceptionCode ec = 0;
477    bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec) >= 0 && !ec
478        && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(ec), selectedRange->endOffset(ec), ec) <= 0 && !ec;
479    ASSERT(!ec);
480    if (nodeFullySelected)
481        return true;
482
483    bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(ec), selectedRange->endOffset(ec), ec) > 0 && !ec)
484        || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec) < 0 && !ec);
485    ASSERT(!ec);
486    if (nodeFullyUnselected)
487        return false;
488
489    return allowPartial || n->isTextNode();
490}
491
492void DOMSelection::selectAllChildren(Node* n, ExceptionCode& ec)
493{
494    if (!n)
495        return;
496
497    // This doesn't (and shouldn't) select text node characters.
498    setBaseAndExtent(n, 0, n, n->childNodeCount(), ec);
499}
500
501String DOMSelection::toString()
502{
503    if (!m_frame)
504        return String();
505
506    return plainText(m_frame->selection()->selection().toNormalizedRange().get());
507}
508
509bool DOMSelection::isValidForPosition(Node* node) const
510{
511    ASSERT(m_frame);
512    if (!node)
513        return true;
514    return node->document() == m_frame->document();
515}
516
517} // namespace WebCore
518