mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 17:27:35 +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:
parent
8b16c61ff8
commit
07e13e9868
9 changed files with 107 additions and 70 deletions
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html contenteditable="true">
|
||||||
<head>
|
<head>
|
||||||
<title>Welcome!</title>
|
<title>Welcome!</title>
|
||||||
<!-- this is a comment -->
|
<!-- this is a comment -->
|
||||||
|
|
|
@ -34,51 +34,52 @@ namespace AttributeNames {
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
||||||
#define ENUMERATE_HTML_ATTRIBUTES \
|
#define ENUMERATE_HTML_ATTRIBUTES \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(abbr) \
|
__ENUMERATE_HTML_ATTRIBUTE(abbr) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(accept) \
|
__ENUMERATE_HTML_ATTRIBUTE(accept) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(action) \
|
__ENUMERATE_HTML_ATTRIBUTE(action) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(align) \
|
__ENUMERATE_HTML_ATTRIBUTE(align) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(allow) \
|
__ENUMERATE_HTML_ATTRIBUTE(allow) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(alt) \
|
__ENUMERATE_HTML_ATTRIBUTE(alt) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(async) \
|
__ENUMERATE_HTML_ATTRIBUTE(async) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(bgcolor) \
|
__ENUMERATE_HTML_ATTRIBUTE(bgcolor) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(class_) \
|
__ENUMERATE_HTML_ATTRIBUTE(class_) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(colspan) \
|
__ENUMERATE_HTML_ATTRIBUTE(colspan) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(data) \
|
__ENUMERATE_HTML_ATTRIBUTE(contenteditable) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(download) \
|
__ENUMERATE_HTML_ATTRIBUTE(data) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(defer) \
|
__ENUMERATE_HTML_ATTRIBUTE(download) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(dirname) \
|
__ENUMERATE_HTML_ATTRIBUTE(defer) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(headers) \
|
__ENUMERATE_HTML_ATTRIBUTE(dirname) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(height) \
|
__ENUMERATE_HTML_ATTRIBUTE(headers) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(href) \
|
__ENUMERATE_HTML_ATTRIBUTE(height) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(hreflang) \
|
__ENUMERATE_HTML_ATTRIBUTE(href) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(id) \
|
__ENUMERATE_HTML_ATTRIBUTE(hreflang) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(imagesizes) \
|
__ENUMERATE_HTML_ATTRIBUTE(id) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(imagesrcset) \
|
__ENUMERATE_HTML_ATTRIBUTE(imagesizes) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(integrity) \
|
__ENUMERATE_HTML_ATTRIBUTE(imagesrcset) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(lang) \
|
__ENUMERATE_HTML_ATTRIBUTE(integrity) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(max) \
|
__ENUMERATE_HTML_ATTRIBUTE(lang) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(media) \
|
__ENUMERATE_HTML_ATTRIBUTE(max) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(method) \
|
__ENUMERATE_HTML_ATTRIBUTE(media) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(min) \
|
__ENUMERATE_HTML_ATTRIBUTE(method) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(name) \
|
__ENUMERATE_HTML_ATTRIBUTE(min) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(pattern) \
|
__ENUMERATE_HTML_ATTRIBUTE(name) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(ping) \
|
__ENUMERATE_HTML_ATTRIBUTE(pattern) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(placeholder) \
|
__ENUMERATE_HTML_ATTRIBUTE(ping) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(rel) \
|
__ENUMERATE_HTML_ATTRIBUTE(placeholder) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(size) \
|
__ENUMERATE_HTML_ATTRIBUTE(rel) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(sizes) \
|
__ENUMERATE_HTML_ATTRIBUTE(size) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(src) \
|
__ENUMERATE_HTML_ATTRIBUTE(sizes) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(srcdoc) \
|
__ENUMERATE_HTML_ATTRIBUTE(src) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(srcset) \
|
__ENUMERATE_HTML_ATTRIBUTE(srcdoc) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(step) \
|
__ENUMERATE_HTML_ATTRIBUTE(srcset) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(style) \
|
__ENUMERATE_HTML_ATTRIBUTE(step) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(target) \
|
__ENUMERATE_HTML_ATTRIBUTE(style) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(title) \
|
__ENUMERATE_HTML_ATTRIBUTE(target) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(type) \
|
__ENUMERATE_HTML_ATTRIBUTE(title) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(usemap) \
|
__ENUMERATE_HTML_ATTRIBUTE(type) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(value) \
|
__ENUMERATE_HTML_ATTRIBUTE(usemap) \
|
||||||
|
__ENUMERATE_HTML_ATTRIBUTE(value) \
|
||||||
__ENUMERATE_HTML_ATTRIBUTE(width)
|
__ENUMERATE_HTML_ATTRIBUTE(width)
|
||||||
|
|
||||||
#define __ENUMERATE_HTML_ATTRIBUTE(name) extern FlyString name;
|
#define __ENUMERATE_HTML_ATTRIBUTE(name) extern FlyString name;
|
||||||
|
|
|
@ -157,6 +157,9 @@ public:
|
||||||
const DocumentType* doctype() const;
|
const DocumentType* doctype() const;
|
||||||
const String& compat_mode() const;
|
const String& compat_mode() const;
|
||||||
|
|
||||||
|
void set_editable(bool editable) { m_editable = editable; }
|
||||||
|
virtual bool is_editable() const final;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual RefPtr<LayoutNode> create_layout_node(const CSS::StyleProperties* parent_style) override;
|
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;
|
NonnullRefPtrVector<HTML::HTMLScriptElement> m_scripts_to_execute_as_soon_as_possible;
|
||||||
|
|
||||||
QuirksMode m_quirks_mode { QuirksMode::No };
|
QuirksMode m_quirks_mode { QuirksMode::No };
|
||||||
|
bool m_editable { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -290,4 +290,22 @@ String Element::inner_html() const
|
||||||
return builder.to_string();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,8 @@ public:
|
||||||
String inner_html() const;
|
String inner_html() const;
|
||||||
void set_inner_html(StringView);
|
void set_inner_html(StringView);
|
||||||
|
|
||||||
|
virtual bool is_editable() const final;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
RefPtr<LayoutNode> create_layout_node(const CSS::StyleProperties* parent_style) override;
|
RefPtr<LayoutNode> create_layout_node(const CSS::StyleProperties* parent_style) override;
|
||||||
|
|
||||||
|
|
|
@ -217,4 +217,9 @@ void Node::set_document(Badge<Document>, Document& document)
|
||||||
m_document = &document;
|
m_document = &document;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Node::is_editable() const
|
||||||
|
{
|
||||||
|
return parent() && parent()->is_editable();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,8 @@ public:
|
||||||
bool is_parent_node() const { return is_element() || is_document() || is_document_fragment(); }
|
bool is_parent_node() const { return is_element() || is_document() || is_document_fragment(); }
|
||||||
virtual bool is_svg_element() const { return false; }
|
virtual bool is_svg_element() const { return false; }
|
||||||
|
|
||||||
|
virtual bool is_editable() const;
|
||||||
|
|
||||||
RefPtr<Node> append_child(NonnullRefPtr<Node>, bool notify = true);
|
RefPtr<Node> append_child(NonnullRefPtr<Node>, bool notify = true);
|
||||||
RefPtr<Node> insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool notify = true);
|
RefPtr<Node> insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool notify = true);
|
||||||
|
|
||||||
|
|
|
@ -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())))
|
if (!(frame().cursor_position().offset() >= (unsigned)fragment.start() && frame().cursor_position().offset() < (unsigned)(fragment.start() + fragment.length())))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!fragment.layout_node().node() || !fragment.layout_node().node()->is_editable())
|
||||||
|
return;
|
||||||
|
|
||||||
auto fragment_rect = fragment.absolute_rect();
|
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()));
|
float cursor_x = fragment_rect.x() + specified_style().font().width(fragment.text().substring_view(0, frame().cursor_position().offset() - fragment.start()));
|
||||||
|
|
|
@ -231,31 +231,33 @@ void EventHandler::dump_selection(const char* event_name) const
|
||||||
|
|
||||||
bool EventHandler::handle_keydown(KeyCode key, unsigned, u32 code_point)
|
bool EventHandler::handle_keydown(KeyCode key, unsigned, u32 code_point)
|
||||||
{
|
{
|
||||||
// FIXME: Support backspacing across DOM node boundaries.
|
if (m_frame.cursor_position().node() && m_frame.cursor_position().node()->is_editable()) {
|
||||||
if (key == KeyCode::Key_Backspace && m_frame.cursor_position().offset() > 0) {
|
// FIXME: Support backspacing across DOM node boundaries.
|
||||||
auto& text_node = downcast<DOM::Text>(*m_frame.cursor_position().node());
|
if (key == KeyCode::Key_Backspace && m_frame.cursor_position().offset() > 0) {
|
||||||
StringBuilder builder;
|
auto& text_node = downcast<DOM::Text>(*m_frame.cursor_position().node());
|
||||||
builder.append(text_node.data().substring_view(0, m_frame.cursor_position().offset() - 1));
|
StringBuilder builder;
|
||||||
builder.append(text_node.data().substring_view(m_frame.cursor_position().offset(), text_node.data().length() - m_frame.cursor_position().offset()));
|
builder.append(text_node.data().substring_view(0, m_frame.cursor_position().offset() - 1));
|
||||||
text_node.set_data(builder.to_string());
|
builder.append(text_node.data().substring_view(m_frame.cursor_position().offset(), text_node.data().length() - m_frame.cursor_position().offset()));
|
||||||
m_frame.set_cursor_position({ *m_frame.cursor_position().node(), m_frame.cursor_position().offset() - 1 });
|
text_node.set_data(builder.to_string());
|
||||||
// FIXME: This should definitely use incremental layout invalidation instead!
|
m_frame.set_cursor_position({ *m_frame.cursor_position().node(), m_frame.cursor_position().offset() - 1 });
|
||||||
text_node.document().force_layout();
|
// FIXME: This should definitely use incremental layout invalidation instead!
|
||||||
return true;
|
text_node.document().force_layout();
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (code_point && m_frame.cursor_position().is_valid() && is<DOM::Text>(*m_frame.cursor_position().node())) {
|
if (code_point && m_frame.cursor_position().is_valid() && is<DOM::Text>(*m_frame.cursor_position().node())) {
|
||||||
auto& text_node = downcast<DOM::Text>(*m_frame.cursor_position().node());
|
auto& text_node = downcast<DOM::Text>(*m_frame.cursor_position().node());
|
||||||
StringBuilder builder;
|
StringBuilder builder;
|
||||||
builder.append(text_node.data().substring_view(0, m_frame.cursor_position().offset()));
|
builder.append(text_node.data().substring_view(0, m_frame.cursor_position().offset()));
|
||||||
builder.append_codepoint(code_point);
|
builder.append_codepoint(code_point);
|
||||||
builder.append(text_node.data().substring_view(m_frame.cursor_position().offset(), text_node.data().length() - m_frame.cursor_position().offset()));
|
builder.append(text_node.data().substring_view(m_frame.cursor_position().offset(), text_node.data().length() - m_frame.cursor_position().offset()));
|
||||||
text_node.set_data(builder.to_string());
|
text_node.set_data(builder.to_string());
|
||||||
// FIXME: This will advance the cursor incorrectly when inserting multiple whitespaces (DOM vs layout whitespace collapse difference.)
|
// FIXME: This will advance the cursor incorrectly when inserting multiple whitespaces (DOM vs layout whitespace collapse difference.)
|
||||||
m_frame.set_cursor_position({ *m_frame.cursor_position().node(), m_frame.cursor_position().offset() + 1 });
|
m_frame.set_cursor_position({ *m_frame.cursor_position().node(), m_frame.cursor_position().offset() + 1 });
|
||||||
// FIXME: This should definitely use incremental layout invalidation instead!
|
// FIXME: This should definitely use incremental layout invalidation instead!
|
||||||
text_node.document().force_layout();
|
text_node.document().force_layout();
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue