blob: 5040612e8b0f2b23991cb35ad348d350eb2c21f1 [file] [log] [blame]
/*
* Copyright (C) 2007, 2009 Apple Inc. All rights reserved.
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* 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.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 OR ITS 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 "core/editing/DOMSelection.h"
#include "bindings/core/v8/ExceptionMessages.h"
#include "bindings/core/v8/ExceptionState.h"
#include "core/dom/Document.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/Node.h"
#include "core/dom/Range.h"
#include "core/dom/TreeScope.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/EphemeralRange.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/Position.h"
#include "core/editing/SelectionModifier.h"
#include "core/editing/SelectionTemplate.h"
#include "core/editing/SetSelectionOptions.h"
#include "core/editing/VisibleSelection.h"
#include "core/editing/iterators/TextIterator.h"
#include "core/frame/Deprecation.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/UseCounter.h"
#include "core/inspector/ConsoleMessage.h"
#include "platform/wtf/text/WTFString.h"
namespace blink {
static Node* SelectionShadowAncestor(LocalFrame* frame) {
Node* node = frame->Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.Base()
.AnchorNode();
if (!node)
return nullptr;
if (!node->IsInShadowTree())
return nullptr;
return frame->GetDocument()->AncestorInThisScope(node);
}
DOMSelection::DOMSelection(const TreeScope* tree_scope)
: ContextClient(tree_scope->RootNode().GetDocument().GetFrame()),
tree_scope_(tree_scope) {}
void DOMSelection::ClearTreeScope() {
tree_scope_ = nullptr;
}
// TODO(editing-dev): The behavior after loosing browsing context is not
// specified. https://github.com/w3c/selection-api/issues/82
bool DOMSelection::IsAvailable() const {
return GetFrame() && GetFrame()->Selection().IsAvailable();
}
void DOMSelection::UpdateFrameSelection(
const SelectionInDOMTree& selection,
Range* new_cached_range,
const SetSelectionOptions& passed_options) const {
DCHECK(GetFrame());
FrameSelection& frame_selection = GetFrame()->Selection();
SetSelectionOptions::Builder builder(passed_options);
builder.SetShouldCloseTyping(true).SetShouldClearTypingStyle(true);
SetSelectionOptions options = builder.Build();
// TODO(tkent): Specify FrameSelection::DoNotSetFocus. crbug.com/690272
const bool did_set =
frame_selection.SetSelectionDeprecated(selection, options);
CacheRangeIfSelectionOfDocument(new_cached_range);
if (!did_set)
return;
Element* focused_element = GetFrame()->GetDocument()->FocusedElement();
frame_selection.DidSetSelectionDeprecated(options);
if (GetFrame() && GetFrame()->GetDocument() &&
focused_element != GetFrame()->GetDocument()->FocusedElement())
UseCounter::Count(GetFrame(), WebFeature::kSelectionFuncionsChangeFocus);
}
VisibleSelection DOMSelection::GetVisibleSelection() const {
DCHECK(GetFrame());
return GetFrame()->Selection().ComputeVisibleSelectionInDOMTreeDeprecated();
}
bool DOMSelection::IsBaseFirstInSelection() const {
DCHECK(GetFrame());
const SelectionInDOMTree& selection =
GetFrame()->Selection().GetSelectionInDOMTree();
return selection.Base() <= selection.Extent();
}
// TODO(tkent): Following four functions based on VisibleSelection should be
// removed.
static Position AnchorPosition(const VisibleSelection& selection) {
Position anchor =
selection.IsBaseFirst() ? selection.Start() : selection.End();
return anchor.ParentAnchoredEquivalent();
}
static Position FocusPosition(const VisibleSelection& selection) {
Position focus =
selection.IsBaseFirst() ? selection.End() : selection.Start();
return focus.ParentAnchoredEquivalent();
}
static Position BasePosition(const VisibleSelection& selection) {
return selection.Base().ParentAnchoredEquivalent();
}
static Position ExtentPosition(const VisibleSelection& selection) {
return selection.Extent().ParentAnchoredEquivalent();
}
Node* DOMSelection::anchorNode() const {
if (Range* range = PrimaryRangeOrNull()) {
if (!GetFrame() || IsBaseFirstInSelection())
return range->startContainer();
return range->endContainer();
}
return nullptr;
}
unsigned DOMSelection::anchorOffset() const {
if (Range* range = PrimaryRangeOrNull()) {
if (!GetFrame() || IsBaseFirstInSelection())
return range->startOffset();
return range->endOffset();
}
return 0;
}
Node* DOMSelection::focusNode() const {
if (Range* range = PrimaryRangeOrNull()) {
if (!GetFrame() || IsBaseFirstInSelection())
return range->endContainer();
return range->startContainer();
}
return nullptr;
}
unsigned DOMSelection::focusOffset() const {
if (Range* range = PrimaryRangeOrNull()) {
if (!GetFrame() || IsBaseFirstInSelection())
return range->endOffset();
return range->startOffset();
}
return 0;
}
Node* DOMSelection::baseNode() const {
if (!IsAvailable())
return nullptr;
return ShadowAdjustedNode(BasePosition(GetVisibleSelection()));
}
unsigned DOMSelection::baseOffset() const {
if (!IsAvailable())
return 0;
return ShadowAdjustedOffset(BasePosition(GetVisibleSelection()));
}
Node* DOMSelection::extentNode() const {
if (!IsAvailable())
return nullptr;
return ShadowAdjustedNode(ExtentPosition(GetVisibleSelection()));
}
unsigned DOMSelection::extentOffset() const {
if (!IsAvailable())
return 0;
return ShadowAdjustedOffset(ExtentPosition(GetVisibleSelection()));
}
bool DOMSelection::isCollapsed() const {
if (!IsAvailable() || SelectionShadowAncestor(GetFrame()))
return true;
if (Range* range = PrimaryRangeOrNull())
return range->collapsed();
return true;
}
String DOMSelection::type() const {
if (!IsAvailable())
return String();
// This is a WebKit DOM extension, incompatible with an IE extension
// IE has this same attribute, but returns "none", "text" and "control"
// http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx
if (rangeCount() == 0)
return "None";
// Do not use isCollapsed() here. We'd like to return "Range" for
// range-selection in text control elements.
if (GetFrame()->Selection().GetSelectionInDOMTree().IsCaret())
return "Caret";
return "Range";
}
unsigned DOMSelection::rangeCount() const {
if (!IsAvailable())
return 0;
if (DocumentCachedRange())
return 1;
if (GetFrame()
->Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.IsNone())
return 0;
// Any selection can be adjusted to Range for Document.
if (IsSelectionOfDocument())
return 1;
// In ShadowRoot, we need to try adjustment.
if (CreateRangeFromSelectionEditor().IsNotNull())
return 1;
return 0;
}
// https://www.w3.org/TR/selection-api/#dom-selection-collapse
void DOMSelection::collapse(Node* node,
unsigned offset,
ExceptionState& exception_state) {
if (!IsAvailable())
return;
// 1. If node is null, this method must behave identically as
// removeAllRanges() and abort these steps.
if (!node) {
UseCounter::Count(GetFrame(), WebFeature::kSelectionCollapseNull);
GetFrame()->Selection().Clear();
return;
}
// 2. The method must throw an IndexSizeError exception if offset is longer
// than node's length ([DOM4]) and abort these steps.
Range::CheckNodeWOffset(node, offset, exception_state);
if (exception_state.HadException())
return;
// 3. If node's root is not the document associated with the context object,
// abort these steps.
if (!IsValidForPosition(node))
return;
// 4. Otherwise, let newRange be a new range.
Range* new_range = Range::Create(*GetFrame()->GetDocument());
// 5. Set ([DOM4]) the start and the end of newRange to (node, offset).
new_range->setStart(node, offset, exception_state);
if (exception_state.HadException()) {
new_range->Dispose();
return;
}
new_range->setEnd(node, offset, exception_state);
if (exception_state.HadException()) {
new_range->Dispose();
return;
}
// 6. Set the context object's range to newRange.
UpdateFrameSelection(
SelectionInDOMTree::Builder()
.Collapse(Position(node, offset))
.SetIsDirectional(GetFrame()->Selection().IsDirectional())
.Build(),
new_range,
SetSelectionOptions::Builder()
.SetIsDirectional(GetFrame()->Selection().IsDirectional())
.Build());
}
// https://www.w3.org/TR/selection-api/#dom-selection-collapsetoend
void DOMSelection::collapseToEnd(ExceptionState& exception_state) {
if (!IsAvailable())
return;
// The method must throw InvalidStateError exception if the context object is
// empty.
if (rangeCount() == 0) {
exception_state.ThrowDOMException(kInvalidStateError,
"there is no selection.");
return;
}
if (Range* current_range = DocumentCachedRange()) {
// Otherwise, it must create a new range, set both its start and end to the
// end of the context object's range,
Range* new_range = current_range->cloneRange();
new_range->collapse(false);
// and then set the context object's range to the newly-created range.
SelectionInDOMTree::Builder builder;
builder.Collapse(new_range->EndPosition());
UpdateFrameSelection(builder.Build(), new_range, SetSelectionOptions());
} else {
// TODO(tkent): The Selection API doesn't define this behavior. We should
// discuss this on https://github.com/w3c/selection-api/issues/83.
SelectionInDOMTree::Builder builder;
builder.Collapse(
GetFrame()->Selection().GetSelectionInDOMTree().ComputeEndPosition());
UpdateFrameSelection(builder.Build(), nullptr, SetSelectionOptions());
}
}
// https://www.w3.org/TR/selection-api/#dom-selection-collapsetostart
void DOMSelection::collapseToStart(ExceptionState& exception_state) {
if (!IsAvailable())
return;
// The method must throw InvalidStateError ([DOM4]) exception if the context
// object is empty.
if (rangeCount() == 0) {
exception_state.ThrowDOMException(kInvalidStateError,
"there is no selection.");
return;
}
if (Range* current_range = DocumentCachedRange()) {
// Otherwise, it must create a new range, set both its start and end to the
// start of the context object's range,
Range* new_range = current_range->cloneRange();
new_range->collapse(true);
// and then set the context object's range to the newly-created range.
SelectionInDOMTree::Builder builder;
builder.Collapse(new_range->StartPosition());
UpdateFrameSelection(builder.Build(), new_range, SetSelectionOptions());
} else {
// TODO(tkent): The Selection API doesn't define this behavior. We should
// discuss this on https://github.com/w3c/selection-api/issues/83.
SelectionInDOMTree::Builder builder;
builder.Collapse(
GetFrame()->Selection().GetSelectionInDOMTree().ComputeStartPosition());
UpdateFrameSelection(builder.Build(), nullptr, SetSelectionOptions());
}
}
void DOMSelection::empty() {
if (!IsAvailable())
return;
GetFrame()->Selection().Clear();
}
void DOMSelection::setBaseAndExtent(Node* base_node,
unsigned base_offset,
Node* extent_node,
unsigned extent_offset,
ExceptionState& exception_state) {
if (!IsAvailable())
return;
// TODO(editing-dev): Behavior on where base or extent is null is still
// under discussion: https://github.com/w3c/selection-api/issues/72
if (!base_node) {
UseCounter::Count(GetFrame(), WebFeature::kSelectionSetBaseAndExtentNull);
GetFrame()->Selection().Clear();
return;
}
if (!extent_node) {
UseCounter::Count(GetFrame(), WebFeature::kSelectionSetBaseAndExtentNull);
extent_offset = 0;
}
Range::CheckNodeWOffset(base_node, base_offset, exception_state);
if (exception_state.HadException())
return;
if (extent_node) {
Range::CheckNodeWOffset(extent_node, extent_offset, exception_state);
if (exception_state.HadException())
return;
}
if (!IsValidForPosition(base_node) || !IsValidForPosition(extent_node))
return;
ClearCachedRangeIfSelectionOfDocument();
Position base_position(base_node, base_offset);
Position extent_position(extent_node, extent_offset);
Range* new_range = Range::Create(base_node->GetDocument());
if (extent_position.IsNull()) {
new_range->setStart(base_node, base_offset);
new_range->setEnd(base_node, base_offset);
} else if (base_position < extent_position) {
new_range->setStart(base_node, base_offset);
new_range->setEnd(extent_node, extent_offset);
} else {
new_range->setStart(extent_node, extent_offset);
new_range->setEnd(base_node, base_offset);
}
UpdateFrameSelection(
SelectionInDOMTree::Builder()
.SetBaseAndExtentDeprecated(base_position, extent_position)
.SetIsDirectional(true)
.Build(),
new_range, SetSelectionOptions::Builder().SetIsDirectional(true).Build());
}
void DOMSelection::modify(const String& alter_string,
const String& direction_string,
const String& granularity_string) {
if (!IsAvailable())
return;
SelectionModifyAlteration alter;
if (DeprecatedEqualIgnoringCase(alter_string, "extend"))
alter = SelectionModifyAlteration::kExtend;
else if (DeprecatedEqualIgnoringCase(alter_string, "move"))
alter = SelectionModifyAlteration::kMove;
else
return;
SelectionModifyDirection direction;
if (DeprecatedEqualIgnoringCase(direction_string, "forward"))
direction = SelectionModifyDirection::kForward;
else if (DeprecatedEqualIgnoringCase(direction_string, "backward"))
direction = SelectionModifyDirection::kBackward;
else if (DeprecatedEqualIgnoringCase(direction_string, "left"))
direction = SelectionModifyDirection::kLeft;
else if (DeprecatedEqualIgnoringCase(direction_string, "right"))
direction = SelectionModifyDirection::kRight;
else
return;
TextGranularity granularity;
if (DeprecatedEqualIgnoringCase(granularity_string, "character"))
granularity = TextGranularity::kCharacter;
else if (DeprecatedEqualIgnoringCase(granularity_string, "word"))
granularity = TextGranularity::kWord;
else if (DeprecatedEqualIgnoringCase(granularity_string, "sentence"))
granularity = TextGranularity::kSentence;
else if (DeprecatedEqualIgnoringCase(granularity_string, "line"))
granularity = TextGranularity::kLine;
else if (DeprecatedEqualIgnoringCase(granularity_string, "paragraph"))
granularity = TextGranularity::kParagraph;
else if (DeprecatedEqualIgnoringCase(granularity_string, "lineboundary"))
granularity = TextGranularity::kLineBoundary;
else if (DeprecatedEqualIgnoringCase(granularity_string, "sentenceboundary"))
granularity = TextGranularity::kSentenceBoundary;
else if (DeprecatedEqualIgnoringCase(granularity_string, "paragraphboundary"))
granularity = TextGranularity::kParagraphBoundary;
else if (DeprecatedEqualIgnoringCase(granularity_string, "documentboundary"))
granularity = TextGranularity::kDocumentBoundary;
else
return;
// TODO(editing-dev): The use of updateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. See http://crbug.com/590369 for more details.
GetFrame()->GetDocument()->UpdateStyleAndLayoutIgnorePendingStylesheets();
Element* focused_element = GetFrame()->GetDocument()->FocusedElement();
GetFrame()->Selection().Modify(alter, direction, granularity,
SetSelectionBy::kSystem);
if (GetFrame() && GetFrame()->GetDocument() &&
focused_element != GetFrame()->GetDocument()->FocusedElement())
UseCounter::Count(GetFrame(), WebFeature::kSelectionFuncionsChangeFocus);
}
// https://www.w3.org/TR/selection-api/#dom-selection-extend
void DOMSelection::extend(Node* node,
unsigned offset,
ExceptionState& exception_state) {
DCHECK(node);
if (!IsAvailable())
return;
// 1. If node's root is not the document associated with the context object,
// abort these steps.
if (!IsValidForPosition(node))
return;
// 2. If the context object is empty, throw an InvalidStateError exception and
// abort these steps.
if (rangeCount() == 0) {
exception_state.ThrowDOMException(
kInvalidStateError, "This Selection object doesn't have any Ranges.");
return;
}
Range::CheckNodeWOffset(node, offset, exception_state);
if (exception_state.HadException())
return;
// 3. Let oldAnchor and oldFocus be the context object's anchor and focus, and
// let newFocus be the boundary point (node, offset).
const Position old_anchor(anchorNode(), anchorOffset());
DCHECK(!old_anchor.IsNull());
const Position new_focus(node, offset);
ClearCachedRangeIfSelectionOfDocument();
// 4. Let newRange be a new range.
Range* new_range = Range::Create(*GetFrame()->GetDocument());
// 5. If node's root is not the same as the context object's range's root, set
// newRange's start and end to newFocus.
// E.g. oldAnchor might point in shadow Text node in TextControlElement.
if (old_anchor.AnchorNode()->TreeRoot() != node->TreeRoot()) {
new_range->setStart(node, offset);
new_range->setEnd(node, offset);
} else if (old_anchor <= new_focus) {
// 6. Otherwise, if oldAnchor is before or equal to newFocus, set newRange's
// start to oldAnchor, then set its end to newFocus.
new_range->setStart(old_anchor.AnchorNode(),
old_anchor.OffsetInContainerNode());
new_range->setEnd(node, offset);
} else {
// 7. Otherwise, set newRange's start to newFocus, then set its end to
// oldAnchor.
new_range->setStart(node, offset);
new_range->setEnd(old_anchor.AnchorNode(),
old_anchor.OffsetInContainerNode());
}
// 8. Set the context object's range to newRange.
SelectionInDOMTree::Builder builder;
if (new_range->collapsed())
builder.Collapse(new_focus);
else
builder.Collapse(old_anchor).Extend(new_focus);
UpdateFrameSelection(
builder.SetIsDirectional(true).Build(), new_range,
SetSelectionOptions::Builder().SetIsDirectional(true).Build());
}
Range* DOMSelection::getRangeAt(unsigned index,
ExceptionState& exception_state) const {
if (!IsAvailable())
return nullptr;
if (index >= rangeCount()) {
exception_state.ThrowDOMException(
kIndexSizeError, String::Number(index) + " is not a valid index.");
return nullptr;
}
// If you're hitting this, you've added broken multi-range selection support
DCHECK_EQ(rangeCount(), 1u);
if (Range* cached_range = DocumentCachedRange())
return cached_range;
Range* range = CreateRange(CreateRangeFromSelectionEditor());
CacheRangeIfSelectionOfDocument(range);
return range;
}
Range* DOMSelection::PrimaryRangeOrNull() const {
return rangeCount() > 0 ? getRangeAt(0, ASSERT_NO_EXCEPTION) : nullptr;
}
EphemeralRange DOMSelection::CreateRangeFromSelectionEditor() const {
const VisibleSelection& selection = GetVisibleSelection();
const Position& anchor = blink::AnchorPosition(selection);
if (IsSelectionOfDocument() && !anchor.AnchorNode()->IsInShadowTree())
return FirstEphemeralRangeOf(selection);
Node* const anchor_node = ShadowAdjustedNode(anchor);
if (!anchor_node) // crbug.com/595100
return EphemeralRange();
const Position& focus = FocusPosition(selection);
const Position shadow_adjusted_focus =
Position(ShadowAdjustedNode(focus), ShadowAdjustedOffset(focus));
const Position shadow_adjusted_anchor =
Position(anchor_node, ShadowAdjustedOffset(anchor));
if (selection.IsBaseFirst())
return EphemeralRange(shadow_adjusted_anchor, shadow_adjusted_focus);
return EphemeralRange(shadow_adjusted_focus, shadow_adjusted_anchor);
}
bool DOMSelection::IsSelectionOfDocument() const {
return tree_scope_ == tree_scope_->GetDocument();
}
void DOMSelection::CacheRangeIfSelectionOfDocument(Range* range) const {
if (!IsSelectionOfDocument())
return;
if (!GetFrame())
return;
GetFrame()->Selection().CacheRangeOfDocument(range);
}
Range* DOMSelection::DocumentCachedRange() const {
if (!IsSelectionOfDocument())
return nullptr;
return GetFrame()->Selection().DocumentCachedRange();
}
void DOMSelection::ClearCachedRangeIfSelectionOfDocument() {
if (!IsSelectionOfDocument())
return;
GetFrame()->Selection().ClearDocumentCachedRange();
}
void DOMSelection::removeRange(Range* range) {
DCHECK(range);
if (!IsAvailable())
return;
if (range == PrimaryRangeOrNull())
GetFrame()->Selection().Clear();
}
void DOMSelection::removeAllRanges() {
if (!IsAvailable())
return;
GetFrame()->Selection().Clear();
}
void DOMSelection::addRange(Range* new_range) {
DCHECK(new_range);
if (!IsAvailable())
return;
if (new_range->OwnerDocument() != GetFrame()->GetDocument())
return;
if (!new_range->IsConnected()) {
AddConsoleError("The given range isn't in document.");
return;
}
FrameSelection& selection = GetFrame()->Selection();
if (new_range->OwnerDocument() != selection.GetDocument()) {
// "editing/selection/selection-in-iframe-removed-crash.html" goes here.
return;
}
if (rangeCount() == 0) {
UpdateFrameSelection(SelectionInDOMTree::Builder()
.Collapse(new_range->StartPosition())
.Extend(new_range->EndPosition())
.Build(),
new_range, SetSelectionOptions());
return;
}
Range* original_range = PrimaryRangeOrNull();
DCHECK(original_range);
if (original_range->startContainer()->GetTreeScope() !=
new_range->startContainer()->GetTreeScope()) {
return;
}
if (original_range->compareBoundaryPoints(Range::kStartToEnd, new_range,
ASSERT_NO_EXCEPTION) < 0 ||
new_range->compareBoundaryPoints(Range::kStartToEnd, original_range,
ASSERT_NO_EXCEPTION) < 0) {
return;
}
// TODO(tkent): "Merge the ranges if they intersect" was removed. We show a
// warning message for a while, and continue to collect the usage data.
// <https://code.google.com/p/chromium/issues/detail?id=353069>.
Deprecation::CountDeprecation(GetFrame(),
WebFeature::kSelectionAddRangeIntersect);
}
// https://www.w3.org/TR/selection-api/#dom-selection-deletefromdocument
void DOMSelection::deleteFromDocument() {
if (!IsAvailable())
return;
// The method must invoke deleteContents() ([DOM4]) on the context object's
// range if the context object is not empty. Otherwise the method must do
// nothing.
if (Range* range = DocumentCachedRange()) {
range->deleteContents(ASSERT_NO_EXCEPTION);
return;
}
// TODO(editing-dev): The use of updateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. See http://crbug.com/590369 for more details.
GetFrame()->GetDocument()->UpdateStyleAndLayoutIgnorePendingStylesheets();
// The following code is necessary for
// editing/selection/deleteFromDocument-crash.html, which assumes
// deleteFromDocument() for text selection in a TEXTAREA deletes the TEXTAREA
// value.
FrameSelection& selection = GetFrame()->Selection();
if (selection.ComputeVisibleSelectionInDOMTree().IsNone())
return;
Range* selected_range =
CreateRange(selection.ComputeVisibleSelectionInDOMTree()
.ToNormalizedEphemeralRange());
if (!selected_range)
return;
// |selectedRange| may point nodes in a different root.
selected_range->deleteContents(ASSERT_NO_EXCEPTION);
}
bool DOMSelection::containsNode(const Node* n, bool allow_partial) const {
DCHECK(n);
if (!IsAvailable())
return false;
if (GetFrame()->GetDocument() != n->GetDocument())
return false;
unsigned node_index = n->NodeIndex();
// TODO(editing-dev): The use of updateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. See http://crbug.com/590369 for more details.
// |VisibleSelection::toNormalizedEphemeralRange| requires clean layout.
GetFrame()->GetDocument()->UpdateStyleAndLayoutIgnorePendingStylesheets();
FrameSelection& selection = GetFrame()->Selection();
const EphemeralRange selected_range =
selection.ComputeVisibleSelectionInDOMTreeDeprecated()
.ToNormalizedEphemeralRange();
if (selected_range.IsNull())
return false;
ContainerNode* parent_node = n->parentNode();
if (!parent_node)
return false;
const Position start_position =
selected_range.StartPosition().ToOffsetInAnchor();
const Position end_position = selected_range.EndPosition().ToOffsetInAnchor();
DummyExceptionStateForTesting exception_state;
bool node_fully_selected =
Range::compareBoundaryPoints(
parent_node, node_index, start_position.ComputeContainerNode(),
start_position.OffsetInContainerNode(), exception_state) >= 0 &&
!exception_state.HadException() &&
Range::compareBoundaryPoints(
parent_node, node_index + 1, end_position.ComputeContainerNode(),
end_position.OffsetInContainerNode(), exception_state) <= 0 &&
!exception_state.HadException();
if (exception_state.HadException())
return false;
if (node_fully_selected)
return true;
bool node_fully_unselected =
(Range::compareBoundaryPoints(
parent_node, node_index, end_position.ComputeContainerNode(),
end_position.OffsetInContainerNode(), exception_state) > 0 &&
!exception_state.HadException()) ||
(Range::compareBoundaryPoints(
parent_node, node_index + 1, start_position.ComputeContainerNode(),
start_position.OffsetInContainerNode(), exception_state) < 0 &&
!exception_state.HadException());
DCHECK(!exception_state.HadException());
if (node_fully_unselected)
return false;
return allow_partial || n->IsTextNode();
}
void DOMSelection::selectAllChildren(Node* n, ExceptionState& exception_state) {
DCHECK(n);
// This doesn't (and shouldn't) select text node characters.
setBaseAndExtent(n, 0, n, n->CountChildren(), exception_state);
}
String DOMSelection::toString() {
if (!IsAvailable())
return String();
// TODO(editing-dev): The use of updateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. See http://crbug.com/590369 for more details.
GetFrame()->GetDocument()->UpdateStyleAndLayoutIgnorePendingStylesheets();
DocumentLifecycle::DisallowTransitionScope disallow_transition(
GetFrame()->GetDocument()->Lifecycle());
const EphemeralRange range = GetFrame()
->Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.ToNormalizedEphemeralRange();
return PlainText(
range,
TextIteratorBehavior::Builder().SetForSelectionToString(true).Build());
}
Node* DOMSelection::ShadowAdjustedNode(const Position& position) const {
if (position.IsNull())
return nullptr;
Node* container_node = position.ComputeContainerNode();
Node* adjusted_node = tree_scope_->AncestorInThisScope(container_node);
if (!adjusted_node)
return nullptr;
if (container_node == adjusted_node)
return container_node;
DCHECK(!adjusted_node->IsShadowRoot()) << adjusted_node;
return adjusted_node->ParentOrShadowHostNode();
}
unsigned DOMSelection::ShadowAdjustedOffset(const Position& position) const {
if (position.IsNull())
return 0;
Node* container_node = position.ComputeContainerNode();
Node* adjusted_node = tree_scope_->AncestorInThisScope(container_node);
if (!adjusted_node)
return 0;
if (container_node == adjusted_node)
return position.ComputeOffsetInContainerNode();
return adjusted_node->NodeIndex();
}
bool DOMSelection::IsValidForPosition(Node* node) const {
DCHECK(GetFrame());
if (!node)
return true;
return node->GetDocument() == GetFrame()->GetDocument() &&
node->isConnected();
}
void DOMSelection::AddConsoleError(const String& message) {
if (tree_scope_)
tree_scope_->GetDocument().AddConsoleMessage(
ConsoleMessage::Create(kJSMessageSource, kErrorMessageLevel, message));
}
void DOMSelection::Trace(blink::Visitor* visitor) {
visitor->Trace(tree_scope_);
ScriptWrappable::Trace(visitor);
ContextClient::Trace(visitor);
}
} // namespace blink