diff --git a/Tests/LibWeb/Layout/expected/css-quotes-nesting.txt b/Tests/LibWeb/Layout/expected/css-quotes-nesting.txt new file mode 100644 index 00000000000..5b3740363e0 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/css-quotes-nesting.txt @@ -0,0 +1,313 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x68.40625 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x52.40625 children: not-inline + BlockContainer at (8,8) content-size 784x17.46875 children: inline + line 0 width: 72.140625, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 1, rect: [8,8 9.34375x17.46875] + "a" + frag 1 from TextNode start: 0, length: 1, rect: [17,8 9.46875x17.46875] + "b" + frag 2 from TextNode start: 0, length: 1, rect: [27,8 8.890625x17.46875] + "c" + frag 3 from TextNode start: 0, length: 1, rect: [36,8 7.859375x17.46875] + "d" + frag 4 from TextNode start: 0, length: 1, rect: [44,8 8.71875x17.46875] + "e" + frag 5 from TextNode start: 0, length: 1, rect: [52,8 6.4375x17.46875] + "f" + frag 6 from TextNode start: 0, length: 1, rect: [59,8 7.5625x17.46875] + "g" + frag 7 from TextNode start: 0, length: 1, rect: [66,8 9.296875x17.46875] + "h" + frag 8 from TextNode start: 0, length: 1, rect: [76,8 4.5625x17.46875] + "i" + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + BlockContainer <(anonymous)> at (8,25.46875) content-size 784x0 children: inline + TextNode <#text> + BlockContainer at (8,25.46875) content-size 784x17.46875 children: inline + line 0 width: 130.578125, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 3, rect: [8,25.46875 5.84375x17.46875] + "“" + frag 1 from TextNode start: 0, length: 1, rect: [14,25.46875 9.34375x17.46875] + "a" + frag 2 from TextNode start: 0, length: 3, rect: [23,25.46875 5.84375x17.46875] + "‘" + frag 3 from TextNode start: 0, length: 1, rect: [29,25.46875 9.46875x17.46875] + "b" + frag 4 from TextNode start: 0, length: 3, rect: [39,25.46875 5.84375x17.46875] + "‘" + frag 5 from TextNode start: 0, length: 1, rect: [44,25.46875 8.890625x17.46875] + "c" + frag 6 from TextNode start: 0, length: 3, rect: [53,25.46875 5.84375x17.46875] + "‘" + frag 7 from TextNode start: 0, length: 1, rect: [59,25.46875 7.859375x17.46875] + "d" + frag 8 from TextNode start: 0, length: 3, rect: [67,25.46875 5.84375x17.46875] + "‘" + frag 9 from TextNode start: 0, length: 1, rect: [73,25.46875 8.71875x17.46875] + "e" + frag 10 from TextNode start: 0, length: 3, rect: [82,25.46875 5.84375x17.46875] + "’" + frag 11 from TextNode start: 0, length: 1, rect: [87,25.46875 6.4375x17.46875] + "f" + frag 12 from TextNode start: 0, length: 3, rect: [94,25.46875 5.84375x17.46875] + "’" + frag 13 from TextNode start: 0, length: 1, rect: [100,25.46875 7.5625x17.46875] + "g" + frag 14 from TextNode start: 0, length: 3, rect: [107,25.46875 5.84375x17.46875] + "’" + frag 15 from TextNode start: 0, length: 1, rect: [113,25.46875 9.296875x17.46875] + "h" + frag 16 from TextNode start: 0, length: 3, rect: [122,25.46875 5.84375x17.46875] + "’" + frag 17 from TextNode start: 0, length: 1, rect: [128,25.46875 4.5625x17.46875] + "i" + frag 18 from TextNode start: 0, length: 3, rect: [133,25.46875 5.84375x17.46875] + "”" + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + BlockContainer <(anonymous)> at (8,42.9375) content-size 784x0 children: inline + TextNode <#text> + BlockContainer at (8,42.9375) content-size 784x17.46875 children: inline + line 0 width: 140.234375, height: 17.46875, bottom: 17.46875, baseline: 13.53125 + frag 0 from TextNode start: 0, length: 1, rect: [8,42.9375 5.484375x17.46875] + "(" + frag 1 from TextNode start: 0, length: 1, rect: [13,42.9375 9.34375x17.46875] + "a" + frag 2 from TextNode start: 0, length: 1, rect: [23,42.9375 7.625x17.46875] + "{" + frag 3 from TextNode start: 0, length: 1, rect: [30,42.9375 9.46875x17.46875] + "b" + frag 4 from TextNode start: 0, length: 1, rect: [40,42.9375 6.953125x17.46875] + "[" + frag 5 from TextNode start: 0, length: 1, rect: [47,42.9375 8.890625x17.46875] + "c" + frag 6 from TextNode start: 0, length: 1, rect: [56,42.9375 6.953125x17.46875] + "[" + frag 7 from TextNode start: 0, length: 1, rect: [63,42.9375 7.859375x17.46875] + "d" + frag 8 from TextNode start: 0, length: 1, rect: [71,42.9375 6.953125x17.46875] + "[" + frag 9 from TextNode start: 0, length: 1, rect: [78,42.9375 8.71875x17.46875] + "e" + frag 10 from TextNode start: 0, length: 1, rect: [86,42.9375 7.21875x17.46875] + "]" + frag 11 from TextNode start: 0, length: 1, rect: [93,42.9375 6.4375x17.46875] + "f" + frag 12 from TextNode start: 0, length: 1, rect: [100,42.9375 7.21875x17.46875] + "]" + frag 13 from TextNode start: 0, length: 1, rect: [107,42.9375 7.5625x17.46875] + "g" + frag 14 from TextNode start: 0, length: 1, rect: [115,42.9375 7.21875x17.46875] + "]" + frag 15 from TextNode start: 0, length: 1, rect: [122,42.9375 9.296875x17.46875] + "h" + frag 16 from TextNode start: 0, length: 1, rect: [131,42.9375 7.65625x17.46875] + "}" + frag 17 from TextNode start: 0, length: 1, rect: [139,42.9375 4.5625x17.46875] + "i" + frag 18 from TextNode start: 0, length: 1, rect: [143,42.9375 4.8125x17.46875] + ")" + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + TextNode <#text> + InlineNode <(anonymous)> + TextNode <#text> + BlockContainer <(anonymous)> at (8,60.40625) content-size 784x0 children: inline + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x68.40625] + PaintableWithLines (BlockContainer) [8,8 784x52.40625] + PaintableWithLines (BlockContainer
.a) [8,8 784x17.46875] + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + PaintableWithLines (BlockContainer(anonymous)) [8,25.46875 784x0] + PaintableWithLines (BlockContainer
.b) [8,25.46875 784x17.46875] + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer(anonymous)) [8,42.9375 784x0] + PaintableWithLines (BlockContainer
.c) [8,42.9375 784x17.46875] + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + InlinePaintable (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer(anonymous)) [8,60.40625 784x0] diff --git a/Tests/LibWeb/Layout/input/css-quotes-nesting.html b/Tests/LibWeb/Layout/input/css-quotes-nesting.html new file mode 100644 index 00000000000..0ba39464070 --- /dev/null +++ b/Tests/LibWeb/Layout/input/css-quotes-nesting.html @@ -0,0 +1,21 @@ + + +
abcdefghi
+
abcdefghi
+
abcdefghi
diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp index fbf68157629..a83043ed36d 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -646,11 +646,13 @@ Optional StyleProperties::clear() const return value_id_to_clear(value->to_identifier()); } -CSS::ContentData StyleProperties::content() const +StyleProperties::ContentDataAndQuoteNestingLevel StyleProperties::content(u32 initial_quote_nesting_level) const { auto value = property(CSS::PropertyID::Content); auto quotes_data = quotes(); + auto quote_nesting_level = initial_quote_nesting_level; + auto get_quote_string = [&](bool open, auto depth) { switch (quotes_data.type) { case QuotesData::Type::None: @@ -684,18 +686,24 @@ CSS::ContentData StyleProperties::content() const } else if (item->is_identifier()) { switch (item->to_identifier()) { case ValueID::OpenQuote: - // FIXME: Track nesting level and increment it here. - builder.append(get_quote_string(true, 1)); + builder.append(get_quote_string(true, quote_nesting_level++)); break; case ValueID::CloseQuote: - // FIXME: Track nesting level and decrement it here. - builder.append(get_quote_string(false, 1)); + // A 'close-quote' or 'no-close-quote' that would make the depth negative is in error and is ignored + // (at rendering time): the depth stays at 0 and no quote mark is rendered (although the rest of the + // 'content' property's value is still inserted). + // - https://www.w3.org/TR/CSS21/generate.html#quotes-insert + // (This is missing from the CONTENT-3 spec.) + if (quote_nesting_level > 0) + builder.append(get_quote_string(false, --quote_nesting_level)); break; case ValueID::NoOpenQuote: - // FIXME: Track nesting level and increment it here. + quote_nesting_level++; break; case ValueID::NoCloseQuote: - // FIXME: Track nesting level and decrement it here. + // NOTE: See CloseQuote + if (quote_nesting_level > 0) + quote_nesting_level--; break; default: dbgln("`{}` is not supported in `content` (yet?)", item->to_string()); @@ -721,19 +729,19 @@ CSS::ContentData StyleProperties::content() const content_data.alt_text = MUST(alt_text_builder.to_string()); } - return content_data; + return { content_data, quote_nesting_level }; } switch (value->to_identifier()) { case ValueID::None: - return { ContentData::Type::None }; + return { { ContentData::Type::None }, quote_nesting_level }; case ValueID::Normal: - return { ContentData::Type::Normal }; + return { { ContentData::Type::Normal }, quote_nesting_level }; default: break; } - return CSS::ContentData {}; + return { {}, quote_nesting_level }; } Optional StyleProperties::cursor() const diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.h b/Userland/Libraries/LibWeb/CSS/StyleProperties.h index 682be1b7f92..50cc6a78a1f 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.h +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h @@ -64,7 +64,11 @@ public: CSS::Display display() const; Optional float_() const; Optional clear() const; - CSS::ContentData content() const; + struct ContentDataAndQuoteNestingLevel { + CSS::ContentData content_data; + u32 final_quote_nesting_level { 0 }; + }; + ContentDataAndQuoteNestingLevel content(u32 initial_quote_nesting_level) const; Optional cursor() const; Optional white_space() const; Optional line_style(CSS::PropertyID) const; diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp index 372a44205b3..3ba61180f03 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.cpp +++ b/Userland/Libraries/LibWeb/Layout/Node.cpp @@ -743,7 +743,8 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style) if (auto outline_width = computed_style.property(CSS::PropertyID::OutlineWidth); outline_width->is_length()) computed_values.set_outline_width(outline_width->as_length().length()); - computed_values.set_content(computed_style.content()); + // FIXME: Stop generating the content twice. (First time is in TreeBuilder.) + computed_values.set_content(computed_style.content(initial_quote_nesting_level()).content_data); computed_values.set_grid_auto_columns(computed_style.grid_auto_columns()); computed_values.set_grid_auto_rows(computed_style.grid_auto_rows()); computed_values.set_grid_template_columns(computed_style.grid_template_columns()); diff --git a/Userland/Libraries/LibWeb/Layout/Node.h b/Userland/Libraries/LibWeb/Layout/Node.h index 9771da00d9a..8a99704b01b 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.h +++ b/Userland/Libraries/LibWeb/Layout/Node.h @@ -175,6 +175,9 @@ public: SelectionState selection_state() const { return m_selection_state; } void set_selection_state(SelectionState state) { m_selection_state = state; } + u32 initial_quote_nesting_level() const { return m_initial_quote_nesting_level; } + void set_initial_quote_nesting_level(u32 value) { m_initial_quote_nesting_level = value; } + protected: Node(DOM::Document&, DOM::Node*); @@ -199,6 +202,8 @@ private: bool m_is_grid_item { false }; GeneratedFor m_generated_for { GeneratedFor::NotGenerated }; + + u32 m_initial_quote_nesting_level { 0 }; }; class NodeWithStyle : public Node { diff --git a/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp b/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp index eb56aa13983..286d2e874a8 100644 --- a/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp +++ b/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp @@ -181,7 +181,9 @@ ErrorOr TreeBuilder::create_pseudo_element_if_needed(DOM::Element& element if (!pseudo_element_style) return {}; - auto pseudo_element_content = pseudo_element_style->content(); + auto initial_quote_nesting_level = m_quote_nesting_level; + auto [pseudo_element_content, final_quote_nesting_level] = pseudo_element_style->content(initial_quote_nesting_level); + m_quote_nesting_level = final_quote_nesting_level; auto pseudo_element_display = pseudo_element_style->display(); // ::before and ::after only exist if they have content. `content: normal` computes to `none` for them. // We also don't create them if they are `display: none`. @@ -204,6 +206,7 @@ ErrorOr TreeBuilder::create_pseudo_element_if_needed(DOM::Element& element } pseudo_element_node->set_generated_for(generated_for, element); + pseudo_element_node->set_initial_quote_nesting_level(initial_quote_nesting_level); // FIXME: Handle images, and multiple values if (pseudo_element_content.type == CSS::ContentData::Type::String) { @@ -452,6 +455,7 @@ JS::GCPtr TreeBuilder::build(DOM::Node& dom_node) VERIFY(dom_node.is_document()); Context context; + m_quote_nesting_level = 0; MUST(create_layout_tree(dom_node, context)); // FIXME propagate errors if (auto* root = dom_node.document().layout_node()) diff --git a/Userland/Libraries/LibWeb/Layout/TreeBuilder.h b/Userland/Libraries/LibWeb/Layout/TreeBuilder.h index e1ffd257b50..8c82746e39c 100644 --- a/Userland/Libraries/LibWeb/Layout/TreeBuilder.h +++ b/Userland/Libraries/LibWeb/Layout/TreeBuilder.h @@ -51,6 +51,8 @@ private: JS::GCPtr m_layout_root; Vector> m_ancestor_stack; + + u32 m_quote_nesting_level { 0 }; }; }