[Conic Gradients] Add support for parsing conic gradients
authorweinig@apple.com <weinig@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 29 Oct 2017 23:07:45 +0000 (23:07 +0000)
committerweinig@apple.com <weinig@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 29 Oct 2017 23:07:45 +0000 (23:07 +0000)
https://bugs.webkit.org/show_bug.cgi?id=178987

Reviewed by Dean Jackson.

Source/WebCore:

Adds initial support, disabled by default, for parsing conic gradients as per
CSS 4 Images - https://www.w3.org/TR/css-images-4/#conic-gradients.

Test: fast/gradients/conic-gradient-parsing.html

* css/CSSGradientValue.cpp:
(WebCore::clone):
(WebCore::CSSGradientValue::isCacheable const):
(WebCore::CSSConicGradientValue::customCSSText const):
(WebCore::CSSConicGradientValue::createGradient):
(WebCore::CSSConicGradientValue::equals const):
* css/CSSGradientValue.h:

    Add CSSConicGradientValue as a subclass of CSSGradientValue and implement
    customCSSText() and equals(). Stub out createGradient() as painting is not
    yet implemented.

* css/CSSImageGeneratorValue.cpp:
(WebCore::CSSImageGeneratorValue::image):
(WebCore::CSSImageGeneratorValue::isFixedSize const):
(WebCore::CSSImageGeneratorValue::fixedSize):
(WebCore::CSSImageGeneratorValue::isPending const):
(WebCore::CSSImageGeneratorValue::knownToBeOpaque const):
(WebCore::CSSImageGeneratorValue::loadSubimages):
* css/CSSValue.cpp:
(WebCore::CSSValue::equals const):
(WebCore::CSSValue::cssText const):
(WebCore::CSSValue::destroy):

    Dispatch to CSSConicGradientValue as needed.

* css/CSSValue.h:
(WebCore::CSSValue::isImageGeneratorValue const):
(WebCore::CSSValue::isGradientValue const):
(WebCore::CSSValue::isConicGradientValue const):

    Add conic gradient predicate support and update isImageGeneratorValue and
    isGradientValue to include conic gradient.

* css/CSSValueKeywords.in:

    Add conic-gradient and repeating-conic-gradient.

* css/parser/CSSParser.cpp:
(WebCore::CSSParserContext::CSSParserContext):
(WebCore::operator==):
* css/parser/CSSParserMode.h:
(WebCore::CSSParserContextHash::hash):

    Add runtime flags to enable conic gradients.

* css/parser/CSSPropertyParserHelpers.cpp:
(WebCore::CSSPropertyParserHelpers::consumeAngleOrPercent):

    Helper, similar to consumeLengthOrPercent, for consumeGradientColorStops.
    Corresponds to https://drafts.csswg.org/css-values-4/#typedef-angle-percentage

(WebCore::CSSPropertyParserHelpers::consumeGradientColorStops):

    Convert to take CSSGradientValue by reference.

(WebCore::CSSPropertyParserHelpers::consumeAngularGradientColorStops):

    Helper, similar to consumeGradientColorStops, but for angular color stops
    used in conic gradients. Corresponds to https://www.w3.org/TR/css-images-4/#typedef-angular-color-stop-list
    but does not yet support double position syntax.

(WebCore::CSSPropertyParserHelpers::consumeDeprecatedRadialGradient):
(WebCore::CSSPropertyParserHelpers::consumeRadialGradient):
(WebCore::CSSPropertyParserHelpers::consumeLinearGradient):

    Pass CSSGradientValue by reference.

(WebCore::CSSPropertyParserHelpers::consumeConicGradient):

    Parse conic gradient.

(WebCore::CSSPropertyParserHelpers::consumeGeneratedImage):

    Dispatch to consumeConicGradient for repeating and non-repeating
    conic gradients.

(WebCore::CSSPropertyParserHelpers::isGeneratedImage):

    Put each value on its own line to make it more readable and add CSSValueConicGradient
    and CSSValueRepeatingConicGradient.

* page/Settings.yaml:

    Add a setting to enable conic gradients. Disabled by default.

* features.json:

    Move conic gradients to "In Development".

LayoutTests:

* http/wpt/css: Added.
* http/wpt/css/css-images-4: Added.
* http/wpt/css/css-images-4/conic-gradient-parsing-expected.txt: Added.
* http/wpt/css/css-images-4/conic-gradient-parsing.html: Added.

    Add tests for basic parsing of conic gradients.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@224165 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
LayoutTests/ChangeLog
LayoutTests/http/wpt/css/css-images-4/conic-gradient-parsing-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/css/css-images-4/conic-gradient-parsing.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/css/CSSGradientValue.cpp
Source/WebCore/css/CSSGradientValue.h
Source/WebCore/css/CSSImageGeneratorValue.cpp
Source/WebCore/css/CSSValue.cpp
Source/WebCore/css/CSSValue.h
Source/WebCore/css/CSSValueKeywords.in
Source/WebCore/css/parser/CSSParser.cpp
Source/WebCore/css/parser/CSSParserMode.h
Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp
Source/WebCore/features.json
Source/WebCore/page/Settings.yaml

