1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-24 17:17:42 +00:00

LibWeb: Only allow editing of elements with contenteditable="true"

We now respect the contenteditable HTML attribute and only let you
edit content inside explicitly editable elements.
This commit is contained in:
Andreas Kling 2020-08-02 16:05:59 +02:00
parent 8b16c61ff8
commit 07e13e9868
9 changed files with 107 additions and 70 deletions

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html contenteditable="true">
<head>
<title>Welcome!</title>
<!-- this is a comment -->

View file

@ -45,6 +45,7 @@ void initialize();
__ENUMERATE_HTML_ATTRIBUTE(bgcolor) \
__ENUMERATE_HTML_ATTRIBUTE(class_) \
__ENUMERATE_HTML_ATTRIBUTE(colspan) \
__ENUMERATE_HTML_ATTRIBUTE(contenteditable) \
__ENUMERATE_HTML_ATTRIBUTE(data) \
__ENUMERATE_HTML_ATTRIBUTE(download) \
__ENUMERATE_HTML_ATTRIBUTE(defer) \

View file

@ -157,6 +157,9 @@ public:
const DocumentType* doctype() const;
const String& compat_mode() const;
void set_editable(bool editable) { m_editable = editable; }
virtual bool is_editable() const final;
private:
virtual RefPtr<LayoutNode> create_layout_node(const CSS::StyleProperties* parent_style) override;
@ -186,6 +189,7 @@ private:
NonnullRefPtrVector<HTML::HTMLScriptElement> m_scripts_to_execute_as_soon_as_possible;
QuirksMode m_quirks_mode { QuirksMode::No };
bool m_editable { false };
};
}

View file

@ -290,4 +290,22 @@ String Element::inner_html() const
return builder.to_string();
}
bool Element::is_editable() 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 true;
// "false" maps to the "false" state.
if (contenteditable.equals_ignoring_case("false"))
return false;
// "inherit", an invalid value, and a missing value all map to the "inherit" state.
return parent() && parent()->is_editable();
}
bool Document::is_editable() const
{
return m_editable;
}
}

View file

@ -82,6 +82,8 @@ public:
String inner_html() const;
void set_inner_html(StringView);
virtual bool is_editable() const final;
protected:
RefPtr<LayoutNode> create_layout_node(const CSS::StyleProperties* parent_style) override;

View file

@ -217,4 +217,9 @@ void Node::set_document(Badge<Document>, Document& document)
m_document = &document;
}
bool Node::is_editable() const
{
return parent() && parent()->is_editable();
}
}

View file

@ -75,6 +75,8 @@ public:
bool is_parent_node() const { return is_element() || is_document() || is_document_fragment(); }
virtual bool is_svg_element() const { return false; }
virtual bool is_editable() const;
RefPtr<Node> append_child(NonnullRefPtr<Node>, bool notify = true);
RefPtr<Node> insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool notify = true);

View file

@ -117,6 +117,9 @@ void LayoutText::paint_cursor_if_needed(PaintContext& context, const LineBoxFrag
if (!(frame().cursor_position().offset() >= (unsigned)fragment.start() && frame().cursor_position().offset() < (unsigned)(fragment.start() + fragment.length())))
return;
if (!fragment.layout_node().node() || !fragment.layout_node().node()->is_editable())
return;
auto fragment_rect = fragment.absolute_rect();
float cursor_x = fragment_rect.x() + specified_style().font().width(fragment.text().substring_view(0, frame().cursor_position().offset() - fragment.start()));

View file

@ -231,6 +231,7 @@ void EventHandler::dump_selection(const char* event_name) const
bool EventHandler::handle_keydown(KeyCode key, unsigned, u32 code_point)
{
if (m_frame.cursor_position().node() && m_frame.cursor_position().node()->is_editable()) {
// FIXME: Support backspacing across DOM node boundaries.
if (key == KeyCode::Key_Backspace && m_frame.cursor_position().offset() > 0) {
auto& text_node = downcast<DOM::Text>(*m_frame.cursor_position().node());
@ -257,6 +258,7 @@ bool EventHandler::handle_keydown(KeyCode key, unsigned, u32 code_point)
text_node.document().force_layout();
return true;
}
}
return false;
}