ladybird/Libraries/LibWeb/Selection/Selection.h

89 lines
2.9 KiB
C
Raw Normal View History

/*
* Copyright (c) 2021-2022, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
2022-09-04 13:20:53 +02:00
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::Selection {
2022-09-04 13:20:53 +02:00
class Selection final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(Selection, Bindings::PlatformObject);
GC_DECLARE_ALLOCATOR(Selection);
2022-09-04 13:20:53 +02:00
public:
[[nodiscard]] static GC::Ref<Selection> create(GC::Ref<JS::Realm>, GC::Ref<DOM::Document>);
2022-09-04 13:20:53 +02:00
virtual ~Selection() override;
enum class Direction {
Forwards,
Backwards,
Directionless,
};
GC::Ptr<DOM::Node> anchor_node();
unsigned anchor_offset();
GC::Ptr<DOM::Node> focus_node();
unsigned focus_offset() const;
bool is_collapsed() const;
unsigned range_count() const;
String type() const;
2024-10-13 14:38:25 +11:00
String direction() const;
WebIDL::ExceptionOr<GC::Ptr<DOM::Range>> get_range_at(unsigned index);
void add_range(GC::Ref<DOM::Range>);
WebIDL::ExceptionOr<void> remove_range(GC::Ref<DOM::Range>);
void remove_all_ranges();
void empty();
WebIDL::ExceptionOr<void> collapse(GC::Ptr<DOM::Node>, unsigned offset);
WebIDL::ExceptionOr<void> set_position(GC::Ptr<DOM::Node>, unsigned offset);
WebIDL::ExceptionOr<void> collapse_to_start();
WebIDL::ExceptionOr<void> collapse_to_end();
WebIDL::ExceptionOr<void> extend(GC::Ref<DOM::Node>, unsigned offset);
WebIDL::ExceptionOr<void> set_base_and_extent(GC::Ref<DOM::Node> anchor_node, unsigned anchor_offset, GC::Ref<DOM::Node> focus_node, unsigned focus_offset);
WebIDL::ExceptionOr<void> select_all_children(GC::Ref<DOM::Node>);
2025-05-15 12:31:41 +12:00
WebIDL::ExceptionOr<void> modify(Optional<String> alter, Optional<String> direction, Optional<String> granularity);
WebIDL::ExceptionOr<void>
delete_from_document();
bool contains_node(GC::Ref<DOM::Node>, bool allow_partial_containment) const;
String to_string() const;
2022-09-04 13:20:53 +02:00
// Non-standard convenience accessor for the selection's range.
GC::Ptr<DOM::Range> range() const;
// Non-standard accessor for the selection's document.
GC::Ref<DOM::Document> document() const;
LibWeb: Separate text control input events handling from contenteditable This input event handling change is intended to address the following design issues: - Having `DOM::Position` is unnecessary complexity when `Selection` exists because caret position could be described by the selection object with a collapsed state. Before this change, we had to synchronize those whenever one of them was modified, and there were already bugs caused by that, i.e., caret position was not changed when selection offset was modified from the JS side. - Selection API exposes selection offset within `<textarea>` and `<input>`, which is not supposed to happen. These objects should manage their selection state by themselves and have selection offset even when they are not displayed. - `EventHandler` looks only at `DOM::Text` owned by `DOM::Position` while doing text manipulations. It works fine for `<input>` and `<textarea>`, but `contenteditable` needs to consider all text descendant text nodes; i.e., if the cursor is moved outside of `DOM::Text`, we need to look for an adjacent text node to move the cursor there. With this change, `EventHandler` no longer does direct manipulations on caret position or text content, but instead delegates them to the active `InputEventsTarget`, which could be either `FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or `EditingHostManager` (for `contenteditable`). The `Selection` object is used to manage both selection and caret position for `contenteditable`, and text control elements manage their own selection state that is not exposed by Selection API. This change improves text editing on Discord, as now we don't have to refocus the `contenteditable` element after character input. The problem was that selection manipulations from the JS side were not propagated to `DOM::Position`. I expect this change to make future correctness improvements for `contenteditable` (and `designMode`) easier, as now it's decoupled from `<input>` and `<textarea>` and separated from `EventHandler`, which is quite a busy file.
2024-10-23 21:26:58 +02:00
// Non-standard
GC::Ptr<DOM::Position> cursor_position() const;
LibWeb: Separate text control input events handling from contenteditable This input event handling change is intended to address the following design issues: - Having `DOM::Position` is unnecessary complexity when `Selection` exists because caret position could be described by the selection object with a collapsed state. Before this change, we had to synchronize those whenever one of them was modified, and there were already bugs caused by that, i.e., caret position was not changed when selection offset was modified from the JS side. - Selection API exposes selection offset within `<textarea>` and `<input>`, which is not supposed to happen. These objects should manage their selection state by themselves and have selection offset even when they are not displayed. - `EventHandler` looks only at `DOM::Text` owned by `DOM::Position` while doing text manipulations. It works fine for `<input>` and `<textarea>`, but `contenteditable` needs to consider all text descendant text nodes; i.e., if the cursor is moved outside of `DOM::Text`, we need to look for an adjacent text node to move the cursor there. With this change, `EventHandler` no longer does direct manipulations on caret position or text content, but instead delegates them to the active `InputEventsTarget`, which could be either `FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or `EditingHostManager` (for `contenteditable`). The `Selection` object is used to manage both selection and caret position for `contenteditable`, and text control elements manage their own selection state that is not exposed by Selection API. This change improves text editing on Discord, as now we don't have to refocus the `contenteditable` element after character input. The problem was that selection manipulations from the JS side were not propagated to `DOM::Position`. I expect this change to make future correctness improvements for `contenteditable` (and `designMode`) easier, as now it's decoupled from `<input>` and `<textarea>` and separated from `EventHandler`, which is quite a busy file.
2024-10-23 21:26:58 +02:00
// Non-standard
void move_offset_to_next_character(bool collapse_selection);
void move_offset_to_previous_character(bool collapse_selection);
void move_offset_to_next_word(bool collapse_selection);
void move_offset_to_previous_word(bool collapse_selection);
2022-09-04 13:20:53 +02:00
private:
Selection(GC::Ref<JS::Realm>, GC::Ref<DOM::Document>);
[[nodiscard]] bool is_empty() const;
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
void set_range(GC::Ptr<DOM::Range>);
// https://w3c.github.io/selection-api/#dfn-empty
GC::Ptr<DOM::Range> m_range;
GC::Ref<DOM::Document> m_document;
Direction m_direction { Direction::Directionless };
};
}