diff --git a/Tests/LibWeb/Layout/expected/details-closed.txt b/Tests/LibWeb/Layout/expected/details-closed.txt
new file mode 100644
index 0000000000..968cba8fc7
--- /dev/null
+++ b/Tests/LibWeb/Layout/expected/details-closed.txt
@@ -0,0 +1,23 @@
+Viewport <#document> at (0,0) content-size 800x600 children: not-inline
+ BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline
+ BlockContainer
at (8,8) content-size 784x17.46875 children: not-inline
+ BlockContainer <(anonymous)> at (8,8) content-size 784x0 children: inline
+ InlineNode
+ ListItemBox at (37,8) content-size 755x17.46875 children: inline
+ line 0 width: 114.625, height: 17.46875, bottom: 17.46875, baseline: 13.53125
+ frag 0 from TextNode start: 0, length: 13, rect: [37,8 114.625x17.46875]
+ "I'm a summary"
+ TextNode <#text>
+ ListItemMarkerBox <(anonymous)> at (8,8.234375) content-size 17x17 children: not-inline
+ BlockContainer <(anonymous)> at (8,25.46875) content-size 784x0 children: inline
+ TextNode <#text>
+
+ViewportPaintable (Viewport<#document>) [0,0 800x600]
+ PaintableWithLines (BlockContainer) [0,0 800x600]
+ PaintableWithLines (BlockContainer) [8,8 784x17.46875]
+ PaintableWithLines (BlockContainer(anonymous)) [8,8 784x0]
+ InlinePaintable (InlineNode)
+ PaintableWithLines (ListItemBox) [37,8 755x17.46875]
+ TextPaintable (TextNode<#text>)
+ MarkerPaintable (ListItemMarkerBox(anonymous)) [8,8.234375 17x17]
+ PaintableWithLines (BlockContainer(anonymous)) [8,25.46875 784x0]
diff --git a/Tests/LibWeb/Layout/expected/details-open.txt b/Tests/LibWeb/Layout/expected/details-open.txt
new file mode 100644
index 0000000000..c7c44bc83b
--- /dev/null
+++ b/Tests/LibWeb/Layout/expected/details-open.txt
@@ -0,0 +1,35 @@
+Viewport <#document> at (0,0) content-size 800x600 children: not-inline
+ BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline
+ BlockContainer at (8,8) content-size 784x34.9375 children: not-inline
+ BlockContainer <(anonymous)> at (8,8) content-size 784x0 children: inline
+ InlineNode
+ ListItemBox at (37,8) content-size 755x17.46875 children: inline
+ line 0 width: 114.625, height: 17.46875, bottom: 17.46875, baseline: 13.53125
+ frag 0 from TextNode start: 0, length: 13, rect: [37,8 114.625x17.46875]
+ "I'm a summary"
+ TextNode <#text>
+ ListItemMarkerBox <(anonymous)> at (8,8.234375) content-size 17x17 children: not-inline
+ BlockContainer at (8,25.46875) content-size 784x17.46875 children: inline
+ line 0 width: 82.3125, height: 17.46875, bottom: 17.46875, baseline: 13.53125
+ frag 0 from TextNode start: 0, length: 10, rect: [8,25.46875 82.3125x17.46875]
+ "I'm a node"
+ TextNode <#text>
+ TextNode <#text>
+ InlineNode
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (8,42.9375) content-size 784x0 children: inline
+ TextNode <#text>
+
+ViewportPaintable (Viewport<#document>) [0,0 800x600]
+ PaintableWithLines (BlockContainer) [0,0 800x600]
+ PaintableWithLines (BlockContainer) [8,8 784x34.9375]
+ PaintableWithLines (BlockContainer(anonymous)) [8,8 784x0]
+ InlinePaintable (InlineNode)
+ PaintableWithLines (ListItemBox) [37,8 755x17.46875]
+ TextPaintable (TextNode<#text>)
+ MarkerPaintable (ListItemMarkerBox(anonymous)) [8,8.234375 17x17]
+ PaintableWithLines (BlockContainer) [8,25.46875 784x17.46875]
+ InlinePaintable (InlineNode)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [8,42.9375 784x0]
diff --git a/Tests/LibWeb/Layout/input/details-closed.html b/Tests/LibWeb/Layout/input/details-closed.html
new file mode 100644
index 0000000000..a75ab6fc74
--- /dev/null
+++ b/Tests/LibWeb/Layout/input/details-closed.html
@@ -0,0 +1,4 @@
+
+ I'm a summary
+ I'm a node
+
diff --git a/Tests/LibWeb/Layout/input/details-open.html b/Tests/LibWeb/Layout/input/details-open.html
new file mode 100644
index 0000000000..8430ef7de2
--- /dev/null
+++ b/Tests/LibWeb/Layout/input/details-open.html
@@ -0,0 +1,4 @@
+
+ I'm a summary
+ I'm a node
+
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.cpp
index 66dad22e2f..d17f3e2d51 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.cpp
@@ -1,15 +1,21 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
+ * Copyright (c) 2023, Tim Flynn
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include
+#include
#include
+#include
+#include
#include
#include
+#include
#include
#include
+#include
namespace Web::HTML {
@@ -20,10 +26,19 @@ HTMLDetailsElement::HTMLDetailsElement(DOM::Document& document, DOM::QualifiedNa
HTMLDetailsElement::~HTMLDetailsElement() = default;
+void HTMLDetailsElement::visit_edges(Cell::Visitor& visitor)
+{
+ Base::visit_edges(visitor);
+ visitor.visit(m_summary_slot);
+ visitor.visit(m_descendants_slot);
+}
+
void HTMLDetailsElement::initialize(JS::Realm& realm)
{
Base::initialize(realm);
set_prototype(&Bindings::ensure_web_prototype(realm, "HTMLDetailsElement"));
+
+ create_shadow_tree(realm).release_value_but_fixme_should_propagate_errors();
}
void HTMLDetailsElement::attribute_changed(DeprecatedFlyString const& name, DeprecatedString const& value)
@@ -40,9 +55,17 @@ void HTMLDetailsElement::attribute_changed(DeprecatedFlyString const& name, Depr
else {
queue_a_details_toggle_event_task("open"_string, "closed"_string);
}
+
+ update_shadow_tree_style();
}
}
+void HTMLDetailsElement::children_changed()
+{
+ Base::children_changed();
+ update_shadow_tree_slots();
+}
+
// https://html.spec.whatwg.org/multipage/interactive-elements.html#queue-a-details-toggle-event-task
void HTMLDetailsElement::queue_a_details_toggle_event_task(String old_state, String new_state)
{
@@ -81,4 +104,70 @@ void HTMLDetailsElement::queue_a_details_toggle_event_task(String old_state, Str
};
}
+// https://html.spec.whatwg.org/#the-details-and-summary-elements
+WebIDL::ExceptionOr HTMLDetailsElement::create_shadow_tree(JS::Realm& realm)
+{
+ // The element is also expected to have an internal shadow tree with two slots.
+ auto shadow_root = heap().allocate(realm, document(), *this, Bindings::ShadowRootMode::Closed);
+ shadow_root->set_slot_assignment(Bindings::SlotAssignmentMode::Manual);
+
+ // The first slot is expected to take the details element's first summary element child, if any.
+ auto summary_slot = TRY(DOM::create_element(document(), HTML::TagNames::slot, Namespace::HTML));
+ MUST(shadow_root->append_child(summary_slot));
+
+ // The second slot is expected to take the details element's remaining descendants, if any.
+ auto descendants_slot = TRY(DOM::create_element(document(), HTML::TagNames::slot, Namespace::HTML));
+ MUST(shadow_root->append_child(descendants_slot));
+
+ m_summary_slot = static_cast(*summary_slot);
+ m_descendants_slot = static_cast(*descendants_slot);
+ set_shadow_root(shadow_root);
+
+ return {};
+}
+
+void HTMLDetailsElement::update_shadow_tree_slots()
+{
+ Vector summary_assignment;
+ Vector descendants_assignment;
+
+ auto* summary = first_child_of_type();
+ if (summary != nullptr)
+ summary_assignment.append(JS::make_handle(static_cast(*summary)));
+
+ for_each_in_subtree([&](auto& child) {
+ if (&child == summary)
+ return IterationDecision::Continue;
+ if (!child.is_slottable())
+ return IterationDecision::Continue;
+
+ child.as_slottable().visit([&](auto& node) {
+ descendants_assignment.append(JS::make_handle(node));
+ });
+
+ return IterationDecision::Continue;
+ });
+
+ m_summary_slot->assign(move(summary_assignment));
+ m_descendants_slot->assign(move(descendants_assignment));
+
+ update_shadow_tree_style();
+}
+
+// https://html.spec.whatwg.org/#the-details-and-summary-elements:the-details-element-6
+void HTMLDetailsElement::update_shadow_tree_style()
+{
+ if (has_attribute(HTML::AttributeNames::open)) {
+ MUST(m_descendants_slot->set_attribute(HTML::AttributeNames::style, R"~~~(
+ display: block;
+ )~~~"));
+ } else {
+ // FIXME: Should be `display: block` but we do not support `content-visibility: hidden`.
+ MUST(m_descendants_slot->set_attribute(HTML::AttributeNames::style, R"~~~(
+ display: none;
+ content-visibility: hidden;
+ )~~~"));
+ }
+}
+
}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.h b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.h
index aa00b41b12..47da32973f 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.h
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
+ * Copyright (c) 2023, Tim Flynn
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -10,6 +11,7 @@
#include
#include
#include
+#include
namespace Web::HTML {
@@ -26,13 +28,22 @@ private:
HTMLDetailsElement(DOM::Document&, DOM::QualifiedName);
virtual void initialize(JS::Realm&) override;
+ virtual void visit_edges(Cell::Visitor&) override;
+ virtual void children_changed() override;
virtual void attribute_changed(DeprecatedFlyString const& name, DeprecatedString const& value) override;
void queue_a_details_toggle_event_task(String old_state, String new_state);
+ WebIDL::ExceptionOr create_shadow_tree(JS::Realm&);
+ void update_shadow_tree_slots();
+ void update_shadow_tree_style();
+
// https://html.spec.whatwg.org/multipage/interactive-elements.html#details-toggle-task-tracker
Optional m_details_toggle_task_tracker;
+
+ JS::GCPtr m_summary_slot;
+ JS::GCPtr m_descendants_slot;
};
}