Serenity Operating System
1/*
2 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <AK/TypeCasts.h>
9#include <LibCore/DirIterator.h>
10#include <LibWeb/CSS/Clip.h>
11#include <LibWeb/CSS/StyleProperties.h>
12#include <LibWeb/FontCache.h>
13#include <LibWeb/Layout/BlockContainer.h>
14#include <LibWeb/Layout/Node.h>
15#include <LibWeb/Platform/FontPlugin.h>
16
17namespace Web::CSS {
18
19StyleProperties::StyleProperties(StyleProperties const& other)
20 : m_property_values(other.m_property_values)
21{
22 if (other.m_font) {
23 m_font = other.m_font->clone();
24 } else {
25 m_font = nullptr;
26 }
27}
28
29NonnullRefPtr<StyleProperties> StyleProperties::clone() const
30{
31 return adopt_ref(*new StyleProperties(*this));
32}
33
34void StyleProperties::set_property(CSS::PropertyID id, NonnullRefPtr<StyleValue const> value)
35{
36 m_property_values[to_underlying(id)] = move(value);
37}
38
39NonnullRefPtr<StyleValue const> StyleProperties::property(CSS::PropertyID property_id) const
40{
41 auto value = m_property_values[to_underlying(property_id)];
42 // By the time we call this method, all properties have values assigned.
43 VERIFY(!value.is_null());
44 return value.release_nonnull();
45}
46
47RefPtr<StyleValue const> StyleProperties::maybe_null_property(CSS::PropertyID property_id) const
48{
49 return m_property_values[to_underlying(property_id)];
50}
51
52CSS::Size StyleProperties::size_value(CSS::PropertyID id) const
53{
54 auto value = property(id);
55 if (value->is_identifier()) {
56 switch (value->to_identifier()) {
57 case ValueID::Auto:
58 return CSS::Size::make_auto();
59 case ValueID::MinContent:
60 return CSS::Size::make_min_content();
61 case ValueID::MaxContent:
62 return CSS::Size::make_max_content();
63 case ValueID::None:
64 return CSS::Size::make_none();
65 default:
66 VERIFY_NOT_REACHED();
67 }
68 }
69
70 if (value->is_calculated())
71 return CSS::Size::make_length(CSS::Length::make_calculated(const_cast<CalculatedStyleValue&>(value->as_calculated())));
72
73 if (value->is_percentage())
74 return CSS::Size::make_percentage(value->as_percentage().percentage());
75
76 if (value->has_length()) {
77 auto length = value->to_length();
78 if (length.is_auto())
79 return CSS::Size::make_auto();
80 return CSS::Size::make_length(value->to_length());
81 }
82
83 // FIXME: Support `fit-content(<length>)`
84 dbgln("FIXME: Unsupported size value: `{}`, treating as `auto`", value->to_string());
85 return CSS::Size::make_auto();
86}
87
88LengthPercentage StyleProperties::length_percentage_or_fallback(CSS::PropertyID id, LengthPercentage const& fallback) const
89{
90 return length_percentage(id).value_or(fallback);
91}
92
93Optional<LengthPercentage> StyleProperties::length_percentage(CSS::PropertyID id) const
94{
95 auto value = property(id);
96
97 if (value->is_calculated())
98 return LengthPercentage { const_cast<CalculatedStyleValue&>(value->as_calculated()) };
99
100 if (value->is_percentage())
101 return value->as_percentage().percentage();
102
103 if (value->has_length())
104 return value->to_length();
105
106 return {};
107}
108
109LengthBox StyleProperties::length_box(CSS::PropertyID left_id, CSS::PropertyID top_id, CSS::PropertyID right_id, CSS::PropertyID bottom_id, const CSS::Length& default_value) const
110{
111 LengthBox box;
112 box.left() = length_percentage_or_fallback(left_id, default_value);
113 box.top() = length_percentage_or_fallback(top_id, default_value);
114 box.right() = length_percentage_or_fallback(right_id, default_value);
115 box.bottom() = length_percentage_or_fallback(bottom_id, default_value);
116 return box;
117}
118
119Color StyleProperties::color_or_fallback(CSS::PropertyID id, Layout::NodeWithStyle const& node, Color fallback) const
120{
121 auto value = property(id);
122 if (!value->has_color())
123 return fallback;
124 return value->to_color(node);
125}
126
127NonnullRefPtr<Gfx::Font const> StyleProperties::font_fallback(bool monospace, bool bold)
128{
129 if (monospace && bold)
130 return Platform::FontPlugin::the().default_fixed_width_font().bold_variant();
131
132 if (monospace)
133 return Platform::FontPlugin::the().default_fixed_width_font();
134
135 if (bold)
136 return Platform::FontPlugin::the().default_font().bold_variant();
137
138 return Platform::FontPlugin::the().default_font();
139}
140
141CSSPixels StyleProperties::line_height(Layout::Node const& layout_node) const
142{
143 auto line_height = property(CSS::PropertyID::LineHeight);
144
145 if (line_height->is_identifier() && line_height->to_identifier() == ValueID::Normal)
146 return layout_node.font().pixel_metrics().line_spacing();
147
148 if (line_height->is_length()) {
149 auto line_height_length = line_height->to_length();
150 if (!line_height_length.is_auto())
151 return line_height_length.to_px(layout_node);
152 }
153
154 if (line_height->is_numeric())
155 return Length(line_height->to_number(), Length::Type::Em).to_px(layout_node);
156
157 if (line_height->is_percentage()) {
158 // Percentages are relative to 1em. https://www.w3.org/TR/css-inline-3/#valdef-line-height-percentage
159 auto& percentage = line_height->as_percentage().percentage();
160 return Length(percentage.as_fraction(), Length::Type::Em).to_px(layout_node);
161 }
162
163 if (line_height->is_calculated())
164 return CSS::Length::make_calculated(const_cast<CalculatedStyleValue&>(line_height->as_calculated())).to_px(layout_node);
165
166 return layout_node.font().pixel_metrics().line_spacing();
167}
168
169Optional<int> StyleProperties::z_index() const
170{
171 auto value = property(CSS::PropertyID::ZIndex);
172 if (value->has_auto())
173 return {};
174 if (value->has_integer())
175 return value->to_integer();
176 return {};
177}
178
179float StyleProperties::opacity() const
180{
181 auto value = property(CSS::PropertyID::Opacity);
182
183 float unclamped_opacity = 1.0f;
184
185 if (value->has_number()) {
186 unclamped_opacity = value->to_number();
187 } else if (value->is_calculated()) {
188 auto& calculated = value->as_calculated();
189 if (calculated.resolved_type() == CalculatedStyleValue::ResolvedType::Percentage) {
190 auto maybe_percentage = value->as_calculated().resolve_percentage();
191 if (maybe_percentage.has_value())
192 unclamped_opacity = maybe_percentage->as_fraction();
193 else
194 dbgln("Unable to resolve calc() as opacity (percentage): {}", value->to_string());
195 } else {
196 auto maybe_number = const_cast<CalculatedStyleValue&>(value->as_calculated()).resolve_number();
197 if (maybe_number.has_value())
198 unclamped_opacity = maybe_number.value();
199 else
200 dbgln("Unable to resolve calc() as opacity (number): {}", value->to_string());
201 }
202 } else if (value->is_percentage()) {
203 unclamped_opacity = value->as_percentage().percentage().as_fraction();
204 }
205
206 return clamp(unclamped_opacity, 0.0f, 1.0f);
207}
208
209Optional<CSS::FlexDirection> StyleProperties::flex_direction() const
210{
211 auto value = property(CSS::PropertyID::FlexDirection);
212 return value_id_to_flex_direction(value->to_identifier());
213}
214
215Optional<CSS::FlexWrap> StyleProperties::flex_wrap() const
216{
217 auto value = property(CSS::PropertyID::FlexWrap);
218 return value_id_to_flex_wrap(value->to_identifier());
219}
220
221Optional<CSS::FlexBasisData> StyleProperties::flex_basis() const
222{
223 auto value = property(CSS::PropertyID::FlexBasis);
224
225 if (value->is_identifier() && value->to_identifier() == CSS::ValueID::Content)
226 return { { CSS::FlexBasis::Content, {} } };
227
228 if (value->has_auto())
229 return { { CSS::FlexBasis::Auto, {} } };
230
231 if (value->is_percentage())
232 return { { CSS::FlexBasis::LengthPercentage, value->as_percentage().percentage() } };
233
234 if (value->has_length())
235 return { { CSS::FlexBasis::LengthPercentage, value->to_length() } };
236
237 return {};
238}
239
240float StyleProperties::flex_grow() const
241{
242 auto value = property(CSS::PropertyID::FlexGrow);
243 if (!value->has_number())
244 return 0;
245 return value->to_number();
246}
247
248float StyleProperties::flex_shrink() const
249{
250 auto value = property(CSS::PropertyID::FlexShrink);
251 if (!value->has_number())
252 return 1;
253 return value->to_number();
254}
255
256int StyleProperties::order() const
257{
258 auto value = property(CSS::PropertyID::Order);
259 if (!value->has_integer())
260 return 0;
261 return value->to_integer();
262}
263
264Optional<CSS::ImageRendering> StyleProperties::image_rendering() const
265{
266 auto value = property(CSS::PropertyID::ImageRendering);
267 return value_id_to_image_rendering(value->to_identifier());
268}
269
270CSS::Clip StyleProperties::clip() const
271{
272 auto value = property(CSS::PropertyID::Clip);
273 if (!value->has_rect())
274 return CSS::Clip::make_auto();
275 return CSS::Clip(value->as_rect().rect());
276}
277
278Optional<CSS::JustifyContent> StyleProperties::justify_content() const
279{
280 auto value = property(CSS::PropertyID::JustifyContent);
281 return value_id_to_justify_content(value->to_identifier());
282}
283
284Vector<CSS::Transformation> StyleProperties::transformations() const
285{
286 auto value = property(CSS::PropertyID::Transform);
287
288 if (value->is_identifier() && value->to_identifier() == CSS::ValueID::None)
289 return {};
290
291 if (!value->is_value_list())
292 return {};
293
294 auto& list = value->as_value_list();
295
296 Vector<CSS::Transformation> transformations;
297
298 for (auto& it : list.values()) {
299 if (!it->is_transformation())
300 return {};
301 auto& transformation_style_value = it->as_transformation();
302 CSS::Transformation transformation;
303 transformation.function = transformation_style_value.transform_function();
304 Vector<TransformValue> values;
305 for (auto& transformation_value : transformation_style_value.values()) {
306 if (transformation_value->is_length()) {
307 values.append({ transformation_value->to_length() });
308 } else if (transformation_value->is_percentage()) {
309 values.append({ transformation_value->as_percentage().percentage() });
310 } else if (transformation_value->is_numeric()) {
311 values.append({ transformation_value->to_number() });
312 } else if (transformation_value->is_angle()) {
313 values.append({ transformation_value->as_angle().angle() });
314 } else {
315 dbgln("FIXME: Unsupported value in transform!");
316 }
317 }
318 transformation.values = move(values);
319 transformations.append(move(transformation));
320 }
321 return transformations;
322}
323
324static Optional<LengthPercentage> length_percentage_for_style_value(StyleValue const& value)
325{
326 if (value.is_length())
327 return value.to_length();
328 if (value.is_percentage())
329 return value.as_percentage().percentage();
330 return {};
331}
332
333CSS::TransformOrigin StyleProperties::transform_origin() const
334{
335 auto value = property(CSS::PropertyID::TransformOrigin);
336 if (!value->is_value_list() || value->as_value_list().size() != 2)
337 return {};
338 auto const& list = value->as_value_list();
339 auto x_value = length_percentage_for_style_value(list.values()[0]);
340 auto y_value = length_percentage_for_style_value(list.values()[1]);
341 if (!x_value.has_value() || !y_value.has_value()) {
342 return {};
343 }
344 return { x_value.value(), y_value.value() };
345}
346
347Optional<CSS::AlignContent> StyleProperties::align_content() const
348{
349 auto value = property(CSS::PropertyID::AlignContent);
350 return value_id_to_align_content(value->to_identifier());
351}
352
353Optional<CSS::AlignItems> StyleProperties::align_items() const
354{
355 auto value = property(CSS::PropertyID::AlignItems);
356 return value_id_to_align_items(value->to_identifier());
357}
358
359Optional<CSS::AlignSelf> StyleProperties::align_self() const
360{
361 auto value = property(CSS::PropertyID::AlignSelf);
362 return value_id_to_align_self(value->to_identifier());
363}
364
365Optional<CSS::Appearance> StyleProperties::appearance() const
366{
367 auto value = property(CSS::PropertyID::Appearance);
368 auto appearance = value_id_to_appearance(value->to_identifier());
369 if (appearance.has_value()) {
370 switch (*appearance) {
371 // Note: All these compatibility values can be treated as 'auto'
372 case CSS::Appearance::Textfield:
373 case CSS::Appearance::MenulistButton:
374 case CSS::Appearance::Searchfield:
375 case CSS::Appearance::Textarea:
376 case CSS::Appearance::PushButton:
377 case CSS::Appearance::SliderHorizontal:
378 case CSS::Appearance::Checkbox:
379 case CSS::Appearance::Radio:
380 case CSS::Appearance::SquareButton:
381 case CSS::Appearance::Menulist:
382 case CSS::Appearance::Listbox:
383 case CSS::Appearance::Meter:
384 case CSS::Appearance::ProgressBar:
385 case CSS::Appearance::Button:
386 appearance = CSS::Appearance::Auto;
387 break;
388 default:
389 break;
390 }
391 }
392 return appearance;
393}
394
395CSS::BackdropFilter StyleProperties::backdrop_filter() const
396{
397 auto value = property(CSS::PropertyID::BackdropFilter);
398 if (value->is_filter_value_list())
399 return BackdropFilter(value->as_filter_value_list());
400 return BackdropFilter::make_none();
401}
402
403Optional<CSS::Position> StyleProperties::position() const
404{
405 auto value = property(CSS::PropertyID::Position);
406 return value_id_to_position(value->to_identifier());
407}
408
409bool StyleProperties::operator==(StyleProperties const& other) const
410{
411 if (m_property_values.size() != other.m_property_values.size())
412 return false;
413
414 for (size_t i = 0; i < m_property_values.size(); ++i) {
415 auto const& my_ptr = m_property_values[i];
416 auto const& other_ptr = other.m_property_values[i];
417 if (!my_ptr) {
418 if (other_ptr)
419 return false;
420 continue;
421 }
422 if (!other_ptr)
423 return false;
424 auto const& my_value = *my_ptr;
425 auto const& other_value = *other_ptr;
426 if (my_value.type() != other_value.type())
427 return false;
428 if (my_value != other_value)
429 return false;
430 }
431
432 return true;
433}
434
435Optional<CSS::TextAlign> StyleProperties::text_align() const
436{
437 auto value = property(CSS::PropertyID::TextAlign);
438 return value_id_to_text_align(value->to_identifier());
439}
440
441Optional<CSS::TextJustify> StyleProperties::text_justify() const
442{
443 auto value = property(CSS::PropertyID::TextJustify);
444 return value_id_to_text_justify(value->to_identifier());
445}
446
447Optional<CSS::PointerEvents> StyleProperties::pointer_events() const
448{
449 auto value = property(CSS::PropertyID::PointerEvents);
450 return value_id_to_pointer_events(value->to_identifier());
451}
452
453Optional<CSS::WhiteSpace> StyleProperties::white_space() const
454{
455 auto value = property(CSS::PropertyID::WhiteSpace);
456 return value_id_to_white_space(value->to_identifier());
457}
458
459Optional<CSS::LineStyle> StyleProperties::line_style(CSS::PropertyID property_id) const
460{
461 auto value = property(property_id);
462 return value_id_to_line_style(value->to_identifier());
463}
464
465Optional<CSS::Float> StyleProperties::float_() const
466{
467 auto value = property(CSS::PropertyID::Float);
468 return value_id_to_float(value->to_identifier());
469}
470
471Optional<CSS::Clear> StyleProperties::clear() const
472{
473 auto value = property(CSS::PropertyID::Clear);
474 return value_id_to_clear(value->to_identifier());
475}
476
477CSS::ContentData StyleProperties::content() const
478{
479 auto value = property(CSS::PropertyID::Content);
480 if (value->is_content()) {
481 auto& content_style_value = value->as_content();
482
483 CSS::ContentData content_data;
484
485 // FIXME: The content is a list of things: strings, identifiers or functions that return strings, and images.
486 // So it can't always be represented as a single String, but may have to be multiple boxes.
487 // For now, we'll just assume strings since that is easiest.
488 StringBuilder builder;
489 for (auto const& item : content_style_value.content().values()) {
490 if (item->is_string()) {
491 builder.append(item->to_string().release_value_but_fixme_should_propagate_errors());
492 } else {
493 // TODO: Implement quotes, counters, images, and other things.
494 }
495 }
496 content_data.type = ContentData::Type::String;
497 content_data.data = builder.to_string().release_value_but_fixme_should_propagate_errors();
498
499 if (content_style_value.has_alt_text()) {
500 StringBuilder alt_text_builder;
501 for (auto const& item : content_style_value.alt_text()->values()) {
502 if (item->is_string()) {
503 alt_text_builder.append(item->to_string().release_value_but_fixme_should_propagate_errors());
504 } else {
505 // TODO: Implement counters
506 }
507 }
508 content_data.alt_text = alt_text_builder.to_string().release_value_but_fixme_should_propagate_errors();
509 }
510
511 return content_data;
512 }
513
514 switch (value->to_identifier()) {
515 case ValueID::None:
516 return { ContentData::Type::None };
517 case ValueID::Normal:
518 return { ContentData::Type::Normal };
519 default:
520 break;
521 }
522
523 return CSS::ContentData {};
524}
525
526Optional<CSS::Cursor> StyleProperties::cursor() const
527{
528 auto value = property(CSS::PropertyID::Cursor);
529 return value_id_to_cursor(value->to_identifier());
530}
531
532Optional<CSS::Visibility> StyleProperties::visibility() const
533{
534 auto value = property(CSS::PropertyID::Visibility);
535 if (!value->is_identifier())
536 return {};
537 return value_id_to_visibility(value->to_identifier());
538}
539
540CSS::Display StyleProperties::display() const
541{
542 auto value = property(CSS::PropertyID::Display);
543 if (!value->is_identifier())
544 return CSS::Display::from_short(CSS::Display::Short::Inline);
545 switch (value->to_identifier()) {
546 case CSS::ValueID::None:
547 return CSS::Display::from_short(CSS::Display::Short::None);
548 case CSS::ValueID::Block:
549 return CSS::Display::from_short(CSS::Display::Short::Block);
550 case CSS::ValueID::Inline:
551 return CSS::Display::from_short(CSS::Display::Short::Inline);
552 case CSS::ValueID::InlineBlock:
553 return CSS::Display::from_short(CSS::Display::Short::InlineBlock);
554 case CSS::ValueID::ListItem:
555 return CSS::Display::from_short(CSS::Display::Short::ListItem);
556 case CSS::ValueID::Table:
557 return CSS::Display::from_short(CSS::Display::Short::Table);
558 case CSS::ValueID::InlineTable:
559 return CSS::Display::from_short(CSS::Display::Short::InlineTable);
560 case CSS::ValueID::TableRow:
561 return CSS::Display { CSS::Display::Internal::TableRow };
562 case CSS::ValueID::TableCell:
563 return CSS::Display { CSS::Display::Internal::TableCell };
564 case CSS::ValueID::TableColumn:
565 return CSS::Display { CSS::Display::Internal::TableColumn };
566 case CSS::ValueID::TableColumnGroup:
567 return CSS::Display { CSS::Display::Internal::TableColumnGroup };
568 case CSS::ValueID::TableCaption:
569 return CSS::Display { CSS::Display::Internal::TableCaption };
570 case CSS::ValueID::TableRowGroup:
571 return CSS::Display { CSS::Display::Internal::TableRowGroup };
572 case CSS::ValueID::TableHeaderGroup:
573 return CSS::Display { CSS::Display::Internal::TableHeaderGroup };
574 case CSS::ValueID::TableFooterGroup:
575 return CSS::Display { CSS::Display::Internal::TableFooterGroup };
576 case CSS::ValueID::Flex:
577 return CSS::Display::from_short(CSS::Display::Short::Flex);
578 case CSS::ValueID::InlineFlex:
579 return CSS::Display::from_short(CSS::Display::Short::InlineFlex);
580 case CSS::ValueID::Grid:
581 return CSS::Display::from_short(CSS::Display::Short::Grid);
582 default:
583 return CSS::Display::from_short(CSS::Display::Short::Block);
584 }
585}
586
587Vector<CSS::TextDecorationLine> StyleProperties::text_decoration_line() const
588{
589 auto value = property(CSS::PropertyID::TextDecorationLine);
590
591 if (value->is_value_list()) {
592 Vector<CSS::TextDecorationLine> lines;
593 auto& values = value->as_value_list().values();
594 for (auto const& item : values) {
595 lines.append(value_id_to_text_decoration_line(item->to_identifier()).value());
596 }
597 return lines;
598 }
599
600 if (value->is_identifier() && value->to_identifier() == ValueID::None)
601 return {};
602
603 dbgln("FIXME: Unsupported value for text-decoration-line: {}", value->to_string());
604 return {};
605}
606
607Optional<CSS::TextDecorationStyle> StyleProperties::text_decoration_style() const
608{
609 auto value = property(CSS::PropertyID::TextDecorationStyle);
610 return value_id_to_text_decoration_style(value->to_identifier());
611}
612
613Optional<CSS::TextTransform> StyleProperties::text_transform() const
614{
615 auto value = property(CSS::PropertyID::TextTransform);
616 return value_id_to_text_transform(value->to_identifier());
617}
618
619Optional<CSS::ListStyleType> StyleProperties::list_style_type() const
620{
621 auto value = property(CSS::PropertyID::ListStyleType);
622 return value_id_to_list_style_type(value->to_identifier());
623}
624
625Optional<CSS::Overflow> StyleProperties::overflow_x() const
626{
627 return overflow(CSS::PropertyID::OverflowX);
628}
629
630Optional<CSS::Overflow> StyleProperties::overflow_y() const
631{
632 return overflow(CSS::PropertyID::OverflowY);
633}
634
635Optional<CSS::Overflow> StyleProperties::overflow(CSS::PropertyID property_id) const
636{
637 auto value = property(property_id);
638 return value_id_to_overflow(value->to_identifier());
639}
640
641Vector<ShadowData> StyleProperties::shadow(PropertyID property_id) const
642{
643 auto value = property(property_id);
644
645 auto make_shadow_data = [](ShadowStyleValue const& value) {
646 return ShadowData { value.color(), value.offset_x(), value.offset_y(), value.blur_radius(), value.spread_distance(), value.placement() };
647 };
648
649 if (value->is_value_list()) {
650 auto& value_list = value->as_value_list();
651
652 Vector<ShadowData> shadow_data;
653 shadow_data.ensure_capacity(value_list.size());
654 for (auto const& layer_value : value_list.values())
655 shadow_data.append(make_shadow_data(layer_value->as_shadow()));
656
657 return shadow_data;
658 }
659
660 if (value->is_shadow()) {
661 auto& box = value->as_shadow();
662 return { make_shadow_data(box) };
663 }
664
665 return {};
666}
667
668Vector<ShadowData> StyleProperties::box_shadow() const
669{
670 return shadow(PropertyID::BoxShadow);
671}
672
673Vector<ShadowData> StyleProperties::text_shadow() const
674{
675 return shadow(PropertyID::TextShadow);
676}
677
678Optional<CSS::BoxSizing> StyleProperties::box_sizing() const
679{
680 auto value = property(CSS::PropertyID::BoxSizing);
681 return value_id_to_box_sizing(value->to_identifier());
682}
683
684Variant<CSS::VerticalAlign, CSS::LengthPercentage> StyleProperties::vertical_align() const
685{
686 auto value = property(CSS::PropertyID::VerticalAlign);
687
688 if (value->is_identifier())
689 return value_id_to_vertical_align(value->to_identifier()).release_value();
690
691 if (value->is_length())
692 return CSS::LengthPercentage(value->to_length());
693
694 if (value->is_percentage())
695 return CSS::LengthPercentage(value->as_percentage().percentage());
696
697 VERIFY_NOT_REACHED();
698}
699
700Optional<CSS::FontVariant> StyleProperties::font_variant() const
701{
702 auto value = property(CSS::PropertyID::FontVariant);
703 return value_id_to_font_variant(value->to_identifier());
704}
705
706CSS::GridTrackSizeList StyleProperties::grid_template_columns() const
707{
708 auto value = property(CSS::PropertyID::GridTemplateColumns);
709 return value->as_grid_track_size_list().grid_track_size_list();
710}
711
712CSS::GridTrackSizeList StyleProperties::grid_template_rows() const
713{
714 auto value = property(CSS::PropertyID::GridTemplateRows);
715 return value->as_grid_track_size_list().grid_track_size_list();
716}
717
718CSS::GridTrackPlacement StyleProperties::grid_column_end() const
719{
720 auto value = property(CSS::PropertyID::GridColumnEnd);
721 return value->as_grid_track_placement().grid_track_placement();
722}
723
724CSS::GridTrackPlacement StyleProperties::grid_column_start() const
725{
726 auto value = property(CSS::PropertyID::GridColumnStart);
727 return value->as_grid_track_placement().grid_track_placement();
728}
729
730CSS::GridTrackPlacement StyleProperties::grid_row_end() const
731{
732 auto value = property(CSS::PropertyID::GridRowEnd);
733 return value->as_grid_track_placement().grid_track_placement();
734}
735
736CSS::GridTrackPlacement StyleProperties::grid_row_start() const
737{
738 auto value = property(CSS::PropertyID::GridRowStart);
739 return value->as_grid_track_placement().grid_track_placement();
740}
741
742Optional<CSS::BorderCollapse> StyleProperties::border_collapse() const
743{
744 auto value = property(CSS::PropertyID::BorderCollapse);
745 return value_id_to_border_collapse(value->to_identifier());
746}
747
748Vector<Vector<String>> StyleProperties::grid_template_areas() const
749{
750 auto value = property(CSS::PropertyID::GridTemplateAreas);
751 return value->as_grid_template_area().grid_template_area();
752}
753
754String StyleProperties::grid_area() const
755{
756 auto value = property(CSS::PropertyID::GridArea);
757 return value->as_string().to_string().release_value_but_fixme_should_propagate_errors();
758}
759
760}