[iOS] CoreText and CoreUI SPI are not available in public SDK
[WebKit-https.git] / Source / WebCore / rendering / RenderThemeIOS.mm
1 /*
2  * Copyright (C) 2005, 2006, 2007, 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 INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27
28 #if PLATFORM(IOS)
29
30 #import "CSSPrimitiveValue.h"
31 #import "CSSToLengthConversionData.h"
32 #import "CSSValueKeywords.h"
33 #import "CoreTextSPI.h"
34 #import "DateComponents.h"
35 #import "Document.h"
36 #import "FloatRoundedRect.h"
37 #import "Font.h"
38 #import "FontCache.h"
39 #import "Frame.h"
40 #import "FrameView.h"
41 #import "Gradient.h"
42 #import "GraphicsContext.h"
43 #import "GraphicsContextCG.h"
44 #import "HTMLInputElement.h"
45 #import "HTMLNames.h"
46 #import "HTMLSelectElement.h"
47 #import "Icon.h"
48 #import "LocalizedDateCache.h"
49 #import "NodeRenderStyle.h"
50 #import "Page.h"
51 #import "PaintInfo.h"
52 #import "PlatformLocale.h"
53 #import "RenderObject.h"
54 #import "RenderProgress.h"
55 #import "RenderStyle.h"
56 #import "RenderThemeIOS.h"
57 #import "RenderView.h"
58 #import "SoftLinking.h"
59 #import "UserAgentScripts.h"
60 #import "UserAgentStyleSheets.h"
61 #import "WebCoreThreadRun.h"
62 #import <CoreGraphics/CoreGraphics.h>
63 #import <objc/runtime.h>
64 #import <wtf/NeverDestroyed.h>
65 #import <wtf/RefPtr.h>
66 #import <wtf/StdLibExtras.h>
67
68 @interface UIApplication
69 + (UIApplication *)sharedApplication;
70 @property(nonatomic,copy) NSString *preferredContentSizeCategory;
71 @end
72
73 SOFT_LINK_FRAMEWORK(UIKit)
74 SOFT_LINK_CLASS(UIKit, UIApplication)
75 SOFT_LINK_CONSTANT(UIKit, UIContentSizeCategoryDidChangeNotification, CFStringRef)
76 #define UIContentSizeCategoryDidChangeNotification getUIContentSizeCategoryDidChangeNotification()
77
78 @interface WebCoreRenderThemeBundle : NSObject
79 @end
80
81 @implementation WebCoreRenderThemeBundle
82 @end
83
84 namespace WebCore {
85
86 const float ControlBaseHeight = 20;
87 const float ControlBaseFontSize = 11;
88
89 struct IOSGradient {
90     float* start; // points to static float[4]
91     float* end; // points to static float[4]
92     IOSGradient(float start[4], float end[4])
93         : start(start)
94         , end(end)
95     {
96     }
97 };
98
99 typedef IOSGradient* IOSGradientRef;
100
101 enum Interpolation
102 {
103     LinearInterpolation,
104     ExponentialInterpolation
105 };
106
107 static void interpolateLinearGradient(void *info, const CGFloat *inData, CGFloat *outData)
108 {
109     IOSGradientRef gradient = static_cast<IOSGradientRef>(info);
110     float alpha = inData[0];
111     float inverse = 1.0f - alpha;
112
113     outData[0] = inverse * gradient->start[0] + alpha * gradient->end[0];
114     outData[1] = inverse * gradient->start[1] + alpha * gradient->end[1];
115     outData[2] = inverse * gradient->start[2] + alpha * gradient->end[2];
116     outData[3] = inverse * gradient->start[3] + alpha * gradient->end[3];
117 }
118
119 static void interpolateExponentialGradient(void *info, const CGFloat *inData, CGFloat *outData)
120 {
121     IOSGradientRef gradient = static_cast<IOSGradientRef>(info);
122     float a = inData[0];
123     for (int paintInfo = 0; paintInfo < 4; ++paintInfo) {
124         float end = logf(std::max(gradient->end[paintInfo], 0.01f));
125         float start = logf(std::max(gradient->start[paintInfo], 0.01f));
126         outData[paintInfo] = expf(start - (end + start) * a);
127     }
128 }
129
130 static CGFunctionRef getSharedFunctionRef(IOSGradientRef gradient, Interpolation interpolation)
131 {
132     CGFunctionRef function = nullptr;
133
134     static HashMap<IOSGradientRef, CGFunctionRef>* linearFunctionRefs;
135     static HashMap<IOSGradientRef, CGFunctionRef>* exponentialFunctionRefs;;
136
137     if (interpolation == LinearInterpolation) {
138         if (!linearFunctionRefs)
139             linearFunctionRefs = new HashMap<IOSGradientRef, CGFunctionRef>;
140         else
141             function = linearFunctionRefs->get(gradient);
142     
143         if (!function) {
144             static struct CGFunctionCallbacks linearFunctionCallbacks =  { 0, interpolateLinearGradient, 0 };
145             linearFunctionRefs->set(gradient, function = CGFunctionCreate(gradient, 1, nullptr, 4, nullptr, &linearFunctionCallbacks));
146         }
147
148         return function;
149     }
150
151     if (!exponentialFunctionRefs)
152         exponentialFunctionRefs = new HashMap<IOSGradientRef, CGFunctionRef>;
153     else
154         function = exponentialFunctionRefs->get(gradient);
155
156     if (!function) {
157         static struct CGFunctionCallbacks exponentialFunctionCallbacks =  { 0, interpolateExponentialGradient, 0 };
158         exponentialFunctionRefs->set(gradient, function = CGFunctionCreate(gradient, 1, 0, 4, 0, &exponentialFunctionCallbacks));
159     }
160
161     return function;
162 }
163
164 static void drawAxialGradient(CGContextRef context, IOSGradientRef gradient, const FloatPoint& startPoint, const FloatPoint& stopPoint, Interpolation interpolation)
165 {
166     RetainPtr<CGShadingRef> shading = adoptCF(CGShadingCreateAxial(deviceRGBColorSpaceRef(), startPoint, stopPoint, getSharedFunctionRef(gradient, interpolation), false, false));
167     CGContextDrawShading(context, shading.get());
168 }
169
170 static void drawRadialGradient(CGContextRef context, IOSGradientRef gradient, const FloatPoint& startPoint, float startRadius, const FloatPoint& stopPoint, float stopRadius, Interpolation interpolation)
171 {
172     RetainPtr<CGShadingRef> shading = adoptCF(CGShadingCreateRadial(deviceRGBColorSpaceRef(), startPoint, startRadius, stopPoint, stopRadius, getSharedFunctionRef(gradient, interpolation), false, false));
173     CGContextDrawShading(context, shading.get());
174 }
175
176 enum IOSGradientType {
177     InsetGradient,
178     ShineGradient,
179     ShadeGradient,
180     ConvexGradient,
181     ConcaveGradient,
182     SliderTrackGradient,
183     ReadonlySliderTrackGradient,
184     SliderThumbOpaquePressedGradient,
185 };
186
187 static IOSGradientRef getInsetGradient()
188 {
189     static float end[4] = { 0 / 255.0, 0 / 255.0, 0 / 255.0, 0 };
190     static float start[4] = { 0 / 255.0, 0 / 255.0, 0 / 255.0, 0.2 };
191     static NeverDestroyed<IOSGradient> gradient(start, end);
192     return &gradient.get();
193 }
194
195 static IOSGradientRef getShineGradient()
196 {
197     static float end[4] = { 1, 1, 1, 0.8 };
198     static float start[4] = { 1, 1, 1, 0 };
199     static NeverDestroyed<IOSGradient> gradient(start, end);
200     return &gradient.get();
201 }
202
203 static IOSGradientRef getShadeGradient()
204 {
205     static float end[4] = { 178 / 255.0, 178 / 255.0, 178 / 255.0, 0.65 };
206     static float start[4] = { 252 / 255.0, 252 / 255.0, 252 / 255.0, 0.65 };
207     static NeverDestroyed<IOSGradient> gradient(start, end);
208     return &gradient.get();
209 }
210
211 static IOSGradientRef getConvexGradient()
212 {
213     static float end[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0.05 };
214     static float start[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0.43 };
215     static NeverDestroyed<IOSGradient> gradient(start, end);
216     return &gradient.get();
217 }
218
219 static IOSGradientRef getConcaveGradient()
220 {
221     static float end[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0.46 };
222     static float start[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0 };
223     static NeverDestroyed<IOSGradient> gradient(start, end);
224     return &gradient.get();
225 }
226
227 static IOSGradientRef getSliderTrackGradient()
228 {
229     static float end[4] = { 132 / 255.0, 132 / 255.0, 132 / 255.0, 1 };
230     static float start[4] = { 74 / 255.0, 77 / 255.0, 80 / 255.0, 1 };
231     static NeverDestroyed<IOSGradient> gradient(start, end);
232     return &gradient.get();
233 }
234
235 static IOSGradientRef getReadonlySliderTrackGradient()
236 {
237     static float end[4] = { 132 / 255.0, 132 / 255.0, 132 / 255.0, 0.4 };
238     static float start[4] = { 74 / 255.0, 77 / 255.0, 80 /255.0, 0.4 };
239     static NeverDestroyed<IOSGradient> gradient(start, end);
240     return &gradient.get();
241 }
242
243 static IOSGradientRef getSliderThumbOpaquePressedGradient()
244 {
245     static float end[4] = { 144 / 255.0, 144 / 255.0, 144 / 255.0, 1};
246     static float start[4] = { 55 / 255.0, 55 / 255.0, 55 / 255.0, 1 };
247     static NeverDestroyed<IOSGradient> gradient(start, end);
248     return &gradient.get();
249 }
250
251 static IOSGradientRef gradientWithName(IOSGradientType gradientType)
252 {
253     switch (gradientType) {
254     case InsetGradient:
255         return getInsetGradient();
256     case ShineGradient:
257         return getShineGradient();
258     case ShadeGradient:
259         return getShadeGradient();
260     case ConvexGradient:
261         return getConvexGradient();
262     case ConcaveGradient:
263         return getConcaveGradient();
264     case SliderTrackGradient:
265         return getSliderTrackGradient();
266     case ReadonlySliderTrackGradient:
267         return getReadonlySliderTrackGradient();
268     case SliderThumbOpaquePressedGradient:
269         return getSliderThumbOpaquePressedGradient();
270     }
271     ASSERT_NOT_REACHED();
272     return nullptr;
273 }
274
275 static void contentSizeCategoryDidChange(CFNotificationCenterRef, void*, CFStringRef name, const void*, CFDictionaryRef)
276 {
277     ASSERT_UNUSED(name, CFEqual(name, UIContentSizeCategoryDidChangeNotification));
278     WebThreadRun(^{
279         Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment();
280     });
281 }
282
283 RenderThemeIOS::RenderThemeIOS()
284 {
285     CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), this, contentSizeCategoryDidChange, UIContentSizeCategoryDidChangeNotification, 0, CFNotificationSuspensionBehaviorDeliverImmediately);
286 }
287
288 PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page*)
289 {
290     static RenderTheme* renderTheme = RenderThemeIOS::create().leakRef();
291     return renderTheme;
292 }
293
294 PassRefPtr<RenderTheme> RenderThemeIOS::create()
295 {
296     return adoptRef(new RenderThemeIOS);
297 }
298
299 static String& _contentSizeCategory()
300 {
301     static NeverDestroyed<String> _contentSizeCategory;
302     return _contentSizeCategory.get();
303 }
304
305 CFStringRef RenderThemeIOS::contentSizeCategory()
306 {
307     if (!_contentSizeCategory().isNull())
308         return (__bridge CFStringRef)static_cast<NSString*>(_contentSizeCategory());
309     return (CFStringRef)[[getUIApplicationClass() sharedApplication] preferredContentSizeCategory];
310 }
311
312 void RenderThemeIOS::setContentSizeCategory(const String& contentSizeCategory)
313 {
314     _contentSizeCategory() = contentSizeCategory;
315 }
316
317 const Color& RenderThemeIOS::shadowColor() const
318 {
319     static Color color(0.0f, 0.0f, 0.0f, 0.7f);
320     return color;
321 }
322
323 FloatRect RenderThemeIOS::addRoundedBorderClip(const RenderObject& box, GraphicsContext* context, const IntRect& rect)
324 {
325     // To fix inner border bleeding issues <rdar://problem/9812507>, we clip to the outer border and assert that
326     // the border is opaque or transparent, unless we're checked because checked radio/checkboxes show no bleeding.
327     RenderStyle& style = box.style();
328     RoundedRect border = isChecked(box) ? style.getRoundedInnerBorderFor(rect) : style.getRoundedBorderFor(rect);
329
330     if (border.isRounded())
331         context->clipRoundedRect(FloatRoundedRect(border));
332     else
333         context->clip(border.rect());
334
335     if (isChecked(box)) {
336         ASSERT(style.visitedDependentColor(CSSPropertyBorderTopColor).alpha() % 255 == 0);
337         ASSERT(style.visitedDependentColor(CSSPropertyBorderRightColor).alpha() % 255 == 0);
338         ASSERT(style.visitedDependentColor(CSSPropertyBorderBottomColor).alpha() % 255 == 0);
339         ASSERT(style.visitedDependentColor(CSSPropertyBorderLeftColor).alpha() % 255 == 0);
340     }
341
342     return border.rect();
343 }
344
345 void RenderThemeIOS::adjustCheckboxStyle(StyleResolver&, RenderStyle& style, Element*) const
346 {
347     if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
348         return;
349
350     Length length = Length(static_cast<int>(ceilf(std::max(style.fontSize(), 10))), Fixed);
351     
352     style.setWidth(length);
353     style.setHeight(length);
354 }
355
356 static CGPoint shortened(CGPoint start, CGPoint end, float width)
357 {
358     float x = end.x - start.x;
359     float y = end.y - start.y;
360     float ratio = (!x && !y) ? 0 : width / sqrtf(x * x + y * y);
361     return CGPointMake(start.x + x * ratio, start.y + y * ratio);
362 }
363
364 bool RenderThemeIOS::paintCheckboxDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
365 {
366     GraphicsContextStateSaver stateSaver(*paintInfo.context);
367     FloatRect clip = addRoundedBorderClip(box, paintInfo.context, rect);
368
369     float width = clip.width();
370     float height = clip.height();
371
372     CGContextRef cgContext = paintInfo.context->platformContext();
373     if (isChecked(box)) {
374         drawAxialGradient(cgContext, gradientWithName(ConcaveGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
375
376         static float thicknessRatio = 2 / 14.0;
377         static CGSize size = { 14.0f, 14.0f };
378         static CGPoint pathRatios[3] = {
379             { 2.5f / size.width, 7.5f / size.height },
380             { 5.5f / size.width, 10.5f / size.height },
381             { 11.5f / size.width, 2.5f / size.height }
382         };
383
384         float lineWidth = std::min(width, height) * 2.0f * thicknessRatio;
385
386         CGPoint line[3] = {
387             CGPointMake(clip.x() + width * pathRatios[0].x, clip.y() + height * pathRatios[0].y),
388             CGPointMake(clip.x() + width * pathRatios[1].x, clip.y() + height * pathRatios[1].y),
389             CGPointMake(clip.x() + width * pathRatios[2].x, clip.y() + height * pathRatios[2].y)
390         };
391         CGPoint shadow[3] = {
392             shortened(line[0], line[1], lineWidth / 4.0f),
393             line[1],
394             shortened(line[2], line[1], lineWidth / 4.0f)
395         };
396
397         paintInfo.context->setStrokeThickness(lineWidth);
398         paintInfo.context->setStrokeColor(Color(0.0f, 0.0f, 0.0f, 0.7f), ColorSpaceDeviceRGB);
399
400         paintInfo.context->drawJoinedLines(shadow, 3, true, kCGLineCapSquare);
401
402         paintInfo.context->setStrokeThickness(std::min(clip.width(), clip.height()) * thicknessRatio);
403         paintInfo.context->setStrokeColor(Color(1.0f, 1.0f, 1.0f, 240 / 255.0f), ColorSpaceDeviceRGB);
404
405         paintInfo.context->drawJoinedLines(line, 3, true);
406     } else {
407         FloatPoint bottomCenter(clip.x() + clip.width() / 2.0f, clip.maxY());
408         drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
409         drawRadialGradient(cgContext, gradientWithName(ShineGradient), bottomCenter, 0, bottomCenter, sqrtf((width * width) / 4.0f + height * height), ExponentialInterpolation);
410     }
411
412     return false;
413 }
414
415 int RenderThemeIOS::baselinePosition(const RenderObject& renderer) const
416 {
417     if (!is<RenderBox>(renderer))
418         return 0;
419
420     const auto& box = downcast<RenderBox>(renderer);
421
422     if (box.style().appearance() == CheckboxPart || box.style().appearance() == RadioPart)
423         return box.marginTop() + box.height() - 2; // The baseline is 2px up from the bottom of the checkbox/radio in AppKit.
424     if (box.style().appearance() == MenulistPart)
425         return box.marginTop() + box.height() - 5; // This is to match AppKit. There might be a better way to calculate this though.
426     return RenderTheme::baselinePosition(box);
427 }
428
429 bool RenderThemeIOS::isControlStyled(const RenderStyle& style, const BorderData& border, const FillLayer& background, const Color& backgroundColor) const
430 {
431     // Buttons and MenulistButtons are styled if they contain a background image.
432     if (style.appearance() == PushButtonPart || style.appearance() == MenulistButtonPart)
433         return !style.visitedDependentColor(CSSPropertyBackgroundColor).alpha() || style.backgroundLayers()->hasImage();
434
435     if (style.appearance() == TextFieldPart || style.appearance() == TextAreaPart)
436         return *style.backgroundLayers() != background;
437
438     return RenderTheme::isControlStyled(style, border, background, backgroundColor);
439 }
440
441 void RenderThemeIOS::adjustRadioStyle(StyleResolver&, RenderStyle& style, Element*) const
442 {
443     if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
444         return;
445
446     Length length = Length(static_cast<int>(ceilf(std::max(style.fontSize(), 10))), Fixed);
447     style.setWidth(length);
448     style.setHeight(length);
449     style.setBorderRadius(IntSize(length.value() / 2.0f, length.value() / 2.0f));
450 }
451
452 bool RenderThemeIOS::paintRadioDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
453 {
454     GraphicsContextStateSaver stateSaver(*paintInfo.context);
455     FloatRect clip = addRoundedBorderClip(box, paintInfo.context, rect);
456
457     CGContextRef cgContext = paintInfo.context->platformContext();
458     if (isChecked(box)) {
459         drawAxialGradient(cgContext, gradientWithName(ConcaveGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
460
461         // The inner circle is 6 / 14 the size of the surrounding circle, 
462         // leaving 8 / 14 around it. (8 / 14) / 2 = 2 / 7.
463
464         static float InnerInverseRatio = 2 / 7.0;
465
466         clip.inflateX(-clip.width() * InnerInverseRatio);
467         clip.inflateY(-clip.height() * InnerInverseRatio);
468
469         paintInfo.context->drawRaisedEllipse(clip, Color::white, ColorSpaceDeviceRGB, shadowColor(), ColorSpaceDeviceRGB);
470
471         FloatSize radius(clip.width() / 2.0f, clip.height() / 2.0f);
472         paintInfo.context->clipRoundedRect(FloatRoundedRect(clip, radius, radius, radius, radius));
473     }
474     FloatPoint bottomCenter(clip.x() + clip.width() / 2.0, clip.maxY());
475     drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
476     drawRadialGradient(cgContext, gradientWithName(ShineGradient), bottomCenter, 0, bottomCenter, std::max(clip.width(), clip.height()), ExponentialInterpolation);
477     return false;
478 }
479
480 bool RenderThemeIOS::paintTextFieldDecorations(const RenderObject& box, const PaintInfo& paintInfo, const FloatRect& rect)
481 {
482     RenderStyle& style = box.style();
483     FloatPoint point(rect.x() + style.borderLeftWidth(), rect.y() + style.borderTopWidth());
484
485     GraphicsContextStateSaver stateSaver(*paintInfo.context);
486
487     paintInfo.context->clipRoundedRect(style.getRoundedBorderFor(LayoutRect(rect)).pixelSnappedRoundedRectForPainting(box.document().deviceScaleFactor()));
488
489     // This gradient gets drawn black when printing.
490     // Do not draw the gradient if there is no visible top border.
491     bool topBorderIsInvisible = !style.hasBorder() || !style.borderTopWidth() || style.borderTopIsTransparent();
492     if (!box.view().printing() && !topBorderIsInvisible)
493         drawAxialGradient(paintInfo.context->platformContext(), gradientWithName(InsetGradient), point, FloatPoint(CGPointMake(point.x(), point.y() + 3.0f)), LinearInterpolation);
494     return false;
495 }
496
497 bool RenderThemeIOS::paintTextAreaDecorations(const RenderObject& box, const PaintInfo& paintInfo, const FloatRect& rect)
498 {
499     return paintTextFieldDecorations(box, paintInfo, rect);
500 }
501
502 const int MenuListMinHeight = 15;
503
504 const float MenuListBaseHeight = 20;
505 const float MenuListBaseFontSize = 11;
506
507 const float MenuListArrowWidth = 7;
508 const float MenuListArrowHeight = 6;
509 const float MenuListButtonPaddingRight = 19;
510
511 int RenderThemeIOS::popupInternalPaddingRight(RenderStyle& style) const
512 {
513     if (style.appearance() == MenulistButtonPart)
514         return MenuListButtonPaddingRight + style.borderTopWidth();
515     return 0;
516 }
517
518 void RenderThemeIOS::adjustRoundBorderRadius(RenderStyle& style, RenderBox& box)
519 {
520     if (style.appearance() == NoControlPart || style.backgroundLayers()->hasImage())
521         return;
522
523     // FIXME: We should not be relying on border radius for the appearance of our controls <rdar://problem/7675493>
524     Length radiusWidth(static_cast<int>(std::min(box.width(), box.height()) / 2.0), Fixed);
525     Length radiusHeight(static_cast<int>(box.height() / 2.0), Fixed);
526     style.setBorderRadius(LengthSize(radiusWidth, radiusHeight));
527 }
528
529 static void applyCommonButtonPaddingToStyle(RenderStyle& style, Element& element)
530 {
531     Document& document = element.document();
532     RefPtr<CSSPrimitiveValue> emSize = CSSPrimitiveValue::create(0.5, CSSPrimitiveValue::CSS_EMS);
533     int pixels = emSize->computeLength<int>(CSSToLengthConversionData(&style, document.renderStyle(), document.renderView(), document.frame()->pageZoomFactor()));
534     style.setPaddingBox(LengthBox(0, pixels, 0, pixels));
535 }
536
537 static void adjustSelectListButtonStyle(RenderStyle& style, Element& element)
538 {
539     // Enforce "padding: 0 0.5em".
540     applyCommonButtonPaddingToStyle(style, element);
541
542     // Enforce "line-height: normal".
543     style.setLineHeight(Length(-100.0, Percent));
544 }
545     
546 class RenderThemeMeasureTextClient : public MeasureTextClient {
547 public:
548     RenderThemeMeasureTextClient(const Font& font, RenderObject& renderObject, const RenderStyle& style)
549         : m_font(font)
550         , m_renderObject(renderObject)
551         , m_style(style)
552     {
553     }
554     virtual float measureText(const String& string) const override
555     {
556         TextRun run = RenderBlock::constructTextRun(&m_renderObject, m_font, string, m_style, TextRun::AllowTrailingExpansion | TextRun::ForbidLeadingExpansion, DefaultTextRunFlags);
557         return m_font.width(run);
558     }
559 private:
560     const Font& m_font;
561     RenderObject& m_renderObject;
562     const RenderStyle& m_style;
563 };
564
565 static void adjustInputElementButtonStyle(RenderStyle& style, HTMLInputElement& inputElement)
566 {
567     // Always Enforce "padding: 0 0.5em".
568     applyCommonButtonPaddingToStyle(style, inputElement);
569
570     // Don't adjust the style if the width is specified.
571     if (style.width().isFixed() && style.width().value() > 0)
572         return;
573
574     // Don't adjust for unsupported date input types.
575     DateComponents::Type dateType = inputElement.dateType();
576     if (dateType == DateComponents::Invalid || dateType == DateComponents::Week)
577         return;
578
579     // Enforce the width and set the box-sizing to content-box to not conflict with the padding.
580     Font font = style.font();
581     
582     RenderObject* renderer = inputElement.renderer();
583     if (font.primaryFont()->isSVGFont() && !renderer)
584         return;
585     
586     FontCachePurgePreventer fontCachePurgePreventer;
587     float maximumWidth = localizedDateCache().maximumWidthForDateType(dateType, font, RenderThemeMeasureTextClient(font, *renderer, style));
588
589     ASSERT(maximumWidth >= 0);
590
591     if (maximumWidth > 0) {    
592         int width = static_cast<int>(maximumWidth + MenuListButtonPaddingRight);
593         style.setWidth(Length(width, Fixed));
594         style.setBoxSizing(CONTENT_BOX);
595     }
596 }
597
598 void RenderThemeIOS::adjustMenuListButtonStyle(StyleResolver&, RenderStyle& style, Element* element) const
599 {
600     // Set the min-height to be at least MenuListMinHeight.
601     if (style.height().isAuto())
602         style.setMinHeight(Length(std::max(MenuListMinHeight, static_cast<int>(MenuListBaseHeight / MenuListBaseFontSize * style.fontDescription().computedSize())), Fixed));
603     else
604         style.setMinHeight(Length(MenuListMinHeight, Fixed));
605
606     if (!element)
607         return;
608
609     // Enforce some default styles in the case that this is a non-multiple <select> element,
610     // or a date input. We don't force these if this is just an element with
611     // "-webkit-appearance: menulist-button".
612     if (element->hasTagName(HTMLNames::selectTag) && !element->hasAttribute(HTMLNames::multipleAttr))
613         adjustSelectListButtonStyle(style, *element);
614     else if (element->hasTagName(HTMLNames::inputTag))
615         adjustInputElementButtonStyle(style, static_cast<HTMLInputElement&>(*element));
616 }
617
618 bool RenderThemeIOS::paintMenuListButtonDecorations(const RenderObject& box, const PaintInfo& paintInfo, const FloatRect& rect)
619 {
620     RenderStyle& style = box.style();
621     float borderTopWidth = style.borderTopWidth();
622     FloatRect clip(rect.x() + style.borderLeftWidth(), rect.y() + style.borderTopWidth(), rect.width() - style.borderLeftWidth() - style.borderRightWidth(), rect.height() - style.borderTopWidth() - style.borderBottomWidth());
623     CGContextRef cgContext = paintInfo.context->platformContext();
624
625     float adjustLeft = 0.5;
626     float adjustRight = 0.5;
627     float adjustTop = 0.5;
628     float adjustBottom = 0.5;
629
630     // Paint left-hand title portion.
631     {
632         FloatRect titleClip(clip.x() - adjustLeft, clip.y() - adjustTop, clip.width() - MenuListButtonPaddingRight + adjustLeft, clip.height() + adjustTop + adjustBottom);
633
634         GraphicsContextStateSaver stateSaver(*paintInfo.context);
635
636         paintInfo.context->clipRoundedRect(FloatRoundedRect(titleClip,
637             FloatSize(valueForLength(style.borderTopLeftRadius().width(), rect.width()) - style.borderLeftWidth(), valueForLength(style.borderTopLeftRadius().height(), rect.height()) - style.borderTopWidth()), FloatSize(0, 0),
638             FloatSize(valueForLength(style.borderBottomLeftRadius().width(), rect.width()) - style.borderLeftWidth(), valueForLength(style.borderBottomLeftRadius().height(), rect.height()) - style.borderBottomWidth()), FloatSize(0, 0)));
639
640         drawAxialGradient(cgContext, gradientWithName(ShadeGradient), titleClip.location(), FloatPoint(titleClip.x(), titleClip.maxY()), LinearInterpolation);
641         drawAxialGradient(cgContext, gradientWithName(ShineGradient), FloatPoint(titleClip.x(), titleClip.maxY()), titleClip.location(), ExponentialInterpolation);
642     }
643
644     // Draw the separator after the initial padding.
645
646     float separator = clip.maxX() - MenuListButtonPaddingRight;
647
648     box.drawLineForBoxSide(paintInfo.context, separator - borderTopWidth, clip.y(), separator, clip.maxY(), BSRight, style.visitedDependentColor(CSSPropertyBorderTopColor), style.borderTopStyle(), 0, 0);
649
650     FloatRect buttonClip(separator - adjustTop, clip.y() - adjustTop, MenuListButtonPaddingRight + adjustTop + adjustRight, clip.height() + adjustTop + adjustBottom);
651
652     // Now paint the button portion.
653     {
654         GraphicsContextStateSaver stateSaver(*paintInfo.context);
655
656         paintInfo.context->clipRoundedRect(FloatRoundedRect(buttonClip,
657             FloatSize(0, 0), FloatSize(valueForLength(style.borderTopRightRadius().width(), rect.width()) - style.borderRightWidth(), valueForLength(style.borderTopRightRadius().height(), rect.height()) - style.borderTopWidth()),
658             FloatSize(0, 0), FloatSize(valueForLength(style.borderBottomRightRadius().width(), rect.width()) - style.borderRightWidth(), valueForLength(style.borderBottomRightRadius().height(), rect.height()) - style.borderBottomWidth())));
659
660         paintInfo.context->fillRect(buttonClip, style.visitedDependentColor(CSSPropertyBorderTopColor), style.colorSpace());
661
662         drawAxialGradient(cgContext, gradientWithName(isFocused(box) && !isReadOnlyControl(box) ? ConcaveGradient : ConvexGradient), buttonClip.location(), FloatPoint(buttonClip.x(), buttonClip.maxY()), LinearInterpolation);
663     }
664
665     // Paint Indicators.
666
667     if (box.isMenuList() && downcast<HTMLSelectElement>(box.node())->multiple()) {
668         int size = 2;
669         int count = 3;
670         int padding = 3;
671
672         FloatRect ellipse(buttonClip.x() + (buttonClip.width() - count * (size + padding) + padding) / 2.0, buttonClip.maxY() - 10.0, size, size);
673
674         for (int i = 0; i < count; ++i) {
675             paintInfo.context->drawRaisedEllipse(ellipse, Color::white, ColorSpaceDeviceRGB, Color(0.0f, 0.0f, 0.0f, 0.5f), ColorSpaceDeviceRGB);
676             ellipse.move(size + padding, 0);
677         }
678     }  else {
679         float centerX = floorf(buttonClip.x() + buttonClip.width() / 2.0) - 0.5;
680         float centerY = floorf(buttonClip.y() + buttonClip.height() * 3.0 / 8.0);
681
682         FloatPoint arrow[3];
683         FloatPoint shadow[3];
684
685         arrow[0] = FloatPoint(centerX - MenuListArrowWidth / 2.0, centerY);
686         arrow[1] = FloatPoint(centerX + MenuListArrowWidth / 2.0, centerY);
687         arrow[2] = FloatPoint(centerX, centerY + MenuListArrowHeight);
688
689         shadow[0] = FloatPoint(arrow[0].x(), arrow[0].y() + 1.0f);
690         shadow[1] = FloatPoint(arrow[1].x(), arrow[1].y() + 1.0f);
691         shadow[2] = FloatPoint(arrow[2].x(), arrow[2].y() + 1.0f);
692
693         float opacity = isReadOnlyControl(box) ? 0.2 : 0.5;
694         paintInfo.context->setStrokeColor(Color(0.0f, 0.0f, 0.0f, opacity), ColorSpaceDeviceRGB);
695         paintInfo.context->setFillColor(Color(0.0f, 0.0f, 0.0f, opacity), ColorSpaceDeviceRGB);
696         paintInfo.context->drawConvexPolygon(3, shadow, true);
697
698         paintInfo.context->setStrokeColor(Color::white, ColorSpaceDeviceRGB);
699         paintInfo.context->setFillColor(Color::white, ColorSpaceDeviceRGB);
700         paintInfo.context->drawConvexPolygon(3, arrow, true);
701     }
702
703     return false;
704 }
705
706 const CGFloat kTrackThickness = 4.0;
707 const CGFloat kTrackRadius = kTrackThickness / 2.0;
708 const int kDefaultSliderThumbSize = 16;
709
710 void RenderThemeIOS::adjustSliderTrackStyle(StyleResolver& selector, RenderStyle& style, Element* element) const
711 {
712     RenderTheme::adjustSliderTrackStyle(selector, style, element);
713
714     // FIXME: We should not be relying on border radius for the appearance of our controls <rdar://problem/7675493>
715     Length radiusWidth(static_cast<int>(kTrackRadius), Fixed);
716     Length radiusHeight(static_cast<int>(kTrackRadius), Fixed);
717     style.setBorderRadius(LengthSize(radiusWidth, radiusHeight));
718 }
719
720 bool RenderThemeIOS::paintSliderTrack(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
721 {
722     IntRect trackClip = rect;
723     RenderStyle& style = box.style();
724
725     bool isHorizontal = true;
726     switch (style.appearance()) {
727     case SliderHorizontalPart:
728         isHorizontal = true;
729         // Inset slightly so the thumb covers the edge.
730         if (trackClip.width() > 2) {
731             trackClip.setWidth(trackClip.width() - 2);
732             trackClip.setX(trackClip.x() + 1);
733         }
734         trackClip.setHeight(static_cast<int>(kTrackThickness));
735         trackClip.setY(rect.y() + rect.height() / 2 - kTrackThickness / 2);
736         break;
737     case SliderVerticalPart:
738         isHorizontal = false;
739         // Inset slightly so the thumb covers the edge.
740         if (trackClip.height() > 2) {
741             trackClip.setHeight(trackClip.height() - 2);
742             trackClip.setY(trackClip.y() + 1);
743         }
744         trackClip.setWidth(kTrackThickness);
745         trackClip.setX(rect.x() + rect.width() / 2 - kTrackThickness / 2);
746         break;
747     default:
748         ASSERT_NOT_REACHED();
749     }
750
751     ASSERT(trackClip.width() >= 0);
752     ASSERT(trackClip.height() >= 0);
753     CGFloat cornerWidth = trackClip.width() < kTrackThickness ? trackClip.width() / 2.0f : kTrackRadius;
754     CGFloat cornerHeight = trackClip.height() < kTrackThickness ? trackClip.height() / 2.0f : kTrackRadius;
755
756     bool readonly = isReadOnlyControl(box);
757
758 #if ENABLE(DATALIST_ELEMENT)
759     paintSliderTicks(box, paintInfo, trackClip);
760 #endif
761
762     // Draw the track gradient.
763     {
764         GraphicsContextStateSaver stateSaver(*paintInfo.context);
765
766         IntSize cornerSize(cornerWidth, cornerHeight);
767         FloatRoundedRect innerBorder(trackClip, cornerSize, cornerSize, cornerSize, cornerSize);
768         paintInfo.context->clipRoundedRect(innerBorder);
769
770         CGContextRef cgContext = paintInfo.context->platformContext();
771         IOSGradientRef gradient = readonly ? gradientWithName(ReadonlySliderTrackGradient) : gradientWithName(SliderTrackGradient);
772         if (isHorizontal)
773             drawAxialGradient(cgContext, gradient, trackClip.location(), FloatPoint(trackClip.x(), trackClip.maxY()), LinearInterpolation);
774         else
775             drawAxialGradient(cgContext, gradient, trackClip.location(), FloatPoint(trackClip.maxX(), trackClip.y()), LinearInterpolation);
776     }
777
778     // Draw the track border.
779     {
780         GraphicsContextStateSaver stateSaver(*paintInfo.context);
781
782         CGContextRef cgContext = paintInfo.context->platformContext();
783         if (readonly)
784             paintInfo.context->setStrokeColor(Color(178, 178, 178), ColorSpaceDeviceRGB);
785         else
786             paintInfo.context->setStrokeColor(Color(76, 76, 76), ColorSpaceDeviceRGB);
787
788         RetainPtr<CGMutablePathRef> roundedRectPath = adoptCF(CGPathCreateMutable());
789         CGPathAddRoundedRect(roundedRectPath.get(), 0, trackClip, cornerWidth, cornerHeight);
790         CGContextAddPath(cgContext, roundedRectPath.get());
791         CGContextSetLineWidth(cgContext, 1);
792         CGContextStrokePath(cgContext);
793     }
794
795     return false;
796 }
797
798 void RenderThemeIOS::adjustSliderThumbSize(RenderStyle& style, Element*) const
799 {
800     if (style.appearance() != SliderThumbHorizontalPart && style.appearance() != SliderThumbVerticalPart)
801         return;
802
803     // Enforce "border-radius: 50%".
804     Length length(50.0f, Percent);
805     style.setBorderRadius(LengthSize(length, length));
806
807     // Enforce a 16x16 size if no size is provided.
808     if (style.width().isIntrinsicOrAuto() || style.height().isAuto()) {
809         Length length = Length(kDefaultSliderThumbSize, Fixed);
810         style.setWidth(length);
811         style.setHeight(length);
812     }
813 }
814
815 bool RenderThemeIOS::paintSliderThumbDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
816 {
817     GraphicsContextStateSaver stateSaver(*paintInfo.context);
818     FloatRect clip = addRoundedBorderClip(box, paintInfo.context, rect);
819
820     CGContextRef cgContext = paintInfo.context->platformContext();
821     FloatPoint bottomCenter(clip.x() + clip.width() / 2.0f, clip.maxY());
822     if (isPressed(box))
823         drawAxialGradient(cgContext, gradientWithName(SliderThumbOpaquePressedGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
824     else {
825         drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
826         drawRadialGradient(cgContext, gradientWithName(ShineGradient), bottomCenter, 0.0f, bottomCenter, std::max(clip.width(), clip.height()), ExponentialInterpolation);
827     }
828
829     return false;
830 }
831
832 double RenderThemeIOS::animationRepeatIntervalForProgressBar(RenderProgress&) const
833 {
834     return 0;
835 }
836
837 double RenderThemeIOS::animationDurationForProgressBar(RenderProgress&) const
838 {
839     return 0;
840 }
841
842 bool RenderThemeIOS::paintProgressBar(const RenderObject& renderer, const PaintInfo& paintInfo, const IntRect& rect)
843 {
844     if (!is<RenderProgress>(renderer))
845         return true;
846
847     const int progressBarHeight = 9;
848     const float verticalOffset = (rect.height() - progressBarHeight) / 2.0;
849
850     GraphicsContextStateSaver stateSaver(*paintInfo.context);
851     if (rect.width() < 10 || rect.height() < 9) {
852         // The rect is smaller than the standard progress bar. We clip to the element's rect to avoid
853         // leaking pixels outside the repaint rect.
854         paintInfo.context->clip(rect);
855     }
856
857     // 1) Draw the progress bar track.
858     // 1.1) Draw the white background with grey gradient border.
859     GraphicsContext* context = paintInfo.context;
860     context->setStrokeThickness(0.68);
861     context->setStrokeStyle(SolidStroke);
862
863     const float verticalRenderingPosition = rect.y() + verticalOffset;
864     RefPtr<Gradient> strokeGradient = Gradient::create(FloatPoint(rect.x(), verticalRenderingPosition), FloatPoint(rect.x(), verticalRenderingPosition + progressBarHeight - 1));
865     strokeGradient->addColorStop(0.0, Color(0x8d, 0x8d, 0x8d));
866     strokeGradient->addColorStop(0.45, Color(0xee, 0xee, 0xee));
867     strokeGradient->addColorStop(0.55, Color(0xee, 0xee, 0xee));
868     strokeGradient->addColorStop(1.0, Color(0x8d, 0x8d, 0x8d));
869     context->setStrokeGradient(strokeGradient.releaseNonNull());
870
871     ColorSpace colorSpace = renderer.style().colorSpace();
872     context->setFillColor(Color(255, 255, 255), colorSpace);
873
874     Path trackPath;
875     FloatRect trackRect(rect.x() + 0.25, verticalRenderingPosition + 0.25, rect.width() - 0.5, progressBarHeight - 0.5);
876     FloatSize roundedCornerRadius(5, 4);
877     trackPath.addRoundedRect(trackRect, roundedCornerRadius);
878     context->drawPath(trackPath);
879
880     // 1.2) Draw top gradient on the upper half. It is supposed to overlay the fill from the background and darker the stroked path.
881     FloatRect border(rect.x(), rect.y() + verticalOffset, rect.width(), progressBarHeight);
882     paintInfo.context->clipRoundedRect(FloatRoundedRect(border, roundedCornerRadius, roundedCornerRadius, roundedCornerRadius, roundedCornerRadius));
883
884     float upperGradientHeight = progressBarHeight / 2.;
885     RefPtr<Gradient> upperGradient = Gradient::create(FloatPoint(rect.x(), verticalRenderingPosition + 0.5), FloatPoint(rect.x(), verticalRenderingPosition + upperGradientHeight - 1.5));
886     upperGradient->addColorStop(0.0, Color(133, 133, 133, 188));
887     upperGradient->addColorStop(1.0, Color(18, 18, 18, 51));
888     context->setFillGradient(upperGradient.releaseNonNull());
889
890     context->fillRect(FloatRect(rect.x(), verticalRenderingPosition, rect.width(), upperGradientHeight));
891
892     const auto& renderProgress = downcast<RenderProgress>(renderer);
893     if (renderProgress.isDeterminate()) {
894         // 2) Draw the progress bar.
895         double position = clampTo(renderProgress.position(), 0.0, 1.0);
896         double barWidth = position * rect.width();
897         RefPtr<Gradient> barGradient = Gradient::create(FloatPoint(rect.x(), verticalRenderingPosition + 0.5), FloatPoint(rect.x(), verticalRenderingPosition + progressBarHeight - 1));
898         barGradient->addColorStop(0.0, Color(195, 217, 247));
899         barGradient->addColorStop(0.45, Color(118, 164, 228));
900         barGradient->addColorStop(0.49, Color(118, 164, 228));
901         barGradient->addColorStop(0.51, Color(36, 114, 210));
902         barGradient->addColorStop(0.55, Color(36, 114, 210));
903         barGradient->addColorStop(1.0, Color(57, 142, 244));
904         context->setFillGradient(barGradient.releaseNonNull());
905
906         RefPtr<Gradient> barStrokeGradient = Gradient::create(FloatPoint(rect.x(), verticalRenderingPosition), FloatPoint(rect.x(), verticalRenderingPosition + progressBarHeight - 1));
907         barStrokeGradient->addColorStop(0.0, Color(95, 107, 183));
908         barStrokeGradient->addColorStop(0.5, Color(66, 106, 174, 240));
909         barStrokeGradient->addColorStop(1.0, Color(38, 104, 166));
910         context->setStrokeGradient(barStrokeGradient.releaseNonNull());
911
912         Path barPath;
913         int left = rect.x();
914         if (!renderProgress.style().isLeftToRightDirection())
915             left = rect.maxX() - barWidth;
916         FloatRect barRect(left + 0.25, verticalRenderingPosition + 0.25, std::max(barWidth - 0.5, 0.0), progressBarHeight - 0.5);
917         barPath.addRoundedRect(barRect, roundedCornerRadius);
918         context->drawPath(barPath);
919     }
920
921     return false;
922 }
923
924 #if ENABLE(DATALIST_ELEMENT)
925 IntSize RenderThemeIOS::sliderTickSize() const
926 {
927     // FIXME: <rdar://problem/12271791> MERGEBOT: Correct values for slider tick of <input type="range"> elements (requires ENABLE_DATALIST_ELEMENT)
928     return IntSize(1, 3);
929 }
930
931 int RenderThemeIOS::sliderTickOffsetFromTrackCenter() const
932 {
933     // FIXME: <rdar://problem/12271791> MERGEBOT: Correct values for slider tick of <input type="range"> elements (requires ENABLE_DATALIST_ELEMENT)
934     return -9;
935 }
936 #endif
937
938 void RenderThemeIOS::adjustSearchFieldStyle(StyleResolver& selector, RenderStyle& style, Element* element) const
939 {
940     RenderTheme::adjustSearchFieldStyle(selector, style, element);
941
942     if (!element)
943         return;
944
945     if (!style.hasBorder())
946         return;
947
948     RenderBox* box = element->renderBox();
949     if (!box)
950         return;
951
952     adjustRoundBorderRadius(style, *box);
953 }
954
955 bool RenderThemeIOS::paintSearchFieldDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
956 {
957     return paintTextFieldDecorations(box, paintInfo, rect);
958 }
959
960 void RenderThemeIOS::adjustButtonStyle(StyleResolver& selector, RenderStyle& style, Element* element) const
961 {
962     RenderTheme::adjustButtonStyle(selector, style, element);
963
964     // Set padding: 0 1.0em; on buttons.
965     // CSSPrimitiveValue::computeLengthInt only needs the element's style to calculate em lengths.
966     // Since the element might not be in a document, just pass nullptr for the root element style
967     // and the render view.
968     RefPtr<CSSPrimitiveValue> emSize = CSSPrimitiveValue::create(1.0, CSSPrimitiveValue::CSS_EMS);
969     int pixels = emSize->computeLength<int>(CSSToLengthConversionData(&style, nullptr, nullptr, 1.0, false));
970     style.setPaddingBox(LengthBox(0, pixels, 0, pixels));
971
972     if (!element)
973         return;
974
975     RenderBox* box = element->renderBox();
976     if (!box)
977         return;
978
979     adjustRoundBorderRadius(style, *box);
980 }
981
982 bool RenderThemeIOS::paintButtonDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
983 {
984     return paintPushButtonDecorations(box, paintInfo, rect);
985 }
986
987 bool RenderThemeIOS::paintPushButtonDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
988 {
989     GraphicsContextStateSaver stateSaver(*paintInfo.context);
990     FloatRect clip = addRoundedBorderClip(box, paintInfo.context, rect);
991
992     CGContextRef cgContext = paintInfo.context->platformContext();
993     if (box.style().visitedDependentColor(CSSPropertyBackgroundColor).isDark())
994         drawAxialGradient(cgContext, gradientWithName(ConvexGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
995     else {
996         drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
997         drawAxialGradient(cgContext, gradientWithName(ShineGradient), FloatPoint(clip.x(), clip.maxY()), clip.location(), ExponentialInterpolation);
998     }
999     return false;
1000 }
1001
1002 void RenderThemeIOS::setButtonSize(RenderStyle& style) const
1003 {
1004     // If the width and height are both specified, then we have nothing to do.
1005     if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
1006         return;
1007
1008     // Use the font size to determine the intrinsic width of the control.
1009     style.setHeight(Length(static_cast<int>(ControlBaseHeight / ControlBaseFontSize * style.fontDescription().computedSize()), Fixed));
1010 }
1011
1012 const int kThumbnailBorderStrokeWidth = 1;
1013 const int kThumbnailBorderCornerRadius = 1;
1014 const int kVisibleBackgroundImageWidth = 1;
1015 const int kMultipleThumbnailShrinkSize = 2;
1016
1017 bool RenderThemeIOS::paintFileUploadIconDecorations(const RenderObject&, const RenderObject& buttonRenderer, const PaintInfo& paintInfo, const IntRect& rect, Icon* icon, FileUploadDecorations fileUploadDecorations)
1018 {
1019     GraphicsContextStateSaver stateSaver(*paintInfo.context);
1020
1021     IntSize cornerSize(kThumbnailBorderCornerRadius, kThumbnailBorderCornerRadius);
1022     Color pictureFrameColor = buttonRenderer.style().visitedDependentColor(CSSPropertyBorderTopColor);
1023
1024     IntRect thumbnailPictureFrameRect = rect;
1025     IntRect thumbnailRect = rect;
1026     thumbnailRect.contract(2 * kThumbnailBorderStrokeWidth, 2 * kThumbnailBorderStrokeWidth);
1027     thumbnailRect.move(kThumbnailBorderStrokeWidth, kThumbnailBorderStrokeWidth);
1028
1029     if (fileUploadDecorations == MultipleFiles) {
1030         // Smaller thumbnails for multiple selection appearance.
1031         thumbnailPictureFrameRect.contract(kMultipleThumbnailShrinkSize, kMultipleThumbnailShrinkSize);
1032         thumbnailRect.contract(kMultipleThumbnailShrinkSize, kMultipleThumbnailShrinkSize);
1033
1034         // Background picture frame and simple background icon with a gradient matching the button.
1035         Color backgroundImageColor = Color(buttonRenderer.style().visitedDependentColor(CSSPropertyBackgroundColor).rgb());
1036         paintInfo.context->fillRoundedRect(FloatRoundedRect(thumbnailPictureFrameRect, cornerSize, cornerSize, cornerSize, cornerSize), pictureFrameColor, ColorSpaceDeviceRGB);
1037         paintInfo.context->fillRect(thumbnailRect, backgroundImageColor, ColorSpaceDeviceRGB);
1038         {
1039             GraphicsContextStateSaver stateSaver2(*paintInfo.context);
1040             CGContextRef cgContext = paintInfo.context->platformContext();
1041             paintInfo.context->clip(thumbnailRect);
1042             if (backgroundImageColor.isDark())
1043                 drawAxialGradient(cgContext, gradientWithName(ConvexGradient), thumbnailRect.location(), FloatPoint(thumbnailRect.x(), thumbnailRect.maxY()), LinearInterpolation);
1044             else {
1045                 drawAxialGradient(cgContext, gradientWithName(ShadeGradient), thumbnailRect.location(), FloatPoint(thumbnailRect.x(), thumbnailRect.maxY()), LinearInterpolation);
1046                 drawAxialGradient(cgContext, gradientWithName(ShineGradient), FloatPoint(thumbnailRect.x(), thumbnailRect.maxY()), thumbnailRect.location(), ExponentialInterpolation);
1047             }
1048         }
1049
1050         // Move the rects for the Foreground picture frame and icon.
1051         int inset = kVisibleBackgroundImageWidth + kThumbnailBorderStrokeWidth;
1052         thumbnailPictureFrameRect.move(inset, inset);
1053         thumbnailRect.move(inset, inset);
1054     }
1055
1056     // Foreground picture frame and icon.
1057     paintInfo.context->fillRoundedRect(FloatRoundedRect(thumbnailPictureFrameRect, cornerSize, cornerSize, cornerSize, cornerSize), pictureFrameColor, ColorSpaceDeviceRGB);
1058     icon->paint(paintInfo.context, thumbnailRect);
1059
1060     return false;
1061 }
1062
1063 Color RenderThemeIOS::platformActiveSelectionBackgroundColor() const
1064 {
1065     return Color::transparent;
1066 }
1067
1068 Color RenderThemeIOS::platformInactiveSelectionBackgroundColor() const
1069 {
1070     return Color::transparent;
1071 }
1072
1073 bool RenderThemeIOS::shouldShowPlaceholderWhenFocused() const
1074 {
1075     return true;
1076 }
1077
1078 bool RenderThemeIOS::shouldHaveSpinButton(HTMLInputElement&) const
1079 {
1080     return false;
1081 }
1082
1083 static FontWeight fromCTFontWeight(float fontWeight)
1084 {
1085     if (fontWeight <= -0.8)
1086         return FontWeight100;
1087     else if (fontWeight <= -0.4)
1088         return FontWeight200;
1089     else if (fontWeight <= -0.2)
1090         return FontWeight300;
1091     else if (fontWeight <= 0.0)
1092         return FontWeight400;
1093     else if (fontWeight <= 0.2)
1094         return FontWeight500;
1095     else if (fontWeight <= 0.3)
1096         return FontWeight600;
1097     else if (fontWeight <= 0.4)
1098         return FontWeight700;
1099     else if (fontWeight <= 0.6)
1100         return FontWeight800;
1101     else if (fontWeight <= 0.8)
1102         return FontWeight900;
1103
1104     return FontWeightNormal;
1105 }
1106
1107 void RenderThemeIOS::systemFont(CSSValueID valueID, FontDescription& fontDescription) const
1108 {
1109     static NeverDestroyed<FontDescription> systemFont;
1110     static NeverDestroyed<FontDescription> headlineFont;
1111     static NeverDestroyed<FontDescription> bodyFont;
1112     static NeverDestroyed<FontDescription> subheadlineFont;
1113     static NeverDestroyed<FontDescription> footnoteFont;
1114     static NeverDestroyed<FontDescription> caption1Font;
1115     static NeverDestroyed<FontDescription> caption2Font;
1116     static NeverDestroyed<FontDescription> shortHeadlineFont;
1117     static NeverDestroyed<FontDescription> shortBodyFont;
1118     static NeverDestroyed<FontDescription> shortSubheadlineFont;
1119     static NeverDestroyed<FontDescription> shortFootnoteFont;
1120     static NeverDestroyed<FontDescription> shortCaption1Font;
1121     static NeverDestroyed<FontDescription> tallBodyFont;
1122
1123     static CFStringRef userTextSize = contentSizeCategory();
1124
1125     if (userTextSize != contentSizeCategory()) {
1126         userTextSize = contentSizeCategory();
1127
1128         headlineFont.get().setIsAbsoluteSize(false);
1129         bodyFont.get().setIsAbsoluteSize(false);
1130         subheadlineFont.get().setIsAbsoluteSize(false);
1131         footnoteFont.get().setIsAbsoluteSize(false);
1132         caption1Font.get().setIsAbsoluteSize(false);
1133         caption2Font.get().setIsAbsoluteSize(false);
1134         shortHeadlineFont.get().setIsAbsoluteSize(false);
1135         shortBodyFont.get().setIsAbsoluteSize(false);
1136         shortSubheadlineFont.get().setIsAbsoluteSize(false);
1137         shortFootnoteFont.get().setIsAbsoluteSize(false);
1138         shortCaption1Font.get().setIsAbsoluteSize(false);
1139         tallBodyFont.get().setIsAbsoluteSize(false);
1140     }
1141
1142     FontDescription* cachedDesc;
1143     RetainPtr<CTFontDescriptorRef> fontDescriptor;
1144     CFStringRef textStyle;
1145     switch (valueID) {
1146     case CSSValueAppleSystemHeadline:
1147         cachedDesc = &headlineFont.get();
1148         textStyle = kCTUIFontTextStyleHeadline;
1149         if (!headlineFont.get().isAbsoluteSize())
1150             fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, userTextSize, 0));
1151         break;
1152     case CSSValueAppleSystemBody:
1153         cachedDesc = &bodyFont.get();
1154         textStyle = kCTUIFontTextStyleBody;
1155         if (!bodyFont.get().isAbsoluteSize())
1156             fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, userTextSize, 0));
1157         break;
1158     case CSSValueAppleSystemSubheadline:
1159         cachedDesc = &subheadlineFont.get();
1160         textStyle = kCTUIFontTextStyleSubhead;
1161         if (!subheadlineFont.get().isAbsoluteSize())
1162             fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, userTextSize, 0));
1163         break;
1164     case CSSValueAppleSystemFootnote:
1165         cachedDesc = &footnoteFont.get();
1166         textStyle = kCTUIFontTextStyleFootnote;
1167         if (!footnoteFont.get().isAbsoluteSize())
1168             fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, userTextSize, 0));
1169         break;
1170     case CSSValueAppleSystemCaption1:
1171         cachedDesc = &caption1Font.get();
1172         textStyle = kCTUIFontTextStyleCaption1;
1173         if (!caption1Font.get().isAbsoluteSize())
1174             fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, userTextSize, 0));
1175         break;
1176     case CSSValueAppleSystemCaption2:
1177         cachedDesc = &caption2Font.get();
1178         textStyle = kCTUIFontTextStyleCaption2;
1179         if (!caption2Font.get().isAbsoluteSize())
1180             fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, userTextSize, 0));
1181         break;
1182
1183     // Short version.
1184     case CSSValueAppleSystemShortHeadline:
1185         cachedDesc = &shortHeadlineFont.get();
1186         textStyle = kCTUIFontTextStyleShortHeadline;
1187         if (!shortHeadlineFont.get().isAbsoluteSize())
1188             fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, userTextSize, 0));
1189         break;
1190     case CSSValueAppleSystemShortBody:
1191         cachedDesc = &shortBodyFont.get();
1192         textStyle = kCTUIFontTextStyleShortBody;
1193         if (!shortBodyFont.get().isAbsoluteSize())
1194             fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, userTextSize, 0));
1195         break;
1196     case CSSValueAppleSystemShortSubheadline:
1197         cachedDesc = &shortSubheadlineFont.get();
1198         textStyle = kCTUIFontTextStyleShortSubhead;
1199         if (!shortSubheadlineFont.get().isAbsoluteSize())
1200             fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, userTextSize, 0));
1201         break;
1202     case CSSValueAppleSystemShortFootnote:
1203         cachedDesc = &shortFootnoteFont.get();
1204         textStyle = kCTUIFontTextStyleShortFootnote;
1205         if (!shortFootnoteFont.get().isAbsoluteSize())
1206             fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, userTextSize, 0));
1207         break;
1208     case CSSValueAppleSystemShortCaption1:
1209         cachedDesc = &shortCaption1Font.get();
1210         textStyle = kCTUIFontTextStyleShortCaption1;
1211         if (!shortCaption1Font.get().isAbsoluteSize())
1212             fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, userTextSize, 0));
1213         break;
1214
1215     // Tall version.
1216     case CSSValueAppleSystemTallBody:
1217         cachedDesc = &tallBodyFont.get();
1218         textStyle = kCTUIFontTextStyleTallBody;
1219         if (!tallBodyFont.get().isAbsoluteSize())
1220             fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, userTextSize, 0));
1221         break;
1222
1223     default:
1224         textStyle = kCTFontDescriptorTextStyleEmphasized;
1225         cachedDesc = &systemFont.get();
1226         if (!systemFont.get().isAbsoluteSize())
1227             fontDescriptor = adoptCF(CTFontDescriptorCreateForUIType(kCTFontSystemFontType, 0, nullptr));
1228     }
1229
1230     if (fontDescriptor) {
1231         RetainPtr<CTFontRef> font = adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor.get(), 0, nullptr));
1232         cachedDesc->setIsAbsoluteSize(true);
1233         cachedDesc->setGenericFamily(FontDescription::NoFamily);
1234         cachedDesc->setOneFamily(textStyle);
1235         cachedDesc->setSpecifiedSize(CTFontGetSize(font.get()));
1236         cachedDesc->setWeight(fromCTFontWeight(FontCache::weightOfCTFont(font.get())));
1237         cachedDesc->setItalic(0);
1238     }
1239     fontDescription = *cachedDesc;
1240 }
1241
1242 #if ENABLE(VIDEO)
1243 String RenderThemeIOS::mediaControlsStyleSheet()
1244 {
1245 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
1246     if (m_mediaControlsStyleSheet.isEmpty()) {
1247         StringBuilder builder;
1248         builder.append([NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"mediaControlsiOS" ofType:@"css"] encoding:NSUTF8StringEncoding error:nil]);
1249         builder.append(wkGetMediaUIImageData(wkMediaUIPartOptimizedFullscreenButton));
1250         m_mediaControlsStyleSheet = builder.toString();
1251     }
1252     return m_mediaControlsStyleSheet;
1253 #else
1254     return emptyString();
1255 #endif
1256 }
1257
1258 String RenderThemeIOS::mediaControlsScript()
1259 {
1260 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
1261     if (m_mediaControlsScript.isEmpty()) {
1262         StringBuilder scriptBuilder;
1263         scriptBuilder.append([NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"mediaControlsLocalizedStrings" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1264         scriptBuilder.append([NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"mediaControlsApple" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1265         scriptBuilder.append([NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"mediaControlsiOS" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1266         m_mediaControlsScript = scriptBuilder.toString();
1267     }
1268     return m_mediaControlsScript;
1269 #else
1270     return emptyString();
1271 #endif
1272 }
1273 #endif // ENABLE(VIDEO)
1274
1275 }
1276
1277 #endif //PLATFORM(IOS)