• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
trace(Visitor * visitor)44 void CSSGradientColorStop::trace(Visitor* visitor)
45 {
46     visitor->trace(m_position);
47     visitor->trace(m_color);
48 }
49 
image(RenderObject * renderer,const IntSize & size)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.
compareStops(const CSSGradientColorStop & a,const CSSGradientColorStop & b)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 
sortStopsIfNeeded()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 
GradientStopWebCore::GradientStop107     GradientStop()
108         : offset(0)
109         , specified(false)
110     { }
111 };
112 
gradientWithStylesResolved(const TextLinkColors & textLinkColors,Color currentColor)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 
addStops(Gradient * gradient,const CSSToLengthConversionData & conversionData,float maxLengthForRepeat)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 
positionFromValue(CSSPrimitiveValue * value,const CSSToLengthConversionData & conversionData,const IntSize & size,bool isHorizontal)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 
computeEndPoint(CSSPrimitiveValue * horizontal,CSSPrimitiveValue * vertical,const CSSToLengthConversionData & conversionData,const IntSize & size)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 
isCacheable() const435 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 
knownToBeOpaque(const RenderObject *) const453 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 
traceAfterDispatch(Visitor * visitor)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 
customCSSText() const472 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.
endPointsFromAngle(float angleDeg,const IntSize & size,FloatPoint & firstPoint,FloatPoint & secondPoint,CSSGradientType type)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 
createGradient(const CSSToLengthConversionData & conversionData,const IntSize & size)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 
equals(const CSSLinearGradientValue & other) const713 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 
traceAfterDispatch(Visitor * visitor)745 void CSSLinearGradientValue::traceAfterDispatch(Visitor* visitor)
746 {
747     visitor->trace(m_angle);
748     CSSGradientValue::traceAfterDispatch(visitor);
749 }
750 
customCSSText() const751 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 
resolveRadius(CSSPrimitiveValue * radius,const CSSToLengthConversionData & conversionData,float * widthOrHeight)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 
distanceToClosestCorner(const FloatPoint & p,const FloatSize & size,FloatPoint & corner)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 
distanceToFarthestCorner(const FloatPoint & p,const FloatSize & size,FloatPoint & corner)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.
horizontalEllipseRadius(const FloatSize & p,float 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
createGradient(const CSSToLengthConversionData & conversionData,const IntSize & size)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 
equals(const CSSRadialGradientValue & other) const1133 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 
traceAfterDispatch(Visitor * visitor)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