LibWeb: Implement and use ListStyleStyleValue

Yes, the name is silly, but it's a StyleValue for list-style, so...
yeah. :^)

Since `list-style-type` and `list-style-image` can both have `none` as a
value, and can appear in any order, we have to handle it separately, and
then assign either or both of those to `none` depending on how many
`none`s there are, and whether those sub-properties already have values.

Added some extra test cases to lists.html to cover list-style-image and
list-style-position parts of the list-style shorthand, and the `none`
values.
This commit is contained in:
Sam Atkins 2021-08-03 15:56:51 +01:00 committed by Andreas Kling
parent dcbfb61816
commit 0e15561df0
7 changed files with 246 additions and 98 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

View file

@ -32,6 +32,18 @@
<li>Another entry</li>
</ul>
<p>list-style: inside url(list-item.png)</p>
<ul style="list-style: inside disc url(custom-list-item.png);">
<li>Entry one</li>
<li>Another entry</li>
</ul>
<p>list-style: outside url(list-item.png)</p>
<ul style="list-style: outside disc url(custom-list-item.png);">
<li>Entry one</li>
<li>Another entry</li>
</ul>
<h2>ol</h2>
<p>default</p>
<ol>
@ -175,6 +187,78 @@
<li>Another Entry</li>
</ol>
<p>list-style: outside lower-roman url(list-file.png)</p>
<ol style="list-style: outside lower-roman url(custome-list-item-2.png);">
<li>First</li>
<li>Second</li>
<li>Third</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
<li>Another Entry</li>
</ol>
<h2>'none' values</h2>
<p>list-style: none</p>
<ul style="list-style: none;">
<li>Entry one</li>
<li>Another entry</li>
</ul>
<p>list-style: none square</p>
<ul style="list-style: none square;">
<li>Entry one</li>
<li>Another entry</li>
</ul>
<p>list-style: square none</p>
<ul style="list-style: square none;">
<li>Entry one</li>
<li>Another entry</li>
</ul>
<p>list-style: url(list-item.png) none</p>
<ul style="list-style: url(custom-list-item.png) none;">
<li>Entry one</li>
<li>Another entry</li>
</ul>
<p>list-style: none none </p>
<ul style="list-style: none none;">
<li>Entry one</li>
<li>Another entry</li>
</ul>
<p>list-style: inside none none</p>
<ul style="list-style: inside none none;">
<li>Entry one</li>
<li>Another entry</li>
</ul>
<p>list-style: square none none (error)</p>
<ul style="list-style: square none none;">
<li>Entry one</li>
<li>Another entry</li>
</ul>
<p>list-style: none none none (is an error)</p>
<ul style="list-style: none none none;">
<li>Entry one</li>
<li>Another entry</li>
</ul>
</body>
</html>

View file

