/src/serenity/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2021-2023, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org> |
4 | | * Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org> |
5 | | * |
6 | | * SPDX-License-Identifier: BSD-2-Clause |
7 | | */ |
8 | | |
9 | | #include <AK/Debug.h> |
10 | | #include <AK/Format.h> |
11 | | #include <AK/NonnullRefPtr.h> |
12 | | #include <LibWeb/CSS/Enums.h> |
13 | | #include <LibWeb/CSS/ResolvedCSSStyleDeclaration.h> |
14 | | #include <LibWeb/CSS/StyleComputer.h> |
15 | | #include <LibWeb/CSS/StyleValues/BackgroundRepeatStyleValue.h> |
16 | | #include <LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h> |
17 | | #include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h> |
18 | | #include <LibWeb/CSS/StyleValues/CSSColorValue.h> |
19 | | #include <LibWeb/CSS/StyleValues/CSSKeywordValue.h> |
20 | | #include <LibWeb/CSS/StyleValues/CSSMathValue.h> |
21 | | #include <LibWeb/CSS/StyleValues/EdgeStyleValue.h> |
22 | | #include <LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h> |
23 | | #include <LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h> |
24 | | #include <LibWeb/CSS/StyleValues/IntegerStyleValue.h> |
25 | | #include <LibWeb/CSS/StyleValues/LengthStyleValue.h> |
26 | | #include <LibWeb/CSS/StyleValues/NumberStyleValue.h> |
27 | | #include <LibWeb/CSS/StyleValues/PercentageStyleValue.h> |
28 | | #include <LibWeb/CSS/StyleValues/PositionStyleValue.h> |
29 | | #include <LibWeb/CSS/StyleValues/RatioStyleValue.h> |
30 | | #include <LibWeb/CSS/StyleValues/RectStyleValue.h> |
31 | | #include <LibWeb/CSS/StyleValues/ShadowStyleValue.h> |
32 | | #include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h> |
33 | | #include <LibWeb/CSS/StyleValues/StyleValueList.h> |
34 | | #include <LibWeb/CSS/StyleValues/TimeStyleValue.h> |
35 | | #include <LibWeb/CSS/StyleValues/TransformationStyleValue.h> |
36 | | #include <LibWeb/CSS/StyleValues/URLStyleValue.h> |
37 | | #include <LibWeb/DOM/Document.h> |
38 | | #include <LibWeb/DOM/Element.h> |
39 | | #include <LibWeb/Layout/Viewport.h> |
40 | | #include <LibWeb/Painting/PaintableBox.h> |
41 | | #include <LibWeb/Painting/StackingContext.h> |
42 | | #include <LibWeb/Painting/ViewportPaintable.h> |
43 | | |
44 | | namespace Web::CSS { |
45 | | |
46 | | JS_DEFINE_ALLOCATOR(ResolvedCSSStyleDeclaration); |
47 | | |
48 | | JS::NonnullGCPtr<ResolvedCSSStyleDeclaration> ResolvedCSSStyleDeclaration::create(DOM::Element& element, Optional<Selector::PseudoElement::Type> pseudo_element) |
49 | 0 | { |
50 | 0 | return element.realm().heap().allocate<ResolvedCSSStyleDeclaration>(element.realm(), element, move(pseudo_element)); |
51 | 0 | } |
52 | | |
53 | | ResolvedCSSStyleDeclaration::ResolvedCSSStyleDeclaration(DOM::Element& element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element) |
54 | 0 | : CSSStyleDeclaration(element.realm()) |
55 | 0 | , m_element(element) |
56 | 0 | , m_pseudo_element(move(pseudo_element)) |
57 | 0 | { |
58 | 0 | } |
59 | | |
60 | | void ResolvedCSSStyleDeclaration::visit_edges(Cell::Visitor& visitor) |
61 | 0 | { |
62 | 0 | Base::visit_edges(visitor); |
63 | 0 | visitor.visit(m_element); |
64 | 0 | } |
65 | | |
66 | | // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-length |
67 | | size_t ResolvedCSSStyleDeclaration::length() const |
68 | 0 | { |
69 | | // The length attribute must return the number of CSS declarations in the declarations. |
70 | | // FIXME: Include the number of custom properties. |
71 | 0 | return to_underlying(last_longhand_property_id) - to_underlying(first_longhand_property_id) + 1; |
72 | 0 | } |
73 | | |
74 | | // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-item |
75 | | String ResolvedCSSStyleDeclaration::item(size_t index) const |
76 | 0 | { |
77 | | // The item(index) method must return the property name of the CSS declaration at position index. |
78 | | // FIXME: Return custom properties if index > last_longhand_property_id. |
79 | 0 | if (index >= length()) |
80 | 0 | return {}; |
81 | 0 | auto property_id = static_cast<PropertyID>(index + to_underlying(first_longhand_property_id)); |
82 | 0 | return string_from_property_id(property_id).to_string(); |
83 | 0 | } |
84 | | |
85 | | static NonnullRefPtr<CSSStyleValue const> style_value_for_background_property(Layout::NodeWithStyle const& layout_node, Function<NonnullRefPtr<CSSStyleValue const>(BackgroundLayerData const&)> callback, Function<NonnullRefPtr<CSSStyleValue const>()> default_value) |
86 | 0 | { |
87 | 0 | auto const& background_layers = layout_node.background_layers(); |
88 | 0 | if (background_layers.is_empty()) |
89 | 0 | return default_value(); |
90 | 0 | if (background_layers.size() == 1) |
91 | 0 | return callback(background_layers.first()); |
92 | 0 | StyleValueVector values; |
93 | 0 | values.ensure_capacity(background_layers.size()); |
94 | 0 | for (auto const& layer : background_layers) |
95 | 0 | values.unchecked_append(callback(layer)); |
96 | 0 | return StyleValueList::create(move(values), StyleValueList::Separator::Comma); |
97 | 0 | } |
98 | | |
99 | | static NonnullRefPtr<CSSStyleValue const> style_value_for_length_percentage(LengthPercentage const& length_percentage) |
100 | 0 | { |
101 | 0 | if (length_percentage.is_auto()) |
102 | 0 | return CSSKeywordValue::create(Keyword::Auto); |
103 | 0 | if (length_percentage.is_percentage()) |
104 | 0 | return PercentageStyleValue::create(length_percentage.percentage()); |
105 | 0 | if (length_percentage.is_length()) |
106 | 0 | return LengthStyleValue::create(length_percentage.length()); |
107 | 0 | return length_percentage.calculated(); |
108 | 0 | } |
109 | | |
110 | | static NonnullRefPtr<CSSStyleValue const> style_value_for_size(Size const& size) |
111 | 0 | { |
112 | 0 | if (size.is_none()) |
113 | 0 | return CSSKeywordValue::create(Keyword::None); |
114 | 0 | if (size.is_percentage()) |
115 | 0 | return PercentageStyleValue::create(size.percentage()); |
116 | 0 | if (size.is_length()) |
117 | 0 | return LengthStyleValue::create(size.length()); |
118 | 0 | if (size.is_auto()) |
119 | 0 | return CSSKeywordValue::create(Keyword::Auto); |
120 | 0 | if (size.is_calculated()) |
121 | 0 | return size.calculated(); |
122 | 0 | if (size.is_min_content()) |
123 | 0 | return CSSKeywordValue::create(Keyword::MinContent); |
124 | 0 | if (size.is_max_content()) |
125 | 0 | return CSSKeywordValue::create(Keyword::MaxContent); |
126 | | // FIXME: Support fit-content(<length>) |
127 | 0 | if (size.is_fit_content()) |
128 | 0 | return CSSKeywordValue::create(Keyword::FitContent); |
129 | 0 | TODO(); |
130 | 0 | } |
131 | | |
132 | | static NonnullRefPtr<CSSStyleValue const> style_value_for_sided_shorthand(ValueComparingNonnullRefPtr<CSSStyleValue const> top, ValueComparingNonnullRefPtr<CSSStyleValue const> right, ValueComparingNonnullRefPtr<CSSStyleValue const> bottom, ValueComparingNonnullRefPtr<CSSStyleValue const> left) |
133 | 0 | { |
134 | 0 | bool top_and_bottom_same = top == bottom; |
135 | 0 | bool left_and_right_same = left == right; |
136 | |
|
137 | 0 | if (top_and_bottom_same && left_and_right_same && top == left) |
138 | 0 | return top; |
139 | | |
140 | 0 | if (top_and_bottom_same && left_and_right_same) |
141 | 0 | return StyleValueList::create(StyleValueVector { move(top), move(right) }, StyleValueList::Separator::Space); |
142 | | |
143 | 0 | if (left_and_right_same) |
144 | 0 | return StyleValueList::create(StyleValueVector { move(top), move(right), move(bottom) }, StyleValueList::Separator::Space); |
145 | | |
146 | 0 | return StyleValueList::create(StyleValueVector { move(top), move(right), move(bottom), move(left) }, StyleValueList::Separator::Space); |
147 | 0 | } |
148 | | |
149 | | enum class LogicalSide { |
150 | | BlockStart, |
151 | | BlockEnd, |
152 | | InlineStart, |
153 | | InlineEnd, |
154 | | }; |
155 | | static RefPtr<CSSStyleValue const> style_value_for_length_box_logical_side(Layout::NodeWithStyle const&, LengthBox const& box, LogicalSide logical_side) |
156 | 0 | { |
157 | | // FIXME: Actually determine the logical sides based on layout_node's writing-mode and direction. |
158 | 0 | switch (logical_side) { |
159 | 0 | case LogicalSide::BlockStart: |
160 | 0 | return style_value_for_length_percentage(box.top()); |
161 | 0 | case LogicalSide::BlockEnd: |
162 | 0 | return style_value_for_length_percentage(box.bottom()); |
163 | 0 | case LogicalSide::InlineStart: |
164 | 0 | return style_value_for_length_percentage(box.left()); |
165 | 0 | case LogicalSide::InlineEnd: |
166 | 0 | return style_value_for_length_percentage(box.right()); |
167 | 0 | } |
168 | 0 | VERIFY_NOT_REACHED(); |
169 | 0 | } |
170 | | |
171 | | static RefPtr<CSSStyleValue const> style_value_for_shadow(Vector<ShadowData> const& shadow_data) |
172 | 0 | { |
173 | 0 | if (shadow_data.is_empty()) |
174 | 0 | return CSSKeywordValue::create(Keyword::None); |
175 | | |
176 | 0 | auto make_shadow_style_value = [](ShadowData const& shadow) { |
177 | 0 | return ShadowStyleValue::create( |
178 | 0 | CSSColorValue::create_from_color(shadow.color), |
179 | 0 | style_value_for_length_percentage(shadow.offset_x), |
180 | 0 | style_value_for_length_percentage(shadow.offset_y), |
181 | 0 | style_value_for_length_percentage(shadow.blur_radius), |
182 | 0 | style_value_for_length_percentage(shadow.spread_distance), |
183 | 0 | shadow.placement); |
184 | 0 | }; |
185 | |
|
186 | 0 | if (shadow_data.size() == 1) |
187 | 0 | return make_shadow_style_value(shadow_data.first()); |
188 | | |
189 | 0 | StyleValueVector style_values; |
190 | 0 | style_values.ensure_capacity(shadow_data.size()); |
191 | 0 | for (auto& shadow : shadow_data) |
192 | 0 | style_values.unchecked_append(make_shadow_style_value(shadow)); |
193 | |
|
194 | 0 | return StyleValueList::create(move(style_values), StyleValueList::Separator::Comma); |
195 | 0 | } |
196 | | |
197 | | RefPtr<CSSStyleValue const> ResolvedCSSStyleDeclaration::style_value_for_property(Layout::NodeWithStyle const& layout_node, PropertyID property_id) const |
198 | 0 | { |
199 | 0 | auto used_value_for_property = [&layout_node, property_id](Function<CSSPixels(Painting::PaintableBox const&)>&& used_value_getter) -> Optional<CSSPixels> { |
200 | 0 | auto const& display = layout_node.computed_values().display(); |
201 | 0 | if (!display.is_none() && !display.is_contents() && layout_node.paintable()) { |
202 | 0 | if (layout_node.paintable()->is_paintable_box()) { |
203 | 0 | auto const& paintable_box = static_cast<Painting::PaintableBox const&>(*layout_node.paintable()); |
204 | 0 | return used_value_getter(paintable_box); |
205 | 0 | } |
206 | 0 | dbgln("FIXME: Support getting used value for property `{}` on {}", string_from_property_id(property_id), layout_node.debug_description()); |
207 | 0 | } |
208 | 0 | return {}; |
209 | 0 | }; |
210 | |
|
211 | 0 | auto get_computed_value = [this](PropertyID property_id) { |
212 | 0 | if (m_pseudo_element.has_value()) |
213 | 0 | return m_element->pseudo_element_computed_css_values(m_pseudo_element.value())->property(property_id); |
214 | 0 | return m_element->computed_css_values()->property(property_id); |
215 | 0 | }; |
216 | | |
217 | | // A limited number of properties have special rules for producing their "resolved value". |
218 | | // We also have to manually construct shorthands from their longhands here. |
219 | | // Everything else uses the computed value. |
220 | | // https://www.w3.org/TR/cssom-1/#resolved-values |
221 | | |
222 | | // The resolved value for a given longhand property can be determined as follows: |
223 | 0 | switch (property_id) { |
224 | | // -> background-color |
225 | | // FIXME: -> border-block-end-color |
226 | | // FIXME: -> border-block-start-color |
227 | | // -> border-bottom-color |
228 | | // FIXME: -> border-inline-end-color |
229 | | // FIXME: -> border-inline-start-color |
230 | | // -> border-left-color |
231 | | // -> border-right-color |
232 | | // -> border-top-color |
233 | | // -> box-shadow |
234 | | // FIXME: -> caret-color |
235 | | // -> color |
236 | | // -> outline-color |
237 | | // -> A resolved value special case property like color defined in another specification |
238 | | // The resolved value is the used value. |
239 | 0 | case PropertyID::BackgroundColor: |
240 | 0 | return CSSColorValue::create_from_color(layout_node.computed_values().background_color()); |
241 | 0 | case PropertyID::BorderBottomColor: |
242 | 0 | return CSSColorValue::create_from_color(layout_node.computed_values().border_bottom().color); |
243 | 0 | case PropertyID::BorderLeftColor: |
244 | 0 | return CSSColorValue::create_from_color(layout_node.computed_values().border_left().color); |
245 | 0 | case PropertyID::BorderRightColor: |
246 | 0 | return CSSColorValue::create_from_color(layout_node.computed_values().border_right().color); |
247 | 0 | case PropertyID::BorderTopColor: |
248 | 0 | return CSSColorValue::create_from_color(layout_node.computed_values().border_top().color); |
249 | 0 | case PropertyID::BoxShadow: |
250 | 0 | return style_value_for_shadow(layout_node.computed_values().box_shadow()); |
251 | 0 | case PropertyID::Color: |
252 | 0 | return CSSColorValue::create_from_color(layout_node.computed_values().color()); |
253 | 0 | case PropertyID::OutlineColor: |
254 | 0 | return CSSColorValue::create_from_color(layout_node.computed_values().outline_color()); |
255 | 0 | case PropertyID::TextDecorationColor: |
256 | 0 | return CSSColorValue::create_from_color(layout_node.computed_values().text_decoration_color()); |
257 | | // NOTE: text-shadow isn't listed, but is computed the same as box-shadow. |
258 | 0 | case PropertyID::TextShadow: |
259 | 0 | return style_value_for_shadow(layout_node.computed_values().text_shadow()); |
260 | | |
261 | | // -> line-height |
262 | | // The resolved value is normal if the computed value is normal, or the used value otherwise. |
263 | 0 | case PropertyID::LineHeight: { |
264 | 0 | auto line_height = get_computed_value(property_id); |
265 | 0 | if (line_height->is_keyword() && line_height->to_keyword() == Keyword::Normal) |
266 | 0 | return line_height; |
267 | 0 | return LengthStyleValue::create(Length::make_px(layout_node.computed_values().line_height())); |
268 | 0 | } |
269 | | |
270 | | // FIXME: -> block-size |
271 | | // -> height |
272 | | // FIXME: -> inline-size |
273 | | // -> margin-block-end |
274 | | // -> margin-block-start |
275 | | // -> margin-bottom |
276 | | // -> margin-inline-end |
277 | | // -> margin-inline-start |
278 | | // -> margin-left |
279 | | // -> margin-right |
280 | | // -> margin-top |
281 | | // -> padding-block-end |
282 | | // -> padding-block-start |
283 | | // -> padding-bottom |
284 | | // -> padding-inline-end |
285 | | // -> padding-inline-start |
286 | | // -> padding-left |
287 | | // -> padding-right |
288 | | // -> padding-top |
289 | | // -> width |
290 | | // If the property applies to the element or pseudo-element and the resolved value of the |
291 | | // display property is not none or contents, then the resolved value is the used value. |
292 | | // Otherwise the resolved value is the computed value. |
293 | 0 | case PropertyID::Height: { |
294 | 0 | auto maybe_used_height = used_value_for_property([](auto const& paintable_box) { return paintable_box.content_height(); }); |
295 | 0 | if (maybe_used_height.has_value()) |
296 | 0 | return style_value_for_size(Size::make_px(maybe_used_height.release_value())); |
297 | 0 | return style_value_for_size(layout_node.computed_values().height()); |
298 | 0 | } |
299 | 0 | case PropertyID::MarginBlockEnd: |
300 | 0 | return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().margin(), LogicalSide::BlockEnd); |
301 | 0 | case PropertyID::MarginBlockStart: |
302 | 0 | return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().margin(), LogicalSide::BlockStart); |
303 | 0 | case PropertyID::MarginBottom: |
304 | 0 | return style_value_for_length_percentage(layout_node.computed_values().margin().bottom()); |
305 | 0 | case PropertyID::MarginInlineEnd: |
306 | 0 | return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().margin(), LogicalSide::InlineEnd); |
307 | 0 | case PropertyID::MarginInlineStart: |
308 | 0 | return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().margin(), LogicalSide::InlineStart); |
309 | 0 | case PropertyID::MarginLeft: |
310 | 0 | return style_value_for_length_percentage(layout_node.computed_values().margin().left()); |
311 | 0 | case PropertyID::MarginRight: |
312 | 0 | return style_value_for_length_percentage(layout_node.computed_values().margin().right()); |
313 | 0 | case PropertyID::MarginTop: |
314 | 0 | return style_value_for_length_percentage(layout_node.computed_values().margin().top()); |
315 | 0 | case PropertyID::PaddingBlockEnd: |
316 | 0 | return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().padding(), LogicalSide::BlockEnd); |
317 | 0 | case PropertyID::PaddingBlockStart: |
318 | 0 | return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().padding(), LogicalSide::BlockStart); |
319 | 0 | case PropertyID::PaddingBottom: |
320 | 0 | return style_value_for_length_percentage(layout_node.computed_values().padding().bottom()); |
321 | 0 | case PropertyID::PaddingInlineEnd: |
322 | 0 | return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().padding(), LogicalSide::InlineEnd); |
323 | 0 | case PropertyID::PaddingInlineStart: |
324 | 0 | return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().padding(), LogicalSide::InlineStart); |
325 | 0 | case PropertyID::PaddingLeft: |
326 | 0 | return style_value_for_length_percentage(layout_node.computed_values().padding().left()); |
327 | 0 | case PropertyID::PaddingRight: |
328 | 0 | return style_value_for_length_percentage(layout_node.computed_values().padding().right()); |
329 | 0 | case PropertyID::PaddingTop: |
330 | 0 | return style_value_for_length_percentage(layout_node.computed_values().padding().top()); |
331 | 0 | case PropertyID::Width: { |
332 | 0 | auto maybe_used_width = used_value_for_property([](auto const& paintable_box) { return paintable_box.content_width(); }); |
333 | 0 | if (maybe_used_width.has_value()) |
334 | 0 | return style_value_for_size(Size::make_px(maybe_used_width.release_value())); |
335 | 0 | return style_value_for_size(layout_node.computed_values().width()); |
336 | 0 | } |
337 | | |
338 | | // -> bottom |
339 | | // -> left |
340 | | // -> inset-block-end |
341 | | // -> inset-block-start |
342 | | // -> inset-inline-end |
343 | | // -> inset-inline-start |
344 | | // -> right |
345 | | // -> top |
346 | | // -> A resolved value special case property like top defined in another specification |
347 | | // FIXME: If the property applies to a positioned element and the resolved value of the display property is not |
348 | | // none or contents, and the property is not over-constrained, then the resolved value is the used value. |
349 | | // Otherwise the resolved value is the computed value. |
350 | 0 | case PropertyID::Bottom: |
351 | 0 | return style_value_for_length_percentage(layout_node.computed_values().inset().bottom()); |
352 | 0 | case PropertyID::InsetBlockEnd: |
353 | 0 | return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().inset(), LogicalSide::BlockEnd); |
354 | 0 | case PropertyID::InsetBlockStart: |
355 | 0 | return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().inset(), LogicalSide::BlockStart); |
356 | 0 | case PropertyID::InsetInlineEnd: |
357 | 0 | return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().inset(), LogicalSide::InlineEnd); |
358 | 0 | case PropertyID::InsetInlineStart: |
359 | 0 | return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().inset(), LogicalSide::InlineStart); |
360 | 0 | case PropertyID::Left: |
361 | 0 | return style_value_for_length_percentage(layout_node.computed_values().inset().left()); |
362 | 0 | case PropertyID::Right: |
363 | 0 | return style_value_for_length_percentage(layout_node.computed_values().inset().right()); |
364 | 0 | case PropertyID::Top: |
365 | 0 | return style_value_for_length_percentage(layout_node.computed_values().inset().top()); |
366 | | |
367 | | // -> A resolved value special case property defined in another specification |
368 | | // As defined in the relevant specification. |
369 | 0 | case PropertyID::Transform: { |
370 | 0 | auto transformations = layout_node.computed_values().transformations(); |
371 | 0 | if (transformations.is_empty()) |
372 | 0 | return CSSKeywordValue::create(Keyword::None); |
373 | | |
374 | | // https://drafts.csswg.org/css-transforms-2/#serialization-of-the-computed-value |
375 | | // The transform property is a resolved value special case property. [CSSOM] |
376 | | // When the computed value is a <transform-list>, the resolved value is one <matrix()> function or one <matrix3d()> function computed by the following algorithm: |
377 | | // 1. Let transform be a 4x4 matrix initialized to the identity matrix. |
378 | | // The elements m11, m22, m33 and m44 of transform must be set to 1; all other elements of transform must be set to 0. |
379 | 0 | auto transform = FloatMatrix4x4::identity(); |
380 | | |
381 | | // 2. Post-multiply all <transform-function>s in <transform-list> to transform. |
382 | 0 | VERIFY(layout_node.paintable()); |
383 | 0 | auto const& paintable_box = verify_cast<Painting::PaintableBox const>(*layout_node.paintable()); |
384 | 0 | for (auto transformation : transformations) { |
385 | 0 | transform = transform * transformation.to_matrix(paintable_box).release_value(); |
386 | 0 | } |
387 | | |
388 | | // https://drafts.csswg.org/css-transforms-1/#2d-matrix |
389 | 0 | auto is_2d_matrix = [](Gfx::FloatMatrix4x4 const& matrix) -> bool { |
390 | | // A 3x2 transformation matrix, |
391 | | // or a 4x4 matrix where the items m31, m32, m13, m23, m43, m14, m24, m34 are equal to 0 |
392 | | // and m33, m44 are equal to 1. |
393 | | // NOTE: We only care about 4x4 matrices here. |
394 | | // NOTE: Our elements are 0-indexed not 1-indexed, and in the opposite order. |
395 | 0 | if (matrix.elements()[0][2] != 0 // m31 |
396 | 0 | || matrix.elements()[1][2] != 0 // m32 |
397 | 0 | || matrix.elements()[2][0] != 0 // m13 |
398 | 0 | || matrix.elements()[2][1] != 0 // m23 |
399 | 0 | || matrix.elements()[2][3] != 0 // m43 |
400 | 0 | || matrix.elements()[3][0] != 0 // m14 |
401 | 0 | || matrix.elements()[3][1] != 0 // m24 |
402 | 0 | || matrix.elements()[3][2] != 0) // m34 |
403 | 0 | return false; |
404 | | |
405 | 0 | if (matrix.elements()[2][2] != 1 // m33 |
406 | 0 | || matrix.elements()[3][3] != 1) // m44 |
407 | 0 | return false; |
408 | | |
409 | 0 | return true; |
410 | 0 | }; |
411 | | |
412 | | // 3. Chose between <matrix()> or <matrix3d()> serialization: |
413 | | // -> If transform is a 2D matrix |
414 | | // Serialize transform to a <matrix()> function. |
415 | 0 | if (is_2d_matrix(transform)) { |
416 | 0 | StyleValueVector parameters { |
417 | 0 | NumberStyleValue::create(transform.elements()[0][0]), |
418 | 0 | NumberStyleValue::create(transform.elements()[1][0]), |
419 | 0 | NumberStyleValue::create(transform.elements()[0][1]), |
420 | 0 | NumberStyleValue::create(transform.elements()[1][1]), |
421 | 0 | NumberStyleValue::create(transform.elements()[0][3]), |
422 | 0 | NumberStyleValue::create(transform.elements()[1][3]), |
423 | 0 | }; |
424 | 0 | return TransformationStyleValue::create(TransformFunction::Matrix, move(parameters)); |
425 | 0 | } |
426 | | // -> Otherwise |
427 | | // Serialize transform to a <matrix3d()> function. |
428 | 0 | else { |
429 | 0 | StyleValueVector parameters { |
430 | 0 | NumberStyleValue::create(transform.elements()[0][0]), |
431 | 0 | NumberStyleValue::create(transform.elements()[1][0]), |
432 | 0 | NumberStyleValue::create(transform.elements()[2][0]), |
433 | 0 | NumberStyleValue::create(transform.elements()[3][0]), |
434 | 0 | NumberStyleValue::create(transform.elements()[0][1]), |
435 | 0 | NumberStyleValue::create(transform.elements()[1][1]), |
436 | 0 | NumberStyleValue::create(transform.elements()[2][1]), |
437 | 0 | NumberStyleValue::create(transform.elements()[3][1]), |
438 | 0 | NumberStyleValue::create(transform.elements()[0][2]), |
439 | 0 | NumberStyleValue::create(transform.elements()[1][2]), |
440 | 0 | NumberStyleValue::create(transform.elements()[2][2]), |
441 | 0 | NumberStyleValue::create(transform.elements()[3][2]), |
442 | 0 | NumberStyleValue::create(transform.elements()[0][3]), |
443 | 0 | NumberStyleValue::create(transform.elements()[1][3]), |
444 | 0 | NumberStyleValue::create(transform.elements()[2][3]), |
445 | 0 | NumberStyleValue::create(transform.elements()[3][3]), |
446 | 0 | }; |
447 | 0 | return TransformationStyleValue::create(TransformFunction::Matrix3d, move(parameters)); |
448 | 0 | } |
449 | 0 | } |
450 | | |
451 | | // -> Any other property |
452 | | // The resolved value is the computed value. |
453 | | // NOTE: This is handled inside the `default` case. |
454 | | |
455 | | // NOTE: Everything below is a shorthand that requires some manual construction. |
456 | 0 | case PropertyID::BackgroundPosition: |
457 | 0 | return style_value_for_background_property( |
458 | 0 | layout_node, |
459 | 0 | [](auto& layer) -> NonnullRefPtr<CSSStyleValue> { |
460 | 0 | return PositionStyleValue::create( |
461 | 0 | EdgeStyleValue::create(layer.position_edge_x, layer.position_offset_x), |
462 | 0 | EdgeStyleValue::create(layer.position_edge_y, layer.position_offset_y)); |
463 | 0 | }, |
464 | 0 | []() -> NonnullRefPtr<CSSStyleValue> { |
465 | 0 | return PositionStyleValue::create( |
466 | 0 | EdgeStyleValue::create(PositionEdge::Left, Percentage(0)), |
467 | 0 | EdgeStyleValue::create(PositionEdge::Top, Percentage(0))); |
468 | 0 | }); |
469 | 0 | case PropertyID::Border: { |
470 | 0 | auto width = style_value_for_property(layout_node, PropertyID::BorderWidth); |
471 | 0 | auto style = style_value_for_property(layout_node, PropertyID::BorderStyle); |
472 | 0 | auto color = style_value_for_property(layout_node, PropertyID::BorderColor); |
473 | | // `border` only has a reasonable value if all four sides are the same. |
474 | 0 | if (width->is_value_list() || style->is_value_list() || color->is_value_list()) |
475 | 0 | return nullptr; |
476 | 0 | return ShorthandStyleValue::create(property_id, |
477 | 0 | { PropertyID::BorderWidth, PropertyID::BorderStyle, PropertyID::BorderColor }, |
478 | 0 | { width.release_nonnull(), style.release_nonnull(), color.release_nonnull() }); |
479 | 0 | } |
480 | 0 | case PropertyID::BorderColor: { |
481 | 0 | auto top = style_value_for_property(layout_node, PropertyID::BorderTopColor); |
482 | 0 | auto right = style_value_for_property(layout_node, PropertyID::BorderRightColor); |
483 | 0 | auto bottom = style_value_for_property(layout_node, PropertyID::BorderBottomColor); |
484 | 0 | auto left = style_value_for_property(layout_node, PropertyID::BorderLeftColor); |
485 | 0 | return style_value_for_sided_shorthand(top.release_nonnull(), right.release_nonnull(), bottom.release_nonnull(), left.release_nonnull()); |
486 | 0 | } |
487 | 0 | case PropertyID::BorderStyle: { |
488 | 0 | auto top = style_value_for_property(layout_node, PropertyID::BorderTopStyle); |
489 | 0 | auto right = style_value_for_property(layout_node, PropertyID::BorderRightStyle); |
490 | 0 | auto bottom = style_value_for_property(layout_node, PropertyID::BorderBottomStyle); |
491 | 0 | auto left = style_value_for_property(layout_node, PropertyID::BorderLeftStyle); |
492 | 0 | return style_value_for_sided_shorthand(top.release_nonnull(), right.release_nonnull(), bottom.release_nonnull(), left.release_nonnull()); |
493 | 0 | } |
494 | 0 | case PropertyID::BorderWidth: { |
495 | 0 | auto top = style_value_for_property(layout_node, PropertyID::BorderTopWidth); |
496 | 0 | auto right = style_value_for_property(layout_node, PropertyID::BorderRightWidth); |
497 | 0 | auto bottom = style_value_for_property(layout_node, PropertyID::BorderBottomWidth); |
498 | 0 | auto left = style_value_for_property(layout_node, PropertyID::BorderLeftWidth); |
499 | 0 | return style_value_for_sided_shorthand(top.release_nonnull(), right.release_nonnull(), bottom.release_nonnull(), left.release_nonnull()); |
500 | 0 | } |
501 | 0 | case PropertyID::Margin: { |
502 | 0 | auto top = style_value_for_property(layout_node, PropertyID::MarginTop); |
503 | 0 | auto right = style_value_for_property(layout_node, PropertyID::MarginRight); |
504 | 0 | auto bottom = style_value_for_property(layout_node, PropertyID::MarginBottom); |
505 | 0 | auto left = style_value_for_property(layout_node, PropertyID::MarginLeft); |
506 | 0 | return style_value_for_sided_shorthand(top.release_nonnull(), right.release_nonnull(), bottom.release_nonnull(), left.release_nonnull()); |
507 | 0 | } |
508 | 0 | case PropertyID::Padding: { |
509 | 0 | auto top = style_value_for_property(layout_node, PropertyID::PaddingTop); |
510 | 0 | auto right = style_value_for_property(layout_node, PropertyID::PaddingRight); |
511 | 0 | auto bottom = style_value_for_property(layout_node, PropertyID::PaddingBottom); |
512 | 0 | auto left = style_value_for_property(layout_node, PropertyID::PaddingLeft); |
513 | 0 | return style_value_for_sided_shorthand(top.release_nonnull(), right.release_nonnull(), bottom.release_nonnull(), left.release_nonnull()); |
514 | 0 | } |
515 | 0 | case PropertyID::WebkitTextFillColor: |
516 | 0 | return CSSColorValue::create_from_color(layout_node.computed_values().webkit_text_fill_color()); |
517 | 0 | case PropertyID::Invalid: |
518 | 0 | return CSSKeywordValue::create(Keyword::Invalid); |
519 | 0 | case PropertyID::Custom: |
520 | 0 | dbgln_if(LIBWEB_CSS_DEBUG, "Computed style for custom properties was requested (?)"); |
521 | 0 | return nullptr; |
522 | 0 | default: |
523 | 0 | if (!property_is_shorthand(property_id)) |
524 | 0 | return get_computed_value(property_id); |
525 | | |
526 | | // Handle shorthands in a generic way |
527 | 0 | auto longhand_ids = longhands_for_shorthand(property_id); |
528 | 0 | StyleValueVector longhand_values; |
529 | 0 | longhand_values.ensure_capacity(longhand_ids.size()); |
530 | 0 | for (auto longhand_id : longhand_ids) |
531 | 0 | longhand_values.append(style_value_for_property(layout_node, longhand_id).release_nonnull()); |
532 | 0 | return ShorthandStyleValue::create(property_id, move(longhand_ids), move(longhand_values)); |
533 | 0 | } |
534 | 0 | } |
535 | | |
536 | | Optional<StyleProperty> ResolvedCSSStyleDeclaration::property(PropertyID property_id) const |
537 | 0 | { |
538 | | // https://www.w3.org/TR/cssom-1/#dom-window-getcomputedstyle |
539 | | // NOTE: This is a partial enforcement of step 5 ("If elt is connected, ...") |
540 | 0 | if (!m_element->is_connected()) |
541 | 0 | return {}; |
542 | | |
543 | 0 | auto get_layout_node = [&]() { |
544 | 0 | if (m_pseudo_element.has_value()) |
545 | 0 | return m_element->get_pseudo_element_node(m_pseudo_element.value()); |
546 | 0 | return m_element->layout_node(); |
547 | 0 | }; |
548 | |
|
549 | 0 | Layout::NodeWithStyle* layout_node = get_layout_node(); |
550 | | |
551 | | // FIXME: Be smarter about updating layout if there's no layout node. |
552 | | // We may legitimately have no layout node if we're not visible, but this protects against situations |
553 | | // where we're requesting the computed style before layout has happened. |
554 | 0 | if (!layout_node || property_affects_layout(property_id)) { |
555 | 0 | const_cast<DOM::Document&>(m_element->document()).update_layout(); |
556 | 0 | layout_node = get_layout_node(); |
557 | 0 | } else { |
558 | | // FIXME: If we had a way to update style for a single element, this would be a good place to use it. |
559 | 0 | const_cast<DOM::Document&>(m_element->document()).update_style(); |
560 | 0 | } |
561 | |
|
562 | 0 | if (!layout_node) { |
563 | 0 | auto style = m_element->document().style_computer().compute_style(const_cast<DOM::Element&>(*m_element), m_pseudo_element); |
564 | | |
565 | | // FIXME: This is a stopgap until we implement shorthand -> longhand conversion. |
566 | 0 | auto value = style->maybe_null_property(property_id); |
567 | 0 | if (!value) { |
568 | 0 | dbgln("FIXME: ResolvedCSSStyleDeclaration::property(property_id={:#x}) No value for property ID in newly computed style case.", to_underlying(property_id)); |
569 | 0 | return {}; |
570 | 0 | } |
571 | 0 | return StyleProperty { |
572 | 0 | .property_id = property_id, |
573 | 0 | .value = value.release_nonnull(), |
574 | 0 | }; |
575 | 0 | } |
576 | | |
577 | 0 | auto value = style_value_for_property(*layout_node, property_id); |
578 | 0 | if (!value) |
579 | 0 | return {}; |
580 | 0 | return StyleProperty { |
581 | 0 | .property_id = property_id, |
582 | 0 | .value = value.release_nonnull(), |
583 | 0 | }; |
584 | 0 | } |
585 | | |
586 | | static WebIDL::ExceptionOr<void> cannot_modify_computed_property_error(JS::Realm& realm) |
587 | 0 | { |
588 | 0 | return WebIDL::NoModificationAllowedError::create(realm, "Cannot modify properties in result of getComputedStyle()"_string); |
589 | 0 | } |
590 | | |
591 | | // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty |
592 | | WebIDL::ExceptionOr<void> ResolvedCSSStyleDeclaration::set_property(PropertyID, StringView, StringView) |
593 | 0 | { |
594 | | // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. |
595 | 0 | return cannot_modify_computed_property_error(realm()); |
596 | 0 | } |
597 | | |
598 | | // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty |
599 | | WebIDL::ExceptionOr<void> ResolvedCSSStyleDeclaration::set_property(StringView, StringView, StringView) |
600 | 0 | { |
601 | | // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. |
602 | 0 | return cannot_modify_computed_property_error(realm()); |
603 | 0 | } |
604 | | |
605 | | static WebIDL::ExceptionOr<String> cannot_remove_computed_property_error(JS::Realm& realm) |
606 | 0 | { |
607 | 0 | return WebIDL::NoModificationAllowedError::create(realm, "Cannot remove properties from result of getComputedStyle()"_string); |
608 | 0 | } |
609 | | |
610 | | // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty |
611 | | WebIDL::ExceptionOr<String> ResolvedCSSStyleDeclaration::remove_property(PropertyID) |
612 | 0 | { |
613 | | // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. |
614 | 0 | return cannot_remove_computed_property_error(realm()); |
615 | 0 | } |
616 | | |
617 | | // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty |
618 | | WebIDL::ExceptionOr<String> ResolvedCSSStyleDeclaration::remove_property(StringView) |
619 | 0 | { |
620 | | // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. |
621 | 0 | return cannot_remove_computed_property_error(realm()); |
622 | 0 | } |
623 | | |
624 | | String ResolvedCSSStyleDeclaration::serialized() const |
625 | 0 | { |
626 | | // https://www.w3.org/TR/cssom/#dom-cssstyledeclaration-csstext |
627 | | // If the computed flag is set, then return the empty string. |
628 | | |
629 | | // NOTE: ResolvedCSSStyleDeclaration is something you would only get from window.getComputedStyle(), |
630 | | // which returns what the spec calls "resolved style". The "computed flag" is always set here. |
631 | 0 | return String {}; |
632 | 0 | } |
633 | | |
634 | | // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext |
635 | | WebIDL::ExceptionOr<void> ResolvedCSSStyleDeclaration::set_css_text(StringView) |
636 | 0 | { |
637 | | // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. |
638 | 0 | return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties in result of getComputedStyle()"_string); |
639 | 0 | } |
640 | | |
641 | | } |