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