LibWeb: Map logical aliases at cascade time

Previously we would incorrectly map these in
`CSSStyleProperties::convert_declarations_to_specified_order`, aside
from being too early (as it meant we didn't maintain them as distinct
from their physical counterparts in CSSStyleProperties), this meant
that we didn't yet have the required context to map them correctly.

We now map them as part of the cascade process. To compute the mapping
context we do a cascade without mapping, and extract the relevant
properties (writing-direction and direction).
This commit is contained in:
Callum Law 2025-06-18 17:45:26 +12:00 committed by Sam Atkins
parent 4e87f85458
commit cfc8d3031b
Notes: github-actions[bot] 2025-06-23 14:20:40 +00:00
14 changed files with 848 additions and 141 deletions

View file

@ -27,7 +27,7 @@ Each property will have some set of these fields on it:
| `inherited` | Yes | | Boolean. Whether the property is inherited by its child elements. | `bool is_inherited_property(PropertyID)` | | `inherited` | Yes | | Boolean. Whether the property is inherited by its child elements. | `bool is_inherited_property(PropertyID)` |
| `initial` | Yes | | String. The property's initial value if it is not specified. | `NonnullRefPtr<CSSStyleValue const> property_initial_value(PropertyID)` | | `initial` | Yes | | String. The property's initial value if it is not specified. | `NonnullRefPtr<CSSStyleValue const> property_initial_value(PropertyID)` |
| `legacy-alias-for` | No | Nothing | String. The name of a property this is an alias for. See below. | | | `legacy-alias-for` | No | Nothing | String. The name of a property this is an alias for. See below. | |
| `logical-alias-for` | No | Nothing | Array of strings. The name of a property this is an alias for. See below. | | | `logical-alias-for` | No | Nothing | Array of strings. The name of a property this is an alias for. See below. | `bool property_is_logical_alias(PropertyID);` |
| `longhands` | No | `[]` | Array of strings. If this is a shorthand, these are the property names that it expands out into. | `Vector<PropertyID> longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> expanded_longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> shorthands_for_longhand(PropertyID)`| | `longhands` | No | `[]` | Array of strings. If this is a shorthand, these are the property names that it expands out into. | `Vector<PropertyID> longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> expanded_longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> shorthands_for_longhand(PropertyID)`|
| `max-values` | No | `1` | Integer. How many values can be parsed for this property. eg, `margin` can have up to 4 values. | `size_t property_maximum_value_count(PropertyID)` | | `max-values` | No | `1` | Integer. How many values can be parsed for this property. eg, `margin` can have up to 4 values. | `size_t property_maximum_value_count(PropertyID)` |
| `percentages-resolve-to` | No | Nothing | String. What type percentages get resolved to. eg, for `width` percentages are resolved to `length` values. | `Optional<ValueType> property_resolves_percentages_relative_to(PropertyID)` | | `percentages-resolve-to` | No | Nothing | String. What type percentages get resolved to. eg, for `width` percentages are resolved to `length` values. | `Optional<ValueType> property_resolves_percentages_relative_to(PropertyID)` |

View file