@ -2063,6 +2063,116 @@ RefPtr<StyleValue> Parser::parse_font_value(ParsingContext const& context, Vecto
return FontStyleValue::create(font_style.release_nonnull(), font_weight.release_nonnull(), font_size.release_nonnull(), line_height.release_nonnull(), move(font_families));
}
RefPtr<StyleValue> Parser::parse_list_style_value(ParsingContext const& context, Vector<StyleComponentValueRule> const& component_values)
{
auto is_list_style_image = [](StyleValue const& value) -> bool {
if (value.is_image())
return true;
if (value.is_identifier() && value.to_identifier() == ValueID::None)
return true;
return false;
};
auto is_list_style_position = [](StyleValue const& value) -> bool {
switch (value.to_identifier()) {
case ValueID::Inside:
case ValueID::Outside:
return true;
default:
return false;
}
};
auto is_list_style_type = [](StyleValue const& value) -> bool {
// FIXME: Handle strings and symbols("...") syntax
switch (value.to_identifier()) {
case CSS::ValueID::None:
case CSS::ValueID::Disc:
case CSS::ValueID::Circle:
case CSS::ValueID::Square:
case CSS::ValueID::Decimal:
case CSS::ValueID::DecimalLeadingZero:
case CSS::ValueID::LowerAlpha:
case CSS::ValueID::LowerLatin:
case CSS::ValueID::UpperAlpha:
case CSS::ValueID::UpperLatin:
case CSS::ValueID::UpperRoman:
case CSS::ValueID::LowerRoman:
return true;
default:
return false;
}
};
if (component_values.size() > 3)
return nullptr;
RefPtr<StyleValue> list_position;
RefPtr<StyleValue> list_image;
RefPtr<StyleValue> list_type;
int found_nones = 0;
for (auto& part : component_values) {
auto value = parse_css_value(context, PropertyID::ListStyle, part);
if (!value)
return nullptr;
if (value->to_identifier() == ValueID::None) {
found_nones++;
continue;
}
if (is_list_style_position(*value)) {
if (list_position)
return nullptr;
list_position = value.release_nonnull();
continue;
}
if (is_list_style_image(*value)) {
if (list_image)
return nullptr;
list_image = value.release_nonnull();
continue;
}
if (is_list_style_type(*value)) {
if (list_type)
return nullptr;
list_type = value.release_nonnull();
continue;
}
}
if (found_nones > 2)
return nullptr;
if (found_nones == 2) {
if (list_image || list_type)
return nullptr;
auto none = IdentifierStyleValue::create(ValueID::None);
list_image = none;
list_type = none;
} else if (found_nones == 1) {
if (list_image && list_type)
return nullptr;
auto none = IdentifierStyleValue::create(ValueID::None);
if (!list_image)
list_image = none;
if (!list_type)
list_type = none;
}
if (!list_position)
list_position = IdentifierStyleValue::create(ValueID::Outside);
if (!list_image)
list_image = IdentifierStyleValue::create(ValueID::None);
if (!list_type)
list_type = IdentifierStyleValue::create(ValueID::Disc);
return ListStyleStyleValue::create(list_position.release_nonnull(), list_image.release_nonnull(), list_type.release_nonnull());
}
RefPtr<StyleValue> Parser::parse_as_css_value(PropertyID property_id)
{
auto component_values = parse_as_list_of_component_values();
@ -2105,6 +2215,10 @@ RefPtr<StyleValue> Parser::parse_css_value(PropertyID property_id, TokenStream<S
if (auto parsed_value = parse_font_value(m_context, component_values))
return parsed_value;
break;
case PropertyID::ListStyle:
if (auto parsed_value = parse_list_style_value(m_context, component_values))
return parsed_value;
break;
default:
break;
}

View file

@ -178,6 +178,7 @@ private:
static RefPtr<StyleValue> parse_background_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
static RefPtr<StyleValue> parse_box_shadow_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
static RefPtr<StyleValue> parse_font_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
static RefPtr<StyleValue> parse_list_style_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
// calc() parsing, according to https://www.w3.org/TR/css-values-3/#calc-syntax
static OwnPtr<CalculatedStyleValue::CalcSum> parse_calc_sum(ParsingContext const&, TokenStream<StyleComponentValueRule>&);

View file

@ -336,56 +336,6 @@ static inline bool is_line_width(StyleValue const& value)
}
}
static inline bool is_list_style_image(StyleValue const& value)
{
if (value.is_builtin_or_dynamic())
return true;
if (value.is_image())
return true;
if (value.is_identifier() && value.to_identifier() == ValueID::None)
return true;
return false;
}
static inline bool is_list_style_position(StyleValue const& value)
{
if (value.is_builtin_or_dynamic())
return true;
switch (value.to_identifier()) {
case ValueID::Inside:
case ValueID::Outside:
return true;
default:
return false;
}
}
static inline bool is_list_style_type(StyleValue const& value)
{
if (value.is_builtin_or_dynamic())
return true;
// FIXME: Handle strings and symbols("...") syntax
switch (value.to_identifier()) {
case CSS::ValueID::None:
case CSS::ValueID::Disc:
case CSS::ValueID::Circle:
case CSS::ValueID::Square:
case CSS::ValueID::Decimal:
case CSS::ValueID::DecimalLeadingZero:
case CSS::ValueID::LowerAlpha:
case CSS::ValueID::LowerLatin:
case CSS::ValueID::UpperAlpha:
case CSS::ValueID::UpperLatin:
case CSS::ValueID::UpperRoman:
case CSS::ValueID::LowerRoman:
return true;
default:
return true;
}
}
static inline bool is_text_decoration_line(StyleValue const& value)
{
if (value.is_builtin_or_dynamic())
@ -868,59 +818,19 @@ static void set_property_expanding_shorthands(StyleProperties& style, CSS::Prope
}
if (property_id == CSS::PropertyID::ListStyle) {
if (value.is_component_value_list()) {
auto parts = static_cast<CSS::ValueListStyleValue const&>(value).values();
if (!parts.is_empty() && parts.size() <= 3) {
RefPtr<StyleValue> position_value;
RefPtr<StyleValue> image_value;
RefPtr<StyleValue> type_value;
// FIXME: `none` is ambiguous as it is a valid value for ListStyleImage and ListStyleType,
// so requires special handling. https://www.w3.org/TR/css-lists-3/#propdef-list-style
for (auto& part : parts) {
auto value = Parser::parse_css_value(context, property_id, part);
if (!value)
return;
if (is_list_style_position(*value)) {
if (position_value)
return;
position_value = move(value);
continue;
}
if (is_list_style_image(*value)) {
if (image_value)
return;
image_value = move(value);
continue;
}
if (is_list_style_type(*value)) {
if (type_value)
return;
type_value = move(value);
continue;
}
}
if (position_value)
style.set_property(CSS::PropertyID::ListStylePosition, *position_value);
if (image_value)
style.set_property(CSS::PropertyID::ListStyleImage, *image_value);
if (type_value)
style.set_property(CSS::PropertyID::ListStyleType, *type_value);
}
if (value.is_list_style()) {
auto& list_style = static_cast<CSS::ListStyleStyleValue const&>(value);
style.set_property(CSS::PropertyID::ListStylePosition, list_style.position());
style.set_property(CSS::PropertyID::ListStyleImage, list_style.image());
style.set_property(CSS::PropertyID::ListStyleType, list_style.style_type());
return;
}
if (is_list_style_position(value))
if (value.is_builtin()) {
style.set_property(CSS::PropertyID::ListStylePosition, value);
else if (is_list_style_image(value))
style.set_property(CSS::PropertyID::ListStyleImage, value);
else if (is_list_style_type(value))
style.set_property(CSS::PropertyID::ListStyleType, value);
return;
}
return;
}

View file

@ -232,6 +232,7 @@ public:
Background,
BoxShadow,
Font,
ListStyle,
};
Type type() const { return m_type; }
@ -251,6 +252,7 @@ public:
bool is_background() const { return type() == Type::Background; }
bool is_box_shadow() const { return type() == Type::BoxShadow; }
bool is_font() const { return type() == Type::Font; }
bool is_list_style() const { return type() == Type::ListStyle; }
bool is_builtin() const { return is_inherit() || is_initial(); }
@ -717,6 +719,43 @@ private:
// FIXME: Implement font-stretch and font-variant.
};
class ListStyleStyleValue final : public StyleValue {
public:
static NonnullRefPtr<ListStyleStyleValue> create(
NonnullRefPtr<StyleValue> position,
NonnullRefPtr<StyleValue> image,
NonnullRefPtr<StyleValue> style_type)
{
return adopt_ref(*new ListStyleStyleValue(position, image, style_type));
}
virtual ~ListStyleStyleValue() override { }
NonnullRefPtr<StyleValue> position() const { return m_position; }
NonnullRefPtr<StyleValue> image() const { return m_image; }
NonnullRefPtr<StyleValue> style_type() const { return m_style_type; }
virtual String to_string() const override
{
return String::formatted("ListStyle position: {}, image: {}, style_type: {}", m_position->to_string(), m_image->to_string(), m_style_type->to_string());
}
private:
ListStyleStyleValue(
NonnullRefPtr<StyleValue> position,
NonnullRefPtr<StyleValue> image,
NonnullRefPtr<StyleValue> style_type)
: StyleValue(Type::ListStyle)
, m_position(position)
, m_image(image)
, m_style_type(style_type)
{
}
NonnullRefPtr<StyleValue> m_position;
NonnullRefPtr<StyleValue> m_image;
NonnullRefPtr<StyleValue> m_style_type;
};
class StyleValueList final : public StyleValue {
public:
static NonnullRefPtr<StyleValueList> create(NonnullRefPtrVector<StyleValue>&& values) { return adopt_ref(*new StyleValueList(move(values))); }