Serenity Operating System
1/*
2 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <LibWeb/DOM/Range.h>
8#include <LibWeb/Dump.h>
9#include <LibWeb/Layout/Viewport.h>
10#include <LibWeb/Painting/PaintableBox.h>
11#include <LibWeb/Painting/StackingContext.h>
12
13namespace Web::Layout {
14
15Viewport::Viewport(DOM::Document& document, NonnullRefPtr<CSS::StyleProperties> style)
16 : BlockContainer(document, &document, move(style))
17{
18}
19
20Viewport::~Viewport() = default;
21
22JS::GCPtr<Selection::Selection> Viewport::selection() const
23{
24 return const_cast<DOM::Document&>(document()).get_selection();
25}
26
27void Viewport::build_stacking_context_tree_if_needed()
28{
29 if (paint_box()->stacking_context())
30 return;
31 build_stacking_context_tree();
32}
33
34void Viewport::build_stacking_context_tree()
35{
36 const_cast<Painting::PaintableWithLines*>(paint_box())->set_stacking_context(make<Painting::StackingContext>(*this, nullptr));
37
38 for_each_in_subtree_of_type<Box>([&](Box& box) {
39 if (!box.paint_box())
40 return IterationDecision::Continue;
41 const_cast<Painting::PaintableBox*>(box.paint_box())->invalidate_stacking_context();
42 if (!box.establishes_stacking_context()) {
43 VERIFY(!box.paint_box()->stacking_context());
44 return IterationDecision::Continue;
45 }
46 auto* parent_context = const_cast<Painting::PaintableBox*>(box.paint_box())->enclosing_stacking_context();
47 VERIFY(parent_context);
48 const_cast<Painting::PaintableBox*>(box.paint_box())->set_stacking_context(make<Painting::StackingContext>(box, parent_context));
49 return IterationDecision::Continue;
50 });
51
52 const_cast<Painting::PaintableWithLines*>(paint_box())->stacking_context()->sort();
53}
54
55void Viewport::paint_all_phases(PaintContext& context)
56{
57 build_stacking_context_tree_if_needed();
58 context.painter().fill_rect(context.enclosing_device_rect(paint_box()->absolute_rect()).to_type<int>(), document().background_color(context.palette()));
59 context.painter().translate(-context.device_viewport_rect().location().to_type<int>());
60 paint_box()->stacking_context()->paint(context);
61}
62
63void Viewport::recompute_selection_states()
64{
65 // 1. Start by resetting the selection state of all layout nodes to None.
66 for_each_in_inclusive_subtree([&](auto& layout_node) {
67 layout_node.set_selection_state(SelectionState::None);
68 return IterationDecision::Continue;
69 });
70
71 // 2. If there is no active Selection or selected Range, return.
72 auto selection = document().get_selection();
73 if (!selection)
74 return;
75 auto range = selection->range();
76 if (!range)
77 return;
78
79 auto* start_container = range->start_container();
80 auto* end_container = range->end_container();
81
82 // 3. If the selection starts and ends in the same node:
83 if (start_container == end_container) {
84 // 1. If the selection starts and ends at the same offset, return.
85 if (range->start_offset() == range->end_offset()) {
86 // NOTE: A zero-length selection should not be visible.
87 return;
88 }
89
90 // 2. If it's a text node, mark it as StartAndEnd and return.
91 if (is<DOM::Text>(*start_container)) {
92 if (auto* layout_node = start_container->layout_node()) {
93 layout_node->set_selection_state(SelectionState::StartAndEnd);
94 }
95 return;
96 }
97 }
98
99 if (start_container == end_container && is<DOM::Text>(*start_container)) {
100 if (auto* layout_node = start_container->layout_node()) {
101 layout_node->set_selection_state(SelectionState::StartAndEnd);
102 }
103 return;
104 }
105
106 // 4. Mark the selection start node as Start (if text) or Full (if anything else).
107 if (auto* layout_node = start_container->layout_node()) {
108 if (is<DOM::Text>(*start_container))
109 layout_node->set_selection_state(SelectionState::Start);
110 else
111 layout_node->set_selection_state(SelectionState::Full);
112 }
113
114 // 5. Mark the selection end node as End (if text) or Full (if anything else).
115 if (auto* layout_node = end_container->layout_node()) {
116 if (is<DOM::Text>(*end_container))
117 layout_node->set_selection_state(SelectionState::End);
118 else
119 layout_node->set_selection_state(SelectionState::Full);
120 }
121
122 // 6. Mark the nodes between start node and end node (in tree order) as Full.
123 for (auto* node = start_container->next_in_pre_order(); node && node != end_container; node = node->next_in_pre_order()) {
124 if (auto* layout_node = node->layout_node())
125 layout_node->set_selection_state(SelectionState::Full);
126 }
127}
128
129}