mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-29 20:29:18 +00:00
LibWeb: Implement "plaintext-only" state for contenteditable
This commit is contained in:
parent
7e406ac668
commit
217567981a
Notes:
github-actions[bot]
2024-12-02 23:21:14 +00:00
Author: https://github.com/gmta
Commit: 217567981a
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2697
Reviewed-by: https://github.com/awesomekling
Reviewed-by: https://github.com/tcl3
5 changed files with 104 additions and 14 deletions
|
@ -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
|
// 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
|
// state or plaintext-only state, or a child HTML element of a Document whose design mode
|
||||||
// enabled is true.
|
// enabled is true.
|
||||||
// FIXME: check contenteditable "plaintext-only"
|
|
||||||
if (!is<HTML::HTMLElement>(*node))
|
if (!is<HTML::HTMLElement>(*node))
|
||||||
return false;
|
return false;
|
||||||
auto const& html_element = static_cast<HTML::HTMLElement&>(*node);
|
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
|
// https://w3c.github.io/editing/docs/execCommand/#element-with-inline-contents
|
||||||
|
|
|
@ -88,6 +88,7 @@ bool HTMLElement::is_editable() const
|
||||||
{
|
{
|
||||||
switch (m_content_editable_state) {
|
switch (m_content_editable_state) {
|
||||||
case ContentEditableState::True:
|
case ContentEditableState::True:
|
||||||
|
case ContentEditableState::PlaintextOnly:
|
||||||
return true;
|
return true;
|
||||||
case ContentEditableState::False:
|
case ContentEditableState::False:
|
||||||
return false;
|
return false;
|
||||||
|
@ -100,7 +101,8 @@ bool HTMLElement::is_editable() const
|
||||||
|
|
||||||
bool HTMLElement::is_focusable() 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
|
// https://html.spec.whatwg.org/multipage/interaction.html#dom-iscontenteditable
|
||||||
|
@ -118,6 +120,8 @@ StringView HTMLElement::content_editable() const
|
||||||
return "true"sv;
|
return "true"sv;
|
||||||
case ContentEditableState::False:
|
case ContentEditableState::False:
|
||||||
return "false"sv;
|
return "false"sv;
|
||||||
|
case ContentEditableState::PlaintextOnly:
|
||||||
|
return "plaintext-only"sv;
|
||||||
case ContentEditableState::Inherit:
|
case ContentEditableState::Inherit:
|
||||||
return "inherit"sv;
|
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));
|
MUST(set_attribute(HTML::AttributeNames::contenteditable, "true"_string));
|
||||||
return {};
|
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)) {
|
if (content_editable.equals_ignoring_ascii_case("false"sv)) {
|
||||||
MUST(set_attribute(HTML::AttributeNames::contenteditable, "false"_string));
|
MUST(set_attribute(HTML::AttributeNames::contenteditable, "false"_string));
|
||||||
return {};
|
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
|
// 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 (name == HTML::AttributeNames::contenteditable) {
|
||||||
if (!value.has_value()) {
|
if (!value.has_value()) {
|
||||||
|
// No value maps to the "inherit" state.
|
||||||
m_content_editable_state = ContentEditableState::Inherit;
|
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 {
|
} else {
|
||||||
if (value->is_empty() || value->equals_ignoring_ascii_case("true"sv)) {
|
// Having an invalid value maps to the "inherit" state.
|
||||||
// "true", an empty string or a missing value map to the "true" state.
|
m_content_editable_state = ContentEditableState::Inherit;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,9 +105,11 @@ private:
|
||||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#attached-internals
|
// https://html.spec.whatwg.org/multipage/custom-elements.html#attached-internals
|
||||||
GC::Ptr<ElementInternals> m_attached_internals;
|
GC::Ptr<ElementInternals> m_attached_internals;
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/#attr-contenteditable
|
||||||
enum class ContentEditableState {
|
enum class ContentEditableState {
|
||||||
True,
|
True,
|
||||||
False,
|
False,
|
||||||
|
PlaintextOnly,
|
||||||
Inherit,
|
Inherit,
|
||||||
};
|
};
|
||||||
ContentEditableState m_content_editable_state { ContentEditableState::Inherit };
|
ContentEditableState m_content_editable_state { ContentEditableState::Inherit };
|
||||||
|
|
|
@ -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"
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue