StackingContext.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. /*
  2. * Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/Debug.h>
  8. #include <AK/QuickSort.h>
  9. #include <AK/StringBuilder.h>
  10. #include <LibGfx/AffineTransform.h>
  11. #include <LibGfx/Matrix4x4.h>
  12. #include <LibGfx/Rect.h>
  13. #include <LibWeb/CSS/ComputedValues.h>
  14. #include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
  15. #include <LibWeb/Layout/Box.h>
  16. #include <LibWeb/Layout/ReplacedBox.h>
  17. #include <LibWeb/Layout/Viewport.h>
  18. #include <LibWeb/Painting/PaintableBox.h>
  19. #include <LibWeb/Painting/SVGPaintable.h>
  20. #include <LibWeb/Painting/StackingContext.h>
  21. #include <LibWeb/Painting/TableBordersPainting.h>
  22. #include <LibWeb/SVG/SVGMaskElement.h>
  23. namespace Web::Painting {
  24. static void paint_node(Paintable const& paintable, PaintContext& context, PaintPhase phase)
  25. {
  26. paintable.before_paint(context, phase);
  27. paintable.paint(context, phase);
  28. paintable.after_paint(context, phase);
  29. }
  30. StackingContext::StackingContext(Paintable& paintable, StackingContext* parent, size_t index_in_tree_order)
  31. : m_paintable(paintable)
  32. , m_parent(parent)
  33. , m_index_in_tree_order(index_in_tree_order)
  34. {
  35. VERIFY(m_parent != this);
  36. if (m_parent)
  37. m_parent->m_children.append(this);
  38. }
  39. void StackingContext::sort()
  40. {
  41. quick_sort(m_children, [](auto& a, auto& b) {
  42. auto a_z_index = a->paintable().computed_values().z_index().value_or(0);
  43. auto b_z_index = b->paintable().computed_values().z_index().value_or(0);
  44. if (a_z_index == b_z_index)
  45. return a->m_index_in_tree_order < b->m_index_in_tree_order;
  46. return a_z_index < b_z_index;
  47. });
  48. for (auto* child : m_children)
  49. child->sort();
  50. }
  51. void StackingContext::set_last_paint_generation_id(u64 generation_id)
  52. {
  53. VERIFY(!m_last_paint_generation_id.has_value() || m_last_paint_generation_id.value() < generation_id);
  54. m_last_paint_generation_id = generation_id;
  55. }
  56. static PaintPhase to_paint_phase(StackingContext::StackingContextPaintPhase phase)
  57. {
  58. // There are not a fully correct mapping since some stacking context phases are combined.
  59. switch (phase) {
  60. case StackingContext::StackingContextPaintPhase::Floats:
  61. case StackingContext::StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced:
  62. case StackingContext::StackingContextPaintPhase::BackgroundAndBorders:
  63. return PaintPhase::Background;
  64. case StackingContext::StackingContextPaintPhase::Foreground:
  65. return PaintPhase::Foreground;
  66. case StackingContext::StackingContextPaintPhase::FocusAndOverlay:
  67. return PaintPhase::Overlay;
  68. default:
  69. VERIFY_NOT_REACHED();
  70. }
  71. }
  72. void StackingContext::paint_node_as_stacking_context(Paintable const& paintable, PaintContext& context)
  73. {
  74. paint_node(paintable, context, PaintPhase::Background);
  75. paint_node(paintable, context, PaintPhase::Border);
  76. paint_descendants(context, paintable, StackingContextPaintPhase::BackgroundAndBorders);
  77. paint_descendants(context, paintable, StackingContextPaintPhase::Floats);
  78. paint_descendants(context, paintable, StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced);
  79. paint_node(paintable, context, PaintPhase::Foreground);
  80. paint_descendants(context, paintable, StackingContextPaintPhase::Foreground);
  81. paint_node(paintable, context, PaintPhase::Outline);
  82. paint_node(paintable, context, PaintPhase::Overlay);
  83. paint_descendants(context, paintable, StackingContextPaintPhase::FocusAndOverlay);
  84. }
  85. void StackingContext::paint_descendants(PaintContext& context, Paintable const& paintable, StackingContextPaintPhase phase)
  86. {
  87. paintable.before_children_paint(context, to_paint_phase(phase));
  88. paintable.for_each_child([&context, phase](auto& child) {
  89. auto* stacking_context = child.stacking_context();
  90. auto const& z_index = child.computed_values().z_index();
  91. // NOTE: Grid specification https://www.w3.org/TR/css-grid-2/#z-order says that grid items should be treated
  92. // the same way as CSS2 defines for inline-blocks:
  93. // "For each one of these, treat the element as if it created a new stacking context, but any positioned
  94. // descendants and descendants which actually create a new stacking context should be considered part of
  95. // the parent stacking context, not this new one."
  96. auto should_be_treated_as_stacking_context = child.layout_node().is_grid_item() && !z_index.has_value();
  97. if (should_be_treated_as_stacking_context) {
  98. // FIXME: This may not be fully correct with respect to the paint phases.
  99. if (phase == StackingContextPaintPhase::Foreground)
  100. paint_node_as_stacking_context(child, context);
  101. return;
  102. }
  103. if (stacking_context && z_index.value_or(0) != 0)
  104. return;
  105. if (child.is_positioned() && z_index.value_or(0) == 0)
  106. return;
  107. if (stacking_context) {
  108. // FIXME: This may not be fully correct with respect to the paint phases.
  109. if (phase == StackingContextPaintPhase::Foreground) {
  110. paint_child(context, *stacking_context);
  111. }
  112. // Note: Don't further recurse into descendants as paint_child() will do that.
  113. return;
  114. }
  115. bool child_is_inline_or_replaced = child.is_inline() || is<Layout::ReplacedBox>(child.layout_node());
  116. switch (phase) {
  117. case StackingContextPaintPhase::BackgroundAndBorders:
  118. if (!child_is_inline_or_replaced && !child.is_floating()) {
  119. paint_node(child, context, PaintPhase::Background);
  120. bool is_table_with_collapsed_borders = child.display().is_table_inside() && child.computed_values().border_collapse() == CSS::BorderCollapse::Collapse;
  121. if (!child.display().is_table_cell() && !is_table_with_collapsed_borders)
  122. paint_node(child, context, PaintPhase::Border);
  123. paint_descendants(context, child, phase);
  124. if (child.display().is_table_inside() || child.computed_values().border_collapse() == CSS::BorderCollapse::Collapse) {
  125. paint_table_borders(context, verify_cast<PaintableBox>(child));
  126. }
  127. }
  128. break;
  129. case StackingContextPaintPhase::Floats:
  130. if (child.is_floating()) {
  131. paint_node(child, context, PaintPhase::Background);
  132. paint_node(child, context, PaintPhase::Border);
  133. paint_descendants(context, child, StackingContextPaintPhase::BackgroundAndBorders);
  134. }
  135. paint_descendants(context, child, phase);
  136. break;
  137. case StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced:
  138. if (child_is_inline_or_replaced) {
  139. paint_node(child, context, PaintPhase::Background);
  140. paint_node(child, context, PaintPhase::Border);
  141. if (child.display().is_table_inside() && child.computed_values().border_collapse() == CSS::BorderCollapse::Separate)
  142. paint_table_borders(context, verify_cast<PaintableBox>(child));
  143. paint_descendants(context, child, StackingContextPaintPhase::BackgroundAndBorders);
  144. }
  145. paint_descendants(context, child, phase);
  146. break;
  147. case StackingContextPaintPhase::Foreground:
  148. paint_node(child, context, PaintPhase::Foreground);
  149. paint_descendants(context, child, phase);
  150. break;
  151. case StackingContextPaintPhase::FocusAndOverlay:
  152. paint_node(child, context, PaintPhase::Outline);
  153. paint_node(child, context, PaintPhase::Overlay);
  154. paint_descendants(context, child, phase);
  155. break;
  156. }
  157. });
  158. paintable.after_children_paint(context, to_paint_phase(phase));
  159. }
  160. void StackingContext::paint_child(PaintContext& context, StackingContext const& child)
  161. {
  162. const_cast<StackingContext&>(child).set_last_paint_generation_id(context.paint_generation_id());
  163. auto parent_paintable = child.paintable().parent();
  164. if (parent_paintable)
  165. parent_paintable->before_children_paint(context, PaintPhase::Foreground);
  166. child.paint(context);
  167. if (parent_paintable)
  168. parent_paintable->after_children_paint(context, PaintPhase::Foreground);
  169. }
  170. void StackingContext::paint_internal(PaintContext& context) const
  171. {
  172. // For a more elaborate description of the algorithm, see CSS 2.1 Appendix E
  173. // Draw the background and borders for the context root (steps 1, 2)
  174. paint_node(paintable(), context, PaintPhase::Background);
  175. paint_node(paintable(), context, PaintPhase::Border);
  176. // Stacking contexts formed by positioned descendants with negative z-indices (excluding 0) in z-index order
  177. // (most negative first) then tree order. (step 3)
  178. // NOTE: This doesn't check if a descendant is positioned as modern CSS allows for alternative methods to establish stacking contexts.
  179. for (auto* child : m_children) {
  180. if (child->paintable().computed_values().z_index().has_value() && child->paintable().computed_values().z_index().value() < 0)
  181. paint_child(context, *child);
  182. }
  183. // Draw the background and borders for block-level children (step 4)
  184. paint_descendants(context, paintable(), StackingContextPaintPhase::BackgroundAndBorders);
  185. // Draw the non-positioned floats (step 5)
  186. paint_descendants(context, paintable(), StackingContextPaintPhase::Floats);
  187. // Draw inline content, replaced content, etc. (steps 6, 7)
  188. paint_descendants(context, paintable(), StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced);
  189. paint_node(paintable(), context, PaintPhase::Foreground);
  190. paint_descendants(context, paintable(), StackingContextPaintPhase::Foreground);
  191. // Draw positioned descendants with z-index `0` or `auto` in tree order. (step 8)
  192. // FIXME: There's more to this step that we have yet to understand and implement.
  193. for (auto const& paintable : m_positioned_descendants_with_stack_level_0_and_stacking_contexts) {
  194. if (!paintable->is_positioned())
  195. continue;
  196. // At this point, `paintable_box` is a positioned descendant with z-index: auto.
  197. // FIXME: This is basically duplicating logic found elsewhere in this same function. Find a way to make this more elegant.
  198. auto* parent_paintable = paintable->parent();
  199. if (parent_paintable)
  200. parent_paintable->before_children_paint(context, PaintPhase::Foreground);
  201. if (auto* child = paintable->stacking_context()) {
  202. paint_child(context, *child);
  203. } else {
  204. paint_node_as_stacking_context(paintable, context);
  205. }
  206. if (parent_paintable)
  207. parent_paintable->after_children_paint(context, PaintPhase::Foreground);
  208. };
  209. // Stacking contexts formed by positioned descendants with z-indices greater than or equal to 1 in z-index order
  210. // (smallest first) then tree order. (Step 9)
  211. // NOTE: This doesn't check if a descendant is positioned as modern CSS allows for alternative methods to establish stacking contexts.
  212. for (auto* child : m_children) {
  213. if (child->paintable().computed_values().z_index().has_value() && child->paintable().computed_values().z_index().value() >= 1)
  214. paint_child(context, *child);
  215. }
  216. paint_node(paintable(), context, PaintPhase::Outline);
  217. if (context.should_paint_overlay()) {
  218. paint_node(paintable(), context, PaintPhase::Overlay);
  219. paint_descendants(context, paintable(), StackingContextPaintPhase::FocusAndOverlay);
  220. }
  221. }
  222. // FIXME: This extracts the affine 2D part of the full transformation matrix.
  223. // Use the whole matrix when we get better transformation support in LibGfx or use LibGL for drawing the bitmap
  224. Gfx::AffineTransform StackingContext::affine_transform_matrix() const
  225. {
  226. if (paintable().is_paintable_box())
  227. return Gfx::extract_2d_affine_transform(paintable_box().transform());
  228. return Gfx::AffineTransform {};
  229. }
  230. static Gfx::FloatMatrix4x4 matrix_with_scaled_translation(Gfx::FloatMatrix4x4 matrix, float scale)
  231. {
  232. auto* m = matrix.elements();
  233. m[0][3] *= scale;
  234. m[1][3] *= scale;
  235. m[2][3] *= scale;
  236. return matrix;
  237. }
  238. void StackingContext::paint(PaintContext& context) const
  239. {
  240. auto opacity = paintable().computed_values().opacity();
  241. if (opacity == 0.0f)
  242. return;
  243. RecordingPainterStateSaver saver(context.recording_painter());
  244. auto to_device_pixels_scale = float(context.device_pixels_per_css_pixel());
  245. Gfx::IntRect source_paintable_rect;
  246. if (paintable().is_paintable_box()) {
  247. source_paintable_rect = context.enclosing_device_rect(paintable_box().absolute_paint_rect()).to_type<int>();
  248. } else if (paintable().is_inline()) {
  249. source_paintable_rect = context.enclosing_device_rect(inline_paintable().bounding_rect()).to_type<int>();
  250. } else {
  251. VERIFY_NOT_REACHED();
  252. }
  253. auto transform_matrix = Gfx::FloatMatrix4x4::identity();
  254. Gfx::FloatPoint transform_origin;
  255. if (paintable().is_paintable_box()) {
  256. transform_matrix = paintable_box().transform();
  257. transform_origin = paintable_box().transform_origin().to_type<float>();
  258. }
  259. RecordingPainter::PushStackingContextParams push_stacking_context_params {
  260. .opacity = opacity,
  261. .is_fixed_position = paintable().is_fixed_position(),
  262. .source_paintable_rect = source_paintable_rect,
  263. .image_rendering = paintable().computed_values().image_rendering(),
  264. .transform = {
  265. .origin = transform_origin.scaled(to_device_pixels_scale),
  266. .matrix = matrix_with_scaled_translation(transform_matrix, to_device_pixels_scale),
  267. },
  268. };
  269. if (paintable().is_paintable_box()) {
  270. if (auto masking_area = paintable_box().get_masking_area(); masking_area.has_value()) {
  271. if (masking_area->is_empty())
  272. return;
  273. auto mask_bitmap = paintable_box().calculate_mask(context, *masking_area);
  274. if (mask_bitmap) {
  275. auto source_paintable_rect = context.enclosing_device_rect(*masking_area).to_type<int>();
  276. push_stacking_context_params.source_paintable_rect = source_paintable_rect;
  277. push_stacking_context_params.mask = StackingContextMask {
  278. .mask_bitmap = mask_bitmap.release_nonnull(),
  279. .mask_kind = *paintable_box().get_mask_type()
  280. };
  281. }
  282. }
  283. }
  284. context.recording_painter().save();
  285. if (paintable().is_paintable_box() && paintable_box().scroll_frame_id().has_value())
  286. context.recording_painter().set_scroll_frame_id(*paintable_box().scroll_frame_id());
  287. context.recording_painter().push_stacking_context(push_stacking_context_params);
  288. paint_internal(context);
  289. context.recording_painter().pop_stacking_context();
  290. context.recording_painter().restore();
  291. }
  292. TraversalDecision StackingContext::hit_test(CSSPixelPoint position, HitTestType type, Function<TraversalDecision(HitTestResult)> const& callback) const
  293. {
  294. if (!paintable().is_visible())
  295. return TraversalDecision::Continue;
  296. CSSPixelPoint transform_origin { 0, 0 };
  297. if (paintable().is_paintable_box())
  298. transform_origin = paintable_box().transform_origin();
  299. // NOTE: This CSSPixels -> Float -> CSSPixels conversion is because we can't AffineTransform::map() a CSSPixelPoint.
  300. Gfx::FloatPoint offset_position {
  301. (position.x() - transform_origin.x()).to_float(),
  302. (position.y() - transform_origin.y()).to_float()
  303. };
  304. auto transformed_position = affine_transform_matrix().inverse().value_or({}).map(offset_position).to_type<CSSPixels>() + transform_origin;
  305. if (paintable().is_fixed_position()) {
  306. auto scroll_offset = paintable().document().navigable()->viewport_scroll_offset();
  307. transformed_position.translate_by(-scroll_offset);
  308. }
  309. // NOTE: Hit testing basically happens in reverse painting order.
  310. // https://www.w3.org/TR/CSS22/visuren.html#z-index
  311. // 7. the child stacking contexts with positive stack levels (least positive first).
  312. // NOTE: Hit testing follows reverse painting order, that's why the conditions here are reversed.
  313. for (ssize_t i = m_children.size() - 1; i >= 0; --i) {
  314. auto const& child = *m_children[i];
  315. if (child.paintable().computed_values().z_index().value_or(0) <= 0)
  316. break;
  317. if (child.hit_test(transformed_position, type, callback) == TraversalDecision::Break)
  318. return TraversalDecision::Break;
  319. }
  320. // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
  321. for (auto const& paintable : m_positioned_descendants_with_stack_level_0_and_stacking_contexts.in_reverse()) {
  322. if (paintable->stacking_context()) {
  323. if (paintable->stacking_context()->hit_test(transformed_position, type, callback) == TraversalDecision::Break)
  324. return TraversalDecision::Break;
  325. } else {
  326. if (paintable->hit_test(transformed_position, type, callback) == TraversalDecision::Break)
  327. return TraversalDecision::Break;
  328. }
  329. }
  330. // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
  331. if (paintable().layout_node().children_are_inline() && is<Layout::BlockContainer>(paintable().layout_node())) {
  332. for (auto const* child = paintable().last_child(); child; child = child->previous_sibling()) {
  333. if (child->is_inline() && !child->is_absolutely_positioned() && !child->stacking_context()) {
  334. if (child->hit_test(transformed_position, type, callback) == TraversalDecision::Break)
  335. return TraversalDecision::Break;
  336. }
  337. }
  338. }
  339. // 4. the non-positioned floats.
  340. for (auto const& paintable : m_non_positioned_floating_descendants.in_reverse()) {
  341. if (paintable->hit_test(transformed_position, type, callback) == TraversalDecision::Break)
  342. return TraversalDecision::Break;
  343. }
  344. // 3. the in-flow, non-inline-level, non-positioned descendants.
  345. if (!paintable().layout_node().children_are_inline()) {
  346. for (auto const* child = paintable().last_child(); child; child = child->previous_sibling()) {
  347. if (!child->is_paintable_box())
  348. continue;
  349. auto const& paintable_box = verify_cast<PaintableBox>(*child);
  350. if (!paintable_box.is_absolutely_positioned() && !paintable_box.is_floating() && !paintable_box.stacking_context()) {
  351. if (paintable_box.hit_test(transformed_position, type, callback) == TraversalDecision::Break)
  352. return TraversalDecision::Break;
  353. }
  354. }
  355. }
  356. // 2. the child stacking contexts with negative stack levels (most negative first).
  357. // NOTE: Hit testing follows reverse painting order, that's why the conditions here are reversed.
  358. for (ssize_t i = m_children.size() - 1; i >= 0; --i) {
  359. auto const& child = *m_children[i];
  360. if (child.paintable().computed_values().z_index().value_or(0) >= 0)
  361. break;
  362. if (child.hit_test(transformed_position, type, callback) == TraversalDecision::Break)
  363. return TraversalDecision::Break;
  364. }
  365. // 1. the background and borders of the element forming the stacking context.
  366. if (paintable().is_paintable_box()) {
  367. if (paintable_box().absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y())) {
  368. auto hit_test_result = HitTestResult { .paintable = const_cast<PaintableBox&>(paintable_box()) };
  369. if (callback(hit_test_result) == TraversalDecision::Break)
  370. return TraversalDecision::Break;
  371. }
  372. }
  373. return TraversalDecision::Continue;
  374. }
  375. void StackingContext::dump(int indent) const
  376. {
  377. StringBuilder builder;
  378. for (int i = 0; i < indent; ++i)
  379. builder.append(' ');
  380. CSSPixelRect rect;
  381. if (paintable().is_paintable_box()) {
  382. rect = paintable_box().absolute_rect();
  383. } else if (paintable().is_inline_paintable()) {
  384. rect = inline_paintable().bounding_rect();
  385. } else {
  386. VERIFY_NOT_REACHED();
  387. }
  388. builder.appendff("SC for {} {} [children: {}] (z-index: ", paintable().layout_node().debug_description(), rect, m_children.size());
  389. if (paintable().computed_values().z_index().has_value())
  390. builder.appendff("{}", paintable().computed_values().z_index().value());
  391. else
  392. builder.append("auto"sv);
  393. builder.append(')');
  394. auto affine_transform = affine_transform_matrix();
  395. if (!affine_transform.is_identity()) {
  396. builder.appendff(", transform: {}", affine_transform);
  397. }
  398. dbgln("{}", builder.string_view());
  399. for (auto& child : m_children)
  400. child->dump(indent + 1);
  401. }
  402. }