123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- /*
- * Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #include <LibWeb/DOM/Range.h>
- #include <LibWeb/Layout/Viewport.h>
- #include <LibWeb/Painting/StackingContext.h>
- #include <LibWeb/Painting/ViewportPaintable.h>
- #include <LibWeb/Selection/Selection.h>
- namespace Web::Painting {
- JS_DEFINE_ALLOCATOR(ViewportPaintable);
- JS::NonnullGCPtr<ViewportPaintable> ViewportPaintable::create(Layout::Viewport const& layout_viewport)
- {
- return layout_viewport.heap().allocate_without_realm<ViewportPaintable>(layout_viewport);
- }
- ViewportPaintable::ViewportPaintable(Layout::Viewport const& layout_viewport)
- : PaintableWithLines(layout_viewport)
- {
- }
- ViewportPaintable::~ViewportPaintable() = default;
- void ViewportPaintable::build_stacking_context_tree_if_needed()
- {
- if (stacking_context())
- return;
- build_stacking_context_tree();
- }
- void ViewportPaintable::build_stacking_context_tree()
- {
- set_stacking_context(make<StackingContext>(*this, nullptr, 0));
- size_t index_in_tree_order = 1;
- for_each_in_subtree([&](Paintable const& paintable) {
- const_cast<Paintable&>(paintable).invalidate_stacking_context();
- auto* parent_context = const_cast<Paintable&>(paintable).enclosing_stacking_context();
- auto establishes_stacking_context = paintable.layout_node().establishes_stacking_context();
- if ((paintable.is_positioned() || establishes_stacking_context) && paintable.computed_values().z_index().value_or(0) == 0)
- parent_context->m_positioned_descendants_with_stack_level_0_and_stacking_contexts.append(paintable);
- if (!paintable.is_positioned() && paintable.is_floating())
- parent_context->m_non_positioned_floating_descendants.append(paintable);
- if (!establishes_stacking_context) {
- VERIFY(!paintable.stacking_context());
- return TraversalDecision::Continue;
- }
- VERIFY(parent_context);
- const_cast<Paintable&>(paintable).set_stacking_context(make<Painting::StackingContext>(const_cast<Paintable&>(paintable), parent_context, index_in_tree_order++));
- return TraversalDecision::Continue;
- });
- stacking_context()->sort();
- }
- void ViewportPaintable::paint_all_phases(PaintContext& context)
- {
- build_stacking_context_tree_if_needed();
- stacking_context()->paint(context);
- }
- void ViewportPaintable::assign_scroll_frames()
- {
- int next_id = 0;
- for_each_in_inclusive_subtree_of_type<PaintableBox>([&](auto& paintable_box) {
- if (paintable_box.has_scrollable_overflow() || is<ViewportPaintable>(paintable_box)) {
- auto scroll_frame = adopt_ref(*new ScrollFrame());
- scroll_frame->id = next_id++;
- scroll_frame->parent = paintable_box.nearest_scroll_frame();
- paintable_box.set_own_scroll_frame(scroll_frame);
- scroll_state.set(paintable_box, move(scroll_frame));
- }
- return TraversalDecision::Continue;
- });
- for_each_in_subtree([&](auto const& paintable) {
- if (paintable.is_fixed_position()) {
- return TraversalDecision::Continue;
- }
- for (auto block = paintable.containing_block(); block; block = block->containing_block()) {
- if (auto scroll_frame = block->own_scroll_frame(); scroll_frame) {
- if (paintable.is_paintable_box()) {
- auto const& paintable_box = static_cast<PaintableBox const&>(paintable);
- const_cast<PaintableBox&>(paintable_box).set_enclosing_scroll_frame(*scroll_frame);
- } else if (paintable.is_inline_paintable()) {
- auto const& inline_paintable = static_cast<InlinePaintable const&>(paintable);
- const_cast<InlinePaintable&>(inline_paintable).set_enclosing_scroll_frame(*scroll_frame);
- }
- return TraversalDecision::Continue;
- }
- if (block->is_fixed_position()) {
- return TraversalDecision::Continue;
- }
- }
- VERIFY_NOT_REACHED();
- });
- }
- void ViewportPaintable::assign_clip_frames()
- {
- for_each_in_subtree_of_type<PaintableBox>([&](auto const& paintable_box) {
- auto overflow_x = paintable_box.computed_values().overflow_x();
- auto overflow_y = paintable_box.computed_values().overflow_y();
- auto has_hidden_overflow = overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible;
- if (has_hidden_overflow || paintable_box.get_clip_rect().has_value()) {
- auto clip_frame = adopt_ref(*new ClipFrame());
- clip_state.set(paintable_box, move(clip_frame));
- }
- return TraversalDecision::Continue;
- });
- for_each_in_subtree([&](auto const& paintable) {
- for (auto block = paintable.containing_block(); !block->is_viewport(); block = block->containing_block()) {
- if (auto clip_frame = clip_state.get(block); clip_frame.has_value()) {
- if (paintable.is_paintable_box()) {
- auto const& paintable_box = static_cast<PaintableBox const&>(paintable);
- const_cast<PaintableBox&>(paintable_box).set_enclosing_clip_frame(clip_frame.value());
- } else if (paintable.is_inline_paintable()) {
- auto const& inline_paintable = static_cast<InlinePaintable const&>(paintable);
- const_cast<InlinePaintable&>(inline_paintable).set_enclosing_clip_frame(clip_frame.value());
- }
- break;
- }
- if (block->has_css_transform()) {
- break;
- }
- }
- return TraversalDecision::Continue;
- });
- for (auto& it : clip_state) {
- auto const& paintable_box = *it.key;
- auto& clip_frame = *it.value;
- for (auto const* block = &paintable_box.layout_box(); !block->is_viewport(); block = block->containing_block()) {
- auto const& block_paintable_box = *block->paintable_box();
- auto block_overflow_x = block_paintable_box.computed_values().overflow_x();
- auto block_overflow_y = block_paintable_box.computed_values().overflow_y();
- if (block_overflow_x != CSS::Overflow::Visible && block_overflow_y != CSS::Overflow::Visible) {
- auto rect = block_paintable_box.absolute_padding_box_rect();
- clip_frame.add_clip_rect(rect, block_paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes), block_paintable_box.enclosing_scroll_frame());
- }
- if (auto css_clip_property_rect = block->paintable_box()->get_clip_rect(); css_clip_property_rect.has_value()) {
- clip_frame.add_clip_rect(css_clip_property_rect.value(), {}, block_paintable_box.enclosing_scroll_frame());
- }
- if (block->has_css_transform()) {
- break;
- }
- }
- }
- }
- void ViewportPaintable::refresh_scroll_state()
- {
- if (!m_needs_to_refresh_scroll_state)
- return;
- m_needs_to_refresh_scroll_state = false;
- for (auto& it : scroll_state) {
- auto const& paintable_box = *it.key;
- auto& scroll_frame = *it.value;
- scroll_frame.own_offset = -paintable_box.scroll_offset();
- }
- }
- void ViewportPaintable::resolve_paint_only_properties()
- {
- // Resolves layout-dependent properties not handled during layout and stores them in the paint tree.
- // Properties resolved include:
- // - Border radii
- // - Box shadows
- // - Text shadows
- // - Transforms
- // - Transform origins
- // - Outlines
- for_each_in_inclusive_subtree([&](Paintable& paintable) {
- paintable.resolve_paint_properties();
- return TraversalDecision::Continue;
- });
- }
- JS::GCPtr<Selection::Selection> ViewportPaintable::selection() const
- {
- return const_cast<DOM::Document&>(document()).get_selection();
- }
- void ViewportPaintable::recompute_selection_states()
- {
- // 1. Start by resetting the selection state of all layout nodes to None.
- for_each_in_inclusive_subtree([&](auto& layout_node) {
- layout_node.set_selection_state(SelectionState::None);
- return TraversalDecision::Continue;
- });
- // 2. If there is no active Selection or selected Range, return.
- auto selection = document().get_selection();
- if (!selection)
- return;
- auto range = selection->range();
- if (!range)
- return;
- auto* start_container = range->start_container();
- auto* end_container = range->end_container();
- // 3. If the selection starts and ends in the same node:
- if (start_container == end_container) {
- // 1. If the selection starts and ends at the same offset, return.
- if (range->start_offset() == range->end_offset()) {
- // NOTE: A zero-length selection should not be visible.
- return;
- }
- // 2. If it's a text node, mark it as StartAndEnd and return.
- if (is<DOM::Text>(*start_container)) {
- if (auto* paintable = start_container->paintable())
- paintable->set_selection_state(SelectionState::StartAndEnd);
- return;
- }
- }
- // 4. Mark the selection start node as Start (if text) or Full (if anything else).
- if (auto* paintable = start_container->paintable()) {
- if (is<DOM::Text>(*start_container))
- paintable->set_selection_state(SelectionState::Start);
- else
- paintable->set_selection_state(SelectionState::Full);
- }
- // 5. Mark the selection end node as End (if text) or Full (if anything else).
- if (auto* paintable = end_container->paintable()) {
- if (is<DOM::Text>(*end_container))
- paintable->set_selection_state(SelectionState::End);
- else
- paintable->set_selection_state(SelectionState::Full);
- }
- // 6. Mark the nodes between start node and end node (in tree order) as Full.
- for (auto* node = start_container->next_in_pre_order(); node && node != end_container; node = node->next_in_pre_order()) {
- if (auto* paintable = node->paintable())
- paintable->set_selection_state(SelectionState::Full);
- }
- }
- bool ViewportPaintable::handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned, unsigned, int, int)
- {
- return false;
- }
- void ViewportPaintable::visit_edges(Visitor& visitor)
- {
- Base::visit_edges(visitor);
- visitor.visit(scroll_state);
- visitor.visit(clip_state);
- }
- }
|