mirror of
				https://github.com/LadybirdBrowser/ladybird.git
				synced 2025-10-30 21:01:00 +00:00 
			
		
		
		
	 86f78bff2a
			
		
	
	
		86f78bff2a
		
	
	
	
	
		
			
			This is in preparation for the `currentcolor` value, which needs to know what Node it's on so it can check the `color`.
		
			
				
	
	
		
			431 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			431 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-2-Clause
 | |
|  */
 | |
| 
 | |
| #include <AK/Demangle.h>
 | |
| #include <LibGfx/FontDatabase.h>
 | |
| #include <LibGfx/Painter.h>
 | |
| #include <LibWeb/DOM/Document.h>
 | |
| #include <LibWeb/Dump.h>
 | |
| #include <LibWeb/HTML/HTMLHtmlElement.h>
 | |
| #include <LibWeb/Layout/BlockBox.h>
 | |
| #include <LibWeb/Layout/FormattingContext.h>
 | |
| #include <LibWeb/Layout/InitialContainingBlock.h>
 | |
| #include <LibWeb/Layout/Node.h>
 | |
| #include <LibWeb/Layout/TextNode.h>
 | |
| #include <LibWeb/Page/BrowsingContext.h>
 | |
| #include <typeinfo>
 | |
| 
 | |
| namespace Web::Layout {
 | |
| 
 | |
| Node::Node(DOM::Document& document, DOM::Node* node)
 | |
|     : m_document(document)
 | |
|     , m_dom_node(node)
 | |
| {
 | |
|     if (m_dom_node)
 | |
|         m_dom_node->set_layout_node({}, this);
 | |
| }
 | |
| 
 | |
| Node::~Node()
 | |
| {
 | |
|     if (m_dom_node && m_dom_node->layout_node() == this)
 | |
|         m_dom_node->set_layout_node({}, nullptr);
 | |
| }
 | |
| 
 | |
| bool Node::can_contain_boxes_with_position_absolute() const
 | |
| {
 | |
|     return computed_values().position() != CSS::Position::Static || is<InitialContainingBlock>(*this);
 | |
| }
 | |
| 
 | |
| const BlockBox* Node::containing_block() const
 | |
| {
 | |
|     auto nearest_block_ancestor = [this] {
 | |
|         auto* ancestor = parent();
 | |
|         while (ancestor && !is<BlockBox>(*ancestor))
 | |
|             ancestor = ancestor->parent();
 | |
|         return static_cast<const BlockBox*>(ancestor);
 | |
|     };
 | |
| 
 | |
|     if (is<TextNode>(*this))
 | |
|         return nearest_block_ancestor();
 | |
| 
 | |
|     auto position = computed_values().position();
 | |
| 
 | |
|     if (position == CSS::Position::Absolute) {
 | |
|         auto* ancestor = parent();
 | |
|         while (ancestor && !ancestor->can_contain_boxes_with_position_absolute())
 | |
|             ancestor = ancestor->parent();
 | |
|         while (ancestor && (!is<BlockBox>(*ancestor) || ancestor->is_anonymous()))
 | |
|             ancestor = ancestor->containing_block();
 | |
|         return static_cast<const BlockBox*>(ancestor);
 | |
|     }
 | |
| 
 | |
|     if (position == CSS::Position::Fixed)
 | |
|         return &root();
 | |
| 
 | |
|     return nearest_block_ancestor();
 | |
| }
 | |
| 
 | |
| bool Node::establishes_stacking_context() const
 | |
| {
 | |
|     if (!has_style())
 | |
|         return false;
 | |
|     if (dom_node() == &document().root())
 | |
|         return true;
 | |
|     auto position = computed_values().position();
 | |
|     if (position == CSS::Position::Absolute || position == CSS::Position::Relative || position == CSS::Position::Fixed || position == CSS::Position::Sticky)
 | |
|         return true;
 | |
|     auto opacity = computed_values().opacity();
 | |
|     if (opacity.has_value() && opacity.value() != 1.0f)
 | |
|         return true;
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| HitTestResult Node::hit_test(const Gfx::IntPoint& position, HitTestType type) const
 | |
| {
 | |
|     HitTestResult result;
 | |
|     for_each_child_in_paint_order([&](auto& child) {
 | |
|         auto child_result = child.hit_test(position, type);
 | |
|         if (child_result.layout_node)
 | |
|             result = child_result;
 | |
|     });
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| const BrowsingContext& Node::browsing_context() const
 | |
| {
 | |
|     VERIFY(document().browsing_context());
 | |
|     return *document().browsing_context();
 | |
| }
 | |
| 
 | |
| BrowsingContext& Node::browsing_context()
 | |
| {
 | |
|     VERIFY(document().browsing_context());
 | |
|     return *document().browsing_context();
 | |
| }
 | |
| 
 | |
| const InitialContainingBlock& Node::root() const
 | |
| {
 | |
|     VERIFY(document().layout_node());
 | |
|     return *document().layout_node();
 | |
| }
 | |
| 
 | |
| InitialContainingBlock& Node::root()
 | |
| {
 | |
|     VERIFY(document().layout_node());
 | |
|     return *document().layout_node();
 | |
| }
 | |
| 
 | |
| void Node::split_into_lines(InlineFormattingContext& context, LayoutMode layout_mode)
 | |
| {
 | |
|     for_each_child([&](auto& child) {
 | |
|         child.split_into_lines(context, layout_mode);
 | |
|     });
 | |
| }
 | |
| 
 | |
| void Node::set_needs_display()
 | |
| {
 | |
|     if (auto* block = containing_block()) {
 | |
|         block->for_each_fragment([&](auto& fragment) {
 | |
|             if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) {
 | |
|                 browsing_context().set_needs_display(enclosing_int_rect(fragment.absolute_rect()));
 | |
|             }
 | |
|             return IterationDecision::Continue;
 | |
|         });
 | |
|     }
 | |
| }
 | |
| 
 | |
| Gfx::FloatPoint Node::box_type_agnostic_position() const
 | |
| {
 | |
|     if (is<Box>(*this))
 | |
|         return verify_cast<Box>(*this).absolute_position();
 | |
|     VERIFY(is_inline());
 | |
|     Gfx::FloatPoint position;
 | |
|     if (auto* block = containing_block()) {
 | |
|         block->for_each_fragment([&](auto& fragment) {
 | |
|             if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) {
 | |
|                 position = fragment.absolute_rect().location();
 | |
|                 return IterationDecision::Break;
 | |
|             }
 | |
|             return IterationDecision::Continue;
 | |
|         });
 | |
|     }
 | |
|     return position;
 | |
| }
 | |
| 
 | |
| bool Node::is_floating() const
 | |
| {
 | |
|     if (!has_style())
 | |
|         return false;
 | |
|     // flex-items don't float.
 | |
|     if (is_flex_item())
 | |
|         return false;
 | |
|     return computed_values().float_() != CSS::Float::None;
 | |
| }
 | |
| 
 | |
| bool Node::is_positioned() const
 | |
| {
 | |
|     return has_style() && computed_values().position() != CSS::Position::Static;
 | |
| }
 | |
| 
 | |
| bool Node::is_absolutely_positioned() const
 | |
| {
 | |
|     if (!has_style())
 | |
|         return false;
 | |
|     auto position = computed_values().position();
 | |
|     return position == CSS::Position::Absolute || position == CSS::Position::Fixed;
 | |
| }
 | |
| 
 | |
| bool Node::is_fixed_position() const
 | |
| {
 | |
|     if (!has_style())
 | |
|         return false;
 | |
|     auto position = computed_values().position();
 | |
|     return position == CSS::Position::Fixed;
 | |
| }
 | |
| 
 | |
| NodeWithStyle::NodeWithStyle(DOM::Document& document, DOM::Node* node, NonnullRefPtr<CSS::StyleProperties> specified_style)
 | |
|     : Node(document, node)
 | |
| {
 | |
|     m_has_style = true;
 | |
|     apply_style(*specified_style);
 | |
| }
 | |
| 
 | |
| NodeWithStyle::NodeWithStyle(DOM::Document& document, DOM::Node* node, CSS::ComputedValues computed_values)
 | |
|     : Node(document, node)
 | |
|     , m_computed_values(move(computed_values))
 | |
| {
 | |
|     m_has_style = true;
 | |
|     m_font = Gfx::FontDatabase::default_font();
 | |
| }
 | |
| 
 | |
| void NodeWithStyle::apply_style(const CSS::StyleProperties& specified_style)
 | |
| {
 | |
|     auto& computed_values = static_cast<CSS::MutableComputedValues&>(m_computed_values);
 | |
| 
 | |
|     m_font = specified_style.font(*this);
 | |
|     m_line_height = specified_style.line_height(*this);
 | |
| 
 | |
|     {
 | |
|         constexpr int default_font_size = 10;
 | |
|         auto parent_font_size = parent() == nullptr ? default_font_size : parent()->font_size();
 | |
|         auto length = specified_style.length_or_fallback(CSS::PropertyID::FontSize, CSS::Length(default_font_size, CSS::Length::Type::Px));
 | |
|         // FIXME: em sizes return 0 here, for some reason
 | |
|         m_font_size = length.resolved_or_zero(*this, parent_font_size).to_px(*this);
 | |
|         if (m_font_size == 0)
 | |
|             m_font_size = default_font_size;
 | |
|     }
 | |
| 
 | |
|     auto bgimage = specified_style.property(CSS::PropertyID::BackgroundImage);
 | |
|     if (bgimage.has_value() && bgimage.value()->is_image()) {
 | |
|         m_background_image = static_ptr_cast<CSS::ImageStyleValue>(bgimage.value());
 | |
|     }
 | |
| 
 | |
|     // FIXME: BorderXRadius properties are now BorderRadiusStyleValues, so make use of that.
 | |
|     auto border_bottom_left_radius = specified_style.property(CSS::PropertyID::BorderBottomLeftRadius);
 | |
|     if (border_bottom_left_radius.has_value())
 | |
|         computed_values.set_border_bottom_left_radius(border_bottom_left_radius.value()->to_length());
 | |
| 
 | |
|     auto border_bottom_right_radius = specified_style.property(CSS::PropertyID::BorderBottomRightRadius);
 | |
|     if (border_bottom_right_radius.has_value())
 | |
|         computed_values.set_border_bottom_right_radius(border_bottom_right_radius.value()->to_length());
 | |
| 
 | |
|     auto border_top_left_radius = specified_style.property(CSS::PropertyID::BorderTopLeftRadius);
 | |
|     if (border_top_left_radius.has_value())
 | |
|         computed_values.set_border_top_left_radius(border_top_left_radius.value()->to_length());
 | |
| 
 | |
|     auto border_top_right_radius = specified_style.property(CSS::PropertyID::BorderTopRightRadius);
 | |
|     if (border_top_right_radius.has_value())
 | |
|         computed_values.set_border_top_right_radius(border_top_right_radius.value()->to_length());
 | |
| 
 | |
|     auto background_repeat_x = specified_style.background_repeat_x();
 | |
|     if (background_repeat_x.has_value())
 | |
|         computed_values.set_background_repeat_x(background_repeat_x.value());
 | |
| 
 | |
|     auto background_repeat_y = specified_style.background_repeat_y();
 | |
|     if (background_repeat_y.has_value())
 | |
|         computed_values.set_background_repeat_y(background_repeat_y.value());
 | |
| 
 | |
|     computed_values.set_display(specified_style.display());
 | |
| 
 | |
|     auto flex_direction = specified_style.flex_direction();
 | |
|     if (flex_direction.has_value())
 | |
|         computed_values.set_flex_direction(flex_direction.value());
 | |
| 
 | |
|     auto flex_wrap = specified_style.flex_wrap();
 | |
|     if (flex_wrap.has_value())
 | |
|         computed_values.set_flex_wrap(flex_wrap.value());
 | |
| 
 | |
|     auto flex_basis = specified_style.flex_basis();
 | |
|     if (flex_basis.has_value())
 | |
|         computed_values.set_flex_basis(flex_basis.value());
 | |
| 
 | |
|     computed_values.set_flex_grow_factor(specified_style.flex_grow_factor());
 | |
|     computed_values.set_flex_shrink_factor(specified_style.flex_shrink_factor());
 | |
| 
 | |
|     auto justify_content = specified_style.justify_content();
 | |
|     if (justify_content.has_value())
 | |
|         computed_values.set_justify_content(justify_content.value());
 | |
| 
 | |
|     auto align_items = specified_style.align_items();
 | |
|     if (align_items.has_value())
 | |
|         computed_values.set_align_items(align_items.value());
 | |
| 
 | |
|     auto position = specified_style.position();
 | |
|     if (position.has_value()) {
 | |
|         computed_values.set_position(position.value());
 | |
|         if (position.value() == CSS::Position::Absolute) {
 | |
|             m_has_definite_width = true;
 | |
|             m_has_definite_height = true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     auto text_align = specified_style.text_align();
 | |
|     if (text_align.has_value())
 | |
|         computed_values.set_text_align(text_align.value());
 | |
| 
 | |
|     auto white_space = specified_style.white_space();
 | |
|     if (white_space.has_value())
 | |
|         computed_values.set_white_space(white_space.value());
 | |
| 
 | |
|     auto float_ = specified_style.float_();
 | |
|     if (float_.has_value())
 | |
|         computed_values.set_float(float_.value());
 | |
| 
 | |
|     auto clear = specified_style.clear();
 | |
|     if (clear.has_value())
 | |
|         computed_values.set_clear(clear.value());
 | |
| 
 | |
|     auto overflow_x = specified_style.overflow_x();
 | |
|     if (overflow_x.has_value())
 | |
|         computed_values.set_overflow_x(overflow_x.value());
 | |
| 
 | |
|     auto overflow_y = specified_style.overflow_y();
 | |
|     if (overflow_y.has_value())
 | |
|         computed_values.set_overflow_y(overflow_y.value());
 | |
| 
 | |
|     auto cursor = specified_style.cursor();
 | |
|     if (cursor.has_value())
 | |
|         computed_values.set_cursor(cursor.value());
 | |
| 
 | |
|     auto text_decoration_line = specified_style.text_decoration_line();
 | |
|     if (text_decoration_line.has_value())
 | |
|         computed_values.set_text_decoration_line(text_decoration_line.value());
 | |
| 
 | |
|     auto text_transform = specified_style.text_transform();
 | |
|     if (text_transform.has_value())
 | |
|         computed_values.set_text_transform(text_transform.value());
 | |
| 
 | |
|     if (auto list_style_type = specified_style.list_style_type(); list_style_type.has_value())
 | |
|         computed_values.set_list_style_type(list_style_type.value());
 | |
| 
 | |
|     computed_values.set_color(specified_style.color_or_fallback(CSS::PropertyID::Color, *this, Color::Black));
 | |
|     computed_values.set_background_color(specified_style.color_or_fallback(CSS::PropertyID::BackgroundColor, *this, Color::Transparent));
 | |
| 
 | |
|     computed_values.set_z_index(specified_style.z_index());
 | |
|     computed_values.set_opacity(specified_style.opacity());
 | |
|     if (computed_values.opacity() == 0)
 | |
|         m_visible = false;
 | |
| 
 | |
|     if (auto width = specified_style.property(CSS::PropertyID::Width); width.has_value() && !width.value()->is_auto())
 | |
|         m_has_definite_width = true;
 | |
|     computed_values.set_width(specified_style.length_or_fallback(CSS::PropertyID::Width, {}));
 | |
|     computed_values.set_min_width(specified_style.length_or_fallback(CSS::PropertyID::MinWidth, {}));
 | |
|     computed_values.set_max_width(specified_style.length_or_fallback(CSS::PropertyID::MaxWidth, {}));
 | |
| 
 | |
|     if (auto height = specified_style.property(CSS::PropertyID::Height); height.has_value())
 | |
|         m_has_definite_height = true;
 | |
|     computed_values.set_height(specified_style.length_or_fallback(CSS::PropertyID::Height, {}));
 | |
|     computed_values.set_min_height(specified_style.length_or_fallback(CSS::PropertyID::MinHeight, {}));
 | |
|     computed_values.set_max_height(specified_style.length_or_fallback(CSS::PropertyID::MaxHeight, {}));
 | |
| 
 | |
|     computed_values.set_offset(specified_style.length_box(CSS::PropertyID::Left, CSS::PropertyID::Top, CSS::PropertyID::Right, CSS::PropertyID::Bottom, CSS::Length::make_auto()));
 | |
|     computed_values.set_margin(specified_style.length_box(CSS::PropertyID::MarginLeft, CSS::PropertyID::MarginTop, CSS::PropertyID::MarginRight, CSS::PropertyID::MarginBottom, CSS::Length::make_px(0)));
 | |
|     computed_values.set_padding(specified_style.length_box(CSS::PropertyID::PaddingLeft, CSS::PropertyID::PaddingTop, CSS::PropertyID::PaddingRight, CSS::PropertyID::PaddingBottom, CSS::Length::make_px(0)));
 | |
| 
 | |
|     computed_values.set_box_shadow(specified_style.box_shadow());
 | |
| 
 | |
|     auto do_border_style = [&](CSS::BorderData& border, CSS::PropertyID width_property, CSS::PropertyID color_property, CSS::PropertyID style_property) {
 | |
|         border.color = specified_style.color_or_fallback(color_property, *this, Color::Transparent);
 | |
|         border.line_style = specified_style.line_style(style_property).value_or(CSS::LineStyle::None);
 | |
|         if (border.line_style == CSS::LineStyle::None)
 | |
|             border.width = 0;
 | |
|         else
 | |
|             border.width = specified_style.length_or_fallback(width_property, {}).resolved_or_zero(*this, 0).to_px(*this);
 | |
|     };
 | |
| 
 | |
|     do_border_style(computed_values.border_left(), CSS::PropertyID::BorderLeftWidth, CSS::PropertyID::BorderLeftColor, CSS::PropertyID::BorderLeftStyle);
 | |
|     do_border_style(computed_values.border_top(), CSS::PropertyID::BorderTopWidth, CSS::PropertyID::BorderTopColor, CSS::PropertyID::BorderTopStyle);
 | |
|     do_border_style(computed_values.border_right(), CSS::PropertyID::BorderRightWidth, CSS::PropertyID::BorderRightColor, CSS::PropertyID::BorderRightStyle);
 | |
|     do_border_style(computed_values.border_bottom(), CSS::PropertyID::BorderBottomWidth, CSS::PropertyID::BorderBottomColor, CSS::PropertyID::BorderBottomStyle);
 | |
| 
 | |
|     if (auto fill = specified_style.property(CSS::PropertyID::Fill); fill.has_value())
 | |
|         computed_values.set_fill(fill.value()->to_color(*this));
 | |
|     if (auto stroke = specified_style.property(CSS::PropertyID::Stroke); stroke.has_value())
 | |
|         computed_values.set_stroke(stroke.value()->to_color(*this));
 | |
|     if (auto stroke_width = specified_style.property(CSS::PropertyID::StrokeWidth); stroke_width.has_value()) {
 | |
|         // FIXME: Converting to pixels isn't really correct - values should be in "user units"
 | |
|         //        https://svgwg.org/svg2-draft/coords.html#TermUserUnits
 | |
|         if (stroke_width.value()->is_numeric())
 | |
|             computed_values.set_stroke_width(CSS::Length::make_px(static_cast<CSS::NumericStyleValue const&>(*stroke_width.value()).value()));
 | |
|         else
 | |
|             computed_values.set_stroke_width(stroke_width.value()->to_length());
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Node::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned, unsigned)
 | |
| {
 | |
| }
 | |
| 
 | |
| void Node::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned, unsigned)
 | |
| {
 | |
| }
 | |
| 
 | |
| void Node::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned, unsigned)
 | |
| {
 | |
| }
 | |
| 
 | |
| bool Node::handle_mousewheel(Badge<EventHandler>, const Gfx::IntPoint&, unsigned, unsigned, int wheel_delta)
 | |
| {
 | |
|     if (auto* containing_block = this->containing_block()) {
 | |
|         if (!containing_block->is_scrollable())
 | |
|             return false;
 | |
|         auto new_offset = containing_block->scroll_offset();
 | |
|         new_offset.translate_by(0, wheel_delta);
 | |
|         containing_block->set_scroll_offset(new_offset);
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| bool Node::is_root_element() const
 | |
| {
 | |
|     if (is_anonymous())
 | |
|         return false;
 | |
|     return is<HTML::HTMLHtmlElement>(*dom_node());
 | |
| }
 | |
| 
 | |
| String Node::class_name() const
 | |
| {
 | |
|     return demangle(typeid(*this).name());
 | |
| }
 | |
| 
 | |
| bool Node::is_inline_block() const
 | |
| {
 | |
|     return is_inline() && is<BlockBox>(*this);
 | |
| }
 | |
| 
 | |
| NonnullRefPtr<NodeWithStyle> NodeWithStyle::create_anonymous_wrapper() const
 | |
| {
 | |
|     auto wrapper = adopt_ref(*new BlockBox(const_cast<DOM::Document&>(document()), nullptr, m_computed_values.clone_inherited_values()));
 | |
|     wrapper->m_font = m_font;
 | |
|     wrapper->m_font_size = m_font_size;
 | |
|     wrapper->m_line_height = m_line_height;
 | |
|     wrapper->m_background_image = m_background_image;
 | |
|     return wrapper;
 | |
| }
 | |
| 
 | |
| }
 |