blob: 3af4f80f0431a4e8ee8d67a79a64f02d7cb9f0fb [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2007 Apple 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.
*
* 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 "core/editing/EditingUtilities.h"
#include "core/clipboard/DataObject.h"
#include "core/dom/Document.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/NodeComputedStyle.h"
#include "core/dom/Range.h"
#include "core/dom/ShadowRoot.h"
#include "core/dom/Text.h"
#include "core/editing/EditingStrategy.h"
#include "core/editing/Editor.h"
#include "core/editing/EphemeralRange.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/PlainTextRange.h"
#include "core/editing/PositionIterator.h"
#include "core/editing/PositionWithAffinity.h"
#include "core/editing/SelectionTemplate.h"
#include "core/editing/VisiblePosition.h"
#include "core/editing/VisibleSelection.h"
#include "core/editing/VisibleUnits.h"
#include "core/editing/iterators/TextIterator.h"
#include "core/editing/serializers/HTMLInterchange.h"
#include "core/editing/state_machines/BackspaceStateMachine.h"
#include "core/editing/state_machines/BackwardGraphemeBoundaryStateMachine.h"
#include "core/editing/state_machines/ForwardGraphemeBoundaryStateMachine.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/LocalFrameView.h"
#include "core/frame/UseCounter.h"
#include "core/html/HTMLBRElement.h"
#include "core/html/HTMLDivElement.h"
#include "core/html/HTMLLIElement.h"
#include "core/html/HTMLParagraphElement.h"
#include "core/html/HTMLSpanElement.h"
#include "core/html/HTMLTableCellElement.h"
#include "core/html/HTMLUListElement.h"
#include "core/html/forms/HTMLInputElement.h"
#include "core/html_element_factory.h"
#include "core/html_names.h"
#include "core/input_type_names.h"
#include "core/layout/LayoutObject.h"
#include "core/layout/LayoutTableCell.h"
#include "platform/clipboard/ClipboardMimeTypes.h"
#include "platform/wtf/Assertions.h"
#include "platform/wtf/StdLibExtras.h"
#include "platform/wtf/text/StringBuilder.h"
#include "platform/wtf/text/Unicode.h"
namespace blink {
using namespace HTMLNames;
namespace {
std::ostream& operator<<(std::ostream& os, PositionMoveType type) {
static const char* const kTexts[] = {"CodeUnit", "BackwardDeletion",
"GraphemeCluster"};
const auto& it = std::begin(kTexts) + static_cast<size_t>(type);
DCHECK_GE(it, std::begin(kTexts)) << "Unknown PositionMoveType value";
DCHECK_LT(it, std::end(kTexts)) << "Unknown PositionMoveType value";
return os << *it;
}
InputEvent::EventCancelable InputTypeIsCancelable(
InputEvent::InputType input_type) {
using InputType = InputEvent::InputType;
switch (input_type) {
case InputType::kInsertText:
case InputType::kInsertLineBreak:
case InputType::kInsertParagraph:
case InputType::kInsertCompositionText:
case InputType::kInsertReplacementText:
case InputType::kDeleteWordBackward:
case InputType::kDeleteWordForward:
case InputType::kDeleteSoftLineBackward:
case InputType::kDeleteSoftLineForward:
case InputType::kDeleteHardLineBackward:
case InputType::kDeleteHardLineForward:
case InputType::kDeleteContentBackward:
case InputType::kDeleteContentForward:
return InputEvent::EventCancelable::kNotCancelable;
default:
return InputEvent::EventCancelable::kIsCancelable;
}
}
UChar WhitespaceRebalancingCharToAppend(const String& string,
bool start_is_start_of_paragraph,
bool should_emit_nbsp_before_end,
size_t index,
UChar previous) {
DCHECK_LT(index, string.length());
if (!IsWhitespace(string[index]))
return string[index];
if (!index && start_is_start_of_paragraph)
return kNoBreakSpaceCharacter;
if (index + 1 == string.length() && should_emit_nbsp_before_end)
return kNoBreakSpaceCharacter;
// Generally, alternate between space and no-break space.
if (previous == ' ')
return kNoBreakSpaceCharacter;
if (previous == kNoBreakSpaceCharacter)
return ' ';
// Run of two or more spaces starts with a no-break space (crbug.com/453042).
if (index + 1 < string.length() && IsWhitespace(string[index + 1]))
return kNoBreakSpaceCharacter;
return ' ';
}
} // namespace
bool NeedsLayoutTreeUpdate(const Node& node) {
const Document& document = node.GetDocument();
if (document.NeedsLayoutTreeUpdate())
return true;
// TODO(yosin): We should make |document::needsLayoutTreeUpdate()| to
// check |LayoutView::needsLayout()|.
return document.View() && document.View()->NeedsLayout();
}
template <typename PositionType>
static bool NeedsLayoutTreeUpdateAlgorithm(const PositionType& position) {
const Node* node = position.AnchorNode();
if (!node)
return false;
return NeedsLayoutTreeUpdate(*node);
}
bool NeedsLayoutTreeUpdate(const Position& position) {
return NeedsLayoutTreeUpdateAlgorithm<Position>(position);
}
bool NeedsLayoutTreeUpdate(const PositionInFlatTree& position) {
return NeedsLayoutTreeUpdateAlgorithm<PositionInFlatTree>(position);
}
// Atomic means that the node has no children, or has children which are ignored
// for the purposes of editing.
bool IsAtomicNode(const Node* node) {
return node && (!node->hasChildren() || EditingIgnoresContent(*node));
}
template <typename Traversal>
static int ComparePositions(const Node* container_a,
int offset_a,
const Node* container_b,
int offset_b,
bool* disconnected) {
DCHECK(container_a);
DCHECK(container_b);
if (disconnected)
*disconnected = false;
if (!container_a)
return -1;
if (!container_b)
return 1;
// see DOM2 traversal & range section 2.5
// case 1: both points have the same container
if (container_a == container_b) {
if (offset_a == offset_b)
return 0; // A is equal to B
if (offset_a < offset_b)
return -1; // A is before B
return 1; // A is after B
}
// case 2: node C (container B or an ancestor) is a child node of A
const Node* c = container_b;
while (c && Traversal::Parent(*c) != container_a)
c = Traversal::Parent(*c);
if (c) {
int offset_c = 0;
Node* n = Traversal::FirstChild(*container_a);
while (n != c && offset_c < offset_a) {
offset_c++;
n = Traversal::NextSibling(*n);
}
if (offset_a <= offset_c)
return -1; // A is before B
return 1; // A is after B
}
// case 3: node C (container A or an ancestor) is a child node of B
c = container_a;
while (c && Traversal::Parent(*c) != container_b)
c = Traversal::Parent(*c);
if (c) {
int offset_c = 0;
Node* n = Traversal::FirstChild(*container_b);
while (n != c && offset_c < offset_b) {
offset_c++;
n = Traversal::NextSibling(*n);
}
if (offset_c < offset_b)
return -1; // A is before B
return 1; // A is after B
}
// case 4: containers A & B are siblings, or children of siblings
// ### we need to do a traversal here instead
Node* common_ancestor = Traversal::CommonAncestor(*container_a, *container_b);
if (!common_ancestor) {
if (disconnected)
*disconnected = true;
return 0;
}
const Node* child_a = container_a;
while (child_a && Traversal::Parent(*child_a) != common_ancestor)
child_a = Traversal::Parent(*child_a);
if (!child_a)
child_a = common_ancestor;
const Node* child_b = container_b;
while (child_b && Traversal::Parent(*child_b) != common_ancestor)
child_b = Traversal::Parent(*child_b);
if (!child_b)
child_b = common_ancestor;
if (child_a == child_b)
return 0; // A is equal to B
Node* n = Traversal::FirstChild(*common_ancestor);
while (n) {
if (n == child_a)
return -1; // A is before B
if (n == child_b)
return 1; // A is after B
n = Traversal::NextSibling(*n);
}
// Should never reach this point.
NOTREACHED();
return 0;
}
int ComparePositionsInDOMTree(const Node* container_a,
int offset_a,
const Node* container_b,
int offset_b,
bool* disconnected) {
return ComparePositions<NodeTraversal>(container_a, offset_a, container_b,
offset_b, disconnected);
}
int ComparePositionsInFlatTree(const Node* container_a,
int offset_a,
const Node* container_b,
int offset_b,
bool* disconnected) {
return ComparePositions<FlatTreeTraversal>(container_a, offset_a, container_b,
offset_b, disconnected);
}
// Compare two positions, taking into account the possibility that one or both
// could be inside a shadow tree. Only works for non-null values.
int ComparePositions(const Position& a, const Position& b) {
DCHECK(a.IsNotNull());
DCHECK(b.IsNotNull());
const TreeScope* common_scope = Position::CommonAncestorTreeScope(a, b);
DCHECK(common_scope);
if (!common_scope)
return 0;
Node* node_a = common_scope->AncestorInThisScope(a.ComputeContainerNode());
DCHECK(node_a);
bool has_descendent_a = node_a != a.ComputeContainerNode();
int offset_a = has_descendent_a ? 0 : a.ComputeOffsetInContainerNode();
Node* node_b = common_scope->AncestorInThisScope(b.ComputeContainerNode());
DCHECK(node_b);
bool has_descendent_b = node_b != b.ComputeContainerNode();
int offset_b = has_descendent_b ? 0 : b.ComputeOffsetInContainerNode();
int bias = 0;
if (node_a == node_b) {
if (has_descendent_a)
bias = -1;
else if (has_descendent_b)
bias = 1;
}
int result = ComparePositionsInDOMTree(node_a, offset_a, node_b, offset_b);
return result ? result : bias;
}
int ComparePositions(const PositionWithAffinity& a,
const PositionWithAffinity& b) {
return ComparePositions(a.GetPosition(), b.GetPosition());
}
int ComparePositions(const VisiblePosition& a, const VisiblePosition& b) {
return ComparePositions(a.DeepEquivalent(), b.DeepEquivalent());
}
bool IsNodeFullyContained(const EphemeralRange& range, const Node& node) {
if (range.IsNull())
return false;
if (!NodeTraversal::CommonAncestor(*range.StartPosition().AnchorNode(), node))
return false;
return range.StartPosition() <= Position::BeforeNode(node) &&
Position::AfterNode(node) <= range.EndPosition();
}
// TODO(editing-dev): We should implement real version which refers
// "user-select" CSS property.
// TODO(editing-dev): We should make |SelectionAdjuster| to use this funciton
// instead of |isSelectionBondary()|.
bool IsUserSelectContain(const Node& node) {
return IsHTMLTextAreaElement(node) || IsHTMLInputElement(node) ||
IsHTMLSelectElement(node);
}
enum EditableLevel { kEditable, kRichlyEditable };
static bool HasEditableLevel(const Node& node, EditableLevel editable_level) {
DCHECK(node.GetDocument().IsActive());
// TODO(editing-dev): We should have this check:
// DCHECK_GE(node.document().lifecycle().state(),
// DocumentLifecycle::StyleClean);
if (node.IsPseudoElement())
return false;
// Ideally we'd call DCHECK(!needsStyleRecalc()) here, but
// ContainerNode::setFocus() calls setNeedsStyleRecalc(), so the assertion
// would fire in the middle of Document::setFocusedNode().
for (const Node& ancestor : NodeTraversal::InclusiveAncestorsOf(node)) {
if (!(ancestor.IsHTMLElement() || ancestor.IsDocumentNode()))
continue;
const ComputedStyle* style = ancestor.GetComputedStyle();
if (!style)
continue;
switch (style->UserModify()) {
case EUserModify::kReadOnly:
return false;
case EUserModify::kReadWrite:
return true;
case EUserModify::kReadWritePlaintextOnly:
return editable_level != kRichlyEditable;
}
}
return false;
}
bool HasEditableStyle(const Node& node) {
// TODO(editing-dev): We shouldn't check editable style in inactive documents.
// We should hoist this check in the call stack, replace it by a DCHECK of
// active document and ultimately cleanup the code paths with inactive
// documents. See crbug.com/667681
if (!node.GetDocument().IsActive())
return false;
return HasEditableLevel(node, kEditable);
}
bool HasRichlyEditableStyle(const Node& node) {
// TODO(editing-dev): We shouldn't check editable style in inactive documents.
// We should hoist this check in the call stack, replace it by a DCHECK of
// active document and ultimately cleanup the code paths with inactive
// documents. See crbug.com/667681
if (!node.GetDocument().IsActive())
return false;
return HasEditableLevel(node, kRichlyEditable);
}
bool IsRootEditableElement(const Node& node) {
return HasEditableStyle(node) && node.IsElementNode() &&
(!node.parentNode() || !HasEditableStyle(*node.parentNode()) ||
!node.parentNode()->IsElementNode() ||
&node == node.GetDocument().body());
}
Element* RootEditableElement(const Node& node) {
const Node* result = nullptr;
for (const Node* n = &node; n && HasEditableStyle(*n); n = n->parentNode()) {
if (n->IsElementNode())
result = n;
if (node.GetDocument().body() == n)
break;
}
return ToElement(const_cast<Node*>(result));
}
ContainerNode* HighestEditableRoot(
const Position& position,
Element* (*root_editable_element_of)(const Position&),
bool (*has_editable_style)(const Node&)) {
if (position.IsNull())
return nullptr;
ContainerNode* highest_root = root_editable_element_of(position);
if (!highest_root)
return nullptr;
if (IsHTMLBodyElement(*highest_root))
return highest_root;
ContainerNode* node = highest_root->parentNode();
while (node) {
if (has_editable_style(*node))
highest_root = node;
if (IsHTMLBodyElement(*node))
break;
node = node->parentNode();
}
return highest_root;
}
ContainerNode* HighestEditableRoot(const PositionInFlatTree& position) {
return HighestEditableRoot(ToPositionInDOMTree(position));
}
bool IsEditablePosition(const Position& position) {
const Node* node = position.ParentAnchoredEquivalent().AnchorNode();
if (!node)
return false;
DCHECK(node->GetDocument().IsActive());
if (node->GetDocument().Lifecycle().GetState() >=
DocumentLifecycle::kInStyleRecalc) {
// TODO(yosin): Update the condition and DCHECK here given that
// https://codereview.chromium.org/2665823002/ avoided this function from
// being called during InStyleRecalc.
} else {
DCHECK(!NeedsLayoutTreeUpdate(position)) << position;
}
if (IsDisplayInsideTable(node))
node = node->parentNode();
if (node->IsDocumentNode())
return false;
return HasEditableStyle(*node);
}
bool IsEditablePosition(const PositionInFlatTree& p) {
return IsEditablePosition(ToPositionInDOMTree(p));
}
bool IsRichlyEditablePosition(const Position& p) {
const Node* node = p.AnchorNode();
if (!node)
return false;
if (IsDisplayInsideTable(node))
node = node->parentNode();
return HasRichlyEditableStyle(*node);
}
Element* RootEditableElementOf(const Position& p) {
Node* node = p.ComputeContainerNode();
if (!node)
return nullptr;
if (IsDisplayInsideTable(node))
node = node->parentNode();
return RootEditableElement(*node);
}
Element* RootEditableElementOf(const PositionInFlatTree& p) {
return RootEditableElementOf(ToPositionInDOMTree(p));
}
template <typename Strategy>
PositionTemplate<Strategy> NextCandidateAlgorithm(
const PositionTemplate<Strategy>& position) {
TRACE_EVENT0("input", "EditingUtility::nextCandidateAlgorithm");
PositionIteratorAlgorithm<Strategy> p(position);
p.Increment();
while (!p.AtEnd()) {
PositionTemplate<Strategy> candidate = p.ComputePosition();
if (IsVisuallyEquivalentCandidate(candidate))
return candidate;
p.Increment();
}
return PositionTemplate<Strategy>();
}
Position NextCandidate(const Position& position) {
return NextCandidateAlgorithm<EditingStrategy>(position);
}
PositionInFlatTree NextCandidate(const PositionInFlatTree& position) {
return NextCandidateAlgorithm<EditingInFlatTreeStrategy>(position);
}
// |nextVisuallyDistinctCandidate| is similar to |nextCandidate| except
// for returning position which |downstream()| not equal to initial position's
// |downstream()|.
template <typename Strategy>
static PositionTemplate<Strategy> NextVisuallyDistinctCandidateAlgorithm(
const PositionTemplate<Strategy>& position) {
TRACE_EVENT0("input",
"EditingUtility::nextVisuallyDistinctCandidateAlgorithm");
if (position.IsNull())
return PositionTemplate<Strategy>();
PositionIteratorAlgorithm<Strategy> p(position);
const PositionTemplate<Strategy> downstream_start =
MostForwardCaretPosition(position);
p.Increment();
while (!p.AtEnd()) {
PositionTemplate<Strategy> candidate = p.ComputePosition();
if (IsVisuallyEquivalentCandidate(candidate) &&
MostForwardCaretPosition(candidate) != downstream_start)
return candidate;
p.Increment();
}
return PositionTemplate<Strategy>();
}
Position NextVisuallyDistinctCandidate(const Position& position) {
return NextVisuallyDistinctCandidateAlgorithm<EditingStrategy>(position);
}
PositionInFlatTree NextVisuallyDistinctCandidate(
const PositionInFlatTree& position) {
return NextVisuallyDistinctCandidateAlgorithm<EditingInFlatTreeStrategy>(
position);
}
template <typename Strategy>
PositionTemplate<Strategy> PreviousCandidateAlgorithm(
const PositionTemplate<Strategy>& position) {
TRACE_EVENT0("input", "EditingUtility::previousCandidateAlgorithm");
PositionIteratorAlgorithm<Strategy> p(position);
p.Decrement();
while (!p.AtStart()) {
PositionTemplate<Strategy> candidate = p.ComputePosition();
if (IsVisuallyEquivalentCandidate(candidate))
return candidate;
p.Decrement();
}
return PositionTemplate<Strategy>();
}
Position PreviousCandidate(const Position& position) {
return PreviousCandidateAlgorithm<EditingStrategy>(position);
}
PositionInFlatTree PreviousCandidate(const PositionInFlatTree& position) {
return PreviousCandidateAlgorithm<EditingInFlatTreeStrategy>(position);
}
// |previousVisuallyDistinctCandidate| is similar to |previousCandidate| except
// for returning position which |downstream()| not equal to initial position's
// |downstream()|.
template <typename Strategy>
PositionTemplate<Strategy> PreviousVisuallyDistinctCandidateAlgorithm(
const PositionTemplate<Strategy>& position) {
TRACE_EVENT0("input",
"EditingUtility::previousVisuallyDistinctCandidateAlgorithm");
if (position.IsNull())
return PositionTemplate<Strategy>();
PositionIteratorAlgorithm<Strategy> p(position);
PositionTemplate<Strategy> downstream_start =
MostForwardCaretPosition(position);
p.Decrement();
while (!p.AtStart()) {
PositionTemplate<Strategy> candidate = p.ComputePosition();
if (IsVisuallyEquivalentCandidate(candidate) &&
MostForwardCaretPosition(candidate) != downstream_start)
return candidate;
p.Decrement();
}
return PositionTemplate<Strategy>();
}
Position PreviousVisuallyDistinctCandidate(const Position& position) {
return PreviousVisuallyDistinctCandidateAlgorithm<EditingStrategy>(position);
}
PositionInFlatTree PreviousVisuallyDistinctCandidate(
const PositionInFlatTree& position) {
return PreviousVisuallyDistinctCandidateAlgorithm<EditingInFlatTreeStrategy>(
position);
}
VisiblePosition FirstEditableVisiblePositionAfterPositionInRoot(
const Position& position,
ContainerNode& highest_root) {
DCHECK(!NeedsLayoutTreeUpdate(position));
return CreateVisiblePosition(
FirstEditablePositionAfterPositionInRoot(position, highest_root));
}
VisiblePositionInFlatTree FirstEditableVisiblePositionAfterPositionInRoot(
const PositionInFlatTree& position,
ContainerNode& highest_root) {
DCHECK(!NeedsLayoutTreeUpdate(position));
return CreateVisiblePosition(
FirstEditablePositionAfterPositionInRoot(position, highest_root));
}
template <typename Strategy>
PositionTemplate<Strategy> FirstEditablePositionAfterPositionInRootAlgorithm(
const PositionTemplate<Strategy>& position,
const Node& highest_root) {
DCHECK(!NeedsLayoutTreeUpdate(highest_root))
<< position << ' ' << highest_root;
// position falls before highestRoot.
if (position.CompareTo(PositionTemplate<Strategy>::FirstPositionInNode(
highest_root)) == -1 &&
HasEditableStyle(highest_root))
return PositionTemplate<Strategy>::FirstPositionInNode(highest_root);
PositionTemplate<Strategy> editable_position = position;
if (position.AnchorNode()->GetTreeScope() != highest_root.GetTreeScope()) {
Node* shadow_ancestor = highest_root.GetTreeScope().AncestorInThisScope(
editable_position.AnchorNode());
if (!shadow_ancestor)
return PositionTemplate<Strategy>();
editable_position = PositionTemplate<Strategy>::AfterNode(*shadow_ancestor);
}
Node* non_editable_node = nullptr;
while (editable_position.AnchorNode() &&
!IsEditablePosition(editable_position) &&
editable_position.AnchorNode()->IsDescendantOf(&highest_root)) {
non_editable_node = editable_position.AnchorNode();
editable_position = IsAtomicNode(editable_position.AnchorNode())
? PositionTemplate<Strategy>::InParentAfterNode(
*editable_position.AnchorNode())
: NextVisuallyDistinctCandidate(editable_position);
}
if (editable_position.AnchorNode() &&
editable_position.AnchorNode() != &highest_root &&
!editable_position.AnchorNode()->IsDescendantOf(&highest_root))
return PositionTemplate<Strategy>();
// If |editablePosition| has the non-editable child skipped, get the next
// sibling position. If not, we can't get the next paragraph in
// InsertListCommand::doApply's while loop. See http://crbug.com/571420
if (non_editable_node &&
non_editable_node->IsDescendantOf(editable_position.AnchorNode()))
editable_position = NextVisuallyDistinctCandidate(editable_position);
return editable_position;
}
Position FirstEditablePositionAfterPositionInRoot(const Position& position,
const Node& highest_root) {
return FirstEditablePositionAfterPositionInRootAlgorithm<EditingStrategy>(
position, highest_root);
}
PositionInFlatTree FirstEditablePositionAfterPositionInRoot(
const PositionInFlatTree& position,
const Node& highest_root) {
return FirstEditablePositionAfterPositionInRootAlgorithm<
EditingInFlatTreeStrategy>(position, highest_root);
}
VisiblePosition LastEditableVisiblePositionBeforePositionInRoot(
const Position& position,
ContainerNode& highest_root) {
DCHECK(!NeedsLayoutTreeUpdate(position));
return CreateVisiblePosition(
LastEditablePositionBeforePositionInRoot(position, highest_root));
}
VisiblePositionInFlatTree LastEditableVisiblePositionBeforePositionInRoot(
const PositionInFlatTree& position,
ContainerNode& highest_root) {
DCHECK(!NeedsLayoutTreeUpdate(position));
return CreateVisiblePosition(
LastEditablePositionBeforePositionInRoot(position, highest_root));
}
template <typename Strategy>
PositionTemplate<Strategy> LastEditablePositionBeforePositionInRootAlgorithm(
const PositionTemplate<Strategy>& position,
const Node& highest_root) {
DCHECK(!NeedsLayoutTreeUpdate(highest_root))
<< position << ' ' << highest_root;
// When position falls after highestRoot, the result is easy to compute.
if (position.CompareTo(
PositionTemplate<Strategy>::LastPositionInNode(highest_root)) == 1)
return PositionTemplate<Strategy>::LastPositionInNode(highest_root);
PositionTemplate<Strategy> editable_position = position;
if (position.AnchorNode()->GetTreeScope() != highest_root.GetTreeScope()) {
Node* shadow_ancestor = highest_root.GetTreeScope().AncestorInThisScope(
editable_position.AnchorNode());
if (!shadow_ancestor)
return PositionTemplate<Strategy>();
editable_position = PositionTemplate<Strategy>::FirstPositionInOrBeforeNode(
*shadow_ancestor);
}
while (editable_position.AnchorNode() &&
!IsEditablePosition(editable_position) &&
editable_position.AnchorNode()->IsDescendantOf(&highest_root))
editable_position =
IsAtomicNode(editable_position.AnchorNode())
? PositionTemplate<Strategy>::InParentBeforeNode(
*editable_position.AnchorNode())
: PreviousVisuallyDistinctCandidate(editable_position);
if (editable_position.AnchorNode() &&
editable_position.AnchorNode() != &highest_root &&
!editable_position.AnchorNode()->IsDescendantOf(&highest_root))
return PositionTemplate<Strategy>();
return editable_position;
}
Position LastEditablePositionBeforePositionInRoot(const Position& position,
const Node& highest_root) {
return LastEditablePositionBeforePositionInRootAlgorithm<EditingStrategy>(
position, highest_root);
}
PositionInFlatTree LastEditablePositionBeforePositionInRoot(
const PositionInFlatTree& position,
const Node& highest_root) {
return LastEditablePositionBeforePositionInRootAlgorithm<
EditingInFlatTreeStrategy>(position, highest_root);
}
template <typename StateMachine>
int FindNextBoundaryOffset(const String& str, int current) {
StateMachine machine;
TextSegmentationMachineState state = TextSegmentationMachineState::kInvalid;
for (int i = current - 1; i >= 0; --i) {
state = machine.FeedPrecedingCodeUnit(str[i]);
if (state != TextSegmentationMachineState::kNeedMoreCodeUnit)
break;
}
if (current == 0 || state == TextSegmentationMachineState::kNeedMoreCodeUnit)
state = machine.TellEndOfPrecedingText();
if (state == TextSegmentationMachineState::kFinished)
return current + machine.FinalizeAndGetBoundaryOffset();
const int length = str.length();
DCHECK_EQ(TextSegmentationMachineState::kNeedFollowingCodeUnit, state);
for (int i = current; i < length; ++i) {
state = machine.FeedFollowingCodeUnit(str[i]);
if (state != TextSegmentationMachineState::kNeedMoreCodeUnit)
break;
}
return current + machine.FinalizeAndGetBoundaryOffset();
}
int PreviousGraphemeBoundaryOf(const Node& node, int current) {
// TODO(yosin): Need to support grapheme crossing |Node| boundary.
DCHECK_GE(current, 0);
if (current <= 1 || !node.IsTextNode())
return current - 1;
const String& text = ToText(node).data();
// TODO(yosin): Replace with DCHECK for out-of-range request.
if (static_cast<unsigned>(current) > text.length())
return current - 1;
return FindNextBoundaryOffset<BackwardGraphemeBoundaryStateMachine>(text,
current);
}
static int PreviousBackwardDeletionOffsetOf(const Node& node, int current) {
DCHECK_GE(current, 0);
if (current <= 1)
return 0;
if (!node.IsTextNode())
return current - 1;
const String& text = ToText(node).data();
DCHECK_LT(static_cast<unsigned>(current - 1), text.length());
return FindNextBoundaryOffset<BackspaceStateMachine>(text, current);
}
int NextGraphemeBoundaryOf(const Node& node, int current) {
// TODO(yosin): Need to support grapheme crossing |Node| boundary.
if (!node.IsTextNode())
return current + 1;
const String& text = ToText(node).data();
const int length = text.length();
DCHECK_LE(current, length);
if (current >= length - 1)
return current + 1;
return FindNextBoundaryOffset<ForwardGraphemeBoundaryStateMachine>(text,
current);
}
template <typename Strategy>
PositionTemplate<Strategy> PreviousPositionOfAlgorithm(
const PositionTemplate<Strategy>& position,
PositionMoveType move_type) {
Node* const node = position.AnchorNode();
if (!node)
return position;
const int offset = position.ComputeEditingOffset();
if (offset > 0) {
if (EditingIgnoresContent(*node))
return PositionTemplate<Strategy>::BeforeNode(*node);
if (Node* child = Strategy::ChildAt(*node, offset - 1)) {
return PositionTemplate<Strategy>::LastPositionInOrAfterNode(*child);
}
// There are two reasons child might be 0:
// 1) The node is node like a text node that is not an element, and
// therefore has no children. Going backward one character at a
// time is correct.
// 2) The old offset was a bogus offset like (<br>, 1), and there is
// no child. Going from 1 to 0 is correct.
switch (move_type) {
case PositionMoveType::kCodeUnit:
return PositionTemplate<Strategy>(node, offset - 1);
case PositionMoveType::kBackwardDeletion:
return PositionTemplate<Strategy>(
node, PreviousBackwardDeletionOffsetOf(*node, offset));
case PositionMoveType::kGraphemeCluster:
return PositionTemplate<Strategy>(
node, PreviousGraphemeBoundaryOf(*node, offset));
default:
NOTREACHED() << "Unhandled moveType: " << move_type;
}
}
if (ContainerNode* parent = Strategy::Parent(*node)) {
if (EditingIgnoresContent(*parent))
return PositionTemplate<Strategy>::BeforeNode(*parent);
// TODO(yosin) We should use |Strategy::index(Node&)| instead of
// |Node::nodeIndex()|.
return PositionTemplate<Strategy>(parent, node->NodeIndex());
}
return position;
}
Position PreviousPositionOf(const Position& position,
PositionMoveType move_type) {
return PreviousPositionOfAlgorithm<EditingStrategy>(position, move_type);
}
PositionInFlatTree PreviousPositionOf(const PositionInFlatTree& position,
PositionMoveType move_type) {
return PreviousPositionOfAlgorithm<EditingInFlatTreeStrategy>(position,
move_type);
}
template <typename Strategy>
PositionTemplate<Strategy> NextPositionOfAlgorithm(
const PositionTemplate<Strategy>& position,
PositionMoveType move_type) {
// TODO(yosin): We should have printer for PositionMoveType.
DCHECK(move_type != PositionMoveType::kBackwardDeletion);
Node* node = position.AnchorNode();
if (!node)
return position;
const int offset = position.ComputeEditingOffset();
if (Node* child = Strategy::ChildAt(*node, offset)) {
return PositionTemplate<Strategy>::FirstPositionInOrBeforeNode(*child);
}
// TODO(yosin) We should use |Strategy::lastOffsetForEditing()| instead of
// DOM tree version.
if (!Strategy::HasChildren(*node) &&
offset < EditingStrategy::LastOffsetForEditing(node)) {
// There are two reasons child might be 0:
// 1) The node is node like a text node that is not an element, and
// therefore has no children. Going forward one character at a time
// is correct.
// 2) The new offset is a bogus offset like (<br>, 1), and there is no
// child. Going from 0 to 1 is correct.
switch (move_type) {
case PositionMoveType::kCodeUnit:
return PositionTemplate<Strategy>::EditingPositionOf(node, offset + 1);
case PositionMoveType::kBackwardDeletion:
NOTREACHED() << "BackwardDeletion is only available for prevPositionOf "
<< "functions.";
return PositionTemplate<Strategy>::EditingPositionOf(node, offset + 1);
case PositionMoveType::kGraphemeCluster:
return PositionTemplate<Strategy>::EditingPositionOf(
node, NextGraphemeBoundaryOf(*node, offset));
default:
NOTREACHED() << "Unhandled moveType: " << move_type;
}
}
if (ContainerNode* parent = Strategy::Parent(*node))
return PositionTemplate<Strategy>::EditingPositionOf(
parent, Strategy::Index(*node) + 1);
return position;
}
Position NextPositionOf(const Position& position, PositionMoveType move_type) {
return NextPositionOfAlgorithm<EditingStrategy>(position, move_type);
}
PositionInFlatTree NextPositionOf(const PositionInFlatTree& position,
PositionMoveType move_type) {
return NextPositionOfAlgorithm<EditingInFlatTreeStrategy>(position,
move_type);
}
bool IsEnclosingBlock(const Node* node) {
return node && node->GetLayoutObject() &&
!node->GetLayoutObject()->IsInline() &&
!node->GetLayoutObject()->IsRubyText();
}
bool IsInline(const Node* node) {
if (!node)
return false;
const ComputedStyle* style = node->GetComputedStyle();
return style && style->Display() == EDisplay::kInline;
}
// TODO(yosin) Deploy this in all of the places where |enclosingBlockFlow()| and
// |enclosingBlockFlowOrTableElement()| are used.
// TODO(yosin) Callers of |Node| version of |enclosingBlock()| should use
// |Position| version The enclosing block of [table, x] for example, should be
// the block that contains the table and not the table, and this function should
// be the only one responsible for knowing about these kinds of special cases.
Element* EnclosingBlock(const Node* node, EditingBoundaryCrossingRule rule) {
if (!node)
return nullptr;
return EnclosingBlock(FirstPositionInOrBeforeNode(*node), rule);
}
template <typename Strategy>
Element* EnclosingBlockAlgorithm(const PositionTemplate<Strategy>& position,
EditingBoundaryCrossingRule rule) {
Node* enclosing_node = EnclosingNodeOfType(position, IsEnclosingBlock, rule);
return enclosing_node && enclosing_node->IsElementNode()
? ToElement(enclosing_node)
: nullptr;
}
Element* EnclosingBlock(const Position& position,
EditingBoundaryCrossingRule rule) {
return EnclosingBlockAlgorithm<EditingStrategy>(position, rule);
}
Element* EnclosingBlock(const PositionInFlatTree& position,
EditingBoundaryCrossingRule rule) {
return EnclosingBlockAlgorithm<EditingInFlatTreeStrategy>(position, rule);
}
Element* EnclosingBlockFlowElement(const Node& node) {
if (IsBlockFlowElement(node))
return const_cast<Element*>(&ToElement(node));
for (Node& runner : NodeTraversal::AncestorsOf(node)) {
if (IsBlockFlowElement(runner) || IsHTMLBodyElement(runner))
return ToElement(&runner);
}
return nullptr;
}
EUserSelect UsedValueOfUserSelect(const Node& node) {
if (node.IsHTMLElement() && ToHTMLElement(node).IsTextControl())
return EUserSelect::kText;
if (!node.GetLayoutObject())
return EUserSelect::kNone;
const ComputedStyle* style = node.GetLayoutObject()->Style();
if (style->UserModify() != EUserModify::kReadOnly)
return EUserSelect::kText;
return style->UserSelect();
}
template <typename Strategy>
TextDirection DirectionOfEnclosingBlockOfAlgorithm(
const PositionTemplate<Strategy>& position) {
DCHECK(position.IsNotNull());
Element* enclosing_block_element =
EnclosingBlock(PositionTemplate<Strategy>::FirstPositionInOrBeforeNode(
*position.ComputeContainerNode()),
kCannotCrossEditingBoundary);
if (!enclosing_block_element)
return TextDirection::kLtr;
LayoutObject* layout_object = enclosing_block_element->GetLayoutObject();
return layout_object ? layout_object->Style()->Direction()
: TextDirection::kLtr;
}
TextDirection DirectionOfEnclosingBlockOf(const Position& position) {
return DirectionOfEnclosingBlockOfAlgorithm<EditingStrategy>(position);
}
TextDirection DirectionOfEnclosingBlockOf(const PositionInFlatTree& position) {
return DirectionOfEnclosingBlockOfAlgorithm<EditingInFlatTreeStrategy>(
position);
}
TextDirection PrimaryDirectionOf(const Node& node) {
TextDirection primary_direction = TextDirection::kLtr;
for (const LayoutObject* r = node.GetLayoutObject(); r; r = r->Parent()) {
if (r->IsLayoutBlockFlow()) {
primary_direction = r->Style()->Direction();
break;
}
}
return primary_direction;
}
String StringWithRebalancedWhitespace(const String& string,
bool start_is_start_of_paragraph,
bool should_emit_nbs_pbefore_end) {
unsigned length = string.length();
StringBuilder rebalanced_string;
rebalanced_string.ReserveCapacity(length);
UChar char_to_append = 0;
for (size_t index = 0; index < length; index++) {
char_to_append = WhitespaceRebalancingCharToAppend(
string, start_is_start_of_paragraph, should_emit_nbs_pbefore_end, index,
char_to_append);
rebalanced_string.Append(char_to_append);
}
DCHECK_EQ(rebalanced_string.length(), length);
return rebalanced_string.ToString();
}
bool IsTableStructureNode(const Node* node) {
LayoutObject* layout_object = node->GetLayoutObject();
return (layout_object &&
(layout_object->IsTableCell() || layout_object->IsTableRow() ||
layout_object->IsTableSection() ||
layout_object->IsLayoutTableCol()));
}
const String& NonBreakingSpaceString() {
DEFINE_STATIC_LOCAL(String, non_breaking_space_string,
(&kNoBreakSpaceCharacter, 1));
return non_breaking_space_string;
}
String RepeatString(const String& string, unsigned count) {
StringBuilder builder;
builder.ReserveCapacity(string.length() * count);
for (unsigned counter = 0; counter < count; ++counter)
builder.Append(string);
return builder.ToString();
}
// FIXME: need to dump this
static bool IsSpecialHTMLElement(const Node& n) {
if (!n.IsHTMLElement())
return false;
if (n.IsLink())
return true;
LayoutObject* layout_object = n.GetLayoutObject();
if (!layout_object)
return false;
if (layout_object->Style()->Display() == EDisplay::kTable ||
layout_object->Style()->Display() == EDisplay::kInlineTable)
return true;
if (layout_object->Style()->IsFloating())
return true;
return false;
}
static HTMLElement* FirstInSpecialElement(const Position& pos) {
DCHECK(!NeedsLayoutTreeUpdate(pos));
Element* element = RootEditableElement(*pos.ComputeContainerNode());
for (Node& runner : NodeTraversal::InclusiveAncestorsOf(*pos.AnchorNode())) {
if (RootEditableElement(runner) != element)
break;
if (IsSpecialHTMLElement(runner)) {
HTMLElement* special_element = ToHTMLElement(&runner);
VisiblePosition v_pos = CreateVisiblePosition(pos);
VisiblePosition first_in_element =
CreateVisiblePosition(FirstPositionInOrBeforeNode(*special_element));
if (IsDisplayInsideTable(special_element) &&
v_pos.DeepEquivalent() ==
NextPositionOf(first_in_element).DeepEquivalent())
return special_element;
if (v_pos.DeepEquivalent() == first_in_element.DeepEquivalent())
return special_element;
}
}
return nullptr;
}
static HTMLElement* LastInSpecialElement(const Position& pos) {
DCHECK(!NeedsLayoutTreeUpdate(pos));
Element* element = RootEditableElement(*pos.ComputeContainerNode());
for (Node& runner : NodeTraversal::InclusiveAncestorsOf(*pos.AnchorNode())) {
if (RootEditableElement(runner) != element)
break;
if (IsSpecialHTMLElement(runner)) {
HTMLElement* special_element = ToHTMLElement(&runner);
VisiblePosition v_pos = CreateVisiblePosition(pos);
VisiblePosition last_in_element =
CreateVisiblePosition(LastPositionInOrAfterNode(*special_element));
if (IsDisplayInsideTable(special_element) &&
v_pos.DeepEquivalent() ==
PreviousPositionOf(last_in_element).DeepEquivalent())
return special_element;
if (v_pos.DeepEquivalent() == last_in_element.DeepEquivalent())
return special_element;
}
}
return nullptr;
}
Position PositionBeforeContainingSpecialElement(
const Position& pos,
HTMLElement** containing_special_element) {
DCHECK(!NeedsLayoutTreeUpdate(pos));
HTMLElement* n = FirstInSpecialElement(pos);
if (!n)
return pos;
Position result = Position::InParentBeforeNode(*n);
if (result.IsNull() || RootEditableElement(*result.AnchorNode()) !=
RootEditableElement(*pos.AnchorNode()))
return pos;
if (containing_special_element)
*containing_special_element = n;
return result;
}
Position PositionAfterContainingSpecialElement(
const Position& pos,
HTMLElement** containing_special_element) {
DCHECK(!NeedsLayoutTreeUpdate(pos));
HTMLElement* n = LastInSpecialElement(pos);
if (!n)
return pos;
Position result = Position::InParentAfterNode(*n);
if (result.IsNull() || RootEditableElement(*result.AnchorNode()) !=
RootEditableElement(*pos.AnchorNode()))
return pos;
if (containing_special_element)
*containing_special_element = n;
return result;
}
template <typename Strategy>
static Element* TableElementJustBeforeAlgorithm(
const VisiblePositionTemplate<Strategy>& visible_position) {
const PositionTemplate<Strategy> upstream(
MostBackwardCaretPosition(visible_position.DeepEquivalent()));
if (IsDisplayInsideTable(upstream.AnchorNode()) &&
upstream.AtLastEditingPositionForNode())
return ToElement(upstream.AnchorNode());
return nullptr;
}
Element* TableElementJustBefore(const VisiblePosition& visible_position) {
return TableElementJustBeforeAlgorithm<EditingStrategy>(visible_position);
}
Element* TableElementJustBefore(
const VisiblePositionInFlatTree& visible_position) {
return TableElementJustBeforeAlgorithm<EditingInFlatTreeStrategy>(
visible_position);
}
Element* TableElementJustAfter(const VisiblePosition& visible_position) {
Position downstream(
MostForwardCaretPosition(visible_position.DeepEquivalent()));
if (IsDisplayInsideTable(downstream.AnchorNode()) &&
downstream.AtFirstEditingPositionForNode())
return ToElement(downstream.AnchorNode());
return nullptr;
}
// Returns the visible position at the beginning of a node
VisiblePosition VisiblePositionBeforeNode(const Node& node) {
DCHECK(!NeedsLayoutTreeUpdate(node));
if (node.hasChildren())
return CreateVisiblePosition(FirstPositionInOrBeforeNode(node));
DCHECK(node.parentNode()) << node;
DCHECK(!node.parentNode()->IsShadowRoot()) << node.parentNode();
return VisiblePosition::InParentBeforeNode(node);
}
// Returns the visible position at the ending of a node
VisiblePosition VisiblePositionAfterNode(const Node& node) {
DCHECK(!NeedsLayoutTreeUpdate(node));
if (node.hasChildren())
return CreateVisiblePosition(LastPositionInOrAfterNode(node));
DCHECK(node.parentNode()) << node.parentNode();
DCHECK(!node.parentNode()->IsShadowRoot()) << node.parentNode();
return VisiblePosition::InParentAfterNode(node);
}
bool IsHTMLListElement(const Node* n) {
return (n && (IsHTMLUListElement(*n) || IsHTMLOListElement(*n) ||
IsHTMLDListElement(*n)));
}
bool IsListItem(const Node* n) {
return n && n->GetLayoutObject() && n->GetLayoutObject()->IsListItem();
}
bool IsPresentationalHTMLElement(const Node* node) {
if (!node->IsHTMLElement())
return false;
const HTMLElement& element = ToHTMLElement(*node);
return element.HasTagName(uTag) || element.HasTagName(sTag) ||
element.HasTagName(strikeTag) || element.HasTagName(iTag) ||
element.HasTagName(emTag) || element.HasTagName(bTag) ||
element.HasTagName(strongTag);
}
Element* AssociatedElementOf(const Position& position) {
Node* node = position.AnchorNode();
if (!node || node->IsElementNode())
return ToElement(node);
ContainerNode* parent = NodeTraversal::Parent(*node);
return parent && parent->IsElementNode() ? ToElement(parent) : nullptr;
}
Element* EnclosingElementWithTag(const Position& p,
const QualifiedName& tag_name) {
if (p.IsNull())
return nullptr;
ContainerNode* root = HighestEditableRoot(p);
Element* ancestor = p.AnchorNode()->IsElementNode()
? ToElement(p.AnchorNode())
: p.AnchorNode()->parentElement();
for (; ancestor; ancestor = ancestor->parentElement()) {
if (root && !HasEditableStyle(*ancestor))
continue;
if (ancestor->HasTagName(tag_name))
return ancestor;
if (ancestor == root)
return nullptr;
}
return nullptr;
}
template <typename Strategy>
static Node* EnclosingNodeOfTypeAlgorithm(const PositionTemplate<Strategy>& p,
bool (*node_is_of_type)(const Node*),
EditingBoundaryCrossingRule rule) {
// TODO(yosin) support CanSkipCrossEditingBoundary
DCHECK(rule == kCanCrossEditingBoundary ||
rule == kCannotCrossEditingBoundary)
<< rule;
if (p.IsNull())
return nullptr;
ContainerNode* const root =
rule == kCannotCrossEditingBoundary ? HighestEditableRoot(p) : nullptr;
for (Node* n = p.AnchorNode(); n; n = Strategy::Parent(*n)) {
// Don't return a non-editable node if the input position was editable,
// since the callers from editing will no doubt want to perform editing
// inside the returned node.
if (root && !HasEditableStyle(*n))
continue;
if (node_is_of_type(n))
return n;
if (n == root)
return nullptr;
}
return nullptr;
}
Node* EnclosingNodeOfType(const Position& p,
bool (*node_is_of_type)(const Node*),
EditingBoundaryCrossingRule rule) {
return EnclosingNodeOfTypeAlgorithm<EditingStrategy>(p, node_is_of_type,
rule);
}
Node* EnclosingNodeOfType(const PositionInFlatTree& p,
bool (*node_is_of_type)(const Node*),
EditingBoundaryCrossingRule rule) {
return EnclosingNodeOfTypeAlgorithm<EditingInFlatTreeStrategy>(
p, node_is_of_type, rule);
}
Node* HighestEnclosingNodeOfType(const Position& p,
bool (*node_is_of_type)(const Node*),
EditingBoundaryCrossingRule rule,
Node* stay_within) {
Node* highest = nullptr;
ContainerNode* root =
rule == kCannotCrossEditingBoundary ? HighestEditableRoot(p) : nullptr;
for (Node* n = p.ComputeContainerNode(); n && n != stay_within;
n = n->parentNode()) {
if (root && !HasEditableStyle(*n))
continue;
if (node_is_of_type(n))
highest = n;
if (n == root)
break;
}
return highest;
}
static bool HasARenderedDescendant(const Node* node,
const Node* excluded_node) {
for (const Node* n = node->firstChild(); n;) {
if (n == excluded_node) {
n = NodeTraversal::NextSkippingChildren(*n, node);
continue;
}
if (n->GetLayoutObject())
return true;
n = NodeTraversal::Next(*n, node);
}
return false;
}
Node* HighestNodeToRemoveInPruning(Node* node, const Node* exclude_node) {
Node* previous_node = nullptr;
Element* element = node ? RootEditableElement(*node) : nullptr;
for (; node; node = node->parentNode()) {
if (LayoutObject* layout_object = node->GetLayoutObject()) {
if (!layout_object->CanHaveChildren() ||
HasARenderedDescendant(node, previous_node) || element == node ||
exclude_node == node)
return previous_node;
}
previous_node = node;
}
return nullptr;
}
Element* EnclosingTableCell(const Position& p) {
return ToElement(EnclosingNodeOfType(p, IsTableCell));
}
Element* EnclosingAnchorElement(const Position& p) {
if (p.IsNull())
return nullptr;
for (Element* ancestor =
ElementTraversal::FirstAncestorOrSelf(*p.AnchorNode());
ancestor; ancestor = ElementTraversal::FirstAncestor(*ancestor)) {
if (ancestor->IsLink())
return ancestor;
}
return nullptr;
}
HTMLElement* EnclosingList(const Node* node) {
if (!node)
return nullptr;
ContainerNode* root = HighestEditableRoot(FirstPositionInOrBeforeNode(*node));
for (Node& runner : NodeTraversal::AncestorsOf(*node)) {
if (IsHTMLUListElement(runner) || IsHTMLOListElement(runner))
return ToHTMLElement(&runner);
if (runner == root)
return nullptr;
}
return nullptr;
}
Node* EnclosingListChild(const Node* node) {
if (!node)
return nullptr;
// Check for a list item element, or for a node whose parent is a list
// element. Such a node will appear visually as a list item (but without a
// list marker)
ContainerNode* root = HighestEditableRoot(FirstPositionInOrBeforeNode(*node));
// FIXME: This function is inappropriately named if it starts with node
// instead of node->parentNode()
// TODO(editing-dev): We should make this function return |const Node*| and
// make const_cast<> restricted inside editing/commands.
for (Node* n = const_cast<Node*>(node); n && n->parentNode();
n = n->parentNode()) {
if (IsHTMLLIElement(*n) ||
(IsHTMLListElement(n->parentNode()) && n != root))
return n;
if (n == root || IsTableCell(n))
return nullptr;
}
return nullptr;
}
// FIXME: This method should not need to call
// isStartOfParagraph/isEndOfParagraph
Node* EnclosingEmptyListItem(const VisiblePosition& visible_pos) {
DCHECK(visible_pos.IsValid());
// Check that position is on a line by itself inside a list item
Node* list_child_node =
EnclosingListChild(visible_pos.DeepEquivalent().AnchorNode());
if (!list_child_node || !IsStartOfParagraph(visible_pos) ||
!IsEndOfParagraph(visible_pos))
return nullptr;
VisiblePosition first_in_list_child =
CreateVisiblePosition(FirstPositionInOrBeforeNode(*list_child_node));
VisiblePosition last_in_list_child =
CreateVisiblePosition(LastPositionInOrAfterNode(*list_child_node));
if (first_in_list_child.DeepEquivalent() != visible_pos.DeepEquivalent() ||
last_in_list_child.DeepEquivalent() != visible_pos.DeepEquivalent())
return nullptr;
return list_child_node;
}
HTMLElement* OutermostEnclosingList(const Node* node,
const HTMLElement* root_list) {
HTMLElement* list = EnclosingList(node);
if (!list)
return nullptr;
while (HTMLElement* next_list = EnclosingList(list)) {
if (next_list == root_list)
break;
list = next_list;
}
return list;
}
// Determines whether two positions are visibly next to each other (first then
// second) while ignoring whitespaces and unrendered nodes
static bool IsVisiblyAdjacent(const Position& first, const Position& second) {
return CreateVisiblePosition(first).DeepEquivalent() ==
CreateVisiblePosition(MostBackwardCaretPosition(second))
.DeepEquivalent();
}
bool CanMergeLists(const Element& first_list, const Element& second_list) {
if (!first_list.IsHTMLElement() || !second_list.IsHTMLElement())
return false;
DCHECK(!NeedsLayoutTreeUpdate(first_list));
DCHECK(!NeedsLayoutTreeUpdate(second_list));
return first_list.HasTagName(
second_list
.TagQName()) // make sure the list types match (ol vs. ul)
&& HasEditableStyle(first_list) &&
HasEditableStyle(second_list) // both lists are editable
&&
RootEditableElement(first_list) ==
RootEditableElement(second_list) // don't cross editing boundaries
&& IsVisiblyAdjacent(Position::InParentAfterNode(first_list),
Position::InParentBeforeNode(second_list));
// Make sure there is no visible content between this li and the previous list
}
bool IsDisplayInsideTable(const Node* node) {
return node && node->GetLayoutObject() && IsHTMLTableElement(node);
}
bool IsTableCell(const Node* node) {
DCHECK(node);
LayoutObject* r = node->GetLayoutObject();
return r ? r->IsTableCell() : IsHTMLTableCellElement(*node);
}
bool IsEmptyTableCell(const Node* node) {
// Returns true IFF the passed in node is one of:
// .) a table cell with no children,
// .) a table cell with a single BR child, and which has no other child
// layoutObject, including :before and :after layoutObject
// .) the BR child of such a table cell
// Find rendered node
while (node && !node->GetLayoutObject())
node = node->parentNode();
if (!node)
return false;
// Make sure the rendered node is a table cell or <br>.
// If it's a <br>, then the parent node has to be a table cell.
LayoutObject* layout_object = node->GetLayoutObject();
if (layout_object->IsBR()) {
layout_object = layout_object->Parent();
if (!layout_object)
return false;
}
if (!layout_object->IsTableCell())
return false;
// Check that the table cell contains no child layoutObjects except for
// perhaps a single <br>.
LayoutObject* child_layout_object = layout_object->SlowFirstChild();
if (!child_layout_object)
return true;
if (!child_layout_object->IsBR())
return false;
return !child_layout_object->NextSibling();
}
HTMLElement* CreateDefaultParagraphElement(Document& document) {
switch (document.GetFrame()->GetEditor().DefaultParagraphSeparator()) {
case kEditorParagraphSeparatorIsDiv:
return HTMLDivElement::Create(document);
case kEditorParagraphSeparatorIsP:
return HTMLParagraphElement::Create(document);
}
NOTREACHED();
return nullptr;
}
HTMLElement* CreateHTMLElement(Document& document, const QualifiedName& name) {
return HTMLElementFactory::createHTMLElement(name.LocalName(), document,
kCreatedByCloneNode);
}
bool IsTabHTMLSpanElement(const Node* node) {
if (!IsHTMLSpanElement(node))
return false;
const Node* const first_child = NodeTraversal::FirstChild(*node);
if (!first_child || !first_child->IsTextNode())
return false;
if (!ToText(first_child)->data().Contains('\t'))
return false;
// TODO(editing-dev): Hoist the call of UpdateStyleAndLayoutTree to callers.
// See crbug.com/590369 for details.
node->GetDocument().UpdateStyleAndLayoutTree();
return node->GetComputedStyle()->WhiteSpace() == EWhiteSpace::kPre;
}
bool IsTabHTMLSpanElementTextNode(const Node* node) {
return node && node->IsTextNode() && node->parentNode() &&
IsTabHTMLSpanElement(node->parentNode());
}
HTMLSpanElement* TabSpanElement(const Node* node) {
return IsTabHTMLSpanElementTextNode(node)
? ToHTMLSpanElement(node->parentNode())
: nullptr;
}
static HTMLSpanElement* CreateTabSpanElement(Document& document,
Text* tab_text_node) {
// Make the span to hold the tab.
HTMLSpanElement* span_element = HTMLSpanElement::Create(document);
span_element->setAttribute(styleAttr, "white-space:pre");
// Add tab text to that span.
if (!tab_text_node)
tab_text_node = document.CreateEditingTextNode("\t");
span_element->AppendChild(tab_text_node);
return span_element;
}
HTMLSpanElement* CreateTabSpanElement(Document& document,
const String& tab_text) {
return CreateTabSpanElement(document, document.createTextNode(tab_text));
}
HTMLSpanElement* CreateTabSpanElement(Document& document) {
return CreateTabSpanElement(document, nullptr);
}
bool IsNodeRendered(const Node& node) {
LayoutObject* layout_object = node.GetLayoutObject();
if (!layout_object)
return false;
return layout_object->Style()->Visibility() == EVisibility::kVisible;
}
// return first preceding DOM position rendered at a different location, or
// "this"
static Position PreviousCharacterPosition(const Position& position,
TextAffinity affinity) {
DCHECK(!NeedsLayoutTreeUpdate(position));
if (position.IsNull())
return Position();
Element* from_root_editable_element =
RootEditableElement(*position.AnchorNode());
bool at_start_of_line =
IsStartOfLine(CreateVisiblePosition(position, affinity));
bool rendered = IsVisuallyEquivalentCandidate(position);
Position current_pos = position;
while (!current_pos.AtStartOfTree()) {
// TODO(yosin) When we use |previousCharacterPosition()| other than
// finding leading whitespace, we should use |Character| instead of
// |CodePoint|.
current_pos = PreviousPositionOf(current_pos, PositionMoveType::kCodeUnit);
if (RootEditableElement(*current_pos.AnchorNode()) !=
from_root_editable_element)
return position;
if (at_start_of_line || !rendered) {
if (IsVisuallyEquivalentCandidate(current_pos))
return current_pos;
} else if (RendersInDifferentPosition(position, current_pos)) {
return current_pos;
}
}
return position;
}
// This assumes that it starts in editable content.
Position LeadingCollapsibleWhitespacePosition(const Position& position,
TextAffinity affinity,
WhitespacePositionOption option) {
DCHECK(!NeedsLayoutTreeUpdate(position));
DCHECK(IsEditablePosition(position)) << position;
if (position.IsNull())
return Position();
if (IsHTMLBRElement(*MostBackwardCaretPosition(position).AnchorNode()))
return Position();
const Position& prev = PreviousCharacterPosition(position, affinity);
if (prev == position)
return Position();
const Node* const anchor_node = prev.AnchorNode();
if (!anchor_node || !anchor_node->IsTextNode())
return Position();
if (EnclosingBlockFlowElement(*anchor_node) !=
EnclosingBlockFlowElement(*position.AnchorNode()))
return Position();
if (option == kNotConsiderNonCollapsibleWhitespace &&
anchor_node->GetLayoutObject() &&
!anchor_node->GetLayoutObject()->Style()->CollapseWhiteSpace())
return Position();
const String& string = ToText(anchor_node)->data();
const UChar previous_character = string[prev.ComputeOffsetInContainerNode()];
const bool is_space = option == kConsiderNonCollapsibleWhitespace
? (IsSpaceOrNewline(previous_character) ||
previous_character == kNoBreakSpaceCharacter)
: IsCollapsibleWhitespace(previous_character);
if (!is_space || !IsEditablePosition(prev))
return Position();
return prev;
}
// This assumes that it starts in editable content.
Position TrailingWhitespacePosition(const Position& position,
WhitespacePositionOption option) {
DCHECK(!NeedsLayoutTreeUpdate(position));
DCHECK(IsEditablePosition(position)) << position;
if (position.IsNull())
return Position();
VisiblePosition visible_position = CreateVisiblePosition(position);
UChar character_after_visible_position = CharacterAfter(visible_position);
bool is_space =
option == kConsiderNonCollapsibleWhitespace
? (IsSpaceOrNewline(character_after_visible_position) ||
character_after_visible_position == kNoBreakSpaceCharacter)
: IsCollapsibleWhitespace(character_after_visible_position);
// The space must not be in another paragraph and it must be editable.
if (is_space && !IsEndOfParagraph(visible_position) &&
NextPositionOf(visible_position, kCannotCrossEditingBoundary).IsNotNull())
return position;
return Position();
}
unsigned NumEnclosingMailBlockquotes(const Position& p) {
unsigned num = 0;
for (const Node* n = p.AnchorNode(); n; n = n->parentNode()) {
if (IsMailHTMLBlockquoteElement(n))
num++;
}
return num;
}
PositionWithAffinity PositionRespectingEditingBoundary(
const Position& position,
const LayoutPoint& local_point,
Node* target_node) {
if (!target_node->GetLayoutObject())
return PositionWithAffinity();
LayoutPoint selection_end_point = local_point;
Element* editable_element = RootEditableElementOf(position);
if (editable_element && !editable_element->contains(target_node)) {
if (!editable_element->GetLayoutObject())
return PositionWithAffinity();
FloatPoint absolute_point = target_node->GetLayoutObject()->LocalToAbsolute(
FloatPoint(selection_end_point));
selection_end_point = LayoutPoint(
editable_element->GetLayoutObject()->AbsoluteToLocal(absolute_point));
target_node = editable_element;
}
return target_node->GetLayoutObject()->PositionForPoint(selection_end_point);
}
Position ComputePositionForNodeRemoval(const Position& position,
const Node& node) {
if (position.IsNull())
return position;
switch (position.AnchorType()) {
case PositionAnchorType::kBeforeChildren:
if (!node.IsShadowIncludingInclusiveAncestorOf(
position.ComputeContainerNode())) {
return position;
}
return Position::InParentBeforeNode(node);
case PositionAnchorType::kAfterChildren:
if (!node.IsShadowIncludingInclusiveAncestorOf(
position.ComputeContainerNode())) {
return position;
}
return Position::InParentAfterNode(node);
case PositionAnchorType::kOffsetInAnchor:
if (position.ComputeContainerNode() == node.parentNode() &&
static_cast<unsigned>(position.OffsetInContainerNode()) >
node.NodeIndex()) {
return Position(position.ComputeContainerNode(),
position.OffsetInContainerNode() - 1);
}
if (!node.IsShadowIncludingInclusiveAncestorOf(
position.ComputeContainerNode())) {
return position;
}
return Position::InParentBeforeNode(node);
case PositionAnchorType::kAfterAnchor:
if (!node.IsShadowIncludingInclusiveAncestorOf(position.AnchorNode()))
return position;
return Position::InParentAfterNode(node);
case PositionAnchorType::kBeforeAnchor:
if (!node.IsShadowIncludingInclusiveAncestorOf(position.AnchorNode()))
return position;
return Position::InParentBeforeNode(node);
}
NOTREACHED() << "We should handle all PositionAnchorType";
return position;
}
bool IsMailHTMLBlockquoteElement(const Node* node) {
if (!node || !node->IsHTMLElement())
return false;
const HTMLElement& element = ToHTMLElement(*node);
return element.HasTagName(blockquoteTag) &&
element.getAttribute("type") == "cite";
}
bool LineBreakExistsAtVisiblePosition(const VisiblePosition& visible_position) {
return LineBreakExistsAtPosition(
MostForwardCaretPosition(visible_position.DeepEquivalent()));
}
bool LineBreakExistsAtPosition(const Position& position) {
if (position.IsNull())
return false;
if (IsHTMLBRElement(*position.AnchorNode()) &&
position.AtFirstEditingPositionForNode())
return true;
if (!position.AnchorNode()->GetLayoutObject())
return false;
if (!position.AnchorNode()->IsTextNode() ||
!position.AnchorNode()->GetLayoutObject()->Style()->PreserveNewline())
return false;
const Text* text_node = ToText(position.AnchorNode());
unsigned offset = position.OffsetInContainerNode();
return offset < text_node->length() && text_node->data()[offset] == '\n';
}
bool ElementCannotHaveEndTag(const Node& node) {
if (!node.IsHTMLElement())
return false;
return !ToHTMLElement(node).ShouldSerializeEndTag();
}
// Modifies selections that have an end point at the edge of a table
// that contains the other endpoint so that they don't confuse
// code that iterates over selected paragraphs.
VisibleSelection SelectionForParagraphIteration(
const VisibleSelection& original) {
VisibleSelection new_selection(original);
VisiblePosition start_of_selection(new_selection.VisibleStart());
VisiblePosition end_of_selection(new_selection.VisibleEnd());
// If the end of the selection to modify is just after a table, and if the
// start of the selection is inside that table, then the last paragraph that
// we'll want modify is the last one inside the table, not the table itself (a
// table is itself a paragraph).
if (Element* table = TableElementJustBefore(end_of_selection)) {
if (start_of_selection.DeepEquivalent().AnchorNode()->IsDescendantOf(
table)) {
const VisiblePosition& new_end =
PreviousPositionOf(end_of_selection, kCannotCrossEditingBoundary);
if (new_end.IsNotNull()) {
new_selection = CreateVisibleSelection(
SelectionInDOMTree::Builder()
.Collapse(start_of_selection.ToPositionWithAffinity())
.Extend(new_end.DeepEquivalent())
.Build());
} else {
new_selection = CreateVisibleSelection(
SelectionInDOMTree::Builder()
.Collapse(start_of_selection.ToPositionWithAffinity())
.Build());
}
}
}
// If the start of the selection to modify is just before a table, and if the
// end of the selection is inside that table, then the first paragraph we'll
// want to modify is the first one inside the table, not the paragraph
// containing the table itself.
if (Element* table = TableElementJustAfter(start_of_selection)) {
if (end_of_selection.DeepEquivalent().AnchorNode()->IsDescendantOf(table)) {
const VisiblePosition new_start =
NextPositionOf(start_of_selection, kCannotCrossEditingBoundary);
if (new_start.IsNotNull()) {
new_selection = CreateVisibleSelection(
SelectionInDOMTree::Builder()
.Collapse(new_start.ToPositionWithAffinity())
.Extend(end_of_selection.DeepEquivalent())
.Build());
} else {
new_selection = CreateVisibleSelection(
SelectionInDOMTree::Builder()
.Collapse(end_of_selection.ToPositionWithAffinity())
.Build());
}
}
}
return new_selection;
}
// FIXME: indexForVisiblePosition and visiblePositionForIndex use TextIterators
// to convert between VisiblePositions and indices. But TextIterator iteration
// using TextIteratorEmitsCharactersBetweenAllVisiblePositions does not exactly
// match VisiblePosition iteration, so using them to preserve a selection during
// an editing opertion is unreliable. TextIterator's
// TextIteratorEmitsCharactersBetweenAllVisiblePositions mode needs to be fixed,
// or these functions need to be changed to iterate using actual
// VisiblePositions.
// FIXME: Deploy these functions everywhere that TextIterators are used to
// convert between VisiblePositions and indices.
int IndexForVisiblePosition(const VisiblePosition& visible_position,
ContainerNode*& scope) {
if (visible_position.IsNull())
return 0;
Position p(visible_position.DeepEquivalent());
Document& document = *p.GetDocument();
DCHECK(!document.NeedsLayoutTreeUpdate());
ShadowRoot* shadow_root = p.AnchorNode()->ContainingShadowRoot();
if (shadow_root)
scope = shadow_root;
else
scope = document.documentElement();
EphemeralRange range(Position::FirstPositionInNode(*scope),
p.ParentAnchoredEquivalent());
return TextIterator::RangeLength(
range.StartPosition(), range.EndPosition(),
TextIteratorBehavior::AllVisiblePositionsRangeLengthBehavior());
}
EphemeralRange MakeRange(const VisiblePosition& start,
const VisiblePosition& end) {
if (start.IsNull() || end.IsNull())
return EphemeralRange();
Position s = start.DeepEquivalent().ParentAnchoredEquivalent();
Position e = end.DeepEquivalent().ParentAnchoredEquivalent();
if (s.IsNull() || e.IsNull())
return EphemeralRange();
return EphemeralRange(s, e);
}
template <typename Strategy>
static EphemeralRangeTemplate<Strategy> NormalizeRangeAlgorithm(
const EphemeralRangeTemplate<Strategy>& range) {
DCHECK(range.IsNotNull());
DCHECK(!range.GetDocument().NeedsLayoutTreeUpdate());
DocumentLifecycle::DisallowTransitionScope disallow_transition(
range.GetDocument().Lifecycle());
// TODO(yosin) We should not call |parentAnchoredEquivalent()|, it is
// redundant.
const PositionTemplate<Strategy> normalized_start =
MostForwardCaretPosition(range.StartPosition())
.ParentAnchoredEquivalent();
const PositionTemplate<Strategy> normalized_end =
MostBackwardCaretPosition(range.EndPosition()).ParentAnchoredEquivalent();
// The order of the positions of |start| and |end| can be swapped after
// upstream/downstream. e.g. editing/pasteboard/copy-display-none.html
if (normalized_start.CompareTo(normalized_end) > 0)
return EphemeralRangeTemplate<Strategy>(normalized_end, normalized_start);
return EphemeralRangeTemplate<Strategy>(normalized_start, normalized_end);
}
EphemeralRange NormalizeRange(const EphemeralRange& range) {
return NormalizeRangeAlgorithm<EditingStrategy>(range);
}
EphemeralRangeInFlatTree NormalizeRange(const EphemeralRangeInFlatTree& range) {
return NormalizeRangeAlgorithm<EditingInFlatTreeStrategy>(range);
}
VisiblePosition VisiblePositionForIndex(int index, ContainerNode* scope) {
if (!scope)
return VisiblePosition();
DCHECK(!scope->GetDocument().NeedsLayoutTreeUpdate());
DocumentLifecycle::DisallowTransitionScope disallow_transition(
scope->GetDocument().Lifecycle());
EphemeralRange range = PlainTextRange(index).CreateRangeForSelection(*scope);
// Check for an invalid index. Certain editing operations invalidate indices
// because of problems with
// TextIteratorEmitsCharactersBetweenAllVisiblePositions.
if (range.IsNull())
return VisiblePosition();
return CreateVisiblePosition(range.StartPosition());
}
bool IsRenderedAsNonInlineTableImageOrHR(const Node* node) {
if (!node)
return false;
LayoutObject* layout_object = node->GetLayoutObject();
return layout_object &&
((layout_object->IsTable() && !layout_object->IsInline()) ||
(layout_object->IsImage() && !layout_object->IsInline()) ||
layout_object->IsHR());
}
bool AreIdenticalElements(const Node& first, const Node& second) {
if (!first.IsElementNode() || !second.IsElementNode())
return false;
const Element& first_element = ToElement(first);
const Element& second_element = ToElement(second);
if (!first_element.HasTagName(second_element.TagQName()))
return false;
if (!first_element.HasEquivalentAttributes(&second_element))
return false;
return HasEditableStyle(first_element) && HasEditableStyle(second_element);
}
bool IsNonTableCellHTMLBlockElement(const Node* node) {
if (!node->IsHTMLElement())
return false;
const HTMLElement& element = ToHTMLElement(*node);
return element.HasTagName(listingTag) || element.HasTagName(olTag) ||
element.HasTagName(preTag) || element.HasTagName(tableTag) ||
element.HasTagName(ulTag) || element.HasTagName(xmpTag) ||
element.HasTagName(h1Tag) || element.HasTagName(h2Tag) ||
element.HasTagName(h3Tag) || element.HasTagName(h4Tag) ||
element.HasTagName(h5Tag);
}
bool IsBlockFlowElement(const Node& node) {
LayoutObject* layout_object = node.GetLayoutObject();
return node.IsElementNode() && layout_object &&
layout_object->IsLayoutBlockFlow();
}
bool IsInPasswordField(const Position& position) {
TextControlElement* text_control = EnclosingTextControl(position);
return IsHTMLInputElement(text_control) &&
ToHTMLInputElement(text_control)->type() == InputTypeNames::password;
}
bool IsTextSecurityNode(const Node* node) {
return node && node->GetLayoutObject() &&
node->GetLayoutObject()->Style()->TextSecurity() !=
ETextSecurity::kNone;
}
// If current position is at grapheme boundary, return 0; otherwise, return the
// distance to its nearest left grapheme boundary.
size_t ComputeDistanceToLeftGraphemeBoundary(const Position& position) {
const Position& adjusted_position = PreviousPositionOf(
NextPositionOf(position, PositionMoveType::kGraphemeCluster),
PositionMoveType::kGraphemeCluster);
DCHECK_EQ(position.AnchorNode(), adjusted_position.AnchorNode());
DCHECK_GE(position.ComputeOffsetInContainerNode(),
adjusted_position.ComputeOffsetInContainerNode());
return static_cast<size_t>(position.ComputeOffsetInContainerNode() -
adjusted_position.ComputeOffsetInContainerNode());
}
// If current position is at grapheme boundary, return 0; otherwise, return the
// distance to its nearest right grapheme boundary.
size_t ComputeDistanceToRightGraphemeBoundary(const Position& position) {
const Position& adjusted_position = NextPositionOf(
PreviousPositionOf(position, PositionMoveType::kGraphemeCluster),
PositionMoveType::kGraphemeCluster);
DCHECK_EQ(position.AnchorNode(), adjusted_position.AnchorNode());
DCHECK_GE(adjusted_position.ComputeOffsetInContainerNode(),
position.ComputeOffsetInContainerNode());
return static_cast<size_t>(adjusted_position.ComputeOffsetInContainerNode() -
position.ComputeOffsetInContainerNode());
}
const StaticRangeVector* TargetRangesForInputEvent(const Node& node) {
// TODO(editing-dev): The use of updateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. see http://crbug.com/590369 for more details.
node.GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
if (!HasRichlyEditableStyle(node))
return nullptr;
const EphemeralRange& range =
FirstEphemeralRangeOf(node.GetDocument()
.GetFrame()
->Selection()
.ComputeVisibleSelectionInDOMTree());
if (range.IsNull())
return nullptr;
return new StaticRangeVector(1, StaticRange::Create(range));
}
DispatchEventResult DispatchBeforeInputInsertText(
Node* target,
const String& data,
InputEvent::InputType input_type,
const StaticRangeVector* ranges) {
if (!target)
return DispatchEventResult::kNotCanceled;
// TODO(chongz): Pass appropriate |ranges| after it's defined on spec.
// http://w3c.github.io/editing/input-events.html#dom-inputevent-inputtype
InputEvent* before_input_event = InputEvent::CreateBeforeInput(
input_type, data, InputTypeIsCancelable(input_type),
InputEvent::EventIsComposing::kNotComposing,
ranges ? ranges : TargetRangesForInputEvent(*target));
return target->DispatchEvent(before_input_event);
}
DispatchEventResult DispatchBeforeInputEditorCommand(
Node* target,
InputEvent::InputType input_type,
const StaticRangeVector* ranges) {
if (!target)
return DispatchEventResult::kNotCanceled;
InputEvent* before_input_event = InputEvent::CreateBeforeInput(
input_type, g_null_atom, InputTypeIsCancelable(input_type),
InputEvent::EventIsComposing::kNotComposing, ranges);
return target->DispatchEvent(before_input_event);
}
DispatchEventResult DispatchBeforeInputDataTransfer(
Node* target,
InputEvent::InputType input_type,
DataTransfer* data_transfer) {
if (!target)
return DispatchEventResult::kNotCanceled;
DCHECK(input_type == InputEvent::InputType::kInsertFromPaste ||
input_type == InputEvent::InputType::kInsertReplacementText ||
input_type == InputEvent::InputType::kInsertFromDrop ||
input_type == InputEvent::InputType::kDeleteByCut)
<< "Unsupported inputType: " << (int)input_type;
InputEvent* before_input_event;
if (HasRichlyEditableStyle(*(target->ToNode())) || !data_transfer) {
before_input_event = InputEvent::CreateBeforeInput(
input_type, data_transfer, InputTypeIsCancelable(input_type),
InputEvent::EventIsComposing::kNotComposing,
TargetRangesForInputEvent(*target));
} else {
const String& data = data_transfer->getData(kMimeTypeTextPlain);
// TODO(chongz): Pass appropriate |ranges| after it's defined on spec.
// http://w3c.github.io/editing/input-events.html#dom-inputevent-inputtype
before_input_event = InputEvent::CreateBeforeInput(
input_type, data, InputTypeIsCancelable(input_type),
InputEvent::EventIsComposing::kNotComposing,
TargetRangesForInputEvent(*target));
}
return target->DispatchEvent(before_input_event);
}
InputEvent::InputType DeletionInputTypeFromTextGranularity(
DeleteDirection direction,
TextGranularity granularity) {
using InputType = InputEvent::InputType;
switch (direction) {
case DeleteDirection::kForward:
if (granularity == TextGranularity::kWord)
return InputType::kDeleteWordForward;
if (granularity == TextGranularity::kLineBoundary)
return InputType::kDeleteSoftLineForward;
if (granularity == TextGranularity::kParagraphBoundary)
return InputType::kDeleteHardLineForward;
return InputType::kDeleteContentForward;
case DeleteDirection::kBackward:
if (granularity == TextGranularity::kWord)
return InputType::kDeleteWordBackward;
if (granularity == TextGranularity::kLineBoundary)
return InputType::kDeleteSoftLineBackward;
if (granularity == TextGranularity::kParagraphBoundary)
return InputType::kDeleteHardLineBackward;
return InputType::kDeleteContentBackward;
default:
return InputType::kNone;
}
}
// |IsEmptyNonEditableNodeInEditable()| is introduced for fixing
// http://crbug.com/428986.
static bool IsEmptyNonEditableNodeInEditable(const Node& node) {
// Editability is defined the DOM tree rather than the flat tree. For example:
// DOM:
// <host>
// <span>unedittable</span>
// <shadowroot><div ce><content /></div></shadowroot>
// </host>
//
// Flat Tree:
// <host><div ce><span1>unedittable</span></div></host>
// e.g. editing/shadow/breaking-editing-boundaries.html
return !NodeTraversal::HasChildren(node) && !HasEditableStyle(node) &&
node.parentNode() && HasEditableStyle(*node.parentNode());
}
// TODO(yosin): We should not use |IsEmptyNonEditableNodeInEditable()| in
// |EditingIgnoresContent()| since |IsEmptyNonEditableNodeInEditable()|
// requires clean layout tree.
bool EditingIgnoresContent(const Node& node) {
return !node.CanContainRangeEndPoint() ||
IsEmptyNonEditableNodeInEditable(node);
}
ContainerNode* RootEditableElementOrTreeScopeRootNodeOf(
const Position& position) {
Element* const selection_root = RootEditableElementOf(position);
if (selection_root)
return selection_root;
Node* const node = position.ComputeContainerNode();
return node ? &node->GetTreeScope().RootNode() : nullptr;
}
} // namespace blink