index 1f2e0e4..bd010e4 100644 (file)
@@ -1,3 +1,17 @@
+2017-10-29  Sam Weinig  <sam@webkit.org>
+
+        [Conic Gradients] Add support for parsing conic gradients
+        https://bugs.webkit.org/show_bug.cgi?id=178987
+
+        Reviewed by Dean Jackson.
+
+        * http/wpt/css: Added.
+        * http/wpt/css/css-images-4: Added.
+        * http/wpt/css/css-images-4/conic-gradient-parsing-expected.txt: Added.
+        * http/wpt/css/css-images-4/conic-gradient-parsing.html: Added.
+
+            Add tests for basic parsing of conic gradients.
+
 2017-10-29  Antoine Quint  <graouts@apple.com>
 
         [Web Animations] Expose the currentTime property on Animation
diff --git a/LayoutTests/http/wpt/css/css-images-4/conic-gradient-parsing-expected.txt b/LayoutTests/http/wpt/css/css-images-4/conic-gradient-parsing-expected.txt
new file mode 100644 (file)
index 0000000..cc0a39c
--- /dev/null
@@ -0,0 +1,4 @@
+
+PASS Test conic-gradient parsing 
+PASS Test repeating-conic-gradient parsing 
+
diff --git a/LayoutTests/http/wpt/css/css-images-4/conic-gradient-parsing.html b/LayoutTests/http/wpt/css/css-images-4/conic-gradient-parsing.html
new file mode 100644 (file)
index 0000000..6cf7e2c
--- /dev/null
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+
+if (window.internals)
+    internals.settings.setConicGradientsEnabled(true);
+
+function roundTrip(css)
+{
+    var div = document.createElement('div');
+    div.setAttribute('style', css);
+    document.body.appendChild(div);
+
+    var result = getComputedStyle(div).backgroundImage;
+    document.body.removeChild(div);
+    return result;
+}
+
+function testGradient(actual, expected)
+{
+    if (expected === undefined)
+        expected = actual;
+
+    assert_equals(roundTrip("background-image:" + actual), expected, "Testing round trip of " + actual);        
+}
+
+// FIXME Test invalid parameters.
+// FIXME Test double position syntax for color stops.
+
+test(function() {
+    testGradient("conic-gradient(red, gold)")
+    testGradient("conic-gradient(from 0deg, red, gold)")
+    testGradient("conic-gradient(at center, red, gold)", "conic-gradient(at center center, red, gold)")
+    testGradient("conic-gradient(at 25% 75%, red, gold)")
+    testGradient("conic-gradient(from 0deg at 50% 50%, red, gold)")
+    testGradient("conic-gradient(red 0%, gold 100%)")
+    testGradient("conic-gradient(red 0deg, gold 1turn)");
+    testGradient("conic-gradient(white -50%, black 150%)");
+    testGradient("conic-gradient(white -180deg, black 540deg)");
+    testGradient("conic-gradient(hsl(0,0%,75%), hsl(0,0%,25%))", "conic-gradient(rgb(191, 191, 191), rgb(63, 63, 63))");
+    testGradient("conic-gradient(from 45deg, white, black, white)");
+    testGradient("conic-gradient(hsl(0,0%,87.5%), white 45deg, black 225deg, hsl(0,0%,87.5%))", "conic-gradient(rgb(223, 223, 223), white 45deg, black 225deg, rgb(223, 223, 223))");
+
+    var s = "conic-gradient(";
+    for (var x = 0; x < 500; x++)
+         s += `white ${x/500}%, ${(2 * x + 1) / 1000}%, `;
+    s += "black)";
+    testGradient(s);
+}, "Test conic-gradient parsing");
+
+test(function() {
+    testGradient("repeating-conic-gradient(red, gold)");
+    testGradient("repeating-conic-gradient(from 0deg at center, red, gold)", "repeating-conic-gradient(from 0deg at center center, red, gold)");
+}, "Test repeating-conic-gradient parsing");
+
+</script>
+</body>
+</html>
index b1d5068..db69e41 100644 (file)
@@ -1,3 +1,105 @@
+2017-10-29  Sam Weinig  <sam@webkit.org>
+
+        [Conic Gradients] Add support for parsing conic gradients
+        https://bugs.webkit.org/show_bug.cgi?id=178987
+
+        Reviewed by Dean Jackson.
+
+        Adds initial support, disabled by default, for parsing conic gradients as per
+        CSS 4 Images - https://www.w3.org/TR/css-images-4/#conic-gradients.
+
+        Test: fast/gradients/conic-gradient-parsing.html
+
+        * css/CSSGradientValue.cpp:
+        (WebCore::clone):
+        (WebCore::CSSGradientValue::isCacheable const):
+        (WebCore::CSSConicGradientValue::customCSSText const):
+        (WebCore::CSSConicGradientValue::createGradient):
+        (WebCore::CSSConicGradientValue::equals const):
+        * css/CSSGradientValue.h:
+        
+            Add CSSConicGradientValue as a subclass of CSSGradientValue and implement
+            customCSSText() and equals(). Stub out createGradient() as painting is not
+            yet implemented.
+        
+        * css/CSSImageGeneratorValue.cpp:
+        (WebCore::CSSImageGeneratorValue::image):
+        (WebCore::CSSImageGeneratorValue::isFixedSize const):
+        (WebCore::CSSImageGeneratorValue::fixedSize):
+        (WebCore::CSSImageGeneratorValue::isPending const):
+        (WebCore::CSSImageGeneratorValue::knownToBeOpaque const):
+        (WebCore::CSSImageGeneratorValue::loadSubimages):
+        * css/CSSValue.cpp:
+        (WebCore::CSSValue::equals const):
+        (WebCore::CSSValue::cssText const):
+        (WebCore::CSSValue::destroy):
+        
+            Dispatch to CSSConicGradientValue as needed.
+        
+        * css/CSSValue.h:
+        (WebCore::CSSValue::isImageGeneratorValue const):
+        (WebCore::CSSValue::isGradientValue const):
+        (WebCore::CSSValue::isConicGradientValue const):
+        
+            Add conic gradient predicate support and update isImageGeneratorValue and 
+            isGradientValue to include conic gradient.
+        
+        * css/CSSValueKeywords.in:
+        
+            Add conic-gradient and repeating-conic-gradient.
+        
+        * css/parser/CSSParser.cpp:
+        (WebCore::CSSParserContext::CSSParserContext):
+        (WebCore::operator==):
+        * css/parser/CSSParserMode.h:
+        (WebCore::CSSParserContextHash::hash):
+        
+            Add runtime flags to enable conic gradients.
+        
+        * css/parser/CSSPropertyParserHelpers.cpp:
+        (WebCore::CSSPropertyParserHelpers::consumeAngleOrPercent):
+        
+            Helper, similar to consumeLengthOrPercent, for consumeGradientColorStops. 
+            Corresponds to https://drafts.csswg.org/css-values-4/#typedef-angle-percentage
+        
+        (WebCore::CSSPropertyParserHelpers::consumeGradientColorStops):
+        
+            Convert to take CSSGradientValue by reference.
+        
+        (WebCore::CSSPropertyParserHelpers::consumeAngularGradientColorStops):
+        
+            Helper, similar to consumeGradientColorStops, but for angular color stops
+            used in conic gradients. Corresponds to https://www.w3.org/TR/css-images-4/#typedef-angular-color-stop-list
+            but does not yet support double position syntax.
+        
+        (WebCore::CSSPropertyParserHelpers::consumeDeprecatedRadialGradient):
+        (WebCore::CSSPropertyParserHelpers::consumeRadialGradient):
+        (WebCore::CSSPropertyParserHelpers::consumeLinearGradient):
+        
+            Pass CSSGradientValue by reference.
+        
+        (WebCore::CSSPropertyParserHelpers::consumeConicGradient):
+        
+            Parse conic gradient.
+        
+        (WebCore::CSSPropertyParserHelpers::consumeGeneratedImage):
+        
+            Dispatch to consumeConicGradient for repeating and non-repeating
+            conic gradients.
+        
+        (WebCore::CSSPropertyParserHelpers::isGeneratedImage):
+        
+            Put each value on its own line to make it more readable and add CSSValueConicGradient
+            and CSSValueRepeatingConicGradient.
+        
+        * page/Settings.yaml:
+        
+            Add a setting to enable conic gradients. Disabled by default.
+
+        * features.json:
+        
+            Move conic gradients to "In Development".
+
 2017-10-29  Antoine Quint  <graouts@apple.com>
 
         [Web Animations] Expose the currentTime property on Animation
index 95b2047..a800c20 100644 (file)
@@ -95,8 +95,10 @@ static inline Ref<CSSGradientValue> clone(CSSGradientValue& value)
 {
     if (is<CSSLinearGradientValue>(value))
         return downcast<CSSLinearGradientValue>(value).clone();
-    ASSERT(is<CSSRadialGradientValue>(value));
-    return downcast<CSSRadialGradientValue>(value).clone();
+    if (is<CSSRadialGradientValue>(value))
+        return downcast<CSSRadialGradientValue>(value).clone();
+    ASSERT(is<CSSConicGradientValue>(value));
+    return downcast<CSSConicGradientValue>(value).clone();
 }
 
 Ref<CSSGradientValue> CSSGradientValue::gradientWithStylesResolved(const StyleResolver& styleResolver)
@@ -544,9 +546,7 @@ FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* horizontal, CSSP
 
 bool CSSGradientValue::isCacheable() const
 {
-    for (size_t i = 0; i < m_stops.size(); ++i) {
-        const CSSGradientColorStop& stop = m_stops[i];
-
+    for (auto& stop : m_stops) {
         if (stop.m_colorIsDerivedFromElement)
             return false;
 
@@ -1274,4 +1274,80 @@ bool CSSRadialGradientValue::equals(const CSSRadialGradientValue& other) const
     return equalShape && equalSizingBehavior && equalHorizontalAndVerticalSize && m_stops == other.m_stops;
 }
 
+
+String CSSConicGradientValue::customCSSText() const
+{
+    StringBuilder result;
+
+    if (m_repeating)
+        result.appendLiteral("repeating-conic-gradient(");
+    else
+        result.appendLiteral("conic-gradient(");
+
+    bool wroteSomething = false;
+
+    if (m_angle) {
+        result.appendLiteral("from ");
+        result.append(m_angle->cssText());
+        wroteSomething = true;
+    }
+
+    if (m_firstX && m_firstY) {
+        if (wroteSomething)
+            result.appendLiteral(" ");
+        result.appendLiteral("at ");
+        result.append(m_firstX->cssText());
+        result.append(' ');
+        result.append(m_firstY->cssText());
+        wroteSomething = true;
+    }
+
+    if (wroteSomething)
+        result.appendLiteral(", ");
+
+    bool wroteFirstStop = false;
+    for (auto& stop : m_stops) {
+        if (wroteFirstStop)
+            result.appendLiteral(", ");
+        wroteFirstStop = true;
+        if (!stop.isMidpoint)
+            result.append(stop.m_color->cssText());
+        if (stop.m_position) {
+            if (!stop.isMidpoint)
+                result.append(' ');
+            result.append(stop.m_position->cssText());
+        }
+    }
+    
+    result.append(')');
+    return result.toString();
+}
+
+Ref<Gradient> CSSConicGradientValue::createGradient(RenderElement&, const FloatSize&)
+{
+    // FIXME: Implement.
+    return Gradient::create(FloatPoint { }, FloatPoint { });
+}
+
+bool CSSConicGradientValue::equals(const CSSConicGradientValue& other) const
+{
+    if (m_repeating != other.m_repeating)
+        return false;
+
+    if (!compareCSSValuePtr(m_angle, other.m_angle))
+        return false;
+
+    bool equalXandY = false;
+    if (m_firstX && m_firstY)
+        equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
+    else if (m_firstX)
+        equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
+    else if (m_firstY)
+        equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
+    else
+        equalXandY = !other.m_firstX && !other.m_firstY;
+
+    return equalXandY && m_stops == other.m_stops;
+}
+
 } // namespace WebCore
index a8e66c4..9f66aec 100644 (file)
@@ -41,7 +41,8 @@ enum CSSGradientType {
     CSSPrefixedLinearGradient,
     CSSPrefixedRadialGradient,
     CSSLinearGradient,
-    CSSRadialGradient
+    CSSRadialGradient,
+    CSSConicGradient
 };
 enum CSSGradientRepeat { NonRepeating, Repeating };
 
@@ -225,8 +226,45 @@ private:
     RefPtr<CSSPrimitiveValue> m_endVerticalSize;
 };
 
