Prechádzať zdrojové kódy

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.
Andreas Kling 5 rokov pred
rodič
commit
07e13e9868

+ 1 - 1
Base/res/html/misc/welcome.html

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

+ 46 - 45
Libraries/LibWeb/DOM/AttributeNames.h

@@ -34,51 +34,52 @@ namespace AttributeNames {
 
 void initialize();
 
-#define ENUMERATE_HTML_ATTRIBUTES           \
-    __ENUMERATE_HTML_ATTRIBUTE(abbr)        \
-    __ENUMERATE_HTML_ATTRIBUTE(accept)      \
-    __ENUMERATE_HTML_ATTRIBUTE(action)      \
-    __ENUMERATE_HTML_ATTRIBUTE(align)       \
-    __ENUMERATE_HTML_ATTRIBUTE(allow)       \
-    __ENUMERATE_HTML_ATTRIBUTE(alt)         \
-    __ENUMERATE_HTML_ATTRIBUTE(async)       \
-    __ENUMERATE_HTML_ATTRIBUTE(bgcolor)     \
-    __ENUMERATE_HTML_ATTRIBUTE(class_)      \
-    __ENUMERATE_HTML_ATTRIBUTE(colspan)     \
-    __ENUMERATE_HTML_ATTRIBUTE(data)        \
-    __ENUMERATE_HTML_ATTRIBUTE(download)    \
-    __ENUMERATE_HTML_ATTRIBUTE(defer)       \
-    __ENUMERATE_HTML_ATTRIBUTE(dirname)     \
-    __ENUMERATE_HTML_ATTRIBUTE(headers)     \
-    __ENUMERATE_HTML_ATTRIBUTE(height)      \
-    __ENUMERATE_HTML_ATTRIBUTE(href)        \
-    __ENUMERATE_HTML_ATTRIBUTE(hreflang)    \
-    __ENUMERATE_HTML_ATTRIBUTE(id)          \
-    __ENUMERATE_HTML_ATTRIBUTE(imagesizes)  \
-    __ENUMERATE_HTML_ATTRIBUTE(imagesrcset) \
-    __ENUMERATE_HTML_ATTRIBUTE(integrity)   \
-    __ENUMERATE_HTML_ATTRIBUTE(lang)        \
-    __ENUMERATE_HTML_ATTRIBUTE(max)         \
-    __ENUMERATE_HTML_ATTRIBUTE(media)       \
-    __ENUMERATE_HTML_ATTRIBUTE(method)      \
-    __ENUMERATE_HTML_ATTRIBUTE(min)         \
-    __ENUMERATE_HTML_ATTRIBUTE(name)        \
-    __ENUMERATE_HTML_ATTRIBUTE(pattern)     \
-    __ENUMERATE_HTML_ATTRIBUTE(ping)        \
-    __ENUMERATE_HTML_ATTRIBUTE(placeholder) \
-    __ENUMERATE_HTML_ATTRIBUTE(rel)         \
-    __ENUMERATE_HTML_ATTRIBUTE(size)        \
-    __ENUMERATE_HTML_ATTRIBUTE(sizes)       \
-    __ENUMERATE_HTML_ATTRIBUTE(src)         \
-    __ENUMERATE_HTML_ATTRIBUTE(srcdoc)      \
-    __ENUMERATE_HTML_ATTRIBUTE(srcset)      \
-    __ENUMERATE_HTML_ATTRIBUTE(step)        \
-    __ENUMERATE_HTML_ATTRIBUTE(style)       \
-    __ENUMERATE_HTML_ATTRIBUTE(target)      \
-    __ENUMERATE_HTML_ATTRIBUTE(title)       \
-    __ENUMERATE_HTML_ATTRIBUTE(type)        \
-    __ENUMERATE_HTML_ATTRIBUTE(usemap)      \
-    __ENUMERATE_HTML_ATTRIBUTE(value)       \
+#define ENUMERATE_HTML_ATTRIBUTES               \
+    __ENUMERATE_HTML_ATTRIBUTE(abbr)            \
+    __ENUMERATE_HTML_ATTRIBUTE(accept)          \
+    __ENUMERATE_HTML_ATTRIBUTE(action)          \
+    __ENUMERATE_HTML_ATTRIBUTE(align)           \
+    __ENUMERATE_HTML_ATTRIBUTE(allow)           \
+    __ENUMERATE_HTML_ATTRIBUTE(alt)             \
+    __ENUMERATE_HTML_ATTRIBUTE(async)           \
+    __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)           \
+    __ENUMERATE_HTML_ATTRIBUTE(dirname)         \
+    __ENUMERATE_HTML_ATTRIBUTE(headers)         \
+    __ENUMERATE_HTML_ATTRIBUTE(height)          \
+    __ENUMERATE_HTML_ATTRIBUTE(href)            \
+    __ENUMERATE_HTML_ATTRIBUTE(hreflang)        \
+    __ENUMERATE_HTML_ATTRIBUTE(id)              \
+    __ENUMERATE_HTML_ATTRIBUTE(imagesizes)      \
+    __ENUMERATE_HTML_ATTRIBUTE(imagesrcset)     \
+    __ENUMERATE_HTML_ATTRIBUTE(integrity)       \
+    __ENUMERATE_HTML_ATTRIBUTE(lang)            \
+    __ENUMERATE_HTML_ATTRIBUTE(max)             \
+    __ENUMERATE_HTML_ATTRIBUTE(media)           \
+    __ENUMERATE_HTML_ATTRIBUTE(method)          \
+    __ENUMERATE_HTML_ATTRIBUTE(min)             \
+    __ENUMERATE_HTML_ATTRIBUTE(name)            \
+    __ENUMERATE_HTML_ATTRIBUTE(pattern)         \
+    __ENUMERATE_HTML_ATTRIBUTE(ping)            \
+    __ENUMERATE_HTML_ATTRIBUTE(placeholder)     \
+    __ENUMERATE_HTML_ATTRIBUTE(rel)             \
+    __ENUMERATE_HTML_ATTRIBUTE(size)            \
+    __ENUMERATE_HTML_ATTRIBUTE(sizes)           \
+    __ENUMERATE_HTML_ATTRIBUTE(src)             \
+    __ENUMERATE_HTML_ATTRIBUTE(srcdoc)          \
+    __ENUMERATE_HTML_ATTRIBUTE(srcset)          \
+    __ENUMERATE_HTML_ATTRIBUTE(step)            \
+    __ENUMERATE_HTML_ATTRIBUTE(style)           \
+    __ENUMERATE_HTML_ATTRIBUTE(target)          \
+    __ENUMERATE_HTML_ATTRIBUTE(title)           \
+    __ENUMERATE_HTML_ATTRIBUTE(type)            \
+    __ENUMERATE_HTML_ATTRIBUTE(usemap)          \
+    __ENUMERATE_HTML_ATTRIBUTE(value)           \
     __ENUMERATE_HTML_ATTRIBUTE(width)
 
 #define __ENUMERATE_HTML_ATTRIBUTE(name) extern FlyString name;

