ladybird/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp
Andreas Kling 89ba7246dd LibWeb: Don't resolve CSS property values for unconnected elements
While it's possible to getComputedStyle() on an unconnected element,
the resulting object is not supposed to have any values, since we can't
resolve style without a document root anyway.

This fixes a crash on https://bandcamp.com
2023-05-10 17:27:07 +02:00

742 lines
40 KiB
C++

/*
* Copyright (c) 2021-2023, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/Format.h>
#include <AK/NonnullRefPtr.h>
#include <LibWeb/CSS/Enums.h>
#include <LibWeb/CSS/ResolvedCSSStyleDeclaration.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/CSS/StyleValues/BackgroundRepeatStyleValue.h>
#include <LibWeb/CSS/StyleValues/BackgroundStyleValue.h>
#include <LibWeb/CSS/StyleValues/BorderRadiusShorthandStyleValue.h>
#include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h>
#include <LibWeb/CSS/StyleValues/BorderStyleValue.h>
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
#include <LibWeb/CSS/StyleValues/ColorStyleValue.h>
#include <LibWeb/CSS/StyleValues/EdgeStyleValue.h>
#include <LibWeb/CSS/StyleValues/GridAreaShorthandStyleValue.h>
#include <LibWeb/CSS/StyleValues/GridTrackPlacementShorthandStyleValue.h>
#include <LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h>
#include <LibWeb/CSS/StyleValues/GridTrackSizeListShorthandStyleValue.h>
#include <LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h>
#include <LibWeb/CSS/StyleValues/IdentifierStyleValue.h>
#include <LibWeb/CSS/StyleValues/InitialStyleValue.h>
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
#include <LibWeb/CSS/StyleValues/NumericStyleValue.h>
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
#include <LibWeb/CSS/StyleValues/RectStyleValue.h>
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Painting/StackingContext.h>
namespace Web::CSS {
WebIDL::ExceptionOr<JS::NonnullGCPtr<ResolvedCSSStyleDeclaration>> ResolvedCSSStyleDeclaration::create(DOM::Element& element)
{
return MUST_OR_THROW_OOM(element.realm().heap().allocate<ResolvedCSSStyleDeclaration>(element.realm(), element));
}
ResolvedCSSStyleDeclaration::ResolvedCSSStyleDeclaration(DOM::Element& element)
: CSSStyleDeclaration(element.realm())
, m_element(element)
{
}
void ResolvedCSSStyleDeclaration::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_element.ptr());
}
size_t ResolvedCSSStyleDeclaration::length() const
{
return 0;
}
DeprecatedString ResolvedCSSStyleDeclaration::item(size_t index) const
{
(void)index;
return {};
}
static ErrorOr<NonnullRefPtr<StyleValue const>> style_value_for_background_property(Layout::NodeWithStyle const& layout_node, Function<ErrorOr<NonnullRefPtr<StyleValue const>>(BackgroundLayerData const&)> callback, Function<ErrorOr<NonnullRefPtr<StyleValue const>>()> default_value)
{
auto const& background_layers = layout_node.background_layers();
if (background_layers.is_empty())
return default_value();
if (background_layers.size() == 1)
return callback(background_layers.first());
StyleValueVector values;
TRY(values.try_ensure_capacity(background_layers.size()));
for (auto const& layer : background_layers)
values.unchecked_append(TRY(callback(layer)));
return StyleValueList::create(move(values), StyleValueList::Separator::Comma);
}
static ErrorOr<RefPtr<StyleValue>> style_value_for_display(CSS::Display display)
{
if (display.is_none())
return IdentifierStyleValue::create(CSS::ValueID::None);
if (display.is_outside_and_inside()) {
StyleValueVector values;
switch (display.outside()) {
case CSS::Display::Outside::Inline:
TRY(values.try_append(TRY(IdentifierStyleValue::create(CSS::ValueID::Inline))));
break;
case CSS::Display::Outside::Block:
TRY(values.try_append(TRY(IdentifierStyleValue::create(CSS::ValueID::Block))));
break;
case CSS::Display::Outside::RunIn:
TRY(values.try_append(TRY(IdentifierStyleValue::create(CSS::ValueID::RunIn))));
break;
}
switch (display.inside()) {
case CSS::Display::Inside::Flow:
TRY(values.try_append(TRY(IdentifierStyleValue::create(CSS::ValueID::Flow))));
break;
case CSS::Display::Inside::FlowRoot:
TRY(values.try_append(TRY(IdentifierStyleValue::create(CSS::ValueID::FlowRoot))));
break;
case CSS::Display::Inside::Table:
TRY(values.try_append(TRY(IdentifierStyleValue::create(CSS::ValueID::Table))));
break;
case CSS::Display::Inside::Flex:
TRY(values.try_append(TRY(IdentifierStyleValue::create(CSS::ValueID::Flex))));
break;
case CSS::Display::Inside::Grid:
TRY(values.try_append(TRY(IdentifierStyleValue::create(CSS::ValueID::Grid))));
break;
case CSS::Display::Inside::Ruby:
TRY(values.try_append(TRY(IdentifierStyleValue::create(CSS::ValueID::Ruby))));
break;
}
return StyleValueList::create(move(values), StyleValueList::Separator::Space);
}
if (display.is_internal()) {
switch (display.internal()) {
case CSS::Display::Internal::TableRowGroup:
return IdentifierStyleValue::create(CSS::ValueID::TableRowGroup);
case CSS::Display::Internal::TableHeaderGroup:
return IdentifierStyleValue::create(CSS::ValueID::TableHeaderGroup);
case CSS::Display::Internal::TableFooterGroup:
return IdentifierStyleValue::create(CSS::ValueID::TableFooterGroup);
case CSS::Display::Internal::TableRow:
return IdentifierStyleValue::create(CSS::ValueID::TableRow);
case CSS::Display::Internal::TableCell:
return IdentifierStyleValue::create(CSS::ValueID::TableCell);
case CSS::Display::Internal::TableColumnGroup:
return IdentifierStyleValue::create(CSS::ValueID::TableColumnGroup);
case CSS::Display::Internal::TableColumn:
return IdentifierStyleValue::create(CSS::ValueID::TableColumn);
case CSS::Display::Internal::TableCaption:
return IdentifierStyleValue::create(CSS::ValueID::TableCaption);
case CSS::Display::Internal::RubyBase:
return IdentifierStyleValue::create(CSS::ValueID::RubyBase);
case CSS::Display::Internal::RubyText:
return IdentifierStyleValue::create(CSS::ValueID::RubyText);
case CSS::Display::Internal::RubyBaseContainer:
return IdentifierStyleValue::create(CSS::ValueID::RubyBaseContainer);
case CSS::Display::Internal::RubyTextContainer:
return IdentifierStyleValue::create(CSS::ValueID::RubyTextContainer);
}
}
TODO();
}
static NonnullRefPtr<StyleValue const> value_or_default(Optional<StyleProperty> property, NonnullRefPtr<StyleValue> default_style)
{
if (property.has_value())
return property.value().value;
return default_style;
}
static ErrorOr<NonnullRefPtr<StyleValue const>> style_value_for_length_percentage(LengthPercentage const& length_percentage)
{
if (length_percentage.is_auto())
return IdentifierStyleValue::create(ValueID::Auto);
if (length_percentage.is_percentage())
return PercentageStyleValue::create(length_percentage.percentage());
if (length_percentage.is_length())
return LengthStyleValue::create(length_percentage.length());
return length_percentage.calculated();
}
static ErrorOr<NonnullRefPtr<StyleValue const>> style_value_for_size(CSS::Size const& size)
{
if (size.is_none())
return IdentifierStyleValue::create(ValueID::None);
if (size.is_percentage())
return PercentageStyleValue::create(size.percentage());
if (size.is_length())
return LengthStyleValue::create(size.length());
if (size.is_auto())
return IdentifierStyleValue::create(ValueID::Auto);
if (size.is_calculated())
return size.calculated();
if (size.is_min_content())
return IdentifierStyleValue::create(ValueID::MinContent);
if (size.is_max_content())
return IdentifierStyleValue::create(ValueID::MaxContent);
// FIXME: Support fit-content(<length>)
TODO();
}
ErrorOr<RefPtr<StyleValue const>> ResolvedCSSStyleDeclaration::style_value_for_property(Layout::NodeWithStyle const& layout_node, PropertyID property_id) const
{
switch (property_id) {
case CSS::PropertyID::Background: {
auto maybe_background_color = property(CSS::PropertyID::BackgroundColor);
auto maybe_background_image = property(CSS::PropertyID::BackgroundImage);
auto maybe_background_position = property(CSS::PropertyID::BackgroundPosition);
auto maybe_background_size = property(CSS::PropertyID::BackgroundSize);
auto maybe_background_repeat = property(CSS::PropertyID::BackgroundRepeat);
auto maybe_background_attachment = property(CSS::PropertyID::BackgroundAttachment);
auto maybe_background_origin = property(CSS::PropertyID::BackgroundOrigin);
auto maybe_background_clip = property(CSS::PropertyID::BackgroundClip);
return BackgroundStyleValue::create(
value_or_default(maybe_background_color, TRY(InitialStyleValue::the())),
value_or_default(maybe_background_image, TRY(IdentifierStyleValue::create(CSS::ValueID::None))),
value_or_default(maybe_background_position, TRY(PositionStyleValue::create(TRY(EdgeStyleValue::create(PositionEdge::Left, Length::make_px(0))), TRY(EdgeStyleValue::create(PositionEdge::Top, Length::make_px(0)))))),
value_or_default(maybe_background_size, TRY(IdentifierStyleValue::create(CSS::ValueID::Auto))),
value_or_default(maybe_background_repeat, TRY(BackgroundRepeatStyleValue::create(CSS::Repeat::Repeat, CSS::Repeat::Repeat))),
value_or_default(maybe_background_attachment, TRY(IdentifierStyleValue::create(CSS::ValueID::Scroll))),
value_or_default(maybe_background_origin, TRY(IdentifierStyleValue::create(CSS::ValueID::PaddingBox))),
value_or_default(maybe_background_clip, TRY(IdentifierStyleValue::create(CSS::ValueID::BorderBox))));
}
case CSS::PropertyID::BackgroundAttachment:
return style_value_for_background_property(
layout_node,
[](auto& layer) { return IdentifierStyleValue::create(to_value_id(layer.attachment)); },
[] { return IdentifierStyleValue::create(CSS::ValueID::Scroll); });
case CSS::PropertyID::BackgroundClip:
return style_value_for_background_property(
layout_node,
[](auto& layer) { return IdentifierStyleValue::create(to_value_id(layer.clip)); },
[] { return IdentifierStyleValue::create(CSS::ValueID::BorderBox); });
case PropertyID::BackgroundColor:
return ColorStyleValue::create(layout_node.computed_values().background_color());
case CSS::PropertyID::BackgroundImage:
return style_value_for_background_property(
layout_node,
[](auto& layer) -> ErrorOr<NonnullRefPtr<StyleValue const>> {
if (layer.background_image)
return *layer.background_image;
return IdentifierStyleValue::create(CSS::ValueID::None);
},
[] { return IdentifierStyleValue::create(CSS::ValueID::None); });
case CSS::PropertyID::BackgroundOrigin:
return style_value_for_background_property(
layout_node,
[](auto& layer) { return IdentifierStyleValue::create(to_value_id(layer.origin)); },
[] { return IdentifierStyleValue::create(CSS::ValueID::PaddingBox); });
case CSS::PropertyID::BackgroundRepeat:
return style_value_for_background_property(
layout_node,
[](auto& layer) -> ErrorOr<NonnullRefPtr<StyleValue const>> {
StyleValueVector repeat {
TRY(IdentifierStyleValue::create(to_value_id(layer.repeat_x))),
TRY(IdentifierStyleValue::create(to_value_id(layer.repeat_y))),
};
return StyleValueList::create(move(repeat), StyleValueList::Separator::Space);
},
[] { return BackgroundRepeatStyleValue::create(CSS::Repeat::Repeat, CSS::Repeat::Repeat); });
case CSS::PropertyID::BorderBottom: {
auto border = layout_node.computed_values().border_bottom();
auto width = TRY(LengthStyleValue::create(Length::make_px(border.width)));
auto style = TRY(IdentifierStyleValue::create(to_value_id(border.line_style)));
auto color = TRY(ColorStyleValue::create(border.color));
return BorderStyleValue::create(width, style, color);
}
case CSS::PropertyID::BorderBottomColor:
return ColorStyleValue::create(layout_node.computed_values().border_bottom().color);
case CSS::PropertyID::BorderBottomLeftRadius: {
auto const& border_radius = layout_node.computed_values().border_bottom_left_radius();
return BorderRadiusStyleValue::create(border_radius.horizontal_radius, border_radius.vertical_radius);
}
case CSS::PropertyID::BorderBottomRightRadius: {
auto const& border_radius = layout_node.computed_values().border_bottom_right_radius();
return BorderRadiusStyleValue::create(border_radius.horizontal_radius, border_radius.vertical_radius);
}
case CSS::PropertyID::BorderBottomStyle:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().border_bottom().line_style));
case CSS::PropertyID::BorderBottomWidth:
return LengthStyleValue::create(Length::make_px(layout_node.computed_values().border_bottom().width));
case CSS::PropertyID::BorderCollapse:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().border_collapse()));
case CSS::PropertyID::BorderLeft: {
auto border = layout_node.computed_values().border_left();
auto width = TRY(LengthStyleValue::create(Length::make_px(border.width)));
auto style = TRY(IdentifierStyleValue::create(to_value_id(border.line_style)));
auto color = TRY(ColorStyleValue::create(border.color));
return BorderStyleValue::create(width, style, color);
}
case CSS::PropertyID::BorderLeftColor:
return ColorStyleValue::create(layout_node.computed_values().border_left().color);
case CSS::PropertyID::BorderLeftStyle:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().border_left().line_style));
case CSS::PropertyID::BorderLeftWidth:
return LengthStyleValue::create(Length::make_px(layout_node.computed_values().border_left().width));
case CSS::PropertyID::BorderRadius: {
auto maybe_top_left_radius = property(CSS::PropertyID::BorderTopLeftRadius);
auto maybe_top_right_radius = property(CSS::PropertyID::BorderTopRightRadius);
auto maybe_bottom_left_radius = property(CSS::PropertyID::BorderBottomLeftRadius);
auto maybe_bottom_right_radius = property(CSS::PropertyID::BorderBottomRightRadius);
RefPtr<BorderRadiusStyleValue const> top_left_radius, top_right_radius, bottom_left_radius, bottom_right_radius;
if (maybe_top_left_radius.has_value()) {
VERIFY(maybe_top_left_radius.value().value->is_border_radius());
top_left_radius = maybe_top_left_radius.value().value->as_border_radius();
}
if (maybe_top_right_radius.has_value()) {
VERIFY(maybe_top_right_radius.value().value->is_border_radius());
top_right_radius = maybe_top_right_radius.value().value->as_border_radius();
}
if (maybe_bottom_left_radius.has_value()) {
VERIFY(maybe_bottom_left_radius.value().value->is_border_radius());
bottom_left_radius = maybe_bottom_left_radius.value().value->as_border_radius();
}
if (maybe_bottom_right_radius.has_value()) {
VERIFY(maybe_bottom_right_radius.value().value->is_border_radius());
bottom_right_radius = maybe_bottom_right_radius.value().value->as_border_radius();
}
return BorderRadiusShorthandStyleValue::create(top_left_radius.release_nonnull(), top_right_radius.release_nonnull(), bottom_right_radius.release_nonnull(), bottom_left_radius.release_nonnull());
}
case CSS::PropertyID::BorderRight: {
auto border = layout_node.computed_values().border_right();
auto width = TRY(LengthStyleValue::create(Length::make_px(border.width)));
auto style = TRY(IdentifierStyleValue::create(to_value_id(border.line_style)));
auto color = TRY(ColorStyleValue::create(border.color));
return BorderStyleValue::create(width, style, color);
}
case CSS::PropertyID::BorderRightColor:
return ColorStyleValue::create(layout_node.computed_values().border_right().color);
case CSS::PropertyID::BorderRightStyle:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().border_right().line_style));
case CSS::PropertyID::BorderRightWidth:
return LengthStyleValue::create(Length::make_px(layout_node.computed_values().border_right().width));
case CSS::PropertyID::BorderTop: {
auto border = layout_node.computed_values().border_top();
auto width = TRY(LengthStyleValue::create(Length::make_px(border.width)));
auto style = TRY(IdentifierStyleValue::create(to_value_id(border.line_style)));
auto color = TRY(ColorStyleValue::create(border.color));
return BorderStyleValue::create(width, style, color);
}
case CSS::PropertyID::BorderTopColor:
return ColorStyleValue::create(layout_node.computed_values().border_top().color);
case CSS::PropertyID::BorderTopLeftRadius: {
auto const& border_radius = layout_node.computed_values().border_top_left_radius();
return BorderRadiusStyleValue::create(border_radius.horizontal_radius, border_radius.vertical_radius);
}
case CSS::PropertyID::BorderTopRightRadius: {
auto const& border_radius = layout_node.computed_values().border_top_right_radius();
return BorderRadiusStyleValue::create(border_radius.horizontal_radius, border_radius.vertical_radius);
}
case CSS::PropertyID::BorderTopStyle:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().border_top().line_style));
case CSS::PropertyID::BorderTopWidth:
return LengthStyleValue::create(Length::make_px(layout_node.computed_values().border_top().width));
case CSS::PropertyID::BoxShadow: {
auto box_shadow_layers = layout_node.computed_values().box_shadow();
if (box_shadow_layers.is_empty())
return nullptr;
auto make_box_shadow_style_value = [](ShadowData const& data) {
return ShadowStyleValue::create(data.color, data.offset_x, data.offset_y, data.blur_radius, data.spread_distance, data.placement);
};
if (box_shadow_layers.size() == 1)
return make_box_shadow_style_value(box_shadow_layers.first());
StyleValueVector box_shadow;
TRY(box_shadow.try_ensure_capacity(box_shadow_layers.size()));
for (auto const& layer : box_shadow_layers)
box_shadow.unchecked_append(TRY(make_box_shadow_style_value(layer)));
return StyleValueList::create(move(box_shadow), StyleValueList::Separator::Comma);
}
case CSS::PropertyID::BoxSizing:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().box_sizing()));
case CSS::PropertyID::Bottom:
return style_value_for_length_percentage(layout_node.computed_values().inset().bottom());
case CSS::PropertyID::Clear:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().clear()));
case CSS::PropertyID::Clip:
return RectStyleValue::create(layout_node.computed_values().clip().to_rect());
case CSS::PropertyID::Color:
return ColorStyleValue::create(layout_node.computed_values().color());
case CSS::PropertyID::ColumnGap:
return style_value_for_size(layout_node.computed_values().column_gap());
case CSS::PropertyID::Cursor:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().cursor()));
case CSS::PropertyID::Display:
return style_value_for_display(layout_node.display());
case CSS::PropertyID::FlexBasis: {
switch (layout_node.computed_values().flex_basis().type) {
case FlexBasis::Content:
return IdentifierStyleValue::create(CSS::ValueID::Content);
case FlexBasis::LengthPercentage:
return style_value_for_length_percentage(*layout_node.computed_values().flex_basis().length_percentage);
case FlexBasis::Auto:
return IdentifierStyleValue::create(CSS::ValueID::Auto);
default:
VERIFY_NOT_REACHED();
}
break;
case CSS::PropertyID::FlexDirection:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().flex_direction()));
case CSS::PropertyID::FlexGrow:
return NumericStyleValue::create_float(layout_node.computed_values().flex_grow());
case CSS::PropertyID::FlexShrink:
return NumericStyleValue::create_float(layout_node.computed_values().flex_shrink());
case CSS::PropertyID::FlexWrap:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().flex_wrap()));
case CSS::PropertyID::Float:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().float_()));
case CSS::PropertyID::FontSize:
return LengthStyleValue::create(Length::make_px(layout_node.computed_values().font_size()));
case CSS::PropertyID::FontVariant: {
auto font_variant = layout_node.computed_values().font_variant();
switch (font_variant) {
case FontVariant::Normal:
return IdentifierStyleValue::create(ValueID::Normal);
case FontVariant::SmallCaps:
return IdentifierStyleValue::create(ValueID::SmallCaps);
}
VERIFY_NOT_REACHED();
}
case CSS::PropertyID::FontWeight:
return NumericStyleValue::create_integer(layout_node.computed_values().font_weight());
case CSS::PropertyID::GridArea: {
auto maybe_grid_row_start = property(CSS::PropertyID::GridRowStart);
auto maybe_grid_column_start = property(CSS::PropertyID::GridColumnStart);
auto maybe_grid_row_end = property(CSS::PropertyID::GridRowEnd);
auto maybe_grid_column_end = property(CSS::PropertyID::GridColumnEnd);
RefPtr<GridTrackPlacementStyleValue const> grid_row_start, grid_column_start, grid_row_end, grid_column_end;
if (maybe_grid_row_start.has_value()) {
VERIFY(maybe_grid_row_start.value().value->is_grid_track_placement());
grid_row_start = maybe_grid_row_start.value().value->as_grid_track_placement();
}
if (maybe_grid_column_start.has_value()) {
VERIFY(maybe_grid_column_start.value().value->is_grid_track_placement());
grid_column_start = maybe_grid_column_start.value().value->as_grid_track_placement();
}
if (maybe_grid_row_end.has_value()) {
VERIFY(maybe_grid_row_end.value().value->is_grid_track_placement());
grid_row_end = maybe_grid_row_end.value().value->as_grid_track_placement();
}
if (maybe_grid_column_end.has_value()) {
VERIFY(maybe_grid_column_end.value().value->is_grid_track_placement());
grid_column_end = maybe_grid_column_end.value().value->as_grid_track_placement();
}
return GridAreaShorthandStyleValue::create(
grid_row_start.release_nonnull(),
grid_column_start.release_nonnull(),
grid_row_end.release_nonnull(),
grid_column_end.release_nonnull());
}
case CSS::PropertyID::GridColumn: {
auto maybe_grid_column_end = property(CSS::PropertyID::GridColumnEnd);
auto maybe_grid_column_start = property(CSS::PropertyID::GridColumnStart);
RefPtr<GridTrackPlacementStyleValue const> grid_column_start, grid_column_end;
if (maybe_grid_column_end.has_value()) {
VERIFY(maybe_grid_column_end.value().value->is_grid_track_placement());
grid_column_end = maybe_grid_column_end.value().value->as_grid_track_placement();
}
if (maybe_grid_column_start.has_value()) {
VERIFY(maybe_grid_column_start.value().value->is_grid_track_placement());
grid_column_start = maybe_grid_column_start.value().value->as_grid_track_placement();
}
return GridTrackPlacementShorthandStyleValue::create(grid_column_end.release_nonnull(), grid_column_start.release_nonnull());
}
case CSS::PropertyID::GridColumnEnd:
return GridTrackPlacementStyleValue::create(layout_node.computed_values().grid_column_end());
case CSS::PropertyID::GridColumnStart:
return GridTrackPlacementStyleValue::create(layout_node.computed_values().grid_column_start());
case CSS::PropertyID::GridRow: {
auto maybe_grid_row_end = property(CSS::PropertyID::GridRowEnd);
auto maybe_grid_row_start = property(CSS::PropertyID::GridRowStart);
RefPtr<GridTrackPlacementStyleValue const> grid_row_start, grid_row_end;
if (maybe_grid_row_end.has_value()) {
VERIFY(maybe_grid_row_end.value().value->is_grid_track_placement());
grid_row_end = maybe_grid_row_end.value().value->as_grid_track_placement();
}
if (maybe_grid_row_start.has_value()) {
VERIFY(maybe_grid_row_start.value().value->is_grid_track_placement());
grid_row_start = maybe_grid_row_start.value().value->as_grid_track_placement();
}
return GridTrackPlacementShorthandStyleValue::create(grid_row_end.release_nonnull(), grid_row_start.release_nonnull());
}
case CSS::PropertyID::GridRowEnd:
return GridTrackPlacementStyleValue::create(layout_node.computed_values().grid_row_end());
case CSS::PropertyID::GridRowStart:
return GridTrackPlacementStyleValue::create(layout_node.computed_values().grid_row_start());
case CSS::PropertyID::GridTemplate: {
auto maybe_grid_template_areas = property(CSS::PropertyID::GridTemplateAreas);
auto maybe_grid_template_rows = property(CSS::PropertyID::GridTemplateRows);
auto maybe_grid_template_columns = property(CSS::PropertyID::GridTemplateColumns);
RefPtr<GridTemplateAreaStyleValue const> grid_template_areas;
RefPtr<GridTrackSizeListStyleValue const> grid_template_rows, grid_template_columns;
if (maybe_grid_template_areas.has_value()) {
VERIFY(maybe_grid_template_areas.value().value->is_grid_template_area());
grid_template_areas = maybe_grid_template_areas.value().value->as_grid_template_area();
}
if (maybe_grid_template_rows.has_value()) {
VERIFY(maybe_grid_template_rows.value().value->is_grid_track_size_list());
grid_template_rows = maybe_grid_template_rows.value().value->as_grid_track_size_list();
}
if (maybe_grid_template_columns.has_value()) {
VERIFY(maybe_grid_template_columns.value().value->is_grid_track_size_list());
grid_template_columns = maybe_grid_template_columns.value().value->as_grid_track_size_list();
}
return GridTrackSizeListShorthandStyleValue::create(grid_template_areas.release_nonnull(), grid_template_rows.release_nonnull(), grid_template_columns.release_nonnull());
}
case CSS::PropertyID::GridTemplateColumns:
return GridTrackSizeListStyleValue::create(layout_node.computed_values().grid_template_columns());
case CSS::PropertyID::GridTemplateRows:
return GridTrackSizeListStyleValue::create(layout_node.computed_values().grid_template_rows());
case CSS::PropertyID::GridTemplateAreas:
return GridTemplateAreaStyleValue::create(layout_node.computed_values().grid_template_areas());
case CSS::PropertyID::Height:
return style_value_for_size(layout_node.computed_values().height());
case CSS::PropertyID::ImageRendering:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().image_rendering()));
case CSS::PropertyID::JustifyContent:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().justify_content()));
case CSS::PropertyID::Left:
return style_value_for_length_percentage(layout_node.computed_values().inset().left());
case CSS::PropertyID::LineHeight:
return LengthStyleValue::create(Length::make_px(layout_node.line_height()));
case CSS::PropertyID::ListStyleType:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().list_style_type()));
case CSS::PropertyID::Margin: {
auto margin = layout_node.computed_values().margin();
auto values = StyleValueVector {};
TRY(values.try_ensure_capacity(4));
values.unchecked_append(TRY(style_value_for_length_percentage(margin.top())));
values.unchecked_append(TRY(style_value_for_length_percentage(margin.right())));
values.unchecked_append(TRY(style_value_for_length_percentage(margin.bottom())));
values.unchecked_append(TRY(style_value_for_length_percentage(margin.left())));
return StyleValueList::create(move(values), StyleValueList::Separator::Space);
}
case CSS::PropertyID::MarginBottom:
return style_value_for_length_percentage(layout_node.computed_values().margin().bottom());
case CSS::PropertyID::MarginLeft:
return style_value_for_length_percentage(layout_node.computed_values().margin().left());
case CSS::PropertyID::MarginRight:
return style_value_for_length_percentage(layout_node.computed_values().margin().right());
case CSS::PropertyID::MarginTop:
return style_value_for_length_percentage(layout_node.computed_values().margin().top());
case CSS::PropertyID::MaxHeight:
return style_value_for_size(layout_node.computed_values().max_height());
case CSS::PropertyID::MaxWidth:
return style_value_for_size(layout_node.computed_values().max_width());
case CSS::PropertyID::MinHeight:
return style_value_for_size(layout_node.computed_values().min_height());
case CSS::PropertyID::MinWidth:
return style_value_for_size(layout_node.computed_values().min_width());
case CSS::PropertyID::Opacity:
return NumericStyleValue::create_float(layout_node.computed_values().opacity());
case CSS::PropertyID::Order:
return NumericStyleValue::create_integer(layout_node.computed_values().order());
case CSS::PropertyID::OverflowX:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().overflow_x()));
case CSS::PropertyID::OverflowY:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().overflow_y()));
case CSS::PropertyID::Padding: {
auto padding = layout_node.computed_values().padding();
auto values = StyleValueVector {};
TRY(values.try_ensure_capacity(4));
values.unchecked_append(TRY(style_value_for_length_percentage(padding.top())));
values.unchecked_append(TRY(style_value_for_length_percentage(padding.right())));
values.unchecked_append(TRY(style_value_for_length_percentage(padding.bottom())));
values.unchecked_append(TRY(style_value_for_length_percentage(padding.left())));
return StyleValueList::create(move(values), StyleValueList::Separator::Space);
}
case CSS::PropertyID::PaddingBottom:
return style_value_for_length_percentage(layout_node.computed_values().padding().bottom());
case CSS::PropertyID::PaddingLeft:
return style_value_for_length_percentage(layout_node.computed_values().padding().left());
case CSS::PropertyID::PaddingRight:
return style_value_for_length_percentage(layout_node.computed_values().padding().right());
case CSS::PropertyID::PaddingTop:
return style_value_for_length_percentage(layout_node.computed_values().padding().top());
case CSS::PropertyID::Position:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().position()));
case CSS::PropertyID::Right:
return style_value_for_length_percentage(layout_node.computed_values().inset().right());
case CSS::PropertyID::RowGap:
return style_value_for_size(layout_node.computed_values().row_gap());
case CSS::PropertyID::TextAlign:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().text_align()));
case CSS::PropertyID::TextDecorationLine: {
auto text_decoration_lines = layout_node.computed_values().text_decoration_line();
if (text_decoration_lines.is_empty())
return IdentifierStyleValue::create(ValueID::None);
StyleValueVector style_values;
TRY(style_values.try_ensure_capacity(text_decoration_lines.size()));
for (auto const& line : text_decoration_lines) {
style_values.unchecked_append(TRY(IdentifierStyleValue::create(to_value_id(line))));
}
return StyleValueList::create(move(style_values), StyleValueList::Separator::Space);
}
case CSS::PropertyID::TextDecorationStyle:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().text_decoration_style()));
case CSS::PropertyID::TextTransform:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().text_transform()));
case CSS::PropertyID::Top:
return style_value_for_length_percentage(layout_node.computed_values().inset().top());
case CSS::PropertyID::Transform: {
// NOTE: The computed value for `transform` serializes as a single `matrix(...)` value, instead of
// the original list of transform functions. So, we produce a StyleValue for that.
// https://www.w3.org/TR/css-transforms-1/#serialization-of-the-computed-value
auto transformations = layout_node.computed_values().transformations();
if (transformations.is_empty())
return IdentifierStyleValue::create(ValueID::None);
// The transform matrix is held by the StackingContext, so we need to make sure we have one first.
auto const* viewport = layout_node.document().layout_node();
VERIFY(viewport);
const_cast<Layout::Viewport&>(*viewport).build_stacking_context_tree_if_needed();
VERIFY(layout_node.paintable());
auto const& paintable_box = verify_cast<Painting::PaintableBox const>(layout_node.paintable());
VERIFY(paintable_box->stacking_context());
// FIXME: This needs to serialize to matrix3d if the transformation matrix is a 3D matrix.
// https://w3c.github.io/csswg-drafts/css-transforms-2/#serialization-of-the-computed-value
auto affine_matrix = paintable_box->stacking_context()->affine_transform_matrix();
StyleValueVector parameters;
TRY(parameters.try_ensure_capacity(6));
parameters.unchecked_append(TRY(NumericStyleValue::create_float(affine_matrix.a())));
parameters.unchecked_append(TRY(NumericStyleValue::create_float(affine_matrix.b())));
parameters.unchecked_append(TRY(NumericStyleValue::create_float(affine_matrix.c())));
parameters.unchecked_append(TRY(NumericStyleValue::create_float(affine_matrix.d())));
parameters.unchecked_append(TRY(NumericStyleValue::create_float(affine_matrix.e())));
parameters.unchecked_append(TRY(NumericStyleValue::create_float(affine_matrix.f())));
NonnullRefPtr<StyleValue> matrix_function = TRY(TransformationStyleValue::create(TransformFunction::Matrix, move(parameters)));
// Elsewhere we always store the transform property's value as a StyleValueList of TransformationStyleValues,
// so this is just for consistency.
StyleValueVector matrix_functions { matrix_function };
return StyleValueList::create(move(matrix_functions), StyleValueList::Separator::Space);
}
case CSS::PropertyID::VerticalAlign:
if (auto const* length_percentage = layout_node.computed_values().vertical_align().get_pointer<CSS::LengthPercentage>()) {
return style_value_for_length_percentage(*length_percentage);
}
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().vertical_align().get<CSS::VerticalAlign>()));
case CSS::PropertyID::WhiteSpace:
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().white_space()));
case CSS::PropertyID::Width:
return style_value_for_size(layout_node.computed_values().width());
case CSS::PropertyID::ZIndex: {
auto maybe_z_index = layout_node.computed_values().z_index();
if (!maybe_z_index.has_value())
return nullptr;
return NumericStyleValue::create_integer(maybe_z_index.release_value());
}
case CSS::PropertyID::Invalid:
return IdentifierStyleValue::create(CSS::ValueID::Invalid);
case CSS::PropertyID::Custom:
dbgln_if(LIBWEB_CSS_DEBUG, "Computed style for custom properties was requested (?)");
return nullptr;
default:
dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Computed style for the '{}' property was requested", string_from_property_id(property_id));
return nullptr;
}
}
}
Optional<StyleProperty> ResolvedCSSStyleDeclaration::property(PropertyID property_id) const
{
// https://www.w3.org/TR/cssom-1/#dom-window-getcomputedstyle
// NOTE: This is a partial enforcement of step 5 ("If elt is connected, ...")
if (!m_element->is_connected())
return {};
if (CSS::property_affects_layout(property_id)) {
const_cast<DOM::Document&>(m_element->document()).update_layout();
} else {
// FIXME: If we had a way to update style for a single element, this would be a good place to use it.
const_cast<DOM::Document&>(m_element->document()).update_style();
}
if (!m_element->layout_node()) {
auto style_or_error = m_element->document().style_computer().compute_style(const_cast<DOM::Element&>(*m_element));
if (style_or_error.is_error()) {
dbgln("ResolvedCSSStyleDeclaration::property style computer failed");
return {};
}
auto style = style_or_error.release_value();
// FIXME: This is a stopgap until we implement shorthand -> longhand conversion.
auto value = style->maybe_null_property(property_id);
if (!value) {
dbgln("FIXME: ResolvedCSSStyleDeclaration::property(property_id=0x{:x}) No value for property ID in newly computed style case.", to_underlying(property_id));
return {};
}
return StyleProperty {
.property_id = property_id,
.value = value.release_nonnull(),
};
}
auto& layout_node = *m_element->layout_node();
auto value = style_value_for_property(layout_node, property_id).release_value_but_fixme_should_propagate_errors();
if (!value)
return {};
return StyleProperty {
.property_id = property_id,
.value = value.release_nonnull(),
};
}
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty
WebIDL::ExceptionOr<void> ResolvedCSSStyleDeclaration::set_property(PropertyID, StringView, StringView)
{
// 1. If the computed flag is set, then throw a NoModificationAllowedError exception.
return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties in result of getComputedStyle()");
}
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty
WebIDL::ExceptionOr<DeprecatedString> ResolvedCSSStyleDeclaration::remove_property(PropertyID)
{
// 1. If the computed flag is set, then throw a NoModificationAllowedError exception.
return WebIDL::NoModificationAllowedError::create(realm(), "Cannot remove properties from result of getComputedStyle()");
}
DeprecatedString ResolvedCSSStyleDeclaration::serialized() const
{
// https://www.w3.org/TR/cssom/#dom-cssstyledeclaration-csstext
// If the computed flag is set, then return the empty string.
// NOTE: ResolvedCSSStyleDeclaration is something you would only get from window.getComputedStyle(),
// which returns what the spec calls "resolved style". The "computed flag" is always set here.
return DeprecatedString::empty();
}
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext
WebIDL::ExceptionOr<void> ResolvedCSSStyleDeclaration::set_css_text(StringView)
{
// 1. If the computed flag is set, then throw a NoModificationAllowedError exception.
return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties in result of getComputedStyle()");
}
}