diff --git a/Libraries/LibWeb/Editing/Commands.cpp b/Libraries/LibWeb/Editing/Commands.cpp index f521c01c61146..3ee882bf3b6be 100644 --- a/Libraries/LibWeb/Editing/Commands.cpp +++ b/Libraries/LibWeb/Editing/Commands.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -417,10 +418,11 @@ bool command_insert_linebreak_action(DOM::Document& document, String const&) // * Insert another newline (\n) character if the active range's start offset is equal to the length of the // active range's start node. // * Return true. - if (is(*start_node) && start_node->layout_node()) { + if (is(*start_node)) { auto& text_node = static_cast(*start_node); - auto white_space = text_node.layout_node()->computed_values().white_space(); - if (first_is_one_of(white_space, CSS::WhiteSpace::Pre, CSS::WhiteSpace::PreLine, CSS::WhiteSpace::PreWrap)) { + auto resolved_white_space = resolved_keyword(*start_node, CSS::PropertyID::WhiteSpace); + if (resolved_white_space.has_value() + && first_is_one_of(resolved_white_space.value(), CSS::Keyword::Pre, CSS::Keyword::PreLine, CSS::Keyword::PreWrap)) { MUST(text_node.insert_data(active_range.start_offset(), "\n"_string)); MUST(selection.collapse(start_node, active_range.start_offset() + 1)); if (selection.range()->start_offset() == start_node->length()) diff --git a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp index 367086970dc54..3889cbae61a29 100644 --- a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp @@ -4,6 +4,8 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include #include #include @@ -250,15 +252,14 @@ void canonicalize_whitespace(GC::Ref node, u32 offset, bool fix_colla // "white-space" is neither "pre" nor "pre-wrap" and start offset is not zero and the // (start offset − 1)st code unit of start node's data is a space (0x0020) or // non-breaking space (0x00A0), subtract one from start offset. - auto* layout_node = start_node->parent()->layout_node(); - if (layout_node && is(*start_node) && start_offset != 0) { - auto parent_white_space = layout_node->computed_values().white_space(); + if (is(*start_node) && start_offset != 0) { + auto parent_white_space = resolved_keyword(*start_node->parent(), CSS::PropertyID::WhiteSpace); // FIXME: Find a way to get code points directly from the UTF-8 string auto start_node_data = *start_node->text_content(); auto utf16_code_units = MUST(AK::utf8_to_utf16(start_node_data)); auto offset_minus_one_code_point = Utf16View { utf16_code_units }.code_point_at(start_offset - 1); - if (parent_white_space != CSS::WhiteSpace::Pre && parent_white_space != CSS::WhiteSpace::PreWrap + if (parent_white_space != CSS::Keyword::Pre && parent_white_space != CSS::Keyword::PreWrap && (offset_minus_one_code_point == 0x20 || offset_minus_one_code_point == 0xA0)) { --start_offset; continue; @@ -304,15 +305,14 @@ void canonicalize_whitespace(GC::Ref node, u32 offset, bool fix_colla // "white-space" is neither "pre" nor "pre-wrap" and end offset is not end node's length // and the end offsetth code unit of end node's data is a space (0x0020) or non-breaking // space (0x00A0): - auto* layout_node = end_node->parent()->layout_node(); - if (layout_node && is(*end_node) && end_offset != end_node->length()) { - auto parent_white_space = layout_node->computed_values().white_space(); + if (is(*end_node) && end_offset != end_node->length()) { + auto parent_white_space = resolved_keyword(*end_node->parent(), CSS::PropertyID::WhiteSpace); // FIXME: Find a way to get code points directly from the UTF-8 string auto end_node_data = *end_node->text_content(); auto utf16_code_units = MUST(AK::utf8_to_utf16(end_node_data)); auto offset_code_point = Utf16View { utf16_code_units }.code_point_at(end_offset); - if (parent_white_space != CSS::WhiteSpace::Pre && parent_white_space != CSS::WhiteSpace::PreWrap + if (parent_white_space != CSS::Keyword::Pre && parent_white_space != CSS::Keyword::PreWrap && (offset_code_point == 0x20 || offset_code_point == 0xA0)) { // 1. If fix collapsed space is true, and collapse spaces is true, and the end offsetth // code unit of end node's data is a space (0x0020): call deleteData(end offset, 1) @@ -370,10 +370,9 @@ void canonicalize_whitespace(GC::Ref node, u32 offset, bool fix_colla // "white-space" is neither "pre" nor "pre-wrap" and end offset is end node's length and // the last code unit of end node's data is a space (0x0020) and end node precedes a line // break: - auto* layout_node = end_node->parent()->layout_node(); - if (layout_node && is(*end_node) && end_offset == end_node->length() && precedes_a_line_break(end_node)) { - auto parent_white_space = layout_node->computed_values().white_space(); - if (parent_white_space != CSS::WhiteSpace::Pre && parent_white_space != CSS::WhiteSpace::PreWrap + if (is(*end_node) && end_offset == end_node->length() && precedes_a_line_break(end_node)) { + auto parent_white_space = resolved_keyword(*end_node->parent(), CSS::PropertyID::WhiteSpace); + if (parent_white_space != CSS::Keyword::Pre && parent_white_space != CSS::Keyword::PreWrap && end_node->text_content().value().ends_with_bytes(" "sv)) { // 1. Subtract one from end offset. --end_offset; @@ -1267,14 +1266,11 @@ bool is_block_node(GC::Ref node) if (is(*node) || is(*node)) return true; - auto layout_node = node->layout_node(); - if (!layout_node) + if (!is(*node)) return false; - - auto display = layout_node->display(); - return is(*node) - && !(display.is_inline_outside() && (display.is_flow_inside() || display.is_flow_root_inside() || display.is_table_inside())) - && !display.is_none(); + auto display_keyword = resolved_keyword(node, CSS::PropertyID::Display); + return !display_keyword.has_value() + || !first_is_one_of(display_keyword.value(), CSS::Keyword::Inline, CSS::Keyword::InlineBlock, CSS::Keyword::InlineTable, CSS::Keyword::None); } // https://w3c.github.io/editing/docs/execCommand/#block-start-point @@ -1364,8 +1360,15 @@ bool is_collapsed_whitespace_node(GC::Ref node) return true; // 5. If the "display" property of some ancestor of node has resolved value "none", return true. - if (ancestor->layout_node() && ancestor->layout_node()->display().is_none()) - return true; + GC::Ptr some_ancestor = node->parent(); + while (some_ancestor) { + if (is(*some_ancestor)) { + auto resolved_display = resolved_keyword(*some_ancestor, CSS::PropertyID::Display); + if (resolved_display.has_value() && resolved_display.value() == CSS::Keyword::None) + return true; + } + some_ancestor = some_ancestor->parent(); + } // 6. While ancestor is not a block node and its parent is not null, set ancestor to its parent. while (!is_block_node(*ancestor) && ancestor->parent()) @@ -1615,8 +1618,8 @@ bool is_visible_node(GC::Ref node) // value "none". GC::Ptr inclusive_ancestor = node; while (inclusive_ancestor) { - auto* layout_node = inclusive_ancestor->layout_node(); - if (layout_node && layout_node->display().is_none()) + auto resolved_display = resolved_keyword(*inclusive_ancestor, CSS::PropertyID::Display); + if (resolved_display.has_value() && resolved_display.value() == CSS::Keyword::None) return false; inclusive_ancestor = inclusive_ancestor->parent(); } @@ -1666,10 +1669,10 @@ bool is_whitespace_node(GC::Ref node) GC::Ptr parent = node->parent(); if (!is(parent.ptr())) return false; - auto* layout_node = parent->layout_node(); - if (!layout_node) + auto resolved_white_space = resolved_keyword(*parent, CSS::PropertyID::WhiteSpace); + if (!resolved_white_space.has_value()) return false; - auto white_space = layout_node->computed_values().white_space(); + auto white_space = resolved_white_space.value(); // or a Text node whose data consists only of one or more tabs (0x0009), line feeds (0x000A), // carriage returns (0x000D), and/or spaces (0x0020), and whose parent is an Element whose @@ -1678,7 +1681,7 @@ bool is_whitespace_node(GC::Ref node) return codepoint == '\t' || codepoint == '\n' || codepoint == '\r' || codepoint == ' '; }; auto code_points = character_data.data().code_points(); - if (all_of(code_points, is_tab_lf_cr_or_space) && (white_space == CSS::WhiteSpace::Normal || white_space == CSS::WhiteSpace::Nowrap)) + if (all_of(code_points, is_tab_lf_cr_or_space) && (white_space == CSS::Keyword::Normal || white_space == CSS::Keyword::Nowrap)) return true; // or a Text node whose data consists only of one or more tabs (0x0009), carriage returns @@ -1687,7 +1690,7 @@ bool is_whitespace_node(GC::Ref node) auto is_tab_cr_or_space = [](u32 codepoint) { return codepoint == '\t' || codepoint == '\r' || codepoint == ' '; }; - if (all_of(code_points, is_tab_cr_or_space) && white_space == CSS::WhiteSpace::PreLine) + if (all_of(code_points, is_tab_cr_or_space) && white_space == CSS::Keyword::PreLine) return true; return false; @@ -2116,10 +2119,10 @@ GC::Ref set_the_tag_name(GC::Ref element, FlyString // https://w3c.github.io/editing/docs/execCommand/#specified-command-value Optional specified_command_value(GC::Ref element, FlyString const& command) { - // 1. If command is "backColor" or "hiliteColor" and the Element's display property does not have resolved value "inline", return null. - auto layout_node = element->layout_node(); - if ((command == CommandNames::backColor || command == CommandNames::hiliteColor) && layout_node) { - if (layout_node->computed_values().display().is_inline_outside()) + // 1. If command is "backColor" or "hiliteColor" and the Element's display property does not have resolved value + // "inline", return null. + if (command == CommandNames::backColor || command == CommandNames::hiliteColor) { + if (resolved_keyword(element, CSS::PropertyID::Display) != CSS::Keyword::Inline) return {}; } @@ -2518,4 +2521,29 @@ bool is_heading(FlyString const& local_name) HTML::TagNames::h6); } +Optional resolved_keyword(GC::Ref node, CSS::PropertyID property_id) +{ + auto resolved_property = resolved_value(node, property_id); + if (!resolved_property.has_value() || !resolved_property.value()->is_keyword()) + return {}; + return resolved_property.value()->as_keyword().keyword(); +} + +Optional> resolved_value(GC::Ref node, CSS::PropertyID property_id) +{ + // Find the nearest inclusive ancestor of node that is an Element + GC::Ptr element = node; + while (element && !is(*element)) + element = element->parent(); + if (!element) + return {}; + + // Retrieve resolved style value + auto resolved_css_style_declaration = CSS::ResolvedCSSStyleDeclaration::create(static_cast(*element)); + auto optional_style_property = resolved_css_style_declaration->property(property_id); + if (!optional_style_property.has_value()) + return {}; + return optional_style_property.value().value; +} + } diff --git a/Libraries/LibWeb/Editing/Internal/Algorithms.h b/Libraries/LibWeb/Editing/Internal/Algorithms.h index 78a42b436983b..1ac3f23e9ad3c 100644 --- a/Libraries/LibWeb/Editing/Internal/Algorithms.h +++ b/Libraries/LibWeb/Editing/Internal/Algorithms.h @@ -90,5 +90,7 @@ GC::Ptr wrap(Vector>, Function); bool is_heading(FlyString const&); +Optional resolved_keyword(GC::Ref, CSS::PropertyID); +Optional> resolved_value(GC::Ref, CSS::PropertyID); }