Serenity Operating System
1/*
2 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021, the SerenityOS developers.
4 * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
5 *
6 * SPDX-License-Identifier: BSD-2-Clause
7 */
8
9#include <AK/Debug.h>
10#include <AK/Error.h>
11#include <AK/HashMap.h>
12#include <AK/QuickSort.h>
13#include <AK/TemporaryChange.h>
14#include <LibGfx/Font/Font.h>
15#include <LibGfx/Font/FontDatabase.h>
16#include <LibGfx/Font/FontStyleMapping.h>
17#include <LibGfx/Font/OpenType/Font.h>
18#include <LibGfx/Font/ScaledFont.h>
19#include <LibGfx/Font/VectorFont.h>
20#include <LibGfx/Font/WOFF/Font.h>
21#include <LibWeb/CSS/CSSFontFaceRule.h>
22#include <LibWeb/CSS/CSSImportRule.h>
23#include <LibWeb/CSS/CSSStyleRule.h>
24#include <LibWeb/CSS/Parser/Parser.h>
25#include <LibWeb/CSS/SelectorEngine.h>
26#include <LibWeb/CSS/StyleComputer.h>
27#include <LibWeb/CSS/StyleSheet.h>
28#include <LibWeb/DOM/Document.h>
29#include <LibWeb/DOM/Element.h>
30#include <LibWeb/FontCache.h>
31#include <LibWeb/HTML/HTMLHtmlElement.h>
32#include <LibWeb/Loader/ResourceLoader.h>
33#include <LibWeb/Platform/FontPlugin.h>
34#include <stdio.h>
35
36namespace Web::CSS {
37
38StyleComputer::StyleComputer(DOM::Document& document)
39 : m_document(document)
40{
41}
42
43StyleComputer::~StyleComputer() = default;
44
45class StyleComputer::FontLoader : public ResourceClient {
46public:
47 explicit FontLoader(StyleComputer& style_computer, FlyString family_name, AK::URL url)
48 : m_style_computer(style_computer)
49 , m_family_name(move(family_name))
50 {
51 LoadRequest request;
52 request.set_url(move(url));
53 set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, request));
54 }
55
56 virtual ~FontLoader() override { }
57
58 virtual void resource_did_load() override
59 {
60 auto result = try_load_font();
61 if (result.is_error())
62 return;
63 m_vector_font = result.release_value();
64 m_style_computer.did_load_font(m_family_name);
65 }
66
67 virtual void resource_did_fail() override
68 {
69 }
70
71 RefPtr<Gfx::Font> font_with_point_size(float point_size) const
72 {
73 if (!m_vector_font)
74 return nullptr;
75
76 if (auto it = m_cached_fonts.find(point_size); it != m_cached_fonts.end())
77 return it->value;
78
79 // FIXME: It might be nicer to have a global cap on the number of fonts we cache
80 // instead of doing it at the per-font level like this.
81 constexpr size_t max_cached_font_size_count = 64;
82 if (m_cached_fonts.size() > max_cached_font_size_count)
83 m_cached_fonts.remove(m_cached_fonts.begin());
84
85 auto font = adopt_ref(*new Gfx::ScaledFont(*m_vector_font, point_size, point_size));
86 m_cached_fonts.set(point_size, font);
87 return font;
88 }
89
90private:
91 ErrorOr<NonnullRefPtr<Gfx::VectorFont>> try_load_font()
92 {
93 // FIXME: This could maybe use the format() provided in @font-face as well, since often the mime type is just application/octet-stream and we have to try every format
94 auto mime_type = resource()->mime_type();
95 if (mime_type == "font/ttf"sv || mime_type == "application/x-font-ttf"sv)
96 return TRY(OpenType::Font::try_load_from_externally_owned_memory(resource()->encoded_data()));
97 if (mime_type == "font/woff"sv)
98 return TRY(WOFF::Font::try_load_from_externally_owned_memory(resource()->encoded_data()));
99 auto ttf = OpenType::Font::try_load_from_externally_owned_memory(resource()->encoded_data());
100 if (!ttf.is_error())
101 return ttf.release_value();
102 auto woff = WOFF::Font::try_load_from_externally_owned_memory(resource()->encoded_data());
103 if (!woff.is_error())
104 return woff.release_value();
105 return ttf.release_error();
106 }
107
108 StyleComputer& m_style_computer;
109 FlyString m_family_name;
110 RefPtr<Gfx::VectorFont> m_vector_font;
111
112 HashMap<float, NonnullRefPtr<Gfx::ScaledFont>> mutable m_cached_fonts;
113};
114
115static CSSStyleSheet& default_stylesheet(DOM::Document const& document)
116{
117 static JS::Handle<CSSStyleSheet> sheet;
118 if (!sheet.cell()) {
119 extern StringView default_stylesheet_source;
120 sheet = JS::make_handle(parse_css_stylesheet(CSS::Parser::ParsingContext(document), default_stylesheet_source));
121 }
122 return *sheet;
123}
124
125static CSSStyleSheet& quirks_mode_stylesheet(DOM::Document const& document)
126{
127 static JS::Handle<CSSStyleSheet> sheet;
128 if (!sheet.cell()) {
129 extern StringView quirks_mode_stylesheet_source;
130 sheet = JS::make_handle(parse_css_stylesheet(CSS::Parser::ParsingContext(document), quirks_mode_stylesheet_source));
131 }
132 return *sheet;
133}
134
135static void collect_style_sheets(CSSStyleSheet const& sheet, Vector<JS::NonnullGCPtr<CSSStyleSheet const>>& sheets)
136{
137 sheets.append(sheet);
138 for (auto const& rule : sheet.rules()) {
139 if (rule.type() == CSSRule::Type::Import) {
140 auto const& import_rule = static_cast<CSSImportRule const&>(rule);
141 if (auto const* imported_sheet = import_rule.loaded_style_sheet()) {
142 collect_style_sheets(*imported_sheet, sheets);
143 }
144 }
145 }
146}
147
148template<typename Callback>
149void StyleComputer::for_each_stylesheet(CascadeOrigin cascade_origin, Callback callback) const
150{
151 if (cascade_origin == CascadeOrigin::UserAgent) {
152 callback(default_stylesheet(document()));
153 if (document().in_quirks_mode())
154 callback(quirks_mode_stylesheet(document()));
155 }
156 if (cascade_origin == CascadeOrigin::Author) {
157 Vector<JS::NonnullGCPtr<CSSStyleSheet const>> sheets;
158 for (auto const& sheet : document().style_sheets().sheets())
159 collect_style_sheets(sheet, sheets);
160 for (auto const& sheet : sheets)
161 callback(*sheet);
162 }
163}
164
165StyleComputer::RuleCache const& StyleComputer::rule_cache_for_cascade_origin(CascadeOrigin cascade_origin) const
166{
167 switch (cascade_origin) {
168 case CascadeOrigin::Author:
169 return *m_author_rule_cache;
170 case CascadeOrigin::UserAgent:
171 return *m_user_agent_rule_cache;
172 default:
173 TODO();
174 }
175}
176
177Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::Selector::PseudoElement> pseudo_element) const
178{
179 auto const& rule_cache = rule_cache_for_cascade_origin(cascade_origin);
180
181 Vector<MatchingRule> rules_to_run;
182 auto add_rules_to_run = [&](Vector<MatchingRule> const& rules) {
183 if (pseudo_element.has_value()) {
184 for (auto& rule : rules) {
185 if (rule.contains_pseudo_element)
186 rules_to_run.append(rule);
187 }
188 } else {
189 rules_to_run.extend(rules);
190 }
191 };
192
193 for (auto const& class_name : element.class_names()) {
194 if (auto it = rule_cache.rules_by_class.find(class_name); it != rule_cache.rules_by_class.end())
195 add_rules_to_run(it->value);
196 }
197 if (auto id = element.get_attribute(HTML::AttributeNames::id); !id.is_null()) {
198 if (auto it = rule_cache.rules_by_id.find(FlyString::from_deprecated_fly_string(id).release_value_but_fixme_should_propagate_errors()); it != rule_cache.rules_by_id.end())
199 add_rules_to_run(it->value);
200 }
201 if (auto it = rule_cache.rules_by_tag_name.find(FlyString::from_deprecated_fly_string(element.local_name()).release_value_but_fixme_should_propagate_errors()); it != rule_cache.rules_by_tag_name.end())
202 add_rules_to_run(it->value);
203 add_rules_to_run(rule_cache.other_rules);
204
205 Vector<MatchingRule> matching_rules;
206 matching_rules.ensure_capacity(rules_to_run.size());
207 for (auto const& rule_to_run : rules_to_run) {
208 auto const& selector = rule_to_run.rule->selectors()[rule_to_run.selector_index];
209 if (SelectorEngine::matches(selector, element, pseudo_element))
210 matching_rules.append(rule_to_run);
211 }
212 return matching_rules;
213}
214
215static void sort_matching_rules(Vector<MatchingRule>& matching_rules)
216{
217 quick_sort(matching_rules, [&](MatchingRule& a, MatchingRule& b) {
218 auto const& a_selector = a.rule->selectors()[a.selector_index];
219 auto const& b_selector = b.rule->selectors()[b.selector_index];
220 auto a_specificity = a_selector->specificity();
221 auto b_specificity = b_selector->specificity();
222 if (a_selector->specificity() == b_selector->specificity()) {
223 if (a.style_sheet_index == b.style_sheet_index)
224 return a.rule_index < b.rule_index;
225 return a.style_sheet_index < b.style_sheet_index;
226 }
227 return a_specificity < b_specificity;
228 });
229}
230
231enum class Edge {
232 Top,
233 Right,
234 Bottom,
235 Left,
236 All,
237};
238
239static bool contains(Edge a, Edge b)
240{
241 return a == b || b == Edge::All;
242}
243
244static void set_property_expanding_shorthands(StyleProperties& style, CSS::PropertyID property_id, StyleValue const& value, DOM::Document& document)
245{
246 auto assign_edge_values = [&style](PropertyID top_property, PropertyID right_property, PropertyID bottom_property, PropertyID left_property, auto const& values) {
247 if (values.size() == 4) {
248 style.set_property(top_property, values[0]);
249 style.set_property(right_property, values[1]);
250 style.set_property(bottom_property, values[2]);
251 style.set_property(left_property, values[3]);
252 } else if (values.size() == 3) {
253 style.set_property(top_property, values[0]);
254 style.set_property(right_property, values[1]);
255 style.set_property(bottom_property, values[2]);
256 style.set_property(left_property, values[1]);
257 } else if (values.size() == 2) {
258 style.set_property(top_property, values[0]);
259 style.set_property(right_property, values[1]);
260 style.set_property(bottom_property, values[0]);
261 style.set_property(left_property, values[1]);
262 } else if (values.size() == 1) {
263 style.set_property(top_property, values[0]);
264 style.set_property(right_property, values[0]);
265 style.set_property(bottom_property, values[0]);
266 style.set_property(left_property, values[0]);
267 }
268 };
269
270 if (property_id == CSS::PropertyID::TextDecoration) {
271 if (value.is_text_decoration()) {
272 auto const& text_decoration = value.as_text_decoration();
273 style.set_property(CSS::PropertyID::TextDecorationLine, text_decoration.line());
274 style.set_property(CSS::PropertyID::TextDecorationThickness, text_decoration.thickness());
275 style.set_property(CSS::PropertyID::TextDecorationStyle, text_decoration.style());
276 style.set_property(CSS::PropertyID::TextDecorationColor, text_decoration.color());
277 return;
278 }
279
280 style.set_property(CSS::PropertyID::TextDecorationLine, value);
281 style.set_property(CSS::PropertyID::TextDecorationThickness, value);
282 style.set_property(CSS::PropertyID::TextDecorationStyle, value);
283 style.set_property(CSS::PropertyID::TextDecorationColor, value);
284 return;
285 }
286
287 if (property_id == CSS::PropertyID::Overflow) {
288 if (value.is_overflow()) {
289 auto const& overflow = value.as_overflow();
290 style.set_property(CSS::PropertyID::OverflowX, overflow.overflow_x());
291 style.set_property(CSS::PropertyID::OverflowY, overflow.overflow_y());
292 return;
293 }
294
295 style.set_property(CSS::PropertyID::OverflowX, value);
296 style.set_property(CSS::PropertyID::OverflowY, value);
297 return;
298 }
299
300 if (property_id == CSS::PropertyID::Border) {
301 set_property_expanding_shorthands(style, CSS::PropertyID::BorderTop, value, document);
302 set_property_expanding_shorthands(style, CSS::PropertyID::BorderRight, value, document);
303 set_property_expanding_shorthands(style, CSS::PropertyID::BorderBottom, value, document);
304 set_property_expanding_shorthands(style, CSS::PropertyID::BorderLeft, value, document);
305 // FIXME: Also reset border-image, in line with the spec: https://www.w3.org/TR/css-backgrounds-3/#border-shorthands
306 return;
307 }
308
309 if (property_id == CSS::PropertyID::BorderRadius) {
310 if (value.is_border_radius_shorthand()) {
311 auto const& shorthand = value.as_border_radius_shorthand();
312 style.set_property(CSS::PropertyID::BorderTopLeftRadius, shorthand.top_left());
313 style.set_property(CSS::PropertyID::BorderTopRightRadius, shorthand.top_right());
314 style.set_property(CSS::PropertyID::BorderBottomRightRadius, shorthand.bottom_right());
315 style.set_property(CSS::PropertyID::BorderBottomLeftRadius, shorthand.bottom_left());
316 return;
317 }
318
319 style.set_property(CSS::PropertyID::BorderTopLeftRadius, value);
320 style.set_property(CSS::PropertyID::BorderTopRightRadius, value);
321 style.set_property(CSS::PropertyID::BorderBottomRightRadius, value);
322 style.set_property(CSS::PropertyID::BorderBottomLeftRadius, value);
323 return;
324 }
325
326 if (property_id == CSS::PropertyID::BorderTop
327 || property_id == CSS::PropertyID::BorderRight
328 || property_id == CSS::PropertyID::BorderBottom
329 || property_id == CSS::PropertyID::BorderLeft) {
330
331 Edge edge = Edge::All;
332 switch (property_id) {
333 case CSS::PropertyID::BorderTop:
334 edge = Edge::Top;
335 break;
336 case CSS::PropertyID::BorderRight:
337 edge = Edge::Right;
338 break;
339 case CSS::PropertyID::BorderBottom:
340 edge = Edge::Bottom;
341 break;
342 case CSS::PropertyID::BorderLeft:
343 edge = Edge::Left;
344 break;
345 default:
346 break;
347 }
348
349 if (value.is_border()) {
350 auto const& border = value.as_border();
351 if (contains(Edge::Top, edge)) {
352 style.set_property(PropertyID::BorderTopWidth, border.border_width());
353 style.set_property(PropertyID::BorderTopStyle, border.border_style());
354 style.set_property(PropertyID::BorderTopColor, border.border_color());
355 }
356 if (contains(Edge::Right, edge)) {
357 style.set_property(PropertyID::BorderRightWidth, border.border_width());
358 style.set_property(PropertyID::BorderRightStyle, border.border_style());
359 style.set_property(PropertyID::BorderRightColor, border.border_color());
360 }
361 if (contains(Edge::Bottom, edge)) {
362 style.set_property(PropertyID::BorderBottomWidth, border.border_width());
363 style.set_property(PropertyID::BorderBottomStyle, border.border_style());
364 style.set_property(PropertyID::BorderBottomColor, border.border_color());
365 }
366 if (contains(Edge::Left, edge)) {
367 style.set_property(PropertyID::BorderLeftWidth, border.border_width());
368 style.set_property(PropertyID::BorderLeftStyle, border.border_style());
369 style.set_property(PropertyID::BorderLeftColor, border.border_color());
370 }
371 return;
372 }
373 return;
374 }
375
376 if (property_id == CSS::PropertyID::BorderStyle) {
377 if (value.is_value_list()) {
378 auto const& values_list = value.as_value_list();
379 assign_edge_values(PropertyID::BorderTopStyle, PropertyID::BorderRightStyle, PropertyID::BorderBottomStyle, PropertyID::BorderLeftStyle, values_list.values());
380 return;
381 }
382
383 style.set_property(CSS::PropertyID::BorderTopStyle, value);
384 style.set_property(CSS::PropertyID::BorderRightStyle, value);
385 style.set_property(CSS::PropertyID::BorderBottomStyle, value);
386 style.set_property(CSS::PropertyID::BorderLeftStyle, value);
387 return;
388 }
389
390 if (property_id == CSS::PropertyID::BorderWidth) {
391 if (value.is_value_list()) {
392 auto const& values_list = value.as_value_list();
393 assign_edge_values(PropertyID::BorderTopWidth, PropertyID::BorderRightWidth, PropertyID::BorderBottomWidth, PropertyID::BorderLeftWidth, values_list.values());
394 return;
395 }
396
397 style.set_property(CSS::PropertyID::BorderTopWidth, value);
398 style.set_property(CSS::PropertyID::BorderRightWidth, value);
399 style.set_property(CSS::PropertyID::BorderBottomWidth, value);
400 style.set_property(CSS::PropertyID::BorderLeftWidth, value);
401 return;
402 }
403
404 if (property_id == CSS::PropertyID::BorderColor) {
405 if (value.is_value_list()) {
406 auto const& values_list = value.as_value_list();
407 assign_edge_values(PropertyID::BorderTopColor, PropertyID::BorderRightColor, PropertyID::BorderBottomColor, PropertyID::BorderLeftColor, values_list.values());
408 return;
409 }
410
411 style.set_property(CSS::PropertyID::BorderTopColor, value);
412 style.set_property(CSS::PropertyID::BorderRightColor, value);
413 style.set_property(CSS::PropertyID::BorderBottomColor, value);
414 style.set_property(CSS::PropertyID::BorderLeftColor, value);
415 return;
416 }
417
418 if (property_id == CSS::PropertyID::Background) {
419 if (value.is_background()) {
420 auto const& background = value.as_background();
421 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundColor, background.color(), document);
422 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundImage, background.image(), document);
423 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundPosition, background.position(), document);
424 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundSize, background.size(), document);
425 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundRepeat, background.repeat(), document);
426 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundAttachment, background.attachment(), document);
427 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundOrigin, background.origin(), document);
428 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundClip, background.clip(), document);
429 return;
430 }
431
432 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundColor, value, document);
433 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundImage, value, document);
434 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundPosition, value, document);
435 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundSize, value, document);
436 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundRepeat, value, document);
437 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundAttachment, value, document);
438 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundOrigin, value, document);
439 set_property_expanding_shorthands(style, CSS::PropertyID::BackgroundClip, value, document);
440 return;
441 }
442
443 if (property_id == CSS::PropertyID::Margin) {
444 if (value.is_value_list()) {
445 auto const& values_list = value.as_value_list();
446 assign_edge_values(PropertyID::MarginTop, PropertyID::MarginRight, PropertyID::MarginBottom, PropertyID::MarginLeft, values_list.values());
447 return;
448 }
449
450 style.set_property(CSS::PropertyID::MarginTop, value);
451 style.set_property(CSS::PropertyID::MarginRight, value);
452 style.set_property(CSS::PropertyID::MarginBottom, value);
453 style.set_property(CSS::PropertyID::MarginLeft, value);
454 return;
455 }
456
457 if (property_id == CSS::PropertyID::Padding) {
458 if (value.is_value_list()) {
459 auto const& values_list = value.as_value_list();
460 assign_edge_values(PropertyID::PaddingTop, PropertyID::PaddingRight, PropertyID::PaddingBottom, PropertyID::PaddingLeft, values_list.values());
461 return;
462 }
463
464 style.set_property(CSS::PropertyID::PaddingTop, value);
465 style.set_property(CSS::PropertyID::PaddingRight, value);
466 style.set_property(CSS::PropertyID::PaddingBottom, value);
467 style.set_property(CSS::PropertyID::PaddingLeft, value);
468 return;
469 }
470
471 if (property_id == CSS::PropertyID::ListStyle) {
472 if (value.is_list_style()) {
473 auto const& list_style = value.as_list_style();
474 style.set_property(CSS::PropertyID::ListStylePosition, list_style.position());
475 style.set_property(CSS::PropertyID::ListStyleImage, list_style.image());
476 style.set_property(CSS::PropertyID::ListStyleType, list_style.style_type());
477 return;
478 }
479
480 style.set_property(CSS::PropertyID::ListStylePosition, value);
481 style.set_property(CSS::PropertyID::ListStyleImage, value);
482 style.set_property(CSS::PropertyID::ListStyleType, value);
483 return;
484 }
485
486 if (property_id == CSS::PropertyID::Font) {
487 if (value.is_font()) {
488 auto const& font_shorthand = value.as_font();
489 style.set_property(CSS::PropertyID::FontSize, font_shorthand.font_size());
490 style.set_property(CSS::PropertyID::FontFamily, font_shorthand.font_families());
491 style.set_property(CSS::PropertyID::FontStretch, font_shorthand.font_stretch());
492 style.set_property(CSS::PropertyID::FontStyle, font_shorthand.font_style());
493 style.set_property(CSS::PropertyID::FontWeight, font_shorthand.font_weight());
494 style.set_property(CSS::PropertyID::LineHeight, font_shorthand.line_height());
495 // FIXME: Implement font-variant
496 return;
497 }
498
499 style.set_property(CSS::PropertyID::FontStretch, value);
500 style.set_property(CSS::PropertyID::FontSize, value);
501 style.set_property(CSS::PropertyID::FontFamily, value);
502 style.set_property(CSS::PropertyID::FontStyle, value);
503 style.set_property(CSS::PropertyID::FontWeight, value);
504 style.set_property(CSS::PropertyID::LineHeight, value);
505 // FIXME: Implement font-variant
506 return;
507 }
508
509 if (property_id == CSS::PropertyID::Flex) {
510 if (value.is_flex()) {
511 auto const& flex = value.as_flex();
512 style.set_property(CSS::PropertyID::FlexGrow, flex.grow());
513 style.set_property(CSS::PropertyID::FlexShrink, flex.shrink());
514 style.set_property(CSS::PropertyID::FlexBasis, flex.basis());
515 return;
516 }
517
518 style.set_property(CSS::PropertyID::FlexGrow, value);
519 style.set_property(CSS::PropertyID::FlexShrink, value);
520 style.set_property(CSS::PropertyID::FlexBasis, value);
521 return;
522 }
523
524 if (property_id == CSS::PropertyID::FlexFlow) {
525 if (value.is_flex_flow()) {
526 auto const& flex_flow = value.as_flex_flow();
527 style.set_property(CSS::PropertyID::FlexDirection, flex_flow.flex_direction());
528 style.set_property(CSS::PropertyID::FlexWrap, flex_flow.flex_wrap());
529 return;
530 }
531
532 style.set_property(CSS::PropertyID::FlexDirection, value);
533 style.set_property(CSS::PropertyID::FlexWrap, value);
534 return;
535 }
536
537 if (property_id == CSS::PropertyID::GridArea) {
538 if (value.is_grid_area_shorthand()) {
539 auto const& shorthand = value.as_grid_area_shorthand();
540 style.set_property(CSS::PropertyID::GridRowStart, shorthand.row_start());
541 style.set_property(CSS::PropertyID::GridColumnStart, shorthand.column_start());
542 style.set_property(CSS::PropertyID::GridRowEnd, shorthand.row_end());
543 style.set_property(CSS::PropertyID::GridColumnEnd, shorthand.column_end());
544 return;
545 }
546 style.set_property(CSS::PropertyID::GridRowStart, value);
547 style.set_property(CSS::PropertyID::GridColumnStart, value);
548 style.set_property(CSS::PropertyID::GridRowEnd, value);
549 style.set_property(CSS::PropertyID::GridColumnEnd, value);
550 return;
551 }
552
553 if (property_id == CSS::PropertyID::GridColumn) {
554 if (value.is_grid_track_placement_shorthand()) {
555 auto const& shorthand = value.as_grid_track_placement_shorthand();
556 style.set_property(CSS::PropertyID::GridColumnStart, shorthand.start());
557 style.set_property(CSS::PropertyID::GridColumnEnd, shorthand.end());
558 return;
559 }
560
561 style.set_property(CSS::PropertyID::GridColumnStart, value);
562 style.set_property(CSS::PropertyID::GridColumnEnd, value);
563 return;
564 }
565
566 if (property_id == CSS::PropertyID::GridRow) {
567 if (value.is_grid_track_placement_shorthand()) {
568 auto const& shorthand = value.as_grid_track_placement_shorthand();
569 style.set_property(CSS::PropertyID::GridRowStart, shorthand.start());
570 style.set_property(CSS::PropertyID::GridRowEnd, shorthand.end());
571 return;
572 }
573
574 style.set_property(CSS::PropertyID::GridRowStart, value);
575 style.set_property(CSS::PropertyID::GridRowEnd, value);
576 return;
577 }
578
579 if (property_id == CSS::PropertyID::Gap || property_id == CSS::PropertyID::GridGap) {
580 if (value.is_value_list()) {
581 auto const& values_list = value.as_value_list();
582 style.set_property(CSS::PropertyID::RowGap, values_list.values()[0]);
583 style.set_property(CSS::PropertyID::ColumnGap, values_list.values()[1]);
584 return;
585 }
586 style.set_property(CSS::PropertyID::RowGap, value);
587 style.set_property(CSS::PropertyID::ColumnGap, value);
588 return;
589 }
590
591 if (property_id == CSS::PropertyID::RowGap || property_id == CSS::PropertyID::GridRowGap) {
592 style.set_property(CSS::PropertyID::RowGap, value);
593 return;
594 }
595
596 if (property_id == CSS::PropertyID::ColumnGap || property_id == CSS::PropertyID::GridColumnGap) {
597 style.set_property(CSS::PropertyID::ColumnGap, value);
598 return;
599 }
600
601 style.set_property(property_id, value);
602}
603
604static RefPtr<StyleValue const> get_custom_property(DOM::Element const& element, FlyString const& custom_property_name)
605{
606 for (auto const* current_element = &element; current_element; current_element = current_element->parent_element()) {
607 if (auto it = current_element->custom_properties().find(custom_property_name.to_string().to_deprecated_string()); it != current_element->custom_properties().end())
608 return it->value.value;
609 }
610 return nullptr;
611}
612
613bool StyleComputer::expand_variables(DOM::Element& element, StringView property_name, HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>>& dependencies, Parser::TokenStream<Parser::ComponentValue>& source, Vector<Parser::ComponentValue>& dest) const
614{
615 // Arbitrary large value chosen to avoid the billion-laughs attack.
616 // https://www.w3.org/TR/css-variables-1/#long-variables
617 const size_t MAX_VALUE_COUNT = 16384;
618 if (source.remaining_token_count() + dest.size() > MAX_VALUE_COUNT) {
619 dbgln("Stopped expanding CSS variables: maximum length reached.");
620 return false;
621 }
622
623 auto get_dependency_node = [&](FlyString name) -> NonnullRefPtr<PropertyDependencyNode> {
624 if (auto existing = dependencies.get(name); existing.has_value())
625 return *existing.value();
626 auto new_node = PropertyDependencyNode::create(name.to_string());
627 dependencies.set(name, new_node);
628 return new_node;
629 };
630
631 while (source.has_next_token()) {
632 auto const& value = source.next_token();
633 if (!value.is_function()) {
634 dest.empend(value);
635 continue;
636 }
637 if (!value.function().name().equals_ignoring_ascii_case("var"sv)) {
638 auto const& source_function = value.function();
639 Vector<Parser::ComponentValue> function_values;
640 Parser::TokenStream source_function_contents { source_function.values() };
641 if (!expand_variables(element, property_name, dependencies, source_function_contents, function_values))
642 return false;
643 NonnullRefPtr<Parser::Function> function = Parser::Function::create(FlyString::from_utf8(source_function.name()).release_value_but_fixme_should_propagate_errors(), move(function_values));
644 dest.empend(function);
645 continue;
646 }
647
648 Parser::TokenStream var_contents { value.function().values() };
649 var_contents.skip_whitespace();
650 if (!var_contents.has_next_token())
651 return false;
652
653 auto const& custom_property_name_token = var_contents.next_token();
654 if (!custom_property_name_token.is(Parser::Token::Type::Ident))
655 return false;
656 auto custom_property_name = custom_property_name_token.token().ident();
657 if (!custom_property_name.starts_with("--"sv))
658 return false;
659
660 // Detect dependency cycles. https://www.w3.org/TR/css-variables-1/#cycles
661 // We do not do this by the spec, since we are not keeping a graph of var dependencies around,
662 // but rebuilding it every time.
663 if (custom_property_name == property_name)
664 return false;
665 auto parent = get_dependency_node(FlyString::from_utf8(property_name).release_value_but_fixme_should_propagate_errors());
666 auto child = get_dependency_node(FlyString::from_utf8(custom_property_name).release_value_but_fixme_should_propagate_errors());
667 parent->add_child(child);
668 if (parent->has_cycles())
669 return false;
670
671 if (auto custom_property_value = get_custom_property(element, FlyString::from_utf8(custom_property_name).release_value_but_fixme_should_propagate_errors())) {
672 VERIFY(custom_property_value->is_unresolved());
673 Parser::TokenStream custom_property_tokens { custom_property_value->as_unresolved().values() };
674 if (!expand_variables(element, custom_property_name, dependencies, custom_property_tokens, dest))
675 return false;
676 continue;
677 }
678
679 // Use the provided fallback value, if any.
680 var_contents.skip_whitespace();
681 if (var_contents.has_next_token()) {
682 auto const& comma_token = var_contents.next_token();
683 if (!comma_token.is(Parser::Token::Type::Comma))
684 return false;
685 var_contents.skip_whitespace();
686 if (!expand_variables(element, property_name, dependencies, var_contents, dest))
687 return false;
688 }
689 }
690 return true;
691}
692
693bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView property_name, Parser::TokenStream<Parser::ComponentValue>& source, Vector<Parser::ComponentValue>& dest) const
694{
695 // FIXME: Do this better!
696 // We build a copy of the tree of ComponentValues, with all var()s and attr()s replaced with their contents.
697 // This is a very naive solution, and we could do better if the CSS Parser could accept tokens one at a time.
698
699 while (source.has_next_token()) {
700 auto const& value = source.next_token();
701 if (value.is_function()) {
702 if (value.function().name().equals_ignoring_ascii_case("attr"sv)) {
703 // https://drafts.csswg.org/css-values-5/#attr-substitution
704 Parser::TokenStream attr_contents { value.function().values() };
705 attr_contents.skip_whitespace();
706 if (!attr_contents.has_next_token())
707 return false;
708
709 auto const& attr_name_token = attr_contents.next_token();
710 if (!attr_name_token.is(Parser::Token::Type::Ident))
711 return false;
712 auto attr_name = attr_name_token.token().ident();
713
714 auto attr_value = element.get_attribute(attr_name);
715 // 1. If the attr() function has a substitution value, replace the attr() function by the substitution value.
716 if (!attr_value.is_null()) {
717 // FIXME: attr() should also accept an optional type argument, not just strings.
718 dest.empend(Parser::Token::of_string(FlyString::from_deprecated_fly_string(attr_value).release_value_but_fixme_should_propagate_errors()));
719 continue;
720 }
721
722 // 2. Otherwise, if the attr() function has a fallback value as its last argument, replace the attr() function by the fallback value.
723 // If there are any var() or attr() references in the fallback, substitute them as well.
724 attr_contents.skip_whitespace();
725 if (attr_contents.has_next_token()) {
726 auto const& comma_token = attr_contents.next_token();
727 if (!comma_token.is(Parser::Token::Type::Comma))
728 return false;
729 attr_contents.skip_whitespace();
730 if (!expand_unresolved_values(element, property_name, attr_contents, dest))
731 return false;
732 continue;
733 }
734
735 // 3. Otherwise, the property containing the attr() function is invalid at computed-value time.
736 return false;
737 }
738
739 if (value.function().name().equals_ignoring_ascii_case("calc"sv)) {
740 auto const& calc_function = value.function();
741 if (auto calc_value = CSS::Parser::Parser::parse_calculated_value({}, Parser::ParsingContext { document() }, calc_function.values())) {
742 switch (calc_value->resolved_type()) {
743 case CalculatedStyleValue::ResolvedType::Integer: {
744 auto resolved_value = calc_value->resolve_integer();
745 dest.empend(Parser::Token::create_number(resolved_value.value()));
746 continue;
747 }
748 case CalculatedStyleValue::ResolvedType::Percentage: {
749 auto resolved_value = calc_value->resolve_percentage();
750 dest.empend(Parser::Token::create_percentage(resolved_value.value().value()));
751 continue;
752 }
753 default:
754 dbgln("FIXME: Unimplement calc() expansion in StyleComputer");
755 break;
756 }
757 }
758 }
759
760 auto const& source_function = value.function();
761 Vector<Parser::ComponentValue> function_values;
762 Parser::TokenStream source_function_contents { source_function.values() };
763 if (!expand_unresolved_values(element, property_name, source_function_contents, function_values))
764 return false;
765 // FIXME: This would be much nicer if we could access the source_function's FlyString value directly.
766 NonnullRefPtr<Parser::Function> function = Parser::Function::create(FlyString::from_utf8(source_function.name()).release_value_but_fixme_should_propagate_errors(), move(function_values));
767 dest.empend(function);
768 continue;
769 }
770 if (value.is_block()) {
771 auto const& source_block = value.block();
772 Parser::TokenStream source_block_values { source_block.values() };
773 Vector<Parser::ComponentValue> block_values;
774 if (!expand_unresolved_values(element, property_name, source_block_values, block_values))
775 return false;
776 NonnullRefPtr<Parser::Block> block = Parser::Block::create(source_block.token(), move(block_values));
777 dest.empend(move(block));
778 continue;
779 }
780 dest.empend(value.token());
781 }
782
783 return true;
784}
785
786RefPtr<StyleValue> StyleComputer::resolve_unresolved_style_value(DOM::Element& element, PropertyID property_id, UnresolvedStyleValue const& unresolved) const
787{
788 // Unresolved always contains a var() or attr(), unless it is a custom property's value, in which case we shouldn't be trying
789 // to produce a different StyleValue from it.
790 VERIFY(unresolved.contains_var_or_attr());
791
792 Parser::TokenStream unresolved_values_without_variables_expanded { unresolved.values() };
793 Vector<Parser::ComponentValue> values_with_variables_expanded;
794
795 HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>> dependencies;
796 if (!expand_variables(element, string_from_property_id(property_id), dependencies, unresolved_values_without_variables_expanded, values_with_variables_expanded))
797 return {};
798
799 Parser::TokenStream unresolved_values_with_variables_expanded { values_with_variables_expanded };
800 Vector<Parser::ComponentValue> expanded_values;
801 if (!expand_unresolved_values(element, string_from_property_id(property_id), unresolved_values_with_variables_expanded, expanded_values))
802 return {};
803
804 if (auto parsed_value = Parser::Parser::parse_css_value({}, Parser::ParsingContext { document() }, property_id, expanded_values))
805 return parsed_value.release_nonnull();
806
807 return {};
808}
809
810void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& element, Vector<MatchingRule> const& matching_rules, CascadeOrigin cascade_origin, Important important) const
811{
812 for (auto const& match : matching_rules) {
813 for (auto const& property : verify_cast<PropertyOwningCSSStyleDeclaration>(match.rule->declaration()).properties()) {
814 if (important != property.important)
815 continue;
816 auto property_value = property.value;
817 if (property.value->is_unresolved()) {
818 if (auto resolved = resolve_unresolved_style_value(element, property.property_id, property.value->as_unresolved()))
819 property_value = resolved.release_nonnull();
820 }
821 if (!property_value->is_unresolved())
822 set_property_expanding_shorthands(style, property.property_id, property_value, m_document);
823 }
824 }
825
826 if (cascade_origin == CascadeOrigin::Author) {
827 if (auto const* inline_style = verify_cast<ElementInlineCSSStyleDeclaration>(element.inline_style())) {
828 for (auto const& property : inline_style->properties()) {
829 if (important != property.important)
830 continue;
831 auto property_value = property.value;
832 if (property.value->is_unresolved()) {
833 if (auto resolved = resolve_unresolved_style_value(element, property.property_id, property.value->as_unresolved()))
834 property_value = resolved.release_nonnull();
835 }
836 if (!property_value->is_unresolved())
837 set_property_expanding_shorthands(style, property.property_id, property_value, m_document);
838 }
839 }
840 }
841}
842
843static ErrorOr<void> cascade_custom_properties(DOM::Element& element, Vector<MatchingRule> const& matching_rules)
844{
845 size_t needed_capacity = 0;
846 for (auto const& matching_rule : matching_rules)
847 needed_capacity += verify_cast<PropertyOwningCSSStyleDeclaration>(matching_rule.rule->declaration()).custom_properties().size();
848 if (auto const* inline_style = verify_cast<PropertyOwningCSSStyleDeclaration>(element.inline_style()))
849 needed_capacity += inline_style->custom_properties().size();
850
851 HashMap<DeprecatedFlyString, StyleProperty> custom_properties;
852 TRY(custom_properties.try_ensure_capacity(needed_capacity));
853
854 for (auto const& matching_rule : matching_rules) {
855 for (auto const& it : verify_cast<PropertyOwningCSSStyleDeclaration>(matching_rule.rule->declaration()).custom_properties())
856 custom_properties.set(it.key, it.value);
857 }
858
859 if (auto const* inline_style = verify_cast<PropertyOwningCSSStyleDeclaration>(element.inline_style())) {
860 for (auto const& it : inline_style->custom_properties())
861 custom_properties.set(it.key, it.value);
862 }
863
864 element.set_custom_properties(move(custom_properties));
865
866 return {};
867}
868
869// https://www.w3.org/TR/css-cascade/#cascading
870ErrorOr<void> StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element& element, Optional<CSS::Selector::PseudoElement> pseudo_element) const
871{
872 // First, we collect all the CSS rules whose selectors match `element`:
873 MatchingRuleSet matching_rule_set;
874 matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element);
875 sort_matching_rules(matching_rule_set.user_agent_rules);
876 matching_rule_set.author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element);
877 sort_matching_rules(matching_rule_set.author_rules);
878
879 // Then we resolve all the CSS custom properties ("variables") for this element:
880 // FIXME: Look into how custom properties should interact with pseudo elements and support that properly.
881 if (!pseudo_element.has_value())
882 TRY(cascade_custom_properties(element, matching_rule_set.author_rules));
883
884 // Then we apply the declarations from the matched rules in cascade order:
885
886 // Normal user agent declarations
887 cascade_declarations(style, element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, Important::No);
888
889 // FIXME: Normal user declarations
890
891 // Author presentational hints (NOTE: The spec doesn't say exactly how to prioritize these.)
892 element.apply_presentational_hints(style);
893
894 // Normal author declarations
895 cascade_declarations(style, element, matching_rule_set.author_rules, CascadeOrigin::Author, Important::No);
896
897 // FIXME: Animation declarations [css-animations-1]
898
899 // Important author declarations
900 cascade_declarations(style, element, matching_rule_set.author_rules, CascadeOrigin::Author, Important::Yes);
901
902 // FIXME: Important user declarations
903
904 // Important user agent declarations
905 cascade_declarations(style, element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, Important::Yes);
906
907 // FIXME: Transition declarations [css-transitions-1]
908
909 return {};
910}
911
912static DOM::Element const* element_to_inherit_style_from(DOM::Element const* element, Optional<CSS::Selector::PseudoElement> pseudo_element)
913{
914 // Pseudo-elements treat their originating element as their parent.
915 DOM::Element const* parent_element = nullptr;
916 if (pseudo_element.has_value()) {
917 parent_element = element;
918 } else if (element) {
919 parent_element = element->parent_or_shadow_host_element();
920 }
921 return parent_element;
922}
923
924static NonnullRefPtr<StyleValue const> get_inherit_value(JS::Realm& initial_value_context_realm, CSS::PropertyID property_id, DOM::Element const* element, Optional<CSS::Selector::PseudoElement> pseudo_element)
925{
926 auto* parent_element = element_to_inherit_style_from(element, pseudo_element);
927
928 if (!parent_element || !parent_element->computed_css_values())
929 return property_initial_value(initial_value_context_realm, property_id);
930 return parent_element->computed_css_values()->property(property_id);
931};
932
933void StyleComputer::compute_defaulted_property_value(StyleProperties& style, DOM::Element const* element, CSS::PropertyID property_id, Optional<CSS::Selector::PseudoElement> pseudo_element) const
934{
935 // FIXME: If we don't know the correct initial value for a property, we fall back to InitialStyleValue.
936
937 auto& value_slot = style.m_property_values[to_underlying(property_id)];
938 if (!value_slot) {
939 if (is_inherited_property(property_id))
940 style.m_property_values[to_underlying(property_id)] = get_inherit_value(document().realm(), property_id, element, pseudo_element);
941 else
942 style.m_property_values[to_underlying(property_id)] = property_initial_value(document().realm(), property_id);
943 return;
944 }
945
946 if (value_slot->is_initial()) {
947 value_slot = property_initial_value(document().realm(), property_id);
948 return;
949 }
950
951 if (value_slot->is_inherit()) {
952 value_slot = get_inherit_value(document().realm(), property_id, element, pseudo_element);
953 return;
954 }
955
956 // https://www.w3.org/TR/css-cascade-4/#inherit-initial
957 // If the cascaded value of a property is the unset keyword,
958 if (value_slot->is_unset()) {
959 if (is_inherited_property(property_id)) {
960 // then if it is an inherited property, this is treated as inherit,
961 value_slot = get_inherit_value(document().realm(), property_id, element, pseudo_element);
962 } else {
963 // and if it is not, this is treated as initial.
964 value_slot = property_initial_value(document().realm(), property_id);
965 }
966 }
967}
968
969// https://www.w3.org/TR/css-cascade/#defaulting
970void StyleComputer::compute_defaulted_values(StyleProperties& style, DOM::Element const* element, Optional<CSS::Selector::PseudoElement> pseudo_element) const
971{
972 // Walk the list of all known CSS properties and:
973 // - Add them to `style` if they are missing.
974 // - Resolve `inherit` and `initial` as needed.
975 for (auto i = to_underlying(CSS::first_longhand_property_id); i <= to_underlying(CSS::last_longhand_property_id); ++i) {
976 auto property_id = (CSS::PropertyID)i;
977 compute_defaulted_property_value(style, element, property_id, pseudo_element);
978 }
979}
980
981CSSPixels StyleComputer::root_element_font_size() const
982{
983 constexpr float default_root_element_font_size = 16;
984
985 auto const* root_element = m_document.first_child_of_type<HTML::HTMLHtmlElement>();
986 if (!root_element)
987 return default_root_element_font_size;
988
989 auto const* computed_root_style = root_element->computed_css_values();
990 if (!computed_root_style)
991 return default_root_element_font_size;
992
993 auto root_value = computed_root_style->property(CSS::PropertyID::FontSize);
994
995 return root_value->to_length().to_px(viewport_rect(), computed_root_style->computed_font().pixel_metrics(), default_root_element_font_size, default_root_element_font_size);
996}
997
998void StyleComputer::compute_font(StyleProperties& style, DOM::Element const* element, Optional<CSS::Selector::PseudoElement> pseudo_element) const
999{
1000 // To compute the font, first ensure that we've defaulted the relevant CSS font properties.
1001 // FIXME: This should be more sophisticated.
1002 compute_defaulted_property_value(style, element, CSS::PropertyID::FontFamily, pseudo_element);
1003 compute_defaulted_property_value(style, element, CSS::PropertyID::FontSize, pseudo_element);
1004 compute_defaulted_property_value(style, element, CSS::PropertyID::FontStretch, pseudo_element);
1005 compute_defaulted_property_value(style, element, CSS::PropertyID::FontStyle, pseudo_element);
1006 compute_defaulted_property_value(style, element, CSS::PropertyID::FontWeight, pseudo_element);
1007
1008 auto* parent_element = element_to_inherit_style_from(element, pseudo_element);
1009
1010 auto font_size = style.property(CSS::PropertyID::FontSize);
1011 auto font_style = style.property(CSS::PropertyID::FontStyle);
1012 auto font_weight = style.property(CSS::PropertyID::FontWeight);
1013 auto font_stretch = style.property(CSS::PropertyID::FontStretch);
1014
1015 int width = Gfx::FontWidth::Normal;
1016 if (font_stretch->is_identifier()) {
1017 switch (static_cast<IdentifierStyleValue const&>(*font_stretch).id()) {
1018 case CSS::ValueID::UltraCondensed:
1019 width = Gfx::FontWidth::UltraCondensed;
1020 break;
1021 case CSS::ValueID::ExtraCondensed:
1022 width = Gfx::FontWidth::ExtraCondensed;
1023 break;
1024 case CSS::ValueID::Condensed:
1025 width = Gfx::FontWidth::Condensed;
1026 break;
1027 case CSS::ValueID::SemiCondensed:
1028 width = Gfx::FontWidth::SemiCondensed;
1029 break;
1030 case CSS::ValueID::Normal:
1031 width = Gfx::FontWidth::Normal;
1032 break;
1033 case CSS::ValueID::SemiExpanded:
1034 width = Gfx::FontWidth::SemiExpanded;
1035 break;
1036 case CSS::ValueID::Expanded:
1037 width = Gfx::FontWidth::Expanded;
1038 break;
1039 case CSS::ValueID::ExtraExpanded:
1040 width = Gfx::FontWidth::ExtraExpanded;
1041 break;
1042 case CSS::ValueID::UltraExpanded:
1043 width = Gfx::FontWidth::UltraExpanded;
1044 break;
1045 default:
1046 break;
1047 }
1048 } else if (font_stretch->is_percentage()) {
1049 float percentage = font_stretch->as_percentage().percentage().value();
1050 if (percentage <= 50) {
1051 width = Gfx::FontWidth::UltraCondensed;
1052 } else if (percentage <= 62.5f) {
1053 width = Gfx::FontWidth::ExtraCondensed;
1054 } else if (percentage <= 75.0f) {
1055 width = Gfx::FontWidth::Condensed;
1056 } else if (percentage <= 87.5f) {
1057 width = Gfx::FontWidth::SemiCondensed;
1058 } else if (percentage <= 100.0f) {
1059 width = Gfx::FontWidth::Normal;
1060 } else if (percentage <= 112.5f) {
1061 width = Gfx::FontWidth::SemiExpanded;
1062 } else if (percentage <= 125.0f) {
1063 width = Gfx::FontWidth::Expanded;
1064 } else if (percentage <= 150.0f) {
1065 width = Gfx::FontWidth::ExtraExpanded;
1066 } else {
1067 width = Gfx::FontWidth::UltraExpanded;
1068 }
1069 }
1070
1071 int weight = Gfx::FontWeight::Regular;
1072 if (font_weight->is_identifier()) {
1073 switch (static_cast<IdentifierStyleValue const&>(*font_weight).id()) {
1074 case CSS::ValueID::Normal:
1075 weight = Gfx::FontWeight::Regular;
1076 break;
1077 case CSS::ValueID::Bold:
1078 weight = Gfx::FontWeight::Bold;
1079 break;
1080 case CSS::ValueID::Lighter:
1081 // FIXME: This should be relative to the parent.
1082 weight = Gfx::FontWeight::Regular;
1083 break;
1084 case CSS::ValueID::Bolder:
1085 // FIXME: This should be relative to the parent.
1086 weight = Gfx::FontWeight::Bold;
1087 break;
1088 default:
1089 break;
1090 }
1091 } else if (font_weight->has_integer()) {
1092 int font_weight_integer = font_weight->to_integer();
1093 if (font_weight_integer <= Gfx::FontWeight::Regular)
1094 weight = Gfx::FontWeight::Regular;
1095 else if (font_weight_integer <= Gfx::FontWeight::Bold)
1096 weight = Gfx::FontWeight::Bold;
1097 else
1098 weight = Gfx::FontWeight::Black;
1099 } else if (font_weight->is_calculated()) {
1100 auto maybe_weight = const_cast<CalculatedStyleValue&>(font_weight->as_calculated()).resolve_integer();
1101 if (maybe_weight.has_value())
1102 weight = maybe_weight.value();
1103 }
1104
1105 bool bold = weight > Gfx::FontWeight::Regular;
1106
1107 // FIXME: Should be based on "user's default font size"
1108 float font_size_in_px = 16;
1109
1110 if (font_size->is_identifier()) {
1111 // https://w3c.github.io/csswg-drafts/css-fonts/#absolute-size-mapping
1112 AK::HashMap<Web::CSS::ValueID, float> absolute_size_mapping = {
1113 { CSS::ValueID::XxSmall, 0.6 },
1114 { CSS::ValueID::XSmall, 0.75 },
1115 { CSS::ValueID::Small, 8.0 / 9.0 },
1116 { CSS::ValueID::Medium, 1.0 },
1117 { CSS::ValueID::Large, 1.2 },
1118 { CSS::ValueID::XLarge, 1.5 },
1119 { CSS::ValueID::XxLarge, 2.0 },
1120 { CSS::ValueID::XxxLarge, 3.0 },
1121 { CSS::ValueID::Smaller, 0.8 },
1122 { CSS::ValueID::Larger, 1.25 },
1123 };
1124
1125 auto const identifier = static_cast<IdentifierStyleValue const&>(*font_size).id();
1126
1127 // https://w3c.github.io/csswg-drafts/css-fonts/#valdef-font-size-relative-size
1128 // TODO: If the parent element has a keyword font size in the absolute size keyword mapping table,
1129 // larger may compute the font size to the next entry in the table,
1130 // and smaller may compute the font size to the previous entry in the table.
1131 if (identifier == CSS::ValueID::Smaller || identifier == CSS::ValueID::Larger) {
1132 if (parent_element && parent_element->computed_css_values()) {
1133 font_size_in_px = parent_element->computed_css_values()->computed_font().pixel_metrics().size;
1134 }
1135 }
1136 auto const multiplier = absolute_size_mapping.get(identifier).value_or(1.0);
1137 font_size_in_px *= multiplier;
1138
1139 } else {
1140 auto root_font_size = root_element_font_size();
1141
1142 Gfx::FontPixelMetrics font_metrics;
1143 if (parent_element && parent_element->computed_css_values())
1144 font_metrics = parent_element->computed_css_values()->computed_font().pixel_metrics();
1145 else
1146 font_metrics = Platform::FontPlugin::the().default_font().pixel_metrics();
1147
1148 auto parent_font_size = [&]() -> CSSPixels {
1149 if (!parent_element || !parent_element->computed_css_values())
1150 return font_size_in_px;
1151 auto value = parent_element->computed_css_values()->property(CSS::PropertyID::FontSize);
1152 if (value->is_length()) {
1153 auto length = static_cast<LengthStyleValue const&>(*value).to_length();
1154 if (length.is_absolute() || length.is_relative())
1155 return length.to_px(viewport_rect(), font_metrics, font_size_in_px, root_font_size);
1156 }
1157 return font_size_in_px;
1158 };
1159
1160 Optional<Length> maybe_length;
1161 if (font_size->is_percentage()) {
1162 // Percentages refer to parent element's font size
1163 maybe_length = Length::make_px(font_size->as_percentage().percentage().as_fraction() * parent_font_size());
1164
1165 } else if (font_size->is_length()) {
1166 maybe_length = font_size->to_length();
1167
1168 } else if (font_size->is_calculated()) {
1169 maybe_length = Length::make_calculated(const_cast<CalculatedStyleValue&>(font_size->as_calculated()));
1170 }
1171 if (maybe_length.has_value()) {
1172 // FIXME: Support font-size: calc(...)
1173 // Theoretically we can do this now, but to resolve it we need a layout_node which we might not have. :^(
1174 if (!maybe_length->is_calculated()) {
1175 auto px = maybe_length.value().to_px(viewport_rect(), font_metrics, parent_font_size(), root_font_size).value();
1176 if (px != 0)
1177 font_size_in_px = px;
1178 }
1179 }
1180 }
1181
1182 int slope = Gfx::name_to_slope("Normal"sv);
1183 // FIXME: Implement oblique <angle>
1184 if (font_style->is_identifier()) {
1185 switch (static_cast<IdentifierStyleValue const&>(*font_style).id()) {
1186 case CSS::ValueID::Italic:
1187 slope = Gfx::name_to_slope("Italic"sv);
1188 break;
1189 case CSS::ValueID::Oblique:
1190 slope = Gfx::name_to_slope("Oblique"sv);
1191 break;
1192 case CSS::ValueID::Normal:
1193 default:
1194 break;
1195 }
1196 }
1197
1198 // FIXME: Implement the full font-matching algorithm: https://www.w3.org/TR/css-fonts-4/#font-matching-algorithm
1199
1200 // Note: This is modified by the find_font() lambda
1201 FontSelector font_selector;
1202 bool monospace = false;
1203
1204 auto find_font = [&](String const& family) -> RefPtr<Gfx::Font const> {
1205 float font_size_in_pt = font_size_in_px * 0.75f;
1206 font_selector = { family, font_size_in_pt, weight, width, slope };
1207
1208 if (auto it = m_loaded_fonts.find(family); it != m_loaded_fonts.end()) {
1209 auto& loader = *it->value;
1210 if (auto found_font = loader.font_with_point_size(font_size_in_pt))
1211 return found_font;
1212 }
1213
1214 if (auto found_font = FontCache::the().get(font_selector))
1215 return found_font;
1216
1217 if (auto found_font = Gfx::FontDatabase::the().get(family.to_deprecated_string(), font_size_in_pt, weight, width, slope, Gfx::Font::AllowInexactSizeMatch::Yes))
1218 return found_font;
1219
1220 return {};
1221 };
1222
1223 auto find_generic_font = [&](ValueID font_id) -> RefPtr<Gfx::Font const> {
1224 Platform::GenericFont generic_font {};
1225 switch (font_id) {
1226 case ValueID::Monospace:
1227 case ValueID::UiMonospace:
1228 generic_font = Platform::GenericFont::Monospace;
1229 monospace = true;
1230 break;
1231 case ValueID::Serif:
1232 generic_font = Platform::GenericFont::Serif;
1233 break;
1234 case ValueID::Fantasy:
1235 generic_font = Platform::GenericFont::Fantasy;
1236 break;
1237 case ValueID::SansSerif:
1238 generic_font = Platform::GenericFont::SansSerif;
1239 break;
1240 case ValueID::Cursive:
1241 generic_font = Platform::GenericFont::Cursive;
1242 break;
1243 case ValueID::UiSerif:
1244 generic_font = Platform::GenericFont::UiSerif;
1245 break;
1246 case ValueID::UiSansSerif:
1247 generic_font = Platform::GenericFont::UiSansSerif;
1248 break;
1249 case ValueID::UiRounded:
1250 generic_font = Platform::GenericFont::UiRounded;
1251 break;
1252 default:
1253 return {};
1254 }
1255 return find_font(String::from_utf8(Platform::FontPlugin::the().generic_font_name(generic_font)).release_value_but_fixme_should_propagate_errors());
1256 };
1257
1258 RefPtr<Gfx::Font const> found_font;
1259
1260 auto family_value = style.property(PropertyID::FontFamily);
1261 if (family_value->is_value_list()) {
1262 auto const& family_list = static_cast<StyleValueList const&>(*family_value).values();
1263 for (auto const& family : family_list) {
1264 if (family->is_identifier()) {
1265 found_font = find_generic_font(family->to_identifier());
1266 } else if (family->is_string()) {
1267 found_font = find_font(family->to_string().release_value_but_fixme_should_propagate_errors());
1268 }
1269 if (found_font)
1270 break;
1271 }
1272 } else if (family_value->is_identifier()) {
1273 found_font = find_generic_font(family_value->to_identifier());
1274 } else if (family_value->is_string()) {
1275 found_font = find_font(family_value->to_string().release_value_but_fixme_should_propagate_errors());
1276 }
1277
1278 if (!found_font) {
1279 found_font = StyleProperties::font_fallback(monospace, bold);
1280 }
1281
1282 FontCache::the().set(font_selector, *found_font);
1283
1284 style.set_property(CSS::PropertyID::FontSize, LengthStyleValue::create(CSS::Length::make_px(font_size_in_px)));
1285 style.set_property(CSS::PropertyID::FontWeight, NumericStyleValue::create_integer(weight));
1286
1287 style.set_computed_font(found_font.release_nonnull());
1288}
1289
1290Gfx::Font const& StyleComputer::initial_font() const
1291{
1292 // FIXME: This is not correct.
1293 return StyleProperties::font_fallback(false, false);
1294}
1295
1296void StyleComputer::absolutize_values(StyleProperties& style, DOM::Element const*, Optional<CSS::Selector::PseudoElement>) const
1297{
1298 auto font_metrics = style.computed_font().pixel_metrics();
1299 auto root_font_size = root_element_font_size();
1300 auto font_size = style.property(CSS::PropertyID::FontSize)->to_length().to_px(viewport_rect(), font_metrics, root_font_size, root_font_size);
1301
1302 // NOTE: Percentage line-height values are relative to the font-size of the element.
1303 // We have to resolve them right away, so that the *computed* line-height is ready for inheritance.
1304 // We can't simply absolutize *all* percentage values against the font size,
1305 // because most percentages are relative to containing block metrics.
1306 auto& line_height_value_slot = style.m_property_values[to_underlying(CSS::PropertyID::LineHeight)];
1307 if (line_height_value_slot && line_height_value_slot->is_percentage()) {
1308 line_height_value_slot = LengthStyleValue::create(
1309 Length::make_px(font_size * line_height_value_slot->as_percentage().percentage().as_fraction()));
1310 }
1311
1312 for (size_t i = 0; i < style.m_property_values.size(); ++i) {
1313 auto& value_slot = style.m_property_values[i];
1314 if (!value_slot)
1315 continue;
1316 value_slot = value_slot->absolutized(viewport_rect(), font_metrics, font_size.value(), root_font_size.value());
1317 }
1318}
1319
1320enum class BoxTypeTransformation {
1321 None,
1322 Blockify,
1323 Inlinify,
1324};
1325
1326static BoxTypeTransformation required_box_type_transformation(StyleProperties const& style, DOM::Element const& element, Optional<CSS::Selector::PseudoElement> const&)
1327{
1328 // Absolute positioning or floating an element blockifies the box’s display type. [CSS2]
1329 if (style.position() == CSS::Position::Absolute || style.position() == CSS::Position::Fixed || style.float_() != CSS::Float::None)
1330 return BoxTypeTransformation::Blockify;
1331
1332 // FIXME: Containment in a ruby container inlinifies the box’s display type, as described in [CSS-RUBY-1].
1333
1334 // A parent with a grid or flex display value blockifies the box’s display type. [CSS-GRID-1] [CSS-FLEXBOX-1]
1335 if (element.parent_element() && element.parent_element()->computed_css_values()) {
1336 auto const& parent_display = element.parent_element()->computed_css_values()->display();
1337 if (parent_display.is_grid_inside() || parent_display.is_flex_inside())
1338 return BoxTypeTransformation::Blockify;
1339 }
1340
1341 return BoxTypeTransformation::None;
1342}
1343
1344// https://drafts.csswg.org/css-display/#transformations
1345void StyleComputer::transform_box_type_if_needed(StyleProperties& style, DOM::Element const& element, Optional<CSS::Selector::PseudoElement> pseudo_element) const
1346{
1347 // 2.7. Automatic Box Type Transformations
1348
1349 // Some layout effects require blockification or inlinification of the box type,
1350 // which sets the box’s computed outer display type to block or inline (respectively).
1351 // (This has no effect on display types that generate no box at all, such as none or contents.)
1352
1353 // FIXME: If a block box (block flow) is inlinified, its inner display type is set to flow-root so that it remains a block container.
1354 //
1355 // FIXME: If an inline box (inline flow) is inlinified, it recursively inlinifies all of its in-flow children,
1356 // so that no block-level descendants break up the inline formatting context in which it participates.
1357 //
1358 // FIXME: For legacy reasons, if an inline block box (inline flow-root) is blockified, it becomes a block box (losing its flow-root nature).
1359 // For consistency, a run-in flow-root box also blockifies to a block box.
1360 //
1361 // FIXME: If a layout-internal box is blockified, its inner display type converts to flow so that it becomes a block container.
1362 // Inlinification has no effect on layout-internal boxes. (However, placement in such an inline context will typically cause them
1363 // to be wrapped in an appropriately-typed anonymous inline-level box.)
1364
1365 auto display = style.display();
1366 if (display.is_none() || display.is_contents())
1367 return;
1368
1369 switch (required_box_type_transformation(style, element, pseudo_element)) {
1370 case BoxTypeTransformation::None:
1371 break;
1372 case BoxTypeTransformation::Blockify:
1373 if (!display.is_block_outside()) {
1374 // FIXME: We only want to change the outer display type here, but we don't have a nice API
1375 // to do that specifically. For now, we simply check for "inline-flex" and convert
1376 // that to "flex".
1377 if (display.is_flex_inside())
1378 style.set_property(CSS::PropertyID::Display, IdentifierStyleValue::create(CSS::ValueID::Flex));
1379 else
1380 style.set_property(CSS::PropertyID::Display, IdentifierStyleValue::create(CSS::ValueID::Block));
1381 }
1382 break;
1383 case BoxTypeTransformation::Inlinify:
1384 if (!display.is_inline_outside())
1385 style.set_property(CSS::PropertyID::Display, IdentifierStyleValue::create(CSS::ValueID::Inline));
1386 break;
1387 }
1388}
1389
1390NonnullRefPtr<StyleProperties> StyleComputer::create_document_style() const
1391{
1392 auto style = StyleProperties::create();
1393 compute_font(style, nullptr, {});
1394 compute_defaulted_values(style, nullptr, {});
1395 absolutize_values(style, nullptr, {});
1396 style->set_property(CSS::PropertyID::Width, CSS::LengthStyleValue::create(CSS::Length::make_px(viewport_rect().width())));
1397 style->set_property(CSS::PropertyID::Height, CSS::LengthStyleValue::create(CSS::Length::make_px(viewport_rect().height())));
1398 style->set_property(CSS::PropertyID::Display, CSS::IdentifierStyleValue::create(CSS::ValueID::Block));
1399 return style;
1400}
1401
1402ErrorOr<NonnullRefPtr<StyleProperties>> StyleComputer::compute_style(DOM::Element& element, Optional<CSS::Selector::PseudoElement> pseudo_element) const
1403{
1404 build_rule_cache_if_needed();
1405
1406 auto style = StyleProperties::create();
1407 // 1. Perform the cascade. This produces the "specified style"
1408 TRY(compute_cascaded_values(style, element, pseudo_element));
1409
1410 // 2. Compute the font, since that may be needed for font-relative CSS units
1411 compute_font(style, &element, pseudo_element);
1412
1413 // 3. Absolutize values, turning font/viewport relative lengths into absolute lengths
1414 absolutize_values(style, &element, pseudo_element);
1415
1416 // 4. Default the values, applying inheritance and 'initial' as needed
1417 compute_defaulted_values(style, &element, pseudo_element);
1418
1419 // 5. Run automatic box type transformations
1420 transform_box_type_if_needed(style, element, pseudo_element);
1421
1422 return style;
1423}
1424
1425PropertyDependencyNode::PropertyDependencyNode(String name)
1426 : m_name(move(name))
1427{
1428}
1429
1430void PropertyDependencyNode::add_child(NonnullRefPtr<PropertyDependencyNode> new_child)
1431{
1432 for (auto const& child : m_children) {
1433 if (child->m_name == new_child->m_name)
1434 return;
1435 }
1436
1437 // We detect self-reference already.
1438 VERIFY(new_child->m_name != m_name);
1439 m_children.append(move(new_child));
1440}
1441
1442bool PropertyDependencyNode::has_cycles()
1443{
1444 if (m_marked)
1445 return true;
1446
1447 TemporaryChange change { m_marked, true };
1448 for (auto& child : m_children) {
1449 if (child->has_cycles())
1450 return true;
1451 }
1452 return false;
1453}
1454
1455void StyleComputer::build_rule_cache_if_needed() const
1456{
1457 if (m_author_rule_cache && m_user_agent_rule_cache)
1458 return;
1459 const_cast<StyleComputer&>(*this).build_rule_cache();
1460}
1461
1462NonnullOwnPtr<StyleComputer::RuleCache> StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin)
1463{
1464 auto rule_cache = make<RuleCache>();
1465
1466 size_t num_class_rules = 0;
1467 size_t num_id_rules = 0;
1468 size_t num_tag_name_rules = 0;
1469 size_t num_pseudo_element_rules = 0;
1470
1471 Vector<MatchingRule> matching_rules;
1472 size_t style_sheet_index = 0;
1473 for_each_stylesheet(cascade_origin, [&](auto& sheet) {
1474 size_t rule_index = 0;
1475 sheet.for_each_effective_style_rule([&](auto const& rule) {
1476 size_t selector_index = 0;
1477 for (CSS::Selector const& selector : rule.selectors()) {
1478 MatchingRule matching_rule {
1479 &rule,
1480 style_sheet_index,
1481 rule_index,
1482 selector_index,
1483 selector.specificity(),
1484 };
1485
1486 for (auto const& simple_selector : selector.compound_selectors().last().simple_selectors) {
1487 if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoElement) {
1488 matching_rule.contains_pseudo_element = true;
1489 ++num_pseudo_element_rules;
1490 break;
1491 }
1492 }
1493
1494 bool added_to_bucket = false;
1495 for (auto const& simple_selector : selector.compound_selectors().last().simple_selectors) {
1496 if (simple_selector.type == CSS::Selector::SimpleSelector::Type::Id) {
1497 rule_cache->rules_by_id.ensure(simple_selector.name()).append(move(matching_rule));
1498 ++num_id_rules;
1499 added_to_bucket = true;
1500 break;
1501 }
1502 if (simple_selector.type == CSS::Selector::SimpleSelector::Type::Class) {
1503 rule_cache->rules_by_class.ensure(simple_selector.name()).append(move(matching_rule));
1504 ++num_class_rules;
1505 added_to_bucket = true;
1506 break;
1507 }
1508 if (simple_selector.type == CSS::Selector::SimpleSelector::Type::TagName) {
1509 rule_cache->rules_by_tag_name.ensure(simple_selector.name()).append(move(matching_rule));
1510 ++num_tag_name_rules;
1511 added_to_bucket = true;
1512 break;
1513 }
1514 }
1515 if (!added_to_bucket)
1516 rule_cache->other_rules.append(move(matching_rule));
1517
1518 ++selector_index;
1519 }
1520 ++rule_index;
1521 });
1522 ++style_sheet_index;
1523 });
1524
1525 if constexpr (LIBWEB_CSS_DEBUG) {
1526 dbgln("Built rule cache!");
1527 dbgln(" ID: {}", num_id_rules);
1528 dbgln(" Class: {}", num_class_rules);
1529 dbgln(" TagName: {}", num_tag_name_rules);
1530 dbgln("PseudoElement: {}", num_pseudo_element_rules);
1531 dbgln(" Other: {}", rule_cache->other_rules.size());
1532 dbgln(" Total: {}", num_class_rules + num_id_rules + num_tag_name_rules + rule_cache->other_rules.size());
1533 }
1534 return rule_cache;
1535}
1536
1537void StyleComputer::build_rule_cache()
1538{
1539 m_author_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::Author);
1540 m_user_agent_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::UserAgent);
1541}
1542
1543void StyleComputer::invalidate_rule_cache()
1544{
1545 m_author_rule_cache = nullptr;
1546
1547 // NOTE: It might not be necessary to throw away the UA rule cache.
1548 // If we are sure that it's safe, we could keep it as an optimization.
1549 m_user_agent_rule_cache = nullptr;
1550}
1551
1552CSSPixelRect StyleComputer::viewport_rect() const
1553{
1554 if (auto const* browsing_context = document().browsing_context())
1555 return browsing_context->viewport_rect();
1556 return {};
1557}
1558
1559void StyleComputer::did_load_font([[maybe_unused]] FlyString const& family_name)
1560{
1561 document().invalidate_layout();
1562}
1563
1564void StyleComputer::load_fonts_from_sheet(CSSStyleSheet const& sheet)
1565{
1566 for (auto const& rule : static_cast<CSSStyleSheet const&>(sheet).rules()) {
1567 if (!is<CSSFontFaceRule>(rule))
1568 continue;
1569 auto const& font_face = static_cast<CSSFontFaceRule const&>(rule).font_face();
1570 if (font_face.sources().is_empty())
1571 continue;
1572 if (m_loaded_fonts.contains(font_face.font_family()))
1573 continue;
1574
1575 // NOTE: This is rather ad-hoc, we just look for the first valid
1576 // source URL that's either a WOFF or OpenType file and try loading that.
1577 // FIXME: Find out exactly which resources we need to load and how.
1578 Optional<AK::URL> candidate_url;
1579 for (auto& source : font_face.sources()) {
1580 if (!source.url.is_valid())
1581 continue;
1582
1583 if (source.url.scheme() != "data") {
1584 auto path = source.url.path();
1585 if (!path.ends_with(".woff"sv, AK::CaseSensitivity::CaseInsensitive)
1586 && !path.ends_with(".ttf"sv, AK::CaseSensitivity::CaseInsensitive)) {
1587 continue;
1588 }
1589 }
1590
1591 candidate_url = source.url;
1592 break;
1593 }
1594
1595 if (!candidate_url.has_value())
1596 continue;
1597
1598 LoadRequest request;
1599 auto url = m_document.parse_url(candidate_url.value().to_deprecated_string());
1600 auto loader = make<FontLoader>(const_cast<StyleComputer&>(*this), font_face.font_family(), move(url));
1601 const_cast<StyleComputer&>(*this).m_loaded_fonts.set(font_face.font_family().to_string(), move(loader));
1602 }
1603}
1604
1605}