LibWeb: Create a shadow tree for details elements with manual slots
The spec requires that details elements be assigned a shadow tree with two slots. The first slot is assigned the first summary child element of the details element. The second slot is assigned all other children.
This commit is contained in:
parent
bdf2323b3f
commit
b1632c58bf
Notes:
sideshowbarker
2024-07-17 00:47:29 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/SerenityOS/serenity/commit/b1632c58bf Pull-request: https://github.com/SerenityOS/serenity/pull/20965 Reviewed-by: https://github.com/Lubrsi Reviewed-by: https://github.com/shannonbooth
6 changed files with 166 additions and 0 deletions
23
Tests/LibWeb/Layout/expected/details-closed.txt
Normal file
23
Tests/LibWeb/Layout/expected/details-closed.txt
Normal file
|
@ -0,0 +1,23 @@
|
|||
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
|
||||
BlockContainer <body> at (8,8) content-size 784x17.46875 children: not-inline
|
||||
BlockContainer <(anonymous)> at (8,8) content-size 784x0 children: inline
|
||||
InlineNode <details>
|
||||
ListItemBox <summary> 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<HTML>) [0,0 800x600]
|
||||
PaintableWithLines (BlockContainer<BODY>) [8,8 784x17.46875]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [8,8 784x0]
|
||||
InlinePaintable (InlineNode<DETAILS>)
|
||||
PaintableWithLines (ListItemBox<SUMMARY>) [37,8 755x17.46875]
|
||||
TextPaintable (TextNode<#text>)
|
||||
MarkerPaintable (ListItemMarkerBox(anonymous)) [8,8.234375 17x17]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [8,25.46875 784x0]
|
35
Tests/LibWeb/Layout/expected/details-open.txt
Normal file
35
Tests/LibWeb/Layout/expected/details-open.txt
Normal file
|
@ -0,0 +1,35 @@
|
|||
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
|
||||
BlockContainer <body> at (8,8) content-size 784x34.9375 children: not-inline
|
||||
BlockContainer <(anonymous)> at (8,8) content-size 784x0 children: inline
|
||||
InlineNode <details>
|
||||
ListItemBox <summary> 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 <slot> 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 <span>
|
||||
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<HTML>) [0,0 800x600]
|
||||
PaintableWithLines (BlockContainer<BODY>) [8,8 784x34.9375]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [8,8 784x0]
|
||||
InlinePaintable (InlineNode<DETAILS>)
|
||||
PaintableWithLines (ListItemBox<SUMMARY>) [37,8 755x17.46875]
|
||||
TextPaintable (TextNode<#text>)
|
||||
MarkerPaintable (ListItemMarkerBox(anonymous)) [8,8.234375 17x17]
|
||||
PaintableWithLines (BlockContainer<SLOT>) [8,25.46875 784x17.46875]
|
||||
InlinePaintable (InlineNode<SPAN>)
|
||||
TextPaintable (TextNode<#text>)
|
||||
PaintableWithLines (BlockContainer(anonymous)) [8,42.9375 784x0]
|
4
Tests/LibWeb/Layout/input/details-closed.html
Normal file
4
Tests/LibWeb/Layout/input/details-closed.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<details>
|
||||
<summary>I'm a summary</summary>
|
||||
<span>I'm a node</span>
|
||||
</details>
|
4
Tests/LibWeb/Layout/input/details-open.html
Normal file
4
Tests/LibWeb/Layout/input/details-open.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<details open>
|
||||
<summary>I'm a summary</summary>
|
||||
<span>I'm a node</span>
|
||||
</details>
|
|
@ -1,15 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/DOM/ElementFactory.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/DOM/ShadowRoot.h>
|
||||
#include <LibWeb/DOM/Text.h>
|
||||
#include <LibWeb/HTML/EventLoop/TaskQueue.h>
|
||||
#include <LibWeb/HTML/HTMLDetailsElement.h>
|
||||
#include <LibWeb/HTML/HTMLSlotElement.h>
|
||||
#include <LibWeb/HTML/HTMLSummaryElement.h>
|
||||
#include <LibWeb/HTML/ToggleEvent.h>
|
||||
#include <LibWeb/Namespace.h>
|
||||
|
||||
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<Bindings::HTMLDetailsElementPrototype>(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<void> 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<DOM::ShadowRoot>(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<HTML::HTMLSlotElement&>(*summary_slot);
|
||||
m_descendants_slot = static_cast<HTML::HTMLSlotElement&>(*descendants_slot);
|
||||
set_shadow_root(shadow_root);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void HTMLDetailsElement::update_shadow_tree_slots()
|
||||
{
|
||||
Vector<HTMLSlotElement::SlottableHandle> summary_assignment;
|
||||
Vector<HTMLSlotElement::SlottableHandle> descendants_assignment;
|
||||
|
||||
auto* summary = first_child_of_type<HTMLSummaryElement>();
|
||||
if (summary != nullptr)
|
||||
summary_assignment.append(JS::make_handle(static_cast<DOM::Element&>(*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;
|
||||
)~~~"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -10,6 +11,7 @@
|
|||
#include <LibWeb/ARIA/Roles.h>
|
||||
#include <LibWeb/HTML/HTMLElement.h>
|
||||
#include <LibWeb/HTML/ToggleTaskTracker.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
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<void> 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<ToggleTaskTracker> m_details_toggle_task_tracker;
|
||||
|
||||
JS::GCPtr<HTML::HTMLSlotElement> m_summary_slot;
|
||||
JS::GCPtr<HTML::HTMLSlotElement> m_descendants_slot;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue