mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-10 10:09:14 +00:00
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:
parent
f88c13a58c
commit
1c55153d43
Notes:
github-actions[bot]
2024-12-10 13:55:36 +00:00
Author: https://github.com/gmta
Commit: 1c55153d43
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2844
22 changed files with 85 additions and 102 deletions
|
@ -411,7 +411,7 @@ static bool matches_read_write_pseudo_class(DOM::Element const& element)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// - elements that are editing hosts or editable and are neither input elements nor textarea elements
|
// - elements that are editing hosts or editable and are neither input elements nor textarea elements
|
||||||
return element.is_editable();
|
return element.is_editable_or_editing_host();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.w3.org/TR/selectors-4/#open-state
|
// https://www.w3.org/TR/selectors-4/#open-state
|
||||||
|
|
|
@ -2000,11 +2000,6 @@ String const& Document::compat_mode() const
|
||||||
return css1_compat;
|
return css1_compat;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Document::is_editable() const
|
|
||||||
{
|
|
||||||
return m_editable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/interaction.html#dom-documentorshadowroot-activeelement
|
// https://html.spec.whatwg.org/multipage/interaction.html#dom-documentorshadowroot-activeelement
|
||||||
void Document::update_active_element()
|
void Document::update_active_element()
|
||||||
{
|
{
|
||||||
|
@ -2060,9 +2055,8 @@ void Document::set_focused_element(Element* element)
|
||||||
if (auto* invalidation_target = find_common_ancestor(old_focused_element, m_focused_element) ?: this)
|
if (auto* invalidation_target = find_common_ancestor(old_focused_element, m_focused_element) ?: this)
|
||||||
invalidation_target->invalidate_style(StyleInvalidationReason::FocusedElementChange);
|
invalidation_target->invalidate_style(StyleInvalidationReason::FocusedElementChange);
|
||||||
|
|
||||||
if (m_focused_element) {
|
if (m_focused_element)
|
||||||
m_focused_element->did_receive_focus();
|
m_focused_element->did_receive_focus();
|
||||||
}
|
|
||||||
|
|
||||||
if (paintable())
|
if (paintable())
|
||||||
paintable()->set_needs_display();
|
paintable()->set_needs_display();
|
||||||
|
@ -5538,7 +5532,7 @@ InputEventsTarget* Document::active_input_events_target()
|
||||||
return static_cast<HTML::HTMLInputElement*>(focused_element);
|
return static_cast<HTML::HTMLInputElement*>(focused_element);
|
||||||
if (is<HTML::HTMLTextAreaElement>(*focused_element))
|
if (is<HTML::HTMLTextAreaElement>(*focused_element))
|
||||||
return static_cast<HTML::HTMLTextAreaElement*>(focused_element);
|
return static_cast<HTML::HTMLTextAreaElement*>(focused_element);
|
||||||
if (is<HTML::HTMLElement>(*focused_element) && static_cast<HTML::HTMLElement*>(focused_element)->is_editable())
|
if (focused_element->is_editable_or_editing_host())
|
||||||
return m_editing_host_manager;
|
return m_editing_host_manager;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -5546,9 +5540,8 @@ InputEventsTarget* Document::active_input_events_target()
|
||||||
GC::Ptr<DOM::Position> Document::cursor_position() const
|
GC::Ptr<DOM::Position> Document::cursor_position() const
|
||||||
{
|
{
|
||||||
auto const* focused_element = this->focused_element();
|
auto const* focused_element = this->focused_element();
|
||||||
if (!focused_element) {
|
if (!focused_element)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
|
||||||
|
|
||||||
Optional<HTML::FormAssociatedTextControlElement const&> target {};
|
Optional<HTML::FormAssociatedTextControlElement const&> target {};
|
||||||
if (is<HTML::HTMLInputElement>(*focused_element))
|
if (is<HTML::HTMLInputElement>(*focused_element))
|
||||||
|
@ -5556,13 +5549,11 @@ GC::Ptr<DOM::Position> Document::cursor_position() const
|
||||||
else if (is<HTML::HTMLTextAreaElement>(*focused_element))
|
else if (is<HTML::HTMLTextAreaElement>(*focused_element))
|
||||||
target = static_cast<HTML::HTMLTextAreaElement const&>(*focused_element);
|
target = static_cast<HTML::HTMLTextAreaElement const&>(*focused_element);
|
||||||
|
|
||||||
if (target.has_value()) {
|
if (target.has_value())
|
||||||
return target->cursor_position();
|
return target->cursor_position();
|
||||||
}
|
|
||||||
|
|
||||||
if (is<HTML::HTMLElement>(*focused_element) && static_cast<HTML::HTMLElement const*>(focused_element)->is_editable()) {
|
if (focused_element->is_editable_or_editing_host())
|
||||||
return m_selection->cursor_position();
|
return m_selection->cursor_position();
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -335,7 +335,6 @@ public:
|
||||||
String const& compat_mode() const;
|
String const& compat_mode() const;
|
||||||
|
|
||||||
void set_editable(bool editable) { m_editable = editable; }
|
void set_editable(bool editable) { m_editable = editable; }
|
||||||
virtual bool is_editable() const final;
|
|
||||||
|
|
||||||
Element* focused_element() { return m_focused_element.ptr(); }
|
Element* focused_element() { return m_focused_element.ptr(); }
|
||||||
Element const* focused_element() const { return m_focused_element.ptr(); }
|
Element const* focused_element() const { return m_focused_element.ptr(); }
|
||||||
|
|
|
@ -36,14 +36,12 @@ void EditingHostManager::handle_insert(String const& data)
|
||||||
auto selection = m_document->get_selection();
|
auto selection = m_document->get_selection();
|
||||||
|
|
||||||
auto selection_range = selection->range();
|
auto selection_range = selection->range();
|
||||||
if (!selection_range) {
|
if (!selection_range)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
auto node = selection->anchor_node();
|
auto node = selection->anchor_node();
|
||||||
if (!node || !node->is_editable()) {
|
if (!node || !node->is_editable_or_editing_host())
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (!is<DOM::Text>(*node)) {
|
if (!is<DOM::Text>(*node)) {
|
||||||
auto& realm = node->realm();
|
auto& realm = node->realm();
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
* Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org>
|
* Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org>
|
||||||
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
||||||
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
|
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
|
||||||
|
* Copyright (c) 2024, Jelle Raaijmakers <jelle@ladybird.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -11,7 +12,6 @@
|
||||||
#include <LibGC/DeferGC.h>
|
#include <LibGC/DeferGC.h>
|
||||||
#include <LibJS/Runtime/FunctionObject.h>
|
#include <LibJS/Runtime/FunctionObject.h>
|
||||||
#include <LibRegex/Regex.h>
|
#include <LibRegex/Regex.h>
|
||||||
#include <LibURL/Origin.h>
|
|
||||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||||
#include <LibWeb/Bindings/NodePrototype.h>
|
#include <LibWeb/Bindings/NodePrototype.h>
|
||||||
#include <LibWeb/DOM/Attr.h>
|
#include <LibWeb/DOM/Attr.h>
|
||||||
|
@ -44,16 +44,18 @@
|
||||||
#include <LibWeb/HTML/HTMLSlotElement.h>
|
#include <LibWeb/HTML/HTMLSlotElement.h>
|
||||||
#include <LibWeb/HTML/HTMLStyleElement.h>
|
#include <LibWeb/HTML/HTMLStyleElement.h>
|
||||||
#include <LibWeb/HTML/HTMLTableElement.h>
|
#include <LibWeb/HTML/HTMLTableElement.h>
|
||||||
|
#include <LibWeb/HTML/HTMLTextAreaElement.h>
|
||||||
#include <LibWeb/HTML/Navigable.h>
|
#include <LibWeb/HTML/Navigable.h>
|
||||||
#include <LibWeb/HTML/NavigableContainer.h>
|
#include <LibWeb/HTML/NavigableContainer.h>
|
||||||
#include <LibWeb/HTML/Parser/HTMLParser.h>
|
#include <LibWeb/HTML/Parser/HTMLParser.h>
|
||||||
#include <LibWeb/Infra/CharacterTypes.h>
|
#include <LibWeb/Infra/CharacterTypes.h>
|
||||||
#include <LibWeb/Layout/Node.h>
|
#include <LibWeb/Layout/Node.h>
|
||||||
#include <LibWeb/Layout/TextNode.h>
|
#include <LibWeb/Layout/TextNode.h>
|
||||||
#include <LibWeb/Layout/Viewport.h>
|
#include <LibWeb/MathML/MathMLElement.h>
|
||||||
#include <LibWeb/Namespace.h>
|
#include <LibWeb/Namespace.h>
|
||||||
#include <LibWeb/Painting/Paintable.h>
|
#include <LibWeb/Painting/Paintable.h>
|
||||||
#include <LibWeb/Painting/PaintableBox.h>
|
#include <LibWeb/Painting/PaintableBox.h>
|
||||||
|
#include <LibWeb/SVG/SVGElement.h>
|
||||||
#include <LibWeb/SVG/SVGTitleElement.h>
|
#include <LibWeb/SVG/SVGTitleElement.h>
|
||||||
#include <LibWeb/XLink/AttributeNames.h>
|
#include <LibWeb/XLink/AttributeNames.h>
|
||||||
|
|
||||||
|
@ -1183,9 +1185,48 @@ void Node::set_document(Badge<Document>, Document& document)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#editable
|
||||||
bool Node::is_editable() const
|
bool Node::is_editable() const
|
||||||
{
|
{
|
||||||
return parent() && parent()->is_editable();
|
// Something is editable if it is a node; it is not an editing host;
|
||||||
|
if (is_editing_host())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// it does not have a contenteditable attribute set to the false state;
|
||||||
|
if (is<HTML::HTMLElement>(this) && static_cast<HTML::HTMLElement const&>(*this).content_editable_state() == HTML::ContentEditableState::False)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// its parent is an editing host or editable;
|
||||||
|
if (!parent() || !parent()->is_editable_or_editing_host())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// and either it is an HTML element,
|
||||||
|
if (is<HTML::HTMLElement>(this))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// or it is an svg or math element,
|
||||||
|
if (is<SVG::SVGElement>(this) || is<MathML::MathMLElement>(this))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// or it is not an Element and its parent is an HTML element.
|
||||||
|
return !is<Element>(this) && is<HTML::HTMLElement>(parent());
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/interaction.html#editing-host
|
||||||
|
bool Node::is_editing_host() const
|
||||||
|
{
|
||||||
|
// NOTE: Both conditions below require this to be an HTML element.
|
||||||
|
if (!is<HTML::HTMLElement>(this))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// An editing host is either an HTML element with its contenteditable attribute in the true state or
|
||||||
|
// plaintext-only state,
|
||||||
|
auto state = static_cast<HTML::HTMLElement const&>(*this).content_editable_state();
|
||||||
|
if (state == HTML::ContentEditableState::True || state == HTML::ContentEditableState::PlaintextOnly)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// or a child HTML element of a Document whose design mode enabled is true.
|
||||||
|
return is<Document>(parent()) && static_cast<Document const&>(*parent()).design_mode_enabled_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::set_layout_node(Badge<Layout::Node>, GC::Ref<Layout::Node> layout_node)
|
void Node::set_layout_node(Badge<Layout::Node>, GC::Ref<Layout::Node> layout_node)
|
||||||
|
|
|
@ -137,7 +137,9 @@ public:
|
||||||
// NOTE: This is intended for the JS bindings.
|
// NOTE: This is intended for the JS bindings.
|
||||||
u16 node_type() const { return (u16)m_type; }
|
u16 node_type() const { return (u16)m_type; }
|
||||||
|
|
||||||
virtual bool is_editable() const;
|
bool is_editable() const;
|
||||||
|
bool is_editing_host() const;
|
||||||
|
bool is_editable_or_editing_host() const { return is_editable() || is_editing_host(); }
|
||||||
|
|
||||||
virtual bool is_dom_node() const final { return true; }
|
virtual bool is_dom_node() const final { return true; }
|
||||||
virtual bool is_html_element() const { return false; }
|
virtual bool is_html_element() const { return false; }
|
||||||
|
|
|
@ -26,9 +26,6 @@ public:
|
||||||
|
|
||||||
// ^Node
|
// ^Node
|
||||||
virtual FlyString node_name() const override { return "#text"_fly_string; }
|
virtual FlyString node_name() const override { return "#text"_fly_string; }
|
||||||
virtual bool is_editable() const override { return m_always_editable || CharacterData::is_editable(); }
|
|
||||||
|
|
||||||
void set_always_editable(bool b) { m_always_editable = b; }
|
|
||||||
|
|
||||||
Optional<size_t> max_length() const { return m_max_length; }
|
Optional<size_t> max_length() const { return m_max_length; }
|
||||||
void set_max_length(Optional<size_t> max_length) { m_max_length = move(max_length); }
|
void set_max_length(Optional<size_t> max_length) { m_max_length = move(max_length); }
|
||||||
|
@ -51,7 +48,6 @@ protected:
|
||||||
private:
|
private:
|
||||||
GC::Ptr<Element> m_owner;
|
GC::Ptr<Element> m_owner;
|
||||||
|
|
||||||
bool m_always_editable { false };
|
|
||||||
Optional<size_t> m_max_length {};
|
Optional<size_t> m_max_length {};
|
||||||
bool m_is_password_input { false };
|
bool m_is_password_input { false };
|
||||||
};
|
};
|
||||||
|
|
|
@ -384,7 +384,7 @@ bool command_insert_paragraph_action(DOM::Document& document, String const&)
|
||||||
// 2. If the active range's start node is neither editable nor an editing host, return true.
|
// 2. If the active range's start node is neither editable nor an editing host, return true.
|
||||||
auto& active_range = *selection.range();
|
auto& active_range = *selection.range();
|
||||||
GC::Ptr<DOM::Node> node = active_range.start_container();
|
GC::Ptr<DOM::Node> node = active_range.start_container();
|
||||||
if (!node->is_editable() && !is_editing_host(*node))
|
if (!node->is_editable_or_editing_host())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// 3. Let node and offset be the active range's start node and offset.
|
// 3. Let node and offset be the active range's start node and offset.
|
||||||
|
|
|
@ -118,7 +118,7 @@ bool Document::query_command_enabled(FlyString const& command)
|
||||||
|
|
||||||
// its start node is either editable or an editing host,
|
// its start node is either editable or an editing host,
|
||||||
auto start_node = active_range->start_container();
|
auto start_node = active_range->start_container();
|
||||||
if (!start_node->is_editable() && !Editing::is_editing_host(start_node))
|
if (!start_node->is_editable_or_editing_host())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// FIXME: the editing host of its start node is not an EditContext editing host,
|
// FIXME: the editing host of its start node is not an EditContext editing host,
|
||||||
|
@ -126,7 +126,7 @@ bool Document::query_command_enabled(FlyString const& command)
|
||||||
|
|
||||||
// its end node is either editable or an editing host,
|
// its end node is either editable or an editing host,
|
||||||
auto& end_node = *active_range->end_container();
|
auto& end_node = *active_range->end_container();
|
||||||
if (!end_node.is_editable() && !Editing::is_editing_host(end_node))
|
if (!end_node.is_editable_or_editing_host())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// FIXME: the editing host of its end node is not an EditContext editing host,
|
// FIXME: the editing host of its end node is not an EditContext editing host,
|
||||||
|
|
|
@ -201,7 +201,7 @@ String canonical_space_sequence(u32 length, bool non_breaking_start, bool non_br
|
||||||
void canonicalize_whitespace(GC::Ref<DOM::Node> node, u32 offset, bool fix_collapsed_space)
|
void canonicalize_whitespace(GC::Ref<DOM::Node> node, u32 offset, bool fix_collapsed_space)
|
||||||
{
|
{
|
||||||
// 1. If node is neither editable nor an editing host, abort these steps.
|
// 1. If node is neither editable nor an editing host, abort these steps.
|
||||||
if (!node->is_editable() || !is_editing_host(node))
|
if (!node->is_editable_or_editing_host())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// 2. Let start node equal node and let start offset equal offset.
|
// 2. Let start node equal node and let start offset equal offset.
|
||||||
|
@ -916,20 +916,6 @@ bool is_collapsed_whitespace_node(GC::Ref<DOM::Node> node)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/interaction.html#editing-host
|
|
||||||
bool is_editing_host(GC::Ref<DOM::Node> node)
|
|
||||||
{
|
|
||||||
// An editing host is either an HTML element with its contenteditable attribute in the true
|
|
||||||
// state or plaintext-only state, or a child HTML element of a Document whose design mode
|
|
||||||
// enabled is true.
|
|
||||||
if (!is<HTML::HTMLElement>(*node))
|
|
||||||
return false;
|
|
||||||
auto const& html_element = static_cast<HTML::HTMLElement&>(*node);
|
|
||||||
return html_element.content_editable_state() == HTML::ContentEditableState::True
|
|
||||||
|| html_element.content_editable_state() == HTML::ContentEditableState::PlaintextOnly
|
|
||||||
|| node->document().design_mode_enabled_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://w3c.github.io/editing/docs/execCommand/#element-with-inline-contents
|
// https://w3c.github.io/editing/docs/execCommand/#element-with-inline-contents
|
||||||
bool is_element_with_inline_contents(GC::Ref<DOM::Node> node)
|
bool is_element_with_inline_contents(GC::Ref<DOM::Node> node)
|
||||||
{
|
{
|
||||||
|
|
|
@ -34,7 +34,6 @@ bool is_block_end_point(GC::Ref<DOM::Node>, u32 offset);
|
||||||
bool is_block_node(GC::Ref<DOM::Node>);
|
bool is_block_node(GC::Ref<DOM::Node>);
|
||||||
bool is_block_start_point(GC::Ref<DOM::Node>, u32 offset);
|
bool is_block_start_point(GC::Ref<DOM::Node>, u32 offset);
|
||||||
bool is_collapsed_whitespace_node(GC::Ref<DOM::Node>);
|
bool is_collapsed_whitespace_node(GC::Ref<DOM::Node>);
|
||||||
bool is_editing_host(GC::Ref<DOM::Node>);
|
|
||||||
bool is_element_with_inline_contents(GC::Ref<DOM::Node>);
|
bool is_element_with_inline_contents(GC::Ref<DOM::Node>);
|
||||||
bool is_extraneous_line_break(GC::Ref<DOM::Node>);
|
bool is_extraneous_line_break(GC::Ref<DOM::Node>);
|
||||||
bool is_in_same_editing_host(GC::Ref<DOM::Node>, GC::Ref<DOM::Node>);
|
bool is_in_same_editing_host(GC::Ref<DOM::Node>, GC::Ref<DOM::Node>);
|
||||||
|
|
|
@ -588,7 +588,7 @@ void FormAssociatedTextControlElement::set_the_selection_range(Optional<WebIDL::
|
||||||
void FormAssociatedTextControlElement::handle_insert(String const& data)
|
void FormAssociatedTextControlElement::handle_insert(String const& data)
|
||||||
{
|
{
|
||||||
auto text_node = form_associated_element_to_text_node();
|
auto text_node = form_associated_element_to_text_node();
|
||||||
if (!text_node || !text_node->is_editable())
|
if (!text_node || !is_mutable())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
String data_for_insertion = data;
|
String data_for_insertion = data;
|
||||||
|
@ -613,7 +613,7 @@ void FormAssociatedTextControlElement::handle_insert(String const& data)
|
||||||
void FormAssociatedTextControlElement::handle_delete(DeleteDirection direction)
|
void FormAssociatedTextControlElement::handle_delete(DeleteDirection direction)
|
||||||
{
|
{
|
||||||
auto text_node = form_associated_element_to_text_node();
|
auto text_node = form_associated_element_to_text_node();
|
||||||
if (!text_node || !text_node->is_editable())
|
if (!text_node || !is_mutable())
|
||||||
return;
|
return;
|
||||||
auto selection_start = this->selection_start();
|
auto selection_start = this->selection_start();
|
||||||
auto selection_end = this->selection_end();
|
auto selection_end = this->selection_end();
|
||||||
|
|
|
@ -170,6 +170,9 @@ public:
|
||||||
bool has_scheduled_selectionchange_event() const { return m_has_scheduled_selectionchange_event; }
|
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; }
|
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 void did_edit_text_node() = 0;
|
||||||
|
|
||||||
virtual GC::Ptr<DOM::Text> form_associated_element_to_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
|
// https://w3c.github.io/selection-api/#dfn-has-scheduled-selectionchange-event
|
||||||
bool m_has_scheduled_selectionchange_event { false };
|
bool m_has_scheduled_selectionchange_event { false };
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#mutability
|
||||||
|
bool m_is_mutable { true };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,25 +86,9 @@ void HTMLElement::set_dir(String const& dir)
|
||||||
MUST(set_attribute(HTML::AttributeNames::dir, 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
|
bool HTMLElement::is_focusable() const
|
||||||
{
|
{
|
||||||
return m_content_editable_state == ContentEditableState::True
|
return is_editing_host();
|
||||||
|| m_content_editable_state == ContentEditableState::PlaintextOnly;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/interaction.html#dom-iscontenteditable
|
// 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
|
// The isContentEditable IDL attribute, on getting, must return true if the element is either an editing host or
|
||||||
// editable, and false otherwise.
|
// editable, and false otherwise.
|
||||||
return is_editable();
|
return is_editable_or_editing_host();
|
||||||
}
|
}
|
||||||
|
|
||||||
StringView HTMLElement::content_editable() const
|
StringView HTMLElement::content_editable() const
|
||||||
|
@ -1181,7 +1165,7 @@ WebIDL::ExceptionOr<bool> HTMLElement::toggle_popover(TogglePopoverOptionsOrForc
|
||||||
|
|
||||||
void HTMLElement::did_receive_focus()
|
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;
|
return;
|
||||||
|
|
||||||
auto editing_host = document().editing_host_manager();
|
auto editing_host = document().editing_host_manager();
|
||||||
|
@ -1202,7 +1186,7 @@ void HTMLElement::did_receive_focus()
|
||||||
|
|
||||||
void HTMLElement::did_lose_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;
|
return;
|
||||||
|
|
||||||
document().editing_host_manager()->set_active_contenteditable_element(nullptr);
|
document().editing_host_manager()->set_active_contenteditable_element(nullptr);
|
||||||
|
|
|
@ -73,7 +73,6 @@ public:
|
||||||
StringView dir() const;
|
StringView dir() const;
|
||||||
void set_dir(String const&);
|
void set_dir(String const&);
|
||||||
|
|
||||||
virtual bool is_editable() const final;
|
|
||||||
virtual bool is_focusable() const override;
|
virtual bool is_focusable() const override;
|
||||||
bool is_content_editable() const;
|
bool is_content_editable() const;
|
||||||
StringView content_editable() const;
|
StringView content_editable() const;
|
||||||
|
|
|
@ -336,7 +336,7 @@ WebIDL::ExceptionOr<void> HTMLInputElement::show_picker()
|
||||||
// The showPicker() method steps are:
|
// The showPicker() method steps are:
|
||||||
|
|
||||||
// 1. If this is not mutable, then throw an "InvalidStateError" DOMException.
|
// 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);
|
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,
|
// 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)
|
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.
|
// 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);
|
set_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:attr-input-placeholder-3
|
// 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));
|
MUST(element->append_child(*m_inner_text_element));
|
||||||
|
|
||||||
m_text_node = realm().create<DOM::Text>(document(), move(initial_value));
|
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)
|
if (type_state() == TypeAttributeState::Password)
|
||||||
m_text_node->set_is_password_input({}, true);
|
m_text_node->set_is_password_input({}, true);
|
||||||
handle_maxlength_attribute();
|
handle_maxlength_attribute();
|
||||||
|
@ -907,7 +899,7 @@ void HTMLInputElement::create_text_input_shadow_tree()
|
||||||
|
|
||||||
auto up_callback_function = JS::NativeFunction::create(
|
auto up_callback_function = JS::NativeFunction::create(
|
||||||
realm(), [this](JS::VM&) {
|
realm(), [this](JS::VM&) {
|
||||||
if (m_is_mutable) {
|
if (is_mutable()) {
|
||||||
MUST(step_up());
|
MUST(step_up());
|
||||||
user_interaction_did_change_input_value();
|
user_interaction_did_change_input_value();
|
||||||
}
|
}
|
||||||
|
@ -929,7 +921,7 @@ void HTMLInputElement::create_text_input_shadow_tree()
|
||||||
|
|
||||||
auto down_callback_function = JS::NativeFunction::create(
|
auto down_callback_function = JS::NativeFunction::create(
|
||||||
realm(), [this](JS::VM&) {
|
realm(), [this](JS::VM&) {
|
||||||
if (m_is_mutable) {
|
if (is_mutable()) {
|
||||||
MUST(step_down());
|
MUST(step_down());
|
||||||
user_interaction_did_change_input_value();
|
user_interaction_did_change_input_value();
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,8 +100,6 @@ public:
|
||||||
bool indeterminate() const { return m_indeterminate; }
|
bool indeterminate() const { return m_indeterminate; }
|
||||||
void set_indeterminate(bool);
|
void set_indeterminate(bool);
|
||||||
|
|
||||||
bool is_mutable() const { return m_is_mutable; }
|
|
||||||
|
|
||||||
void did_pick_color(Optional<Color> picked_color, ColorPickerUpdateState state);
|
void did_pick_color(Optional<Color> picked_color, ColorPickerUpdateState state);
|
||||||
|
|
||||||
enum class MultipleHandling {
|
enum class MultipleHandling {
|
||||||
|
@ -339,9 +337,6 @@ private:
|
||||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-dirty
|
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-dirty
|
||||||
bool m_dirty_value { false };
|
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
|
// 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_checked { false };
|
||||||
bool m_before_legacy_pre_activation_behavior_indeterminate { false };
|
bool m_before_legacy_pre_activation_behavior_indeterminate { false };
|
||||||
|
|
|
@ -394,10 +394,7 @@ void HTMLTextAreaElement::create_shadow_tree_if_needed()
|
||||||
void HTMLTextAreaElement::handle_readonly_attribute(Optional<String> const& maybe_value)
|
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.
|
// 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();
|
set_is_mutable(!maybe_value.has_value());
|
||||||
|
|
||||||
if (m_text_node)
|
|
||||||
m_text_node->set_always_editable(m_is_mutable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-maxlength
|
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-maxlength
|
||||||
|
|
|
@ -157,9 +157,6 @@ private:
|
||||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-dirty
|
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-dirty
|
||||||
bool m_dirty_value { false };
|
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
|
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-textarea-raw-value
|
||||||
String m_raw_value;
|
String m_raw_value;
|
||||||
|
|
||||||
|
|
|
@ -611,7 +611,7 @@ bool DragAndDropEventHandler::allow_text_drop(GC::Ref<DOM::Node> node) const
|
||||||
if (!m_drag_data_store->has_text_item())
|
if (!m_drag_data_store->has_text_item())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (node->is_editable())
|
if (node->is_editable_or_editing_host())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (is<HTML::HTMLTextAreaElement>(*node))
|
if (is<HTML::HTMLTextAreaElement>(*node))
|
||||||
|
|
|
@ -564,7 +564,12 @@ void paint_cursor_if_needed(PaintContext& context, TextPaintable const& paintabl
|
||||||
if (cursor_position->offset() < (unsigned)fragment.start() || cursor_position->offset() > (unsigned)(fragment.start() + fragment.length()))
|
if (cursor_position->offset() < (unsigned)fragment.start() || cursor_position->offset() > (unsigned)(fragment.start() + fragment.length()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!fragment.layout_node().dom_node() || !fragment.layout_node().dom_node()->is_editable())
|
auto active_element = document.active_element();
|
||||||
|
auto active_element_is_editable = is<HTML::FormAssociatedTextControlElement>(active_element)
|
||||||
|
&& dynamic_cast<HTML::FormAssociatedTextControlElement const&>(*active_element).is_mutable();
|
||||||
|
|
||||||
|
auto dom_node = fragment.layout_node().dom_node();
|
||||||
|
if (!dom_node || (!dom_node->is_editable() && !active_element_is_editable))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto fragment_rect = fragment.absolute_rect();
|
auto fragment_rect = fragment.absolute_rect();
|
||||||
|
|
|
@ -288,11 +288,7 @@ bool is_element_editable(Web::DOM::Element const& element)
|
||||||
bool is_element_mutable(Web::DOM::Element const& element)
|
bool is_element_mutable(Web::DOM::Element const& element)
|
||||||
{
|
{
|
||||||
// Denotes elements that are editing hosts or content editable.
|
// Denotes elements that are editing hosts or content editable.
|
||||||
if (!is<HTML::HTMLElement>(element))
|
return element.is_editable_or_editing_host();
|
||||||
return false;
|
|
||||||
|
|
||||||
auto const& html_element = static_cast<HTML::HTMLElement const&>(element);
|
|
||||||
return html_element.is_editable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://w3c.github.io/webdriver/#dfn-mutable-form-control-element
|
// https://w3c.github.io/webdriver/#dfn-mutable-form-control-element
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue