1/*
2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All rights reserved.
3 * Copyright (C) 2005 Alexey Proskuryakov.
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 * 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 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "core/editing/PlainTextRange.h"
29
30#include "core/dom/ContainerNode.h"
31#include "core/dom/Document.h"
32#include "core/dom/Range.h"
33#include "core/editing/TextIterator.h"
34#include "core/editing/VisiblePosition.h"
35
36namespace blink {
37
38PlainTextRange::PlainTextRange()
39    : m_start(kNotFound)
40    , m_end(kNotFound)
41{
42}
43
44PlainTextRange::PlainTextRange(int location)
45    : m_start(location)
46    , m_end(location)
47{
48    ASSERT(location >= 0);
49}
50
51PlainTextRange::PlainTextRange(int start, int end)
52    : m_start(start)
53    , m_end(end)
54{
55    ASSERT(start >= 0);
56    ASSERT(end >= 0);
57    ASSERT(start <= end);
58}
59
60PassRefPtrWillBeRawPtr<Range> PlainTextRange::createRange(const ContainerNode& scope) const
61{
62    return createRangeFor(scope, ForGeneric);
63}
64
65PassRefPtrWillBeRawPtr<Range> PlainTextRange::createRangeForSelection(const ContainerNode& scope) const
66{
67    return createRangeFor(scope, ForSelection);
68}
69
70PassRefPtrWillBeRawPtr<Range> PlainTextRange::createRangeFor(const ContainerNode& scope, GetRangeFor getRangeFor) const
71{
72    ASSERT(isNotNull());
73
74    RefPtrWillBeRawPtr<Range> resultRange = scope.document().createRange();
75
76    size_t docTextPosition = 0;
77    bool startRangeFound = false;
78
79    Position textRunStartPosition;
80    Position textRunEndPosition;
81
82    TextIteratorBehaviorFlags behaviorFlags = TextIteratorEmitsObjectReplacementCharacter;
83    if (getRangeFor == ForSelection)
84        behaviorFlags |= TextIteratorEmitsCharactersBetweenAllVisiblePositions;
85    TextIterator it(rangeOfContents(const_cast<ContainerNode*>(&scope)).get(), behaviorFlags);
86
87    // FIXME: the atEnd() check shouldn't be necessary, workaround for <http://bugs.webkit.org/show_bug.cgi?id=6289>.
88    if (!start() && !length() && it.atEnd()) {
89        resultRange->setStart(it.startContainer(), 0, ASSERT_NO_EXCEPTION);
90        resultRange->setEnd(it.startContainer(), 0, ASSERT_NO_EXCEPTION);
91        return resultRange.release();
92    }
93
94    for (; !it.atEnd(); it.advance()) {
95        int len = it.length();
96
97        textRunStartPosition = it.startPosition();
98        textRunEndPosition = it.endPosition();
99
100        bool foundStart = start() >= docTextPosition && start() <= docTextPosition + len;
101        bool foundEnd = end() >= docTextPosition && end() <= docTextPosition + len;
102
103        // Fix textRunRange->endPosition(), but only if foundStart || foundEnd, because it is only
104        // in those cases that textRunRange is used.
105        if (foundEnd) {
106            // FIXME: This is a workaround for the fact that the end of a run
107            // is often at the wrong position for emitted '\n's or if the
108            // renderer of the current node is a replaced element.
109            if (len == 1 && (it.characterAt(0) == '\n' || it.isInsideReplacedElement())) {
110                it.advance();
111                if (!it.atEnd()) {
112                    textRunEndPosition = it.startPosition();
113                } else {
114                    Position runEnd = VisiblePosition(textRunStartPosition).next().deepEquivalent();
115                    if (runEnd.isNotNull())
116                        textRunEndPosition = createLegacyEditingPosition(runEnd.containerNode(), runEnd.computeOffsetInContainerNode());
117                }
118            }
119        }
120
121        if (foundStart) {
122            startRangeFound = true;
123            if (textRunStartPosition.containerNode()->isTextNode()) {
124                int offset = start() - docTextPosition;
125                resultRange->setStart(textRunStartPosition.containerNode(), offset + textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION);
126            } else {
127                if (start() == docTextPosition)
128                    resultRange->setStart(textRunStartPosition.containerNode(), textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION);
129                else
130                    resultRange->setStart(textRunEndPosition.containerNode(), textRunEndPosition.offsetInContainerNode(), IGNORE_EXCEPTION);
131            }
132        }
133
134        if (foundEnd) {
135            if (textRunStartPosition.containerNode()->isTextNode()) {
136                int offset = end() - docTextPosition;
137                resultRange->setEnd(textRunStartPosition.containerNode(), offset + textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION);
138            } else {
139                if (end() == docTextPosition)
140                    resultRange->setEnd(textRunStartPosition.containerNode(), textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION);
141                else
142                    resultRange->setEnd(textRunEndPosition.containerNode(), textRunEndPosition.offsetInContainerNode(), IGNORE_EXCEPTION);
143            }
144            docTextPosition += len;
145            break;
146        }
147        docTextPosition += len;
148    }
149
150    if (!startRangeFound)
151        return nullptr;
152
153    if (length() && end() > docTextPosition) { // end() is out of bounds
154        resultRange->setEnd(textRunEndPosition.containerNode(), textRunEndPosition.offsetInContainerNode(), IGNORE_EXCEPTION);
155    }
156
157    return resultRange.release();
158}
159
160PlainTextRange PlainTextRange::create(const ContainerNode& scope, const Range& range)
161{
162    if (!range.startContainer())
163        return PlainTextRange();
164
165    // The critical assumption is that this only gets called with ranges that
166    // concentrate on a given area containing the selection root. This is done
167    // because of text fields and textareas. The DOM for those is not
168    // directly in the document DOM, so ensure that the range does not cross a
169    // boundary of one of those.
170    if (range.startContainer() != &scope && !range.startContainer()->isDescendantOf(&scope))
171        return PlainTextRange();
172    if (range.endContainer() != scope && !range.endContainer()->isDescendantOf(&scope))
173        return PlainTextRange();
174
175    RefPtrWillBeRawPtr<Range> testRange = Range::create(scope.document(), const_cast<ContainerNode*>(&scope), 0, range.startContainer(), range.startOffset());
176    ASSERT(testRange->startContainer() == &scope);
177    size_t start = TextIterator::rangeLength(testRange.get());
178
179    testRange->setEnd(range.endContainer(), range.endOffset(), IGNORE_EXCEPTION);
180    ASSERT(testRange->startContainer() == &scope);
181    size_t end = TextIterator::rangeLength(testRange.get());
182
183    return PlainTextRange(start, end);
184}
185
186}
187