blob: fe3f95f7da56fbd15fb32accc8c11d0865d06828 [file] [log] [blame]
/*
* Copyright (C) 2005, 2006 Apple Computer, 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 "third_party/blink/renderer/core/editing/commands/insert_line_break_command.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/editing/commands/delete_selection_options.h"
#include "third_party/blink/renderer/core/editing/commands/editing_commands_utilities.h"
#include "third_party/blink/renderer/core/editing/editing_style.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/editor.h"
#include "third_party/blink/renderer/core/editing/selection_template.h"
#include "third_party/blink/renderer/core/editing/visible_position.h"
#include "third_party/blink/renderer/core/editing/visible_units.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/html/forms/text_control_element.h"
#include "third_party/blink/renderer/core/html/html_br_element.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_text.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
namespace blink {
InsertLineBreakCommand::InsertLineBreakCommand(Document& document)
: CompositeEditCommand(document) {}
bool InsertLineBreakCommand::PreservesTypingStyle() const {
return true;
}
// Whether we should insert a break element or a '\n'.
bool InsertLineBreakCommand::ShouldUseBreakElement(
const Position& insertion_pos) {
// An editing position like [input, 0] actually refers to the position before
// the input element, and in that case we need to check the input element's
// parent's layoutObject.
Position p(insertion_pos.ParentAnchoredEquivalent());
return IsRichlyEditablePosition(p) && p.AnchorNode()->GetLayoutObject() &&
!p.AnchorNode()->GetLayoutObject()->Style()->PreserveNewline();
}
void InsertLineBreakCommand::DoApply(EditingState* editing_state) {
if (!DeleteSelection(editing_state, DeleteSelectionOptions::NormalDelete()))
return;
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
VisibleSelection selection = EndingVisibleSelection();
if (selection.IsNone() || selection.Start().IsOrphan() ||
selection.End().IsOrphan())
return;
// TODO(editing-dev): Stop storing VisiblePositions through mutations.
// See crbug.com/648949 for details.
VisiblePosition caret(selection.VisibleStart());
// FIXME: If the node is hidden, we should still be able to insert text. For
// now, we return to avoid a crash.
// https://bugs.webkit.org/show_bug.cgi?id=40342
if (caret.IsNull())
return;
Position pos(caret.DeepEquivalent());
pos = PositionAvoidingSpecialElementBoundary(pos, editing_state);
if (editing_state->IsAborted())
return;
pos = PositionOutsideTabSpan(pos);
Node* node_to_insert = nullptr;
if (ShouldUseBreakElement(pos))
node_to_insert = MakeGarbageCollected<HTMLBRElement>(GetDocument());
else
node_to_insert = GetDocument().createTextNode("\n");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
// FIXME: Need to merge text nodes when inserting just after or before text.
if (IsEndOfParagraph(CreateVisiblePosition(caret.ToPositionWithAffinity())) &&
!LineBreakExistsAtVisiblePosition(caret)) {
bool need_extra_line_break = !IsA<HTMLHRElement>(*pos.AnchorNode()) &&
!IsA<HTMLTableElement>(*pos.AnchorNode());
InsertNodeAt(node_to_insert, pos, editing_state);
if (editing_state->IsAborted())
return;
if (need_extra_line_break) {
Node* extra_node;
// TODO(tkent): Can we remove TextControlElement dependency?
if (TextControlElement* text_control =
EnclosingTextControl(node_to_insert)) {
extra_node = text_control->CreatePlaceholderBreakElement();
// The placeholder BR should be the last child. There might be
// empty Text nodes at |pos|.
AppendNode(extra_node, node_to_insert->parentNode(), editing_state);
} else {
extra_node = node_to_insert->cloneNode(false);
InsertNodeAfter(extra_node, node_to_insert, editing_state);
}
if (editing_state->IsAborted())
return;
node_to_insert = extra_node;
}
SetEndingSelection(SelectionForUndoStep::From(
SelectionInDOMTree::Builder()
.Collapse(Position::BeforeNode(*node_to_insert))
.Build()));
} else if (pos.ComputeEditingOffset() <= CaretMinOffset(pos.AnchorNode())) {
InsertNodeAt(node_to_insert, pos, editing_state);
if (editing_state->IsAborted())
return;
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
// Insert an extra br or '\n' if the just inserted one collapsed.
if (!IsStartOfParagraph(VisiblePosition::BeforeNode(*node_to_insert))) {
InsertNodeBefore(node_to_insert->cloneNode(false), node_to_insert,
editing_state);
if (editing_state->IsAborted())
return;
}
SetEndingSelection(SelectionForUndoStep::From(
SelectionInDOMTree::Builder()
.Collapse(Position::InParentAfterNode(*node_to_insert))
.Build()));
// If we're inserting after all of the rendered text in a text node, or into
// a non-text node, a simple insertion is sufficient.
} else if (!pos.AnchorNode()->IsTextNode() ||
pos.ComputeOffsetInContainerNode() >=
CaretMaxOffset(pos.AnchorNode())) {
InsertNodeAt(node_to_insert, pos, editing_state);
if (editing_state->IsAborted())
return;
SetEndingSelection(SelectionForUndoStep::From(
SelectionInDOMTree::Builder()
.Collapse(Position::InParentAfterNode(*node_to_insert))
.Build()));
} else if (auto* text_node = DynamicTo<Text>(pos.AnchorNode())) {
// Split a text node
SplitTextNode(text_node, pos.ComputeOffsetInContainerNode());
InsertNodeBefore(node_to_insert, text_node, editing_state);
if (editing_state->IsAborted())
return;
Position ending_position = Position::FirstPositionInNode(*text_node);
// Handle whitespace that occurs after the split
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
if (!IsRenderedCharacter(ending_position)) {
Position position_before_text_node(
Position::InParentBeforeNode(*text_node));
// Clear out all whitespace and insert one non-breaking space
DeleteInsignificantTextDownstream(ending_position);
// Deleting insignificant whitespace will remove textNode if it contains
// nothing but insignificant whitespace.
if (text_node->isConnected()) {
InsertTextIntoNode(text_node, 0, NonBreakingSpaceString());
} else {
Text* nbsp_node =
GetDocument().createTextNode(NonBreakingSpaceString());
InsertNodeAt(nbsp_node, position_before_text_node, editing_state);
if (editing_state->IsAborted())
return;
ending_position = Position::FirstPositionInNode(*nbsp_node);
}
}
SetEndingSelection(SelectionForUndoStep::From(
SelectionInDOMTree::Builder()
.Collapse(ending_position)
.Build()));
}
// Handle the case where there is a typing style.
EditingStyle* typing_style =
GetDocument().GetFrame()->GetEditor().TypingStyle();
if (typing_style && !typing_style->IsEmpty()) {
DCHECK(node_to_insert);
// Apply the typing style to the inserted line break, so that if the
// selection leaves and then comes back, new input will have the right
// style.
// FIXME: We shouldn't always apply the typing style to the line break here,
// see <rdar://problem/5794462>.
ApplyStyle(typing_style, FirstPositionInOrBeforeNode(*node_to_insert),
LastPositionInOrAfterNode(*node_to_insert), editing_state);
if (editing_state->IsAborted())
return;
// Even though this applyStyle operates on a Range, it still sets an
// endingSelection(). It tries to set a VisibleSelection around the content
// it operated on. So, that VisibleSelection will either
// (a) select the line break we inserted, or it will
// (b) be a caret just before the line break (if the line break is at the
// end of a block it isn't selectable).
// So, this next call sets the endingSelection() to a caret just after the
// line break that we inserted, or just before it if it's at the end of a
// block.
SetEndingSelection(
SelectionForUndoStep::From(SelectionInDOMTree::Builder()
.Collapse(EndingVisibleSelection().End())
.Build()));
}
RebalanceWhitespace();
}
} // namespace blink