mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-05-04 02:08:53 +00:00
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.
129 lines
3.8 KiB
C++
129 lines
3.8 KiB
C++
/*
|
|
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <LibWeb/DOM/Element.h>
|
|
#include <LibWeb/HTML/EventNames.h>
|
|
#include <LibWeb/HTML/GlobalEventHandlers.h>
|
|
#include <LibWeb/HTML/TokenizedFeatures.h>
|
|
|
|
namespace Web::HTML {
|
|
|
|
// https://html.spec.whatwg.org/multipage/dom.html#attr-dir
|
|
#define ENUMERATE_HTML_ELEMENT_DIR_ATTRIBUTES \
|
|
__ENUMERATE_HTML_ELEMENT_DIR_ATTRIBUTE(ltr) \
|
|
__ENUMERATE_HTML_ELEMENT_DIR_ATTRIBUTE(rtl) \
|
|
__ENUMERATE_HTML_ELEMENT_DIR_ATTRIBUTE(auto)
|
|
|
|
class HTMLElement
|
|
: public DOM::Element
|
|
, public HTML::GlobalEventHandlers {
|
|
WEB_PLATFORM_OBJECT(HTMLElement, DOM::Element);
|
|
JS_DECLARE_ALLOCATOR(HTMLElement);
|
|
|
|
public:
|
|
virtual ~HTMLElement() override;
|
|
|
|
Optional<String> title() const { return attribute(HTML::AttributeNames::title); }
|
|
|
|
StringView dir() const;
|
|
void set_dir(String const&);
|
|
|
|
virtual bool is_editable() const final;
|
|
virtual bool is_focusable() const override;
|
|
bool is_content_editable() const;
|
|
StringView content_editable() const;
|
|
WebIDL::ExceptionOr<void> set_content_editable(StringView);
|
|
|
|
String inner_text();
|
|
void set_inner_text(StringView);
|
|
|
|
[[nodiscard]] String outer_text();
|
|
WebIDL::ExceptionOr<void> set_outer_text(String);
|
|
|
|
int offset_top() const;
|
|
int offset_left() const;
|
|
int offset_width() const;
|
|
int offset_height() const;
|
|
JS::GCPtr<Element> offset_parent() const;
|
|
|
|
bool cannot_navigate() const;
|
|
|
|
[[nodiscard]] JS::NonnullGCPtr<DOMStringMap> dataset();
|
|
|
|
void focus();
|
|
|
|
void click();
|
|
|
|
void blur();
|
|
|
|
[[nodiscard]] String access_key_label() const;
|
|
|
|
bool fire_a_synthetic_pointer_event(FlyString const& type, DOM::Element& target, bool not_trusted);
|
|
|
|
// https://html.spec.whatwg.org/multipage/forms.html#category-label
|
|
virtual bool is_labelable() const { return false; }
|
|
|
|
JS::GCPtr<DOM::NodeList> labels();
|
|
|
|
virtual Optional<ARIA::Role> default_role() const override;
|
|
|
|
String get_an_elements_target() const;
|
|
TokenizedFeature::NoOpener get_an_elements_noopener(StringView target) const;
|
|
|
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<ElementInternals>> attach_internals();
|
|
|
|
WebIDL::ExceptionOr<void> set_popover(Optional<String> value);
|
|
Optional<String> popover() const;
|
|
|
|
protected:
|
|
HTMLElement(DOM::Document&, DOM::QualifiedName);
|
|
|
|
virtual void initialize(JS::Realm&) override;
|
|
|
|
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value) override;
|
|
|
|
virtual void visit_edges(Cell::Visitor&) override;
|
|
|
|
private:
|
|
virtual bool is_html_element() const final { return true; }
|
|
|
|
// ^HTML::GlobalEventHandlers
|
|
virtual JS::GCPtr<DOM::EventTarget> global_event_handlers_to_event_target(FlyString const&) override { return *this; }
|
|
virtual void did_receive_focus() override;
|
|
virtual void did_lose_focus() override;
|
|
|
|
[[nodiscard]] String get_the_text_steps();
|
|
void append_rendered_text_fragment(StringView input);
|
|
|
|
JS::GCPtr<DOMStringMap> m_dataset;
|
|
|
|
JS::GCPtr<DOM::NodeList> m_labels;
|
|
|
|
// https://html.spec.whatwg.org/multipage/custom-elements.html#attached-internals
|
|
JS::GCPtr<ElementInternals> m_attached_internals;
|
|
|
|
enum class ContentEditableState {
|
|
True,
|
|
False,
|
|
Inherit,
|
|
};
|
|
ContentEditableState m_content_editable_state { ContentEditableState::Inherit };
|
|
|
|
// https://html.spec.whatwg.org/multipage/interaction.html#locked-for-focus
|
|
bool m_locked_for_focus { false };
|
|
|
|
// https://html.spec.whatwg.org/multipage/interaction.html#click-in-progress-flag
|
|
bool m_click_in_progress { false };
|
|
};
|
|
|
|
}
|
|
|
|
namespace Web::DOM {
|
|
template<>
|
|
inline bool Node::fast_is<HTML::HTMLElement>() const { return is_html_element(); }
|
|
}
|