ViewportPaintable.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  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/SVGPaintable.h>
  9. #include <LibWeb/Painting/SVGSVGPaintable.h>
  10. #include <LibWeb/Painting/StackingContext.h>
  11. #include <LibWeb/Painting/ViewportPaintable.h>
  12. #include <LibWeb/Selection/Selection.h>
  13. namespace Web::Painting {
  14. JS_DEFINE_ALLOCATOR(ViewportPaintable);
  15. JS::NonnullGCPtr<ViewportPaintable> ViewportPaintable::create(Layout::Viewport const& layout_viewport)
  16. {
  17. return layout_viewport.heap().allocate_without_realm<ViewportPaintable>(layout_viewport);
  18. }
  19. ViewportPaintable::ViewportPaintable(Layout::Viewport const& layout_viewport)
  20. : PaintableWithLines(layout_viewport)
  21. {
  22. }
  23. ViewportPaintable::~ViewportPaintable() = default;
  24. void ViewportPaintable::build_stacking_context_tree_if_needed()
  25. {
  26. if (stacking_context())
  27. return;
  28. build_stacking_context_tree();
  29. }
  30. void ViewportPaintable::build_stacking_context_tree()
  31. {
  32. set_stacking_context(make<StackingContext>(*this, nullptr, 0));
  33. size_t index_in_tree_order = 1;
  34. for_each_in_subtree([&](Paintable const& paintable) {
  35. const_cast<Paintable&>(paintable).invalidate_stacking_context();
  36. auto* parent_context = const_cast<Paintable&>(paintable).enclosing_stacking_context();
  37. auto establishes_stacking_context = paintable.layout_node().establishes_stacking_context();
  38. if ((paintable.is_positioned() || establishes_stacking_context) && paintable.computed_values().z_index().value_or(0) == 0)
  39. parent_context->m_positioned_descendants_with_stack_level_0_and_stacking_contexts.append(paintable);
  40. if (!paintable.is_positioned() && paintable.is_floating())
  41. parent_context->m_non_positioned_floating_descendants.append(paintable);
  42. if (!establishes_stacking_context) {
  43. VERIFY(!paintable.stacking_context());
  44. return TraversalDecision::Continue;
  45. }
  46. VERIFY(parent_context);
  47. const_cast<Paintable&>(paintable).set_stacking_context(make<Painting::StackingContext>(const_cast<Paintable&>(paintable), parent_context, index_in_tree_order++));
  48. return TraversalDecision::Continue;
  49. });
  50. stacking_context()->sort();
  51. }
  52. void ViewportPaintable::paint_all_phases(PaintContext& context)
  53. {
  54. build_stacking_context_tree_if_needed();
  55. context.display_list_recorder().translate(-context.device_viewport_rect().location().to_type<int>());
  56. stacking_context()->paint(context);
  57. }
  58. void ViewportPaintable::assign_scroll_frames()
  59. {
  60. int next_id = 0;
  61. for_each_in_subtree_of_type<PaintableBox>([&](auto const& paintable_box) {
  62. if (paintable_box.has_scrollable_overflow()) {
  63. auto scroll_frame = adopt_ref(*new ScrollFrame());
  64. scroll_frame->id = next_id++;
  65. scroll_state.set(paintable_box, move(scroll_frame));
  66. }
  67. return TraversalDecision::Continue;
  68. });
  69. for_each_in_subtree([&](auto const& paintable) {
  70. for (auto block = paintable.containing_block(); !block->is_viewport(); block = block->containing_block()) {
  71. if (auto scroll_frame = scroll_state.get(block); scroll_frame.has_value()) {
  72. if (paintable.is_paintable_box()) {
  73. auto const& paintable_box = static_cast<PaintableBox const&>(paintable);
  74. const_cast<PaintableBox&>(paintable_box).set_enclosing_scroll_frame(scroll_frame.value());
  75. } else if (paintable.is_inline_paintable()) {
  76. auto const& inline_paintable = static_cast<InlinePaintable const&>(paintable);
  77. const_cast<InlinePaintable&>(inline_paintable).set_enclosing_scroll_frame(scroll_frame.value());
  78. }
  79. break;
  80. }
  81. }
  82. return TraversalDecision::Continue;
  83. });
  84. }
  85. void ViewportPaintable::assign_clip_frames()
  86. {
  87. for_each_in_subtree_of_type<PaintableBox>([&](auto const& paintable_box) {
  88. auto overflow_x = paintable_box.computed_values().overflow_x();
  89. auto overflow_y = paintable_box.computed_values().overflow_y();
  90. auto has_hidden_overflow = overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible;
  91. if (has_hidden_overflow || paintable_box.get_clip_rect().has_value()) {
  92. auto clip_frame = adopt_ref(*new ClipFrame());
  93. clip_state.set(paintable_box, move(clip_frame));
  94. }
  95. return TraversalDecision::Continue;
  96. });
  97. for_each_in_subtree([&](auto const& paintable) {
  98. for (auto block = paintable.containing_block(); !block->is_viewport(); block = block->containing_block()) {
  99. if (auto clip_frame = clip_state.get(block); clip_frame.has_value()) {
  100. if (paintable.is_paintable_box()) {
  101. auto const& paintable_box = static_cast<PaintableBox const&>(paintable);
  102. const_cast<PaintableBox&>(paintable_box).set_enclosing_clip_frame(clip_frame.value());
  103. } else if (paintable.is_inline_paintable()) {
  104. auto const& inline_paintable = static_cast<InlinePaintable const&>(paintable);
  105. const_cast<InlinePaintable&>(inline_paintable).set_enclosing_clip_frame(clip_frame.value());
  106. }
  107. break;
  108. }
  109. }
  110. return TraversalDecision::Continue;
  111. });
  112. }
  113. void ViewportPaintable::refresh_scroll_state()
  114. {
  115. if (!m_needs_to_refresh_scroll_state)
  116. return;
  117. m_needs_to_refresh_scroll_state = false;
  118. for (auto& it : scroll_state) {
  119. auto const& paintable_box = *it.key;
  120. auto& scroll_frame = *it.value;
  121. CSSPixelPoint offset;
  122. for (auto const* block = &paintable_box.layout_box(); !block->is_viewport(); block = block->containing_block()) {
  123. auto const& block_paintable_box = *block->paintable_box();
  124. offset.translate_by(block_paintable_box.scroll_offset());
  125. }
  126. scroll_frame.offset = -offset;
  127. }
  128. }
  129. void ViewportPaintable::refresh_clip_state()
  130. {
  131. if (!m_needs_to_refresh_clip_state)
  132. return;
  133. m_needs_to_refresh_clip_state = false;
  134. for (auto& it : clip_state) {
  135. auto const& paintable_box = *it.key;
  136. auto& clip_frame = *it.value;
  137. auto overflow_x = paintable_box.computed_values().overflow_x();
  138. auto overflow_y = paintable_box.computed_values().overflow_y();
  139. // Start from CSS clip property if it exists.
  140. Optional<CSSPixelRect> clip_rect = paintable_box.get_clip_rect();
  141. clip_frame.clear_border_radii_clips();
  142. if (overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible) {
  143. auto overflow_clip_rect = paintable_box.compute_absolute_padding_rect_with_css_transform_applied();
  144. for (auto const* block = &paintable_box.layout_box(); !block->is_viewport(); block = block->containing_block()) {
  145. auto const& block_paintable_box = *block->paintable_box();
  146. auto block_overflow_x = block_paintable_box.computed_values().overflow_x();
  147. auto block_overflow_y = block_paintable_box.computed_values().overflow_y();
  148. if (block_overflow_x != CSS::Overflow::Visible && block_overflow_y != CSS::Overflow::Visible) {
  149. auto rect = block_paintable_box.compute_absolute_padding_rect_with_css_transform_applied();
  150. overflow_clip_rect.intersect(rect);
  151. auto border_radii_data = block_paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
  152. if (border_radii_data.has_any_radius()) {
  153. BorderRadiiClip border_radii_clip { .rect = rect, .radii = border_radii_data };
  154. clip_frame.add_border_radii_clip(border_radii_clip);
  155. }
  156. }
  157. if (auto css_clip_property_rect = block->paintable_box()->get_clip_rect(); css_clip_property_rect.has_value())
  158. overflow_clip_rect.intersect(css_clip_property_rect.value());
  159. }
  160. clip_rect = overflow_clip_rect;
  161. }
  162. clip_frame.set_rect(*clip_rect);
  163. }
  164. }
  165. static Painting::BorderRadiiData normalize_border_radii_data(Layout::Node const& node, CSSPixelRect const& rect, CSS::BorderRadiusData const& top_left_radius, CSS::BorderRadiusData const& top_right_radius, CSS::BorderRadiusData const& bottom_right_radius, CSS::BorderRadiusData const& bottom_left_radius)
  166. {
  167. Painting::BorderRadiusData bottom_left_radius_px {};
  168. Painting::BorderRadiusData bottom_right_radius_px {};
  169. Painting::BorderRadiusData top_left_radius_px {};
  170. Painting::BorderRadiusData top_right_radius_px {};
  171. bottom_left_radius_px.horizontal_radius = bottom_left_radius.horizontal_radius.to_px(node, rect.width());
  172. bottom_right_radius_px.horizontal_radius = bottom_right_radius.horizontal_radius.to_px(node, rect.width());
  173. top_left_radius_px.horizontal_radius = top_left_radius.horizontal_radius.to_px(node, rect.width());
  174. top_right_radius_px.horizontal_radius = top_right_radius.horizontal_radius.to_px(node, rect.width());
  175. bottom_left_radius_px.vertical_radius = bottom_left_radius.vertical_radius.to_px(node, rect.height());
  176. bottom_right_radius_px.vertical_radius = bottom_right_radius.vertical_radius.to_px(node, rect.height());
  177. top_left_radius_px.vertical_radius = top_left_radius.vertical_radius.to_px(node, rect.height());
  178. top_right_radius_px.vertical_radius = top_right_radius.vertical_radius.to_px(node, rect.height());
  179. // Scale overlapping curves according to https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
  180. // Let f = min(Li/Si), where i ∈ {top, right, bottom, left},
  181. // Si is the sum of the two corresponding radii of the corners on side i,
  182. // and Ltop = Lbottom = the width of the box, and Lleft = Lright = the height of the box.
  183. auto l_top = rect.width();
  184. auto l_bottom = l_top;
  185. auto l_left = rect.height();
  186. auto l_right = l_left;
  187. auto s_top = (top_left_radius_px.horizontal_radius + top_right_radius_px.horizontal_radius);
  188. auto s_right = (top_right_radius_px.vertical_radius + bottom_right_radius_px.vertical_radius);
  189. auto s_bottom = (bottom_left_radius_px.horizontal_radius + bottom_right_radius_px.horizontal_radius);
  190. auto s_left = (top_left_radius_px.vertical_radius + bottom_left_radius_px.vertical_radius);
  191. CSSPixelFraction f = 1;
  192. f = (s_top != 0) ? min(f, l_top / s_top) : f;
  193. f = (s_right != 0) ? min(f, l_right / s_right) : f;
  194. f = (s_bottom != 0) ? min(f, l_bottom / s_bottom) : f;
  195. f = (s_left != 0) ? min(f, l_left / s_left) : f;
  196. // If f < 1, then all corner radii are reduced by multiplying them by f.
  197. if (f < 1) {
  198. top_left_radius_px.horizontal_radius *= f;
  199. top_left_radius_px.vertical_radius *= f;
  200. top_right_radius_px.horizontal_radius *= f;
  201. top_right_radius_px.vertical_radius *= f;
  202. bottom_right_radius_px.horizontal_radius *= f;
  203. bottom_right_radius_px.vertical_radius *= f;
  204. bottom_left_radius_px.horizontal_radius *= f;
  205. bottom_left_radius_px.vertical_radius *= f;
  206. }
  207. return Painting::BorderRadiiData { top_left_radius_px, top_right_radius_px, bottom_right_radius_px, bottom_left_radius_px };
  208. }
  209. void ViewportPaintable::resolve_paint_only_properties()
  210. {
  211. // Resolves layout-dependent properties not handled during layout and stores them in the paint tree.
  212. // Properties resolved include:
  213. // - Border radii
  214. // - Box shadows
  215. // - Text shadows
  216. // - Transforms
  217. // - Transform origins
  218. // - Outlines
  219. for_each_in_inclusive_subtree([&](Paintable& paintable) {
  220. auto& layout_node = paintable.layout_node();
  221. auto const is_inline_paintable = paintable.is_inline_paintable();
  222. auto const is_paintable_box = paintable.is_paintable_box();
  223. auto const is_paintable_with_lines = paintable.is_paintable_with_lines();
  224. auto const& computed_values = layout_node.computed_values();
  225. // Border radii
  226. if (is_inline_paintable) {
  227. auto& inline_paintable = static_cast<Painting::InlinePaintable&>(paintable);
  228. auto& fragments = inline_paintable.fragments();
  229. auto const& top_left_border_radius = computed_values.border_top_left_radius();
  230. auto const& top_right_border_radius = computed_values.border_top_right_radius();
  231. auto const& bottom_right_border_radius = computed_values.border_bottom_right_radius();
  232. auto const& bottom_left_border_radius = computed_values.border_bottom_left_radius();
  233. auto containing_block_position_in_absolute_coordinates = inline_paintable.containing_block()->absolute_position();
  234. for (size_t i = 0; i < fragments.size(); ++i) {
  235. auto is_first_fragment = i == 0;
  236. auto is_last_fragment = i == fragments.size() - 1;
  237. auto& fragment = fragments[i];
  238. CSSPixelRect absolute_fragment_rect {
  239. containing_block_position_in_absolute_coordinates.translated(fragment.offset()),
  240. fragment.size()
  241. };
  242. if (is_first_fragment) {
  243. auto extra_start_width = inline_paintable.box_model().padding.left;
  244. absolute_fragment_rect.translate_by(-extra_start_width, 0);
  245. absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_start_width);
  246. }
  247. if (is_last_fragment) {
  248. auto extra_end_width = inline_paintable.box_model().padding.right;
  249. absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_end_width);
  250. }
  251. auto border_radii_data = normalize_border_radii_data(layout_node,
  252. absolute_fragment_rect, top_left_border_radius,
  253. top_right_border_radius,
  254. bottom_right_border_radius,
  255. bottom_left_border_radius);
  256. fragment.set_border_radii_data(border_radii_data);
  257. }
  258. }
  259. // Border radii
  260. if (is_paintable_box) {
  261. auto& paintable_box = static_cast<Painting::PaintableBox&>(paintable);
  262. CSSPixelRect const border_rect { 0, 0, paintable_box.border_box_width(), paintable_box.border_box_height() };
  263. auto const& border_top_left_radius = computed_values.border_top_left_radius();
  264. auto const& border_top_right_radius = computed_values.border_top_right_radius();
  265. auto const& border_bottom_right_radius = computed_values.border_bottom_right_radius();
  266. auto const& border_bottom_left_radius = computed_values.border_bottom_left_radius();
  267. auto radii_data = normalize_border_radii_data(layout_node, border_rect, border_top_left_radius,
  268. border_top_right_radius, border_bottom_right_radius,
  269. border_bottom_left_radius);
  270. paintable_box.set_border_radii_data(radii_data);
  271. }
  272. // Box shadows
  273. auto const& box_shadow_data = computed_values.box_shadow();
  274. if (!box_shadow_data.is_empty()) {
  275. Vector<Painting::ShadowData> resolved_box_shadow_data;
  276. resolved_box_shadow_data.ensure_capacity(box_shadow_data.size());
  277. for (auto const& layer : box_shadow_data) {
  278. resolved_box_shadow_data.empend(
  279. layer.color,
  280. layer.offset_x.to_px(layout_node),
  281. layer.offset_y.to_px(layout_node),
  282. layer.blur_radius.to_px(layout_node),
  283. layer.spread_distance.to_px(layout_node),
  284. layer.placement == CSS::ShadowPlacement::Outer ? Painting::ShadowPlacement::Outer
  285. : Painting::ShadowPlacement::Inner);
  286. }
  287. if (is<Painting::PaintableBox>(paintable)) {
  288. auto& paintable_box = static_cast<Painting::PaintableBox&>(paintable);
  289. paintable_box.set_box_shadow_data(move(resolved_box_shadow_data));
  290. } else if (is<Painting::InlinePaintable>(paintable)) {
  291. auto& inline_paintable = static_cast<Painting::InlinePaintable&>(paintable);
  292. inline_paintable.set_box_shadow_data(move(resolved_box_shadow_data));
  293. }
  294. }
  295. // Text shadows
  296. if (is_paintable_with_lines) {
  297. auto const& paintable_with_lines = static_cast<Painting::PaintableWithLines const&>(paintable);
  298. for (auto const& fragment : paintable_with_lines.fragments()) {
  299. auto const& text_shadow = fragment.m_layout_node->computed_values().text_shadow();
  300. if (!text_shadow.is_empty()) {
  301. Vector<Painting::ShadowData> resolved_shadow_data;
  302. resolved_shadow_data.ensure_capacity(text_shadow.size());
  303. for (auto const& layer : text_shadow) {
  304. resolved_shadow_data.empend(
  305. layer.color,
  306. layer.offset_x.to_px(layout_node),
  307. layer.offset_y.to_px(layout_node),
  308. layer.blur_radius.to_px(layout_node),
  309. layer.spread_distance.to_px(layout_node),
  310. Painting::ShadowPlacement::Outer);
  311. }
  312. const_cast<Painting::PaintableFragment&>(fragment).set_shadows(move(resolved_shadow_data));
  313. }
  314. }
  315. }
  316. // Transform and transform origin
  317. if (is_paintable_box) {
  318. auto& paintable_box = static_cast<Painting::PaintableBox&>(paintable);
  319. auto const& transformations = paintable_box.computed_values().transformations();
  320. if (!transformations.is_empty()) {
  321. auto matrix = Gfx::FloatMatrix4x4::identity();
  322. for (auto const& transform : transformations)
  323. matrix = matrix * transform.to_matrix(paintable_box).release_value();
  324. paintable_box.set_transform(matrix);
  325. }
  326. auto const& transform_origin = paintable_box.computed_values().transform_origin();
  327. // https://www.w3.org/TR/css-transforms-1/#transform-box
  328. auto transform_box = paintable_box.computed_values().transform_box();
  329. // For SVG elements without associated CSS layout box, the used value for content-box is fill-box and for
  330. // border-box is stroke-box.
  331. // FIXME: This currently detects any SVG element except the <svg> one. Is that correct?
  332. // And is it correct to use `else` below?
  333. if (is<Painting::SVGPaintable>(paintable_box)) {
  334. switch (transform_box) {
  335. case CSS::TransformBox::ContentBox:
  336. transform_box = CSS::TransformBox::FillBox;
  337. break;
  338. case CSS::TransformBox::BorderBox:
  339. transform_box = CSS::TransformBox::StrokeBox;
  340. break;
  341. default:
  342. break;
  343. }
  344. }
  345. // For elements with associated CSS layout box, the used value for fill-box is content-box and for
  346. // stroke-box and view-box is border-box.
  347. else {
  348. switch (transform_box) {
  349. case CSS::TransformBox::FillBox:
  350. transform_box = CSS::TransformBox::ContentBox;
  351. break;
  352. case CSS::TransformBox::StrokeBox:
  353. case CSS::TransformBox::ViewBox:
  354. transform_box = CSS::TransformBox::BorderBox;
  355. break;
  356. default:
  357. break;
  358. }
  359. }
  360. CSSPixelRect reference_box = [&]() {
  361. switch (transform_box) {
  362. case CSS::TransformBox::ContentBox:
  363. // Uses the content box as reference box.
  364. // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box.
  365. return paintable_box.absolute_rect();
  366. case CSS::TransformBox::BorderBox:
  367. // Uses the border box as reference box.
  368. // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box.
  369. return paintable_box.absolute_border_box_rect();
  370. case CSS::TransformBox::FillBox:
  371. // Uses the object bounding box as reference box.
  372. // FIXME: For now we're using the content rect as an approximation.
  373. return paintable_box.absolute_rect();
  374. case CSS::TransformBox::StrokeBox:
  375. // Uses the stroke bounding box as reference box.
  376. // FIXME: For now we're using the border rect as an approximation.
  377. return paintable_box.absolute_border_box_rect();
  378. case CSS::TransformBox::ViewBox:
  379. // Uses the nearest SVG viewport as reference box.
  380. // FIXME: If a viewBox attribute is specified for the SVG viewport creating element:
  381. // - The reference box is positioned at the origin of the coordinate system established by the viewBox attribute.
  382. // - The dimension of the reference box is set to the width and height values of the viewBox attribute.
  383. auto* svg_paintable = paintable_box.first_ancestor_of_type<Painting::SVGSVGPaintable>();
  384. if (!svg_paintable)
  385. return paintable_box.absolute_border_box_rect();
  386. return svg_paintable->absolute_rect();
  387. }
  388. VERIFY_NOT_REACHED();
  389. }();
  390. auto x = reference_box.left() + transform_origin.x.to_px(layout_node, reference_box.width());
  391. auto y = reference_box.top() + transform_origin.y.to_px(layout_node, reference_box.height());
  392. paintable_box.set_transform_origin({ x, y });
  393. paintable_box.set_transform_origin({ x, y });
  394. }
  395. // Outlines
  396. auto outline_width = computed_values.outline_width().to_px(layout_node);
  397. auto outline_data = borders_data_for_outline(layout_node, computed_values.outline_color(), computed_values.outline_style(), outline_width);
  398. auto outline_offset = computed_values.outline_offset().to_px(layout_node);
  399. if (is_paintable_box) {
  400. auto& paintable_box = static_cast<Painting::PaintableBox&>(paintable);
  401. paintable_box.set_outline_data(outline_data);
  402. paintable_box.set_outline_offset(outline_offset);
  403. } else if (is_inline_paintable) {
  404. auto& inline_paintable = static_cast<Painting::InlinePaintable&>(paintable);
  405. inline_paintable.set_outline_data(outline_data);
  406. inline_paintable.set_outline_offset(outline_offset);
  407. }
  408. if (is_paintable_box) {
  409. auto& paintable_box = static_cast<Painting::PaintableBox&>(paintable);
  410. auto combined_transform = paintable.compute_combined_css_transform();
  411. paintable_box.set_combined_css_transform(combined_transform);
  412. } else if (is_inline_paintable) {
  413. auto& inline_paintable = static_cast<Painting::InlinePaintable&>(paintable);
  414. auto combined_transform = paintable.compute_combined_css_transform();
  415. inline_paintable.set_combined_css_transform(combined_transform);
  416. }
  417. return TraversalDecision::Continue;
  418. });
  419. }
  420. JS::GCPtr<Selection::Selection> ViewportPaintable::selection() const
  421. {
  422. return const_cast<DOM::Document&>(document()).get_selection();
  423. }
  424. void ViewportPaintable::recompute_selection_states()
  425. {
  426. // 1. Start by resetting the selection state of all layout nodes to None.
  427. for_each_in_inclusive_subtree([&](auto& layout_node) {
  428. layout_node.set_selection_state(SelectionState::None);
  429. return TraversalDecision::Continue;
  430. });
  431. // 2. If there is no active Selection or selected Range, return.
  432. auto selection = document().get_selection();
  433. if (!selection)
  434. return;
  435. auto range = selection->range();
  436. if (!range)
  437. return;
  438. auto* start_container = range->start_container();
  439. auto* end_container = range->end_container();
  440. // 3. If the selection starts and ends in the same node:
  441. if (start_container == end_container) {
  442. // 1. If the selection starts and ends at the same offset, return.
  443. if (range->start_offset() == range->end_offset()) {
  444. // NOTE: A zero-length selection should not be visible.
  445. return;
  446. }
  447. // 2. If it's a text node, mark it as StartAndEnd and return.
  448. if (is<DOM::Text>(*start_container)) {
  449. if (auto* paintable = start_container->paintable()) {
  450. paintable->set_selection_state(SelectionState::StartAndEnd);
  451. }
  452. return;
  453. }
  454. }
  455. if (start_container == end_container && is<DOM::Text>(*start_container)) {
  456. if (auto* paintable = start_container->paintable()) {
  457. paintable->set_selection_state(SelectionState::StartAndEnd);
  458. }
  459. return;
  460. }
  461. // 4. Mark the selection start node as Start (if text) or Full (if anything else).
  462. if (auto* paintable = start_container->paintable()) {
  463. if (is<DOM::Text>(*start_container))
  464. paintable->set_selection_state(SelectionState::Start);
  465. else
  466. paintable->set_selection_state(SelectionState::Full);
  467. }
  468. // 5. Mark the selection end node as End (if text) or Full (if anything else).
  469. if (auto* paintable = end_container->paintable()) {
  470. if (is<DOM::Text>(*end_container))
  471. paintable->set_selection_state(SelectionState::End);
  472. else
  473. paintable->set_selection_state(SelectionState::Full);
  474. }
  475. // 6. Mark the nodes between start node and end node (in tree order) as Full.
  476. for (auto* node = start_container->next_in_pre_order(); node && node != end_container; node = node->next_in_pre_order()) {
  477. if (auto* paintable = node->paintable())
  478. paintable->set_selection_state(SelectionState::Full);
  479. }
  480. }
  481. bool ViewportPaintable::handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned, unsigned, int, int)
  482. {
  483. return false;
  484. }
  485. void ViewportPaintable::visit_edges(Visitor& visitor)
  486. {
  487. Base::visit_edges(visitor);
  488. visitor.visit(scroll_state);
  489. visitor.visit(clip_state);
  490. }
  491. }