mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-05-17 00:23:00 +00:00
When matching selectors in HTML documents, we know that all the elements have lowercase local names already (the parser makes sure of this.) Style sheets still need to remember the original name strings, in case we want to match against non-HTML content like XML/SVG. To make the common HTML case faster, we now cache a lowercase version of the name with each type/class/id SimpleSelector. This makes tag type checks O(1) instead of O(n).
674 lines
28 KiB
C++
674 lines
28 KiB
C++
/*
|
|
* Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
|
|
* Copyright (c) 2021, the SerenityOS developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/QuickSort.h>
|
|
#include <AK/StringBuilder.h>
|
|
#include <AK/Utf8View.h>
|
|
#include <LibWeb/CSS/CSSFontFaceRule.h>
|
|
#include <LibWeb/CSS/CSSImportRule.h>
|
|
#include <LibWeb/CSS/CSSMediaRule.h>
|
|
#include <LibWeb/CSS/CSSRule.h>
|
|
#include <LibWeb/CSS/CSSStyleRule.h>
|
|
#include <LibWeb/CSS/CSSStyleSheet.h>
|
|
#include <LibWeb/CSS/CSSSupportsRule.h>
|
|
#include <LibWeb/CSS/PropertyID.h>
|
|
#include <LibWeb/DOM/Comment.h>
|
|
#include <LibWeb/DOM/Document.h>
|
|
#include <LibWeb/DOM/Element.h>
|
|
#include <LibWeb/DOM/ShadowRoot.h>
|
|
#include <LibWeb/DOM/Text.h>
|
|
#include <LibWeb/Dump.h>
|
|
#include <LibWeb/HTML/HTMLTemplateElement.h>
|
|
#include <LibWeb/Layout/BlockContainer.h>
|
|
#include <LibWeb/Layout/FrameBox.h>
|
|
#include <LibWeb/Layout/Node.h>
|
|
#include <LibWeb/Layout/SVGBox.h>
|
|
#include <LibWeb/Layout/TextNode.h>
|
|
#include <LibWeb/Painting/PaintableBox.h>
|
|
#include <stdio.h>
|
|
|
|
namespace Web {
|
|
|
|
static void indent(StringBuilder& builder, int levels)
|
|
{
|
|
for (int i = 0; i < levels; i++)
|
|
builder.append(" "sv);
|
|
}
|
|
|
|
void dump_tree(DOM::Node const& node)
|
|
{
|
|
StringBuilder builder;
|
|
dump_tree(builder, node);
|
|
dbgln("{}", builder.string_view());
|
|
}
|
|
|
|
void dump_tree(StringBuilder& builder, DOM::Node const& node)
|
|
{
|
|
static int indent = 0;
|
|
for (int i = 0; i < indent; ++i)
|
|
builder.append(" "sv);
|
|
if (is<DOM::Element>(node)) {
|
|
builder.appendff("<{}", verify_cast<DOM::Element>(node).local_name());
|
|
verify_cast<DOM::Element>(node).for_each_attribute([&](auto& name, auto& value) {
|
|
builder.appendff(" {}={}", name, value);
|
|
});
|
|
builder.append(">\n"sv);
|
|
} else if (is<DOM::Text>(node)) {
|
|
builder.appendff("\"{}\"\n", verify_cast<DOM::Text>(node).data());
|
|
} else {
|
|
builder.appendff("{}\n", node.node_name());
|
|
}
|
|
++indent;
|
|
if (is<DOM::Element>(node) && verify_cast<DOM::Element>(node).shadow_root()) {
|
|
dump_tree(builder, *verify_cast<DOM::Element>(node).shadow_root());
|
|
}
|
|
if (is<DOM::ParentNode>(node)) {
|
|
if (!is<HTML::HTMLTemplateElement>(node)) {
|
|
static_cast<DOM::ParentNode const&>(node).for_each_child([&](auto& child) {
|
|
dump_tree(builder, child);
|
|
});
|
|
} else {
|
|
auto& template_element = verify_cast<HTML::HTMLTemplateElement>(node);
|
|
dump_tree(builder, template_element.content());
|
|
}
|
|
}
|
|
--indent;
|
|
}
|
|
|
|
void dump_tree(Layout::Node const& layout_node, bool show_box_model, bool show_specified_style)
|
|
{
|
|
StringBuilder builder;
|
|
dump_tree(builder, layout_node, show_box_model, show_specified_style, true);
|
|
dbgln("{}", builder.string_view());
|
|
}
|
|
|
|
void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool show_box_model, bool show_specified_style, bool interactive)
|
|
{
|
|
static size_t indent = 0;
|
|
for (size_t i = 0; i < indent; ++i)
|
|
builder.append(" "sv);
|
|
|
|
FlyString tag_name;
|
|
if (layout_node.is_anonymous())
|
|
tag_name = "(anonymous)";
|
|
else if (is<DOM::Element>(layout_node.dom_node()))
|
|
tag_name = verify_cast<DOM::Element>(*layout_node.dom_node()).local_name();
|
|
else
|
|
tag_name = layout_node.dom_node()->node_name();
|
|
|
|
String identifier = "";
|
|
if (layout_node.dom_node() && is<DOM::Element>(*layout_node.dom_node())) {
|
|
auto& element = verify_cast<DOM::Element>(*layout_node.dom_node());
|
|
StringBuilder builder;
|
|
auto id = element.attribute(HTML::AttributeNames::id);
|
|
if (!id.is_empty()) {
|
|
builder.append('#');
|
|
builder.append(id);
|
|
}
|
|
for (auto& class_name : element.class_names()) {
|
|
builder.append('.');
|
|
builder.append(class_name);
|
|
}
|
|
identifier = builder.to_string();
|
|
}
|
|
|
|
StringView nonbox_color_on = ""sv;
|
|
StringView box_color_on = ""sv;
|
|
StringView svg_box_color_on = ""sv;
|
|
StringView positioned_color_on = ""sv;
|
|
StringView floating_color_on = ""sv;
|
|
StringView inline_block_color_on = ""sv;
|
|
StringView line_box_color_on = ""sv;
|
|
StringView fragment_color_on = ""sv;
|
|
StringView flex_color_on = ""sv;
|
|
StringView color_off = ""sv;
|
|
|
|
if (interactive) {
|
|
nonbox_color_on = "\033[33m"sv;
|
|
box_color_on = "\033[34m"sv;
|
|
svg_box_color_on = "\033[31m"sv;
|
|
positioned_color_on = "\033[31;1m"sv;
|
|
floating_color_on = "\033[32;1m"sv;
|
|
inline_block_color_on = "\033[36;1m"sv;
|
|
line_box_color_on = "\033[34;1m"sv;
|
|
fragment_color_on = "\033[35;1m"sv;
|
|
flex_color_on = "\033[34;1m"sv;
|
|
color_off = "\033[0m"sv;
|
|
}
|
|
|
|
if (!is<Layout::Box>(layout_node)) {
|
|
builder.appendff("{}{}{} <{}{}{}{}>",
|
|
nonbox_color_on,
|
|
layout_node.class_name().substring_view(13),
|
|
color_off,
|
|
tag_name,
|
|
nonbox_color_on,
|
|
identifier,
|
|
color_off);
|
|
if (interactive)
|
|
builder.appendff(" @{:p}", &layout_node);
|
|
builder.append("\n"sv);
|
|
} else {
|
|
auto& box = verify_cast<Layout::Box>(layout_node);
|
|
StringView color_on = is<Layout::SVGBox>(box) ? svg_box_color_on : box_color_on;
|
|
|
|
builder.appendff("{}{}{} <{}{}{}{}> ",
|
|
color_on,
|
|
box.class_name().substring_view(13),
|
|
color_off,
|
|
color_on,
|
|
tag_name,
|
|
color_off,
|
|
identifier.characters());
|
|
|
|
if (interactive)
|
|
builder.appendff("@{:p} ", &layout_node);
|
|
|
|
if (auto const* paint_box = box.paint_box()) {
|
|
builder.appendff("at ({},{}) content-size {}x{}",
|
|
paint_box->absolute_x(),
|
|
paint_box->absolute_y(),
|
|
paint_box->content_width(),
|
|
paint_box->content_height());
|
|
}
|
|
|
|
if (box.is_positioned())
|
|
builder.appendff(" {}positioned{}", positioned_color_on, color_off);
|
|
if (box.is_floating())
|
|
builder.appendff(" {}floating{}", floating_color_on, color_off);
|
|
if (box.is_inline_block())
|
|
builder.appendff(" {}inline-block{}", inline_block_color_on, color_off);
|
|
if (box.computed_values().display().is_flex_inside()) {
|
|
StringView direction;
|
|
switch (box.computed_values().flex_direction()) {
|
|
case CSS::FlexDirection::Column:
|
|
direction = "column"sv;
|
|
break;
|
|
case CSS::FlexDirection::ColumnReverse:
|
|
direction = "column-reverse"sv;
|
|
break;
|
|
case CSS::FlexDirection::Row:
|
|
direction = "row"sv;
|
|
break;
|
|
case CSS::FlexDirection::RowReverse:
|
|
direction = "row-reverse"sv;
|
|
break;
|
|
}
|
|
builder.appendff(" {}flex-container({}){}", flex_color_on, direction, color_off);
|
|
}
|
|
if (box.is_flex_item())
|
|
builder.appendff(" {}flex-item{}", flex_color_on, color_off);
|
|
|
|
if (show_box_model) {
|
|
// Dump the horizontal box properties
|
|
builder.appendff(" [{}+{}+{} {} {}+{}+{}]",
|
|
box.box_model().margin.left,
|
|
box.box_model().border.left,
|
|
box.box_model().padding.left,
|
|
box.paint_box() ? box.paint_box()->content_width() : 0,
|
|
box.box_model().padding.right,
|
|
box.box_model().border.right,
|
|
box.box_model().margin.right);
|
|
|
|
// And the vertical box properties
|
|
builder.appendff(" [{}+{}+{} {} {}+{}+{}]",
|
|
box.box_model().margin.top,
|
|
box.box_model().border.top,
|
|
box.box_model().padding.top,
|
|
box.paint_box() ? box.paint_box()->content_height() : 0,
|
|
box.box_model().padding.bottom,
|
|
box.box_model().border.bottom,
|
|
box.box_model().margin.bottom);
|
|
}
|
|
|
|
builder.appendff(" children: {}", box.children_are_inline() ? "inline" : "not-inline");
|
|
|
|
if (is<Layout::FrameBox>(box)) {
|
|
auto const& frame_box = static_cast<Layout::FrameBox const&>(box);
|
|
if (auto* nested_browsing_context = frame_box.dom_node().nested_browsing_context()) {
|
|
if (auto* document = nested_browsing_context->active_document()) {
|
|
builder.appendff(" (url: {})", document->url());
|
|
}
|
|
}
|
|
}
|
|
|
|
builder.append("\n"sv);
|
|
}
|
|
|
|
if (is<Layout::BlockContainer>(layout_node) && static_cast<Layout::BlockContainer const&>(layout_node).children_are_inline()) {
|
|
auto& block = static_cast<Layout::BlockContainer const&>(layout_node);
|
|
for (size_t line_box_index = 0; block.paint_box() && line_box_index < block.paint_box()->line_boxes().size(); ++line_box_index) {
|
|
auto& line_box = block.paint_box()->line_boxes()[line_box_index];
|
|
for (size_t i = 0; i < indent; ++i)
|
|
builder.append(" "sv);
|
|
builder.appendff(" {}line {}{} width: {}, height: {}, bottom: {}, baseline: {}\n",
|
|
line_box_color_on,
|
|
line_box_index,
|
|
color_off,
|
|
line_box.width(),
|
|
line_box.height(),
|
|
line_box.bottom(),
|
|
line_box.baseline());
|
|
for (size_t fragment_index = 0; fragment_index < line_box.fragments().size(); ++fragment_index) {
|
|
auto& fragment = line_box.fragments()[fragment_index];
|
|
for (size_t i = 0; i < indent; ++i)
|
|
builder.append(" "sv);
|
|
builder.appendff(" {}frag {}{} from {} ",
|
|
fragment_color_on,
|
|
fragment_index,
|
|
color_off,
|
|
fragment.layout_node().class_name());
|
|
if (interactive)
|
|
builder.appendff("@{:p}, ", &fragment.layout_node());
|
|
builder.appendff("start: {}, length: {}, rect: {}\n",
|
|
fragment.start(),
|
|
fragment.length(),
|
|
fragment.absolute_rect().to_string());
|
|
if (is<Layout::TextNode>(fragment.layout_node())) {
|
|
for (size_t i = 0; i < indent; ++i)
|
|
builder.append(" "sv);
|
|
auto& layout_text = static_cast<Layout::TextNode const&>(fragment.layout_node());
|
|
auto fragment_text = layout_text.text_for_rendering().substring(fragment.start(), fragment.length());
|
|
builder.appendff(" \"{}\"\n", fragment_text);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (show_specified_style && layout_node.dom_node() && layout_node.dom_node()->is_element() && verify_cast<DOM::Element>(layout_node.dom_node())->computed_css_values()) {
|
|
struct NameAndValue {
|
|
String name;
|
|
String value;
|
|
};
|
|
Vector<NameAndValue> properties;
|
|
verify_cast<DOM::Element>(*layout_node.dom_node()).computed_css_values()->for_each_property([&](auto property_id, auto& value) {
|
|
properties.append({ CSS::string_from_property_id(property_id), value.to_string() });
|
|
});
|
|
quick_sort(properties, [](auto& a, auto& b) { return a.name < b.name; });
|
|
|
|
for (auto& property : properties) {
|
|
for (size_t i = 0; i < indent; ++i)
|
|
builder.append(" "sv);
|
|
builder.appendff(" ({}: {})\n", property.name, property.value);
|
|
}
|
|
}
|
|
|
|
++indent;
|
|
layout_node.for_each_child([&](auto& child) {
|
|
dump_tree(builder, child, show_box_model, show_specified_style, interactive);
|
|
});
|
|
--indent;
|
|
}
|
|
|
|
void dump_selector(CSS::Selector const& selector)
|
|
{
|
|
StringBuilder builder;
|
|
dump_selector(builder, selector);
|
|
dbgln("{}", builder.string_view());
|
|
}
|
|
|
|
void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
|
|
{
|
|
builder.append(" CSS::Selector:\n"sv);
|
|
|
|
for (auto& relative_selector : selector.compound_selectors()) {
|
|
builder.append(" "sv);
|
|
|
|
char const* relation_description = "";
|
|
switch (relative_selector.combinator) {
|
|
case CSS::Selector::Combinator::None:
|
|
relation_description = "None";
|
|
break;
|
|
case CSS::Selector::Combinator::ImmediateChild:
|
|
relation_description = "ImmediateChild";
|
|
break;
|
|
case CSS::Selector::Combinator::Descendant:
|
|
relation_description = "Descendant";
|
|
break;
|
|
case CSS::Selector::Combinator::NextSibling:
|
|
relation_description = "AdjacentSibling";
|
|
break;
|
|
case CSS::Selector::Combinator::SubsequentSibling:
|
|
relation_description = "GeneralSibling";
|
|
break;
|
|
case CSS::Selector::Combinator::Column:
|
|
relation_description = "Column";
|
|
break;
|
|
}
|
|
|
|
if (*relation_description)
|
|
builder.appendff("{{{}}} ", relation_description);
|
|
|
|
for (size_t i = 0; i < relative_selector.simple_selectors.size(); ++i) {
|
|
auto& simple_selector = relative_selector.simple_selectors[i];
|
|
char const* type_description = "Unknown";
|
|
switch (simple_selector.type) {
|
|
case CSS::Selector::SimpleSelector::Type::Universal:
|
|
type_description = "Universal";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::Type::Id:
|
|
type_description = "Id";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::Type::Class:
|
|
type_description = "Class";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::Type::TagName:
|
|
type_description = "TagName";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::Type::Attribute:
|
|
type_description = "Attribute";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::Type::PseudoClass:
|
|
type_description = "PseudoClass";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::Type::PseudoElement:
|
|
type_description = "PseudoElement";
|
|
break;
|
|
}
|
|
|
|
builder.appendff("{}:", type_description);
|
|
// FIXME: This is goofy
|
|
if (simple_selector.value.has<CSS::Selector::SimpleSelector::Name>())
|
|
builder.append(simple_selector.name());
|
|
|
|
if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass) {
|
|
auto const& pseudo_class = simple_selector.pseudo_class();
|
|
|
|
char const* pseudo_class_description = "";
|
|
switch (pseudo_class.type) {
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Link:
|
|
pseudo_class_description = "Link";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Visited:
|
|
pseudo_class_description = "Visited";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Active:
|
|
pseudo_class_description = "Active";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Root:
|
|
pseudo_class_description = "Root";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstOfType:
|
|
pseudo_class_description = "FirstOfType";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::LastOfType:
|
|
pseudo_class_description = "LastOfType";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyOfType:
|
|
pseudo_class_description = "OnlyOfType";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::NthOfType:
|
|
pseudo_class_description = "NthOfType";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastOfType:
|
|
pseudo_class_description = "NthLastOfType";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild:
|
|
pseudo_class_description = "NthChild";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild:
|
|
pseudo_class_description = "NthLastChild";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Focus:
|
|
pseudo_class_description = "Focus";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::FocusWithin:
|
|
pseudo_class_description = "FocusWithin";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Empty:
|
|
pseudo_class_description = "Empty";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Hover:
|
|
pseudo_class_description = "Hover";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::LastChild:
|
|
pseudo_class_description = "LastChild";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstChild:
|
|
pseudo_class_description = "FirstChild";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyChild:
|
|
pseudo_class_description = "OnlyChild";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Disabled:
|
|
pseudo_class_description = "Disabled";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Enabled:
|
|
pseudo_class_description = "Enabled";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Checked:
|
|
pseudo_class_description = "Checked";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Not:
|
|
pseudo_class_description = "Not";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Is:
|
|
pseudo_class_description = "Is";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Where:
|
|
pseudo_class_description = "Where";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Lang:
|
|
pseudo_class_description = "Lang";
|
|
break;
|
|
}
|
|
|
|
builder.appendff(" pseudo_class={}", pseudo_class_description);
|
|
if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Lang) {
|
|
builder.append('(');
|
|
builder.join(',', pseudo_class.languages);
|
|
builder.append(')');
|
|
} else if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Not
|
|
|| pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Is
|
|
|| pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Where) {
|
|
builder.append("(["sv);
|
|
for (auto& selector : pseudo_class.argument_selector_list)
|
|
dump_selector(builder, selector);
|
|
builder.append("])"sv);
|
|
} else if ((pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild)
|
|
|| (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild)
|
|
|| (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthOfType)
|
|
|| (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastOfType)) {
|
|
builder.appendff("(step={}, offset={}", pseudo_class.nth_child_pattern.step_size, pseudo_class.nth_child_pattern.offset);
|
|
if (!pseudo_class.argument_selector_list.is_empty()) {
|
|
builder.append(", selectors=["sv);
|
|
for (auto const& child_selector : pseudo_class.argument_selector_list)
|
|
dump_selector(builder, child_selector);
|
|
builder.append("]"sv);
|
|
}
|
|
builder.append(")"sv);
|
|
}
|
|
}
|
|
|
|
if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoElement) {
|
|
char const* pseudo_element_description = "";
|
|
switch (simple_selector.pseudo_element()) {
|
|
case CSS::Selector::PseudoElement::Before:
|
|
pseudo_element_description = "before";
|
|
break;
|
|
case CSS::Selector::PseudoElement::After:
|
|
pseudo_element_description = "after";
|
|
break;
|
|
case CSS::Selector::PseudoElement::FirstLine:
|
|
pseudo_element_description = "first-line";
|
|
break;
|
|
case CSS::Selector::PseudoElement::FirstLetter:
|
|
pseudo_element_description = "first-letter";
|
|
break;
|
|
case CSS::Selector::PseudoElement::Marker:
|
|
pseudo_element_description = "marker";
|
|
break;
|
|
case CSS::Selector::PseudoElement::ProgressBar:
|
|
pseudo_element_description = "-webkit-progress-bar";
|
|
break;
|
|
case CSS::Selector::PseudoElement::ProgressValue:
|
|
pseudo_element_description = "-webkit-progress-value";
|
|
break;
|
|
}
|
|
|
|
builder.appendff(" pseudo_element={}", pseudo_element_description);
|
|
}
|
|
|
|
if (simple_selector.type == CSS::Selector::SimpleSelector::Type::Attribute) {
|
|
auto const& attribute = simple_selector.attribute();
|
|
char const* attribute_match_type_description = "";
|
|
|
|
switch (attribute.match_type) {
|
|
case CSS::Selector::SimpleSelector::Attribute::MatchType::HasAttribute:
|
|
attribute_match_type_description = "HasAttribute";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch:
|
|
attribute_match_type_description = "ExactValueMatch";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsWord:
|
|
attribute_match_type_description = "ContainsWord";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsString:
|
|
attribute_match_type_description = "ContainsString";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment:
|
|
attribute_match_type_description = "StartsWithSegment";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithString:
|
|
attribute_match_type_description = "StartsWithString";
|
|
break;
|
|
case CSS::Selector::SimpleSelector::Attribute::MatchType::EndsWithString:
|
|
attribute_match_type_description = "EndsWithString";
|
|
break;
|
|
}
|
|
|
|
builder.appendff(" [{}, name='{}', value='{}']", attribute_match_type_description, attribute.name, attribute.value);
|
|
}
|
|
|
|
if (i != relative_selector.simple_selectors.size() - 1)
|
|
builder.append(", "sv);
|
|
}
|
|
builder.append("\n"sv);
|
|
}
|
|
}
|
|
|
|
void dump_rule(CSS::CSSRule const& rule)
|
|
{
|
|
StringBuilder builder;
|
|
dump_rule(builder, rule);
|
|
dbgln("{}", builder.string_view());
|
|
}
|
|
|
|
void dump_rule(StringBuilder& builder, CSS::CSSRule const& rule, int indent_levels)
|
|
{
|
|
indent(builder, indent_levels);
|
|
builder.appendff("{}:\n", rule.class_name());
|
|
|
|
switch (rule.type()) {
|
|
case CSS::CSSRule::Type::FontFace:
|
|
dump_font_face_rule(builder, verify_cast<CSS::CSSFontFaceRule const>(rule), indent_levels);
|
|
break;
|
|
case CSS::CSSRule::Type::Import:
|
|
dump_import_rule(builder, verify_cast<CSS::CSSImportRule const>(rule), indent_levels);
|
|
break;
|
|
case CSS::CSSRule::Type::Media:
|
|
dump_media_rule(builder, verify_cast<CSS::CSSMediaRule const>(rule), indent_levels);
|
|
break;
|
|
case CSS::CSSRule::Type::Style:
|
|
dump_style_rule(builder, verify_cast<CSS::CSSStyleRule const>(rule), indent_levels);
|
|
break;
|
|
case CSS::CSSRule::Type::Supports:
|
|
dump_supports_rule(builder, verify_cast<CSS::CSSSupportsRule const>(rule), indent_levels);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void dump_font_face_rule(StringBuilder& builder, CSS::CSSFontFaceRule const& rule, int indent_levels)
|
|
{
|
|
auto& font_face = rule.font_face();
|
|
indent(builder, indent_levels + 1);
|
|
builder.appendff("font-family: {}\n", font_face.font_family());
|
|
|
|
indent(builder, indent_levels + 1);
|
|
builder.append("sources:\n"sv);
|
|
for (auto const& source : font_face.sources()) {
|
|
indent(builder, indent_levels + 2);
|
|
builder.appendff("url={}, format={}\n", source.url, source.format.value_or("???"));
|
|
}
|
|
|
|
indent(builder, indent_levels + 1);
|
|
builder.append("unicode-ranges:\n"sv);
|
|
for (auto const& unicode_range : font_face.unicode_ranges()) {
|
|
indent(builder, indent_levels + 2);
|
|
builder.appendff("{}\n", unicode_range.to_string());
|
|
}
|
|
}
|
|
|
|
void dump_import_rule(StringBuilder& builder, CSS::CSSImportRule const& rule, int indent_levels)
|
|
{
|
|
indent(builder, indent_levels);
|
|
builder.appendff(" Document URL: {}\n", rule.url());
|
|
}
|
|
|
|
void dump_media_rule(StringBuilder& builder, CSS::CSSMediaRule const& media, int indent_levels)
|
|
{
|
|
indent(builder, indent_levels);
|
|
builder.appendff(" Media: {}\n Rules ({}):\n", media.condition_text(), media.css_rules().length());
|
|
|
|
for (auto& rule : media.css_rules()) {
|
|
dump_rule(builder, rule, indent_levels + 1);
|
|
}
|
|
}
|
|
|
|
void dump_supports_rule(StringBuilder& builder, CSS::CSSSupportsRule const& supports, int indent_levels)
|
|
{
|
|
indent(builder, indent_levels);
|
|
builder.appendff(" Supports: {}\n Rules ({}):\n", supports.condition_text(), supports.css_rules().length());
|
|
|
|
for (auto& rule : supports.css_rules()) {
|
|
dump_rule(builder, rule, indent_levels + 1);
|
|
}
|
|
}
|
|
|
|
void dump_style_rule(StringBuilder& builder, CSS::CSSStyleRule const& rule, int indent_levels)
|
|
{
|
|
for (auto& selector : rule.selectors()) {
|
|
dump_selector(builder, selector);
|
|
}
|
|
indent(builder, indent_levels);
|
|
builder.append(" Declarations:\n"sv);
|
|
auto& style_declaration = verify_cast<CSS::PropertyOwningCSSStyleDeclaration>(rule.declaration());
|
|
for (auto& property : style_declaration.properties()) {
|
|
indent(builder, indent_levels);
|
|
builder.appendff(" {}: '{}'", CSS::string_from_property_id(property.property_id), property.value->to_string());
|
|
if (property.important == CSS::Important::Yes)
|
|
builder.append(" \033[31;1m!important\033[0m"sv);
|
|
builder.append('\n');
|
|
}
|
|
for (auto& property : style_declaration.custom_properties()) {
|
|
indent(builder, indent_levels);
|
|
builder.appendff(" {}: '{}'", property.key, property.value.value->to_string());
|
|
if (property.value.important == CSS::Important::Yes)
|
|
builder.append(" \033[31;1m!important\033[0m"sv);
|
|
builder.append('\n');
|
|
}
|
|
}
|
|
|
|
void dump_sheet(CSS::StyleSheet const& sheet)
|
|
{
|
|
StringBuilder builder;
|
|
dump_sheet(builder, sheet);
|
|
dbgln("{}", builder.string_view());
|
|
}
|
|
|
|
void dump_sheet(StringBuilder& builder, CSS::StyleSheet const& sheet)
|
|
{
|
|
auto& css_stylesheet = verify_cast<CSS::CSSStyleSheet>(sheet);
|
|
|
|
builder.appendff("CSSStyleSheet{{{}}}: {} rule(s)\n", &sheet, css_stylesheet.rules().length());
|
|
|
|
for (auto& rule : css_stylesheet.rules()) {
|
|
dump_rule(builder, rule);
|
|
}
|
|
}
|
|
|
|
}
|