+class CSSConicGradientValue final : public CSSGradientValue {
+public:
+    static Ref<CSSConicGradientValue> create(CSSGradientRepeat repeat)
+    {
+        return adoptRef(*new CSSConicGradientValue(repeat));
+    }
+
+    Ref<CSSConicGradientValue> clone() const
+    {
+        return adoptRef(*new CSSConicGradientValue(*this));
+    }
+
+    String customCSSText() const;
+
+    void setAngle(RefPtr<CSSPrimitiveValue>&& val) { m_angle = WTFMove(val); }
+
+    // Create the gradient for a given size.
+    Ref<Gradient> createGradient(RenderElement&, const FloatSize&);
+
+    bool equals(const CSSConicGradientValue&) const;
+
+private:
+    CSSConicGradientValue(CSSGradientRepeat repeat)
+        : CSSGradientValue(ConicGradientClass, repeat, CSSConicGradient)
+    {
+    }
+
+    CSSConicGradientValue(const CSSConicGradientValue& other)
+        : CSSGradientValue(other, ConicGradientClass, other.gradientType())
+        , m_angle(other.m_angle)
+    {
+    }
+
+    RefPtr<CSSPrimitiveValue> m_angle; // may be null.
+};
+
 } // namespace WebCore
 
 SPECIALIZE_TYPE_TRAITS_CSS_VALUE(CSSGradientValue, isGradientValue())
 SPECIALIZE_TYPE_TRAITS_CSS_VALUE(CSSLinearGradientValue, isLinearGradientValue())
 SPECIALIZE_TYPE_TRAITS_CSS_VALUE(CSSRadialGradientValue, isRadialGradientValue())
+SPECIALIZE_TYPE_TRAITS_CSS_VALUE(CSSConicGradientValue, isConicGradientValue())
index e20aa2f..a7e664c 100644 (file)
@@ -148,6 +148,8 @@ RefPtr<Image> CSSImageGeneratorValue::image(RenderElement& renderer, const Float
         return downcast<CSSLinearGradientValue>(*this).image(renderer, size);
     case RadialGradientClass:
         return downcast<CSSRadialGradientValue>(*this).image(renderer, size);
+    case ConicGradientClass:
+        return downcast<CSSConicGradientValue>(*this).image(renderer, size);
     default:
         ASSERT_NOT_REACHED();
     }
@@ -169,6 +171,8 @@ bool CSSImageGeneratorValue::isFixedSize() const
         return downcast<CSSLinearGradientValue>(*this).isFixedSize();
     case RadialGradientClass:
         return downcast<CSSRadialGradientValue>(*this).isFixedSize();
+    case ConicGradientClass:
+        return downcast<CSSConicGradientValue>(*this).isFixedSize();
     default:
         ASSERT_NOT_REACHED();
     }
@@ -188,6 +192,8 @@ FloatSize CSSImageGeneratorValue::fixedSize(const RenderElement& renderer)
         return downcast<CSSLinearGradientValue>(*this).fixedSize(renderer);
     case RadialGradientClass:
         return downcast<CSSRadialGradientValue>(*this).fixedSize(renderer);
+    case ConicGradientClass:
+        return downcast<CSSConicGradientValue>(*this).fixedSize(renderer);
     default:
         ASSERT_NOT_REACHED();
     }
@@ -209,6 +215,8 @@ bool CSSImageGeneratorValue::isPending() const
         return downcast<CSSLinearGradientValue>(*this).isPending();
     case RadialGradientClass:
         return downcast<CSSRadialGradientValue>(*this).isPending();
+    case ConicGradientClass:
+        return downcast<CSSConicGradientValue>(*this).isPending();
     default:
         ASSERT_NOT_REACHED();
     }
@@ -230,6 +238,8 @@ bool CSSImageGeneratorValue::knownToBeOpaque(const RenderElement& renderer) cons
         return downcast<CSSLinearGradientValue>(*this).knownToBeOpaque();
     case RadialGradientClass:
         return downcast<CSSRadialGradientValue>(*this).knownToBeOpaque();
+    case ConicGradientClass:
+        return downcast<CSSConicGradientValue>(*this).knownToBeOpaque();
     default:
         ASSERT_NOT_REACHED();
     }
@@ -254,6 +264,9 @@ void CSSImageGeneratorValue::loadSubimages(CachedResourceLoader& cachedResourceL
     case RadialGradientClass:
         downcast<CSSRadialGradientValue>(*this).loadSubimages(cachedResourceLoader, options);
         break;
+    case ConicGradientClass:
+        downcast<CSSConicGradientValue>(*this).loadSubimages(cachedResourceLoader, options);
+        break;
     default:
         ASSERT_NOT_REACHED();
     }
index d765eda..4e18f0e 100644 (file)
@@ -156,6 +156,8 @@ bool CSSValue::equals(const CSSValue& other) const
             return compareCSSValues<CSSLinearGradientValue>(*this, other);
         case RadialGradientClass:
             return compareCSSValues<CSSRadialGradientValue>(*this, other);
+        case ConicGradientClass:
+            return compareCSSValues<CSSConicGradientValue>(*this, other);
         case CrossfadeClass:
             return compareCSSValues<CSSCrossfadeValue>(*this, other);
         case ImageClass:
@@ -254,6 +256,8 @@ String CSSValue::cssText() const
         return downcast<CSSLinearGradientValue>(*this).customCSSText();
     case RadialGradientClass:
         return downcast<CSSRadialGradientValue>(*this).customCSSText();
+    case ConicGradientClass:
+        return downcast<CSSConicGradientValue>(*this).customCSSText();
     case CrossfadeClass:
         return downcast<CSSCrossfadeValue>(*this).customCSSText();
     case ImageClass:
@@ -359,6 +363,9 @@ void CSSValue::destroy()
     case RadialGradientClass:
         delete downcast<CSSRadialGradientValue>(this);
         return;
+    case ConicGradientClass:
+        delete downcast<CSSConicGradientValue>(this);
+        return;
     case CrossfadeClass:
         delete downcast<CSSCrossfadeValue>(this);
         return;
index a2fbd09..42bdd07 100644 (file)
@@ -81,8 +81,8 @@ public:
     bool isFontValue() const { return m_classType == FontClass; }
     bool isFontStyleValue() const { return m_classType == FontStyleClass; }
     bool isFontStyleRangeValue() const { return m_classType == FontStyleRangeClass; }
-    bool isImageGeneratorValue() const { return m_classType >= CanvasClass && m_classType <= RadialGradientClass; }
-    bool isGradientValue() const { return m_classType >= LinearGradientClass && m_classType <= RadialGradientClass; }
+    bool isImageGeneratorValue() const { return m_classType >= CanvasClass && m_classType <= ConicGradientClass; }
+    bool isGradientValue() const { return m_classType >= LinearGradientClass && m_classType <= ConicGradientClass; }
     bool isNamedImageValue() const { return m_classType == NamedImageClass; }
     bool isImageSetValue() const { return m_classType == ImageSetClass; }
     bool isImageValue() const { return m_classType == ImageClass; }
@@ -95,6 +95,7 @@ public:
     bool treatAsInheritedValue(CSSPropertyID) const;
     bool isLinearGradientValue() const { return m_classType == LinearGradientClass; }
     bool isRadialGradientValue() const { return m_classType == RadialGradientClass; }
+    bool isConicGradientValue() const { return m_classType == ConicGradientClass; }
     bool isReflectValue() const { return m_classType == ReflectClass; }
     bool isShadowValue() const { return m_classType == ShadowClass; }
     bool isCubicBezierTimingFunctionValue() const { return m_classType == CubicBezierTimingFunctionClass; }
@@ -143,6 +144,7 @@ protected:
         FilterImageClass,
         LinearGradientClass,
         RadialGradientClass,
+        ConicGradientClass,
 
         // Timing function classes.
         CubicBezierTimingFunctionClass,
index d9bca6b..5a49fa6 100644 (file)
@@ -1155,8 +1155,10 @@ cross-fade
 image-set
 linear-gradient
 radial-gradient
+conic-gradient
 repeating-linear-gradient
 repeating-radial-gradient
+repeating-conic-gradient
 -webkit-canvas
 -webkit-cross-fade
 -webkit-gradient
index 720b26b..843c404 100644 (file)
@@ -87,6 +87,7 @@ CSSParserContext::CSSParserContext(Document& document, const URL& baseURL, const
 #endif
     springTimingFunctionEnabled = document.settings().springTimingFunctionEnabled();
     constantPropertiesEnabled = document.settings().constantPropertiesEnabled();
+    conicGradientsEnabled = document.settings().conicGradientsEnabled();
     deferredCSSParserEnabled = document.settings().deferredCSSParserEnabled();
 
 #if PLATFORM(IOS)
@@ -109,6 +110,7 @@ bool operator==(const CSSParserContext& a, const CSSParserContext& b)
         && a.useLegacyBackgroundSizeShorthandBehavior == b.useLegacyBackgroundSizeShorthandBehavior
         && a.springTimingFunctionEnabled == b.springTimingFunctionEnabled
         && a.constantPropertiesEnabled == b.constantPropertiesEnabled
+        && a.conicGradientsEnabled == b.conicGradientsEnabled
         && a.deferredCSSParserEnabled == b.deferredCSSParserEnabled;
 }
 
index 0c0255c..fcb5794 100644 (file)
@@ -105,7 +105,7 @@ public:
     bool useLegacyBackgroundSizeShorthandBehavior { false };
     bool springTimingFunctionEnabled { false };
     bool constantPropertiesEnabled { false };
-    
+    bool conicGradientsEnabled { false };
     bool deferredCSSParserEnabled { false };
 
     URL completeURL(const String& url) const
@@ -139,8 +139,9 @@ struct CSSParserContextHash {
             & key.enforcesCSSMIMETypeInNoQuirksMode         << 5
             & key.useLegacyBackgroundSizeShorthandBehavior  << 6
             & key.springTimingFunctionEnabled               << 7
-            & key.deferredCSSParserEnabled                  << 8
-            & key.mode                                      << 9;
+            & key.conicGradientsEnabled                     << 8
+            & key.deferredCSSParserEnabled                  << 9
+            & key.mode                                      << 10;
         hash ^= WTF::intHash(bits);
         return hash;
     }
index 8c6e358..853c24a 100644 (file)
@@ -356,6 +356,34 @@ RefPtr<CSSPrimitiveValue> consumeAngle(CSSParserTokenRange& range, CSSParserMode
     return nullptr;
 }
 