+ 4 - 0
Libraries/LibWeb/DOM/Document.h

@@ -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 };
 };
 
 }

+ 18 - 0
Libraries/LibWeb/DOM/Element.cpp

@@ -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;
+}
+
 }

+ 2 - 0
Libraries/LibWeb/DOM/Element.h

@@ -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;
 

+ 5 - 0
Libraries/LibWeb/DOM/Node.cpp

@@ -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();
+}
+
 }

+ 2 - 0
Libraries/LibWeb/DOM/Node.h

@@ -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);
 

+ 3 - 0
Libraries/LibWeb/Layout/LayoutText.cpp

@@ -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()));

+ 26 - 24
Libraries/LibWeb/Page/EventHandler.cpp

@@ -231,31 +231,33 @@ void EventHandler::dump_selection(const char* event_name) const
 
 bool EventHandler::handle_keydown(KeyCode key, unsigned, u32 code_point)
 {
-    // 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());
-        StringBuilder builder;
-        builder.append(text_node.data().substring_view(0, m_frame.cursor_position().offset() - 1));
-        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());
-        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!
-        text_node.document().force_layout();
-        return true;
-    }
+    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());
+            StringBuilder builder;
+            builder.append(text_node.data().substring_view(0, m_frame.cursor_position().offset() - 1));
+            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());
+            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!
+            text_node.document().force_layout();
+            return true;
+        }
 
-    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());
-        StringBuilder builder;
-        builder.append(text_node.data().substring_view(0, m_frame.cursor_position().offset()));
-        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()));
-        text_node.set_data(builder.to_string());
-        // 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 });
-        // FIXME: This should definitely use incremental layout invalidation instead!
-        text_node.document().force_layout();
-        return true;
+        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());
+            StringBuilder builder;
+            builder.append(text_node.data().substring_view(0, m_frame.cursor_position().offset()));
+            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()));
+            text_node.set_data(builder.to_string());
+            // 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 });
+            // FIXME: This should definitely use incremental layout invalidation instead!
+            text_node.document().force_layout();
+            return true;
+        }
     }
     return false;
 }