ViewportPaintable.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. /*
  2. * Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibWeb/DOM/Range.h>
  7. #include <LibWeb/Layout/Viewport.h>
  8. #include <LibWeb/Painting/StackingContext.h>
  9. #include <LibWeb/Painting/ViewportPaintable.h>
  10. #include <LibWeb/Selection/Selection.h>
  11. namespace Web::Painting {
  12. JS_DEFINE_ALLOCATOR(ViewportPaintable);
  13. JS::NonnullGCPtr<ViewportPaintable> ViewportPaintable::create(Layout::Viewport const& layout_viewport)
  14. {
  15. return layout_viewport.heap().allocate_without_realm<ViewportPaintable>(layout_viewport);
  16. }
  17. ViewportPaintable::ViewportPaintable(Layout::Viewport const& layout_viewport)
  18. : PaintableWithLines(layout_viewport)
  19. {
  20. }
  21. ViewportPaintable::~ViewportPaintable() = default;
  22. void ViewportPaintable::build_stacking_context_tree_if_needed()
  23. {
  24. if (stacking_context())
  25. return;
  26. build_stacking_context_tree();
  27. }
  28. void ViewportPaintable::build_stacking_context_tree()
  29. {
  30. set_stacking_context(make<StackingContext>(*this, nullptr, 0));
  31. size_t index_in_tree_order = 1;
  32. for_each_in_subtree([&](Paintable const& paintable) {
  33. const_cast<Paintable&>(paintable).invalidate_stacking_context();
  34. auto* parent_context = const_cast<Paintable&>(paintable).enclosing_stacking_context();
  35. auto establishes_stacking_context = paintable.layout_node().establishes_stacking_context();
  36. if ((paintable.is_positioned() || establishes_stacking_context) && paintable.computed_values().z_index().value_or(0) == 0)
  37. parent_context->m_positioned_descendants_with_stack_level_0_and_stacking_contexts.append(paintable);
  38. if (!paintable.is_positioned() && paintable.is_floating())
  39. parent_context->m_non_positioned_floating_descendants.append(paintable);
  40. if (!establishes_stacking_context) {
  41. VERIFY(!paintable.stacking_context());
  42. return TraversalDecision::Continue;
  43. }
  44. VERIFY(parent_context);
  45. const_cast<Paintable&>(paintable).set_stacking_context(make<Painting::StackingContext>(const_cast<Paintable&>(paintable), parent_context, index_in_tree_order++));
  46. return TraversalDecision::Continue;
  47. });
  48. stacking_context()->sort();
  49. }
  50. void ViewportPaintable::paint_all_phases(PaintContext& context)
  51. {
  52. build_stacking_context_tree_if_needed();
  53. stacking_context()->paint(context);
  54. }
  55. void ViewportPaintable::assign_scroll_frames()
  56. {
  57. int next_id = 0;
  58. for_each_in_inclusive_subtree_of_type<PaintableBox>([&](auto& paintable_box) {
  59. RefPtr<ScrollFrame> sticky_scroll_frame;
  60. if (paintable_box.is_sticky_position()) {
  61. sticky_scroll_frame = adopt_ref(*new ScrollFrame());
  62. sticky_scroll_frame->id = next_id++;
  63. auto const* nearest_scrollable_ancestor = paintable_box.nearest_scrollable_ancestor();
  64. if (nearest_scrollable_ancestor) {
  65. sticky_scroll_frame->parent = nearest_scrollable_ancestor->nearest_scroll_frame();
  66. }
  67. const_cast<PaintableBox&>(paintable_box).set_enclosing_scroll_frame(sticky_scroll_frame);
  68. const_cast<PaintableBox&>(paintable_box).set_own_scroll_frame(sticky_scroll_frame);
  69. sticky_state.set(paintable_box, sticky_scroll_frame);
  70. }
  71. if (paintable_box.has_scrollable_overflow() || is<ViewportPaintable>(paintable_box)) {
  72. auto scroll_frame = adopt_ref(*new ScrollFrame());
  73. scroll_frame->id = next_id++;
  74. if (sticky_scroll_frame) {
  75. scroll_frame->parent = sticky_scroll_frame;
  76. } else {
  77. scroll_frame->parent = paintable_box.nearest_scroll_frame();
  78. }
  79. paintable_box.set_own_scroll_frame(scroll_frame);
  80. scroll_state.set(paintable_box, move(scroll_frame));
  81. }
  82. return TraversalDecision::Continue;
  83. });
  84. for_each_in_subtree([&](auto const& paintable) {
  85. if (paintable.is_fixed_position()) {
  86. return TraversalDecision::Continue;
  87. }
  88. if (paintable.is_sticky_position()) {
  89. return TraversalDecision::Continue;
  90. }
  91. for (auto block = paintable.containing_block(); block; block = block->containing_block()) {
  92. if (auto scroll_frame = block->own_scroll_frame(); scroll_frame) {
  93. if (paintable.is_paintable_box()) {
  94. auto const& paintable_box = static_cast<PaintableBox const&>(paintable);
  95. const_cast<PaintableBox&>(paintable_box).set_enclosing_scroll_frame(*scroll_frame);
  96. } else if (paintable.is_inline_paintable()) {
  97. auto const& inline_paintable = static_cast<InlinePaintable const&>(paintable);
  98. const_cast<InlinePaintable&>(inline_paintable).set_enclosing_scroll_frame(*scroll_frame);
  99. }
  100. return TraversalDecision::Continue;
  101. }
  102. if (block->is_fixed_position()) {
  103. return TraversalDecision::Continue;
  104. }
  105. }
  106. VERIFY_NOT_REACHED();
  107. });
  108. }
  109. void ViewportPaintable::assign_clip_frames()
  110. {
  111. for_each_in_subtree_of_type<PaintableBox>([&](auto const& paintable_box) {
  112. auto overflow_x = paintable_box.computed_values().overflow_x();
  113. auto overflow_y = paintable_box.computed_values().overflow_y();
  114. auto has_hidden_overflow = overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible;
  115. if (has_hidden_overflow || paintable_box.get_clip_rect().has_value()) {
  116. auto clip_frame = adopt_ref(*new ClipFrame());
  117. clip_state.set(paintable_box, move(clip_frame));
  118. }
  119. return TraversalDecision::Continue;
  120. });
  121. for_each_in_subtree([&](auto const& paintable) {
  122. for (auto block = paintable.containing_block(); !block->is_viewport(); block = block->containing_block()) {
  123. if (auto clip_frame = clip_state.get(block); clip_frame.has_value()) {
  124. if (paintable.is_paintable_box()) {
  125. auto const& paintable_box = static_cast<PaintableBox const&>(paintable);
  126. const_cast<PaintableBox&>(paintable_box).set_enclosing_clip_frame(clip_frame.value());
  127. } else if (paintable.is_inline_paintable()) {
  128. auto const& inline_paintable = static_cast<InlinePaintable const&>(paintable);
  129. const_cast<InlinePaintable&>(inline_paintable).set_enclosing_clip_frame(clip_frame.value());
  130. }
  131. break;
  132. }
  133. if (block->has_css_transform()) {
  134. break;
  135. }
  136. }
  137. return TraversalDecision::Continue;
  138. });
  139. for (auto& it : clip_state) {
  140. auto const& paintable_box = *it.key;
  141. auto& clip_frame = *it.value;
  142. for (auto const* block = &paintable_box.layout_box(); !block->is_viewport(); block = block->containing_block()) {
  143. auto const& block_paintable_box = *block->paintable_box();
  144. auto block_overflow_x = block_paintable_box.computed_values().overflow_x();
  145. auto block_overflow_y = block_paintable_box.computed_values().overflow_y();
  146. if (block_overflow_x != CSS::Overflow::Visible && block_overflow_y != CSS::Overflow::Visible) {
  147. auto rect = block_paintable_box.absolute_padding_box_rect();
  148. clip_frame.add_clip_rect(rect, block_paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes), block_paintable_box.enclosing_scroll_frame());
  149. }
  150. if (auto css_clip_property_rect = block->paintable_box()->get_clip_rect(); css_clip_property_rect.has_value()) {
  151. clip_frame.add_clip_rect(css_clip_property_rect.value(), {}, block_paintable_box.enclosing_scroll_frame());
  152. }
  153. if (block->has_css_transform()) {
  154. break;
  155. }
  156. }
  157. }
  158. }
  159. void ViewportPaintable::refresh_scroll_state()
  160. {
  161. if (!m_needs_to_refresh_scroll_state)
  162. return;
  163. m_needs_to_refresh_scroll_state = false;
  164. for (auto& it : sticky_state) {
  165. auto const& sticky_box = *it.key;
  166. auto& scroll_frame = *it.value;
  167. auto const& sticky_insets = sticky_box.sticky_insets();
  168. auto const* nearest_scrollable_ancestor = sticky_box.nearest_scrollable_ancestor();
  169. if (!nearest_scrollable_ancestor) {
  170. continue;
  171. }
  172. // Min and max offsets are needed to clamp the sticky box's position to stay within bounds of containing block.
  173. CSSPixels min_y_offset_relative_to_nearest_scrollable_ancestor;
  174. CSSPixels max_y_offset_relative_to_nearest_scrollable_ancestor;
  175. CSSPixels min_x_offset_relative_to_nearest_scrollable_ancestor;
  176. CSSPixels max_x_offset_relative_to_nearest_scrollable_ancestor;
  177. auto const* containing_block_of_sticky_box = sticky_box.containing_block();
  178. if (containing_block_of_sticky_box->is_scrollable()) {
  179. min_y_offset_relative_to_nearest_scrollable_ancestor = 0;
  180. max_y_offset_relative_to_nearest_scrollable_ancestor = containing_block_of_sticky_box->scrollable_overflow_rect()->height() - sticky_box.absolute_padding_box_rect().height();
  181. min_x_offset_relative_to_nearest_scrollable_ancestor = 0;
  182. max_x_offset_relative_to_nearest_scrollable_ancestor = containing_block_of_sticky_box->scrollable_overflow_rect()->width() - sticky_box.absolute_padding_box_rect().width();
  183. } else {
  184. auto containing_block_rect_relative_to_nearest_scrollable_ancestor = containing_block_of_sticky_box->absolute_padding_box_rect().translated(-nearest_scrollable_ancestor->absolute_rect().top_left());
  185. min_y_offset_relative_to_nearest_scrollable_ancestor = containing_block_rect_relative_to_nearest_scrollable_ancestor.top();
  186. max_y_offset_relative_to_nearest_scrollable_ancestor = containing_block_rect_relative_to_nearest_scrollable_ancestor.bottom() - sticky_box.absolute_padding_box_rect().height();
  187. min_x_offset_relative_to_nearest_scrollable_ancestor = containing_block_rect_relative_to_nearest_scrollable_ancestor.left();
  188. max_x_offset_relative_to_nearest_scrollable_ancestor = containing_block_rect_relative_to_nearest_scrollable_ancestor.right() - sticky_box.absolute_padding_box_rect().width();
  189. }
  190. auto padding_rect_of_sticky_box_relative_to_nearest_scrollable_ancestor = sticky_box.padding_box_rect_relative_to_nearest_scrollable_ancestor();
  191. // By default, the sticky box is shifted by the scroll offset of the nearest scrollable ancestor.
  192. CSSPixelPoint sticky_offset = -nearest_scrollable_ancestor->scroll_offset();
  193. CSSPixelRect const scrollport_rect { nearest_scrollable_ancestor->scroll_offset(), nearest_scrollable_ancestor->absolute_rect().size() };
  194. if (sticky_insets.top.has_value()) {
  195. auto top_inset = sticky_insets.top.value();
  196. auto stick_to_top_scroll_offset_threshold = padding_rect_of_sticky_box_relative_to_nearest_scrollable_ancestor.top() - top_inset;
  197. if (scrollport_rect.top() > stick_to_top_scroll_offset_threshold) {
  198. sticky_offset.translate_by({ 0, -padding_rect_of_sticky_box_relative_to_nearest_scrollable_ancestor.top() });
  199. sticky_offset.translate_by({ 0, min(scrollport_rect.top() + top_inset, max_y_offset_relative_to_nearest_scrollable_ancestor) });
  200. }
  201. }
  202. if (sticky_insets.left.has_value()) {
  203. auto left_inset = sticky_insets.left.value();
  204. auto stick_to_left_scroll_offset_threshold = padding_rect_of_sticky_box_relative_to_nearest_scrollable_ancestor.left() - left_inset;
  205. if (scrollport_rect.left() > stick_to_left_scroll_offset_threshold) {
  206. sticky_offset.translate_by({ -padding_rect_of_sticky_box_relative_to_nearest_scrollable_ancestor.left(), 0 });
  207. sticky_offset.translate_by({ min(scrollport_rect.left() + left_inset, max_x_offset_relative_to_nearest_scrollable_ancestor), 0 });
  208. }
  209. }
  210. if (sticky_insets.bottom.has_value()) {
  211. auto bottom_inset = sticky_insets.bottom.value();
  212. auto stick_to_bottom_scroll_offset_threshold = padding_rect_of_sticky_box_relative_to_nearest_scrollable_ancestor.bottom() + bottom_inset;
  213. if (scrollport_rect.bottom() < stick_to_bottom_scroll_offset_threshold) {
  214. sticky_offset.translate_by({ 0, -padding_rect_of_sticky_box_relative_to_nearest_scrollable_ancestor.top() });
  215. sticky_offset.translate_by({ 0, max(scrollport_rect.bottom() - sticky_box.absolute_padding_box_rect().height() - bottom_inset, min_y_offset_relative_to_nearest_scrollable_ancestor) });
  216. }
  217. }
  218. if (sticky_insets.right.has_value()) {
  219. auto right_inset = sticky_insets.right.value();
  220. auto stick_to_right_scroll_offset_threshold = padding_rect_of_sticky_box_relative_to_nearest_scrollable_ancestor.right() + right_inset;
  221. if (scrollport_rect.right() < stick_to_right_scroll_offset_threshold) {
  222. sticky_offset.translate_by({ -padding_rect_of_sticky_box_relative_to_nearest_scrollable_ancestor.left(), 0 });
  223. sticky_offset.translate_by({ max(scrollport_rect.right() - sticky_box.absolute_padding_box_rect().width() - right_inset, min_x_offset_relative_to_nearest_scrollable_ancestor), 0 });
  224. }
  225. }
  226. scroll_frame.own_offset = sticky_offset;
  227. }
  228. for (auto& it : scroll_state) {
  229. auto const& paintable_box = *it.key;
  230. auto& scroll_frame = *it.value;
  231. scroll_frame.own_offset = -paintable_box.scroll_offset();
  232. }
  233. }
  234. void ViewportPaintable::resolve_paint_only_properties()
  235. {
  236. // Resolves layout-dependent properties not handled during layout and stores them in the paint tree.
  237. // Properties resolved include:
  238. // - Border radii
  239. // - Box shadows
  240. // - Text shadows
  241. // - Transforms
  242. // - Transform origins
  243. // - Outlines
  244. for_each_in_inclusive_subtree([&](Paintable& paintable) {
  245. paintable.resolve_paint_properties();
  246. return TraversalDecision::Continue;
  247. });
  248. }
  249. JS::GCPtr<Selection::Selection> ViewportPaintable::selection() const
  250. {
  251. return const_cast<DOM::Document&>(document()).get_selection();
  252. }
  253. void ViewportPaintable::recompute_selection_states()
  254. {
  255. // 1. Start by resetting the selection state of all layout nodes to None.
  256. for_each_in_inclusive_subtree([&](auto& layout_node) {
  257. layout_node.set_selection_state(SelectionState::None);
  258. return TraversalDecision::Continue;
  259. });
  260. // 2. If there is no active Selection or selected Range, return.
  261. auto selection = document().get_selection();
  262. if (!selection)
  263. return;
  264. auto range = selection->range();
  265. if (!range)
  266. return;
  267. auto* start_container = range->start_container();
  268. auto* end_container = range->end_container();
  269. // 3. If the selection starts and ends in the same node:
  270. if (start_container == end_container) {
  271. // 1. If the selection starts and ends at the same offset, return.
  272. if (range->start_offset() == range->end_offset()) {
  273. // NOTE: A zero-length selection should not be visible.
  274. return;
  275. }
  276. // 2. If it's a text node, mark it as StartAndEnd and return.
  277. if (is<DOM::Text>(*start_container)) {
  278. if (auto* paintable = start_container->paintable())
  279. paintable->set_selection_state(SelectionState::StartAndEnd);
  280. return;
  281. }
  282. }
  283. // 4. Mark the selection start node as Start (if text) or Full (if anything else).
  284. if (auto* paintable = start_container->paintable()) {
  285. if (is<DOM::Text>(*start_container))
  286. paintable->set_selection_state(SelectionState::Start);
  287. else
  288. paintable->set_selection_state(SelectionState::Full);
  289. }
  290. // 5. Mark the selection end node as End (if text) or Full (if anything else).
  291. if (auto* paintable = end_container->paintable()) {
  292. if (is<DOM::Text>(*end_container))
  293. paintable->set_selection_state(SelectionState::End);
  294. else
  295. paintable->set_selection_state(SelectionState::Full);
  296. }
  297. // 6. Mark the nodes between start node and end node (in tree order) as Full.
  298. for (auto* node = start_container->next_in_pre_order(); node && node != end_container; node = node->next_in_pre_order()) {
  299. if (auto* paintable = node->paintable())
  300. paintable->set_selection_state(SelectionState::Full);
  301. }
  302. }
  303. bool ViewportPaintable::handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned, unsigned, int, int)
  304. {
  305. return false;
  306. }
  307. void ViewportPaintable::visit_edges(Visitor& visitor)
  308. {
  309. Base::visit_edges(visitor);
  310. visitor.visit(scroll_state);
  311. visitor.visit(sticky_state);
  312. visitor.visit(clip_state);
  313. }
  314. }