Home | History | Annotate | Download | only in css
      1 /*
      2  * Copyright (C) 2008 Apple Inc.  All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "core/css/CSSGradientValue.h"
     28 
     29 #include "core/CSSValueKeywords.h"
     30 #include "core/css/CSSCalculationValue.h"
     31 #include "core/css/CSSToLengthConversionData.h"
     32 #include "core/css/Pair.h"
     33 #include "core/dom/NodeRenderStyle.h"
     34 #include "core/dom/TextLinkColors.h"
     35 #include "core/rendering/RenderObject.h"
     36 #include "platform/geometry/IntSize.h"
     37 #include "platform/graphics/Gradient.h"
     38 #include "platform/graphics/GradientGeneratedImage.h"
     39 #include "platform/graphics/Image.h"
     40 #include "wtf/text/StringBuilder.h"
     41 #include "wtf/text/WTFString.h"
     42 
     43 namespace blink {
     44 
     45 void CSSGradientColorStop::trace(Visitor* visitor)
     46 {
     47     visitor->trace(m_position);
     48     visitor->trace(m_color);
     49 }
     50 
     51 PassRefPtr<Image> CSSGradientValue::image(RenderObject* renderer, const IntSize& size)
     52 {
     53     if (size.isEmpty())
     54         return nullptr;
     55 
     56     bool cacheable = isCacheable();
     57     if (cacheable) {
     58         if (!clients().contains(renderer))
     59             return nullptr;
     60 
     61         // Need to look up our size.  Create a string of width*height to use as a hash key.
     62         Image* result = getImage(renderer, size);
     63         if (result)
     64             return result;
     65     }
     66 
     67     // We need to create an image.
     68     RefPtr<Gradient> gradient;
     69 
     70     RenderStyle* rootStyle = renderer->document().documentElement()->renderStyle();
     71     CSSToLengthConversionData conversionData(renderer->style(), rootStyle, renderer->view());
     72     if (isLinearGradientValue())
     73         gradient = toCSSLinearGradientValue(this)->createGradient(conversionData, size);
     74     else
     75         gradient = toCSSRadialGradientValue(this)->createGradient(conversionData, size);
     76 
     77     RefPtr<Image> newImage = GradientGeneratedImage::create(gradient, size);
     78     if (cacheable)
     79         putImage(size, newImage);
     80 
     81     return newImage.release();
     82 }
     83 
     84 // Should only ever be called for deprecated gradients.
     85 static inline bool compareStops(const CSSGradientColorStop& a, const CSSGradientColorStop& b)
     86 {
     87     double aVal = a.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
     88     double bVal = b.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
     89 
     90     return aVal < bVal;
     91 }
     92 
     93 void CSSGradientValue::sortStopsIfNeeded()
     94 {
     95     ASSERT(m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient);
     96     if (!m_stopsSorted) {
     97         if (m_stops.size())
     98             std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
     99         m_stopsSorted = true;
    100     }
    101 }
    102 
    103 struct GradientStop {
    104     Color color;
    105     float offset;
    106     bool specified;
    107 
    108     GradientStop()
    109         : offset(0)
    110         , specified(false)
    111     { }
    112 };
    113 
    114 PassRefPtrWillBeRawPtr<CSSGradientValue> CSSGradientValue::gradientWithStylesResolved(const TextLinkColors& textLinkColors, Color currentColor)
    115 {
    116     bool derived = false;
    117     for (unsigned i = 0; i < m_stops.size(); i++)
    118         if (m_stops[i].m_color->colorIsDerivedFromElement()) {
    119             m_stops[i].m_colorIsDerivedFromElement = true;
    120             derived = true;
    121             break;
    122         }
    123 
    124     RefPtrWillBeRawPtr<CSSGradientValue> result = nullptr;
    125     if (!derived)
    126         result = this;
    127     else if (isLinearGradientValue())
    128         result = toCSSLinearGradientValue(this)->clone();
    129     else if (isRadialGradientValue())
    130         result = toCSSRadialGradientValue(this)->clone();
    131     else {
    132         ASSERT_NOT_REACHED();
    133         return nullptr;
    134     }
    135 
    136     for (unsigned i = 0; i < result->m_stops.size(); i++)
    137         result->m_stops[i].m_resolvedColor = textLinkColors.colorFromPrimitiveValue(result->m_stops[i].m_color.get(), currentColor);
    138 
    139     return result.release();
    140 }
    141 
    142 void CSSGradientValue::addStops(Gradient* gradient, const CSSToLengthConversionData& conversionData, float maxLengthForRepeat)
    143 {
    144     if (m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient) {
    145         sortStopsIfNeeded();
    146 
    147         for (unsigned i = 0; i < m_stops.size(); i++) {
    148             const CSSGradientColorStop& stop = m_stops[i];
    149 
    150             float offset;
    151             if (stop.m_position->isPercentage())
    152                 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
    153             else
    154                 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER);
    155 
    156             gradient->addColorStop(offset, stop.m_resolvedColor);
    157         }
    158 
    159         return;
    160     }
    161 
    162     size_t numStops = m_stops.size();
    163 
    164     Vector<GradientStop> stops(numStops);
    165 
    166     float gradientLength = 0;
    167     bool computedGradientLength = false;
    168 
    169     FloatPoint gradientStart = gradient->p0();
    170     FloatPoint gradientEnd;
    171     if (isLinearGradientValue())
    172         gradientEnd = gradient->p1();
    173     else if (isRadialGradientValue())
    174         gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0);
    175 
    176     for (size_t i = 0; i < numStops; ++i) {
    177         const CSSGradientColorStop& stop = m_stops[i];
    178 
    179         stops[i].color = stop.m_resolvedColor;
    180 
    181         if (stop.m_position) {
    182             if (stop.m_position->isPercentage())
    183                 stops[i].offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
    184             else if (stop.m_position->isLength() || stop.m_position->isCalculatedPercentageWithLength()) {
    185                 if (!computedGradientLength) {
    186                     FloatSize gradientSize(gradientStart - gradientEnd);
    187                     gradientLength = gradientSize.diagonalLength();
    188                 }
    189                 float length;
    190                 if (stop.m_position->isLength())
    191                     length = stop.m_position->computeLength<float>(conversionData);
    192                 else
    193                     length = stop.m_position->cssCalcValue()->toCalcValue(conversionData)->evaluate(gradientLength);
    194                 stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0;
    195             } else {
    196                 ASSERT_NOT_REACHED();
    197                 stops[i].offset = 0;
    198             }
    199             stops[i].specified = true;
    200         } else {
    201             // If the first color-stop does not have a position, its position defaults to 0%.
    202             // If the last color-stop does not have a position, its position defaults to 100%.
    203             if (!i) {
    204                 stops[i].offset = 0;
    205                 stops[i].specified = true;
    206             } else if (numStops > 1 && i == numStops - 1) {
    207                 stops[i].offset = 1;
    208                 stops[i].specified = true;
    209             }
    210         }
    211 
    212         // If a color-stop has a position that is less than the specified position of any
    213         // color-stop before it in the list, its position is changed to be equal to the
    214         // largest specified position of any color-stop before it.
    215         if (stops[i].specified && i > 0) {
    216             size_t prevSpecifiedIndex;
    217             for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) {
    218                 if (stops[prevSpecifiedIndex].specified)
    219                     break;
    220             }
    221 
    222             if (stops[i].offset < stops[prevSpecifiedIndex].offset)
    223                 stops[i].offset = stops[prevSpecifiedIndex].offset;
    224         }
    225     }
    226 
    227     ASSERT(stops[0].specified && stops[numStops - 1].specified);
    228 
    229     // If any color-stop still does not have a position, then, for each run of adjacent
    230     // color-stops without positions, set their positions so that they are evenly spaced
    231     // between the preceding and following color-stops with positions.
    232     if (numStops > 2) {
    233         size_t unspecifiedRunStart = 0;
    234         bool inUnspecifiedRun = false;
    235 
    236         for (size_t i = 0; i < numStops; ++i) {
    237             if (!stops[i].specified && !inUnspecifiedRun) {
    238                 unspecifiedRunStart = i;
    239                 inUnspecifiedRun = true;
    240             } else if (stops[i].specified && inUnspecifiedRun) {
    241                 size_t unspecifiedRunEnd = i;
    242 
    243                 if (unspecifiedRunStart < unspecifiedRunEnd) {
    244                     float lastSpecifiedOffset = stops[unspecifiedRunStart - 1].offset;
    245                     float nextSpecifiedOffset = stops[unspecifiedRunEnd].offset;
    246                     float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1);
    247 
    248                     for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j)
    249                         stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta;
    250                 }
    251 
    252                 inUnspecifiedRun = false;
    253             }
    254         }
    255     }
    256 
    257     // If the gradient is repeating, repeat the color stops.
    258     // We can't just push this logic down into the platform-specific Gradient code,
    259     // because we have to know the extent of the gradient, and possible move the end points.
    260     if (m_repeating && numStops > 1) {
    261         // If the difference in the positions of the first and last color-stops is 0,
    262         // the gradient defines a solid-color image with the color of the last color-stop in the rule.
    263         float gradientRange = stops[numStops - 1].offset - stops[0].offset;
    264         if (!gradientRange) {
    265             stops.first().offset = 0;
    266             stops.first().color = stops.last().color;
    267             stops.shrink(1);
    268         } else {
    269             float maxExtent = 1;
    270 
    271             // Radial gradients may need to extend further than the endpoints, because they have
    272             // to repeat out to the corners of the box.
    273             if (isRadialGradientValue()) {
    274                 if (!computedGradientLength) {
    275                     FloatSize gradientSize(gradientStart - gradientEnd);
    276                     gradientLength = gradientSize.diagonalLength();
    277                 }
    278 
    279                 if (maxLengthForRepeat > gradientLength)
    280                     maxExtent = gradientLength > 0 ? maxLengthForRepeat / gradientLength : 0;
    281             }
    282 
    283             size_t originalNumStops = numStops;
    284             size_t originalFirstStopIndex = 0;
    285 
    286             // Work backwards from the first, adding stops until we get one before 0.
    287             float firstOffset = stops[0].offset;
    288             if (firstOffset > 0) {
    289                 float currOffset = firstOffset;
    290                 size_t srcStopOrdinal = originalNumStops - 1;
    291 
    292                 while (true) {
    293                     GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal];
    294                     newStop.offset = currOffset;
    295                     stops.prepend(newStop);
    296                     ++originalFirstStopIndex;
    297                     if (currOffset < 0)
    298                         break;
    299 
    300                     if (srcStopOrdinal)
    301                         currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset;
    302                     srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops;
    303                 }
    304             }
    305 
    306             // Work forwards from the end, adding stops until we get one after 1.
    307             float lastOffset = stops[stops.size() - 1].offset;
    308             if (lastOffset < maxExtent) {
    309                 float currOffset = lastOffset;
    310                 size_t srcStopOrdinal = 0;
    311 
    312                 while (true) {
    313                     size_t srcStopIndex = originalFirstStopIndex + srcStopOrdinal;
    314                     GradientStop newStop = stops[srcStopIndex];
    315                     newStop.offset = currOffset;
    316                     stops.append(newStop);
    317                     if (currOffset > maxExtent)
    318                         break;
    319                     if (srcStopOrdinal < originalNumStops - 1)
    320                         currOffset += stops[srcStopIndex + 1].offset - stops[srcStopIndex].offset;
    321                     srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops;
    322                 }
    323             }
    324         }
    325     }
    326 
    327     numStops = stops.size();
    328 
    329     // If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops.
    330     if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) {
    331         if (isLinearGradientValue()) {
    332             float firstOffset = stops[0].offset;
    333             float lastOffset = stops[numStops - 1].offset;
    334             float scale = lastOffset - firstOffset;
    335 
    336             for (size_t i = 0; i < numStops; ++i)
    337                 stops[i].offset = (stops[i].offset - firstOffset) / scale;
    338 
    339             FloatPoint p0 = gradient->p0();
    340             FloatPoint p1 = gradient->p1();
    341             gradient->setP0(FloatPoint(p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y())));
    342             gradient->setP1(FloatPoint(p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y())));
    343         } else if (isRadialGradientValue()) {
    344             // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point.
    345             float firstOffset = 0;
    346             float lastOffset = stops[numStops - 1].offset;
    347             float scale = lastOffset - firstOffset;
    348 
    349             // Reset points below 0 to the first visible color.
    350             size_t firstZeroOrGreaterIndex = numStops;
    351             for (size_t i = 0; i < numStops; ++i) {
    352                 if (stops[i].offset >= 0) {
    353                     firstZeroOrGreaterIndex = i;
    354                     break;
    355                 }
    356             }
    357 
    358             if (firstZeroOrGreaterIndex > 0) {
    359                 if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) {
    360                     float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset;
    361                     float nextOffset = stops[firstZeroOrGreaterIndex].offset;
    362 
    363                     float interStopProportion = -prevOffset / (nextOffset - prevOffset);
    364                     // FIXME: when we interpolate gradients using premultiplied colors, this should do premultiplication.
    365                     Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion);
    366 
    367                     // Clamp the positions to 0 and set the color.
    368                     for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
    369                         stops[i].offset = 0;
    370                         stops[i].color = blendedColor;
    371                     }
    372                 } else {
    373                     // All stops are below 0; just clamp them.
    374                     for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
    375                         stops[i].offset = 0;
    376                 }
    377             }
    378 
    379             for (size_t i = 0; i < numStops; ++i)
    380                 stops[i].offset /= scale;
    381 
    382             gradient->setStartRadius(gradient->startRadius() * scale);
    383             gradient->setEndRadius(gradient->endRadius() * scale);
    384         }
    385     }
    386 
    387     for (unsigned i = 0; i < numStops; i++)
    388         gradient->addColorStop(stops[i].offset, stops[i].color);
    389 }
    390 
    391 static float positionFromValue(CSSPrimitiveValue* value, const CSSToLengthConversionData& conversionData, const IntSize& size, bool isHorizontal)
    392 {
    393     int origin = 0;
    394     int sign = 1;
    395     int edgeDistance = isHorizontal ? size.width() : size.height();
    396 
    397     // In this case the center of the gradient is given relative to an edge in the form of:
    398     // [ top | bottom | right | left ] [ <percentage> | <length> ].
    399     if (Pair* pair = value->getPairValue()) {
    400         CSSValueID originID = pair->first()->getValueID();
    401         value = pair->second();
    402 
    403         if (originID == CSSValueRight || originID == CSSValueBottom) {
    404             // For right/bottom, the offset is relative to the far edge.
    405             origin = edgeDistance;
    406             sign = -1;
    407         }
    408     }
    409 
    410     if (value->isNumber())
    411         return origin + sign * value->getFloatValue() * conversionData.zoom();
    412 
    413     if (value->isPercentage())
    414         return origin + sign * value->getFloatValue() / 100.f * edgeDistance;
    415 
    416     if (value->isCalculatedPercentageWithLength())
    417         return origin + sign * value->cssCalcValue()->toCalcValue(conversionData)->evaluate(edgeDistance);
    418 
    419     switch (value->getValueID()) {
    420     case CSSValueTop:
    421         ASSERT(!isHorizontal);
    422         return 0;
    423     case CSSValueLeft:
    424         ASSERT(isHorizontal);
    425         return 0;
    426     case CSSValueBottom:
    427         ASSERT(!isHorizontal);
    428         return size.height();
    429     case CSSValueRight:
    430         ASSERT(isHorizontal);
    431         return size.width();
    432     default:
    433         break;
    434     }
    435 
    436     return origin + sign * value->computeLength<float>(conversionData);
    437 }
    438 
    439 FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* horizontal, CSSPrimitiveValue* vertical, const CSSToLengthConversionData& conversionData, const IntSize& size)
    440 {
    441     FloatPoint result;
    442 
    443     if (horizontal)
    444         result.setX(positionFromValue(horizontal, conversionData, size, true));
    445 
    446     if (vertical)
    447         result.setY(positionFromValue(vertical, conversionData, size, false));
    448 
    449     return result;
    450 }
    451 
    452 bool CSSGradientValue::isCacheable() const
    453 {
    454     for (size_t i = 0; i < m_stops.size(); ++i) {
    455         const CSSGradientColorStop& stop = m_stops[i];
    456 
    457         if (stop.m_colorIsDerivedFromElement)
    458             return false;
    459 
    460         if (!stop.m_position)
    461             continue;
    462 
    463         if (stop.m_position->isFontRelativeLength())
    464             return false;
    465     }
    466 
    467     return true;
    468 }
    469 
    470 bool CSSGradientValue::knownToBeOpaque(const RenderObject*) const
    471 {
    472     for (size_t i = 0; i < m_stops.size(); ++i) {
    473         if (m_stops[i].m_resolvedColor.hasAlpha())
    474             return false;
    475     }
    476     return true;
    477 }
    478 
    479 void CSSGradientValue::traceAfterDispatch(Visitor* visitor)
    480 {
    481     visitor->trace(m_firstX);
    482     visitor->trace(m_firstY);
    483     visitor->trace(m_secondX);
    484     visitor->trace(m_secondY);
    485     visitor->trace(m_stops);
    486     CSSImageGeneratorValue::traceAfterDispatch(visitor);
    487 }
    488 
    489 String CSSLinearGradientValue::customCSSText() const
    490 {
    491     StringBuilder result;
    492     if (m_gradientType == CSSDeprecatedLinearGradient) {
    493         result.appendLiteral("-webkit-gradient(linear, ");
    494         result.append(m_firstX->cssText());
    495         result.append(' ');
    496         result.append(m_firstY->cssText());
    497         result.appendLiteral(", ");
    498         result.append(m_secondX->cssText());
    499         result.append(' ');
    500         result.append(m_secondY->cssText());
    501 
    502         for (unsigned i = 0; i < m_stops.size(); i++) {
    503             const CSSGradientColorStop& stop = m_stops[i];
    504             result.appendLiteral(", ");
    505             if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
    506                 result.appendLiteral("from(");
    507                 result.append(stop.m_color->cssText());
    508                 result.append(')');
    509             } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
    510                 result.appendLiteral("to(");
    511                 result.append(stop.m_color->cssText());
    512                 result.append(')');
    513             } else {
    514                 result.appendLiteral("color-stop(");
    515                 result.appendNumber(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER));
    516                 result.appendLiteral(", ");
    517                 result.append(stop.m_color->cssText());
    518                 result.append(')');
    519             }
    520         }
    521     } else if (m_gradientType == CSSPrefixedLinearGradient) {
    522         if (m_repeating)
    523             result.appendLiteral("-webkit-repeating-linear-gradient(");
    524         else
    525             result.appendLiteral("-webkit-linear-gradient(");
    526 
    527         if (m_angle)
    528             result.append(m_angle->cssText());
    529         else {
    530             if (m_firstX && m_firstY) {
    531                 result.append(m_firstX->cssText());
    532                 result.append(' ');
    533                 result.append(m_firstY->cssText());
    534             } else if (m_firstX || m_firstY) {
    535                 if (m_firstX)
    536                     result.append(m_firstX->cssText());
    537 
    538                 if (m_firstY)
    539                     result.append(m_firstY->cssText());
    540             }
    541         }
    542 
    543         for (unsigned i = 0; i < m_stops.size(); i++) {
    544             const CSSGradientColorStop& stop = m_stops[i];
    545             result.appendLiteral(", ");
    546             result.append(stop.m_color->cssText());
    547             if (stop.m_position) {
    548                 result.append(' ');
    549                 result.append(stop.m_position->cssText());
    550             }
    551         }
    552     } else {
    553         if (m_repeating)
    554             result.appendLiteral("repeating-linear-gradient(");
    555         else
    556             result.appendLiteral("linear-gradient(");
    557 
    558         bool wroteSomething = false;
    559 
    560         if (m_angle && m_angle->computeDegrees() != 180) {
    561             result.append(m_angle->cssText());
    562             wroteSomething = true;
    563         } else if ((m_firstX || m_firstY) && !(!m_firstX && m_firstY && m_firstY->getValueID() == CSSValueBottom)) {
    564             result.appendLiteral("to ");
    565             if (m_firstX && m_firstY) {
    566                 result.append(m_firstX->cssText());
    567                 result.append(' ');
    568                 result.append(m_firstY->cssText());
    569             } else if (m_firstX)
    570                 result.append(m_firstX->cssText());
    571             else
    572                 result.append(m_firstY->cssText());
    573             wroteSomething = true;
    574         }
    575 
    576         if (wroteSomething)
    577             result.appendLiteral(", ");
    578 
    579         for (unsigned i = 0; i < m_stops.size(); i++) {
    580             const CSSGradientColorStop& stop = m_stops[i];
    581             if (i)
    582                 result.appendLiteral(", ");
    583             result.append(stop.m_color->cssText());
    584             if (stop.m_position) {
    585                 result.append(' ');
    586                 result.append(stop.m_position->cssText());
    587             }
    588         }
    589 
    590     }
    591 
    592     result.append(')');
    593     return result.toString();
    594 }
    595 
    596 // Compute the endpoints so that a gradient of the given angle covers a box of the given size.
    597 static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint, CSSGradientType type)
    598 {
    599     // Prefixed gradients use "polar coordinate" angles, rather than "bearing" angles.
    600     if (type == CSSPrefixedLinearGradient)
    601         angleDeg = 90 - angleDeg;
    602 
    603     angleDeg = fmodf(angleDeg, 360);
    604     if (angleDeg < 0)
    605         angleDeg += 360;
    606 
    607     if (!angleDeg) {
    608         firstPoint.set(0, size.height());
    609         secondPoint.set(0, 0);
    610         return;
    611     }
    612 
    613     if (angleDeg == 90) {
    614         firstPoint.set(0, 0);
    615         secondPoint.set(size.width(), 0);
    616         return;
    617     }
    618 
    619     if (angleDeg == 180) {
    620         firstPoint.set(0, 0);
    621         secondPoint.set(0, size.height());
    622         return;
    623     }
    624 
    625     if (angleDeg == 270) {
    626         firstPoint.set(size.width(), 0);
    627         secondPoint.set(0, 0);
    628         return;
    629     }
    630 
    631     // angleDeg is a "bearing angle" (0deg = N, 90deg = E),
    632     // but tan expects 0deg = E, 90deg = N.
    633     float slope = tan(deg2rad(90 - angleDeg));
    634 
    635     // We find the endpoint by computing the intersection of the line formed by the slope,
    636     // and a line perpendicular to it that intersects the corner.
    637     float perpendicularSlope = -1 / slope;
    638 
    639     // Compute start corner relative to center, in Cartesian space (+y = up).
    640     float halfHeight = size.height() / 2;
    641     float halfWidth = size.width() / 2;
    642     FloatPoint endCorner;
    643     if (angleDeg < 90)
    644         endCorner.set(halfWidth, halfHeight);
    645     else if (angleDeg < 180)
    646         endCorner.set(halfWidth, -halfHeight);
    647     else if (angleDeg < 270)
    648         endCorner.set(-halfWidth, -halfHeight);
    649     else
    650         endCorner.set(-halfWidth, halfHeight);
    651 
    652     // Compute c (of y = mx + c) using the corner point.
    653     float c = endCorner.y() - perpendicularSlope * endCorner.x();
    654     float endX = c / (slope - perpendicularSlope);
    655     float endY = perpendicularSlope * endX + c;
    656 
    657     // We computed the end point, so set the second point,
    658     // taking into account the moved origin and the fact that we're in drawing space (+y = down).
    659     secondPoint.set(halfWidth + endX, halfHeight - endY);
    660     // Reflect around the center for the start point.
    661     firstPoint.set(halfWidth - endX, halfHeight + endY);
    662 }
    663 
    664 PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(const CSSToLengthConversionData& conversionData, const IntSize& size)
    665 {
    666     ASSERT(!size.isEmpty());
    667 
    668     FloatPoint firstPoint;
    669     FloatPoint secondPoint;
    670     if (m_angle) {
    671         float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG);
    672         endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
    673     } else {
    674         switch (m_gradientType) {
    675         case CSSDeprecatedLinearGradient:
    676             firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
    677             if (m_secondX || m_secondY)
    678                 secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
    679             else {
    680                 if (m_firstX)
    681                     secondPoint.setX(size.width() - firstPoint.x());
    682                 if (m_firstY)
    683                     secondPoint.setY(size.height() - firstPoint.y());
    684             }
    685             break;
    686         case CSSPrefixedLinearGradient:
    687             firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
    688             if (m_firstX)
    689                 secondPoint.setX(size.width() - firstPoint.x());
    690             if (m_firstY)
    691                 secondPoint.setY(size.height() - firstPoint.y());
    692             break;
    693         case CSSLinearGradient:
    694             if (m_firstX && m_firstY) {
    695                 // "Magic" corners, so the 50% line touches two corners.
    696                 float rise = size.width();
    697                 float run = size.height();
    698                 if (m_firstX && m_firstX->getValueID() == CSSValueLeft)
    699                     run *= -1;
    700                 if (m_firstY && m_firstY->getValueID() == CSSValueBottom)
    701                     rise *= -1;
    702                 // Compute angle, and flip it back to "bearing angle" degrees.
    703                 float angle = 90 - rad2deg(atan2(rise, run));
    704                 endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
    705             } else if (m_firstX || m_firstY) {
    706                 secondPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
    707                 if (m_firstX)
    708                     firstPoint.setX(size.width() - secondPoint.x());
    709                 if (m_firstY)
    710                     firstPoint.setY(size.height() - secondPoint.y());
    711             } else
    712                 secondPoint.setY(size.height());
    713             break;
    714         default:
    715             ASSERT_NOT_REACHED();
    716         }
    717 
    718     }
    719 
    720     RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
    721 
    722     gradient->setDrawsInPMColorSpace(true);
    723 
    724     // Now add the stops.
    725     addStops(gradient.get(), conversionData, 1);
    726 
    727     return gradient.release();
    728 }
    729 
    730 bool CSSLinearGradientValue::equals(const CSSLinearGradientValue& other) const
    731 {
    732     if (m_gradientType == CSSDeprecatedLinearGradient)
    733         return other.m_gradientType == m_gradientType
    734             && compareCSSValuePtr(m_firstX, other.m_firstX)
    735             && compareCSSValuePtr(m_firstY, other.m_firstY)
    736             && compareCSSValuePtr(m_secondX, other.m_secondX)
    737             && compareCSSValuePtr(m_secondY, other.m_secondY)
    738             && m_stops == other.m_stops;
    739 
    740     if (m_repeating != other.m_repeating)
    741         return false;
    742 
    743     if (m_angle)
    744         return compareCSSValuePtr(m_angle, other.m_angle) && m_stops == other.m_stops;
    745 
    746     if (other.m_angle)
    747         return false;
    748 
    749     bool equalXandY = false;
    750     if (m_firstX && m_firstY)
    751         equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
    752     else if (m_firstX)
    753         equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
    754     else if (m_firstY)
    755         equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
    756     else
    757         equalXandY = !other.m_firstX && !other.m_firstY;
    758 
    759     return equalXandY && m_stops == other.m_stops;
    760 }
    761 
    762 void CSSLinearGradientValue::traceAfterDispatch(Visitor* visitor)
    763 {
    764     visitor->trace(m_angle);
    765     CSSGradientValue::traceAfterDispatch(visitor);
    766 }
    767 
    768 String CSSRadialGradientValue::customCSSText() const
    769 {
    770     StringBuilder result;
    771 
    772     if (m_gradientType == CSSDeprecatedRadialGradient) {
    773         result.appendLiteral("-webkit-gradient(radial, ");
    774         result.append(m_firstX->cssText());
    775         result.append(' ');
    776         result.append(m_firstY->cssText());
    777         result.appendLiteral(", ");
    778         result.append(m_firstRadius->cssText());
    779         result.appendLiteral(", ");
    780         result.append(m_secondX->cssText());
    781         result.append(' ');
    782         result.append(m_secondY->cssText());
    783         result.appendLiteral(", ");
    784         result.append(m_secondRadius->cssText());
    785 
    786         // FIXME: share?
    787         for (unsigned i = 0; i < m_stops.size(); i++) {
    788             const CSSGradientColorStop& stop = m_stops[i];
    789             result.appendLiteral(", ");
    790             if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
    791                 result.appendLiteral("from(");
    792                 result.append(stop.m_color->cssText());
    793                 result.append(')');
    794             } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
    795                 result.appendLiteral("to(");
    796                 result.append(stop.m_color->cssText());
    797                 result.append(')');
    798             } else {
    799                 result.appendLiteral("color-stop(");
    800                 result.appendNumber(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER));
    801                 result.appendLiteral(", ");
    802                 result.append(stop.m_color->cssText());
    803                 result.append(')');
    804             }
    805         }
    806     } else if (m_gradientType == CSSPrefixedRadialGradient) {
    807         if (m_repeating)
    808             result.appendLiteral("-webkit-repeating-radial-gradient(");
    809         else
    810             result.appendLiteral("-webkit-radial-gradient(");
    811 
    812         if (m_firstX && m_firstY) {
    813             result.append(m_firstX->cssText());
    814             result.append(' ');
    815             result.append(m_firstY->cssText());
    816         } else if (m_firstX)
    817             result.append(m_firstX->cssText());
    818          else if (m_firstY)
    819             result.append(m_firstY->cssText());
    820         else
    821             result.appendLiteral("center");
    822 
    823         if (m_shape || m_sizingBehavior) {
    824             result.appendLiteral(", ");
    825             if (m_shape) {
    826                 result.append(m_shape->cssText());
    827                 result.append(' ');
    828             } else
    829                 result.appendLiteral("ellipse ");
    830 
    831             if (m_sizingBehavior)
    832                 result.append(m_sizingBehavior->cssText());
    833             else
    834                 result.appendLiteral("cover");
    835 
    836         } else if (m_endHorizontalSize && m_endVerticalSize) {
    837             result.appendLiteral(", ");
    838             result.append(m_endHorizontalSize->cssText());
    839             result.append(' ');
    840             result.append(m_endVerticalSize->cssText());
    841         }
    842 
    843         for (unsigned i = 0; i < m_stops.size(); i++) {
    844             const CSSGradientColorStop& stop = m_stops[i];
    845             result.appendLiteral(", ");
    846             result.append(stop.m_color->cssText());
    847             if (stop.m_position) {
    848                 result.append(' ');
    849                 result.append(stop.m_position->cssText());
    850             }
    851         }
    852     } else {
    853         if (m_repeating)
    854             result.appendLiteral("repeating-radial-gradient(");
    855         else
    856             result.appendLiteral("radial-gradient(");
    857 
    858         bool wroteSomething = false;
    859 
    860         // The only ambiguous case that needs an explicit shape to be provided
    861         // is when a sizing keyword is used (or all sizing is omitted).
    862         if (m_shape && m_shape->getValueID() != CSSValueEllipse && (m_sizingBehavior || (!m_sizingBehavior && !m_endHorizontalSize))) {
    863             result.appendLiteral("circle");
    864             wroteSomething = true;
    865         }
    866 
    867         if (m_sizingBehavior && m_sizingBehavior->getValueID() != CSSValueFarthestCorner) {
    868             if (wroteSomething)
    869                 result.append(' ');
    870             result.append(m_sizingBehavior->cssText());
    871             wroteSomething = true;
    872         } else if (m_endHorizontalSize) {
    873             if (wroteSomething)
    874                 result.append(' ');
    875             result.append(m_endHorizontalSize->cssText());
    876             if (m_endVerticalSize) {
    877                 result.append(' ');
    878                 result.append(m_endVerticalSize->cssText());
    879             }
    880             wroteSomething = true;
    881         }
    882 
    883         if (m_firstX || m_firstY) {
    884             if (wroteSomething)
    885                 result.append(' ');
    886             result.appendLiteral("at ");
    887             if (m_firstX && m_firstY) {
    888                 result.append(m_firstX->cssText());
    889                 result.append(' ');
    890                 result.append(m_firstY->cssText());
    891             } else if (m_firstX)
    892                 result.append(m_firstX->cssText());
    893             else
    894                 result.append(m_firstY->cssText());
    895             wroteSomething = true;
    896         }
    897 
    898         if (wroteSomething)
    899             result.appendLiteral(", ");
    900 
    901         for (unsigned i = 0; i < m_stops.size(); i++) {
    902             const CSSGradientColorStop& stop = m_stops[i];
    903             if (i)
    904                 result.appendLiteral(", ");
    905             result.append(stop.m_color->cssText());
    906             if (stop.m_position) {
    907                 result.append(' ');
    908                 result.append(stop.m_position->cssText());
    909             }
    910         }
    911 
    912     }
    913 
    914     result.append(')');
    915     return result.toString();
    916 }
    917 
    918 float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, const CSSToLengthConversionData& conversionData, float* widthOrHeight)
    919 {
    920     float result = 0;
    921     if (radius->isNumber()) // Can the radius be a percentage?
    922         result = radius->getFloatValue() * conversionData.zoom();
    923     else if (widthOrHeight && radius->isPercentage())
    924         result = *widthOrHeight * radius->getFloatValue() / 100;
    925     else
    926         result = radius->computeLength<float>(conversionData);
    927 
    928     return result;
    929 }
    930 
    931 static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
    932 {
    933     FloatPoint topLeft;
    934     float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
    935 
    936     FloatPoint topRight(size.width(), 0);
    937     float topRightDistance = FloatSize(p - topRight).diagonalLength();
    938 
    939     FloatPoint bottomLeft(0, size.height());
    940     float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
    941 
    942     FloatPoint bottomRight(size.width(), size.height());
    943     float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
    944 
    945     corner = topLeft;
    946     float minDistance = topLeftDistance;
    947     if (topRightDistance < minDistance) {
    948         minDistance = topRightDistance;
    949         corner = topRight;
    950     }
    951 
    952     if (bottomLeftDistance < minDistance) {
    953         minDistance = bottomLeftDistance;
    954         corner = bottomLeft;
    955     }
    956 
    957     if (bottomRightDistance < minDistance) {
    958         minDistance = bottomRightDistance;
    959         corner = bottomRight;
    960     }
    961     return minDistance;
    962 }
    963 
    964 static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
    965 {
    966     FloatPoint topLeft;
    967     float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
    968 
    969     FloatPoint topRight(size.width(), 0);
    970     float topRightDistance = FloatSize(p - topRight).diagonalLength();
    971 
    972     FloatPoint bottomLeft(0, size.height());
    973     float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
    974 
    975     FloatPoint bottomRight(size.width(), size.height());
    976     float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
    977 
    978     corner = topLeft;
    979     float maxDistance = topLeftDistance;
    980     if (topRightDistance > maxDistance) {
    981         maxDistance = topRightDistance;
    982         corner = topRight;
    983     }
    984 
    985     if (bottomLeftDistance > maxDistance) {
    986         maxDistance = bottomLeftDistance;
    987         corner = bottomLeft;
    988     }
    989 
    990     if (bottomRightDistance > maxDistance) {
    991         maxDistance = bottomRightDistance;
    992         corner = bottomRight;
    993     }
    994     return maxDistance;
    995 }
    996 
    997 // Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has
    998 // width/height given by aspectRatio.
    999 static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio)
   1000 {
   1001     // x^2/a^2 + y^2/b^2 = 1
   1002     // a/b = aspectRatio, b = a/aspectRatio
   1003     // a = sqrt(x^2 + y^2/(1/r^2))
   1004     return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio)));
   1005 }
   1006 
   1007 // FIXME: share code with the linear version
   1008 PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(const CSSToLengthConversionData& conversionData, const IntSize& size)
   1009 {
   1010     ASSERT(!size.isEmpty());
   1011 
   1012     FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
   1013     if (!m_firstX)
   1014         firstPoint.setX(size.width() / 2);
   1015     if (!m_firstY)
   1016         firstPoint.setY(size.height() / 2);
   1017 
   1018     FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
   1019     if (!m_secondX)
   1020         secondPoint.setX(size.width() / 2);
   1021     if (!m_secondY)
   1022         secondPoint.setY(size.height() / 2);
   1023 
   1024     float firstRadius = 0;
   1025     if (m_firstRadius)
   1026         firstRadius = resolveRadius(m_firstRadius.get(), conversionData);
   1027 
   1028     float secondRadius = 0;
   1029     float aspectRatio = 1; // width / height.
   1030     if (m_secondRadius)
   1031         secondRadius = resolveRadius(m_secondRadius.get(), conversionData);
   1032     else if (m_endHorizontalSize) {
   1033         float width = size.width();
   1034         float height = size.height();
   1035         secondRadius = resolveRadius(m_endHorizontalSize.get(), conversionData, &width);
   1036         if (m_endVerticalSize)
   1037             aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), conversionData, &height);
   1038         else
   1039             aspectRatio = 1;
   1040     } else {
   1041         enum GradientShape { Circle, Ellipse };
   1042         GradientShape shape = Ellipse;
   1043         if ((m_shape && m_shape->getValueID() == CSSValueCircle)
   1044             || (!m_shape && !m_sizingBehavior && m_endHorizontalSize && !m_endVerticalSize))
   1045             shape = Circle;
   1046 
   1047         enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
   1048         GradientFill fill = FarthestCorner;
   1049 
   1050         switch (m_sizingBehavior ? m_sizingBehavior->getValueID() : 0) {
   1051         case CSSValueContain:
   1052         case CSSValueClosestSide:
   1053             fill = ClosestSide;
   1054             break;
   1055         case CSSValueClosestCorner:
   1056             fill = ClosestCorner;
   1057             break;
   1058         case CSSValueFarthestSide:
   1059             fill = FarthestSide;
   1060             break;
   1061         case CSSValueCover:
   1062         case CSSValueFarthestCorner:
   1063             fill = FarthestCorner;
   1064             break;
   1065         default:
   1066             break;
   1067         }
   1068 
   1069         // Now compute the end radii based on the second point, shape and fill.
   1070 
   1071         // Horizontal
   1072         switch (fill) {
   1073         case ClosestSide: {
   1074             float xDist = std::min(secondPoint.x(), size.width() - secondPoint.x());
   1075             float yDist = std::min(secondPoint.y(), size.height() - secondPoint.y());
   1076             if (shape == Circle) {
   1077                 float smaller = std::min(xDist, yDist);
   1078                 xDist = smaller;
   1079                 yDist = smaller;
   1080             }
   1081             secondRadius = xDist;
   1082             aspectRatio = xDist / yDist;
   1083             break;
   1084         }
   1085         case FarthestSide: {
   1086             float xDist = std::max(secondPoint.x(), size.width() - secondPoint.x());
   1087             float yDist = std::max(secondPoint.y(), size.height() - secondPoint.y());
   1088             if (shape == Circle) {
   1089                 float larger = std::max(xDist, yDist);
   1090                 xDist = larger;
   1091                 yDist = larger;
   1092             }
   1093             secondRadius = xDist;
   1094             aspectRatio = xDist / yDist;
   1095             break;
   1096         }
   1097         case ClosestCorner: {
   1098             FloatPoint corner;
   1099             float distance = distanceToClosestCorner(secondPoint, size, corner);
   1100             if (shape == Circle)
   1101                 secondRadius = distance;
   1102             else {
   1103                 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
   1104                 // that it would if closest-side or farthest-side were specified, as appropriate.
   1105                 float xDist = std::min(secondPoint.x(), size.width() - secondPoint.x());
   1106                 float yDist = std::min(secondPoint.y(), size.height() - secondPoint.y());
   1107 
   1108                 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
   1109                 aspectRatio = xDist / yDist;
   1110             }
   1111             break;
   1112         }
   1113 
   1114         case FarthestCorner: {
   1115             FloatPoint corner;
   1116             float distance = distanceToFarthestCorner(secondPoint, size, corner);
   1117             if (shape == Circle)
   1118                 secondRadius = distance;
   1119             else {
   1120                 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
   1121                 // that it would if closest-side or farthest-side were specified, as appropriate.
   1122                 float xDist = std::max(secondPoint.x(), size.width() - secondPoint.x());
   1123                 float yDist = std::max(secondPoint.y(), size.height() - secondPoint.y());
   1124 
   1125                 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
   1126                 aspectRatio = xDist / yDist;
   1127             }
   1128             break;
   1129         }
   1130         }
   1131     }
   1132 
   1133     RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio);
   1134 
   1135     gradient->setDrawsInPMColorSpace(true);
   1136 
   1137     // addStops() only uses maxExtent for repeating gradients.
   1138     float maxExtent = 0;
   1139     if (m_repeating) {
   1140         FloatPoint corner;
   1141         maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
   1142     }
   1143 
   1144     // Now add the stops.
   1145     addStops(gradient.get(), conversionData, maxExtent);
   1146 
   1147     return gradient.release();
   1148 }
   1149 
   1150 bool CSSRadialGradientValue::equals(const CSSRadialGradientValue& other) const
   1151 {
   1152     if (m_gradientType == CSSDeprecatedRadialGradient)
   1153         return other.m_gradientType == m_gradientType
   1154             && compareCSSValuePtr(m_firstX, other.m_firstX)
   1155             && compareCSSValuePtr(m_firstY, other.m_firstY)
   1156             && compareCSSValuePtr(m_secondX, other.m_secondX)
   1157             && compareCSSValuePtr(m_secondY, other.m_secondY)
   1158             && compareCSSValuePtr(m_firstRadius, other.m_firstRadius)
   1159             && compareCSSValuePtr(m_secondRadius, other.m_secondRadius)
   1160             && m_stops == other.m_stops;
   1161 
   1162     if (m_repeating != other.m_repeating)
   1163         return false;
   1164 
   1165     bool equalXandY = false;
   1166     if (m_firstX && m_firstY)
   1167         equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
   1168     else if (m_firstX)
   1169         equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
   1170     else if (m_firstY)
   1171         equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
   1172     else
   1173         equalXandY = !other.m_firstX && !other.m_firstY;
   1174 
   1175     if (!equalXandY)
   1176         return false;
   1177 
   1178     bool equalShape = true;
   1179     bool equalSizingBehavior = true;
   1180     bool equalHorizontalAndVerticalSize = true;
   1181 
   1182     if (m_shape)
   1183         equalShape = compareCSSValuePtr(m_shape, other.m_shape);
   1184     else if (m_sizingBehavior)
   1185         equalSizingBehavior = compareCSSValuePtr(m_sizingBehavior, other.m_sizingBehavior);
   1186     else if (m_endHorizontalSize && m_endVerticalSize)
   1187         equalHorizontalAndVerticalSize = compareCSSValuePtr(m_endHorizontalSize, other.m_endHorizontalSize) && compareCSSValuePtr(m_endVerticalSize, other.m_endVerticalSize);
   1188     else {
   1189         equalShape = !other.m_shape;
   1190         equalSizingBehavior = !other.m_sizingBehavior;
   1191         equalHorizontalAndVerticalSize = !other.m_endHorizontalSize && !other.m_endVerticalSize;
   1192     }
   1193     return equalShape && equalSizingBehavior && equalHorizontalAndVerticalSize && m_stops == other.m_stops;
   1194 }
   1195 
   1196 void CSSRadialGradientValue::traceAfterDispatch(Visitor* visitor)
   1197 {
   1198     visitor->trace(m_firstRadius);
   1199     visitor->trace(m_secondRadius);
   1200     visitor->trace(m_shape);
   1201     visitor->trace(m_sizingBehavior);
   1202     visitor->trace(m_endHorizontalSize);
   1203     visitor->trace(m_endVerticalSize);
   1204     CSSGradientValue::traceAfterDispatch(visitor);
   1205 }
   1206 
   1207 } // namespace blink
   1208