@ -627,71 +627,6 @@ void StyleComputer::for_each_property_expanding_shorthands(PropertyID property_i
return; return;
} }
auto map_logical_property_to_real_property = [](PropertyID property_id) -> Optional<PropertyID> {
// FIXME: Honor writing-mode, direction and text-orientation.
switch (property_id) {
case PropertyID::BlockSize:
return PropertyID::Height;
case PropertyID::BorderBlockEndColor:
return PropertyID::BorderBottomColor;
case PropertyID::BorderBlockEndStyle:
return PropertyID::BorderBottomStyle;
case PropertyID::BorderBlockEndWidth:
return PropertyID::BorderBottomWidth;
case PropertyID::BorderBlockStartColor:
return PropertyID::BorderTopColor;
case PropertyID::BorderBlockStartStyle:
return PropertyID::BorderTopStyle;
case PropertyID::BorderBlockStartWidth:
return PropertyID::BorderTopWidth;
case PropertyID::BorderInlineStartColor:
return PropertyID::BorderLeftColor;
case PropertyID::BorderInlineStartStyle:
return PropertyID::BorderLeftStyle;
case PropertyID::BorderInlineStartWidth:
return PropertyID::BorderLeftWidth;
case PropertyID::BorderInlineEndColor:
return PropertyID::BorderRightColor;
case PropertyID::BorderInlineEndStyle:
return PropertyID::BorderRightStyle;
case PropertyID::BorderInlineEndWidth:
return PropertyID::BorderRightWidth;
case PropertyID::MarginBlockStart:
return PropertyID::MarginTop;
case PropertyID::MarginBlockEnd:
return PropertyID::MarginBottom;
case PropertyID::MarginInlineStart:
return PropertyID::MarginLeft;
case PropertyID::MarginInlineEnd:
return PropertyID::MarginRight;
case PropertyID::PaddingBlockStart:
return PropertyID::PaddingTop;
case PropertyID::PaddingBlockEnd:
return PropertyID::PaddingBottom;
case PropertyID::PaddingInlineStart:
return PropertyID::PaddingLeft;
case PropertyID::PaddingInlineEnd:
return PropertyID::PaddingRight;
case PropertyID::InlineSize:
return PropertyID::Width;
case PropertyID::InsetBlockStart:
return PropertyID::Top;
case PropertyID::InsetBlockEnd:
return PropertyID::Bottom;
case PropertyID::InsetInlineStart:
return PropertyID::Left;
case PropertyID::InsetInlineEnd:
return PropertyID::Right;
default:
return {};
}
};
if (auto real_property_id = map_logical_property_to_real_property(property_id); real_property_id.has_value()) {
for_each_property_expanding_shorthands(real_property_id.value(), value, set_longhand_property);
return;
}
if (value.is_shorthand()) { if (value.is_shorthand()) {
auto& shorthand_value = value.as_shorthand(); auto& shorthand_value = value.as_shorthand();
auto& properties = shorthand_value.sub_properties(); auto& properties = shorthand_value.sub_properties();
@ -902,46 +837,6 @@ void StyleComputer::for_each_property_expanding_shorthands(PropertyID property_i
return; return;
} }
if (property_id == CSS::PropertyID::MaxInlineSize || property_id == CSS::PropertyID::MinInlineSize) {
// FIXME: Use writing-mode to determine if we should set width or height.
bool is_horizontal = true;
if (is_horizontal) {
if (property_id == CSS::PropertyID::MaxInlineSize) {
set_longhand_property(CSS::PropertyID::MaxWidth, value);
} else {
set_longhand_property(CSS::PropertyID::MinWidth, value);
}
} else {
if (property_id == CSS::PropertyID::MaxInlineSize) {
set_longhand_property(CSS::PropertyID::MaxHeight, value);
} else {
set_longhand_property(CSS::PropertyID::MinHeight, value);
}
}
return;
}
if (property_id == CSS::PropertyID::MaxBlockSize || property_id == CSS::PropertyID::MinBlockSize) {
// FIXME: Use writing-mode to determine if we should set width or height.
bool is_horizontal = true;
if (is_horizontal) {
if (property_id == CSS::PropertyID::MaxBlockSize) {
set_longhand_property(CSS::PropertyID::MaxHeight, value);
} else {
set_longhand_property(CSS::PropertyID::MinHeight, value);
}
} else {
if (property_id == CSS::PropertyID::MaxBlockSize) {
set_longhand_property(CSS::PropertyID::MaxWidth, value);
} else {
set_longhand_property(CSS::PropertyID::MinWidth, value);
}
}
return;
}
if (property_id == CSS::PropertyID::Transition) { if (property_id == CSS::PropertyID::Transition) {
if (value.to_keyword() == Keyword::None) { if (value.to_keyword() == Keyword::None) {
// Handle `none` as a shorthand for `all 0s ease 0s`. // Handle `none` as a shorthand for `all 0s ease 0s`.
@ -993,6 +888,76 @@ void StyleComputer::for_each_property_expanding_shorthands(PropertyID property_i
set_longhand_property(property_id, value); set_longhand_property(property_id, value);
} }
PropertyID StyleComputer::map_logical_alias_to_physical_property_id(PropertyID property_id, LogicalAliasMappingContext)
{
// FIXME: Honor writing-mode, direction and text-orientation.
switch (property_id) {
case PropertyID::BlockSize:
return PropertyID::Height;
case PropertyID::BorderBlockEndColor:
return PropertyID::BorderBottomColor;
case PropertyID::BorderBlockEndStyle:
return PropertyID::BorderBottomStyle;
case PropertyID::BorderBlockEndWidth:
return PropertyID::BorderBottomWidth;
case PropertyID::BorderBlockStartColor:
return PropertyID::BorderTopColor;
case PropertyID::BorderBlockStartStyle:
return PropertyID::BorderTopStyle;
case PropertyID::BorderBlockStartWidth:
return PropertyID::BorderTopWidth;
case PropertyID::BorderInlineStartColor:
return PropertyID::BorderLeftColor;
case PropertyID::BorderInlineStartStyle:
return PropertyID::BorderLeftStyle;
case PropertyID::BorderInlineStartWidth:
return PropertyID::BorderLeftWidth;
case PropertyID::BorderInlineEndColor:
return PropertyID::BorderRightColor;
case PropertyID::BorderInlineEndStyle:
return PropertyID::BorderRightStyle;
case PropertyID::BorderInlineEndWidth:
return PropertyID::BorderRightWidth;
case PropertyID::MarginBlockStart:
return PropertyID::MarginTop;
case PropertyID::MarginBlockEnd:
return PropertyID::MarginBottom;
case PropertyID::MarginInlineStart:
return PropertyID::MarginLeft;
case PropertyID::MarginInlineEnd:
return PropertyID::MarginRight;
case PropertyID::MaxBlockSize:
return PropertyID::MaxHeight;
case PropertyID::MaxInlineSize:
return PropertyID::MaxWidth;
case PropertyID::MinBlockSize:
return PropertyID::MinHeight;
case PropertyID::MinInlineSize:
return PropertyID::MinWidth;
case PropertyID::PaddingBlockStart:
return PropertyID::PaddingTop;
case PropertyID::PaddingBlockEnd:
return PropertyID::PaddingBottom;
case PropertyID::PaddingInlineStart:
return PropertyID::PaddingLeft;
case PropertyID::PaddingInlineEnd:
return PropertyID::PaddingRight;
case PropertyID::InlineSize:
return PropertyID::Width;
case PropertyID::InsetBlockStart:
return PropertyID::Top;
case PropertyID::InsetBlockEnd:
return PropertyID::Bottom;
case PropertyID::InsetInlineStart:
return PropertyID::Left;
case PropertyID::InsetInlineEnd:
return PropertyID::Right;
default:
VERIFY(!property_is_logical_alias(property_id));
return property_id;
}
}
void StyleComputer::cascade_declarations( void StyleComputer::cascade_declarations(
CascadedProperties& cascaded_properties, CascadedProperties& cascaded_properties,
DOM::Element& element, DOM::Element& element,
@ -1000,7 +965,8 @@ void StyleComputer::cascade_declarations(
Vector<MatchingRule const*> const& matching_rules, Vector<MatchingRule const*> const& matching_rules,
CascadeOrigin cascade_origin, CascadeOrigin cascade_origin,
Important important, Important important,
Optional<FlyString> layer_name) const Optional<FlyString> layer_name,
Optional<LogicalAliasMappingContext> logical_alias_mapping_context) const
{ {
auto seen_properties = MUST(Bitmap::create(to_underlying(last_property_id) + 1, false)); auto seen_properties = MUST(Bitmap::create(to_underlying(last_property_id) + 1, false));
auto cascade_style_declaration = [&](CSSStyleProperties const& declaration) { auto cascade_style_declaration = [&](CSSStyleProperties const& declaration) {
@ -1044,12 +1010,22 @@ void StyleComputer::cascade_declarations(
return; return;
seen_properties.set(to_underlying(longhand_id), true); seen_properties.set(to_underlying(longhand_id), true);
if (longhand_value.is_revert()) { PropertyID physical_property_id;
cascaded_properties.revert_property(longhand_id, important, cascade_origin);
} else if (longhand_value.is_revert_layer()) { if (property_is_logical_alias(longhand_id)) {
cascaded_properties.revert_layer_property(longhand_id, important, layer_name); if (!logical_alias_mapping_context.has_value())
return;
physical_property_id = map_logical_alias_to_physical_property_id(longhand_id, logical_alias_mapping_context.value());
} else { } else {
cascaded_properties.set_property(longhand_id, longhand_value, important, cascade_origin, layer_name, declaration); physical_property_id = longhand_id;
}
if (longhand_value.is_revert()) {
cascaded_properties.revert_property(physical_property_id, important, cascade_origin);
} else if (longhand_value.is_revert_layer()) {
cascaded_properties.revert_layer_property(physical_property_id, important, layer_name);
} else {
cascaded_properties.set_property(physical_property_id, longhand_value, important, cascade_origin, layer_name, declaration);
} }
}); });
} }
@ -1152,6 +1128,14 @@ void StyleComputer::collect_animation_into(DOM::Element& element, Optional<CSS::
HashMap<PropertyID, PropertyID> longhands_set_by_property_id; HashMap<PropertyID, PropertyID> longhands_set_by_property_id;
auto property_is_set_by_use_initial = MUST(Bitmap::create(to_underlying(last_longhand_property_id) - to_underlying(first_longhand_property_id) + 1, false)); auto property_is_set_by_use_initial = MUST(Bitmap::create(to_underlying(last_longhand_property_id) - to_underlying(first_longhand_property_id) + 1, false));
auto property_is_logical_alias_including_shorthands = [&](PropertyID property_id) {
if (property_is_shorthand(property_id))
// NOTE: All expanded longhands for a logical alias shorthand are logical aliases so we only need to check the first one.
return property_is_logical_alias(expanded_longhands_for_shorthand(property_id)[0]);
return property_is_logical_alias(property_id);
};
// https://drafts.csswg.org/web-animations-1/#ref-for-computed-keyframes // https://drafts.csswg.org/web-animations-1/#ref-for-computed-keyframes
auto is_property_preferred = [&](PropertyID a, PropertyID b) { auto is_property_preferred = [&](PropertyID a, PropertyID b) {
// If conflicts arise when expanding shorthand properties or replacing logical properties with physical properties, apply the following rules in order until the conflict is resolved: // If conflicts arise when expanding shorthand properties or replacing logical properties with physical properties, apply the following rules in order until the conflict is resolved:
@ -1168,7 +1152,12 @@ void StyleComputer::collect_animation_into(DOM::Element& element, Optional<CSS::
return number_of_expanded_shorthands_a < number_of_expanded_shorthands_b; return number_of_expanded_shorthands_a < number_of_expanded_shorthands_b;
} }
// FIXME: 3. Physical properties override logical properties. auto property_a_is_logical_alias = property_is_logical_alias_including_shorthands(a);
auto property_b_is_logical_alias = property_is_logical_alias_including_shorthands(b);
// 3. Physical properties override logical properties.
if (property_a_is_logical_alias != property_b_is_logical_alias)
return !property_a_is_logical_alias;
// 4. For shorthand properties with an equal number of longhand components, properties whose IDL name (see // 4. For shorthand properties with an equal number of longhand components, properties whose IDL name (see
// the CSS property to IDL attribute algorithm [CSSOM]) appears earlier when sorted in ascending order // the CSS property to IDL attribute algorithm [CSSOM]) appears earlier when sorted in ascending order
@ -1208,19 +1197,20 @@ void StyleComputer::collect_animation_into(DOM::Element& element, Optional<CSS::
style_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { element.document() }, element, pseudo_element, property_id, style_value->as_unresolved()); style_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { element.document() }, element, pseudo_element, property_id, style_value->as_unresolved());
for_each_property_expanding_shorthands(property_id, *style_value, [&](PropertyID longhand_id, CSSStyleValue const& longhand_value) { for_each_property_expanding_shorthands(property_id, *style_value, [&](PropertyID longhand_id, CSSStyleValue const& longhand_value) {
auto longhand_id_bitmap_index = to_underlying(longhand_id) - to_underlying(first_longhand_property_id); auto physical_longhand_id = map_logical_alias_to_physical_property_id(longhand_id, LogicalAliasMappingContext { computed_properties.writing_mode(), computed_properties.direction() });
auto physical_longhand_id_bitmap_index = to_underlying(physical_longhand_id) - to_underlying(first_longhand_property_id);
// Don't overwrite values if this is the result of a UseInitial // Don't overwrite values if this is the result of a UseInitial
if (result.contains(longhand_id) && result.get(longhand_id) != nullptr && is_use_initial) if (result.contains(physical_longhand_id) && result.get(physical_longhand_id) != nullptr && is_use_initial)
return; return;
// Don't overwrite unless the value was originally set by a UseInitial or this property is preferred over the one that set it originally // Don't overwrite unless the value was originally set by a UseInitial or this property is preferred over the one that set it originally
if (result.contains(longhand_id) && result.get(longhand_id) != nullptr && !property_is_set_by_use_initial.get(longhand_id_bitmap_index) && !is_property_preferred(property_id, longhands_set_by_property_id.get(longhand_id).value())) if (result.contains(physical_longhand_id) && result.get(physical_longhand_id) != nullptr && !property_is_set_by_use_initial.get(physical_longhand_id_bitmap_index) && !is_property_preferred(property_id, longhands_set_by_property_id.get(physical_longhand_id).value()))
return; return;
longhands_set_by_property_id.set(longhand_id, property_id); longhands_set_by_property_id.set(physical_longhand_id, property_id);
property_is_set_by_use_initial.set(longhand_id_bitmap_index, is_use_initial); property_is_set_by_use_initial.set(physical_longhand_id_bitmap_index, is_use_initial);
result.set(longhand_id, { longhand_value }); result.set(physical_longhand_id, { longhand_value });
}); });
} }
return result; return result;
@ -1632,7 +1622,7 @@ void StyleComputer::start_needed_transitions(ComputedProperties const& previous_
// https://www.w3.org/TR/css-cascade/#cascading // https://www.w3.org/TR/css-cascade/#cascading
// https://drafts.csswg.org/css-cascade-5/#layering // https://drafts.csswg.org/css-cascade-5/#layering
GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, bool& did_match_any_pseudo_element_rules, PseudoClassBitmap& attempted_pseudo_class_matches, ComputeStyleMode mode) const GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, bool& did_match_any_pseudo_element_rules, PseudoClassBitmap& attempted_pseudo_class_matches, ComputeStyleMode mode, Optional<LogicalAliasMappingContext> logical_alias_mapping_context) const
{ {
auto cascaded_properties = m_document->heap().allocate<CascadedProperties>(); auto cascaded_properties = m_document->heap().allocate<CascadedProperties>();
@ -1676,10 +1666,10 @@ GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element&
// Then we apply the declarations from the matched rules in cascade order: // Then we apply the declarations from the matched rules in cascade order:
// Normal user agent declarations // Normal user agent declarations
cascade_declarations(*cascaded_properties, element, pseudo_element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, Important::No, {}); cascade_declarations(*cascaded_properties, element, pseudo_element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, Important::No, {}, logical_alias_mapping_context);
// Normal user declarations // Normal user declarations
cascade_declarations(*cascaded_properties, element, pseudo_element, matching_rule_set.user_rules, CascadeOrigin::User, Important::No, {}); cascade_declarations(*cascaded_properties, element, pseudo_element, matching_rule_set.user_rules, CascadeOrigin::User, Important::No, {}, logical_alias_mapping_context);
// Author presentational hints // Author presentational hints
// The spec calls this a special "Author presentational hint origin": // The spec calls this a special "Author presentational hint origin":
@ -1702,23 +1692,23 @@ GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element&
// Normal author declarations, ordered by @layer, with un-@layer-ed rules last // Normal author declarations, ordered by @layer, with un-@layer-ed rules last
for (auto const& layer : matching_rule_set.author_rules) { for (auto const& layer : matching_rule_set.author_rules) {
cascade_declarations(cascaded_properties, element, pseudo_element, layer.rules, CascadeOrigin::Author, Important::No, layer.qualified_layer_name); cascade_declarations(cascaded_properties, element, pseudo_element, layer.rules, CascadeOrigin::Author, Important::No, layer.qualified_layer_name, logical_alias_mapping_context);
} }
// Important author declarations, with un-@layer-ed rules first, followed by each @layer in reverse order. // Important author declarations, with un-@layer-ed rules first, followed by each @layer in reverse order.
for (auto const& layer : matching_rule_set.author_rules.in_reverse()) { for (auto const& layer : matching_rule_set.author_rules.in_reverse()) {
cascade_declarations(cascaded_properties, element, pseudo_element, layer.rules, CascadeOrigin::Author, Important::Yes, {}); cascade_declarations(cascaded_properties, element, pseudo_element, layer.rules, CascadeOrigin::Author, Important::Yes, {}, logical_alias_mapping_context);
} }
// Important user declarations // Important user declarations
cascade_declarations(cascaded_properties, element, pseudo_element, matching_rule_set.user_rules, CascadeOrigin::User, Important::Yes, {}); cascade_declarations(cascaded_properties, element, pseudo_element, matching_rule_set.user_rules, CascadeOrigin::User, Important::Yes, {}, logical_alias_mapping_context);
// Important user agent declarations // Important user agent declarations
cascade_declarations(cascaded_properties, element, pseudo_element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, Important::Yes, {}); cascade_declarations(cascaded_properties, element, pseudo_element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, Important::Yes, {}, logical_alias_mapping_context);
// Transition declarations [css-transitions-1] // Transition declarations [css-transitions-1]
// Note that we have to do these after finishing computing the style, // Note that we have to do these after finishing computing the style,
// so they're not done here, but as the final step in compute_style_impl() // so they're not done here, but as the final step in compute_properties()
return cascaded_properties; return cascaded_properties;
} }
@ -2216,6 +2206,38 @@ void StyleComputer::compute_font(ComputedProperties& style, DOM::Element const*
} }
} }
StyleComputer::LogicalAliasMappingContext StyleComputer::compute_logical_alias_mapping_context(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, ComputeStyleMode mode) const
{
auto normalize_value = [&](auto property_id, auto value) {
if (!value || value->is_inherit() || value->is_unset()) {
if (auto const* inheritance_parent = element_to_inherit_style_from(&element, pseudo_element)) {
value = inheritance_parent->computed_properties()->property(property_id);
} else {
value = property_initial_value(property_id);
}
}
if (value->is_initial())
value = property_initial_value(property_id);
return value;
};
bool did_match_any_pseudo_element_rules = false;
PseudoClassBitmap attempted_pseudo_class_matches;
// FIXME: Ideally we wouldn't run the whole cascade just for these few properties.
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, attempted_pseudo_class_matches, mode, {});
auto writing_mode = normalize_value(PropertyID::WritingMode, cascaded_properties->property(PropertyID::WritingMode));
auto direction = normalize_value(PropertyID::Direction, cascaded_properties->property(PropertyID::Direction));
return LogicalAliasMappingContext {
.writing_mode = keyword_to_writing_mode(writing_mode->to_keyword()).release_value(),
.direction = keyword_to_direction(direction->to_keyword()).release_value()
};
}
Gfx::Font const& StyleComputer::initial_font() const Gfx::Font const& StyleComputer::initial_font() const
{ {
// FIXME: This is not correct. // FIXME: This is not correct.
@ -2493,8 +2515,8 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& elem
// 1. Perform the cascade. This produces the "specified style" // 1. Perform the cascade. This produces the "specified style"
bool did_match_any_pseudo_element_rules = false; bool did_match_any_pseudo_element_rules = false;
PseudoClassBitmap attempted_pseudo_class_matches; PseudoClassBitmap attempted_pseudo_class_matches;
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, attempted_pseudo_class_matches, mode); auto logical_alias_mapping_context = compute_logical_alias_mapping_context(element, pseudo_element, mode);
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, attempted_pseudo_class_matches, mode, logical_alias_mapping_context);
element.set_cascaded_properties(pseudo_element, cascaded_properties); element.set_cascaded_properties(pseudo_element, cascaded_properties);
if (mode == ComputeStyleMode::CreatePseudoElementStyleIfNeeded) { if (mode == ComputeStyleMode::CreatePseudoElementStyleIfNeeded) {
@ -2631,6 +2653,7 @@ GC::Ref<ComputedProperties> StyleComputer::compute_properties(DOM::Element& elem
if (property_id == PropertyID::FontSize && !value && new_font_size) if (property_id == PropertyID::FontSize && !value && new_font_size)
continue; continue;
// FIXME: Logical properties should inherit from their parent's equivalent unmapped logical property.
if ((!value && is_inherited_property(property_id)) if ((!value && is_inherited_property(property_id))
|| (value && value->is_inherit())) { || (value && value->is_inherit())) {
if (auto inheritance_parent = element_to_inherit_style_from(&element, pseudo_element)) { if (auto inheritance_parent = element_to_inherit_style_from(&element, pseudo_element)) {

View file

@ -131,6 +131,13 @@ public:
static void for_each_property_expanding_shorthands(PropertyID, CSSStyleValue const&, Function<void(PropertyID, CSSStyleValue const&)> const& set_longhand_property); static void for_each_property_expanding_shorthands(PropertyID, CSSStyleValue const&, Function<void(PropertyID, CSSStyleValue const&)> const& set_longhand_property);
static NonnullRefPtr<CSSStyleValue const> get_inherit_value(CSS::PropertyID, DOM::Element const*, Optional<CSS::PseudoElement> = {}); static NonnullRefPtr<CSSStyleValue const> get_inherit_value(CSS::PropertyID, DOM::Element const*, Optional<CSS::PseudoElement> = {});
struct LogicalAliasMappingContext {
CSS::WritingMode writing_mode;
CSS::Direction direction;
// TODO: text-orientation
};
static PropertyID map_logical_alias_to_physical_property_id(PropertyID, LogicalAliasMappingContext);
static Optional<String> user_agent_style_sheet_source(StringView name); static Optional<String> user_agent_style_sheet_source(StringView name);
explicit StyleComputer(DOM::Document&); explicit StyleComputer(DOM::Document&);
@ -201,8 +208,9 @@ private:
struct MatchingFontCandidate; struct MatchingFontCandidate;
LogicalAliasMappingContext compute_logical_alias_mapping_context(DOM::Element&, Optional<CSS::PseudoElement>, ComputeStyleMode) const;
[[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::Element&, Optional<CSS::PseudoElement>, ComputeStyleMode) const; [[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::Element&, Optional<CSS::PseudoElement>, ComputeStyleMode) const;
[[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::Element&, Optional<CSS::PseudoElement>, bool& did_match_any_pseudo_element_rules, PseudoClassBitmap& attempted_pseudo_class_matches, ComputeStyleMode) const; [[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::Element&, Optional<CSS::PseudoElement>, bool& did_match_any_pseudo_element_rules, PseudoClassBitmap& attempted_pseudo_class_matches, ComputeStyleMode, Optional<LogicalAliasMappingContext>) const;
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive); static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive); static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const; RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const;
@ -242,7 +250,8 @@ private:
Vector<MatchingRule const*> const&, Vector<MatchingRule const*> const&,
CascadeOrigin, CascadeOrigin,
Important, Important,
Optional<FlyString> layer_name) const; Optional<FlyString> layer_name,
Optional<LogicalAliasMappingContext>) const;
void build_rule_cache(); void build_rule_cache();
void build_rule_cache_if_needed() const; void build_rule_cache_if_needed() const;

View file

@ -132,7 +132,7 @@ void populate_all_property_longhands(JsonObject& properties)
VERIFY(all_entry.has_value()); VERIFY(all_entry.has_value());
properties.for_each_member([&](auto name, auto value) { properties.for_each_member([&](auto name, auto value) {
if (value.as_object().has_array("longhands"sv) || value.as_object().has_array("logical-alias-for"sv) || value.as_object().has_string("legacy-alias-for"sv) || name == "direction" || name == "unicode-bidi") if (value.as_object().has_array("longhands"sv) || value.as_object().has_string("legacy-alias-for"sv) || name == "direction" || name == "unicode-bidi")
return; return;
MUST(all_entry->get_array("longhands"sv)->append(JsonValue { name })); MUST(all_entry->get_array("longhands"sv)->append(JsonValue { name }));
@ -310,6 +310,8 @@ enum class Quirk {
}; };
bool property_has_quirk(PropertyID, Quirk); bool property_has_quirk(PropertyID, Quirk);
bool property_is_logical_alias(PropertyID);
} // namespace Web::CSS } // namespace Web::CSS
namespace AK { namespace AK {
@ -1275,6 +1277,33 @@ Vector<PropertyID> shorthands_for_longhand(PropertyID property_id)
return { }; return { };
} }
} }
)~~~");
generator.append(R"~~~(
bool property_is_logical_alias(PropertyID property_id)
{
switch(property_id) {
)~~~");
properties.for_each_member([&](auto& name, auto& value) {
if (is_legacy_alias(value.as_object()))
return;
if (value.as_object().has("logical-alias-for"sv)) {
auto property_generator = generator.fork();
property_generator.set("name:titlecase", title_casify(name));
property_generator.append(R"~~~(
case PropertyID::@name:titlecase@:
)~~~");
}
});
generator.append(R"~~~(
return true;
default:
return false;
}
}
)~~~"); )~~~");
generator.append(R"~~~( generator.append(R"~~~(

View file

@ -1,9 +1,9 @@
Harness status: OK Harness status: OK
Found 206 tests Found 236 tests
202 Pass 218 Pass
4 Fail 18 Fail
Pass accent-color Pass accent-color
Pass border-collapse Pass border-collapse
Pass border-spacing Pass border-spacing
@ -90,11 +90,24 @@ Pass background-position-x
Pass background-position-y Pass background-position-y
Pass background-repeat Pass background-repeat
Pass background-size Pass background-size
Fail block-size
Pass border-block-end-color
Fail border-block-end-style
Fail border-block-end-width
Pass border-block-start-color
Fail border-block-start-style
Fail border-block-start-width
Pass border-bottom-color Pass border-bottom-color
Pass border-bottom-left-radius Pass border-bottom-left-radius
Pass border-bottom-right-radius Pass border-bottom-right-radius
Pass border-bottom-style Pass border-bottom-style
Pass border-bottom-width Pass border-bottom-width
Pass border-inline-end-color
Fail border-inline-end-style
Fail border-inline-end-width
Pass border-inline-start-color
Fail border-inline-start-style
Fail border-inline-start-width
Pass border-left-color Pass border-left-color
Pass border-left-style Pass border-left-style
Pass border-left-width Pass border-left-width
@ -142,20 +155,33 @@ Pass grid-template-areas
Pass grid-template-columns Pass grid-template-columns
Pass grid-template-rows Pass grid-template-rows
Fail height Fail height
Fail inline-size
Pass inset-block-end
Pass inset-block-start
Pass inset-inline-end
Pass inset-inline-start
Pass isolation Pass isolation
Pass justify-content Pass justify-content
Pass justify-items Pass justify-items
Pass justify-self Pass justify-self
Pass left Pass left
Pass margin-block-end
Pass margin-block-start
Pass margin-bottom Pass margin-bottom
Pass margin-inline-end
Pass margin-inline-start
Pass margin-left Pass margin-left
Pass margin-right Pass margin-right
Pass margin-top Pass margin-top
Pass mask-image Pass mask-image
Pass mask-type Pass mask-type
Fail max-block-size
Pass max-height Pass max-height
Fail max-inline-size
Pass max-width Pass max-width
Fail min-block-size
Pass min-height Pass min-height
Fail min-inline-size
Pass min-width Pass min-width
Pass mix-blend-mode Pass mix-blend-mode
Pass object-fit Pass object-fit
@ -168,7 +194,11 @@ Pass outline-style
Pass outline-width Pass outline-width
Pass overflow-x Pass overflow-x
Pass overflow-y Pass overflow-y
Pass padding-block-end
Pass padding-block-start
Pass padding-bottom Pass padding-bottom
Pass padding-inline-end
Pass padding-inline-start
Pass padding-left Pass padding-left
Pass padding-right Pass padding-right
Pass padding-top Pass padding-top

View file

@ -0,0 +1,50 @@
Harness status: OK
Found 44 tests
8 Pass
36 Fail
Pass Test that logical inset-* properties are supported.
Pass Test that inset-inline shorthand sets longhands and serializes correctly.
Pass Test that inset-block shorthand sets longhands and serializes correctly.
Pass Test that inset shorthand sets longhands and serializes correctly.
Pass Test that logical inset-* properties share computed values with their physical associates, with 'writing-mode: horizontal-tb; direction: ltr; '.
Pass Test that inset-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: horizontal-tb; direction: ltr; '.
Pass Test that inset-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: horizontal-tb; direction: ltr; '.
Pass Test that inset-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: horizontal-tb; direction: ltr; '.
Fail Test that logical inset-* properties share computed values with their physical associates, with 'writing-mode: horizontal-tb; direction: rtl; '.
Fail Test that inset-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: horizontal-tb; direction: rtl; '.
Fail Test that inset-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: horizontal-tb; direction: rtl; '.
Fail Test that inset-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: horizontal-tb; direction: rtl; '.
Fail Test that logical inset-* properties share computed values with their physical associates, with 'writing-mode: vertical-rl; direction: rtl; '.
Fail Test that inset-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: vertical-rl; direction: rtl; '.
Fail Test that inset-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: vertical-rl; direction: rtl; '.
Fail Test that inset-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: vertical-rl; direction: rtl; '.
Fail Test that logical inset-* properties share computed values with their physical associates, with 'writing-mode: sideways-rl; direction: rtl; '.
Fail Test that inset-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: sideways-rl; direction: rtl; '.
Fail Test that inset-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: sideways-rl; direction: rtl; '.
Fail Test that inset-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: sideways-rl; direction: rtl; '.
Fail Test that logical inset-* properties share computed values with their physical associates, with 'writing-mode: vertical-rl; direction: ltr; '.
Fail Test that inset-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: vertical-rl; direction: ltr; '.
Fail Test that inset-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: vertical-rl; direction: ltr; '.
Fail Test that inset-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: vertical-rl; direction: ltr; '.
Fail Test that logical inset-* properties share computed values with their physical associates, with 'writing-mode: sideways-rl; direction: ltr; '.
Fail Test that inset-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: sideways-rl; direction: ltr; '.
Fail Test that inset-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: sideways-rl; direction: ltr; '.
Fail Test that inset-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: sideways-rl; direction: ltr; '.
Fail Test that logical inset-* properties share computed values with their physical associates, with 'writing-mode: vertical-lr; direction: rtl; '.
Fail Test that inset-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: vertical-lr; direction: rtl; '.
Fail Test that inset-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: vertical-lr; direction: rtl; '.
Fail Test that inset-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: vertical-lr; direction: rtl; '.
Fail Test that logical inset-* properties share computed values with their physical associates, with 'writing-mode: sideways-lr; direction: ltr; '.
Fail Test that inset-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: sideways-lr; direction: ltr; '.
Fail Test that inset-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: sideways-lr; direction: ltr; '.
Fail Test that inset-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: sideways-lr; direction: ltr; '.
Fail Test that logical inset-* properties share computed values with their physical associates, with 'writing-mode: vertical-lr; direction: ltr; '.
Fail Test that inset-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: vertical-lr; direction: ltr; '.
Fail Test that inset-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: vertical-lr; direction: ltr; '.
Fail Test that inset-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: vertical-lr; direction: ltr; '.
Fail Test that logical inset-* properties share computed values with their physical associates, with 'writing-mode: sideways-lr; direction: rtl; '.
Fail Test that inset-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: sideways-lr; direction: rtl; '.
Fail Test that inset-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: sideways-lr; direction: rtl; '.
Fail Test that inset-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: sideways-lr; direction: rtl; '.

View file

@ -0,0 +1,50 @@
Harness status: OK
Found 44 tests
8 Pass
36 Fail
Pass Test that logical margin-* properties are supported.
Pass Test that margin-inline shorthand sets longhands and serializes correctly.
Pass Test that margin-block shorthand sets longhands and serializes correctly.
Pass Test that margin shorthand sets longhands and serializes correctly.
Pass Test that logical margin-* properties share computed values with their physical associates, with 'writing-mode: horizontal-tb; direction: ltr; '.
Pass Test that margin-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: horizontal-tb; direction: ltr; '.
Pass Test that margin-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: horizontal-tb; direction: ltr; '.
Pass Test that margin-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: horizontal-tb; direction: ltr; '.
Fail Test that logical margin-* properties share computed values with their physical associates, with 'writing-mode: horizontal-tb; direction: rtl; '.
Fail Test that margin-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: horizontal-tb; direction: rtl; '.
Fail Test that margin-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: horizontal-tb; direction: rtl; '.
Fail Test that margin-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: horizontal-tb; direction: rtl; '.
Fail Test that logical margin-* properties share computed values with their physical associates, with 'writing-mode: vertical-rl; direction: rtl; '.
Fail Test that margin-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: vertical-rl; direction: rtl; '.
Fail Test that margin-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: vertical-rl; direction: rtl; '.
Fail Test that margin-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: vertical-rl; direction: rtl; '.
Fail Test that logical margin-* properties share computed values with their physical associates, with 'writing-mode: sideways-rl; direction: rtl; '.
Fail Test that margin-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: sideways-rl; direction: rtl; '.
Fail Test that margin-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: sideways-rl; direction: rtl; '.
Fail Test that margin-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: sideways-rl; direction: rtl; '.
Fail Test that logical margin-* properties share computed values with their physical associates, with 'writing-mode: vertical-rl; direction: ltr; '.
Fail Test that margin-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: vertical-rl; direction: ltr; '.
Fail Test that margin-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: vertical-rl; direction: ltr; '.
Fail Test that margin-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: vertical-rl; direction: ltr; '.
Fail Test that logical margin-* properties share computed values with their physical associates, with 'writing-mode: sideways-rl; direction: ltr; '.
Fail Test that margin-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: sideways-rl; direction: ltr; '.
Fail Test that margin-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: sideways-rl; direction: ltr; '.
Fail Test that margin-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: sideways-rl; direction: ltr; '.
Fail Test that logical margin-* properties share computed values with their physical associates, with 'writing-mode: vertical-lr; direction: rtl; '.
Fail Test that margin-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: vertical-lr; direction: rtl; '.
Fail Test that margin-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: vertical-lr; direction: rtl; '.
Fail Test that margin-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: vertical-lr; direction: rtl; '.
Fail Test that logical margin-* properties share computed values with their physical associates, with 'writing-mode: sideways-lr; direction: ltr; '.
Fail Test that margin-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: sideways-lr; direction: ltr; '.
Fail Test that margin-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: sideways-lr; direction: ltr; '.
Fail Test that margin-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: sideways-lr; direction: ltr; '.
Fail Test that logical margin-* properties share computed values with their physical associates, with 'writing-mode: vertical-lr; direction: ltr; '.
Fail Test that margin-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: vertical-lr; direction: ltr; '.
Fail Test that margin-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: vertical-lr; direction: ltr; '.
Fail Test that margin-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: vertical-lr; direction: ltr; '.
Fail Test that logical margin-* properties share computed values with their physical associates, with 'writing-mode: sideways-lr; direction: rtl; '.
Fail Test that margin-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: sideways-lr; direction: rtl; '.
Fail Test that margin-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: sideways-lr; direction: rtl; '.
Fail Test that margin-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: sideways-lr; direction: rtl; '.

View file

@ -0,0 +1,50 @@
Harness status: OK
Found 44 tests
8 Pass
36 Fail
Pass Test that logical padding-* properties are supported.
Pass Test that padding-inline shorthand sets longhands and serializes correctly.
Pass Test that padding-block shorthand sets longhands and serializes correctly.
Pass Test that padding shorthand sets longhands and serializes correctly.
Pass Test that logical padding-* properties share computed values with their physical associates, with 'writing-mode: horizontal-tb; direction: ltr; '.
Pass Test that padding-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: horizontal-tb; direction: ltr; '.
Pass Test that padding-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: horizontal-tb; direction: ltr; '.
Pass Test that padding-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: horizontal-tb; direction: ltr; '.
Fail Test that logical padding-* properties share computed values with their physical associates, with 'writing-mode: horizontal-tb; direction: rtl; '.
Fail Test that padding-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: horizontal-tb; direction: rtl; '.
Fail Test that padding-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: horizontal-tb; direction: rtl; '.
Fail Test that padding-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: horizontal-tb; direction: rtl; '.
Fail Test that logical padding-* properties share computed values with their physical associates, with 'writing-mode: vertical-rl; direction: rtl; '.
Fail Test that padding-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: vertical-rl; direction: rtl; '.
Fail Test that padding-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: vertical-rl; direction: rtl; '.
Fail Test that padding-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: vertical-rl; direction: rtl; '.
Fail Test that logical padding-* properties share computed values with their physical associates, with 'writing-mode: sideways-rl; direction: rtl; '.
Fail Test that padding-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: sideways-rl; direction: rtl; '.
Fail Test that padding-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: sideways-rl; direction: rtl; '.
Fail Test that padding-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: sideways-rl; direction: rtl; '.
Fail Test that logical padding-* properties share computed values with their physical associates, with 'writing-mode: vertical-rl; direction: ltr; '.
Fail Test that padding-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: vertical-rl; direction: ltr; '.
Fail Test that padding-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: vertical-rl; direction: ltr; '.
Fail Test that padding-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: vertical-rl; direction: ltr; '.
Fail Test that logical padding-* properties share computed values with their physical associates, with 'writing-mode: sideways-rl; direction: ltr; '.
Fail Test that padding-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: sideways-rl; direction: ltr; '.
Fail Test that padding-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: sideways-rl; direction: ltr; '.
Fail Test that padding-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: sideways-rl; direction: ltr; '.
Fail Test that logical padding-* properties share computed values with their physical associates, with 'writing-mode: vertical-lr; direction: rtl; '.
Fail Test that padding-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: vertical-lr; direction: rtl; '.
Fail Test that padding-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: vertical-lr; direction: rtl; '.
Fail Test that padding-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: vertical-lr; direction: rtl; '.
Fail Test that logical padding-* properties share computed values with their physical associates, with 'writing-mode: sideways-lr; direction: ltr; '.
Fail Test that padding-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: sideways-lr; direction: ltr; '.
Fail Test that padding-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: sideways-lr; direction: ltr; '.
Fail Test that padding-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: sideways-lr; direction: ltr; '.
Fail Test that logical padding-* properties share computed values with their physical associates, with 'writing-mode: vertical-lr; direction: ltr; '.
Fail Test that padding-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: vertical-lr; direction: ltr; '.
Fail Test that padding-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: vertical-lr; direction: ltr; '.
Fail Test that padding-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: vertical-lr; direction: ltr; '.
Fail Test that logical padding-* properties share computed values with their physical associates, with 'writing-mode: sideways-lr; direction: rtl; '.
Fail Test that padding-* shorthands set the computed value of both logical and physical longhands, with 'writing-mode: sideways-lr; direction: rtl; '.
Fail Test that padding-* properties honor order of appearance when both logical and physical associates are declared, with 'writing-mode: sideways-lr; direction: rtl; '.
Fail Test that padding-* properties honor selector specificty when both logical and physical associates are declared, with 'writing-mode: sideways-lr; direction: rtl; '.

View file

@ -2,13 +2,12 @@ Harness status: OK
Found 11 tests Found 11 tests
7 Pass 11 Pass
4 Fail Pass All properties can serialize 'initial'
Fail All properties can serialize 'initial'
Pass All properties (except 'all') can serialize their initial value (computed) Pass All properties (except 'all') can serialize their initial value (computed)
Fail All properties (except 'all') can serialize their initial value (specified) Pass All properties (except 'all') can serialize their initial value (specified)
Fail All shorthands can serialize their longhands set to 'initial' Pass All shorthands can serialize their longhands set to 'initial'
Fail All shorthands (except 'all') can serialize their longhands set to their initial value Pass All shorthands (except 'all') can serialize their longhands set to their initial value
Pass All aliases can serialize target property set to 'initial' Pass All aliases can serialize target property set to 'initial'
Pass All aliases can serialize target property set to its initial value Pass All aliases can serialize target property set to its initial value
Pass Can't serialize shorthand when longhands are set to different css-wide keywords Pass Can't serialize shorthand when longhands are set to different css-wide keywords

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<title>CSS Logical Properties: Flow-Relative Offsets</title>
<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com" />
<link rel="help" href="https://drafts.csswg.org/css-logical/#inset-properties">
<meta name="assert" content="This test checks the interaction of the flow-relative inset-* properties with the physical ones in different writing modes." />
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<div id="log"></div>
<script type="module">
import {runTests, createBoxPropertyGroup} from "./resources/test-box-properties.js";
runTests(createBoxPropertyGroup("inset-*", {
type: "length",
prerequisites: {"position": "relative"},
}));
</script>

View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<title>CSS Logical Properties: Flow-Relative Margins</title>
<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com" />
<link rel="help" href="https://drafts.csswg.org/css-logical/#margin-properties">
<meta name="assert" content="This test checks the interaction of the flow-relative margin-* properties with the physical ones in different writing modes." />
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<div id="log"></div>
<script type="module">
import {runTests, createBoxPropertyGroup} from "./resources/test-box-properties.js";
runTests(createBoxPropertyGroup("margin-*", {type: "length"}));
</script>

View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<title>CSS Logical Properties: Flow-Relative Padding</title>
<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com" />
<link rel="help" href="https://drafts.csswg.org/css-logical/#padding-properties">
<meta name="assert" content="This test checks the interaction of the flow-relative padding-* properties with the physical ones in different writing modes." />
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<div id="log"></div>
<script type="module">
import {runTests, createBoxPropertyGroup} from "./resources/test-box-properties.js";
runTests(createBoxPropertyGroup("padding-*", {type: "length"}));
</script>

View file

@ -0,0 +1,297 @@
import {
testElement,
writingModes,
testCSSValues,
testComputedValues,
makeDeclaration
} from "./test-shared.js";
// Values to use while testing
const testValues = {
"length": ["1px", "2px", "3px", "4px", "5px"],
"color": ["rgb(1, 1, 1)", "rgb(2, 2, 2)", "rgb(3, 3, 3)", "rgb(4, 4, 4)", "rgb(5, 5, 5)"],
"border-style": ["solid", "dashed", "dotted", "double", "groove"],
};
/**
* Creates a group of physical and logical box properties, such as
*
* { physical: {
* left: "margin-left", right: "margin-right",
* top: "margin-top", bottom: "margin-bottom",
* }, logical: {
* inlineStart: "margin-inline-start", inlineEnd: "margin-inline-end",
* blockStart: "margin-block-start", blockEnd: "margin-block-end",
* }, shorthands: {
* "margin": ["margin-top", "margin-right", "margin-bottom", "margin-left"],
* "margin-inline": ["margin-inline-start", "margin-inline-end"],
* "margin-block": ["margin-block-start", "margin-block-end"],
* }, type: ["length"], prerequisites: "...", property: "margin-*" }
*
* @param {string} property
* A string representing the property names, like "margin-*".
* @param {Object} descriptor
* @param {string|string[]} descriptor.type
* Describes the kind of values accepted by the property, like "length".
* Must be a key or a collection of keys from the `testValues` object.
* @param {Object={}} descriptor.prerequisites
* Represents property declarations that are needed by `property` to work.
* For example, border-width properties require a border style.
*/
export function createBoxPropertyGroup(property, descriptor) {
const logical = {};
const physical = {};
const shorthands = {};
for (const axis of ["inline", "block"]) {
const shorthand = property.replace("*", axis);
const longhands = [];
shorthands[shorthand] = longhands;
for (const side of ["start", "end"]) {
const logicalSide = axis + "-" + side;
const camelCase = logicalSide.replace(/-(.)/g, (match, $1) => $1.toUpperCase());
const longhand = property.replace("*", logicalSide);
logical[camelCase] = longhand;
longhands.push(longhand);
}
}
const isInset = property === "inset-*";
let prerequisites = "";
for (const physicalSide of ["left", "right", "top", "bottom"]) {
physical[physicalSide] = isInset ? physicalSide : property.replace("*", physicalSide);
prerequisites += makeDeclaration(descriptor.prerequisites, physicalSide);
}
shorthands[property.replace("-*", "")] =
["top", "right", "bottom", "left"].map(physicalSide => physical[physicalSide]);
const type = [].concat(descriptor.type);
return { logical, physical, shorthands, type, prerequisites, property };
}
/**
* Creates a group physical and logical box-corner properties.
*
* @param {string} property
* A string representing the property names, like "border-*-radius".
* @param {Object} descriptor
* @param {string|string[]} descriptor.type
* Describes the kind of values accepted by the property, like "length".
* Must be a key or a collection of keys from the `testValues` object.
* @param {Object={}} descriptor.prerequisites
* Represents property declarations that are needed by `property` to work.
* For example, border-width properties require a border style.
*/
export function createCornerPropertyGroup(property, descriptor) {
const logical = {};
const physical = {};
const shorthands = {};
for (const logicalCorner of ["start-start", "start-end", "end-start", "end-end"]) {
const prop = property.replace("*", logicalCorner);
const [block_side, inline_side] = logicalCorner.split("-");
const b = "block" + block_side.charAt(0).toUpperCase() + block_side.slice(1);
const i = "inline" + inline_side.charAt(0).toUpperCase() + inline_side.slice(1);
const index = b + "-" + i; // e.g. "blockStart-inlineEnd"
logical[index] = prop;
}
let prerequisites = "";
for (const physicalCorner of ["top-left", "top-right", "bottom-left", "bottom-right"]) {
const prop = property.replace("*", physicalCorner);
physical[physicalCorner] = prop;
prerequisites += makeDeclaration(descriptor.prerequisites, physicalCorner);
}
const type = [].concat(descriptor.type);
return { logical, physical, shorthands, type, prerequisites, property };
}
/**
* Creates a group of physical and logical sizing properties.
*
* @param {string} prefix
* One of "", "max-" or "min-".
*/
export function createSizingPropertyGroup(prefix) {
return {
logical: {
inline: `${prefix}inline-size`,
block: `${prefix}block-size`,
},
physical: {
horizontal: `${prefix}width`,
vertical: `${prefix}height`,
},
type: ["length"],
prerequisites: makeDeclaration({ display: "block" }),
property: (prefix ? prefix.slice(0, -1) + " " : "") + "sizing",
};
}
/**
* Tests a grup of logical and physical properties in different writing modes.
*
* @param {Object} group
* An object returned by createBoxPropertyGroup or createSizingPropertyGroup.
*/
export function runTests(group) {
const values = testValues[group.type[0]].map(function (_, i) {
return group.type.map(type => testValues[type][i]).join(" ");
});
const logicals = Object.values(group.logical);
const physicals = Object.values(group.physical);
const shorthands = group.shorthands ? Object.entries(group.shorthands) : null;
const is_corner = group.property == "border-*-radius";
test(function () {
const expected = [];
for (const [i, logicalProp] of logicals.entries()) {
testElement.style.setProperty(logicalProp, values[i]);
expected.push([logicalProp, values[i]]);
}
testCSSValues("logical properties in inline style", testElement.style, expected);
}, `Test that logical ${group.property} properties are supported.`);
testElement.style.cssText = "";
const shorthandValues = {};
for (const [shorthand, longhands] of shorthands || []) {
let valueArray;
if (group.type.length > 1) {
valueArray = [values[0]];
} else {
valueArray = testValues[group.type].slice(0, longhands.length);
}
shorthandValues[shorthand] = valueArray;
const value = valueArray.join(" ");
const expected = [[shorthand, value]];
for (let [i, longhand] of longhands.entries()) {
expected.push([longhand, valueArray[group.type.length > 1 ? 0 : i]]);
}
test(function () {
testElement.style.setProperty(shorthand, value);
testCSSValues("shorthand in inline style", testElement.style, expected);
const stylesheet = `.test { ${group.prerequisites} }`;
testComputedValues("shorthand in computed style", stylesheet, expected);
}, `Test that ${shorthand} shorthand sets longhands and serializes correctly.`);
testElement.style.cssText = "";
}
for (const writingMode of writingModes) {
for (const style of writingMode.styles) {
const writingModeDecl = makeDeclaration(style);
const associated = {};
for (const [logicalSide, logicalProp] of Object.entries(group.logical)) {
let physicalProp;
if (is_corner) {
const [block_side, inline_side] = logicalSide.split("-");
const physicalSide1 = writingMode[block_side];
const physicalSide2 = writingMode[inline_side];
let physicalCorner;
// mirror "left-top" to "top-left" etc
if (["top", "bottom"].includes(physicalSide1)) {
physicalCorner = physicalSide1 + "-" + physicalSide2;
} else {
physicalCorner = physicalSide2 + "-" + physicalSide1;
}
physicalProp = group.physical[physicalCorner];
} else {
physicalProp = group.physical[writingMode[logicalSide]];
}
associated[logicalProp] = physicalProp;
associated[physicalProp] = logicalProp;
}
// Test that logical properties are converted to their physical
// equivalent correctly when all in the group are present on a single
// declaration, with no overwriting of previous properties and
// no physical properties present. We put the writing mode properties
// on a separate declaration to test that the computed values of these
// properties are used, rather than those on the same declaration.
test(function () {
let decl = group.prerequisites;
const expected = [];
for (const [i, logicalProp] of logicals.entries()) {
decl += `${logicalProp}: ${values[i]}; `;
expected.push([logicalProp, values[i]]);
expected.push([associated[logicalProp], values[i]]);
}
testComputedValues("logical properties on one declaration, writing " +
`mode properties on another, '${writingModeDecl}'`,
`.test { ${writingModeDecl} } .test { ${decl} }`,
expected);
}, `Test that logical ${group.property} properties share computed values `
+ `with their physical associates, with '${writingModeDecl}'.`);
// Test logical shorthand properties.
if (shorthands) {
test(function () {
for (const [shorthand, longhands] of shorthands) {
let valueArray = shorthandValues[shorthand];
const decl = group.prerequisites + `${shorthand}: ${valueArray.join(" ")}; `;
const expected = [];
for (let [i, longhand] of longhands.entries()) {
const longhandValue = valueArray[group.type.length > 1 ? 0 : i];
expected.push([longhand, longhandValue]);
expected.push([associated[longhand], longhandValue]);
}
testComputedValues("shorthand properties on one declaration, writing " +
`mode properties on another, '${writingModeDecl}'`,
`.test { ${writingModeDecl} } .test { ${decl} }`,
expected);
}
}, `Test that ${group.property} shorthands set the computed value of both `
+ `logical and physical longhands, with '${writingModeDecl}'.`);
}
// Test that logical and physical properties are cascaded together,
// honoring their relative order on a single declaration
// (a) with a single logical property after the physical ones
// (b) with a single physical property after the logical ones
test(function () {
for (const lastIsLogical of [true, false]) {
const lasts = lastIsLogical ? logicals : physicals;
const others = lastIsLogical ? physicals : logicals;
for (const lastProp of lasts) {
let decl = writingModeDecl + group.prerequisites;
const expected = [];
for (const [i, prop] of others.entries()) {
decl += `${prop}: ${values[i]}; `;
const valueIdx = associated[prop] === lastProp ? others.length : i;
expected.push([prop, values[valueIdx]]);
expected.push([associated[prop], values[valueIdx]]);
}
decl += `${lastProp}: ${values[others.length]}; `;
testComputedValues(`'${lastProp}' last on single declaration, '${writingModeDecl}'`,
`.test { ${decl} }`,
expected);
}
}
}, `Test that ${group.property} properties honor order of appearance when both `
+ `logical and physical associates are declared, with '${writingModeDecl}'.`);
// Test that logical and physical properties are cascaded properly when
// on different declarations
// (a) with a logical property in the high specificity rule
// (b) with a physical property in the high specificity rule
test(function () {
for (const highIsLogical of [true, false]) {
let lowDecl = writingModeDecl + group.prerequisites;
const high = highIsLogical ? logicals : physicals;
const others = highIsLogical ? physicals : logicals;
for (const [i, prop] of others.entries()) {
lowDecl += `${prop}: ${values[i]}; `;
}
for (const highProp of high) {
const highDecl = `${highProp}: ${values[others.length]}; `;
const expected = [];
for (const [i, prop] of others.entries()) {
const valueIdx = associated[prop] === highProp ? others.length : i;
expected.push([prop, values[valueIdx]]);
expected.push([associated[prop], values[valueIdx]]);
}
testComputedValues(`'${highProp}', two declarations, '${writingModeDecl}'`,
`#test { ${highDecl} } .test { ${lowDecl} }`,
expected);
}
}
}, `Test that ${group.property} properties honor selector specificty when both `
+ `logical and physical associates are declared, with '${writingModeDecl}'.`);
}
}
}

View file

@ -0,0 +1,122 @@
const sheet = document.head.appendChild(document.createElement("style"));
// Specify size for outer <div> to avoid unconstrained-size warnings
// when writing-mode of the inner test <div> is vertical-*
const wrapper = document.body.appendChild(document.createElement("div"));
wrapper.style.cssText = "width:100px; height: 100px;";
export const testElement = wrapper.appendChild(document.createElement("div"));
testElement.id = testElement.className = "test";
// Six unique overall writing modes for property-mapping purposes.
export const writingModes = [
{
styles: [
{ "writing-mode": "horizontal-tb", "direction": "ltr" },
],
blockStart: "top", blockEnd: "bottom", inlineStart: "left", inlineEnd: "right",
over: "top", under: "bottom", lineLeft: "left", lineRight: "right",
block: "vertical", inline: "horizontal"
},
{
styles: [
{ "writing-mode": "horizontal-tb", "direction": "rtl" },
],
blockStart: "top", blockEnd: "bottom", inlineStart: "right", inlineEnd: "left",
over: "top", under: "bottom", lineLeft: "left", lineRight: "right",
block: "vertical", inline: "horizontal"
},
{
styles: [
{ "writing-mode": "vertical-rl", "direction": "rtl" },
{ "writing-mode": "sideways-rl", "direction": "rtl" },
],
blockStart: "right", blockEnd: "left", inlineStart: "bottom", inlineEnd: "top",
over: "right", under: "left", lineLeft: "top", lineRight: "bottom",
block: "horizontal", inline: "vertical"
},
{
styles: [
{ "writing-mode": "vertical-rl", "direction": "ltr" },
{ "writing-mode": "sideways-rl", "direction": "ltr" },
],
blockStart: "right", blockEnd: "left", inlineStart: "top", inlineEnd: "bottom",
over: "right", under: "left", lineLeft: "top", lineRight: "bottom",
block: "horizontal", inline: "vertical"
},
{
styles: [
{ "writing-mode": "vertical-lr", "direction": "rtl" },
],
blockStart: "left", blockEnd: "right", inlineStart: "bottom", inlineEnd: "top",
over: "right", under: "left", lineLeft: "top", lineRight: "bottom",
block: "horizontal", inline: "vertical"
},
{
styles: [
{ "writing-mode": "sideways-lr", "direction": "ltr" },
],
blockStart: "left", blockEnd: "right", inlineStart: "bottom", inlineEnd: "top",
over: "left", under: "right", lineLeft: "bottom", lineRight: "top",
block: "horizontal", inline: "vertical"
},
{
styles: [
{ "writing-mode": "vertical-lr", "direction": "ltr" },
],
blockStart: "left", blockEnd: "right", inlineStart: "top", inlineEnd: "bottom",
over: "right", under: "left", lineLeft: "top", lineRight: "bottom",
block: "horizontal", inline: "vertical"
},
{
styles: [
{ "writing-mode": "sideways-lr", "direction": "rtl" },
],
blockStart: "left", blockEnd: "right", inlineStart: "top", inlineEnd: "bottom",
over: "left", under: "right", lineLeft: "bottom", lineRight: "top",
block: "horizontal", inline: "vertical"
},
];
// Check if logical properties work well in WebKit non-standard
// '-webkit-writing-mode: horizontal-bt' mode
if (CSS.supports("-webkit-writing-mode", "horizontal-bt")) {
writingModes.push(
{
styles: [
{ "-webkit-writing-mode": "horizontal-bt", "direction": "ltr" },
],
blockStart: "bottom", blockEnd: "top", inlineStart: "left", inlineEnd: "right",
over: "top", under: "bottom", lineLeft: "left", lineRight: "right",
block: "vertical", inline: "horizontal"
},
{
styles: [
{ "-webkit-writing-mode": "horizontal-bt", "direction": "rtl" },
],
blockStart: "bottom", blockEnd: "top", inlineStart: "right", inlineEnd: "left",
over: "top", under: "bottom", lineLeft: "left", lineRight: "right",
block: "vertical", inline: "horizontal"
},
)
}
export function testCSSValues(testName, style, expectedValues) {
for (const [property, value] of expectedValues) {
assert_equals(style.getPropertyValue(property), value, `${testName}, ${property}`);
}
}
export function testComputedValues(testName, rules, expectedValues) {
sheet.textContent = rules;
const cs = getComputedStyle(testElement);
testCSSValues(testName, cs, expectedValues);
sheet.textContent = "";
}
export function makeDeclaration(object = {}, replacement = "*") {
let decl = "";
for (const [property, value] of Object.entries(object)) {
decl += `${property.replace("*", replacement)}: ${value}; `;
}
return decl;
}