LibWeb: Refactor "editable" and "editing host" concepts

The DOM spec defines what it means for an element to be an "editing
host", and the Editing spec does the same for the "editable" concept.
Replace our `Node::is_editable()` implementation with these
spec-compliant algorithms.

An editing host is an element that has the properties to make its
contents effectively editable. Editable elements are descendants of an
editing host. Concepts like the inheritable contenteditable attribute
are propagated through the editable algorithm.
This commit is contained in:
Jelle Raaijmakers 2024-12-06 11:41:20 +01:00 committed by Jelle Raaijmakers
commit 1c55153d43
Notes: github-actions[bot] 2024-12-10 13:55:36 +00:00
22 changed files with 85 additions and 102 deletions

View file

@ -588,7 +588,7 @@ void FormAssociatedTextControlElement::set_the_selection_range(Optional<WebIDL::
void FormAssociatedTextControlElement::handle_insert(String const& data)
{
auto text_node = form_associated_element_to_text_node();
if (!text_node || !text_node->is_editable())
if (!text_node || !is_mutable())
return;
String data_for_insertion = data;
@ -613,7 +613,7 @@ void FormAssociatedTextControlElement::handle_insert(String const& data)
void FormAssociatedTextControlElement::handle_delete(DeleteDirection direction)
{
auto text_node = form_associated_element_to_text_node();
if (!text_node || !text_node->is_editable())
if (!text_node || !is_mutable())
return;
auto selection_start = this->selection_start();
auto selection_end = this->selection_end();

View file

@ -170,6 +170,9 @@ public:
bool has_scheduled_selectionchange_event() const { return m_has_scheduled_selectionchange_event; }
void set_scheduled_selectionchange_event(bool value) { m_has_scheduled_selectionchange_event = value; }
bool is_mutable() const { return m_is_mutable; }
void set_is_mutable(bool is_mutable) { m_is_mutable = is_mutable; }
virtual void did_edit_text_node() = 0;
virtual GC::Ptr<DOM::Text> form_associated_element_to_text_node() = 0;
@ -205,6 +208,9 @@ private:
// https://w3c.github.io/selection-api/#dfn-has-scheduled-selectionchange-event
bool m_has_scheduled_selectionchange_event { false };
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#mutability
bool m_is_mutable { true };
};
}

View file

@ -86,25 +86,9 @@ void HTMLElement::set_dir(String const& dir)
MUST(set_attribute(HTML::AttributeNames::dir, dir));
}
bool HTMLElement::is_editable() const
{
switch (m_content_editable_state) {
case ContentEditableState::True:
case ContentEditableState::PlaintextOnly:
return true;
case ContentEditableState::False:
return false;
case ContentEditableState::Inherit:
return parent() && parent()->is_editable();
default:
VERIFY_NOT_REACHED();
}
}
bool HTMLElement::is_focusable() const
{
return m_content_editable_state == ContentEditableState::True
|| m_content_editable_state == ContentEditableState::PlaintextOnly;
return is_editing_host();
}
// https://html.spec.whatwg.org/multipage/interaction.html#dom-iscontenteditable
@ -112,7 +96,7 @@ bool HTMLElement::is_content_editable() const
{
// The isContentEditable IDL attribute, on getting, must return true if the element is either an editing host or
// editable, and false otherwise.
return is_editable();
return is_editable_or_editing_host();
}
StringView HTMLElement::content_editable() const
@ -1181,7 +1165,7 @@ WebIDL::ExceptionOr<bool> HTMLElement::toggle_popover(TogglePopoverOptionsOrForc
void HTMLElement::did_receive_focus()
{
if (m_content_editable_state != ContentEditableState::True)
if (!first_is_one_of(m_content_editable_state, ContentEditableState::True, ContentEditableState::PlaintextOnly))
return;
auto editing_host = document().editing_host_manager();
@ -1202,7 +1186,7 @@ void HTMLElement::did_receive_focus()
void HTMLElement::did_lose_focus()
{
if (m_content_editable_state != ContentEditableState::True)
if (!first_is_one_of(m_content_editable_state, ContentEditableState::True, ContentEditableState::PlaintextOnly))
return;
document().editing_host_manager()->set_active_contenteditable_element(nullptr);

View file

@ -73,7 +73,6 @@ public:
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;

View file

@ -336,7 +336,7 @@ WebIDL::ExceptionOr<void> HTMLInputElement::show_picker()
// The showPicker() method steps are:
// 1. If this is not mutable, then throw an "InvalidStateError" DOMException.
if (!m_is_mutable)
if (!is_mutable())
return WebIDL::InvalidStateError::create(realm(), "Element is not mutable"_string);
// 2. If this's relevant settings object's origin is not same origin with this's relevant settings object's top-level origin,
@ -712,10 +712,7 @@ void HTMLInputElement::handle_maxlength_attribute()
void HTMLInputElement::handle_readonly_attribute(Optional<String> const& maybe_value)
{
// The readonly attribute is a boolean attribute that controls whether or not the user can edit the form control. When specified, the element is not mutable.
m_is_mutable = !maybe_value.has_value() || !is_allowed_to_be_readonly(m_type);
if (m_text_node)
m_text_node->set_always_editable(m_is_mutable);
set_is_mutable(!maybe_value.has_value() || !is_allowed_to_be_readonly(m_type));
}
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:attr-input-placeholder-3
@ -871,12 +868,7 @@ void HTMLInputElement::create_text_input_shadow_tree()
MUST(element->append_child(*m_inner_text_element));
m_text_node = realm().create<DOM::Text>(document(), move(initial_value));
if (type_state() == TypeAttributeState::FileUpload) {
// NOTE: file upload state is mutable, but we don't allow the text node to be modifed
m_text_node->set_always_editable(false);
} else {
handle_readonly_attribute(attribute(HTML::AttributeNames::readonly));
}
handle_readonly_attribute(attribute(HTML::AttributeNames::readonly));
if (type_state() == TypeAttributeState::Password)
m_text_node->set_is_password_input({}, true);
handle_maxlength_attribute();
@ -907,7 +899,7 @@ void HTMLInputElement::create_text_input_shadow_tree()
auto up_callback_function = JS::NativeFunction::create(
realm(), [this](JS::VM&) {
if (m_is_mutable) {
if (is_mutable()) {
MUST(step_up());
user_interaction_did_change_input_value();
}
@ -929,7 +921,7 @@ void HTMLInputElement::create_text_input_shadow_tree()
auto down_callback_function = JS::NativeFunction::create(
realm(), [this](JS::VM&) {
if (m_is_mutable) {
if (is_mutable()) {
MUST(step_down());
user_interaction_did_change_input_value();
}

View file

@ -100,8 +100,6 @@ public:
bool indeterminate() const { return m_indeterminate; }
void set_indeterminate(bool);
bool is_mutable() const { return m_is_mutable; }
void did_pick_color(Optional<Color> picked_color, ColorPickerUpdateState state);
enum class MultipleHandling {
@ -339,9 +337,6 @@ private:
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-dirty
bool m_dirty_value { false };
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:concept-fe-mutable
bool m_is_mutable { true };
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:legacy-pre-activation-behavior
bool m_before_legacy_pre_activation_behavior_checked { false };
bool m_before_legacy_pre_activation_behavior_indeterminate { false };

View file

@ -394,10 +394,7 @@ void HTMLTextAreaElement::create_shadow_tree_if_needed()
void HTMLTextAreaElement::handle_readonly_attribute(Optional<String> const& maybe_value)
{
// The readonly attribute is a boolean attribute that controls whether or not the user can edit the form control. When specified, the element is not mutable.
m_is_mutable = !maybe_value.has_value();
if (m_text_node)
m_text_node->set_always_editable(m_is_mutable);
set_is_mutable(!maybe_value.has_value());
}
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-maxlength

View file

@ -157,9 +157,6 @@ private:
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-dirty
bool m_dirty_value { false };
// https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:concept-fe-mutable
bool m_is_mutable { true };
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-textarea-raw-value
String m_raw_value;