mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-20 11:36:10 +00:00
LibWeb: Implement "preserves overrides" property of editing commands
This commit is contained in:
parent
e21ee10b3c
commit
2b6a14c5ee
Notes:
github-actions[bot]
2025-01-10 22:38:22 +00:00
Author: https://github.com/gmta Commit: https://github.com/LadybirdBrowser/ladybird/commit/2b6a14c5eec Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3216
9 changed files with 315 additions and 80 deletions
|
@ -1774,6 +1774,41 @@ RefPtr<Gfx::FontCascadeList const> StyleComputer::font_matching_algorithm(FlyStr
|
|||
return {};
|
||||
}
|
||||
|
||||
CSSPixels StyleComputer::default_user_font_size()
|
||||
{
|
||||
// FIXME: This value should be configurable by the user.
|
||||
return 16;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/csswg-drafts/css-fonts/#absolute-size-mapping
|
||||
CSSPixelFraction StyleComputer::absolute_size_mapping(Keyword keyword)
|
||||
{
|
||||
switch (keyword) {
|
||||
case Keyword::XxSmall:
|
||||
return CSSPixels(3) / 5;
|
||||
case Keyword::XSmall:
|
||||
return CSSPixels(3) / 4;
|
||||
case Keyword::Small:
|
||||
return CSSPixels(8) / 9;
|
||||
case Keyword::Medium:
|
||||
return 1;
|
||||
case Keyword::Large:
|
||||
return CSSPixels(6) / 5;
|
||||
case Keyword::XLarge:
|
||||
return CSSPixels(3) / 2;
|
||||
case Keyword::XxLarge:
|
||||
return 2;
|
||||
case Keyword::XxxLarge:
|
||||
return 3;
|
||||
case Keyword::Smaller:
|
||||
return CSSPixels(4) / 5;
|
||||
case Keyword::Larger:
|
||||
return CSSPixels(5) / 4;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<Gfx::FontCascadeList const> StyleComputer::compute_font_for_style_values(DOM::Element const* element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, CSSStyleValue const& font_family, CSSStyleValue const& font_size, CSSStyleValue const& font_style, CSSStyleValue const& font_weight, CSSStyleValue const& font_stretch, int math_depth) const
|
||||
{
|
||||
auto* parent_element = element_to_inherit_style_from(element, pseudo_element);
|
||||
|
@ -1781,8 +1816,7 @@ RefPtr<Gfx::FontCascadeList const> StyleComputer::compute_font_for_style_values(
|
|||
auto width = font_stretch.to_font_width();
|
||||
auto weight = font_weight.to_font_weight();
|
||||
|
||||
// FIXME: Should be based on "user's default font size"
|
||||
CSSPixels font_size_in_px = 16;
|
||||
auto font_size_in_px = default_user_font_size();
|
||||
|
||||
Gfx::FontPixelMetrics font_pixel_metrics;
|
||||
if (parent_element && parent_element->computed_properties())
|
||||
|
@ -1805,34 +1839,6 @@ RefPtr<Gfx::FontCascadeList const> StyleComputer::compute_font_for_style_values(
|
|||
Length::FontMetrics font_metrics { parent_font_size(), font_pixel_metrics };
|
||||
|
||||
if (font_size.is_keyword()) {
|
||||
// https://w3c.github.io/csswg-drafts/css-fonts/#absolute-size-mapping
|
||||
auto get_absolute_size_mapping = [](Keyword keyword) -> CSSPixelFraction {
|
||||
switch (keyword) {
|
||||
case Keyword::XxSmall:
|
||||
return CSSPixels(3) / 5;
|
||||
case Keyword::XSmall:
|
||||
return CSSPixels(3) / 4;
|
||||
case Keyword::Small:
|
||||
return CSSPixels(8) / 9;
|
||||
case Keyword::Medium:
|
||||
return 1;
|
||||
case Keyword::Large:
|
||||
return CSSPixels(6) / 5;
|
||||
case Keyword::XLarge:
|
||||
return CSSPixels(3) / 2;
|
||||
case Keyword::XxLarge:
|
||||
return 2;
|
||||
case Keyword::XxxLarge:
|
||||
return 3;
|
||||
case Keyword::Smaller:
|
||||
return CSSPixels(4) / 5;
|
||||
case Keyword::Larger:
|
||||
return CSSPixels(5) / 4;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
auto const keyword = font_size.to_keyword();
|
||||
|
||||
if (keyword == Keyword::Math) {
|
||||
|
@ -1885,7 +1891,7 @@ RefPtr<Gfx::FontCascadeList const> StyleComputer::compute_font_for_style_values(
|
|||
font_size_in_px = CSSPixels::nearest_value_for(parent_element->computed_properties()->first_available_computed_font().pixel_metrics().size);
|
||||
}
|
||||
}
|
||||
font_size_in_px *= get_absolute_size_mapping(keyword);
|
||||
font_size_in_px *= absolute_size_mapping(keyword);
|
||||
}
|
||||
} else {
|
||||
Length::ResolutionContext const length_resolution_context {
|
||||
|
|
|
@ -160,6 +160,8 @@ public:
|
|||
void load_fonts_from_sheet(CSSStyleSheet&);
|
||||
void unload_fonts_from_sheet(CSSStyleSheet&);
|
||||
|
||||
static CSSPixels default_user_font_size();
|
||||
static CSSPixelFraction absolute_size_mapping(Keyword);
|
||||
RefPtr<Gfx::FontCascadeList const> compute_font_for_style_values(DOM::Element const* element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, CSSStyleValue const& font_family, CSSStyleValue const& font_size, CSSStyleValue const& font_style, CSSStyleValue const& font_weight, CSSStyleValue const& font_stretch, int math_depth = 0) const;
|
||||
|
||||
void set_viewport_rect(Badge<DOM::Document>, CSSPixelRect const& viewport_rect) { m_viewport_rect = viewport_rect; }
|
||||
|
|
|
@ -791,6 +791,7 @@ static Array const commands {
|
|||
CommandDefinition {
|
||||
.command = CommandNames::delete_,
|
||||
.action = command_delete_action,
|
||||
.preserves_overrides = true,
|
||||
},
|
||||
// https://w3c.github.io/editing/docs/execCommand/#the-defaultparagraphseparator-command
|
||||
CommandDefinition {
|
||||
|
@ -802,11 +803,13 @@ static Array const commands {
|
|||
CommandDefinition {
|
||||
.command = CommandNames::insertLineBreak,
|
||||
.action = command_insert_linebreak_action,
|
||||
.preserves_overrides = true,
|
||||
},
|
||||
// https://w3c.github.io/editing/docs/execCommand/#the-insertparagraph-command
|
||||
CommandDefinition {
|
||||
.command = CommandNames::insertParagraph,
|
||||
.action = command_insert_paragraph_action,
|
||||
.preserves_overrides = true,
|
||||
},
|
||||
// https://w3c.github.io/editing/docs/execCommand/#the-stylewithcss-command
|
||||
CommandDefinition {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Jelle Raaijmakers <jelle@ladybird.org>
|
||||
* Copyright (c) 2024-2025, Jelle Raaijmakers <jelle@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -19,6 +19,9 @@ struct CommandDefinition {
|
|||
Function<String(DOM::Document const&)> value {};
|
||||
Optional<CSS::PropertyID> relevant_css_property {};
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#preserves-overrides
|
||||
bool preserves_overrides { false };
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#inline-command-activated-values
|
||||
Vector<String> inline_activated_values {};
|
||||
};
|
||||
|
|
|
@ -69,12 +69,22 @@ bool Document::exec_command(FlyString const& command, [[maybe_unused]] bool show
|
|||
// at the editing host that was actually affected.
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#preserves-overrides
|
||||
// If a command preserves overrides, then before taking its action, the user agent must record current overrides.
|
||||
auto overrides = Editing::record_current_overrides(*this);
|
||||
|
||||
// 5. Take the action for command, passing value to the instructions as an argument.
|
||||
auto optional_command = Editing::find_command_definition(command);
|
||||
VERIFY(optional_command.has_value());
|
||||
auto const& command_definition = optional_command.release_value();
|
||||
auto command_result = command_definition.action(*this, value);
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#preserves-overrides
|
||||
// After taking the action, if the active range is collapsed, it must restore states and values from the recorded
|
||||
// list.
|
||||
if (m_selection && m_selection->is_collapsed())
|
||||
Editing::restore_states_and_values(*this, overrides);
|
||||
|
||||
// 6. If the previous step returned false, return false.
|
||||
if (!command_result)
|
||||
return false;
|
||||
|
@ -303,8 +313,12 @@ String Document::query_command_value(FlyString const& command)
|
|||
if (!command_definition.value && !value_override.has_value())
|
||||
return {};
|
||||
|
||||
// FIXME: 2. If command is "fontSize" and its value override is set, convert the value override to an
|
||||
// 2. If command is "fontSize" and its value override is set, convert the value override to an
|
||||
// integer number of pixels and return the legacy font size for the result.
|
||||
if (command == Editing::CommandNames::fontSize && value_override.has_value()) {
|
||||
auto pixel_size = Editing::font_size_to_pixel_size(value_override.release_value());
|
||||
return Editing::legacy_font_size(pixel_size.to_int());
|
||||
}
|
||||
|
||||
// 3. If the value override for command is set, return it.
|
||||
if (value_override.has_value())
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibWeb/CSS/ResolvedCSSStyleDeclaration.h>
|
||||
#include <LibWeb/CSS/StyleComputer.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
|
||||
|
@ -21,6 +23,7 @@
|
|||
#include <LibWeb/HTML/HTMLAnchorElement.h>
|
||||
#include <LibWeb/HTML/HTMLBRElement.h>
|
||||
#include <LibWeb/HTML/HTMLElement.h>
|
||||
#include <LibWeb/HTML/HTMLFontElement.h>
|
||||
#include <LibWeb/HTML/HTMLImageElement.h>
|
||||
#include <LibWeb/HTML/HTMLLIElement.h>
|
||||
#include <LibWeb/HTML/HTMLOListElement.h>
|
||||
|
@ -561,7 +564,7 @@ void delete_the_selection(Selection& selection, bool block_merging, bool strip_w
|
|||
end_block = {};
|
||||
|
||||
// 19. Record current states and values, and let overrides be the result.
|
||||
auto overrides = record_current_states_and_values(*active_range(document));
|
||||
auto overrides = record_current_states_and_values(document);
|
||||
|
||||
// 21. If start node and end node are the same, and start node is an editable Text node:
|
||||
if (start.node == end.node && is<DOM::Text>(*start.node) && start.node->is_editable()) {
|
||||
|
@ -582,7 +585,7 @@ void delete_the_selection(Selection& selection, bool block_merging, bool strip_w
|
|||
}
|
||||
|
||||
// 5. Restore states and values from overrides.
|
||||
restore_states_and_values(*selection.range(), overrides);
|
||||
restore_states_and_values(document, overrides);
|
||||
|
||||
// 6. Abort these steps.
|
||||
return;
|
||||
|
@ -662,7 +665,7 @@ void delete_the_selection(Selection& selection, bool block_merging, bool strip_w
|
|||
}
|
||||
|
||||
// 3. Restore states and values from overrides.
|
||||
restore_states_and_values(*selection.range(), overrides);
|
||||
restore_states_and_values(document, overrides);
|
||||
|
||||
// 4. Abort these steps.
|
||||
return;
|
||||
|
@ -712,7 +715,7 @@ void delete_the_selection(Selection& selection, bool block_merging, bool strip_w
|
|||
end_block->remove();
|
||||
|
||||
// 4. Restore states and values from overrides.
|
||||
restore_states_and_values(*active_range(document), overrides);
|
||||
restore_states_and_values(document, overrides);
|
||||
|
||||
// 5. Abort these steps.
|
||||
return;
|
||||
|
@ -721,7 +724,7 @@ void delete_the_selection(Selection& selection, bool block_merging, bool strip_w
|
|||
// 5. If end block's firstChild is not an inline node, restore states and values from record, then abort these
|
||||
// steps.
|
||||
if (!is_inline_node(*end_block->first_child())) {
|
||||
restore_states_and_values(*active_range(document), overrides);
|
||||
restore_states_and_values(document, overrides);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -883,7 +886,7 @@ void delete_the_selection(Selection& selection, bool block_merging, bool strip_w
|
|||
remove_extraneous_line_breaks_at_the_end_of_node(*start_block);
|
||||
|
||||
// 41. Restore states and values from overrides.
|
||||
restore_states_and_values(*active_range(document), overrides);
|
||||
restore_states_and_values(document, overrides);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#editing-host-of
|
||||
|
@ -1917,6 +1920,37 @@ DOM::BoundaryPoint last_equivalent_point(DOM::BoundaryPoint boundary_point)
|
|||
return boundary_point;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#legacy-font-size-for
|
||||
String legacy_font_size(int pixel_size)
|
||||
{
|
||||
// 1. Let returned size be 1.
|
||||
auto returned_size = 1;
|
||||
|
||||
// 2. While returned size is less than 7:
|
||||
while (returned_size < 7) {
|
||||
// 1. Let lower bound be the resolved value of "font-size" in pixels of a font element whose size attribute is
|
||||
// set to returned size.
|
||||
auto lower_bound = font_size_to_pixel_size(MUST(String::formatted("{}", returned_size))).to_float();
|
||||
|
||||
// 2. Let upper bound be the resolved value of "font-size" in pixels of a font element whose size attribute is
|
||||
// set to one plus returned size.
|
||||
auto upper_bound = font_size_to_pixel_size(MUST(String::formatted("{}", returned_size + 1))).to_float();
|
||||
|
||||
// 3. Let average be the average of upper bound and lower bound.
|
||||
auto average = (lower_bound + upper_bound) / 2;
|
||||
|
||||
// 4. If pixel size is less than average, return the one-code unit string consisting of the digit returned size.
|
||||
if (pixel_size < average)
|
||||
return MUST(String::formatted("{}", returned_size));
|
||||
|
||||
// 5. Add one to returned size.
|
||||
++returned_size;
|
||||
}
|
||||
|
||||
// 3. Return "7".
|
||||
return "7"_string;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#preserving-ranges
|
||||
void move_node_preserving_ranges(GC::Ref<DOM::Node> node, GC::Ref<DOM::Node> new_parent, u32 new_index)
|
||||
{
|
||||
|
@ -2075,22 +2109,45 @@ Optional<DOM::BoundaryPoint> previous_equivalent_point(DOM::BoundaryPoint bounda
|
|||
return {};
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#record-current-overrides
|
||||
Vector<RecordedOverride> record_current_overrides(DOM::Document const& document)
|
||||
{
|
||||
// 1. Let overrides be a list of (string, string or boolean) ordered pairs, initially empty.
|
||||
Vector<RecordedOverride> overrides;
|
||||
|
||||
// 2. If there is a value override for "createLink", add ("createLink", value override for "createLink") to
|
||||
// overrides.
|
||||
if (auto override = document.command_value_override(CommandNames::createLink); override.has_value())
|
||||
overrides.empend(CommandNames::createLink, override.release_value());
|
||||
|
||||
// 3. For each command in the list "bold", "italic", "strikethrough", "subscript", "superscript", "underline", in
|
||||
// order: if there is a state override for command, add (command, command's state override) to overrides.
|
||||
for (auto const& command : { CommandNames::bold, CommandNames::italic, CommandNames::strikethrough,
|
||||
CommandNames::subscript, CommandNames::superscript, CommandNames::underline }) {
|
||||
if (auto override = document.command_state_override(command); override.has_value())
|
||||
overrides.empend(command, override.release_value());
|
||||
}
|
||||
|
||||
// 4. For each command in the list "fontName", "fontSize", "foreColor", "hiliteColor", in order: if there is a value
|
||||
// override for command, add (command, command's value override) to overrides.
|
||||
for (auto const& command : { CommandNames::fontName, CommandNames::fontSize, CommandNames::foreColor,
|
||||
CommandNames::hiliteColor }) {
|
||||
if (auto override = document.command_value_override(command); override.has_value())
|
||||
overrides.empend(command, override.release_value());
|
||||
}
|
||||
|
||||
// 5. Return overrides.
|
||||
return overrides;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#record-current-states-and-values
|
||||
Vector<RecordedOverride> record_current_states_and_values(GC::Ref<DOM::Range> active_range)
|
||||
Vector<RecordedOverride> record_current_states_and_values(DOM::Document const& document)
|
||||
{
|
||||
// 1. Let overrides be a list of (string, string or boolean) ordered pairs, initially empty.
|
||||
Vector<RecordedOverride> overrides;
|
||||
|
||||
// 2. Let node be the first formattable node effectively contained in the active range, or null if there is none.
|
||||
GC::Ptr<DOM::Node> node;
|
||||
auto common_ancestor = active_range->common_ancestor_container();
|
||||
common_ancestor->for_each_in_subtree([&](GC::Ref<DOM::Node> descendant) {
|
||||
if (is_formattable_node(descendant) && is_effectively_contained_in_range(descendant, active_range)) {
|
||||
node = descendant;
|
||||
return TraversalDecision::Break;
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
auto node = first_formattable_node_effectively_contained(active_range(document));
|
||||
|
||||
// 3. If node is null, return overrides.
|
||||
if (!node)
|
||||
|
@ -2253,44 +2310,88 @@ void remove_node_preserving_its_descendants(GC::Ref<DOM::Node> node)
|
|||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#restore-states-and-values
|
||||
void restore_states_and_values(GC::Ref<DOM::Range>, Vector<RecordedOverride> const& overrides)
|
||||
void restore_states_and_values(DOM::Document& document, Vector<RecordedOverride> const& overrides)
|
||||
{
|
||||
// FIXME: 1. Let node be the first formattable node effectively contained in the active range, or null if there is none.
|
||||
// 1. Let node be the first formattable node effectively contained in the active range, or null if there is none.
|
||||
auto node = first_formattable_node_effectively_contained(active_range(document));
|
||||
|
||||
// FIXME: 2. If node is not null, then for each (command, override) pair in overrides, in order:
|
||||
{
|
||||
// FIXME: 1. If override is a boolean, and queryCommandState(command) returns something different from override, take
|
||||
// the action for command, with value equal to the empty string.
|
||||
// 2. If node is not null,
|
||||
if (node) {
|
||||
auto take_the_action_for_command = [&document](FlyString const& command, String const& value) {
|
||||
auto const& command_definition = find_command_definition(command);
|
||||
// FIXME: replace with VERIFY(command_definition.has_value()) as soon as all command definitions are in place.
|
||||
if (command_definition.has_value())
|
||||
command_definition->action(document, value);
|
||||
};
|
||||
|
||||
// FIXME: 2. Otherwise, if override is a string, and command is neither "createLink" nor "fontSize", and
|
||||
// queryCommandValue(command) returns something not equivalent to override, take the action for command, with
|
||||
// value equal to override.
|
||||
// then for each (command, override) pair in overrides, in order:
|
||||
for (auto override : overrides) {
|
||||
// 1. If override is a boolean, and queryCommandState(command) returns something different from override,
|
||||
// take the action for command, with value equal to the empty string.
|
||||
if (override.value.has<bool>() && document.query_command_state(override.command) != override.value.get<bool>()) {
|
||||
take_the_action_for_command(override.command, {});
|
||||
}
|
||||
|
||||
// FIXME: 3. Otherwise, if override is a string; and command is "createLink"; and either there is a value override for
|
||||
// "createLink" that is not equal to override, or there is no value override for "createLink" and node's
|
||||
// effective command value for "createLink" is not equal to override: take the action for "createLink", with
|
||||
// value equal to override.
|
||||
// 2. Otherwise, if override is a string, and command is neither "createLink" nor "fontSize", and
|
||||
// queryCommandValue(command) returns something not equivalent to override, take the action for command,
|
||||
// with value equal to override.
|
||||
else if (override.value.has<String>() && !override.command.is_one_of(CommandNames::createLink, CommandNames::fontSize)
|
||||
&& document.query_command_value(override.command) != override.value.get<String>()) {
|
||||
take_the_action_for_command(override.command, override.value.get<String>());
|
||||
}
|
||||
|
||||
// FIXME: 4. Otherwise, if override is a string; and command is "fontSize"; and either there is a value override for
|
||||
// "fontSize" that is not equal to override, or there is no value override for "fontSize" and node's
|
||||
// effective command value for "fontSize" is not loosely equivalent to override:
|
||||
{
|
||||
// FIXME: 1. Convert override to an integer number of pixels, and set override to the legacy font size for the
|
||||
// result.
|
||||
// 3. Otherwise, if override is a string; and command is "createLink"; and either there is a value override
|
||||
// for "createLink" that is not equal to override, or there is no value override for "createLink" and
|
||||
// node's effective command value for "createLink" is not equal to override: take the action for
|
||||
// "createLink", with value equal to override.
|
||||
else if (auto value_override = document.command_value_override(CommandNames::createLink);
|
||||
override.value.has<String>() && override.command == CommandNames::createLink
|
||||
&& ((value_override.has_value() && value_override.value() != override.value.get<String>())
|
||||
|| (!value_override.has_value()
|
||||
&& effective_command_value(node, CommandNames::createLink) != override.value.get<String>()))) {
|
||||
take_the_action_for_command(CommandNames::createLink, override.value.get<String>());
|
||||
}
|
||||
|
||||
// FIXME: 2. Take the action for "fontSize", with value equal to override.
|
||||
// 4. Otherwise, if override is a string; and command is "fontSize"; and either there is a value override
|
||||
// for "fontSize" that is not equal to override, or there is no value override for "fontSize" and node's
|
||||
// effective command value for "fontSize" is not loosely equivalent to override:
|
||||
else if (auto value_override = document.command_value_override(CommandNames::fontSize);
|
||||
override.value.has<String>() && override.command == CommandNames::fontSize
|
||||
&& ((value_override.has_value() && value_override.value() != override.value.get<String>())
|
||||
|| (!value_override.has_value()
|
||||
&& !values_are_loosely_equivalent(
|
||||
CommandNames::fontSize,
|
||||
effective_command_value(node, CommandNames::fontSize),
|
||||
override.value.get<String>())))) {
|
||||
// 1. Convert override to an integer number of pixels, and set override to the legacy font size for the
|
||||
// result.
|
||||
auto override_pixel_size = font_size_to_pixel_size(override.value.get<String>());
|
||||
override.value = legacy_font_size(override_pixel_size.to_int());
|
||||
|
||||
// 2. Take the action for "fontSize", with value equal to override.
|
||||
take_the_action_for_command(CommandNames::fontSize, override.value.get<String>());
|
||||
}
|
||||
|
||||
// 5. Otherwise, continue this loop from the beginning.
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 6. Set node to the first formattable node effectively contained in the active range, if there is one.
|
||||
if (auto new_formattable_node = first_formattable_node_effectively_contained(active_range(document)))
|
||||
node = new_formattable_node;
|
||||
}
|
||||
|
||||
// FIXME: 5. Otherwise, continue this loop from the beginning.
|
||||
|
||||
// FIXME: 6. Set node to the first formattable node effectively contained in the active range, if there is one.
|
||||
}
|
||||
|
||||
// 3. Otherwise, for each (command, override) pair in overrides, in order:
|
||||
for ([[maybe_unused]] auto const& override : overrides) {
|
||||
// FIXME: 1. If override is a boolean, set the state override for command to override.
|
||||
|
||||
// FIXME: 2. If override is a string, set the value override for command to override.
|
||||
else {
|
||||
for (auto const& override : overrides) {
|
||||
// 1. If override is a boolean, set the state override for command to override.
|
||||
// 2. If override is a string, set the value override for command to override.
|
||||
override.value.visit(
|
||||
[&](bool value) { document.set_command_state_override(override.command, value); },
|
||||
[&](String const& value) { document.set_command_value_override(override.command, value); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2564,6 +2665,72 @@ void split_the_parent_of_nodes(Vector<GC::Ref<DOM::Node>> const& node_list)
|
|||
remove_extraneous_line_breaks_at_the_end_of_node(*last_node->parent());
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#equivalent-values
|
||||
bool values_are_equivalent(FlyString const& command, Optional<String> a, Optional<String> b)
|
||||
{
|
||||
// Two quantities are equivalent values for a command if either both are null,
|
||||
if (!a.has_value() && !b.has_value())
|
||||
return true;
|
||||
|
||||
// NOTE: Both need to be strings for all remaining conditions.
|
||||
if (!a.has_value() || !b.has_value())
|
||||
return false;
|
||||
|
||||
// or both are strings and the command defines equivalent values and they match the definition.
|
||||
if (command.is_one_of(CommandNames::backColor, CommandNames::foreColor, CommandNames::hiliteColor)) {
|
||||
// Either both strings are valid CSS colors and have the same red, green, blue, and alpha components, or neither
|
||||
// string is a valid CSS color.
|
||||
auto a_color = Color::from_string(a.value());
|
||||
auto b_color = Color::from_string(b.value());
|
||||
if (a_color.has_value())
|
||||
return a_color == b_color;
|
||||
return !a_color.has_value() && !b_color.has_value();
|
||||
}
|
||||
if (command == CommandNames::bold) {
|
||||
// Either the two strings are equal, or one is "bold" and the other is "700", or one is "normal" and the other
|
||||
// is "400".
|
||||
if (a.value() == b.value())
|
||||
return true;
|
||||
|
||||
auto either_is_bold = first_is_one_of("bold"sv, a.value(), b.value());
|
||||
auto either_is_700 = first_is_one_of("700"sv, a.value(), b.value());
|
||||
auto either_is_normal = first_is_one_of("normal"sv, a.value(), b.value());
|
||||
auto either_is_400 = first_is_one_of("400"sv, a.value(), b.value());
|
||||
|
||||
return (either_is_bold && either_is_700) || (either_is_normal && either_is_400);
|
||||
}
|
||||
|
||||
// or both are strings and they're equal and the command does not define any equivalent values,
|
||||
return a.value() == b.value();
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#loosely-equivalent-values
|
||||
bool values_are_loosely_equivalent(FlyString const& command, Optional<String> a, Optional<String> b)
|
||||
{
|
||||
// Two quantities are loosely equivalent values for a command if either they are equivalent values for the command,
|
||||
if (values_are_equivalent(command, a, b))
|
||||
return true;
|
||||
|
||||
// or if the command is the fontSize command; one of the quantities is one of "x-small", "small", "medium", "large",
|
||||
// "x-large", "xx-large", or "xxx-large"; and the other quantity is the resolved value of "font-size" on a font
|
||||
// element whose size attribute has the corresponding value set ("1" through "7" respectively).
|
||||
if (command == CommandNames::fontSize && a.has_value() && b.has_value()) {
|
||||
static constexpr Array named_quantities { "x-small"sv, "small"sv, "medium"sv, "large"sv, "x-large"sv,
|
||||
"xx-large"sv, "xxx-large"sv };
|
||||
static constexpr Array size_quantities { "1"sv, "2"sv, "3"sv, "4"sv, "5"sv, "6"sv, "7"sv };
|
||||
static_assert(named_quantities.size() == size_quantities.size());
|
||||
|
||||
auto a_index = named_quantities.first_index_of(a.value())
|
||||
.value_or_lazy_evaluated_optional([&] { return size_quantities.first_index_of(a.value()); });
|
||||
auto b_index = named_quantities.first_index_of(b.value())
|
||||
.value_or_lazy_evaluated_optional([&] { return size_quantities.first_index_of(b.value()); });
|
||||
|
||||
return a_index.has_value() && a_index == b_index;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#wrap
|
||||
GC::Ptr<DOM::Node> wrap(
|
||||
Vector<GC::Ref<DOM::Node>> node_list,
|
||||
|
@ -2745,6 +2912,38 @@ GC::Ptr<DOM::Node> wrap(
|
|||
return new_parent;
|
||||
}
|
||||
|
||||
GC::Ptr<DOM::Node> first_formattable_node_effectively_contained(GC::Ptr<DOM::Range> range)
|
||||
{
|
||||
GC::Ptr<DOM::Node> node;
|
||||
for_each_node_effectively_contained_in_range(range, [&](GC::Ref<DOM::Node> descendant) {
|
||||
if (is_formattable_node(descendant)) {
|
||||
node = descendant;
|
||||
return TraversalDecision::Break;
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
CSSPixels font_size_to_pixel_size(StringView font_size)
|
||||
{
|
||||
auto pixel_size = CSS::StyleComputer::default_user_font_size();
|
||||
|
||||
// Try to map the font size directly to a keyword (e.g. medium or x-large)
|
||||
auto keyword = CSS::keyword_from_string(font_size);
|
||||
|
||||
// If that failed, try to interpret it as a legacy font size (e.g. 1 through 7)
|
||||
if (!keyword.has_value())
|
||||
keyword = HTML::HTMLFontElement::parse_legacy_font_size(font_size);
|
||||
|
||||
// If that also failed, give up
|
||||
if (!keyword.has_value())
|
||||
return pixel_size;
|
||||
|
||||
// Return scaled pixel size
|
||||
return pixel_size * CSS::StyleComputer::absolute_size_mapping(keyword.release_value());
|
||||
}
|
||||
|
||||
void for_each_node_effectively_contained_in_range(GC::Ptr<DOM::Range> range, Function<TraversalDecision(GC::Ref<DOM::Node>)> callback)
|
||||
{
|
||||
if (!range)
|
||||
|
|
|
@ -66,26 +66,32 @@ bool is_single_line_container(GC::Ref<DOM::Node>);
|
|||
bool is_visible_node(GC::Ref<DOM::Node>);
|
||||
bool is_whitespace_node(GC::Ref<DOM::Node>);
|
||||
DOM::BoundaryPoint last_equivalent_point(DOM::BoundaryPoint);
|
||||
String legacy_font_size(int);
|
||||
void move_node_preserving_ranges(GC::Ref<DOM::Node>, GC::Ref<DOM::Node> new_parent, u32 new_index);
|
||||
Optional<DOM::BoundaryPoint> next_equivalent_point(DOM::BoundaryPoint);
|
||||
void normalize_sublists_in_node(GC::Ref<DOM::Node>);
|
||||
bool precedes_a_line_break(GC::Ref<DOM::Node>);
|
||||
Optional<DOM::BoundaryPoint> previous_equivalent_point(DOM::BoundaryPoint);
|
||||
Vector<RecordedOverride> record_current_states_and_values(GC::Ref<DOM::Range>);
|
||||
Vector<RecordedOverride> record_current_overrides(DOM::Document const&);
|
||||
Vector<RecordedOverride> record_current_states_and_values(DOM::Document const&);
|
||||
Vector<RecordedNodeValue> record_the_values_of_nodes(Vector<GC::Ref<DOM::Node>> const&);
|
||||
void remove_extraneous_line_breaks_at_the_end_of_node(GC::Ref<DOM::Node>);
|
||||
void remove_extraneous_line_breaks_before_node(GC::Ref<DOM::Node>);
|
||||
void remove_extraneous_line_breaks_from_a_node(GC::Ref<DOM::Node>);
|
||||
void remove_node_preserving_its_descendants(GC::Ref<DOM::Node>);
|
||||
void restore_states_and_values(GC::Ref<DOM::Range>, Vector<RecordedOverride> const&);
|
||||
void restore_states_and_values(DOM::Document&, Vector<RecordedOverride> const&);
|
||||
void restore_the_values_of_nodes(Vector<RecordedNodeValue> const&);
|
||||
GC::Ref<DOM::Element> set_the_tag_name(GC::Ref<DOM::Element>, FlyString const&);
|
||||
Optional<String> specified_command_value(GC::Ref<DOM::Element>, FlyString const& command);
|
||||
void split_the_parent_of_nodes(Vector<GC::Ref<DOM::Node>> const&);
|
||||
bool values_are_equivalent(FlyString const&, Optional<String>, Optional<String>);
|
||||
bool values_are_loosely_equivalent(FlyString const&, Optional<String>, Optional<String>);
|
||||
GC::Ptr<DOM::Node> wrap(Vector<GC::Ref<DOM::Node>>, Function<bool(GC::Ref<DOM::Node>)> sibling_criteria, Function<GC::Ptr<DOM::Node>()> new_parent_instructions);
|
||||
|
||||
// Utility methods:
|
||||
|
||||
GC::Ptr<DOM::Node> first_formattable_node_effectively_contained(GC::Ptr<DOM::Range>);
|
||||
CSSPixels font_size_to_pixel_size(StringView);
|
||||
void for_each_node_effectively_contained_in_range(GC::Ptr<DOM::Range>, Function<TraversalDecision(GC::Ref<DOM::Node>)>);
|
||||
bool has_visible_children(GC::Ref<DOM::Node>);
|
||||
bool is_heading(FlyString const&);
|
||||
|
|
|
@ -26,7 +26,7 @@ enum class Mode {
|
|||
};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/rendering.html#rules-for-parsing-a-legacy-font-size
|
||||
static Optional<CSS::Keyword> parse_legacy_font_size(StringView string)
|
||||
Optional<CSS::Keyword> HTMLFontElement::parse_legacy_font_size(StringView string)
|
||||
{
|
||||
// 1. Let input be the attribute's value.
|
||||
// 2. Let position be a pointer into input, initially pointing at the start of the string.
|
||||
|
|
|
@ -20,6 +20,8 @@ public:
|
|||
virtual bool is_presentational_hint(FlyString const&) const override;
|
||||
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
|
||||
|
||||
static Optional<CSS::Keyword> parse_legacy_font_size(StringView);
|
||||
|
||||
private:
|
||||
HTMLFontElement(DOM::Document&, DOM::QualifiedName);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue