Selaa lähdekoodia

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.
Sam Atkins 4 vuotta sitten
vanhempi
commit
0e15561df0

BIN
Base/res/html/misc/custom-list-item.png


BIN
Base/res/html/misc/custome-list-item-2.png


+ 84 - 0
Base/res/html/misc/lists.html

@@ -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>

+ 114 - 0
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -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;
     }

+ 1 - 0
Userland/Libraries/LibWeb/CSS/Parser/Parser.h

@@ -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>&);

+ 8 - 98
Userland/Libraries/LibWeb/CSS/StyleResolver.cpp

@@ -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;
     }
 

+ 39 - 0
Userland/Libraries/LibWeb/CSS/StyleValue.h

@@ -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))); }