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