blob: 5fa2b1b4797bd1e872e22a060d2862fd3b844ee2 [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All
* rights reserved.
* Copyright (C) 2005 Alexey Proskuryakov.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/editing/plain_text_range.h"
#include "third_party/blink/renderer/core/dom/container_node.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/range.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/iterators/text_iterator.h"
#include "third_party/blink/renderer/core/editing/visible_position.h"
#include "third_party/blink/renderer/core/editing/visible_units.h"
namespace blink {
PlainTextRange::PlainTextRange() : start_(kNotFound), end_(kNotFound) {}
PlainTextRange::PlainTextRange(const PlainTextRange&) = default;
PlainTextRange::PlainTextRange(wtf_size_t location)
: start_(location), end_(location) {}
PlainTextRange::PlainTextRange(wtf_size_t start, wtf_size_t end)
: start_(start), end_(end) {
DCHECK_LE(start, end);
}
EphemeralRange PlainTextRange::CreateRange(const ContainerNode& scope) const {
const TextIteratorBehavior& behavior =
TextIteratorBehavior::Builder()
.SetEmitsObjectReplacementCharacter(true)
.Build();
return CreateRangeFor(scope, behavior);
}
EphemeralRange PlainTextRange::CreateRangeForSelection(
const ContainerNode& scope) const {
const TextIteratorBehavior& behavior =
TextIteratorBehavior::Builder()
.SetEmitsObjectReplacementCharacter(true)
.SetEmitsCharactersBetweenAllVisiblePositions(true)
.Build();
return CreateRangeFor(scope, behavior);
}
EphemeralRange PlainTextRange::CreateRangeForSelectionIndexing(
const ContainerNode& scope) const {
const TextIteratorBehavior& behavior =
TextIteratorBehavior::Builder()
.SetEmitsObjectReplacementCharacter(true)
.SetEmitsCharactersBetweenAllVisiblePositions(true)
.SetSuppressesExtraNewlineEmission(true)
.Build();
return CreateRangeFor(scope, behavior);
}
static Position CreatePositionInTextRun(wtf_size_t offset_in_run,
const Position& text_run_start_position,
const Position& text_run_end_position) {
if (text_run_start_position.ComputeContainerNode()->IsTextNode()) {
// TODO(editing-dev): DCHECK |offset_in_run| falls in int value range.
const int offset = offset_in_run;
return Position(text_run_start_position.ComputeContainerNode(),
offset + text_run_start_position.OffsetInContainerNode());
}
if (!offset_in_run)
return text_run_start_position;
return text_run_end_position;
}
EphemeralRange PlainTextRange::CreateRangeFor(
const ContainerNode& scope,
const TextIteratorBehavior& behavior) const {
DCHECK(IsNotNull());
wtf_size_t doc_text_position = 0;
bool start_range_found = false;
Position text_run_start_position;
Position text_run_end_position;
TextIterator it(EphemeralRange::RangeOfContents(scope), behavior);
// FIXME: the atEnd() check shouldn't be necessary, workaround for
// <http://bugs.webkit.org/show_bug.cgi?id=6289>.
if (!Start() && !length() && it.AtEnd())
return EphemeralRange(Position(it.CurrentContainer(), 0));
Position result_start = Position(&scope.GetDocument(), 0);
Position result_end = result_start;
for (; !it.AtEnd(); it.Advance()) {
const int len = it.length();
text_run_start_position =
it.StartPositionInCurrentContainer().ToOffsetInAnchor();
text_run_end_position =
it.EndPositionInCurrentContainer().ToOffsetInAnchor();
const bool found_start =
Start() >= doc_text_position && Start() <= doc_text_position + len;
const bool found_end =
End() >= doc_text_position && End() <= doc_text_position + len;
// Fix textRunRange->endPosition(), but only if foundStart || foundEnd,
// because it is only in those cases that textRunRange is used.
if (found_end) {
// FIXME: This is a workaround for the fact that the end of a run
// is often at the wrong position for emitted '\n's or if the
// layoutObject of the current node is a replaced element.
if (len == 1 && (it.CharacterAt(0) == '\n' || it.CharacterAt(0) == '\t' ||
it.IsInsideAtomicInlineElement())) {
it.Advance();
if (!it.AtEnd()) {
text_run_end_position = it.StartPositionInCurrentContainer();
} else {
Position run_end =
NextPositionOf(CreateVisiblePosition(text_run_start_position))
.DeepEquivalent();
if (run_end.IsNotNull())
text_run_end_position = run_end;
}
}
}
if (found_start) {
start_range_found = true;
result_start = CreatePositionInTextRun(Start() - doc_text_position,
text_run_start_position,
text_run_end_position);
}
if (found_end) {
result_end = CreatePositionInTextRun(End() - doc_text_position,
text_run_start_position,
text_run_end_position);
DCHECK(start_range_found);
return EphemeralRange(result_start, result_end);
}
doc_text_position += len;
}
// Start() is out of bounds
if (!start_range_found)
return EphemeralRange();
// End() is out of bounds
return EphemeralRange(result_start, text_run_end_position);
}
PlainTextRange PlainTextRange::Create(const ContainerNode& scope,
const EphemeralRange& range) {
if (range.IsNull())
return PlainTextRange();
// The critical assumption is that this only gets called with ranges that
// concentrate on a given area containing the selection root. This is done
// because of text fields and textareas. The DOM for those is not
// directly in the document DOM, so ensure that the range does not cross a
// boundary of one of those.
Node* start_container = range.StartPosition().ComputeContainerNode();
if (start_container != &scope && !start_container->IsDescendantOf(&scope))
return PlainTextRange();
Node* end_container = range.EndPosition().ComputeContainerNode();
if (end_container != scope && !end_container->IsDescendantOf(&scope))
return PlainTextRange();
DocumentLifecycle::DisallowTransitionScope disallow_transition(
scope.GetDocument().Lifecycle());
wtf_size_t start =
TextIterator::RangeLength(Position(scope, 0), range.StartPosition());
wtf_size_t end =
TextIterator::RangeLength(Position(scope, 0), range.EndPosition());
return PlainTextRange(start, end);
}
PlainTextRange PlainTextRange::Create(const ContainerNode& scope,
const Range& range) {
return Create(scope, EphemeralRange(&range));
}
} // namespace blink