+static RefPtr<CSSPrimitiveValue> consumeAngleOrPercent(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
+{
+    const CSSParserToken& token = range.peek();
+    if (token.type() == DimensionToken) {
+        switch (token.unitType()) {
+        case CSSPrimitiveValue::UnitType::CSS_DEG:
+        case CSSPrimitiveValue::UnitType::CSS_RAD:
+        case CSSPrimitiveValue::UnitType::CSS_GRAD:
+        case CSSPrimitiveValue::UnitType::CSS_TURN:
+            return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
+        default:
+            return nullptr;
+        }
+    }
+    if (token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless))
+        return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_DEG);
+    if (token.type() == PercentageToken)
+        return consumePercent(range, valueRange);
+
+    CalcParser calcParser(range, CalcAngle, valueRange);
+    if (const CSSCalcValue* calculation = calcParser.value()) {
+        if (calculation->category() == CalcAngle)
+            return calcParser.consumeValue();
+    }
+    return nullptr;
+}
+
+
 RefPtr<CSSPrimitiveValue> consumeTime(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
 {
     const CSSParserToken& token = range.peek();
@@ -904,9 +932,9 @@ static RefPtr<CSSValue> consumeDeprecatedGradient(CSSParserTokenRange& args, CSS
     return result;
 }
 
-static bool consumeGradientColorStops(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSGradientValue* gradient)
+static bool consumeGradientColorStops(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSGradientValue& gradient)
 {
-    bool supportsColorHints = gradient->gradientType() == CSSLinearGradient || gradient->gradientType() == CSSRadialGradient;
+    bool supportsColorHints = gradient.gradientType() == CSSLinearGradient || gradient.gradientType() == CSSRadialGradient;
 
     // The first color stop cannot be a color hint.
     bool previousStopWasColorHint = true;
@@ -925,7 +953,40 @@ static bool consumeGradientColorStops(CSSParserTokenRange& range, CSSParserMode
         stop.m_position = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll);
         if (!stop.m_color && !stop.m_position)
             return false;
-        gradient->addStop(stop);
+        gradient.addStop(stop);
+    } while (consumeCommaIncludingWhitespace(range));
+
+    // The last color stop cannot be a color hint.
+    if (previousStopWasColorHint)
+        return false;
+
+    // Must have 2 or more stops to be valid.
+    return gradient.stopCount() >= 2;
+}
+
+// https://www.w3.org/TR/css-images-4/#typedef-angular-color-stop-list
+// FIXME: This should support up to two position hints per color stop.
+static bool consumeAngularGradientColorStops(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSGradientValue& gradient)
+{
+    // The first color stop cannot be a color hint.
+    bool previousStopWasColorHint = true;
+    do {
+        CSSGradientColorStop stop;
+        stop.m_color = consumeColor(range, cssParserMode);
+
+        // Two hints in a row are not allowed.
+        if (!stop.m_color && previousStopWasColorHint)
+            return false;
+        
+        previousStopWasColorHint = !stop.m_color;
+        
+        // FIXME-NEWPARSER: This boolean could be removed. Null checking color would be sufficient.
+        stop.isMidpoint = !stop.m_color;
+
+        stop.m_position = consumeAngleOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Forbid);
+        if (!stop.m_color && !stop.m_position)
+            return false;
+        gradient.addStop(stop);
     } while (consumeCommaIncludingWhitespace(range));
 
     // The last color stop cannot be a color hint.
@@ -933,7 +994,7 @@ static bool consumeGradientColorStops(CSSParserTokenRange& range, CSSParserMode
         return false;
 
     // Must have 2 or more stops to be valid.
-    return gradient->stopCount() >= 2;
+    return gradient.stopCount() >= 2;
 }
 
 static RefPtr<CSSValue> consumeDeprecatedRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
@@ -972,7 +1033,7 @@ static RefPtr<CSSValue> consumeDeprecatedRadialGradient(CSSParserTokenRange& arg
     } else {
         consumeCommaIncludingWhitespace(args);
     }
-    if (!consumeGradientColorStops(args, cssParserMode, result.get()))
+    if (!consumeGradientColorStops(args, cssParserMode, *result))
         return nullptr;
 
     return result;
@@ -1059,7 +1120,7 @@ static RefPtr<CSSValue> consumeRadialGradient(CSSParserTokenRange& args, CSSPars
 
     if ((shape || sizeKeyword || horizontalSize || centerX || centerY) && !consumeCommaIncludingWhitespace(args))
         return nullptr;
-    if (!consumeGradientColorStops(args, cssParserMode, result.get()))
+    if (!consumeGradientColorStops(args, cssParserMode, *result))
         return nullptr;
     return result;
 }
@@ -1092,7 +1153,49 @@ static RefPtr<CSSValue> consumeLinearGradient(CSSParserTokenRange& args, CSSPars
 
     if (expectComma && !consumeCommaIncludingWhitespace(args))
         return nullptr;
-    if (!consumeGradientColorStops(args, cssParserMode, result.get()))
+    if (!consumeGradientColorStops(args, cssParserMode, *result))
+        return nullptr;
+    return result;
+}
+
+static RefPtr<CSSValue> consumeConicGradient(CSSParserTokenRange& args, CSSParserContext context, CSSGradientRepeat repeating)
+{
+    if (!context.conicGradientsEnabled)
+        return nullptr;
+
+    RefPtr<CSSConicGradientValue> result = CSSConicGradientValue::create(repeating);
+
+    bool expectComma = false;
+    if (args.peek().type() == IdentToken) {
+        if (consumeIdent<CSSValueFrom>(args)) {
+            auto angle = consumeAngle(args, context.mode, UnitlessQuirk::Forbid);
+            if (!angle)
+                return nullptr;
+            result->setAngle(angle.releaseNonNull());
+            expectComma = true;
+        }
+        
+        if (consumeIdent<CSSValueAt>(args)) {
+            RefPtr<CSSPrimitiveValue> centerX;
+            RefPtr<CSSPrimitiveValue> centerY;
+            consumePosition(args, context.mode, UnitlessQuirk::Forbid, centerX, centerY);
+            if (!(centerX && centerY))
+                return nullptr;
+            
+            result->setFirstX(centerX.copyRef());
+            result->setFirstY(centerY.copyRef());
+            
+            // Right now, conic gradients have the same start and end centers.
+            result->setSecondX(centerX.copyRef());
+            result->setSecondY(centerY.copyRef());
+    
+            expectComma = true;
+        }
+    }
+
+    if (expectComma && !consumeCommaIncludingWhitespace(args))
+        return nullptr;
+    if (!consumeAngularGradientColorStops(args, context.mode, *result))
         return nullptr;
     return result;
 }
@@ -1186,6 +1289,10 @@ static RefPtr<CSSValue> consumeGeneratedImage(CSSParserTokenRange& range, CSSPar
         result = consumeDeprecatedRadialGradient(args, context.mode, NonRepeating);
     else if (id == CSSValueWebkitRepeatingRadialGradient)
         result = consumeDeprecatedRadialGradient(args, context.mode, Repeating);
+    else if (id == CSSValueConicGradient)
+        result = consumeConicGradient(args, context, NonRepeating);
+    else if (id == CSSValueRepeatingConicGradient)
+        result = consumeConicGradient(args, context, Repeating);
     else if (id == CSSValueWebkitCrossFade || id == CSSValueCrossFade)
         result = consumeCrossFade(args, context, id == CSSValueWebkitCrossFade);
     else if (id == CSSValueWebkitCanvas)
@@ -1232,12 +1339,23 @@ static RefPtr<CSSValue> consumeImageSet(CSSParserTokenRange& range, const CSSPar
 
 static bool isGeneratedImage(CSSValueID id)
 {
-    return id == CSSValueLinearGradient || id == CSSValueRadialGradient
-        || id == CSSValueRepeatingLinearGradient || id == CSSValueRepeatingRadialGradient
-        || id == CSSValueWebkitLinearGradient || id == CSSValueWebkitRadialGradient
-        || id == CSSValueWebkitRepeatingLinearGradient || id == CSSValueWebkitRepeatingRadialGradient
-        || id == CSSValueWebkitGradient || id == CSSValueWebkitCrossFade || id == CSSValueWebkitCanvas
-        || id == CSSValueCrossFade || id == CSSValueWebkitNamedImage || id == CSSValueWebkitFilter || id == CSSValueFilter;
+    return id == CSSValueLinearGradient
+        || id == CSSValueRadialGradient
+        || id == CSSValueConicGradient
+        || id == CSSValueRepeatingLinearGradient
+        || id == CSSValueRepeatingRadialGradient
+        || id == CSSValueRepeatingConicGradient
+        || id == CSSValueWebkitLinearGradient
+        || id == CSSValueWebkitRadialGradient
+        || id == CSSValueWebkitRepeatingLinearGradient
+        || id == CSSValueWebkitRepeatingRadialGradient
+        || id == CSSValueWebkitGradient
+        || id == CSSValueWebkitCrossFade
+        || id == CSSValueWebkitCanvas
+        || id == CSSValueCrossFade
+        || id == CSSValueWebkitNamedImage
+        || id == CSSValueWebkitFilter
+        || id == CSSValueFilter;
 }
     
 static bool isValidPrimitiveFilterFunction(CSSValueID filterFunction)
index fa98875..fca133a 100644 (file)
     {
         "name": "Conic Gradients",
         "status": {
-            "status": "Under Consideration"
+            "status": "In Development"
         },
         "url": "https://www.w3.org/TR/css4-images/#conic-gradients",
         "specification": "CSS Image Values and Replaced Content Module Level 4",
index 55cbb68..d07a76c 100644 (file)
@@ -509,6 +509,8 @@ shouldConvertInvalidURLsToBlank:
 
 springTimingFunctionEnabled:
   initial: false
+conicGradientsEnabled:
+  initial: false
 
 treatIPAddressAsDomain:
   initial: false