mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-30 04:39:06 +00:00
LibWeb: Rework obtaining resolved styles in the Editing API
The algorithm referenced to in the Editing spec whenever they talk about obtaining the "resolved" style or value is actually implemented in ResolvedCSSStyleDeclaration, so use that instead of going directly to the computed styles.
This commit is contained in:
parent
433c19c1ed
commit
2c51ed8dec
Notes:
github-actions[bot]
2024-12-21 18:17:40 +00:00
Author: https://github.com/gmta
Commit: 2c51ed8dec
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2991
3 changed files with 79 additions and 34 deletions
|
@ -4,6 +4,7 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
|
||||||
#include <LibWeb/DOM/Comment.h>
|
#include <LibWeb/DOM/Comment.h>
|
||||||
#include <LibWeb/DOM/Document.h>
|
#include <LibWeb/DOM/Document.h>
|
||||||
#include <LibWeb/DOM/DocumentFragment.h>
|
#include <LibWeb/DOM/DocumentFragment.h>
|
||||||
|
@ -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
|
// * Insert another newline (\n) character if the active range's start offset is equal to the length of the
|
||||||
// active range's start node.
|
// active range's start node.
|
||||||
// * Return true.
|
// * Return true.
|
||||||
if (is<DOM::Text>(*start_node) && start_node->layout_node()) {
|
if (is<DOM::Text>(*start_node)) {
|
||||||
auto& text_node = static_cast<DOM::Text&>(*start_node);
|
auto& text_node = static_cast<DOM::Text&>(*start_node);
|
||||||
auto white_space = text_node.layout_node()->computed_values().white_space();
|
auto resolved_white_space = resolved_keyword(*start_node, CSS::PropertyID::WhiteSpace);
|
||||||
if (first_is_one_of(white_space, CSS::WhiteSpace::Pre, CSS::WhiteSpace::PreLine, CSS::WhiteSpace::PreWrap)) {
|
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(text_node.insert_data(active_range.start_offset(), "\n"_string));
|
||||||
MUST(selection.collapse(start_node, active_range.start_offset() + 1));
|
MUST(selection.collapse(start_node, active_range.start_offset() + 1));
|
||||||
if (selection.range()->start_offset() == start_node->length())
|
if (selection.range()->start_offset() == start_node->length())
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <LibWeb/CSS/ResolvedCSSStyleDeclaration.h>
|
||||||
|
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
|
||||||
|
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
|
||||||
|
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
|
||||||
#include <LibWeb/DOM/CharacterData.h>
|
#include <LibWeb/DOM/CharacterData.h>
|
||||||
#include <LibWeb/DOM/DocumentFragment.h>
|
#include <LibWeb/DOM/DocumentFragment.h>
|
||||||
#include <LibWeb/DOM/DocumentType.h>
|
#include <LibWeb/DOM/DocumentType.h>
|
||||||
|
@ -250,15 +254,14 @@ void canonicalize_whitespace(GC::Ref<DOM::Node> node, u32 offset, bool fix_colla
|
||||||
// "white-space" is neither "pre" nor "pre-wrap" and start offset is not zero and the
|
// "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
|
// (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.
|
// non-breaking space (0x00A0), subtract one from start offset.
|
||||||
auto* layout_node = start_node->parent()->layout_node();
|
if (is<DOM::Text>(*start_node) && start_offset != 0) {
|
||||||
if (layout_node && is<DOM::Text>(*start_node) && start_offset != 0) {
|
auto parent_white_space = resolved_keyword(*start_node->parent(), CSS::PropertyID::WhiteSpace);
|
||||||
auto parent_white_space = layout_node->computed_values().white_space();
|
|
||||||
|
|
||||||
// FIXME: Find a way to get code points directly from the UTF-8 string
|
// FIXME: Find a way to get code points directly from the UTF-8 string
|
||||||
auto start_node_data = *start_node->text_content();
|
auto start_node_data = *start_node->text_content();
|
||||||
auto utf16_code_units = MUST(AK::utf8_to_utf16(start_node_data));
|
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);
|
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)) {
|
&& (offset_minus_one_code_point == 0x20 || offset_minus_one_code_point == 0xA0)) {
|
||||||
--start_offset;
|
--start_offset;
|
||||||
continue;
|
continue;
|
||||||
|
@ -304,15 +307,14 @@ void canonicalize_whitespace(GC::Ref<DOM::Node> node, u32 offset, bool fix_colla
|
||||||
// "white-space" is neither "pre" nor "pre-wrap" and end offset is not end node's length
|
// "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
|
// and the end offsetth code unit of end node's data is a space (0x0020) or non-breaking
|
||||||
// space (0x00A0):
|
// space (0x00A0):
|
||||||
auto* layout_node = end_node->parent()->layout_node();
|
if (is<DOM::Text>(*end_node) && end_offset != end_node->length()) {
|
||||||
if (layout_node && is<DOM::Text>(*end_node) && end_offset != end_node->length()) {
|
auto parent_white_space = resolved_keyword(*end_node->parent(), CSS::PropertyID::WhiteSpace);
|
||||||
auto parent_white_space = layout_node->computed_values().white_space();
|
|
||||||
|
|
||||||
// FIXME: Find a way to get code points directly from the UTF-8 string
|
// FIXME: Find a way to get code points directly from the UTF-8 string
|
||||||
auto end_node_data = *end_node->text_content();
|
auto end_node_data = *end_node->text_content();
|
||||||
auto utf16_code_units = MUST(AK::utf8_to_utf16(end_node_data));
|
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);
|
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)) {
|
&& (offset_code_point == 0x20 || offset_code_point == 0xA0)) {
|
||||||
// 1. If fix collapsed space is true, and collapse spaces is true, and the end offsetth
|
// 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)
|
// code unit of end node's data is a space (0x0020): call deleteData(end offset, 1)
|
||||||
|
@ -370,10 +372,9 @@ void canonicalize_whitespace(GC::Ref<DOM::Node> node, u32 offset, bool fix_colla
|
||||||
// "white-space" is neither "pre" nor "pre-wrap" and end offset is end node's length and
|
// "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
|
// the last code unit of end node's data is a space (0x0020) and end node precedes a line
|
||||||
// break:
|
// break:
|
||||||
auto* layout_node = end_node->parent()->layout_node();
|
if (is<DOM::Text>(*end_node) && end_offset == end_node->length() && precedes_a_line_break(end_node)) {
|
||||||
if (layout_node && is<DOM::Text>(*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);
|
||||||
auto parent_white_space = layout_node->computed_values().white_space();
|
if (parent_white_space != CSS::Keyword::Pre && parent_white_space != CSS::Keyword::PreWrap
|
||||||
if (parent_white_space != CSS::WhiteSpace::Pre && parent_white_space != CSS::WhiteSpace::PreWrap
|
|
||||||
&& end_node->text_content().value().ends_with_bytes(" "sv)) {
|
&& end_node->text_content().value().ends_with_bytes(" "sv)) {
|
||||||
// 1. Subtract one from end offset.
|
// 1. Subtract one from end offset.
|
||||||
--end_offset;
|
--end_offset;
|
||||||
|
@ -1267,14 +1268,14 @@ bool is_block_node(GC::Ref<DOM::Node> node)
|
||||||
if (is<DOM::Document>(*node) || is<DOM::DocumentFragment>(*node))
|
if (is<DOM::Document>(*node) || is<DOM::DocumentFragment>(*node))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
auto layout_node = node->layout_node();
|
if (!is<DOM::Element>(*node))
|
||||||
if (!layout_node)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto display = layout_node->display();
|
auto display = resolved_display(node);
|
||||||
return is<DOM::Element>(*node)
|
if (!display.has_value())
|
||||||
&& !(display.is_inline_outside() && (display.is_flow_inside() || display.is_flow_root_inside() || display.is_table_inside()))
|
return true;
|
||||||
&& !display.is_none();
|
return !(display->is_inline_outside() && (display->is_flow_inside() || display->is_flow_root_inside() || display->is_table_inside()))
|
||||||
|
&& !display->is_none();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://w3c.github.io/editing/docs/execCommand/#block-start-point
|
// https://w3c.github.io/editing/docs/execCommand/#block-start-point
|
||||||
|
@ -1364,8 +1365,13 @@ bool is_collapsed_whitespace_node(GC::Ref<DOM::Node> node)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// 5. If the "display" property of some ancestor of node has resolved value "none", 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())
|
GC::Ptr<DOM::Node> some_ancestor = node->parent();
|
||||||
return true;
|
while (some_ancestor) {
|
||||||
|
auto display = resolved_display(*some_ancestor);
|
||||||
|
if (display.has_value() && display->is_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.
|
// 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())
|
while (!is_block_node(*ancestor) && ancestor->parent())
|
||||||
|
@ -1615,8 +1621,8 @@ bool is_visible_node(GC::Ref<DOM::Node> node)
|
||||||
// value "none".
|
// value "none".
|
||||||
GC::Ptr<DOM::Node> inclusive_ancestor = node;
|
GC::Ptr<DOM::Node> inclusive_ancestor = node;
|
||||||
while (inclusive_ancestor) {
|
while (inclusive_ancestor) {
|
||||||
auto* layout_node = inclusive_ancestor->layout_node();
|
auto display = resolved_display(*inclusive_ancestor);
|
||||||
if (layout_node && layout_node->display().is_none())
|
if (display.has_value() && display->is_none())
|
||||||
return false;
|
return false;
|
||||||
inclusive_ancestor = inclusive_ancestor->parent();
|
inclusive_ancestor = inclusive_ancestor->parent();
|
||||||
}
|
}
|
||||||
|
@ -1666,10 +1672,10 @@ bool is_whitespace_node(GC::Ref<DOM::Node> node)
|
||||||
GC::Ptr<DOM::Node> parent = node->parent();
|
GC::Ptr<DOM::Node> parent = node->parent();
|
||||||
if (!is<DOM::Element>(parent.ptr()))
|
if (!is<DOM::Element>(parent.ptr()))
|
||||||
return false;
|
return false;
|
||||||
auto* layout_node = parent->layout_node();
|
auto resolved_white_space = resolved_keyword(*parent, CSS::PropertyID::WhiteSpace);
|
||||||
if (!layout_node)
|
if (!resolved_white_space.has_value())
|
||||||
return false;
|
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),
|
// 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
|
// carriage returns (0x000D), and/or spaces (0x0020), and whose parent is an Element whose
|
||||||
|
@ -1678,7 +1684,7 @@ bool is_whitespace_node(GC::Ref<DOM::Node> node)
|
||||||
return codepoint == '\t' || codepoint == '\n' || codepoint == '\r' || codepoint == ' ';
|
return codepoint == '\t' || codepoint == '\n' || codepoint == '\r' || codepoint == ' ';
|
||||||
};
|
};
|
||||||
auto code_points = character_data.data().code_points();
|
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;
|
return true;
|
||||||
|
|
||||||
// or a Text node whose data consists only of one or more tabs (0x0009), carriage returns
|
// or a Text node whose data consists only of one or more tabs (0x0009), carriage returns
|
||||||
|
@ -1687,7 +1693,7 @@ bool is_whitespace_node(GC::Ref<DOM::Node> node)
|
||||||
auto is_tab_cr_or_space = [](u32 codepoint) {
|
auto is_tab_cr_or_space = [](u32 codepoint) {
|
||||||
return codepoint == '\t' || codepoint == '\r' || 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 true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -2116,10 +2122,11 @@ GC::Ref<DOM::Element> set_the_tag_name(GC::Ref<DOM::Element> element, FlyString
|
||||||
// https://w3c.github.io/editing/docs/execCommand/#specified-command-value
|
// https://w3c.github.io/editing/docs/execCommand/#specified-command-value
|
||||||
Optional<String> specified_command_value(GC::Ref<DOM::Element> element, FlyString const& command)
|
Optional<String> specified_command_value(GC::Ref<DOM::Element> 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.
|
// 1. If command is "backColor" or "hiliteColor" and the Element's display property does not have resolved value
|
||||||
auto layout_node = element->layout_node();
|
// "inline", return null.
|
||||||
if ((command == CommandNames::backColor || command == CommandNames::hiliteColor) && layout_node) {
|
if (command == CommandNames::backColor || command == CommandNames::hiliteColor) {
|
||||||
if (layout_node->computed_values().display().is_inline_outside())
|
auto display = resolved_display(element);
|
||||||
|
if (!display.has_value() || !display->is_inline_outside() || !display->is_flow_inside())
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2518,4 +2525,37 @@ bool is_heading(FlyString const& local_name)
|
||||||
HTML::TagNames::h6);
|
HTML::TagNames::h6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Optional<CSS::Display> resolved_display(GC::Ref<DOM::Node> node)
|
||||||
|
{
|
||||||
|
auto resolved_property = resolved_value(node, CSS::PropertyID::Display);
|
||||||
|
if (!resolved_property.has_value() || !resolved_property.value()->is_display())
|
||||||
|
return {};
|
||||||
|
return resolved_property.value()->as_display().display();
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<CSS::Keyword> resolved_keyword(GC::Ref<DOM::Node> 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<NonnullRefPtr<CSS::CSSStyleValue const>> resolved_value(GC::Ref<DOM::Node> node, CSS::PropertyID property_id)
|
||||||
|
{
|
||||||
|
// Find the nearest inclusive ancestor of node that is an Element. This allows for passing in a DOM::Text node.
|
||||||
|
GC::Ptr<DOM::Node> element = node;
|
||||||
|
while (element && !is<DOM::Element>(*element))
|
||||||
|
element = element->parent();
|
||||||
|
if (!element)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// Retrieve resolved style value
|
||||||
|
auto resolved_css_style_declaration = CSS::ResolvedCSSStyleDeclaration::create(static_cast<DOM::Element&>(*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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,5 +90,8 @@ GC::Ptr<DOM::Node> wrap(Vector<GC::Ref<DOM::Node>>, Function<bool(GC::Ref<DOM::N
|
||||||
|
|
||||||
bool has_visible_children(GC::Ref<DOM::Node>);
|
bool has_visible_children(GC::Ref<DOM::Node>);
|
||||||
bool is_heading(FlyString const&);
|
bool is_heading(FlyString const&);
|
||||||
|
Optional<CSS::Display> resolved_display(GC::Ref<DOM::Node>);
|
||||||
|
Optional<CSS::Keyword> resolved_keyword(GC::Ref<DOM::Node>, CSS::PropertyID);
|
||||||
|
Optional<NonnullRefPtr<CSS::CSSStyleValue const>> resolved_value(GC::Ref<DOM::Node>, CSS::PropertyID);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue