LibWeb: Move contentEditable from Element to HTMLElement

HTMLElement is the only interface that includes ElementContentEditable
in the HTML specification. This makes sense, as Element is also a base
class for elements in other specifications such as SVG,
which definitely shouldn't be editable.

Also adds a test for the attribute based on what Andreas did in the
video that added it.
This commit is contained in:
Luke 2020-08-03 02:57:28 +01:00 committed by Andreas Kling
parent 64ba289cfb
commit bc15144972
Notes: sideshowbarker 2024-07-19 04:22:05 +09:00
7 changed files with 80 additions and 71 deletions

View file

@ -295,62 +295,4 @@ String Element::inner_html() const
return builder.to_string();
}
Element::ContentEditableState Element::content_editable_state() const
{
auto contenteditable = attribute(HTML::AttributeNames::contenteditable);
// "true" and the empty string map to the "true" state.
if ((!contenteditable.is_null() && contenteditable.is_empty()) || contenteditable.equals_ignoring_case("true"))
return ContentEditableState::True;
// "false" maps to the "false" state.
if (contenteditable.equals_ignoring_case("false"))
return ContentEditableState::False;
// "inherit", an invalid value, and a missing value all map to the "inherit" state.
return ContentEditableState::Inherit;
}
bool Element::is_editable() const
{
switch (content_editable_state()) {
case ContentEditableState::True:
return true;
case ContentEditableState::False:
return false;
case ContentEditableState::Inherit:
return parent() && parent()->is_editable();
default:
ASSERT_NOT_REACHED();
}
}
String Element::content_editable() const
{
switch (content_editable_state()) {
case ContentEditableState::True:
return "true";
case ContentEditableState::False:
return "false";
case ContentEditableState::Inherit:
return "inherit";
default:
ASSERT_NOT_REACHED();
}
}
void Element::set_content_editable(const String& content_editable)
{
if (content_editable.equals_ignoring_case("inherit")) {
remove_attribute(HTML::AttributeNames::contenteditable);
return;
}
if (content_editable.equals_ignoring_case("true")) {
set_attribute(HTML::AttributeNames::contenteditable, "true");
return;
}
if (content_editable.equals_ignoring_case("false")) {
set_attribute(HTML::AttributeNames::contenteditable, "false");
return;
}
// FIXME: otherwise the attribute setter must throw a "SyntaxError" DOMException.
}
}

View file

@ -83,21 +83,10 @@ public:
String inner_html() const;
void set_inner_html(StringView);
virtual bool is_editable() const final;
String content_editable() const;
void set_content_editable(const String&);
protected:
RefPtr<LayoutNode> create_layout_node(const CSS::StyleProperties* parent_style) override;
private:
enum class ContentEditableState {
True,
False,
Inherit,
};
ContentEditableState content_editable_state() const;
Attribute* find_attribute(const FlyString& name);
const Attribute* find_attribute(const FlyString& name) const;

View file

@ -8,7 +8,5 @@ interface Element : Node {
attribute DOMString innerHTML;
[Reflect] attribute DOMString id;
[Reflect=class] attribute DOMString className;
attribute DOMString contentEditable;
}

View file

@ -37,4 +37,62 @@ HTMLElement::~HTMLElement()
{
}
HTMLElement::ContentEditableState HTMLElement::content_editable_state() const
{
auto contenteditable = attribute(HTML::AttributeNames::contenteditable);
// "true" and the empty string map to the "true" state.
if ((!contenteditable.is_null() && contenteditable.is_empty()) || contenteditable.equals_ignoring_case("true"))
return ContentEditableState::True;
// "false" maps to the "false" state.
if (contenteditable.equals_ignoring_case("false"))
return ContentEditableState::False;
// "inherit", an invalid value, and a missing value all map to the "inherit" state.
return ContentEditableState::Inherit;
}
bool HTMLElement::is_editable() const
{
switch (content_editable_state()) {
case ContentEditableState::True:
return true;
case ContentEditableState::False:
return false;
case ContentEditableState::Inherit:
return parent() && parent()->is_editable();
default:
ASSERT_NOT_REACHED();
}
}
String HTMLElement::content_editable() const
{
switch (content_editable_state()) {
case ContentEditableState::True:
return "true";
case ContentEditableState::False:
return "false";
case ContentEditableState::Inherit:
return "inherit";
default:
ASSERT_NOT_REACHED();
}
}
void HTMLElement::set_content_editable(const String& content_editable)
{
if (content_editable.equals_ignoring_case("inherit")) {
remove_attribute(HTML::AttributeNames::contenteditable);
return;
}
if (content_editable.equals_ignoring_case("true")) {
set_attribute(HTML::AttributeNames::contenteditable, "true");
return;
}
if (content_editable.equals_ignoring_case("false")) {
set_attribute(HTML::AttributeNames::contenteditable, "false");
return;
}
// FIXME: otherwise the attribute setter must throw a "SyntaxError" DOMException.
}
}

View file

@ -39,8 +39,19 @@ public:
String title() const { return attribute(HTML::AttributeNames::title); }
virtual bool is_editable() const final;
String content_editable() const;
void set_content_editable(const String&);
private:
virtual bool is_html_element() const final { return true; }
enum class ContentEditableState {
True,
False,
Inherit,
};
ContentEditableState content_editable_state() const;
};
}

View file

@ -3,4 +3,6 @@ interface HTMLElement : Element {
[Reflect] attribute DOMString title;
[Reflect] attribute DOMString lang;
attribute DOMString contentEditable;
}

View file

@ -0,0 +1,9 @@
loadPage("file:///res/html/misc/welcome.html")
afterInitialPageLoad(() => {
test("contentEditable attribute", () => {
expect(document.body.contentEditable).toBe("inherit");
expect(document.firstChild.nextSibling.nodeName).toBe("html");
expect(document.firstChild.nextSibling.contentEditable).toBe("true");
});
});