blob: 0a809e99445ef3cc05e627f1e6bae0fdf8239aa3 [file] [log] [blame]
/*
* Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
* Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
*
* 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/editor.h"
#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
#include "third_party/blink/renderer/core/clipboard/data_object.h"
#include "third_party/blink/renderer/core/clipboard/data_transfer.h"
#include "third_party/blink/renderer/core/clipboard/data_transfer_access_policy.h"
#include "third_party/blink/renderer/core/clipboard/system_clipboard.h"
#include "third_party/blink/renderer/core/css/css_computed_style_declaration.h"
#include "third_party/blink/renderer/core/css/css_property_names.h"
#include "third_party/blink/renderer/core/css/css_property_value_set.h"
#include "third_party/blink/renderer/core/dom/document_fragment.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
#include "third_party/blink/renderer/core/dom/node_traversal.h"
#include "third_party/blink/renderer/core/dom/parser_content_policy.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/editing/commands/apply_style_command.h"
#include "third_party/blink/renderer/core/editing/commands/delete_selection_command.h"
#include "third_party/blink/renderer/core/editing/commands/indent_outdent_command.h"
#include "third_party/blink/renderer/core/editing/commands/insert_list_command.h"
#include "third_party/blink/renderer/core/editing/commands/replace_selection_command.h"
#include "third_party/blink/renderer/core/editing/commands/simplify_markup_command.h"
#include "third_party/blink/renderer/core/editing/commands/typing_command.h"
#include "third_party/blink/renderer/core/editing/commands/undo_stack.h"
#include "third_party/blink/renderer/core/editing/editing_behavior.h"
#include "third_party/blink/renderer/core/editing/editing_style_utilities.h"
#include "third_party/blink/renderer/core/editing/editing_tri_state.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/finder/find_buffer.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/editing/ime/input_method_controller.h"
#include "third_party/blink/renderer/core/editing/kill_ring.h"
#include "third_party/blink/renderer/core/editing/markers/document_marker.h"
#include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
#include "third_party/blink/renderer/core/editing/selection_template.h"
#include "third_party/blink/renderer/core/editing/serializers/serialization.h"
#include "third_party/blink/renderer/core/editing/set_selection_options.h"
#include "third_party/blink/renderer/core/editing/spellcheck/spell_checker.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/events/keyboard_event.h"
#include "third_party/blink/renderer/core/events/text_event.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/forms/html_text_area_element.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/input/event_handler.h"
#include "third_party/blink/renderer/core/input_type_names.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/loader/empty_clients.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
#include "third_party/blink/renderer/core/page/drag_data.h"
#include "third_party/blink/renderer/core/page/focus_controller.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/scroll/scroll_alignment.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/text/character_names.h"
namespace blink {
namespace {
bool IsInPasswordFieldWithUnrevealedPassword(const Position& position) {
if (auto* input =
DynamicTo<HTMLInputElement>(EnclosingTextControl(position))) {
return (input->type() == input_type_names::kPassword) &&
!input->ShouldRevealPassword();
}
return false;
}
} // anonymous namespace
// When an event handler has moved the selection outside of a text control
// we should use the target control's selection for this editing operation.
SelectionInDOMTree Editor::SelectionForCommand(Event* event) {
const SelectionInDOMTree selection =
GetFrameSelection().GetSelectionInDOMTree();
if (!event)
return selection;
// If the target is a text control, and the current selection is outside of
// its shadow tree, then use the saved selection for that text control.
if (!IsTextControl(*event->target()->ToNode()))
return selection;
auto* text_control_of_selection_start =
EnclosingTextControl(selection.Base());
auto* text_control_of_target = ToTextControl(event->target()->ToNode());
if (!selection.IsNone() &&
text_control_of_target == text_control_of_selection_start)
return selection;
const SelectionInDOMTree& select = text_control_of_target->Selection();
if (select.IsNone())
return selection;
return select;
}
// Function considers Mac editing behavior a fallback when Page or Settings is
// not available.
EditingBehavior Editor::Behavior() const {
if (!GetFrame().GetSettings())
return EditingBehavior(mojom::blink::EditingBehavior::kEditingMacBehavior);
return EditingBehavior(GetFrame().GetSettings()->GetEditingBehaviorType());
}
static bool IsCaretAtStartOfWrappedLine(const FrameSelection& selection) {
if (!selection.ComputeVisibleSelectionInDOMTree().IsCaret())
return false;
if (selection.GetSelectionInDOMTree().Affinity() != TextAffinity::kDownstream)
return false;
const Position& position =
selection.ComputeVisibleSelectionInDOMTree().Start();
if (InSameLine(PositionWithAffinity(position, TextAffinity::kUpstream),
PositionWithAffinity(position, TextAffinity::kDownstream)))
return false;
// Only when the previous character is a space to avoid undesired side
// effects. There are cases where a new line is desired even if the previous
// character is not a space, but typing another space will do.
Position prev =
PreviousPositionOf(position, PositionMoveType::kGraphemeCluster);
const auto* prev_node = DynamicTo<Text>(prev.ComputeContainerNode());
if (!prev_node)
return false;
int prev_offset = prev.ComputeOffsetInContainerNode();
UChar prev_char = prev_node->data()[prev_offset];
return prev_char == kSpaceCharacter;
}
bool Editor::HandleTextEvent(TextEvent* event) {
// Default event handling for Drag and Drop will be handled by DragController
// so we leave the event for it.
if (event->IsDrop())
return false;
// Default event handling for IncrementalInsertion will be handled by
// TypingCommand::insertText(), so we leave the event for it.
if (event->IsIncrementalInsertion())
return false;
// TODO(editing-dev): The use of UpdateStyleAndLayout
// needs to be audited. See http://crbug.com/590369 for more details.
frame_->GetDocument()->UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
if (event->IsPaste()) {
if (event->PastingFragment()) {
ReplaceSelectionWithFragment(
event->PastingFragment(), false, event->ShouldSmartReplace(),
event->ShouldMatchStyle(), InputEvent::InputType::kInsertFromPaste);
} else {
ReplaceSelectionWithText(event->data(), false,
event->ShouldSmartReplace(),
InputEvent::InputType::kInsertFromPaste);
}
return true;
}
String data = event->data();
if (data == "\n") {
if (event->IsLineBreak())
return InsertLineBreak();
return InsertParagraphSeparator();
}
// Typing spaces at the beginning of wrapped line is confusing, because
// inserted spaces would appear in the previous line.
// Insert a line break automatically so that the spaces appear at the caret.
// TODO(kojii): rich editing has the same issue, but has more options and
// needs coordination with JS. Enable for plaintext only for now and collect
// feedback.
if (data == " " && !CanEditRichly() &&
IsCaretAtStartOfWrappedLine(GetFrameSelection())) {
InsertLineBreak();
}
return InsertTextWithoutSendingTextEvent(data, false, event);
}
bool Editor::CanEdit() const {
return GetFrame()
.Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.RootEditableElement();
}
bool Editor::CanEditRichly() const {
return IsRichlyEditablePosition(
GetFrame()
.Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.Base());
}
bool Editor::CanCut() const {
return CanCopy() && CanDelete();
}
bool Editor::CanCopy() const {
if (ImageElementFromImageDocument(GetFrame().GetDocument()))
return true;
FrameSelection& selection = GetFrameSelection();
if (!selection.IsAvailable())
return false;
frame_->GetDocument()->UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
const VisibleSelectionInFlatTree& visible_selection =
selection.ComputeVisibleSelectionInFlatTree();
return visible_selection.IsRange() &&
!IsInPasswordFieldWithUnrevealedPassword(
ToPositionInDOMTree(visible_selection.Start()));
}
bool Editor::CanPaste() const {
return CanEdit();
}
bool Editor::CanDelete() const {
FrameSelection& selection = GetFrameSelection();
return selection.ComputeVisibleSelectionInDOMTreeDeprecated().IsRange() &&
selection.ComputeVisibleSelectionInDOMTree().RootEditableElement();
}
bool Editor::SmartInsertDeleteEnabled() const {
if (Settings* settings = GetFrame().GetSettings())
return settings->GetSmartInsertDeleteEnabled();
return false;
}
bool Editor::IsSelectTrailingWhitespaceEnabled() const {
if (Settings* settings = GetFrame().GetSettings())
return settings->GetSelectTrailingWhitespaceEnabled();
return false;
}
void Editor::DeleteSelectionWithSmartDelete(
DeleteMode delete_mode,
InputEvent::InputType input_type,
const Position& reference_move_position) {
if (GetFrame()
.Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.IsNone())
return;
DCHECK(GetFrame().GetDocument());
MakeGarbageCollected<DeleteSelectionCommand>(
*GetFrame().GetDocument(),
DeleteSelectionOptions::Builder()
.SetSmartDelete(delete_mode == DeleteMode::kSmart)
.SetMergeBlocksAfterDelete(true)
.SetExpandForSpecialElements(true)
.SetSanitizeMarkup(true)
.Build(),
input_type, reference_move_position)
->Apply();
}
void Editor::ReplaceSelectionWithFragment(DocumentFragment* fragment,
bool select_replacement,
bool smart_replace,
bool match_style,
InputEvent::InputType input_type) {
DCHECK(!GetFrame().GetDocument()->NeedsLayoutTreeUpdate());
const VisibleSelection& selection =
GetFrameSelection().ComputeVisibleSelectionInDOMTree();
if (selection.IsNone() || !selection.IsContentEditable() || !fragment)
return;
ReplaceSelectionCommand::CommandOptions options =
ReplaceSelectionCommand::kPreventNesting |
ReplaceSelectionCommand::kSanitizeFragment;
if (select_replacement)
options |= ReplaceSelectionCommand::kSelectReplacement;
if (smart_replace)
options |= ReplaceSelectionCommand::kSmartReplace;
if (match_style)
options |= ReplaceSelectionCommand::kMatchStyle;
DCHECK(GetFrame().GetDocument());
MakeGarbageCollected<ReplaceSelectionCommand>(*GetFrame().GetDocument(),
fragment, options, input_type)
->Apply();
RevealSelectionAfterEditingOperation();
}
void Editor::ReplaceSelectionWithText(const String& text,
bool select_replacement,
bool smart_replace,
InputEvent::InputType input_type) {
ReplaceSelectionWithFragment(CreateFragmentFromText(SelectedRange(), text),
select_replacement, smart_replace, true,
input_type);
}
void Editor::ReplaceSelectionAfterDragging(DocumentFragment* fragment,
InsertMode insert_mode,
DragSourceType drag_source_type) {
ReplaceSelectionCommand::CommandOptions options =
ReplaceSelectionCommand::kSelectReplacement |
ReplaceSelectionCommand::kPreventNesting;
if (insert_mode == InsertMode::kSmart)
options |= ReplaceSelectionCommand::kSmartReplace;
if (drag_source_type == DragSourceType::kPlainTextSource)
options |= ReplaceSelectionCommand::kMatchStyle;
DCHECK(GetFrame().GetDocument());
MakeGarbageCollected<ReplaceSelectionCommand>(
*GetFrame().GetDocument(), fragment, options,
InputEvent::InputType::kInsertFromDrop)
->Apply();
}
bool Editor::DeleteSelectionAfterDraggingWithEvents(
Element* drag_source,
DeleteMode delete_mode,
const Position& reference_move_position) {
if (!drag_source || !drag_source->isConnected())
return true;
// Dispatch 'beforeinput'.
const bool should_delete =
DispatchBeforeInputEditorCommand(
drag_source, InputEvent::InputType::kDeleteByDrag,
TargetRangesForInputEvent(*drag_source)) ==
DispatchEventResult::kNotCanceled;
// 'beforeinput' event handler may destroy frame, return false to cancel
// remaining actions;
if (frame_->GetDocument()->GetFrame() != frame_)
return false;
if (should_delete && drag_source->isConnected()) {
DeleteSelectionWithSmartDelete(delete_mode,
InputEvent::InputType::kDeleteByDrag,
reference_move_position);
}
return true;
}
bool Editor::ReplaceSelectionAfterDraggingWithEvents(
Element* drop_target,
DragData* drag_data,
DocumentFragment* fragment,
Range* drop_caret_range,
InsertMode insert_mode,
DragSourceType drag_source_type) {
if (!drop_target || !drop_target->isConnected())
return true;
// Dispatch 'beforeinput'.
DataTransfer* data_transfer = DataTransfer::Create(
DataTransfer::kDragAndDrop, DataTransferAccessPolicy::kReadable,
drag_data->PlatformData());
data_transfer->SetSourceOperation(drag_data->DraggingSourceOperationMask());
const bool should_insert =
DispatchBeforeInputDataTransfer(
drop_target, InputEvent::InputType::kInsertFromDrop, data_transfer) ==
DispatchEventResult::kNotCanceled;
// 'beforeinput' event handler may destroy frame, return false to cancel
// remaining actions;
if (frame_->GetDocument()->GetFrame() != frame_)
return false;
if (should_insert && drop_target->isConnected())
ReplaceSelectionAfterDragging(fragment, insert_mode, drag_source_type);
return true;
}
EphemeralRange Editor::SelectedRange() {
return GetFrame()
.Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.ToNormalizedEphemeralRange();
}
void Editor::RespondToChangedContents(const Position& position) {
if (AXObjectCache* cache =
GetFrame().GetDocument()->ExistingAXObjectCache()) {
cache->HandleEditableTextContentChanged(position.AnchorNode());
}
GetSpellChecker().RespondToChangedContents();
frame_->Client()->DidChangeContents();
}
void Editor::RegisterCommandGroup(CompositeEditCommand* command_group_wrapper) {
DCHECK(command_group_wrapper->IsCommandGroupWrapper());
last_edit_command_ = command_group_wrapper;
}
void Editor::ApplyParagraphStyle(CSSPropertyValueSet* style,
InputEvent::InputType input_type) {
if (GetFrame()
.Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.IsNone() ||
!style)
return;
DCHECK(GetFrame().GetDocument());
MakeGarbageCollected<ApplyStyleCommand>(
*GetFrame().GetDocument(), MakeGarbageCollected<EditingStyle>(style),
input_type, ApplyStyleCommand::kForceBlockProperties)
->Apply();
}
void Editor::ApplyParagraphStyleToSelection(CSSPropertyValueSet* style,
InputEvent::InputType input_type) {
if (!style || style->IsEmpty() || !CanEditRichly())
return;
ApplyParagraphStyle(style, input_type);
}
Editor::Editor(LocalFrame& frame)
: frame_(&frame),
undo_stack_(MakeGarbageCollected<UndoStack>()),
prevent_reveal_selection_(0),
should_start_new_kill_ring_sequence_(false),
// This is off by default, since most editors want this behavior (this
// matches IE but not FF).
should_style_with_css_(false),
kill_ring_(std::make_unique<KillRing>()),
are_marked_text_matches_highlighted_(false),
default_paragraph_separator_(EditorParagraphSeparator::kIsDiv),
overwrite_mode_enabled_(false) {}
Editor::~Editor() = default;
void Editor::Clear() {
should_style_with_css_ = false;
default_paragraph_separator_ = EditorParagraphSeparator::kIsDiv;
last_edit_command_ = nullptr;
undo_stack_->Clear();
}
bool Editor::InsertText(const String& text, KeyboardEvent* triggering_event) {
return GetFrame().GetEventHandler().HandleTextInputEvent(text,
triggering_event);
}
bool Editor::InsertTextWithoutSendingTextEvent(
const String& text,
bool select_inserted_text,
TextEvent* triggering_event,
InputEvent::InputType input_type) {
const VisibleSelection& selection =
CreateVisibleSelection(SelectionForCommand(triggering_event));
if (!selection.IsContentEditable())
return false;
EditingState editing_state;
// Insert the text
TypingCommand::InsertText(
*selection.Start().GetDocument(), text, selection.AsSelection(),
select_inserted_text ? TypingCommand::kSelectInsertedText : 0,
&editing_state,
triggering_event && triggering_event->IsComposition()
? TypingCommand::kTextCompositionConfirm
: TypingCommand::kTextCompositionNone,
false, input_type);
if (editing_state.IsAborted())
return false;
// Reveal the current selection
if (LocalFrame* edited_frame = selection.Start().GetDocument()->GetFrame()) {
if (Page* page = edited_frame->GetPage()) {
LocalFrame* focused_or_main_frame =
To<LocalFrame>(page->GetFocusController().FocusedOrMainFrame());
focused_or_main_frame->Selection().RevealSelection(
ScrollAlignment::ToEdgeIfNeeded());
}
}
return true;
}
bool Editor::InsertLineBreak() {
if (!CanEdit())
return false;
VisiblePosition caret =
GetFrameSelection().ComputeVisibleSelectionInDOMTree().VisibleStart();
DCHECK(GetFrame().GetDocument());
if (!TypingCommand::InsertLineBreak(*GetFrame().GetDocument()))
return false;
RevealSelectionAfterEditingOperation(ScrollAlignment::ToEdgeIfNeeded());
return true;
}
bool Editor::InsertParagraphSeparator() {
if (!CanEdit())
return false;
if (!CanEditRichly())
return InsertLineBreak();
VisiblePosition caret =
GetFrameSelection().ComputeVisibleSelectionInDOMTree().VisibleStart();
DCHECK(GetFrame().GetDocument());
EditingState editing_state;
if (!TypingCommand::InsertParagraphSeparator(*GetFrame().GetDocument()))
return false;
RevealSelectionAfterEditingOperation(ScrollAlignment::ToEdgeIfNeeded());
return true;
}
static void CountEditingEvent(ExecutionContext* execution_context,
const Event& event,
WebFeature feature_on_input,
WebFeature feature_on_text_area,
WebFeature feature_on_content_editable,
WebFeature feature_on_non_node) {
EventTarget* event_target = event.target();
Node* node = event_target->ToNode();
if (!node) {
UseCounter::Count(execution_context, feature_on_non_node);
return;
}
if (IsA<HTMLInputElement>(node)) {
UseCounter::Count(execution_context, feature_on_input);
return;
}
if (IsA<HTMLTextAreaElement>(node)) {
UseCounter::Count(execution_context, feature_on_text_area);
return;
}
TextControlElement* control = EnclosingTextControl(node);
if (IsA<HTMLInputElement>(control)) {
UseCounter::Count(execution_context, feature_on_input);
return;
}
if (IsA<HTMLTextAreaElement>(control)) {
UseCounter::Count(execution_context, feature_on_text_area);
return;
}
UseCounter::Count(execution_context, feature_on_content_editable);
}
void Editor::CountEvent(ExecutionContext* execution_context,
const Event& event) {
if (!execution_context)
return;
if (event.type() == event_type_names::kTextInput) {
CountEditingEvent(execution_context, event,
WebFeature::kTextInputEventOnInput,
WebFeature::kTextInputEventOnTextArea,
WebFeature::kTextInputEventOnContentEditable,
WebFeature::kTextInputEventOnNotNode);
return;
}
if (event.type() == event_type_names::kWebkitBeforeTextInserted) {
CountEditingEvent(execution_context, event,
WebFeature::kWebkitBeforeTextInsertedOnInput,
WebFeature::kWebkitBeforeTextInsertedOnTextArea,
WebFeature::kWebkitBeforeTextInsertedOnContentEditable,
WebFeature::kWebkitBeforeTextInsertedOnNotNode);
return;
}
if (event.type() == event_type_names::kWebkitEditableContentChanged) {
CountEditingEvent(
execution_context, event,
WebFeature::kWebkitEditableContentChangedOnInput,
WebFeature::kWebkitEditableContentChangedOnTextArea,
WebFeature::kWebkitEditableContentChangedOnContentEditable,
WebFeature::kWebkitEditableContentChangedOnNotNode);
}
}
void Editor::CopyImage(const HitTestResult& result) {
WriteImageNodeToClipboard(*frame_->GetSystemClipboard(),
*result.InnerNodeOrImageMapImage(),
result.AltDisplayString());
}
bool Editor::CanUndo() {
return undo_stack_->CanUndo();
}
void Editor::Undo() {
undo_stack_->Undo();
}
bool Editor::CanRedo() {
return undo_stack_->CanRedo();
}
void Editor::Redo() {
undo_stack_->Redo();
}
void Editor::SetBaseWritingDirection(
mojo_base::mojom::blink::TextDirection direction) {
Element* focused_element = GetFrame().GetDocument()->FocusedElement();
if (auto* text_control = ToTextControlOrNull(focused_element)) {
if (direction == mojo_base::mojom::blink::TextDirection::UNKNOWN_DIRECTION)
return;
text_control->setAttribute(
html_names::kDirAttr,
direction == mojo_base::mojom::blink::TextDirection::LEFT_TO_RIGHT
? "ltr"
: "rtl");
text_control->DispatchInputEvent();
return;
}
auto* style =
MakeGarbageCollected<MutableCSSPropertyValueSet>(kHTMLQuirksMode);
style->SetProperty(
CSSPropertyID::kDirection,
direction == mojo_base::mojom::blink::TextDirection::LEFT_TO_RIGHT
? "ltr"
: direction == mojo_base::mojom::blink::TextDirection::RIGHT_TO_LEFT
? "rtl"
: "inherit",
/* important */ false, GetFrame().DomWindow()->GetSecureContextMode());
ApplyParagraphStyleToSelection(
style, InputEvent::InputType::kFormatSetBlockTextDirection);
}
void Editor::RevealSelectionAfterEditingOperation(
const mojom::blink::ScrollAlignment& alignment) {
if (prevent_reveal_selection_)
return;
if (!GetFrameSelection().IsAvailable())
return;
GetFrameSelection().RevealSelection(alignment, kDoNotRevealExtent);
}
void Editor::AddToKillRing(const EphemeralRange& range) {
if (should_start_new_kill_ring_sequence_)
GetKillRing().StartNewSequence();
DCHECK(!GetFrame().GetDocument()->NeedsLayoutTreeUpdate());
String text = PlainText(range);
GetKillRing().Append(text);
should_start_new_kill_ring_sequence_ = false;
}
EphemeralRange Editor::RangeForPoint(const IntPoint& frame_point) const {
const PositionWithAffinity position_with_affinity =
GetFrame().PositionForPoint(PhysicalOffset(frame_point));
if (position_with_affinity.IsNull())
return EphemeralRange();
const VisiblePosition position =
CreateVisiblePosition(position_with_affinity);
const VisiblePosition previous = PreviousPositionOf(position);
if (previous.IsNotNull()) {
const EphemeralRange previous_character_range =
MakeRange(previous, position);
const IntRect rect = FirstRectForRange(previous_character_range);
if (rect.Contains(frame_point))
return EphemeralRange(previous_character_range);
}
const VisiblePosition next = NextPositionOf(position);
const EphemeralRange next_character_range = MakeRange(position, next);
if (next_character_range.IsNotNull()) {
const IntRect rect = FirstRectForRange(next_character_range);
if (rect.Contains(frame_point))
return EphemeralRange(next_character_range);
}
return EphemeralRange();
}
void Editor::ComputeAndSetTypingStyle(CSSPropertyValueSet* style,
InputEvent::InputType input_type) {
if (!style || style->IsEmpty()) {
ClearTypingStyle();
return;
}
// Calculate the current typing style.
if (typing_style_)
typing_style_->OverrideWithStyle(style);
else
typing_style_ = MakeGarbageCollected<EditingStyle>(style);
typing_style_->PrepareToApplyAt(
GetFrame()
.Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.VisibleStart()
.DeepEquivalent(),
EditingStyle::kPreserveWritingDirection);
// Handle block styles, substracting these from the typing style.
EditingStyle* block_style =
typing_style_->ExtractAndRemoveBlockProperties(GetFrame().DomWindow());
if (!block_style->IsEmpty()) {
DCHECK(GetFrame().GetDocument());
MakeGarbageCollected<ApplyStyleCommand>(*GetFrame().GetDocument(),
block_style, input_type)
->Apply();
}
}
bool Editor::FindString(LocalFrame& frame,
const String& target,
FindOptions options) {
VisibleSelectionInFlatTree selection =
frame.Selection().ComputeVisibleSelectionInFlatTree();
// TODO(yosin) We should make |findRangeOfString()| to return
// |EphemeralRange| rather than|Range| object.
Range* const result_range = FindRangeOfString(
*frame.GetDocument(), target,
EphemeralRangeInFlatTree(selection.Start(), selection.End()),
static_cast<FindOptions>(options | kFindAPICall));
if (!result_range)
return false;
frame.Selection().SetSelectionAndEndTyping(
SelectionInDOMTree::Builder()
.SetBaseAndExtent(EphemeralRange(result_range))
.Build());
frame.Selection().RevealSelection();
return true;
}
// TODO(yosin) We should return |EphemeralRange| rather than |Range|. We use
// |Range| object for checking whether start and end position crossing shadow
// boundaries, however we can do it without |Range| object.
static Range* FindStringBetweenPositions(
const String& target,
const EphemeralRangeInFlatTree& reference_range,
FindOptions options) {
EphemeralRangeInFlatTree search_range(reference_range);
bool forward = !(options & kBackwards);
while (true) {
EphemeralRangeInFlatTree result_range =
FindBuffer::FindMatchInRange(search_range, target, options);
if (result_range.IsCollapsed())
return nullptr;
auto* range_object = MakeGarbageCollected<Range>(
result_range.GetDocument(),
ToPositionInDOMTree(result_range.StartPosition()),
ToPositionInDOMTree(result_range.EndPosition()));
if (!range_object->collapsed())
return range_object;
// Found text spans over multiple TreeScopes. Since it's impossible to
// return such section as a Range, we skip this match and seek for the
// next occurrence.
// TODO(yosin) Handle this case.
if (forward) {
search_range = EphemeralRangeInFlatTree(
NextPositionOf(result_range.StartPosition(),
PositionMoveType::kGraphemeCluster),
search_range.EndPosition());
} else {
search_range = EphemeralRangeInFlatTree(
search_range.StartPosition(),
PreviousPositionOf(result_range.EndPosition(),
PositionMoveType::kGraphemeCluster));
}
}
NOTREACHED();
return nullptr;
}
Range* Editor::FindRangeOfString(
Document& document,
const String& target,
const EphemeralRangeInFlatTree& reference_range,
FindOptions options,
bool* wrapped_around) {
if (target.IsEmpty())
return nullptr;
// Start from an edge of the reference range. Which edge is used depends on
// whether we're searching forward or backward, and whether startInSelection
// is set.
EphemeralRangeInFlatTree document_range =
EphemeralRangeInFlatTree::RangeOfContents(document);
EphemeralRangeInFlatTree search_range(document_range);
const bool forward = !(options & kBackwards);
bool start_in_reference_range = false;
if (reference_range.IsNotNull()) {
start_in_reference_range = options & kStartInSelection;
if (forward && start_in_reference_range) {
search_range = EphemeralRangeInFlatTree(reference_range.StartPosition(),
document_range.EndPosition());
} else if (forward) {
search_range = EphemeralRangeInFlatTree(reference_range.EndPosition(),
document_range.EndPosition());
} else if (start_in_reference_range) {
search_range = EphemeralRangeInFlatTree(document_range.StartPosition(),
reference_range.EndPosition());
} else {
search_range = EphemeralRangeInFlatTree(document_range.StartPosition(),
reference_range.StartPosition());
}
}
Range* result_range =
FindStringBetweenPositions(target, search_range, options);
// If we started in the reference range and the found range exactly matches
// the reference range, find again. Build a selection with the found range
// to remove collapsed whitespace. Compare ranges instead of selection
// objects to ignore the way that the current selection was made.
if (result_range && start_in_reference_range &&
NormalizeRange(EphemeralRangeInFlatTree(result_range)) ==
reference_range) {
if (forward)
search_range = EphemeralRangeInFlatTree(
ToPositionInFlatTree(result_range->EndPosition()),
search_range.EndPosition());
else
search_range = EphemeralRangeInFlatTree(
search_range.StartPosition(),
ToPositionInFlatTree(result_range->StartPosition()));
result_range = FindStringBetweenPositions(target, search_range, options);
}
if (!result_range && options & kWrapAround) {
if (wrapped_around)
*wrapped_around = true;
return FindStringBetweenPositions(target, document_range, options);
}
return result_range;
}
void Editor::SetMarkedTextMatchesAreHighlighted(bool flag) {
if (flag == are_marked_text_matches_highlighted_)
return;
are_marked_text_matches_highlighted_ = flag;
GetFrame().GetDocument()->Markers().RepaintMarkers(
DocumentMarker::MarkerTypes::TextMatch());
}
void Editor::RespondToChangedSelection() {
GetSpellChecker().RespondToChangedSelection();
frame_->Client()->DidChangeSelection(
!GetFrameSelection().GetSelectionInDOMTree().IsRange());
SetStartNewKillRingSequence(true);
}
SpellChecker& Editor::GetSpellChecker() const {
return GetFrame().GetSpellChecker();
}
FrameSelection& Editor::GetFrameSelection() const {
return GetFrame().Selection();
}
void Editor::SetMark() {
mark_ = GetFrameSelection().ComputeVisibleSelectionInDOMTree();
mark_is_directional_ = GetFrameSelection().IsDirectional();
}
void Editor::ToggleOverwriteModeEnabled() {
overwrite_mode_enabled_ = !overwrite_mode_enabled_;
GetFrameSelection().SetShouldShowBlockCursor(overwrite_mode_enabled_);
}
void Editor::ReplaceSelection(const String& text) {
DCHECK(!GetFrame().GetDocument()->NeedsLayoutTreeUpdate());
bool select_replacement = Behavior().ShouldSelectReplacement();
bool smart_replace = false;
ReplaceSelectionWithText(text, select_replacement, smart_replace,
InputEvent::InputType::kInsertReplacementText);
}
void Editor::Trace(Visitor* visitor) const {
visitor->Trace(frame_);
visitor->Trace(last_edit_command_);
visitor->Trace(undo_stack_);
visitor->Trace(mark_);
visitor->Trace(typing_style_);
}
} // namespace blink