LibWeb: Implement "plaintext-only" state for contenteditable

This commit is contained in:
Jelle Raaijmakers 2024-12-01 23:40:08 +01:00 committed by Andreas Kling
parent 7e406ac668
commit 217567981a
Notes: github-actions[bot] 2024-12-02 23:21:14 +00:00
5 changed files with 104 additions and 14 deletions

View file

@ -816,11 +816,10 @@ 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.
// FIXME: check contenteditable "plaintext-only"
if (!is<HTML::HTMLElement>(*node))
return false;
auto const& html_element = static_cast<HTML::HTMLElement&>(*node);
return html_element.content_editable() == "true"sv || node->document().design_mode_enabled_state();
return html_element.content_editable().is_one_of("true"sv, "plaintext-only"sv) || node->document().design_mode_enabled_state();
}
// https://w3c.github.io/editing/docs/execCommand/#element-with-inline-contents

View file

@ -88,6 +88,7 @@ bool HTMLElement::is_editable() const
{
switch (m_content_editable_state) {
case ContentEditableState::True:
case ContentEditableState::PlaintextOnly:
return true;
case ContentEditableState::False:
return false;
@ -100,7 +101,8 @@ bool HTMLElement::is_editable() const
bool HTMLElement::is_focusable() const
{
return m_content_editable_state == ContentEditableState::True;
return m_content_editable_state == ContentEditableState::True
|| m_content_editable_state == ContentEditableState::PlaintextOnly;
}
// https://html.spec.whatwg.org/multipage/interaction.html#dom-iscontenteditable
@ -118,6 +120,8 @@ StringView HTMLElement::content_editable() const
return "true"sv;
case ContentEditableState::False:
return "false"sv;
case ContentEditableState::PlaintextOnly:
return "plaintext-only"sv;
case ContentEditableState::Inherit:
return "inherit"sv;
}
@ -135,11 +139,15 @@ WebIDL::ExceptionOr<void> HTMLElement::set_content_editable(StringView content_e
MUST(set_attribute(HTML::AttributeNames::contenteditable, "true"_string));
return {};
}
if (content_editable.equals_ignoring_ascii_case("plaintext-only"sv)) {
MUST(set_attribute(HTML::AttributeNames::contenteditable, "plaintext-only"_string));
return {};
}
if (content_editable.equals_ignoring_ascii_case("false"sv)) {
MUST(set_attribute(HTML::AttributeNames::contenteditable, "false"_string));
return {};
}
return WebIDL::SyntaxError::create(realm(), "Invalid contentEditable value, must be 'true', 'false', or 'inherit'"_string);
return WebIDL::SyntaxError::create(realm(), "Invalid contentEditable value, must be 'true', 'false', 'plaintext-only' or 'inherit'"_string);
}
// https://html.spec.whatwg.org/multipage/dom.html#set-the-inner-text-steps
@ -602,18 +610,20 @@ void HTMLElement::attribute_changed(FlyString const& name, Optional<String> cons
if (name == HTML::AttributeNames::contenteditable) {
if (!value.has_value()) {
// No value maps to the "inherit" state.
m_content_editable_state = ContentEditableState::Inherit;
} else if (value->is_empty() || value->equals_ignoring_ascii_case("true"sv)) {
// "true", an empty string or a missing value map to the "true" state.
m_content_editable_state = ContentEditableState::True;
} else if (value->equals_ignoring_ascii_case("false"sv)) {
// "false" maps to the "false" state.
m_content_editable_state = ContentEditableState::False;
} else if (value->equals_ignoring_ascii_case("plaintext-only"sv)) {
// "plaintext-only" maps to the "plaintext-only" state.
m_content_editable_state = ContentEditableState::PlaintextOnly;
} else {
if (value->is_empty() || value->equals_ignoring_ascii_case("true"sv)) {
// "true", an empty string or a missing value map to the "true" state.
m_content_editable_state = ContentEditableState::True;
} else if (value->equals_ignoring_ascii_case("false"sv)) {
// "false" maps to the "false" state.
m_content_editable_state = ContentEditableState::False;
} else {
// Having no such attribute or an invalid value maps to the "inherit" state.
m_content_editable_state = ContentEditableState::Inherit;
}
// Having an invalid value maps to the "inherit" state.
m_content_editable_state = ContentEditableState::Inherit;
}
}

View file

@ -105,9 +105,11 @@ private:
// https://html.spec.whatwg.org/multipage/custom-elements.html#attached-internals
GC::Ptr<ElementInternals> m_attached_internals;
// https://html.spec.whatwg.org/#attr-contenteditable
enum class ContentEditableState {
True,
False,
PlaintextOnly,
Inherit,
};
ContentEditableState m_content_editable_state { ContentEditableState::Inherit };

View file

@ -0,0 +1,34 @@
Summary
Harness status: OK
Rerun
Found 24 tests
24 Pass
Details
Result Test Name MessagePass IDL attribute getter for attribute value "true"
Pass IDL attribute setter for value "true"
Pass IDL attribute getter for attribute value "TRUE"
Pass IDL attribute setter for value "TRUE"
Pass IDL attribute getter for attribute value "false"
Pass IDL attribute setter for value "false"
Pass IDL attribute getter for attribute value "FALSE"
Pass IDL attribute setter for value "FALSE"
Pass IDL attribute getter for attribute value "inherit"
Pass IDL attribute setter for value "inherit"
Pass IDL attribute getter for attribute value "INHERIT"
Pass IDL attribute setter for value "INHERIT"
Pass IDL attribute getter for attribute value "plaintext-only"
Pass IDL attribute setter for value "plaintext-only"
Pass IDL attribute getter for attribute value "PLAINTEXT-ONLY"
Pass IDL attribute setter for value "PLAINTEXT-ONLY"
Pass IDL attribute getter for attribute value "foobar"
Pass IDL attribute setter for value "foobar"
Pass IDL attribute getter for attribute value "falſe"
Pass IDL attribute setter for value "falſe"
Pass IDL attribute getter for attribute value "plaıntext-only"
Pass IDL attribute setter for value "plaıntext-only"
Pass IDL attribute getter for attribute value "plaİntext-only"
Pass IDL attribute setter for value "plaİntext-only"

View file

@ -0,0 +1,45 @@
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="help" href="https://html.spec.whatwg.org/#attr-contenteditable">
<link rel="help" href="https://html.spec.whatwg.org/#enumerated-attribute">
<meta name="assert" content="@contenteditable values are ASCII case-insensitive">
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script>
function testValue(value, isValid) {
const valueLower = value.toLowerCase();
test(() => {
const el = document.createElement('div');
if (valueLower !== "inherit") {
el.setAttribute('contenteditable', value);
}
assert_equals(el.contentEditable, isValid ? valueLower : "inherit");
}, `IDL attribute getter for attribute value "${value}"`);
test(() => {
const el = document.createElement('div');
if (isValid) {
el.contentEditable = value;
assert_equals(el.getAttribute('contenteditable'), valueLower === "inherit" ? null : valueLower);
} else {
assert_throws_dom("SyntaxError", () => {
el.contentEditable = value;
});
}
}, `IDL attribute setter for value "${value}"`);
}
const valid = ["true", "false", "inherit", "plaintext-only"]; // "inherit" is treated specially
const invalid = ["foobar", "falſe", "plaıntext-only", "plaİntext-only"];
for (const value of valid) {
testValue(value, true);
testValue(value.toUpperCase(), true);
}
for (const value of invalid) {
testValue(value, false);
}
</script>