Serenity Operating System
1/*
2 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021, the SerenityOS developers.
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <AK/QuickSort.h>
9#include <AK/StringBuilder.h>
10#include <AK/Utf8View.h>
11#include <LibWeb/CSS/CSSFontFaceRule.h>
12#include <LibWeb/CSS/CSSImportRule.h>
13#include <LibWeb/CSS/CSSMediaRule.h>
14#include <LibWeb/CSS/CSSRule.h>
15#include <LibWeb/CSS/CSSStyleRule.h>
16#include <LibWeb/CSS/CSSStyleSheet.h>
17#include <LibWeb/CSS/CSSSupportsRule.h>
18#include <LibWeb/CSS/PropertyID.h>
19#include <LibWeb/DOM/Comment.h>
20#include <LibWeb/DOM/Document.h>
21#include <LibWeb/DOM/Element.h>
22#include <LibWeb/DOM/ShadowRoot.h>
23#include <LibWeb/DOM/Text.h>
24#include <LibWeb/Dump.h>
25#include <LibWeb/HTML/HTMLTemplateElement.h>
26#include <LibWeb/Layout/BlockContainer.h>
27#include <LibWeb/Layout/FrameBox.h>
28#include <LibWeb/Layout/Node.h>
29#include <LibWeb/Layout/SVGBox.h>
30#include <LibWeb/Layout/TextNode.h>
31#include <LibWeb/Painting/PaintableBox.h>
32#include <stdio.h>
33
34namespace Web {
35
36static void indent(StringBuilder& builder, int levels)
37{
38 for (int i = 0; i < levels; i++)
39 builder.append(" "sv);
40}
41
42void dump_tree(DOM::Node const& node)
43{
44 StringBuilder builder;
45 dump_tree(builder, node);
46 dbgln("{}", builder.string_view());
47}
48
49void dump_tree(StringBuilder& builder, DOM::Node const& node)
50{
51 static int indent = 0;
52 for (int i = 0; i < indent; ++i)
53 builder.append(" "sv);
54 if (is<DOM::Element>(node)) {
55 builder.appendff("<{}", verify_cast<DOM::Element>(node).local_name());
56 verify_cast<DOM::Element>(node).for_each_attribute([&](auto& name, auto& value) {
57 builder.appendff(" {}={}", name, value);
58 });
59 builder.append(">\n"sv);
60 } else if (is<DOM::Text>(node)) {
61 builder.appendff("\"{}\"\n", verify_cast<DOM::Text>(node).data());
62 } else {
63 builder.appendff("{}\n", node.node_name());
64 }
65 ++indent;
66 if (is<DOM::Element>(node)) {
67 if (auto* shadow_root = static_cast<DOM::Element const&>(node).shadow_root_internal()) {
68 dump_tree(builder, *shadow_root);
69 }
70 }
71 if (is<DOM::ParentNode>(node)) {
72 if (!is<HTML::HTMLTemplateElement>(node)) {
73 static_cast<DOM::ParentNode const&>(node).for_each_child([&](auto& child) {
74 dump_tree(builder, child);
75 });
76 } else {
77 auto& template_element = verify_cast<HTML::HTMLTemplateElement>(node);
78 dump_tree(builder, template_element.content());
79 }
80 }
81 --indent;
82}
83
84void dump_tree(Layout::Node const& layout_node, bool show_box_model, bool show_specified_style)
85{
86 StringBuilder builder;
87 dump_tree(builder, layout_node, show_box_model, show_specified_style, true);
88 dbgln("{}", builder.string_view());
89}
90
91void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool show_box_model, bool show_specified_style, bool interactive)
92{
93 static size_t indent = 0;
94 for (size_t i = 0; i < indent; ++i)
95 builder.append(" "sv);
96
97 DeprecatedFlyString tag_name;
98 if (layout_node.is_anonymous())
99 tag_name = "(anonymous)";
100 else if (is<DOM::Element>(layout_node.dom_node()))
101 tag_name = verify_cast<DOM::Element>(*layout_node.dom_node()).local_name();
102 else
103 tag_name = layout_node.dom_node()->node_name();
104
105 DeprecatedString identifier = "";
106 if (layout_node.dom_node() && is<DOM::Element>(*layout_node.dom_node())) {
107 auto& element = verify_cast<DOM::Element>(*layout_node.dom_node());
108 StringBuilder builder;
109 auto id = element.attribute(HTML::AttributeNames::id);
110 if (!id.is_empty()) {
111 builder.append('#');
112 builder.append(id);
113 }
114 for (auto& class_name : element.class_names()) {
115 builder.append('.');
116 builder.append(class_name);
117 }
118 identifier = builder.to_deprecated_string();
119 }
120
121 StringView nonbox_color_on = ""sv;
122 StringView box_color_on = ""sv;
123 StringView svg_box_color_on = ""sv;
124 StringView positioned_color_on = ""sv;
125 StringView floating_color_on = ""sv;
126 StringView inline_color_on = ""sv;
127 StringView line_box_color_on = ""sv;
128 StringView fragment_color_on = ""sv;
129 StringView flex_color_on = ""sv;
130 StringView color_off = ""sv;
131
132 if (interactive) {
133 nonbox_color_on = "\033[33m"sv;
134 box_color_on = "\033[34m"sv;
135 svg_box_color_on = "\033[31m"sv;
136 positioned_color_on = "\033[31;1m"sv;
137 floating_color_on = "\033[32;1m"sv;
138 inline_color_on = "\033[36;1m"sv;
139 line_box_color_on = "\033[34;1m"sv;
140 fragment_color_on = "\033[35;1m"sv;
141 flex_color_on = "\033[34;1m"sv;
142 color_off = "\033[0m"sv;
143 }
144
145 if (!is<Layout::Box>(layout_node)) {
146 builder.appendff("{}{}{} <{}{}{}{}>",
147 nonbox_color_on,
148 layout_node.class_name(),
149 color_off,
150 tag_name,
151 nonbox_color_on,
152 identifier,
153 color_off);
154 builder.append("\n"sv);
155 } else {
156 auto& box = verify_cast<Layout::Box>(layout_node);
157 StringView color_on = is<Layout::SVGBox>(box) ? svg_box_color_on : box_color_on;
158
159 builder.appendff("{}{}{} <{}{}{}{}> ",
160 color_on,
161 box.class_name(),
162 color_off,
163 color_on,
164 tag_name,
165 color_off,
166 identifier.characters());
167
168 if (auto const* paint_box = box.paint_box()) {
169 builder.appendff("at ({},{}) content-size {}x{}",
170 paint_box->absolute_x(),
171 paint_box->absolute_y(),
172 paint_box->content_width(),
173 paint_box->content_height());
174 }
175
176 if (box.is_positioned())
177 builder.appendff(" {}positioned{}", positioned_color_on, color_off);
178 if (box.is_floating())
179 builder.appendff(" {}floating{}", floating_color_on, color_off);
180 if (box.is_inline_block())
181 builder.appendff(" {}inline-block{}", inline_color_on, color_off);
182 if (box.is_inline_table())
183 builder.appendff(" {}inline-table{}", inline_color_on, color_off);
184 if (box.display().is_flex_inside()) {
185 StringView direction;
186 switch (box.computed_values().flex_direction()) {
187 case CSS::FlexDirection::Column:
188 direction = "column"sv;
189 break;
190 case CSS::FlexDirection::ColumnReverse:
191 direction = "column-reverse"sv;
192 break;
193 case CSS::FlexDirection::Row:
194 direction = "row"sv;
195 break;
196 case CSS::FlexDirection::RowReverse:
197 direction = "row-reverse"sv;
198 break;
199 }
200 builder.appendff(" {}flex-container({}){}", flex_color_on, direction, color_off);
201 }
202 if (box.is_flex_item())
203 builder.appendff(" {}flex-item{}", flex_color_on, color_off);
204
205 if (show_box_model) {
206 // Dump the horizontal box properties
207 builder.appendff(" [{}+{}+{} {} {}+{}+{}]",
208 box.box_model().margin.left,
209 box.box_model().border.left,
210 box.box_model().padding.left,
211 box.paint_box() ? box.paint_box()->content_width() : 0,
212 box.box_model().padding.right,
213 box.box_model().border.right,
214 box.box_model().margin.right);
215
216 // And the vertical box properties
217 builder.appendff(" [{}+{}+{} {} {}+{}+{}]",
218 box.box_model().margin.top,
219 box.box_model().border.top,
220 box.box_model().padding.top,
221 box.paint_box() ? box.paint_box()->content_height() : 0,
222 box.box_model().padding.bottom,
223 box.box_model().border.bottom,
224 box.box_model().margin.bottom);
225 }
226
227 builder.appendff(" children: {}", box.children_are_inline() ? "inline" : "not-inline");
228
229 if (is<Layout::FrameBox>(box)) {
230 auto const& frame_box = static_cast<Layout::FrameBox const&>(box);
231 if (auto* nested_browsing_context = frame_box.dom_node().nested_browsing_context()) {
232 if (auto* document = nested_browsing_context->active_document()) {
233 builder.appendff(" (url: {})", document->url());
234 }
235 }
236 }
237
238 builder.append("\n"sv);
239 }
240
241 if (is<Layout::BlockContainer>(layout_node) && static_cast<Layout::BlockContainer const&>(layout_node).children_are_inline()) {
242 auto& block = static_cast<Layout::BlockContainer const&>(layout_node);
243 for (size_t line_box_index = 0; block.paint_box() && line_box_index < block.paint_box()->line_boxes().size(); ++line_box_index) {
244 auto& line_box = block.paint_box()->line_boxes()[line_box_index];
245 for (size_t i = 0; i < indent; ++i)
246 builder.append(" "sv);
247 builder.appendff(" {}line {}{} width: {}, height: {}, bottom: {}, baseline: {}\n",
248 line_box_color_on,
249 line_box_index,
250 color_off,
251 line_box.width(),
252 line_box.height(),
253 line_box.bottom(),
254 line_box.baseline());
255 for (size_t fragment_index = 0; fragment_index < line_box.fragments().size(); ++fragment_index) {
256 auto& fragment = line_box.fragments()[fragment_index];
257 for (size_t i = 0; i < indent; ++i)
258 builder.append(" "sv);
259 builder.appendff(" {}frag {}{} from {} ",
260 fragment_color_on,
261 fragment_index,
262 color_off,
263 fragment.layout_node().class_name());
264 builder.appendff("start: {}, length: {}, rect: {}\n",
265 fragment.start(),
266 fragment.length(),
267 fragment.absolute_rect());
268 if (is<Layout::TextNode>(fragment.layout_node())) {
269 for (size_t i = 0; i < indent; ++i)
270 builder.append(" "sv);
271 auto& layout_text = static_cast<Layout::TextNode const&>(fragment.layout_node());
272 auto fragment_text = layout_text.text_for_rendering().substring(fragment.start(), fragment.length());
273 builder.appendff(" \"{}\"\n", fragment_text);
274 }
275 }
276 }
277 }
278
279 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()) {
280 struct NameAndValue {
281 DeprecatedString name;
282 DeprecatedString value;
283 };
284 Vector<NameAndValue> properties;
285 verify_cast<DOM::Element>(*layout_node.dom_node()).computed_css_values()->for_each_property([&](auto property_id, auto& value) {
286 properties.append({ CSS::string_from_property_id(property_id), value.to_string().release_value_but_fixme_should_propagate_errors().to_deprecated_string() });
287 });
288 quick_sort(properties, [](auto& a, auto& b) { return a.name < b.name; });
289
290 for (auto& property : properties) {
291 for (size_t i = 0; i < indent; ++i)
292 builder.append(" "sv);
293 builder.appendff(" ({}: {})\n", property.name, property.value);
294 }
295 }
296
297 ++indent;
298 layout_node.for_each_child([&](auto& child) {
299 dump_tree(builder, child, show_box_model, show_specified_style, interactive);
300 });
301 --indent;
302}
303
304void dump_selector(CSS::Selector const& selector)
305{
306 StringBuilder builder;
307 dump_selector(builder, selector);
308 dbgln("{}", builder.string_view());
309}
310
311void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
312{
313 builder.append(" CSS::Selector:\n"sv);
314
315 for (auto& relative_selector : selector.compound_selectors()) {
316 builder.append(" "sv);
317
318 char const* relation_description = "";
319 switch (relative_selector.combinator) {
320 case CSS::Selector::Combinator::None:
321 relation_description = "None";
322 break;
323 case CSS::Selector::Combinator::ImmediateChild:
324 relation_description = "ImmediateChild";
325 break;
326 case CSS::Selector::Combinator::Descendant:
327 relation_description = "Descendant";
328 break;
329 case CSS::Selector::Combinator::NextSibling:
330 relation_description = "AdjacentSibling";
331 break;
332 case CSS::Selector::Combinator::SubsequentSibling:
333 relation_description = "GeneralSibling";
334 break;
335 case CSS::Selector::Combinator::Column:
336 relation_description = "Column";
337 break;
338 }
339
340 if (*relation_description)
341 builder.appendff("{{{}}} ", relation_description);
342
343 for (size_t i = 0; i < relative_selector.simple_selectors.size(); ++i) {
344 auto& simple_selector = relative_selector.simple_selectors[i];
345 char const* type_description = "Unknown";
346 switch (simple_selector.type) {
347 case CSS::Selector::SimpleSelector::Type::Universal:
348 type_description = "Universal";
349 break;
350 case CSS::Selector::SimpleSelector::Type::Id:
351 type_description = "Id";
352 break;
353 case CSS::Selector::SimpleSelector::Type::Class:
354 type_description = "Class";
355 break;
356 case CSS::Selector::SimpleSelector::Type::TagName:
357 type_description = "TagName";
358 break;
359 case CSS::Selector::SimpleSelector::Type::Attribute:
360 type_description = "Attribute";
361 break;
362 case CSS::Selector::SimpleSelector::Type::PseudoClass:
363 type_description = "PseudoClass";
364 break;
365 case CSS::Selector::SimpleSelector::Type::PseudoElement:
366 type_description = "PseudoElement";
367 break;
368 }
369
370 builder.appendff("{}:", type_description);
371 // FIXME: This is goofy
372 if (simple_selector.value.has<CSS::Selector::SimpleSelector::Name>())
373 builder.append(simple_selector.name());
374
375 if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass) {
376 auto const& pseudo_class = simple_selector.pseudo_class();
377
378 char const* pseudo_class_description = "";
379 switch (pseudo_class.type) {
380 case CSS::Selector::SimpleSelector::PseudoClass::Type::Link:
381 pseudo_class_description = "Link";
382 break;
383 case CSS::Selector::SimpleSelector::PseudoClass::Type::Visited:
384 pseudo_class_description = "Visited";
385 break;
386 case CSS::Selector::SimpleSelector::PseudoClass::Type::Active:
387 pseudo_class_description = "Active";
388 break;
389 case CSS::Selector::SimpleSelector::PseudoClass::Type::Root:
390 pseudo_class_description = "Root";
391 break;
392 case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstOfType:
393 pseudo_class_description = "FirstOfType";
394 break;
395 case CSS::Selector::SimpleSelector::PseudoClass::Type::LastOfType:
396 pseudo_class_description = "LastOfType";
397 break;
398 case CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyOfType:
399 pseudo_class_description = "OnlyOfType";
400 break;
401 case CSS::Selector::SimpleSelector::PseudoClass::Type::NthOfType:
402 pseudo_class_description = "NthOfType";
403 break;
404 case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastOfType:
405 pseudo_class_description = "NthLastOfType";
406 break;
407 case CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild:
408 pseudo_class_description = "NthChild";
409 break;
410 case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild:
411 pseudo_class_description = "NthLastChild";
412 break;
413 case CSS::Selector::SimpleSelector::PseudoClass::Type::Focus:
414 pseudo_class_description = "Focus";
415 break;
416 case CSS::Selector::SimpleSelector::PseudoClass::Type::FocusWithin:
417 pseudo_class_description = "FocusWithin";
418 break;
419 case CSS::Selector::SimpleSelector::PseudoClass::Type::Empty:
420 pseudo_class_description = "Empty";
421 break;
422 case CSS::Selector::SimpleSelector::PseudoClass::Type::Hover:
423 pseudo_class_description = "Hover";
424 break;
425 case CSS::Selector::SimpleSelector::PseudoClass::Type::LastChild:
426 pseudo_class_description = "LastChild";
427 break;
428 case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstChild:
429 pseudo_class_description = "FirstChild";
430 break;
431 case CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyChild:
432 pseudo_class_description = "OnlyChild";
433 break;
434 case CSS::Selector::SimpleSelector::PseudoClass::Type::Disabled:
435 pseudo_class_description = "Disabled";
436 break;
437 case CSS::Selector::SimpleSelector::PseudoClass::Type::Enabled:
438 pseudo_class_description = "Enabled";
439 break;
440 case CSS::Selector::SimpleSelector::PseudoClass::Type::Checked:
441 pseudo_class_description = "Checked";
442 break;
443 case CSS::Selector::SimpleSelector::PseudoClass::Type::Not:
444 pseudo_class_description = "Not";
445 break;
446 case CSS::Selector::SimpleSelector::PseudoClass::Type::Is:
447 pseudo_class_description = "Is";
448 break;
449 case CSS::Selector::SimpleSelector::PseudoClass::Type::Where:
450 pseudo_class_description = "Where";
451 break;
452 case CSS::Selector::SimpleSelector::PseudoClass::Type::Lang:
453 pseudo_class_description = "Lang";
454 break;
455 }
456
457 builder.appendff(" pseudo_class={}", pseudo_class_description);
458 if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Lang) {
459 builder.append('(');
460 builder.join(',', pseudo_class.languages);
461 builder.append(')');
462 } else if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Not
463 || pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Is
464 || pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Where) {
465 builder.append("(["sv);
466 for (auto& selector : pseudo_class.argument_selector_list)
467 dump_selector(builder, selector);
468 builder.append("])"sv);
469 } else if ((pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild)
470 || (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild)
471 || (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthOfType)
472 || (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastOfType)) {
473 builder.appendff("(step={}, offset={}", pseudo_class.nth_child_pattern.step_size, pseudo_class.nth_child_pattern.offset);
474 if (!pseudo_class.argument_selector_list.is_empty()) {
475 builder.append(", selectors=["sv);
476 for (auto const& child_selector : pseudo_class.argument_selector_list)
477 dump_selector(builder, child_selector);
478 builder.append("]"sv);
479 }
480 builder.append(")"sv);
481 }
482 }
483
484 if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoElement) {
485 char const* pseudo_element_description = "";
486 switch (simple_selector.pseudo_element()) {
487 case CSS::Selector::PseudoElement::Before:
488 pseudo_element_description = "before";
489 break;
490 case CSS::Selector::PseudoElement::After:
491 pseudo_element_description = "after";
492 break;
493 case CSS::Selector::PseudoElement::FirstLine:
494 pseudo_element_description = "first-line";
495 break;
496 case CSS::Selector::PseudoElement::FirstLetter:
497 pseudo_element_description = "first-letter";
498 break;
499 case CSS::Selector::PseudoElement::Marker:
500 pseudo_element_description = "marker";
501 break;
502 case CSS::Selector::PseudoElement::ProgressBar:
503 pseudo_element_description = "-webkit-progress-bar";
504 break;
505 case CSS::Selector::PseudoElement::ProgressValue:
506 pseudo_element_description = "-webkit-progress-value";
507 break;
508 case CSS::Selector::PseudoElement::Placeholder:
509 pseudo_element_description = "placeholder";
510 break;
511 case CSS::Selector::PseudoElement::PseudoElementCount:
512 VERIFY_NOT_REACHED();
513 break;
514 }
515
516 builder.appendff(" pseudo_element={}", pseudo_element_description);
517 }
518
519 if (simple_selector.type == CSS::Selector::SimpleSelector::Type::Attribute) {
520 auto const& attribute = simple_selector.attribute();
521 char const* attribute_match_type_description = "";
522
523 switch (attribute.match_type) {
524 case CSS::Selector::SimpleSelector::Attribute::MatchType::HasAttribute:
525 attribute_match_type_description = "HasAttribute";
526 break;
527 case CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch:
528 attribute_match_type_description = "ExactValueMatch";
529 break;
530 case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsWord:
531 attribute_match_type_description = "ContainsWord";
532 break;
533 case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsString:
534 attribute_match_type_description = "ContainsString";
535 break;
536 case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment:
537 attribute_match_type_description = "StartsWithSegment";
538 break;
539 case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithString:
540 attribute_match_type_description = "StartsWithString";
541 break;
542 case CSS::Selector::SimpleSelector::Attribute::MatchType::EndsWithString:
543 attribute_match_type_description = "EndsWithString";
544 break;
545 }
546
547 builder.appendff(" [{}, name='{}', value='{}']", attribute_match_type_description, attribute.name, attribute.value);
548 }
549
550 if (i != relative_selector.simple_selectors.size() - 1)
551 builder.append(", "sv);
552 }
553 builder.append("\n"sv);
554 }
555}
556
557ErrorOr<void> dump_rule(CSS::CSSRule const& rule)
558{
559 StringBuilder builder;
560 TRY(dump_rule(builder, rule));
561 dbgln("{}", builder.string_view());
562 return {};
563}
564
565ErrorOr<void> dump_rule(StringBuilder& builder, CSS::CSSRule const& rule, int indent_levels)
566{
567 indent(builder, indent_levels);
568 builder.appendff("{}:\n", rule.class_name());
569
570 switch (rule.type()) {
571 case CSS::CSSRule::Type::FontFace:
572 dump_font_face_rule(builder, verify_cast<CSS::CSSFontFaceRule const>(rule), indent_levels);
573 break;
574 case CSS::CSSRule::Type::Import:
575 dump_import_rule(builder, verify_cast<CSS::CSSImportRule const>(rule), indent_levels);
576 break;
577 case CSS::CSSRule::Type::Media:
578 TRY(dump_media_rule(builder, verify_cast<CSS::CSSMediaRule const>(rule), indent_levels));
579 break;
580 case CSS::CSSRule::Type::Style:
581 TRY(dump_style_rule(builder, verify_cast<CSS::CSSStyleRule const>(rule), indent_levels));
582 break;
583 case CSS::CSSRule::Type::Supports:
584 TRY(dump_supports_rule(builder, verify_cast<CSS::CSSSupportsRule const>(rule), indent_levels));
585 break;
586 }
587 return {};
588}
589
590void dump_font_face_rule(StringBuilder& builder, CSS::CSSFontFaceRule const& rule, int indent_levels)
591{
592 auto& font_face = rule.font_face();
593 indent(builder, indent_levels + 1);
594 builder.appendff("font-family: {}\n", font_face.font_family());
595
596 indent(builder, indent_levels + 1);
597 builder.append("sources:\n"sv);
598 for (auto const& source : font_face.sources()) {
599 indent(builder, indent_levels + 2);
600 builder.appendff("url={}, format={}\n", source.url, source.format.value_or("???"_short_string));
601 }
602
603 indent(builder, indent_levels + 1);
604 builder.append("unicode-ranges:\n"sv);
605 for (auto const& unicode_range : font_face.unicode_ranges()) {
606 indent(builder, indent_levels + 2);
607 builder.appendff("{}\n", unicode_range.to_string().release_value_but_fixme_should_propagate_errors());
608 }
609}
610
611void dump_import_rule(StringBuilder& builder, CSS::CSSImportRule const& rule, int indent_levels)
612{
613 indent(builder, indent_levels);
614 builder.appendff(" Document URL: {}\n", rule.url());
615}
616
617ErrorOr<void> dump_media_rule(StringBuilder& builder, CSS::CSSMediaRule const& media, int indent_levels)
618{
619 indent(builder, indent_levels);
620 builder.appendff(" Media: {}\n Rules ({}):\n", media.condition_text(), media.css_rules().length());
621
622 for (auto& rule : media.css_rules())
623 TRY(dump_rule(builder, rule, indent_levels + 1));
624 return {};
625}
626
627ErrorOr<void> dump_supports_rule(StringBuilder& builder, CSS::CSSSupportsRule const& supports, int indent_levels)
628{
629 indent(builder, indent_levels);
630 builder.appendff(" Supports: {}\n Rules ({}):\n", supports.condition_text(), supports.css_rules().length());
631
632 for (auto& rule : supports.css_rules())
633 TRY(dump_rule(builder, rule, indent_levels + 1));
634 return {};
635}
636
637ErrorOr<void> dump_style_rule(StringBuilder& builder, CSS::CSSStyleRule const& rule, int indent_levels)
638{
639 for (auto& selector : rule.selectors()) {
640 dump_selector(builder, selector);
641 }
642 indent(builder, indent_levels);
643 builder.append(" Declarations:\n"sv);
644 auto& style_declaration = verify_cast<CSS::PropertyOwningCSSStyleDeclaration>(rule.declaration());
645 for (auto& property : style_declaration.properties()) {
646 indent(builder, indent_levels);
647 builder.appendff(" {}: '{}'", CSS::string_from_property_id(property.property_id), TRY(property.value->to_string()));
648 if (property.important == CSS::Important::Yes)
649 builder.append(" \033[31;1m!important\033[0m"sv);
650 builder.append('\n');
651 }
652 for (auto& property : style_declaration.custom_properties()) {
653 indent(builder, indent_levels);
654 builder.appendff(" {}: '{}'", property.key, TRY(property.value.value->to_string()));
655 if (property.value.important == CSS::Important::Yes)
656 builder.append(" \033[31;1m!important\033[0m"sv);
657 builder.append('\n');
658 }
659 return {};
660}
661
662ErrorOr<void> dump_sheet(CSS::StyleSheet const& sheet)
663{
664 StringBuilder builder;
665 TRY(dump_sheet(builder, sheet));
666 dbgln("{}", builder.string_view());
667 return {};
668}
669
670ErrorOr<void> dump_sheet(StringBuilder& builder, CSS::StyleSheet const& sheet)
671{
672 auto& css_stylesheet = verify_cast<CSS::CSSStyleSheet>(sheet);
673
674 builder.appendff("CSSStyleSheet{{{}}}: {} rule(s)\n", &sheet, css_stylesheet.rules().length());
675
676 for (auto& rule : css_stylesheet.rules())
677 TRY(dump_rule(builder, rule));
678 return {};
679}
680
681}