LibWeb: Compute display: contents as none for unusual elements

https://drafts.csswg.org/css-display-3/#unbox-html specifies certain
elements that have their `display` style computed as `none` when
specified as `contents`.

This fixes at least one WPT test:
http://wpt.live/css/css-display/display-contents-suppression-dynamic-001.html
This commit is contained in:
Michael Watt 2024-11-08 20:14:37 +08:00 committed by Andreas Kling
parent eef9a53eec
commit 3b1d1d4582
Notes: github-actions[bot] 2024-11-13 10:12:00 +00:00
31 changed files with 156 additions and 0 deletions

View file

@ -5,6 +5,7 @@
*/
#include <LibWeb/Bindings/HTMLAudioElementPrototype.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/HTML/AudioTrack.h>
#include <LibWeb/HTML/AudioTrackList.h>
#include <LibWeb/HTML/HTMLAudioElement.h>
@ -33,6 +34,13 @@ JS::GCPtr<Layout::Node> HTMLAudioElement::create_layout_node(CSS::StylePropertie
return heap().allocate_without_realm<Layout::AudioBox>(document(), *this, move(style));
}
void HTMLAudioElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
Layout::AudioBox* HTMLAudioElement::layout_node()
{
return static_cast<Layout::AudioBox*>(Node::layout_node());

View file

@ -26,6 +26,7 @@ private:
virtual void initialize(JS::Realm&) override;
virtual JS::GCPtr<Layout::Node> create_layout_node(CSS::StyleProperties) override;
virtual void adjust_computed_style(CSS::StyleProperties&) override;
virtual void on_playing() override;
virtual void on_paused() override;

View file

@ -5,6 +5,7 @@
*/
#include <LibWeb/Bindings/HTMLBRElementPrototype.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/HTMLBRElement.h>
#include <LibWeb/Layout/BreakNode.h>
@ -31,4 +32,11 @@ JS::GCPtr<Layout::Node> HTMLBRElement::create_layout_node(CSS::StyleProperties s
return heap().allocate_without_realm<Layout::BreakNode>(document(), *this, move(style));
}
void HTMLBRElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
}

View file

@ -18,6 +18,7 @@ public:
virtual ~HTMLBRElement() override;
virtual JS::GCPtr<Layout::Node> create_layout_node(CSS::StyleProperties) override;
virtual void adjust_computed_style(CSS::StyleProperties&) override;
private:
virtual bool is_html_br_element() const override { return true; }

View file

@ -14,6 +14,7 @@
#include <LibWeb/Bindings/HTMLCanvasElementPrototype.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
#include <LibWeb/DOM/Document.h>
@ -134,6 +135,13 @@ JS::GCPtr<Layout::Node> HTMLCanvasElement::create_layout_node(CSS::StyleProperti
return heap().allocate_without_realm<Layout::CanvasBox>(document(), *this, move(style));
}
void HTMLCanvasElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
HTMLCanvasElement::HasOrCreatedContext HTMLCanvasElement::create_2d_context()
{
if (!m_context.has<Empty>())

View file

@ -49,6 +49,7 @@ private:
virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
virtual JS::GCPtr<Layout::Node> create_layout_node(CSS::StyleProperties) override;
virtual void adjust_computed_style(CSS::StyleProperties&) override;
enum class HasOrCreatedContext {
No,

View file

@ -8,6 +8,7 @@
#include <LibWeb/ARIA/Roles.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/HTMLElementPrototype.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/EditingHostManager.h>
#include <LibWeb/DOM/ElementFactory.h>
@ -907,6 +908,15 @@ WebIDL::ExceptionOr<void> HTMLElement::set_popover(Optional<String> value)
return {};
}
void HTMLElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (local_name() == HTML::TagNames::wbr) {
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
}
void HTMLElement::did_receive_focus()
{
if (m_content_editable_state != ContentEditableState::True)

View file

@ -91,6 +91,8 @@ protected:
private:
virtual bool is_html_element() const final { return true; }
virtual void adjust_computed_style(CSS::StyleProperties&) override;
// ^HTML::GlobalEventHandlers
virtual JS::GCPtr<DOM::EventTarget> global_event_handlers_to_event_target(FlyString const&) override { return *this; }
virtual void did_receive_focus() override;

View file

@ -8,6 +8,7 @@
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/StyleProperties.h>
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/HTML/HTMLEmbedElement.h>
#include <LibWeb/HTML/Parser/HTMLParser.h>
@ -59,4 +60,11 @@ void HTMLEmbedElement::apply_presentational_hints(CSS::StyleProperties& style) c
});
}
void HTMLEmbedElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
}

View file

