/src/serenity/Userland/Libraries/LibWeb/Layout/Node.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <AK/Demangle.h> |
9 | | #include <LibWeb/CSS/StyleValues/AbstractImageStyleValue.h> |
10 | | #include <LibWeb/CSS/StyleValues/BackgroundRepeatStyleValue.h> |
11 | | #include <LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h> |
12 | | #include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h> |
13 | | #include <LibWeb/CSS/StyleValues/CSSKeywordValue.h> |
14 | | #include <LibWeb/CSS/StyleValues/EdgeStyleValue.h> |
15 | | #include <LibWeb/CSS/StyleValues/IntegerStyleValue.h> |
16 | | #include <LibWeb/CSS/StyleValues/LengthStyleValue.h> |
17 | | #include <LibWeb/CSS/StyleValues/MathDepthStyleValue.h> |
18 | | #include <LibWeb/CSS/StyleValues/NumberStyleValue.h> |
19 | | #include <LibWeb/CSS/StyleValues/PercentageStyleValue.h> |
20 | | #include <LibWeb/CSS/StyleValues/RatioStyleValue.h> |
21 | | #include <LibWeb/CSS/StyleValues/StyleValueList.h> |
22 | | #include <LibWeb/CSS/StyleValues/TimeStyleValue.h> |
23 | | #include <LibWeb/CSS/StyleValues/URLStyleValue.h> |
24 | | #include <LibWeb/DOM/Document.h> |
25 | | #include <LibWeb/Dump.h> |
26 | | #include <LibWeb/HTML/BrowsingContext.h> |
27 | | #include <LibWeb/HTML/HTMLHtmlElement.h> |
28 | | #include <LibWeb/Layout/BlockContainer.h> |
29 | | #include <LibWeb/Layout/FormattingContext.h> |
30 | | #include <LibWeb/Layout/Node.h> |
31 | | #include <LibWeb/Layout/TableWrapper.h> |
32 | | #include <LibWeb/Layout/TextNode.h> |
33 | | #include <LibWeb/Layout/Viewport.h> |
34 | | #include <LibWeb/Page/Page.h> |
35 | | #include <LibWeb/Painting/InlinePaintable.h> |
36 | | #include <LibWeb/Platform/FontPlugin.h> |
37 | | |
38 | | namespace Web::Layout { |
39 | | |
40 | | Node::Node(DOM::Document& document, DOM::Node* node) |
41 | 0 | : m_dom_node(node ? *node : document) |
42 | 0 | , m_browsing_context(*document.browsing_context()) |
43 | 0 | , m_anonymous(node == nullptr) |
44 | 0 | { |
45 | 0 | if (node) |
46 | 0 | node->set_layout_node({}, *this); |
47 | 0 | } |
48 | | |
49 | 0 | Node::~Node() = default; |
50 | | |
51 | | void Node::visit_edges(Cell::Visitor& visitor) |
52 | 0 | { |
53 | 0 | Base::visit_edges(visitor); |
54 | 0 | visitor.visit(m_dom_node); |
55 | 0 | visitor.visit(m_paintable); |
56 | 0 | visitor.visit(m_pseudo_element_generator); |
57 | 0 | visitor.visit(m_browsing_context); |
58 | 0 | TreeNode::visit_edges(visitor); |
59 | 0 | } |
60 | | |
61 | | // https://www.w3.org/TR/css-display-3/#out-of-flow |
62 | | bool Node::is_out_of_flow(FormattingContext const& formatting_context) const |
63 | 0 | { |
64 | | // A layout node is out of flow if either: |
65 | | |
66 | | // 1. It is floated (which requires that floating is not inhibited). |
67 | 0 | if (!formatting_context.inhibits_floating() && computed_values().float_() != CSS::Float::None) |
68 | 0 | return true; |
69 | | |
70 | | // 2. It is "absolutely positioned". |
71 | 0 | if (is_absolutely_positioned()) |
72 | 0 | return true; |
73 | | |
74 | 0 | return false; |
75 | 0 | } |
76 | | |
77 | | bool Node::can_contain_boxes_with_position_absolute() const |
78 | 0 | { |
79 | 0 | if (computed_values().position() != CSS::Positioning::Static) |
80 | 0 | return true; |
81 | | |
82 | 0 | if (is<Viewport>(*this)) |
83 | 0 | return true; |
84 | | |
85 | | // https://w3c.github.io/csswg-drafts/css-transforms-1/#propdef-transform |
86 | | // Any computed value other than none for the transform affects containing block and stacking context |
87 | 0 | if (!computed_values().transformations().is_empty()) |
88 | 0 | return true; |
89 | | |
90 | 0 | return false; |
91 | 0 | } |
92 | | |
93 | | static Box const* nearest_ancestor_capable_of_forming_a_containing_block(Node const& node) |
94 | 0 | { |
95 | 0 | for (auto const* ancestor = node.parent(); ancestor; ancestor = ancestor->parent()) { |
96 | 0 | if (ancestor->is_block_container() |
97 | 0 | || ancestor->display().is_flex_inside() |
98 | 0 | || ancestor->display().is_grid_inside() |
99 | 0 | || ancestor->is_svg_svg_box()) { |
100 | 0 | return verify_cast<Box>(ancestor); |
101 | 0 | } |
102 | 0 | } |
103 | 0 | return nullptr; |
104 | 0 | } |
105 | | |
106 | | Box const* Node::containing_block() const |
107 | 0 | { |
108 | 0 | if (is<TextNode>(*this)) |
109 | 0 | return nearest_ancestor_capable_of_forming_a_containing_block(*this); |
110 | | |
111 | 0 | auto position = computed_values().position(); |
112 | | |
113 | | // https://drafts.csswg.org/css-position-3/#absolute-cb |
114 | 0 | if (position == CSS::Positioning::Absolute) { |
115 | 0 | auto const* ancestor = parent(); |
116 | 0 | while (ancestor && !ancestor->can_contain_boxes_with_position_absolute()) |
117 | 0 | ancestor = ancestor->parent(); |
118 | 0 | while (ancestor && ancestor->is_anonymous()) |
119 | 0 | ancestor = nearest_ancestor_capable_of_forming_a_containing_block(*ancestor); |
120 | 0 | return static_cast<Box const*>(ancestor); |
121 | 0 | } |
122 | | |
123 | 0 | if (position == CSS::Positioning::Fixed) |
124 | 0 | return &root(); |
125 | | |
126 | 0 | return nearest_ancestor_capable_of_forming_a_containing_block(*this); |
127 | 0 | } |
128 | | |
129 | | Box const* Node::static_position_containing_block() const |
130 | 0 | { |
131 | 0 | return nearest_ancestor_capable_of_forming_a_containing_block(*this); |
132 | 0 | } |
133 | | |
134 | | Box const* Node::non_anonymous_containing_block() const |
135 | 0 | { |
136 | 0 | auto nearest_ancestor_box = containing_block(); |
137 | 0 | VERIFY(nearest_ancestor_box); |
138 | 0 | while (nearest_ancestor_box->is_anonymous()) { |
139 | 0 | nearest_ancestor_box = nearest_ancestor_box->containing_block(); |
140 | 0 | VERIFY(nearest_ancestor_box); |
141 | 0 | } |
142 | 0 | return nearest_ancestor_box; |
143 | 0 | } |
144 | | |
145 | | // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context |
146 | | bool Node::establishes_stacking_context() const |
147 | 0 | { |
148 | | // NOTE: While MDN is not authoritative, there isn't a single convenient location |
149 | | // in the CSS specifications where the rules for stacking contexts is described. |
150 | | // That's why the "spec link" here points to MDN. |
151 | |
|
152 | 0 | if (!has_style()) |
153 | 0 | return false; |
154 | | |
155 | | // We make a stacking context for the viewport. Painting and hit testing starts from here. |
156 | 0 | if (is_viewport()) |
157 | 0 | return true; |
158 | | |
159 | | // Root element of the document (<html>). |
160 | 0 | if (is_root_element()) |
161 | 0 | return true; |
162 | | |
163 | 0 | auto position = computed_values().position(); |
164 | | |
165 | | // Element with a position value absolute or relative and z-index value other than auto. |
166 | 0 | if (position == CSS::Positioning::Absolute || position == CSS::Positioning::Relative) { |
167 | 0 | if (computed_values().z_index().has_value()) { |
168 | 0 | return true; |
169 | 0 | } |
170 | 0 | } |
171 | | |
172 | | // Element with a position value fixed or sticky. |
173 | 0 | if (position == CSS::Positioning::Fixed || position == CSS::Positioning::Sticky) |
174 | 0 | return true; |
175 | | |
176 | 0 | if (!computed_values().transformations().is_empty()) |
177 | 0 | return true; |
178 | | |
179 | | // Element that is a child of a flex container, with z-index value other than auto. |
180 | 0 | if (parent() && parent()->display().is_flex_inside() && computed_values().z_index().has_value()) |
181 | 0 | return true; |
182 | | |
183 | | // Element that is a child of a grid container, with z-index value other than auto. |
184 | 0 | if (parent() && parent()->display().is_grid_inside() && computed_values().z_index().has_value()) |
185 | 0 | return true; |
186 | | |
187 | | // https://drafts.fxtf.org/filter-effects-2/#backdrop-filter-operation |
188 | | // A computed value of other than none results in the creation of both a stacking context [CSS21] and a Containing Block for absolute and fixed position descendants, |
189 | | // unless the element it applies to is a document root element in the current browsing context. |
190 | | // Spec Note: This rule works in the same way as for the filter property. |
191 | 0 | if (!computed_values().backdrop_filter().is_none()) |
192 | 0 | return true; |
193 | | |
194 | | // Element with any of the following properties with value other than none: |
195 | | // - transform |
196 | | // - filter |
197 | | // - backdrop-filter |
198 | | // - perspective |
199 | | // - clip-path |
200 | | // - mask / mask-image / mask-border |
201 | 0 | if (computed_values().mask().has_value() || computed_values().clip_path().has_value()) |
202 | 0 | return true; |
203 | | |
204 | 0 | return computed_values().opacity() < 1.0f; |
205 | 0 | } |
206 | | |
207 | | HTML::BrowsingContext const& Node::browsing_context() const |
208 | 0 | { |
209 | 0 | return *m_browsing_context; |
210 | 0 | } |
211 | | |
212 | | HTML::BrowsingContext& Node::browsing_context() |
213 | 0 | { |
214 | 0 | return *m_browsing_context; |
215 | 0 | } |
216 | | |
217 | | JS::GCPtr<HTML::Navigable> Node::navigable() const |
218 | 0 | { |
219 | 0 | return document().navigable(); |
220 | 0 | } |
221 | | |
222 | | Viewport const& Node::root() const |
223 | 0 | { |
224 | 0 | VERIFY(document().layout_node()); |
225 | 0 | return *document().layout_node(); |
226 | 0 | } |
227 | | |
228 | | Viewport& Node::root() |
229 | 0 | { |
230 | 0 | VERIFY(document().layout_node()); |
231 | 0 | return *document().layout_node(); |
232 | 0 | } |
233 | | |
234 | | bool Node::is_floating() const |
235 | 0 | { |
236 | 0 | if (!has_style()) |
237 | 0 | return false; |
238 | | // flex-items don't float. |
239 | 0 | if (is_flex_item()) |
240 | 0 | return false; |
241 | 0 | return computed_values().float_() != CSS::Float::None; |
242 | 0 | } |
243 | | |
244 | | bool Node::is_positioned() const |
245 | 0 | { |
246 | 0 | return has_style() && computed_values().position() != CSS::Positioning::Static; |
247 | 0 | } |
248 | | |
249 | | bool Node::is_absolutely_positioned() const |
250 | 0 | { |
251 | 0 | if (!has_style()) |
252 | 0 | return false; |
253 | 0 | auto position = computed_values().position(); |
254 | 0 | return position == CSS::Positioning::Absolute || position == CSS::Positioning::Fixed; |
255 | 0 | } |
256 | | |
257 | | bool Node::is_fixed_position() const |
258 | 0 | { |
259 | 0 | if (!has_style()) |
260 | 0 | return false; |
261 | 0 | auto position = computed_values().position(); |
262 | 0 | return position == CSS::Positioning::Fixed; |
263 | 0 | } |
264 | | |
265 | | NodeWithStyle::NodeWithStyle(DOM::Document& document, DOM::Node* node, NonnullRefPtr<CSS::StyleProperties> computed_style) |
266 | 0 | : Node(document, node) |
267 | 0 | , m_computed_values(make<CSS::ComputedValues>()) |
268 | 0 | { |
269 | 0 | m_has_style = true; |
270 | 0 | apply_style(*computed_style); |
271 | 0 | } |
272 | | |
273 | | NodeWithStyle::NodeWithStyle(DOM::Document& document, DOM::Node* node, NonnullOwnPtr<CSS::ComputedValues> computed_values) |
274 | 0 | : Node(document, node) |
275 | 0 | , m_computed_values(move(computed_values)) |
276 | 0 | { |
277 | 0 | m_has_style = true; |
278 | 0 | } |
279 | | |
280 | | void NodeWithStyle::visit_edges(Visitor& visitor) |
281 | 0 | { |
282 | 0 | Base::visit_edges(visitor); |
283 | 0 | for (auto& layer : computed_values().background_layers()) { |
284 | 0 | if (layer.background_image && layer.background_image->is_image()) |
285 | 0 | layer.background_image->as_image().visit_edges(visitor); |
286 | 0 | } |
287 | 0 | if (m_list_style_image && m_list_style_image->is_image()) |
288 | 0 | m_list_style_image->as_image().visit_edges(visitor); |
289 | 0 | } |
290 | | |
291 | | // https://www.w3.org/TR/css-values-4/#snap-a-length-as-a-border-width |
292 | | static CSSPixels snap_a_length_as_a_border_width(double device_pixels_per_css_pixel, CSSPixels length) |
293 | 0 | { |
294 | | // 1. Assert: len is non-negative. |
295 | 0 | VERIFY(length >= 0); |
296 | | |
297 | | // 2. If len is an integer number of device pixels, do nothing. |
298 | 0 | auto device_pixels = length.to_double() * device_pixels_per_css_pixel; |
299 | 0 | if (device_pixels == trunc(device_pixels)) |
300 | 0 | return length; |
301 | | |
302 | | // 3. If len is greater than zero, but less than 1 device pixel, round len up to 1 device pixel. |
303 | 0 | if (device_pixels > 0 && device_pixels < 1) |
304 | 0 | return CSSPixels::nearest_value_for(1 / device_pixels_per_css_pixel); |
305 | | |
306 | | // 4. If len is greater than 1 device pixel, round it down to the nearest integer number of device pixels. |
307 | 0 | if (device_pixels > 1) |
308 | 0 | return CSSPixels::nearest_value_for(floor(device_pixels) / device_pixels_per_css_pixel); |
309 | | |
310 | 0 | return length; |
311 | 0 | } |
312 | | |
313 | | void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style) |
314 | 0 | { |
315 | 0 | auto& computed_values = mutable_computed_values(); |
316 | | |
317 | | // NOTE: color must be set first to ensure currentColor can be resolved in other properties (e.g. background-color). |
318 | 0 | computed_values.set_color(computed_style.color_or_fallback(CSS::PropertyID::Color, *this, CSS::InitialValues::color())); |
319 | | |
320 | | // NOTE: We have to be careful that font-related properties get set in the right order. |
321 | | // m_font is used by Length::to_px() when resolving sizes against this layout node. |
322 | | // That's why it has to be set before everything else. |
323 | 0 | computed_values.set_font_list(computed_style.computed_font_list()); |
324 | 0 | computed_values.set_font_size(computed_style.property(CSS::PropertyID::FontSize)->as_length().length().to_px(*this)); |
325 | 0 | computed_values.set_font_weight(round_to<int>(computed_style.property(CSS::PropertyID::FontWeight)->as_number().number())); |
326 | 0 | computed_values.set_line_height(computed_style.line_height()); |
327 | |
|
328 | 0 | computed_values.set_vertical_align(computed_style.vertical_align()); |
329 | |
|
330 | 0 | { |
331 | 0 | auto attachments = computed_style.property(CSS::PropertyID::BackgroundAttachment); |
332 | 0 | auto clips = computed_style.property(CSS::PropertyID::BackgroundClip); |
333 | 0 | auto images = computed_style.property(CSS::PropertyID::BackgroundImage); |
334 | 0 | auto origins = computed_style.property(CSS::PropertyID::BackgroundOrigin); |
335 | 0 | auto x_positions = computed_style.property(CSS::PropertyID::BackgroundPositionX); |
336 | 0 | auto y_positions = computed_style.property(CSS::PropertyID::BackgroundPositionY); |
337 | 0 | auto repeats = computed_style.property(CSS::PropertyID::BackgroundRepeat); |
338 | 0 | auto sizes = computed_style.property(CSS::PropertyID::BackgroundSize); |
339 | |
|
340 | 0 | auto count_layers = [](auto maybe_style_value) -> size_t { |
341 | 0 | if (maybe_style_value->is_value_list()) |
342 | 0 | return maybe_style_value->as_value_list().size(); |
343 | 0 | else |
344 | 0 | return 1; |
345 | 0 | }; |
346 | |
|
347 | 0 | auto value_for_layer = [](auto& style_value, size_t layer_index) -> RefPtr<CSS::CSSStyleValue const> { |
348 | 0 | if (style_value->is_value_list()) |
349 | 0 | return style_value->as_value_list().value_at(layer_index, true); |
350 | 0 | return style_value; |
351 | 0 | }; |
352 | |
|
353 | 0 | size_t layer_count = 1; |
354 | 0 | layer_count = max(layer_count, count_layers(attachments)); |
355 | 0 | layer_count = max(layer_count, count_layers(clips)); |
356 | 0 | layer_count = max(layer_count, count_layers(images)); |
357 | 0 | layer_count = max(layer_count, count_layers(origins)); |
358 | 0 | layer_count = max(layer_count, count_layers(x_positions)); |
359 | 0 | layer_count = max(layer_count, count_layers(y_positions)); |
360 | 0 | layer_count = max(layer_count, count_layers(repeats)); |
361 | 0 | layer_count = max(layer_count, count_layers(sizes)); |
362 | |
|
363 | 0 | Vector<CSS::BackgroundLayerData> layers; |
364 | 0 | layers.ensure_capacity(layer_count); |
365 | |
|
366 | 0 | for (size_t layer_index = 0; layer_index < layer_count; layer_index++) { |
367 | 0 | CSS::BackgroundLayerData layer; |
368 | |
|
369 | 0 | if (auto image_value = value_for_layer(images, layer_index); image_value) { |
370 | 0 | if (image_value->is_abstract_image()) { |
371 | 0 | layer.background_image = image_value->as_abstract_image(); |
372 | 0 | const_cast<CSS::AbstractImageStyleValue&>(*layer.background_image).load_any_resources(document()); |
373 | 0 | } |
374 | 0 | } |
375 | |
|
376 | 0 | if (auto attachment_value = value_for_layer(attachments, layer_index); attachment_value && attachment_value->is_keyword()) { |
377 | 0 | switch (attachment_value->to_keyword()) { |
378 | 0 | case CSS::Keyword::Fixed: |
379 | 0 | layer.attachment = CSS::BackgroundAttachment::Fixed; |
380 | 0 | break; |
381 | 0 | case CSS::Keyword::Local: |
382 | 0 | layer.attachment = CSS::BackgroundAttachment::Local; |
383 | 0 | break; |
384 | 0 | case CSS::Keyword::Scroll: |
385 | 0 | layer.attachment = CSS::BackgroundAttachment::Scroll; |
386 | 0 | break; |
387 | 0 | default: |
388 | 0 | break; |
389 | 0 | } |
390 | 0 | } |
391 | | |
392 | 0 | auto as_box = [](auto keyword) { |
393 | 0 | switch (keyword) { |
394 | 0 | case CSS::Keyword::BorderBox: |
395 | 0 | return CSS::BackgroundBox::BorderBox; |
396 | 0 | case CSS::Keyword::ContentBox: |
397 | 0 | return CSS::BackgroundBox::ContentBox; |
398 | 0 | case CSS::Keyword::PaddingBox: |
399 | 0 | return CSS::BackgroundBox::PaddingBox; |
400 | 0 | case CSS::Keyword::Text: |
401 | 0 | return CSS::BackgroundBox::Text; |
402 | 0 | default: |
403 | 0 | VERIFY_NOT_REACHED(); |
404 | 0 | } |
405 | 0 | }; |
406 | |
|
407 | 0 | if (auto origin_value = value_for_layer(origins, layer_index); origin_value && origin_value->is_keyword()) { |
408 | 0 | layer.origin = as_box(origin_value->to_keyword()); |
409 | 0 | } |
410 | |
|
411 | 0 | if (auto clip_value = value_for_layer(clips, layer_index); clip_value && clip_value->is_keyword()) { |
412 | 0 | layer.clip = as_box(clip_value->to_keyword()); |
413 | 0 | } |
414 | |
|
415 | 0 | if (auto position_value = value_for_layer(x_positions, layer_index); position_value && position_value->is_edge()) { |
416 | 0 | auto& position = position_value->as_edge(); |
417 | 0 | layer.position_edge_x = position.edge(); |
418 | 0 | layer.position_offset_x = position.offset(); |
419 | 0 | } |
420 | |
|
421 | 0 | if (auto position_value = value_for_layer(y_positions, layer_index); position_value && position_value->is_edge()) { |
422 | 0 | auto& position = position_value->as_edge(); |
423 | 0 | layer.position_edge_y = position.edge(); |
424 | 0 | layer.position_offset_y = position.offset(); |
425 | 0 | }; |
426 | |
|
427 | 0 | if (auto size_value = value_for_layer(sizes, layer_index); size_value) { |
428 | 0 | if (size_value->is_background_size()) { |
429 | 0 | auto& size = size_value->as_background_size(); |
430 | 0 | layer.size_type = CSS::BackgroundSize::LengthPercentage; |
431 | 0 | layer.size_x = size.size_x(); |
432 | 0 | layer.size_y = size.size_y(); |
433 | 0 | } else if (size_value->is_keyword()) { |
434 | 0 | switch (size_value->to_keyword()) { |
435 | 0 | case CSS::Keyword::Contain: |
436 | 0 | layer.size_type = CSS::BackgroundSize::Contain; |
437 | 0 | break; |
438 | 0 | case CSS::Keyword::Cover: |
439 | 0 | layer.size_type = CSS::BackgroundSize::Cover; |
440 | 0 | break; |
441 | 0 | default: |
442 | 0 | break; |
443 | 0 | } |
444 | 0 | } |
445 | 0 | } |
446 | | |
447 | 0 | if (auto repeat_value = value_for_layer(repeats, layer_index); repeat_value && repeat_value->is_background_repeat()) { |
448 | 0 | layer.repeat_x = repeat_value->as_background_repeat().repeat_x(); |
449 | 0 | layer.repeat_y = repeat_value->as_background_repeat().repeat_y(); |
450 | 0 | } |
451 | |
|
452 | 0 | layers.append(move(layer)); |
453 | 0 | } |
454 | | |
455 | 0 | computed_values.set_background_layers(move(layers)); |
456 | 0 | } |
457 | 0 | computed_values.set_background_color(computed_style.color_or_fallback(CSS::PropertyID::BackgroundColor, *this, CSS::InitialValues::background_color())); |
458 | |
|
459 | 0 | if (auto box_sizing = computed_style.box_sizing(); box_sizing.has_value()) |
460 | 0 | computed_values.set_box_sizing(box_sizing.release_value()); |
461 | |
|
462 | 0 | if (auto maybe_font_variant = computed_style.font_variant(); maybe_font_variant.has_value()) |
463 | 0 | computed_values.set_font_variant(maybe_font_variant.release_value()); |
464 | 0 | if (auto maybe_font_language_override = computed_style.font_language_override(); maybe_font_language_override.has_value()) |
465 | 0 | computed_values.set_font_language_override(maybe_font_language_override.release_value()); |
466 | 0 | if (auto maybe_font_feature_settings = computed_style.font_feature_settings(); maybe_font_feature_settings.has_value()) |
467 | 0 | computed_values.set_font_feature_settings(maybe_font_feature_settings.release_value()); |
468 | 0 | if (auto maybe_font_variation_settings = computed_style.font_variation_settings(); maybe_font_variation_settings.has_value()) |
469 | 0 | computed_values.set_font_variation_settings(maybe_font_variation_settings.release_value()); |
470 | |
|
471 | 0 | auto border_bottom_left_radius = computed_style.property(CSS::PropertyID::BorderBottomLeftRadius); |
472 | 0 | if (border_bottom_left_radius->is_border_radius()) { |
473 | 0 | computed_values.set_border_bottom_left_radius( |
474 | 0 | CSS::BorderRadiusData { |
475 | 0 | border_bottom_left_radius->as_border_radius().horizontal_radius(), |
476 | 0 | border_bottom_left_radius->as_border_radius().vertical_radius() }); |
477 | 0 | } |
478 | 0 | auto border_bottom_right_radius = computed_style.property(CSS::PropertyID::BorderBottomRightRadius); |
479 | 0 | if (border_bottom_right_radius->is_border_radius()) { |
480 | 0 | computed_values.set_border_bottom_right_radius( |
481 | 0 | CSS::BorderRadiusData { |
482 | 0 | border_bottom_right_radius->as_border_radius().horizontal_radius(), |
483 | 0 | border_bottom_right_radius->as_border_radius().vertical_radius() }); |
484 | 0 | } |
485 | 0 | auto border_top_left_radius = computed_style.property(CSS::PropertyID::BorderTopLeftRadius); |
486 | 0 | if (border_top_left_radius->is_border_radius()) { |
487 | 0 | computed_values.set_border_top_left_radius( |
488 | 0 | CSS::BorderRadiusData { |
489 | 0 | border_top_left_radius->as_border_radius().horizontal_radius(), |
490 | 0 | border_top_left_radius->as_border_radius().vertical_radius() }); |
491 | 0 | } |
492 | 0 | auto border_top_right_radius = computed_style.property(CSS::PropertyID::BorderTopRightRadius); |
493 | 0 | if (border_top_right_radius->is_border_radius()) { |
494 | 0 | computed_values.set_border_top_right_radius( |
495 | 0 | CSS::BorderRadiusData { |
496 | 0 | border_top_right_radius->as_border_radius().horizontal_radius(), |
497 | 0 | border_top_right_radius->as_border_radius().vertical_radius() }); |
498 | 0 | } |
499 | 0 | computed_values.set_display(computed_style.display()); |
500 | |
|
501 | 0 | auto flex_direction = computed_style.flex_direction(); |
502 | 0 | if (flex_direction.has_value()) |
503 | 0 | computed_values.set_flex_direction(flex_direction.value()); |
504 | |
|
505 | 0 | auto flex_wrap = computed_style.flex_wrap(); |
506 | 0 | if (flex_wrap.has_value()) |
507 | 0 | computed_values.set_flex_wrap(flex_wrap.value()); |
508 | |
|
509 | 0 | auto flex_basis = computed_style.flex_basis(); |
510 | 0 | if (flex_basis.has_value()) |
511 | 0 | computed_values.set_flex_basis(flex_basis.value()); |
512 | |
|
513 | 0 | computed_values.set_flex_grow(computed_style.flex_grow()); |
514 | 0 | computed_values.set_flex_shrink(computed_style.flex_shrink()); |
515 | 0 | computed_values.set_order(computed_style.order()); |
516 | 0 | computed_values.set_clip(computed_style.clip()); |
517 | |
|
518 | 0 | auto resolve_filter = [this](CSS::Filter const& computed_filter) -> CSS::ResolvedFilter { |
519 | 0 | CSS::ResolvedFilter resolved_filter; |
520 | 0 | for (auto const& filter : computed_filter.filters()) { |
521 | 0 | filter.visit( |
522 | 0 | [&](CSS::FilterOperation::Blur const& blur) { |
523 | 0 | resolved_filter.filters.append(CSS::ResolvedFilter::Blur { |
524 | 0 | .radius = blur.resolved_radius(*this) }); |
525 | 0 | }, |
526 | 0 | [&](CSS::FilterOperation::DropShadow const& drop_shadow) { |
527 | | // The default value for omitted values is missing length values set to 0 |
528 | | // and the missing used color is taken from the color property. |
529 | 0 | resolved_filter.filters.append(CSS::ResolvedFilter::DropShadow { |
530 | 0 | .offset_x = drop_shadow.offset_x.to_px(*this).to_double(), |
531 | 0 | .offset_y = drop_shadow.offset_y.to_px(*this).to_double(), |
532 | 0 | .radius = drop_shadow.radius.has_value() ? drop_shadow.radius->to_px(*this).to_double() : 0.0, |
533 | 0 | .color = drop_shadow.color.has_value() ? *drop_shadow.color : this->computed_values().color() }); |
534 | 0 | }, |
535 | 0 | [&](CSS::FilterOperation::Color const& color_operation) { |
536 | 0 | resolved_filter.filters.append(CSS::ResolvedFilter::Color { |
537 | 0 | .type = color_operation.operation, |
538 | 0 | .amount = color_operation.resolved_amount() }); |
539 | 0 | }, |
540 | 0 | [&](CSS::FilterOperation::HueRotate const& hue_rotate) { |
541 | 0 | resolved_filter.filters.append(CSS::ResolvedFilter::HueRotate { .angle_degrees = hue_rotate.angle_degrees() }); |
542 | 0 | }); |
543 | 0 | } |
544 | 0 | return resolved_filter; |
545 | 0 | }; |
546 | 0 | if (computed_style.backdrop_filter().has_filters()) |
547 | 0 | computed_values.set_backdrop_filter(resolve_filter(computed_style.backdrop_filter())); |
548 | 0 | if (computed_style.filter().has_filters()) |
549 | 0 | computed_values.set_filter(resolve_filter(computed_style.filter())); |
550 | |
|
551 | 0 | auto justify_content = computed_style.justify_content(); |
552 | 0 | if (justify_content.has_value()) |
553 | 0 | computed_values.set_justify_content(justify_content.value()); |
554 | |
|
555 | 0 | auto justify_items = computed_style.justify_items(); |
556 | 0 | if (justify_items.has_value()) |
557 | 0 | computed_values.set_justify_items(justify_items.value()); |
558 | |
|
559 | 0 | auto justify_self = computed_style.justify_self(); |
560 | 0 | if (justify_self.has_value()) |
561 | 0 | computed_values.set_justify_self(justify_self.value()); |
562 | |
|
563 | 0 | auto accent_color = computed_style.accent_color(*this); |
564 | 0 | if (accent_color.has_value()) |
565 | 0 | computed_values.set_accent_color(accent_color.value()); |
566 | |
|
567 | 0 | auto align_content = computed_style.align_content(); |
568 | 0 | if (align_content.has_value()) |
569 | 0 | computed_values.set_align_content(align_content.value()); |
570 | |
|
571 | 0 | auto align_items = computed_style.align_items(); |
572 | 0 | if (align_items.has_value()) |
573 | 0 | computed_values.set_align_items(align_items.value()); |
574 | |
|
575 | 0 | auto align_self = computed_style.align_self(); |
576 | 0 | if (align_self.has_value()) |
577 | 0 | computed_values.set_align_self(align_self.value()); |
578 | |
|
579 | 0 | auto appearance = computed_style.appearance(); |
580 | 0 | if (appearance.has_value()) |
581 | 0 | computed_values.set_appearance(appearance.value()); |
582 | |
|
583 | 0 | auto position = computed_style.position(); |
584 | 0 | if (position.has_value()) |
585 | 0 | computed_values.set_position(position.value()); |
586 | |
|
587 | 0 | auto text_align = computed_style.text_align(); |
588 | 0 | if (text_align.has_value()) |
589 | 0 | computed_values.set_text_align(text_align.value()); |
590 | |
|
591 | 0 | auto text_justify = computed_style.text_justify(); |
592 | 0 | if (text_align.has_value()) |
593 | 0 | computed_values.set_text_justify(text_justify.value()); |
594 | |
|
595 | 0 | if (auto text_indent = computed_style.length_percentage(CSS::PropertyID::TextIndent); text_indent.has_value()) |
596 | 0 | computed_values.set_text_indent(text_indent.release_value()); |
597 | |
|
598 | 0 | if (auto text_overflow = computed_style.text_overflow(); text_overflow.has_value()) |
599 | 0 | computed_values.set_text_overflow(text_overflow.release_value()); |
600 | |
|
601 | 0 | auto tab_size = computed_style.tab_size(); |
602 | 0 | computed_values.set_tab_size(tab_size); |
603 | |
|
604 | 0 | auto white_space = computed_style.white_space(); |
605 | 0 | if (white_space.has_value()) |
606 | 0 | computed_values.set_white_space(white_space.value()); |
607 | |
|
608 | 0 | auto word_break = computed_style.word_break(); |
609 | 0 | if (word_break.has_value()) |
610 | 0 | computed_values.set_word_break(word_break.value()); |
611 | |
|
612 | 0 | auto word_spacing = computed_style.word_spacing(); |
613 | 0 | if (word_spacing.has_value()) |
614 | 0 | computed_values.set_word_spacing(word_spacing.value()); |
615 | |
|
616 | 0 | auto letter_spacing = computed_style.letter_spacing(); |
617 | 0 | if (letter_spacing.has_value()) |
618 | 0 | computed_values.set_letter_spacing(letter_spacing.value()); |
619 | |
|
620 | 0 | auto float_ = computed_style.float_(); |
621 | 0 | if (float_.has_value()) |
622 | 0 | computed_values.set_float(float_.value()); |
623 | |
|
624 | 0 | computed_values.set_border_spacing_horizontal(computed_style.border_spacing_horizontal(*this)); |
625 | 0 | computed_values.set_border_spacing_vertical(computed_style.border_spacing_vertical(*this)); |
626 | |
|
627 | 0 | auto caption_side = computed_style.caption_side(); |
628 | 0 | if (caption_side.has_value()) |
629 | 0 | computed_values.set_caption_side(caption_side.value()); |
630 | |
|
631 | 0 | auto clear = computed_style.clear(); |
632 | 0 | if (clear.has_value()) |
633 | 0 | computed_values.set_clear(clear.value()); |
634 | |
|
635 | 0 | auto overflow_x = computed_style.overflow_x(); |
636 | 0 | if (overflow_x.has_value()) |
637 | 0 | computed_values.set_overflow_x(overflow_x.value()); |
638 | |
|
639 | 0 | auto overflow_y = computed_style.overflow_y(); |
640 | 0 | if (overflow_y.has_value()) |
641 | 0 | computed_values.set_overflow_y(overflow_y.value()); |
642 | |
|
643 | 0 | auto content_visibility = computed_style.content_visibility(); |
644 | 0 | if (content_visibility.has_value()) |
645 | 0 | computed_values.set_content_visibility(content_visibility.value()); |
646 | |
|
647 | 0 | auto cursor = computed_style.cursor(); |
648 | 0 | if (cursor.has_value()) |
649 | 0 | computed_values.set_cursor(cursor.value()); |
650 | |
|
651 | 0 | auto image_rendering = computed_style.image_rendering(); |
652 | 0 | if (image_rendering.has_value()) |
653 | 0 | computed_values.set_image_rendering(image_rendering.value()); |
654 | |
|
655 | 0 | auto pointer_events = computed_style.pointer_events(); |
656 | 0 | if (pointer_events.has_value()) |
657 | 0 | computed_values.set_pointer_events(pointer_events.value()); |
658 | |
|
659 | 0 | computed_values.set_text_decoration_line(computed_style.text_decoration_line()); |
660 | |
|
661 | 0 | auto text_decoration_style = computed_style.text_decoration_style(); |
662 | 0 | if (text_decoration_style.has_value()) |
663 | 0 | computed_values.set_text_decoration_style(text_decoration_style.value()); |
664 | |
|
665 | 0 | auto text_transform = computed_style.text_transform(); |
666 | 0 | if (text_transform.has_value()) |
667 | 0 | computed_values.set_text_transform(text_transform.value()); |
668 | |
|
669 | 0 | if (auto list_style_type = computed_style.list_style_type(); list_style_type.has_value()) |
670 | 0 | computed_values.set_list_style_type(list_style_type.value()); |
671 | |
|
672 | 0 | auto list_style_image = computed_style.property(CSS::PropertyID::ListStyleImage); |
673 | 0 | if (list_style_image->is_abstract_image()) { |
674 | 0 | m_list_style_image = list_style_image->as_abstract_image(); |
675 | 0 | const_cast<CSS::AbstractImageStyleValue&>(*m_list_style_image).load_any_resources(document()); |
676 | 0 | } |
677 | |
|
678 | 0 | if (auto list_style_position = computed_style.list_style_position(); list_style_position.has_value()) |
679 | 0 | computed_values.set_list_style_position(list_style_position.value()); |
680 | | |
681 | | // FIXME: The default text decoration color value is `currentcolor`, but since we can't resolve that easily, |
682 | | // we just manually grab the value from `color`. This makes it dependent on `color` being |
683 | | // specified first, so it's far from ideal. |
684 | 0 | computed_values.set_text_decoration_color(computed_style.color_or_fallback(CSS::PropertyID::TextDecorationColor, *this, computed_values.color())); |
685 | 0 | if (auto maybe_text_decoration_thickness = computed_style.length_percentage(CSS::PropertyID::TextDecorationThickness); maybe_text_decoration_thickness.has_value()) |
686 | 0 | computed_values.set_text_decoration_thickness(maybe_text_decoration_thickness.release_value()); |
687 | |
|
688 | 0 | computed_values.set_webkit_text_fill_color(computed_style.color_or_fallback(CSS::PropertyID::WebkitTextFillColor, *this, computed_values.color())); |
689 | |
|
690 | 0 | computed_values.set_text_shadow(computed_style.text_shadow(*this)); |
691 | |
|
692 | 0 | computed_values.set_z_index(computed_style.z_index()); |
693 | 0 | computed_values.set_opacity(computed_style.opacity()); |
694 | |
|
695 | 0 | if (auto maybe_visibility = computed_style.visibility(); maybe_visibility.has_value()) |
696 | 0 | computed_values.set_visibility(maybe_visibility.release_value()); |
697 | |
|
698 | 0 | computed_values.set_width(computed_style.size_value(CSS::PropertyID::Width)); |
699 | 0 | computed_values.set_min_width(computed_style.size_value(CSS::PropertyID::MinWidth)); |
700 | 0 | computed_values.set_max_width(computed_style.size_value(CSS::PropertyID::MaxWidth)); |
701 | |
|
702 | 0 | computed_values.set_height(computed_style.size_value(CSS::PropertyID::Height)); |
703 | 0 | computed_values.set_min_height(computed_style.size_value(CSS::PropertyID::MinHeight)); |
704 | 0 | computed_values.set_max_height(computed_style.size_value(CSS::PropertyID::MaxHeight)); |
705 | |
|
706 | 0 | computed_values.set_inset(computed_style.length_box(CSS::PropertyID::Left, CSS::PropertyID::Top, CSS::PropertyID::Right, CSS::PropertyID::Bottom, CSS::Length::make_auto())); |
707 | 0 | computed_values.set_margin(computed_style.length_box(CSS::PropertyID::MarginLeft, CSS::PropertyID::MarginTop, CSS::PropertyID::MarginRight, CSS::PropertyID::MarginBottom, CSS::Length::make_px(0))); |
708 | 0 | computed_values.set_padding(computed_style.length_box(CSS::PropertyID::PaddingLeft, CSS::PropertyID::PaddingTop, CSS::PropertyID::PaddingRight, CSS::PropertyID::PaddingBottom, CSS::Length::make_px(0))); |
709 | |
|
710 | 0 | computed_values.set_box_shadow(computed_style.box_shadow(*this)); |
711 | |
|
712 | 0 | if (auto rotate_value = computed_style.rotate(*this); rotate_value.has_value()) |
713 | 0 | computed_values.set_rotate(rotate_value.value()); |
714 | |
|
715 | 0 | computed_values.set_transformations(computed_style.transformations()); |
716 | 0 | if (auto transform_box = computed_style.transform_box(); transform_box.has_value()) |
717 | 0 | computed_values.set_transform_box(transform_box.value()); |
718 | 0 | computed_values.set_transform_origin(computed_style.transform_origin()); |
719 | |
|
720 | 0 | auto transition_delay_property = computed_style.property(CSS::PropertyID::TransitionDelay); |
721 | 0 | if (transition_delay_property->is_time()) { |
722 | 0 | auto& transition_delay = transition_delay_property->as_time(); |
723 | 0 | computed_values.set_transition_delay(transition_delay.time()); |
724 | 0 | } else if (transition_delay_property->is_math()) { |
725 | 0 | auto& transition_delay = transition_delay_property->as_math(); |
726 | 0 | computed_values.set_transition_delay(transition_delay.resolve_time().value()); |
727 | 0 | } |
728 | |
|
729 | 0 | auto do_border_style = [&](CSS::BorderData& border, CSS::PropertyID width_property, CSS::PropertyID color_property, CSS::PropertyID style_property) { |
730 | | // FIXME: The default border color value is `currentcolor`, but since we can't resolve that easily, |
731 | | // we just manually grab the value from `color`. This makes it dependent on `color` being |
732 | | // specified first, so it's far from ideal. |
733 | 0 | border.color = computed_style.color_or_fallback(color_property, *this, computed_values.color()); |
734 | 0 | border.line_style = computed_style.line_style(style_property).value_or(CSS::LineStyle::None); |
735 | | |
736 | | // https://w3c.github.io/csswg-drafts/css-backgrounds/#border-style |
737 | | // none |
738 | | // No border. Color and width are ignored (i.e., the border has width 0). Note this means that the initial value of border-image-width will also resolve to zero. |
739 | | // hidden |
740 | | // Same as none, but has different behavior in the border conflict resolution rules for border-collapsed tables [CSS2]. |
741 | 0 | if (border.line_style == CSS::LineStyle::None || border.line_style == CSS::LineStyle::Hidden) { |
742 | 0 | border.width = 0; |
743 | 0 | } else { |
744 | 0 | auto resolve_border_width = [&]() -> CSSPixels { |
745 | 0 | auto value = computed_style.property(width_property); |
746 | 0 | if (value->is_math()) |
747 | 0 | return max(CSSPixels { 0 }, value->as_math().resolve_length(*this)->to_px(*this)); |
748 | 0 | if (value->is_length()) |
749 | 0 | return value->as_length().length().to_px(*this); |
750 | 0 | if (value->is_keyword()) { |
751 | | // https://www.w3.org/TR/css-backgrounds-3/#valdef-line-width-thin |
752 | 0 | switch (value->to_keyword()) { |
753 | 0 | case CSS::Keyword::Thin: |
754 | 0 | return 1; |
755 | 0 | case CSS::Keyword::Medium: |
756 | 0 | return 3; |
757 | 0 | case CSS::Keyword::Thick: |
758 | 0 | return 5; |
759 | 0 | default: |
760 | 0 | VERIFY_NOT_REACHED(); |
761 | 0 | } |
762 | 0 | } |
763 | 0 | VERIFY_NOT_REACHED(); |
764 | 0 | }; |
765 | |
|
766 | 0 | border.width = snap_a_length_as_a_border_width(document().page().client().device_pixels_per_css_pixel(), resolve_border_width()); |
767 | 0 | } |
768 | 0 | }; |
769 | |
|
770 | 0 | do_border_style(computed_values.border_left(), CSS::PropertyID::BorderLeftWidth, CSS::PropertyID::BorderLeftColor, CSS::PropertyID::BorderLeftStyle); |
771 | 0 | do_border_style(computed_values.border_top(), CSS::PropertyID::BorderTopWidth, CSS::PropertyID::BorderTopColor, CSS::PropertyID::BorderTopStyle); |
772 | 0 | do_border_style(computed_values.border_right(), CSS::PropertyID::BorderRightWidth, CSS::PropertyID::BorderRightColor, CSS::PropertyID::BorderRightStyle); |
773 | 0 | do_border_style(computed_values.border_bottom(), CSS::PropertyID::BorderBottomWidth, CSS::PropertyID::BorderBottomColor, CSS::PropertyID::BorderBottomStyle); |
774 | |
|
775 | 0 | if (auto outline_color = computed_style.property(CSS::PropertyID::OutlineColor); outline_color->has_color()) |
776 | 0 | computed_values.set_outline_color(outline_color->to_color(*this)); |
777 | 0 | if (auto outline_offset = computed_style.property(CSS::PropertyID::OutlineOffset); outline_offset->is_length()) |
778 | 0 | computed_values.set_outline_offset(outline_offset->as_length().length()); |
779 | 0 | if (auto outline_style = computed_style.outline_style(); outline_style.has_value()) |
780 | 0 | computed_values.set_outline_style(outline_style.value()); |
781 | 0 | if (auto outline_width = computed_style.property(CSS::PropertyID::OutlineWidth); outline_width->is_length()) |
782 | 0 | computed_values.set_outline_width(outline_width->as_length().length()); |
783 | |
|
784 | 0 | computed_values.set_grid_auto_columns(computed_style.grid_auto_columns()); |
785 | 0 | computed_values.set_grid_auto_rows(computed_style.grid_auto_rows()); |
786 | 0 | computed_values.set_grid_template_columns(computed_style.grid_template_columns()); |
787 | 0 | computed_values.set_grid_template_rows(computed_style.grid_template_rows()); |
788 | 0 | computed_values.set_grid_column_end(computed_style.grid_column_end()); |
789 | 0 | computed_values.set_grid_column_start(computed_style.grid_column_start()); |
790 | 0 | computed_values.set_grid_row_end(computed_style.grid_row_end()); |
791 | 0 | computed_values.set_grid_row_start(computed_style.grid_row_start()); |
792 | 0 | computed_values.set_grid_template_areas(computed_style.grid_template_areas()); |
793 | 0 | computed_values.set_grid_auto_flow(computed_style.grid_auto_flow()); |
794 | |
|
795 | 0 | if (auto cx_value = computed_style.length_percentage(CSS::PropertyID::Cx); cx_value.has_value()) |
796 | 0 | computed_values.set_cx(*cx_value); |
797 | 0 | if (auto cy_value = computed_style.length_percentage(CSS::PropertyID::Cy); cy_value.has_value()) |
798 | 0 | computed_values.set_cy(*cy_value); |
799 | 0 | if (auto r_value = computed_style.length_percentage(CSS::PropertyID::R); r_value.has_value()) |
800 | 0 | computed_values.set_r(*r_value); |
801 | 0 | if (auto rx_value = computed_style.length_percentage(CSS::PropertyID::Rx); rx_value.has_value()) |
802 | 0 | computed_values.set_rx(*rx_value); |
803 | 0 | if (auto ry_value = computed_style.length_percentage(CSS::PropertyID::Ry); ry_value.has_value()) |
804 | 0 | computed_values.set_ry(*ry_value); |
805 | 0 | if (auto x_value = computed_style.length_percentage(CSS::PropertyID::X); x_value.has_value()) |
806 | 0 | computed_values.set_x(*x_value); |
807 | 0 | if (auto y_value = computed_style.length_percentage(CSS::PropertyID::Y); y_value.has_value()) |
808 | 0 | computed_values.set_y(*y_value); |
809 | |
|
810 | 0 | auto fill = computed_style.property(CSS::PropertyID::Fill); |
811 | 0 | if (fill->has_color()) |
812 | 0 | computed_values.set_fill(fill->to_color(*this)); |
813 | 0 | else if (fill->is_url()) |
814 | 0 | computed_values.set_fill(fill->as_url().url()); |
815 | 0 | auto stroke = computed_style.property(CSS::PropertyID::Stroke); |
816 | 0 | if (stroke->has_color()) |
817 | 0 | computed_values.set_stroke(stroke->to_color(*this)); |
818 | 0 | else if (stroke->is_url()) |
819 | 0 | computed_values.set_stroke(stroke->as_url().url()); |
820 | 0 | if (auto stop_color = computed_style.property(CSS::PropertyID::StopColor); stop_color->has_color()) |
821 | 0 | computed_values.set_stop_color(stop_color->to_color(*this)); |
822 | 0 | auto stroke_width = computed_style.property(CSS::PropertyID::StrokeWidth); |
823 | | // FIXME: Converting to pixels isn't really correct - values should be in "user units" |
824 | | // https://svgwg.org/svg2-draft/coords.html#TermUserUnits |
825 | 0 | if (stroke_width->is_number()) |
826 | 0 | computed_values.set_stroke_width(CSS::Length::make_px(CSSPixels::nearest_value_for(stroke_width->as_number().number()))); |
827 | 0 | else if (stroke_width->is_length()) |
828 | 0 | computed_values.set_stroke_width(stroke_width->as_length().length()); |
829 | 0 | else if (stroke_width->is_percentage()) |
830 | 0 | computed_values.set_stroke_width(CSS::LengthPercentage { stroke_width->as_percentage().percentage() }); |
831 | |
|
832 | 0 | if (auto mask_type = computed_style.mask_type(); mask_type.has_value()) |
833 | 0 | computed_values.set_mask_type(*mask_type); |
834 | |
|
835 | 0 | if (auto mask = computed_style.property(CSS::PropertyID::Mask); mask->is_url()) |
836 | 0 | computed_values.set_mask(mask->as_url().url()); |
837 | |
|
838 | 0 | auto clip_path = computed_style.property(CSS::PropertyID::ClipPath); |
839 | 0 | if (clip_path->is_url()) |
840 | 0 | computed_values.set_clip_path(clip_path->as_url().url()); |
841 | 0 | else if (clip_path->is_basic_shape()) |
842 | 0 | computed_values.set_clip_path(clip_path->as_basic_shape()); |
843 | |
|
844 | 0 | if (auto clip_rule = computed_style.clip_rule(); clip_rule.has_value()) |
845 | 0 | computed_values.set_clip_rule(*clip_rule); |
846 | |
|
847 | 0 | if (auto fill_rule = computed_style.fill_rule(); fill_rule.has_value()) |
848 | 0 | computed_values.set_fill_rule(*fill_rule); |
849 | |
|
850 | 0 | computed_values.set_fill_opacity(computed_style.fill_opacity()); |
851 | 0 | if (auto stroke_linecap = computed_style.stroke_linecap(); stroke_linecap.has_value()) |
852 | 0 | computed_values.set_stroke_linecap(stroke_linecap.value()); |
853 | 0 | if (auto stroke_linejoin = computed_style.stroke_linejoin(); stroke_linejoin.has_value()) |
854 | 0 | computed_values.set_stroke_linejoin(stroke_linejoin.value()); |
855 | |
|
856 | 0 | computed_values.set_stroke_miterlimit(computed_style.stroke_miterlimit()); |
857 | |
|
858 | 0 | computed_values.set_stroke_opacity(computed_style.stroke_opacity()); |
859 | 0 | computed_values.set_stop_opacity(computed_style.stop_opacity()); |
860 | |
|
861 | 0 | if (auto text_anchor = computed_style.text_anchor(); text_anchor.has_value()) |
862 | 0 | computed_values.set_text_anchor(*text_anchor); |
863 | |
|
864 | 0 | if (auto column_count = computed_style.property(CSS::PropertyID::ColumnCount); column_count->is_integer()) |
865 | 0 | computed_values.set_column_count(CSS::ColumnCount::make_integer(column_count->as_integer().integer())); |
866 | |
|
867 | 0 | if (auto column_span = computed_style.column_span(); column_span.has_value()) |
868 | 0 | computed_values.set_column_span(column_span.value()); |
869 | |
|
870 | 0 | computed_values.set_column_width(computed_style.size_value(CSS::PropertyID::ColumnWidth)); |
871 | |
|
872 | 0 | computed_values.set_column_gap(computed_style.size_value(CSS::PropertyID::ColumnGap)); |
873 | 0 | computed_values.set_row_gap(computed_style.size_value(CSS::PropertyID::RowGap)); |
874 | |
|
875 | 0 | if (auto border_collapse = computed_style.border_collapse(); border_collapse.has_value()) |
876 | 0 | computed_values.set_border_collapse(border_collapse.value()); |
877 | |
|
878 | 0 | if (auto table_layout = computed_style.table_layout(); table_layout.has_value()) |
879 | 0 | computed_values.set_table_layout(table_layout.value()); |
880 | |
|
881 | 0 | auto aspect_ratio = computed_style.property(CSS::PropertyID::AspectRatio); |
882 | 0 | if (aspect_ratio->is_value_list()) { |
883 | 0 | auto& values_list = aspect_ratio->as_value_list().values(); |
884 | 0 | if (values_list.size() == 2 |
885 | 0 | && values_list[0]->is_keyword() && values_list[0]->as_keyword().keyword() == CSS::Keyword::Auto |
886 | 0 | && values_list[1]->is_ratio()) { |
887 | 0 | computed_values.set_aspect_ratio({ true, values_list[1]->as_ratio().ratio() }); |
888 | 0 | } |
889 | 0 | } else if (aspect_ratio->is_keyword() && aspect_ratio->as_keyword().keyword() == CSS::Keyword::Auto) { |
890 | 0 | computed_values.set_aspect_ratio({ true, {} }); |
891 | 0 | } else if (aspect_ratio->is_ratio()) { |
892 | | // https://drafts.csswg.org/css-sizing-4/#aspect-ratio |
893 | | // If the <ratio> is degenerate, the property instead behaves as auto. |
894 | 0 | if (aspect_ratio->as_ratio().ratio().is_degenerate()) |
895 | 0 | computed_values.set_aspect_ratio({ true, {} }); |
896 | 0 | else |
897 | 0 | computed_values.set_aspect_ratio({ false, aspect_ratio->as_ratio().ratio() }); |
898 | 0 | } |
899 | |
|
900 | 0 | auto math_shift_value = computed_style.property(CSS::PropertyID::MathShift); |
901 | 0 | if (auto math_shift = keyword_to_math_shift(math_shift_value->to_keyword()); math_shift.has_value()) |
902 | 0 | computed_values.set_math_shift(math_shift.value()); |
903 | |
|
904 | 0 | auto math_style_value = computed_style.property(CSS::PropertyID::MathStyle); |
905 | 0 | if (auto math_style = keyword_to_math_style(math_style_value->to_keyword()); math_style.has_value()) |
906 | 0 | computed_values.set_math_style(math_style.value()); |
907 | |
|
908 | 0 | computed_values.set_math_depth(computed_style.math_depth()); |
909 | 0 | computed_values.set_quotes(computed_style.quotes()); |
910 | 0 | computed_values.set_counter_increment(computed_style.counter_data(CSS::PropertyID::CounterIncrement)); |
911 | 0 | computed_values.set_counter_reset(computed_style.counter_data(CSS::PropertyID::CounterReset)); |
912 | 0 | computed_values.set_counter_set(computed_style.counter_data(CSS::PropertyID::CounterSet)); |
913 | |
|
914 | 0 | if (auto object_fit = computed_style.object_fit(); object_fit.has_value()) |
915 | 0 | computed_values.set_object_fit(object_fit.value()); |
916 | |
|
917 | 0 | computed_values.set_object_position(computed_style.object_position()); |
918 | |
|
919 | 0 | if (auto direction = computed_style.direction(); direction.has_value()) |
920 | 0 | computed_values.set_direction(direction.value()); |
921 | |
|
922 | 0 | if (auto unicode_bidi = computed_style.unicode_bidi(); unicode_bidi.has_value()) |
923 | 0 | computed_values.set_unicode_bidi(unicode_bidi.value()); |
924 | |
|
925 | 0 | if (auto scrollbar_width = computed_style.scrollbar_width(); scrollbar_width.has_value()) |
926 | 0 | computed_values.set_scrollbar_width(scrollbar_width.value()); |
927 | |
|
928 | 0 | if (auto writing_mode = computed_style.writing_mode(); writing_mode.has_value()) |
929 | 0 | computed_values.set_writing_mode(writing_mode.value()); |
930 | |
|
931 | 0 | propagate_style_to_anonymous_wrappers(); |
932 | 0 | } |
933 | | |
934 | | void NodeWithStyle::propagate_style_to_anonymous_wrappers() |
935 | 0 | { |
936 | | // Update the style of any anonymous wrappers that inherit from this node. |
937 | | // FIXME: This is pretty hackish. It would be nicer if they shared the inherited style |
938 | | // data structure somehow, so this wasn't necessary. |
939 | | |
940 | | // If this is a `display:table` box with an anonymous wrapper parent, |
941 | | // the parent inherits style from *this* node, not the other way around. |
942 | 0 | if (display().is_table_inside() && is<TableWrapper>(parent())) { |
943 | 0 | auto& table_wrapper = *static_cast<TableWrapper*>(parent()); |
944 | 0 | static_cast<CSS::MutableComputedValues&>(static_cast<CSS::ComputedValues&>(const_cast<CSS::ImmutableComputedValues&>(table_wrapper.computed_values()))).inherit_from(computed_values()); |
945 | 0 | transfer_table_box_computed_values_to_wrapper_computed_values(table_wrapper.mutable_computed_values()); |
946 | 0 | } |
947 | | |
948 | | // Propagate style to all anonymous children (except table wrappers!) |
949 | 0 | for_each_child_of_type<NodeWithStyle>([&](NodeWithStyle& child) { |
950 | 0 | if (child.is_anonymous() && !is<TableWrapper>(child)) { |
951 | 0 | auto& child_computed_values = static_cast<CSS::MutableComputedValues&>(static_cast<CSS::ComputedValues&>(const_cast<CSS::ImmutableComputedValues&>(child.computed_values()))); |
952 | 0 | child_computed_values.inherit_from(computed_values()); |
953 | 0 | } |
954 | 0 | return IterationDecision::Continue; |
955 | 0 | }); |
956 | 0 | } |
957 | | |
958 | | bool Node::is_root_element() const |
959 | 0 | { |
960 | 0 | if (is_anonymous()) |
961 | 0 | return false; |
962 | 0 | return is<HTML::HTMLHtmlElement>(*dom_node()); |
963 | 0 | } |
964 | | |
965 | | String Node::debug_description() const |
966 | 0 | { |
967 | 0 | StringBuilder builder; |
968 | 0 | builder.append(class_name()); |
969 | 0 | if (dom_node()) { |
970 | 0 | builder.appendff("<{}>", dom_node()->node_name()); |
971 | 0 | if (dom_node()->is_element()) { |
972 | 0 | auto& element = static_cast<DOM::Element const&>(*dom_node()); |
973 | 0 | if (element.id().has_value()) |
974 | 0 | builder.appendff("#{}", element.id().value()); |
975 | 0 | for (auto const& class_name : element.class_names()) |
976 | 0 | builder.appendff(".{}", class_name); |
977 | 0 | } |
978 | 0 | } else { |
979 | 0 | builder.append("(anonymous)"sv); |
980 | 0 | } |
981 | 0 | return MUST(builder.to_string()); |
982 | 0 | } |
983 | | |
984 | | CSS::Display Node::display() const |
985 | 0 | { |
986 | 0 | if (!has_style()) { |
987 | | // NOTE: No style means this is dumb text content. |
988 | 0 | return CSS::Display(CSS::DisplayOutside::Inline, CSS::DisplayInside::Flow); |
989 | 0 | } |
990 | | |
991 | 0 | return computed_values().display(); |
992 | 0 | } |
993 | | |
994 | | bool Node::is_inline() const |
995 | 0 | { |
996 | 0 | return display().is_inline_outside(); |
997 | 0 | } |
998 | | |
999 | | bool Node::is_inline_block() const |
1000 | 0 | { |
1001 | 0 | auto display = this->display(); |
1002 | 0 | return display.is_inline_outside() && display.is_flow_root_inside(); |
1003 | 0 | } |
1004 | | |
1005 | | bool Node::is_inline_table() const |
1006 | 0 | { |
1007 | 0 | auto display = this->display(); |
1008 | 0 | return display.is_inline_outside() && display.is_table_inside(); |
1009 | 0 | } |
1010 | | |
1011 | | JS::NonnullGCPtr<NodeWithStyle> NodeWithStyle::create_anonymous_wrapper() const |
1012 | 0 | { |
1013 | 0 | auto wrapper = heap().allocate_without_realm<BlockContainer>(const_cast<DOM::Document&>(document()), nullptr, computed_values().clone_inherited_values()); |
1014 | 0 | wrapper->mutable_computed_values().set_display(CSS::Display(CSS::DisplayOutside::Block, CSS::DisplayInside::Flow)); |
1015 | | |
1016 | | // NOTE: These properties are not inherited, but we still have to propagate them to anonymous wrappers. |
1017 | 0 | wrapper->mutable_computed_values().set_text_decoration_line(computed_values().text_decoration_line()); |
1018 | 0 | wrapper->mutable_computed_values().set_text_decoration_thickness(computed_values().text_decoration_thickness()); |
1019 | 0 | wrapper->mutable_computed_values().set_text_decoration_color(computed_values().text_decoration_color()); |
1020 | 0 | wrapper->mutable_computed_values().set_text_decoration_style(computed_values().text_decoration_style()); |
1021 | 0 | return *wrapper; |
1022 | 0 | } |
1023 | | |
1024 | | void NodeWithStyle::reset_table_box_computed_values_used_by_wrapper_to_init_values() |
1025 | 0 | { |
1026 | 0 | VERIFY(this->display().is_table_inside()); |
1027 | | |
1028 | 0 | auto& mutable_computed_values = this->mutable_computed_values(); |
1029 | 0 | mutable_computed_values.set_position(CSS::InitialValues::position()); |
1030 | 0 | mutable_computed_values.set_float(CSS::InitialValues::float_()); |
1031 | 0 | mutable_computed_values.set_clear(CSS::InitialValues::clear()); |
1032 | 0 | mutable_computed_values.set_inset(CSS::InitialValues::inset()); |
1033 | 0 | mutable_computed_values.set_margin(CSS::InitialValues::margin()); |
1034 | 0 | } |
1035 | | |
1036 | | void NodeWithStyle::transfer_table_box_computed_values_to_wrapper_computed_values(CSS::ComputedValues& wrapper_computed_values) |
1037 | 0 | { |
1038 | | // The computed values of properties 'position', 'float', 'margin-*', 'top', 'right', 'bottom', and 'left' on the table element are used on the table wrapper box and not the table box; |
1039 | | // all other values of non-inheritable properties are used on the table box and not the table wrapper box. |
1040 | | // (Where the table element's values are not used on the table and table wrapper boxes, the initial values are used instead.) |
1041 | 0 | auto& mutable_wrapper_computed_values = static_cast<CSS::MutableComputedValues&>(wrapper_computed_values); |
1042 | 0 | if (display().is_inline_outside()) |
1043 | 0 | mutable_wrapper_computed_values.set_display(CSS::Display::from_short(CSS::Display::Short::InlineBlock)); |
1044 | 0 | else |
1045 | 0 | mutable_wrapper_computed_values.set_display(CSS::Display::from_short(CSS::Display::Short::FlowRoot)); |
1046 | 0 | mutable_wrapper_computed_values.set_position(computed_values().position()); |
1047 | 0 | mutable_wrapper_computed_values.set_inset(computed_values().inset()); |
1048 | 0 | mutable_wrapper_computed_values.set_float(computed_values().float_()); |
1049 | 0 | mutable_wrapper_computed_values.set_clear(computed_values().clear()); |
1050 | 0 | mutable_wrapper_computed_values.set_margin(computed_values().margin()); |
1051 | 0 | reset_table_box_computed_values_used_by_wrapper_to_init_values(); |
1052 | 0 | } |
1053 | | |
1054 | | void Node::set_paintable(JS::GCPtr<Painting::Paintable> paintable) |
1055 | 0 | { |
1056 | 0 | m_paintable = move(paintable); |
1057 | 0 | } |
1058 | | |
1059 | | JS::GCPtr<Painting::Paintable> Node::create_paintable() const |
1060 | 0 | { |
1061 | 0 | return nullptr; |
1062 | 0 | } |
1063 | | |
1064 | | bool Node::is_anonymous() const |
1065 | 0 | { |
1066 | 0 | return m_anonymous; |
1067 | 0 | } |
1068 | | |
1069 | | DOM::Node const* Node::dom_node() const |
1070 | 0 | { |
1071 | 0 | if (m_anonymous) |
1072 | 0 | return nullptr; |
1073 | 0 | return m_dom_node.ptr(); |
1074 | 0 | } |
1075 | | |
1076 | | DOM::Node* Node::dom_node() |
1077 | 0 | { |
1078 | 0 | if (m_anonymous) |
1079 | 0 | return nullptr; |
1080 | 0 | return m_dom_node.ptr(); |
1081 | 0 | } |
1082 | | |
1083 | | DOM::Element const* Node::pseudo_element_generator() const |
1084 | 0 | { |
1085 | 0 | VERIFY(m_generated_for != GeneratedFor::NotGenerated); |
1086 | 0 | return m_pseudo_element_generator.ptr(); |
1087 | 0 | } |
1088 | | |
1089 | | DOM::Element* Node::pseudo_element_generator() |
1090 | 0 | { |
1091 | 0 | VERIFY(m_generated_for != GeneratedFor::NotGenerated); |
1092 | 0 | return m_pseudo_element_generator.ptr(); |
1093 | 0 | } |
1094 | | |
1095 | | DOM::Document& Node::document() |
1096 | 0 | { |
1097 | 0 | return m_dom_node->document(); |
1098 | 0 | } |
1099 | | |
1100 | | DOM::Document const& Node::document() const |
1101 | 0 | { |
1102 | 0 | return m_dom_node->document(); |
1103 | 0 | } |
1104 | | |
1105 | | } |