@ -23,6 +23,7 @@ private:
virtual bool is_html_embed_element() const override { return true; }
virtual void initialize(JS::Realm&) override;
virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
virtual void adjust_computed_style(CSS::StyleProperties&) override;
};
}

View file

@ -6,6 +6,7 @@
#include <LibWeb/Bindings/HTMLFrameElementPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/HTML/BrowsingContext.h>
@ -81,6 +82,13 @@ i32 HTMLFrameElement::default_tab_index_value() const
return 0;
}
void HTMLFrameElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
// https://html.spec.whatwg.org/multipage/obsolete.html#process-the-frame-attributes
void HTMLFrameElement::process_the_frame_attributes(bool initial_insertion)
{

View file

@ -28,6 +28,7 @@ private:
virtual void removed_from(Node*) override;
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value) override;
virtual i32 default_tab_index_value() const override;
virtual void adjust_computed_style(CSS::StyleProperties&) override;
void process_the_frame_attributes(bool initial_insertion = false);
};

View file

@ -5,6 +5,7 @@
*/
#include <LibWeb/Bindings/HTMLFrameSetElementPrototype.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/HTMLFrameSetElement.h>
#include <LibWeb/HTML/Window.h>
@ -20,6 +21,13 @@ HTMLFrameSetElement::HTMLFrameSetElement(DOM::Document& document, DOM::Qualified
HTMLFrameSetElement::~HTMLFrameSetElement() = default;
void HTMLFrameSetElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
void HTMLFrameSetElement::initialize(JS::Realm& realm)
{
Base::initialize(realm);

View file

@ -24,6 +24,8 @@ public:
private:
HTMLFrameSetElement(DOM::Document&, DOM::QualifiedName);
virtual void adjust_computed_style(CSS::StyleProperties&) override;
virtual void initialize(JS::Realm&) override;
virtual void attribute_changed(FlyString const&, Optional<String> const& old_value, Optional<String> const&) override;

View file

@ -7,6 +7,7 @@
#include <LibURL/Origin.h>
#include <LibWeb/Bindings/HTMLIFrameElementPrototype.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/HTML/BrowsingContext.h>
@ -37,6 +38,13 @@ JS::GCPtr<Layout::Node> HTMLIFrameElement::create_layout_node(CSS::StyleProperti
return heap().allocate_without_realm<Layout::FrameBox>(document(), *this, move(style));
}
void HTMLIFrameElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
void HTMLIFrameElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value)
{
HTMLElement::attribute_changed(name, old_value, value);

View file

@ -24,6 +24,7 @@ public:
virtual ~HTMLIFrameElement() override;
virtual JS::GCPtr<Layout::Node> create_layout_node(CSS::StyleProperties) override;
virtual void adjust_computed_style(CSS::StyleProperties&) override;
void set_current_navigation_was_lazy_loaded(bool value);

View file

@ -11,6 +11,7 @@
#include <LibWeb/Bindings/HTMLImageElementPrototype.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/Fetch/Fetching/Fetching.h>
@ -116,6 +117,13 @@ JS::GCPtr<Layout::Node> HTMLImageElement::create_layout_node(CSS::StylePropertie
return heap().allocate_without_realm<Layout::ImageBox>(document(), *this, move(style), *this);
}
void HTMLImageElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
RefPtr<Gfx::ImmutableBitmap> HTMLImageElement::immutable_bitmap() const
{
return current_image_bitmap();

View file

@ -131,6 +131,7 @@ private:
virtual bool supports_dimension_attributes() const override { return true; }
virtual JS::GCPtr<Layout::Node> create_layout_node(CSS::StyleProperties) override;
virtual void adjust_computed_style(CSS::StyleProperties&) override;
virtual void did_set_viewport_rect(CSSPixelRect const&) override;

View file

@ -135,6 +135,10 @@ void HTMLInputElement::adjust_computed_style(CSS::StyleProperties& style)
if (type_state() == TypeAttributeState::Hidden || type_state() == TypeAttributeState::SubmitButton || type_state() == TypeAttributeState::Button || type_state() == TypeAttributeState::ResetButton || type_state() == TypeAttributeState::ImageButton || type_state() == TypeAttributeState::Checkbox || type_state() == TypeAttributeState::RadioButton)
return;
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
// AD-HOC: We rewrite `display: inline` to `display: inline-block`.
// This is required for the internal shadow tree to work correctly in layout.
if (style.display().is_inline_outside() && style.display().is_flow_inside())

View file

@ -6,6 +6,7 @@
*/
#include <LibWeb/Bindings/HTMLMeterElementPrototype.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/ElementFactory.h>
#include <LibWeb/DOM/ShadowRoot.h>
@ -177,6 +178,13 @@ void HTMLMeterElement::removed_from(DOM::Node*)
set_shadow_root(nullptr);
}
void HTMLMeterElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
void HTMLMeterElement::create_shadow_tree_if_needed()
{
if (shadow_root())

View file

@ -37,6 +37,8 @@ public:
virtual void inserted() override;
virtual void removed_from(DOM::Node*) override;
virtual void adjust_computed_style(CSS::StyleProperties&) override;
// https://html.spec.whatwg.org/multipage/forms.html#category-label
virtual bool is_labelable() const override { return true; }

View file

@ -8,6 +8,7 @@
#include <LibWeb/Bindings/HTMLObjectElementPrototype.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/DocumentLoading.h>
@ -155,6 +156,13 @@ JS::GCPtr<Layout::Node> HTMLObjectElement::create_layout_node(CSS::StyleProperti
return nullptr;
}
void HTMLObjectElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
bool HTMLObjectElement::has_ancestor_media_element_or_object_element_not_showing_fallback_content() const
{
for (auto const* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {

View file

@ -59,6 +59,7 @@ private:
virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
virtual JS::GCPtr<Layout::Node> create_layout_node(CSS::StyleProperties) override;
virtual void adjust_computed_style(CSS::StyleProperties&) override;
bool has_ancestor_media_element_or_object_element_not_showing_fallback_content() const;

View file

@ -8,6 +8,7 @@
#include <LibWeb/Bindings/HTMLProgressElementPrototype.h>
#include <LibWeb/CSS/StyleProperties.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/ElementFactory.h>
#include <LibWeb/DOM/ShadowRoot.h>
@ -98,6 +99,13 @@ void HTMLProgressElement::removed_from(DOM::Node*)
set_shadow_root(nullptr);
}
void HTMLProgressElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
void HTMLProgressElement::create_shadow_tree_if_needed()
{
if (shadow_root())

View file

@ -31,6 +31,8 @@ public:
virtual void inserted() override;
virtual void removed_from(DOM::Node*) override;
virtual void adjust_computed_style(CSS::StyleProperties&) override;
// https://html.spec.whatwg.org/multipage/forms.html#category-label
virtual bool is_labelable() const override { return true; }

View file

@ -66,6 +66,10 @@ void HTMLSelectElement::visit_edges(Cell::Visitor& visitor)
void HTMLSelectElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
// AD-HOC: We rewrite `display: inline` to `display: inline-block`.
// This is required for the internal shadow tree to work correctly in layout.
if (style.display().is_inline_outside() && style.display().is_flow_inside())

View file

@ -43,6 +43,10 @@ HTMLTextAreaElement::~HTMLTextAreaElement() = default;
void HTMLTextAreaElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
// AD-HOC: We rewrite `display: inline` to `display: inline-block`.
// This is required for the internal shadow tree to work correctly in layout.
if (style.display().is_inline_outside() && style.display().is_flow_inside())

View file

@ -8,6 +8,7 @@
#include <LibGfx/Bitmap.h>
#include <LibWeb/Bindings/HTMLVideoElementPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/Fetch/Fetching/Fetching.h>
#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
@ -67,6 +68,13 @@ JS::GCPtr<Layout::Node> HTMLVideoElement::create_layout_node(CSS::StylePropertie
return heap().allocate_without_realm<Layout::VideoBox>(document(), *this, move(style));
}
void HTMLVideoElement::adjust_computed_style(CSS::StyleProperties& style)
{
// https://drafts.csswg.org/css-display-3/#unbox
if (style.display().is_contents())
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
Layout::VideoBox* HTMLVideoElement::layout_node()
{
return static_cast<Layout::VideoBox*>(Node::layout_node());

View file

@ -61,6 +61,7 @@ private:
virtual bool supports_dimension_attributes() const override { return true; }
virtual JS::GCPtr<Layout::Node> create_layout_node(CSS::StyleProperties) override;
virtual void adjust_computed_style(CSS::StyleProperties&) override;
virtual void on_playing() override;
virtual void on_paused() override;

View file

@ -0,0 +1,5 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test Reference</title>
<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
<p>Test passes if you see nothing below.</p>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Test: display: contents unboxing works in presence of dynamic changes to the tree.</title>
<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
<link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
<link rel="help" href="https://drafts.csswg.org/css-display-3/#unbox">
<link rel="match" href="../../../../expected/wpt-import/css/css-display/display-contents-suppression-dynamic-001-ref.html">
<p>Test passes if you see nothing below.</p>
<textarea style="display: contents">
FAIL
</textarea>
<script>
let textarea = document.querySelector("textarea");
textarea.offsetTop;
textarea.appendChild(document.createTextNode("FAIL"));
</script>