36fbb629601c6e0cccdf7c64a22c344ed9ef03db
[WebKit-https.git] / Source / WebCore / rendering / RenderThemeIOS.mm
1 /*
2  * Copyright (C) 2005-2017 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 #import "RenderThemeIOS.h"
28
29 #if PLATFORM(IOS_FAMILY)
30
31 #import "BitmapImage.h"
32 #import "CSSPrimitiveValue.h"
33 #import "CSSToLengthConversionData.h"
34 #import "CSSValueKey.h"
35 #import "CSSValueKeywords.h"
36 #import "ColorIOS.h"
37 #import "DateComponents.h"
38 #import "Document.h"
39 #import "File.h"
40 #import "FloatRoundedRect.h"
41 #import "FontCache.h"
42 #import "FontCascade.h"
43 #import "Frame.h"
44 #import "FrameSelection.h"
45 #import "FrameView.h"
46 #import "GeometryUtilities.h"
47 #import "Gradient.h"
48 #import "GraphicsContext.h"
49 #import "GraphicsContextCG.h"
50 #import "HTMLAttachmentElement.h"
51 #import "HTMLInputElement.h"
52 #import "HTMLNames.h"
53 #import "HTMLSelectElement.h"
54 #import "IOSurface.h"
55 #import "Icon.h"
56 #import "LocalCurrentTraitCollection.h"
57 #import "LocalizedDateCache.h"
58 #import "NodeRenderStyle.h"
59 #import "Page.h"
60 #import "PaintInfo.h"
61 #import "PathUtilities.h"
62 #import "PlatformLocale.h"
63 #import "RenderAttachment.h"
64 #import "RenderObject.h"
65 #import "RenderProgress.h"
66 #import "RenderStyle.h"
67 #import "RenderView.h"
68 #import "RuntimeEnabledFeatures.h"
69 #import "UTIUtilities.h"
70 #import "UserAgentScripts.h"
71 #import "UserAgentStyleSheets.h"
72 #import "WebCoreThreadRun.h"
73 #import <CoreGraphics/CoreGraphics.h>
74 #import <CoreImage/CoreImage.h>
75 #import <objc/runtime.h>
76 #import <pal/ios/UIKitSoftLink.h>
77 #import <pal/spi/cocoa/CoreTextSPI.h>
78 #import <pal/spi/ios/UIKitSPI.h>
79 #import <wtf/NeverDestroyed.h>
80 #import <wtf/ObjCRuntimeExtras.h>
81 #import <wtf/RefPtr.h>
82 #import <wtf/StdLibExtras.h>
83
84 @interface WebCoreRenderThemeBundle : NSObject
85 @end
86
87 @implementation WebCoreRenderThemeBundle
88 @end
89
90 namespace WebCore {
91
92 using namespace HTMLNames;
93
94 const float ControlBaseHeight = 20;
95 const float ControlBaseFontSize = 11;
96
97 struct IOSGradient {
98     float* start; // points to static float[4]
99     float* end; // points to static float[4]
100     IOSGradient(float start[4], float end[4])
101         : start(start)
102         , end(end)
103     {
104     }
105 };
106
107 typedef IOSGradient* IOSGradientRef;
108
109 enum Interpolation
110 {
111     LinearInterpolation,
112     ExponentialInterpolation
113 };
114
115 static void interpolateLinearGradient(void *info, const CGFloat *inData, CGFloat *outData)
116 {
117     IOSGradientRef gradient = static_cast<IOSGradientRef>(info);
118     float alpha = inData[0];
119     float inverse = 1.0f - alpha;
120
121     outData[0] = inverse * gradient->start[0] + alpha * gradient->end[0];
122     outData[1] = inverse * gradient->start[1] + alpha * gradient->end[1];
123     outData[2] = inverse * gradient->start[2] + alpha * gradient->end[2];
124     outData[3] = inverse * gradient->start[3] + alpha * gradient->end[3];
125 }
126
127 static void interpolateExponentialGradient(void *info, const CGFloat *inData, CGFloat *outData)
128 {
129     IOSGradientRef gradient = static_cast<IOSGradientRef>(info);
130     float a = inData[0];
131     for (int paintInfo = 0; paintInfo < 4; ++paintInfo) {
132         float end = logf(std::max(gradient->end[paintInfo], 0.01f));
133         float start = logf(std::max(gradient->start[paintInfo], 0.01f));
134         outData[paintInfo] = expf(start - (end + start) * a);
135     }
136 }
137
138 static CGFunctionRef getSharedFunctionRef(IOSGradientRef gradient, Interpolation interpolation)
139 {
140     CGFunctionRef function = nullptr;
141
142     static HashMap<IOSGradientRef, CGFunctionRef>* linearFunctionRefs;
143     static HashMap<IOSGradientRef, CGFunctionRef>* exponentialFunctionRefs;
144
145     if (interpolation == LinearInterpolation) {
146         if (!linearFunctionRefs)
147             linearFunctionRefs = new HashMap<IOSGradientRef, CGFunctionRef>;
148         else
149             function = linearFunctionRefs->get(gradient);
150     
151         if (!function) {
152             static struct CGFunctionCallbacks linearFunctionCallbacks =  { 0, interpolateLinearGradient, 0 };
153             linearFunctionRefs->set(gradient, function = CGFunctionCreate(gradient, 1, nullptr, 4, nullptr, &linearFunctionCallbacks));
154         }
155
156         return function;
157     }
158
159     if (!exponentialFunctionRefs)
160         exponentialFunctionRefs = new HashMap<IOSGradientRef, CGFunctionRef>;
161     else
162         function = exponentialFunctionRefs->get(gradient);
163
164     if (!function) {
165         static struct CGFunctionCallbacks exponentialFunctionCallbacks =  { 0, interpolateExponentialGradient, 0 };
166         exponentialFunctionRefs->set(gradient, function = CGFunctionCreate(gradient, 1, 0, 4, 0, &exponentialFunctionCallbacks));
167     }
168
169     return function;
170 }
171
172 static void drawAxialGradient(CGContextRef context, IOSGradientRef gradient, const FloatPoint& startPoint, const FloatPoint& stopPoint, Interpolation interpolation)
173 {
174     RetainPtr<CGShadingRef> shading = adoptCF(CGShadingCreateAxial(sRGBColorSpaceRef(), startPoint, stopPoint, getSharedFunctionRef(gradient, interpolation), false, false));
175     CGContextDrawShading(context, shading.get());
176 }
177
178 static void drawRadialGradient(CGContextRef context, IOSGradientRef gradient, const FloatPoint& startPoint, float startRadius, const FloatPoint& stopPoint, float stopRadius, Interpolation interpolation)
179 {
180     RetainPtr<CGShadingRef> shading = adoptCF(CGShadingCreateRadial(sRGBColorSpaceRef(), startPoint, startRadius, stopPoint, stopRadius, getSharedFunctionRef(gradient, interpolation), false, false));
181     CGContextDrawShading(context, shading.get());
182 }
183
184 enum IOSGradientType {
185     InsetGradient,
186     ShineGradient,
187     ShadeGradient,
188     ConvexGradient,
189     ConcaveGradient,
190     SliderTrackGradient,
191     ReadonlySliderTrackGradient,
192     SliderThumbOpaquePressedGradient,
193 };
194
195 static IOSGradientRef getInsetGradient()
196 {
197     static float end[4] = { 0 / 255.0, 0 / 255.0, 0 / 255.0, 0 };
198     static float start[4] = { 0 / 255.0, 0 / 255.0, 0 / 255.0, 0.2 };
199     static NeverDestroyed<IOSGradient> gradient(start, end);
200     return &gradient.get();
201 }
202
203 static IOSGradientRef getShineGradient()
204 {
205     static float end[4] = { 1, 1, 1, 0.8 };
206     static float start[4] = { 1, 1, 1, 0 };
207     static NeverDestroyed<IOSGradient> gradient(start, end);
208     return &gradient.get();
209 }
210
211 static IOSGradientRef getShadeGradient()
212 {
213     static float end[4] = { 178 / 255.0, 178 / 255.0, 178 / 255.0, 0.65 };
214     static float start[4] = { 252 / 255.0, 252 / 255.0, 252 / 255.0, 0.65 };
215     static NeverDestroyed<IOSGradient> gradient(start, end);
216     return &gradient.get();
217 }
218
219 static IOSGradientRef getConvexGradient()
220 {
221     static float end[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0.05 };
222     static float start[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0.43 };
223     static NeverDestroyed<IOSGradient> gradient(start, end);
224     return &gradient.get();
225 }
226
227 static IOSGradientRef getConcaveGradient()
228 {
229     static float end[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0.46 };
230     static float start[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0 };
231     static NeverDestroyed<IOSGradient> gradient(start, end);
232     return &gradient.get();
233 }
234
235 static IOSGradientRef getSliderTrackGradient()
236 {
237     static float end[4] = { 132 / 255.0, 132 / 255.0, 132 / 255.0, 1 };
238     static float start[4] = { 74 / 255.0, 77 / 255.0, 80 / 255.0, 1 };
239     static NeverDestroyed<IOSGradient> gradient(start, end);
240     return &gradient.get();
241 }
242
243 static IOSGradientRef getReadonlySliderTrackGradient()
244 {
245     static float end[4] = { 132 / 255.0, 132 / 255.0, 132 / 255.0, 0.4 };
246     static float start[4] = { 74 / 255.0, 77 / 255.0, 80 /255.0, 0.4 };
247     static NeverDestroyed<IOSGradient> gradient(start, end);
248     return &gradient.get();
249 }
250
251 static IOSGradientRef getSliderThumbOpaquePressedGradient()
252 {
253     static float end[4] = { 144 / 255.0, 144 / 255.0, 144 / 255.0, 1};
254     static float start[4] = { 55 / 255.0, 55 / 255.0, 55 / 255.0, 1 };
255     static NeverDestroyed<IOSGradient> gradient(start, end);
256     return &gradient.get();
257 }
258
259 static IOSGradientRef gradientWithName(IOSGradientType gradientType)
260 {
261     switch (gradientType) {
262     case InsetGradient:
263         return getInsetGradient();
264     case ShineGradient:
265         return getShineGradient();
266     case ShadeGradient:
267         return getShadeGradient();
268     case ConvexGradient:
269         return getConvexGradient();
270     case ConcaveGradient:
271         return getConcaveGradient();
272     case SliderTrackGradient:
273         return getSliderTrackGradient();
274     case ReadonlySliderTrackGradient:
275         return getReadonlySliderTrackGradient();
276     case SliderThumbOpaquePressedGradient:
277         return getSliderThumbOpaquePressedGradient();
278     }
279     ASSERT_NOT_REACHED();
280     return nullptr;
281 }
282
283 static void contentSizeCategoryDidChange(CFNotificationCenterRef, void*, CFStringRef name, const void*, CFDictionaryRef)
284 {
285     ASSERT_UNUSED(name, CFEqual(name, PAL::get_UIKit_UIContentSizeCategoryDidChangeNotification()));
286     WebThreadRun(^{
287         Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment();
288     });
289 }
290
291 RenderThemeIOS::RenderThemeIOS()
292 {
293     CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), this, contentSizeCategoryDidChange, (__bridge CFStringRef)PAL::get_UIKit_UIContentSizeCategoryDidChangeNotification(), 0, CFNotificationSuspensionBehaviorDeliverImmediately);
294 }
295
296 RenderTheme& RenderTheme::singleton()
297 {
298     static NeverDestroyed<RenderThemeIOS> theme;
299     return theme;
300 }
301
302 static String& _contentSizeCategory()
303 {
304     static NeverDestroyed<String> _contentSizeCategory;
305     return _contentSizeCategory.get();
306 }
307
308 CFStringRef RenderThemeIOS::contentSizeCategory()
309 {
310     if (!_contentSizeCategory().isNull())
311         return (__bridge CFStringRef)static_cast<NSString*>(_contentSizeCategory());
312     return (CFStringRef)[[PAL::getUIApplicationClass() sharedApplication] preferredContentSizeCategory];
313 }
314
315 void RenderThemeIOS::setContentSizeCategory(const String& contentSizeCategory)
316 {
317     _contentSizeCategory() = contentSizeCategory;
318 }
319
320 FloatRect RenderThemeIOS::addRoundedBorderClip(const RenderObject& box, GraphicsContext& context, const IntRect& rect)
321 {
322     // To fix inner border bleeding issues <rdar://problem/9812507>, we clip to the outer border and assert that
323     // the border is opaque or transparent, unless we're checked because checked radio/checkboxes show no bleeding.
324     auto& style = box.style();
325     RoundedRect border = isChecked(box) ? style.getRoundedInnerBorderFor(rect) : style.getRoundedBorderFor(rect);
326
327     if (border.isRounded())
328         context.clipRoundedRect(FloatRoundedRect(border));
329     else
330         context.clip(border.rect());
331
332     if (isChecked(box)) {
333         ASSERT(style.visitedDependentColor(CSSPropertyBorderTopColor).alpha() % 255 == 0);
334         ASSERT(style.visitedDependentColor(CSSPropertyBorderRightColor).alpha() % 255 == 0);
335         ASSERT(style.visitedDependentColor(CSSPropertyBorderBottomColor).alpha() % 255 == 0);
336         ASSERT(style.visitedDependentColor(CSSPropertyBorderLeftColor).alpha() % 255 == 0);
337     }
338
339     return border.rect();
340 }
341
342 void RenderThemeIOS::adjustCheckboxStyle(RenderStyle& style, const Element*) const
343 {
344     if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
345         return;
346
347     int size = std::max(style.computedFontPixelSize(), 10U);
348     style.setWidth({ size, Fixed });
349     style.setHeight({ size, Fixed });
350 }
351
352 static CGPoint shortened(CGPoint start, CGPoint end, float width)
353 {
354     float x = end.x - start.x;
355     float y = end.y - start.y;
356     float ratio = (!x && !y) ? 0 : width / std::hypot(x, y);
357     return CGPointMake(start.x + x * ratio, start.y + y * ratio);
358 }
359
360 static void drawJoinedLines(CGContextRef context, const Vector<CGPoint>& points, CGLineCap lineCap, float lineWidth, Color strokeColor)
361 {
362     CGContextSetLineWidth(context, lineWidth);
363     CGContextSetStrokeColorWithColor(context, cachedCGColor(strokeColor));
364     CGContextSetShouldAntialias(context, true);
365     CGContextBeginPath(context);
366     CGContextSetLineCap(context, lineCap);
367     CGContextMoveToPoint(context, points[0].x, points[0].y);
368     
369     for (unsigned i = 1; i < points.size(); ++i)
370         CGContextAddLineToPoint(context, points[i].x, points[i].y);
371
372     CGContextStrokePath(context);
373 }
374
375 bool RenderThemeIOS::paintCheckboxDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
376 {
377     bool checked = isChecked(box);
378     bool indeterminate = isIndeterminate(box);
379     CGContextRef cgContext = paintInfo.context().platformContext();
380     GraphicsContextStateSaver stateSaver { paintInfo.context() };
381
382     if (checked || indeterminate) {
383         auto border = box.style().getRoundedBorderFor(rect);
384         paintInfo.context().fillRoundedRect(border.pixelSnappedRoundedRectForPainting(box.document().deviceScaleFactor()), makeSimpleColorFromFloats(0.0f, 0.0f, 0.0f, 0.8f));
385
386         auto clip = addRoundedBorderClip(box, paintInfo.context(), rect);
387         auto width = clip.width();
388         auto height = clip.height();
389         drawAxialGradient(cgContext, gradientWithName(ConcaveGradient), clip.location(), FloatPoint { clip.x(), clip.maxY() }, LinearInterpolation);
390
391         constexpr float thicknessRatio = 2 / 14.0;
392         float lineWidth = std::min(width, height) * 2.0f * thicknessRatio;
393
394         Vector<CGPoint, 3> line;
395         Vector<CGPoint, 3> shadow;
396         if (checked) {
397             constexpr CGSize size { 14.0f, 14.0f };
398             constexpr CGPoint pathRatios[] = {
399                 { 2.5f / size.width, 7.5f / size.height },
400                 { 5.5f / size.width, 10.5f / size.height },
401                 { 11.5f / size.width, 2.5f / size.height }
402             };
403
404             line.uncheckedAppend(CGPointMake(clip.x() + width * pathRatios[0].x, clip.y() + height * pathRatios[0].y));
405             line.uncheckedAppend(CGPointMake(clip.x() + width * pathRatios[1].x, clip.y() + height * pathRatios[1].y));
406             line.uncheckedAppend(CGPointMake(clip.x() + width * pathRatios[2].x, clip.y() + height * pathRatios[2].y));
407
408             shadow.uncheckedAppend(shortened(line[0], line[1], lineWidth / 4.0f));
409             shadow.uncheckedAppend(line[1]);
410             shadow.uncheckedAppend(shortened(line[2], line[1], lineWidth / 4.0f));
411         } else {
412             line.uncheckedAppend(CGPointMake(clip.x() + 3.5, clip.center().y()));
413             line.uncheckedAppend(CGPointMake(clip.maxX() - 3.5, clip.center().y()));
414
415             shadow.uncheckedAppend(shortened(line[0], line[1], lineWidth / 4.0f));
416             shadow.uncheckedAppend(shortened(line[1], line[0], lineWidth / 4.0f));
417         }
418
419         lineWidth = std::max<float>(lineWidth, 1);
420         drawJoinedLines(cgContext, Vector<CGPoint> { WTFMove(shadow) }, kCGLineCapSquare, lineWidth, makeSimpleColorFromFloats(0.0f, 0.0f, 0.0f, 0.7f));
421
422         lineWidth = std::max<float>(std::min(width, height) * thicknessRatio, 1);
423         drawJoinedLines(cgContext, Vector<CGPoint> { WTFMove(line) }, kCGLineCapButt, lineWidth, makeSimpleColorFromFloats(1.0f, 1.0f, 1.0f, 240 / 255.0f));
424     } else {
425         auto clip = addRoundedBorderClip(box, paintInfo.context(), rect);
426         auto width = clip.width();
427         auto height = clip.height();
428         FloatPoint bottomCenter { clip.x() + width / 2.0f, clip.maxY() };
429
430         drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint { clip.x(), clip.maxY() }, LinearInterpolation);
431         drawRadialGradient(cgContext, gradientWithName(ShineGradient), bottomCenter, 0, bottomCenter, sqrtf((width * width) / 4.0f + height * height), ExponentialInterpolation);
432     }
433     return false;
434 }
435
436 int RenderThemeIOS::baselinePosition(const RenderBox& box) const
437 {
438     if (box.style().appearance() == CheckboxPart || box.style().appearance() == RadioPart)
439         return box.marginTop() + box.height() - 2; // The baseline is 2px up from the bottom of the checkbox/radio in AppKit.
440     if (box.style().appearance() == MenulistPart)
441         return box.marginTop() + box.height() - 5; // This is to match AppKit. There might be a better way to calculate this though.
442     return RenderTheme::baselinePosition(box);
443 }
444
445 bool RenderThemeIOS::isControlStyled(const RenderStyle& style, const RenderStyle& userAgentStyle) const
446 {
447     // Buttons and MenulistButtons are styled if they contain a background image.
448     if (style.appearance() == PushButtonPart || style.appearance() == MenulistButtonPart)
449         return !style.visitedDependentColor(CSSPropertyBackgroundColor).isVisible() || style.backgroundLayers().hasImage();
450
451     if (style.appearance() == TextFieldPart || style.appearance() == TextAreaPart)
452         return style.backgroundLayers() != userAgentStyle.backgroundLayers();
453
454     return RenderTheme::isControlStyled(style, userAgentStyle);
455 }
456
457 void RenderThemeIOS::adjustRadioStyle(RenderStyle& style, const Element*) const
458 {
459     if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
460         return;
461
462     int size = std::max(style.computedFontPixelSize(), 10U);
463     style.setWidth({ size, Fixed });
464     style.setHeight({ size, Fixed });
465     style.setBorderRadius({ size / 2, size / 2 });
466 }
467
468 bool RenderThemeIOS::paintRadioDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
469 {
470     GraphicsContextStateSaver stateSaver(paintInfo.context());
471     CGContextRef cgContext = paintInfo.context().platformContext();
472
473     auto drawShadeAndShineGradients = [&](auto clip) {
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     };
478
479     if (isChecked(box)) {
480         auto border = box.style().getRoundedBorderFor(rect);
481         paintInfo.context().fillRoundedRect(border.pixelSnappedRoundedRectForPainting(box.document().deviceScaleFactor()), makeSimpleColorFromFloats(0.0f, 0.0f, 0.0f, 0.8f));
482
483         auto clip = addRoundedBorderClip(box, paintInfo.context(), rect);
484         drawAxialGradient(cgContext, gradientWithName(ConcaveGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
485
486         // The inner circle is 6 / 14 the size of the surrounding circle, 
487         // leaving 8 / 14 around it. (8 / 14) / 2 = 2 / 7.
488
489         static const float InnerInverseRatio = 2 / 7.0;
490
491         clip.inflateX(-clip.width() * InnerInverseRatio);
492         clip.inflateY(-clip.height() * InnerInverseRatio);
493         
494         constexpr auto shadowColor = makeSimpleColor(0, 0, 0, 0.7f * 255);
495         paintInfo.context().drawRaisedEllipse(clip, Color::white, shadowColor);
496
497         FloatSize radius(clip.width() / 2.0f, clip.height() / 2.0f);
498         paintInfo.context().clipRoundedRect(FloatRoundedRect(clip, radius, radius, radius, radius));
499         drawShadeAndShineGradients(clip);
500     } else {
501         auto clip = addRoundedBorderClip(box, paintInfo.context(), rect);
502         drawShadeAndShineGradients(clip);
503     }
504     return false;
505 }
506
507 bool RenderThemeIOS::paintTextFieldDecorations(const RenderObject& box, const PaintInfo& paintInfo, const FloatRect& rect)
508 {
509     auto& style = box.style();
510     FloatPoint point(rect.x() + style.borderLeftWidth(), rect.y() + style.borderTopWidth());
511
512     GraphicsContextStateSaver stateSaver(paintInfo.context());
513
514     paintInfo.context().clipRoundedRect(style.getRoundedBorderFor(LayoutRect(rect)).pixelSnappedRoundedRectForPainting(box.document().deviceScaleFactor()));
515
516     // This gradient gets drawn black when printing.
517     // Do not draw the gradient if there is no visible top border.
518     bool topBorderIsInvisible = !style.hasBorder() || !style.borderTopWidth() || style.borderTopIsTransparent();
519     if (!box.view().printing() && !topBorderIsInvisible)
520         drawAxialGradient(paintInfo.context().platformContext(), gradientWithName(InsetGradient), point, FloatPoint(CGPointMake(point.x(), point.y() + 3.0f)), LinearInterpolation);
521     return false;
522 }
523
524 bool RenderThemeIOS::paintTextAreaDecorations(const RenderObject& box, const PaintInfo& paintInfo, const FloatRect& rect)
525 {
526     return paintTextFieldDecorations(box, paintInfo, rect);
527 }
528
529 const int MenuListMinHeight = 15;
530
531 const float MenuListBaseHeight = 20;
532 const float MenuListBaseFontSize = 11;
533
534 const float MenuListArrowWidth = 7;
535 const float MenuListArrowHeight = 6;
536 const float MenuListButtonPaddingAfter = 19;
537
538 LengthBox RenderThemeIOS::popupInternalPaddingBox(const RenderStyle& style) const
539 {
540     if (style.appearance() == MenulistButtonPart) {
541         if (style.direction() == TextDirection::RTL)
542             return { 0, 0, 0, static_cast<int>(MenuListButtonPaddingAfter + style.borderTopWidth()) };
543         return { 0, static_cast<int>(MenuListButtonPaddingAfter + style.borderTopWidth()), 0, 0 };
544     }
545     return { 0, 0, 0, 0 };
546 }
547
548 void RenderThemeIOS::adjustRoundBorderRadius(RenderStyle& style, RenderBox& box)
549 {
550     if (style.appearance() == NoControlPart || style.backgroundLayers().hasImage())
551         return;
552
553     // FIXME: We should not be relying on border radius for the appearance of our controls <rdar://problem/7675493>.
554     style.setBorderRadius({ { std::min(box.width(), box.height()) / 2, Fixed }, { box.height() / 2, Fixed } });
555 }
556
557 static void applyCommonButtonPaddingToStyle(RenderStyle& style, const Element& element)
558 {
559     Document& document = element.document();
560     auto emSize = CSSPrimitiveValue::create(0.5, CSSUnitType::CSS_EMS);
561     // We don't need this element's parent style to calculate `em` units, so it's okay to pass nullptr for it here.
562     int pixels = emSize->computeLength<int>(CSSToLengthConversionData(&style, document.renderStyle(), nullptr, document.renderView(), document.frame()->pageZoomFactor()));
563     style.setPaddingBox(LengthBox(0, pixels, 0, pixels));
564 }
565
566 static void adjustSelectListButtonStyle(RenderStyle& style, const Element& element)
567 {
568     // Enforce "padding: 0 0.5em".
569     applyCommonButtonPaddingToStyle(style, element);
570
571     // Enforce "line-height: normal".
572     style.setLineHeight(Length(-100.0, Percent));
573 }
574     
575 class RenderThemeMeasureTextClient : public MeasureTextClient {
576 public:
577     RenderThemeMeasureTextClient(const FontCascade& font, const RenderStyle& style)
578         : m_font(font)
579         , m_style(style)
580     {
581     }
582     float measureText(const String& string) const override
583     {
584         TextRun run = RenderBlock::constructTextRun(string, m_style);
585         return m_font.width(run);
586     }
587 private:
588     const FontCascade& m_font;
589     const RenderStyle& m_style;
590 };
591
592 static void adjustInputElementButtonStyle(RenderStyle& style, const HTMLInputElement& inputElement)
593 {
594     // Always Enforce "padding: 0 0.5em".
595     applyCommonButtonPaddingToStyle(style, inputElement);
596
597     // Don't adjust the style if the width is specified.
598     if (style.width().isFixed() && style.width().value() > 0)
599         return;
600
601     // Don't adjust for unsupported date input types.
602     DateComponents::Type dateType = inputElement.dateType();
603     if (dateType == DateComponents::Invalid || dateType == DateComponents::Week)
604         return;
605
606     // Enforce the width and set the box-sizing to content-box to not conflict with the padding.
607     FontCascade font = style.fontCascade();
608     
609     float maximumWidth = localizedDateCache().maximumWidthForDateType(dateType, font, RenderThemeMeasureTextClient(font, style));
610
611     ASSERT(maximumWidth >= 0);
612
613     if (maximumWidth > 0) {
614         int width = static_cast<int>(maximumWidth + MenuListButtonPaddingAfter);
615         style.setWidth(Length(width, Fixed));
616         style.setBoxSizing(BoxSizing::ContentBox);
617     }
618 }
619
620 void RenderThemeIOS::adjustMenuListButtonStyle(RenderStyle& style, const Element* element) const
621 {
622     // Set the min-height to be at least MenuListMinHeight.
623     if (style.height().isAuto())
624         style.setMinHeight(Length(std::max(MenuListMinHeight, static_cast<int>(MenuListBaseHeight / MenuListBaseFontSize * style.fontDescription().computedSize())), Fixed));
625     else
626         style.setMinHeight(Length(MenuListMinHeight, Fixed));
627
628     if (!element)
629         return;
630
631     // Enforce some default styles in the case that this is a non-multiple <select> element,
632     // or a date input. We don't force these if this is just an element with
633     // "-webkit-appearance: menulist-button".
634     if (is<HTMLSelectElement>(*element) && !element->hasAttributeWithoutSynchronization(HTMLNames::multipleAttr))
635         adjustSelectListButtonStyle(style, *element);
636     else if (is<HTMLInputElement>(*element))
637         adjustInputElementButtonStyle(style, downcast<HTMLInputElement>(*element));
638 }
639
640 bool RenderThemeIOS::paintMenuListButtonDecorations(const RenderBox& box, const PaintInfo& paintInfo, const FloatRect& rect)
641 {
642     auto& style = box.style();
643     bool isRTL = style.direction() == TextDirection::RTL;
644     float borderTopWidth = style.borderTopWidth();
645     FloatRect clip(rect.x() + style.borderLeftWidth(), rect.y() + style.borderTopWidth(), rect.width() - style.borderLeftWidth() - style.borderRightWidth(), rect.height() - style.borderTopWidth() - style.borderBottomWidth());
646     CGContextRef cgContext = paintInfo.context().platformContext();
647
648     float adjustLeft = 0.5;
649     float adjustRight = 0.5;
650     float adjustTop = 0.5;
651     float adjustBottom = 0.5;
652
653     // Paint title portion.
654     {
655         float leftInset = isRTL ? MenuListButtonPaddingAfter : 0;
656         FloatRect titleClip(clip.x() + leftInset - adjustLeft, clip.y() - adjustTop, clip.width() - MenuListButtonPaddingAfter + adjustLeft, clip.height() + adjustTop + adjustBottom);
657
658         GraphicsContextStateSaver stateSaver(paintInfo.context());
659
660         FloatSize topLeftRadius;
661         FloatSize topRightRadius;
662         FloatSize bottomLeftRadius;
663         FloatSize bottomRightRadius;
664
665         if (isRTL) {
666             topRightRadius = FloatSize(valueForLength(style.borderTopRightRadius().width, rect.width()) - style.borderRightWidth(), valueForLength(style.borderTopRightRadius().height, rect.height()) - style.borderTopWidth());
667             bottomRightRadius = FloatSize(valueForLength(style.borderBottomRightRadius().width, rect.width()) - style.borderRightWidth(), valueForLength(style.borderBottomRightRadius().height, rect.height()) - style.borderBottomWidth());
668         } else {
669             topLeftRadius = FloatSize(valueForLength(style.borderTopLeftRadius().width, rect.width()) - style.borderLeftWidth(), valueForLength(style.borderTopLeftRadius().height, rect.height()) - style.borderTopWidth());
670             bottomLeftRadius = FloatSize(valueForLength(style.borderBottomLeftRadius().width, rect.width()) - style.borderLeftWidth(), valueForLength(style.borderBottomLeftRadius().height, rect.height()) - style.borderBottomWidth());
671         }
672
673         paintInfo.context().clipRoundedRect(FloatRoundedRect(titleClip,
674             topLeftRadius, topRightRadius,
675             bottomLeftRadius, bottomRightRadius));
676
677         drawAxialGradient(cgContext, gradientWithName(ShadeGradient), titleClip.location(), FloatPoint(titleClip.x(), titleClip.maxY()), LinearInterpolation);
678         drawAxialGradient(cgContext, gradientWithName(ShineGradient), FloatPoint(titleClip.x(), titleClip.maxY()), titleClip.location(), ExponentialInterpolation);
679     }
680
681     // Draw the separator after the initial padding.
682
683     float separatorPosition = isRTL ? (clip.x() + MenuListButtonPaddingAfter) : (clip.maxX() - MenuListButtonPaddingAfter);
684
685     box.drawLineForBoxSide(paintInfo.context(), FloatRect(FloatPoint(separatorPosition - borderTopWidth, clip.y()), FloatPoint(separatorPosition, clip.maxY())), BSRight, style.visitedDependentColor(CSSPropertyBorderTopColor), style.borderTopStyle(), 0, 0);
686
687     FloatRect buttonClip;
688     if (isRTL)
689         buttonClip = FloatRect(clip.x() - adjustTop, clip.y() - adjustTop, MenuListButtonPaddingAfter + adjustTop + adjustLeft, clip.height() + adjustTop + adjustBottom);
690     else
691         buttonClip = FloatRect(separatorPosition - adjustTop, clip.y() - adjustTop, MenuListButtonPaddingAfter + adjustTop + adjustRight, clip.height() + adjustTop + adjustBottom);
692
693     // Now paint the button portion.
694     {
695         GraphicsContextStateSaver stateSaver(paintInfo.context());
696
697         FloatSize topLeftRadius;
698         FloatSize topRightRadius;
699         FloatSize bottomLeftRadius;
700         FloatSize bottomRightRadius;
701
702         if (isRTL) {
703             topLeftRadius = FloatSize(valueForLength(style.borderTopLeftRadius().width, rect.width()) - style.borderLeftWidth(), valueForLength(style.borderTopLeftRadius().height, rect.height()) - style.borderTopWidth());
704             bottomLeftRadius = FloatSize(valueForLength(style.borderBottomLeftRadius().width, rect.width()) - style.borderLeftWidth(), valueForLength(style.borderBottomLeftRadius().height, rect.height()) - style.borderBottomWidth());
705         } else {
706             topRightRadius = FloatSize(valueForLength(style.borderTopRightRadius().width, rect.width()) - style.borderRightWidth(), valueForLength(style.borderTopRightRadius().height, rect.height()) - style.borderTopWidth());
707             bottomRightRadius = FloatSize(valueForLength(style.borderBottomRightRadius().width, rect.width()) - style.borderRightWidth(), valueForLength(style.borderBottomRightRadius().height, rect.height()) - style.borderBottomWidth());
708         }
709
710         paintInfo.context().clipRoundedRect(FloatRoundedRect(buttonClip,
711             topLeftRadius, topRightRadius,
712             bottomLeftRadius, bottomRightRadius));
713
714         paintInfo.context().fillRect(buttonClip, style.visitedDependentColor(CSSPropertyBorderTopColor));
715
716         drawAxialGradient(cgContext, gradientWithName(isFocused(box) && !isReadOnlyControl(box) ? ConcaveGradient : ConvexGradient), buttonClip.location(), FloatPoint(buttonClip.x(), buttonClip.maxY()), LinearInterpolation);
717     }
718
719     // Paint Indicators.
720
721     if (box.isMenuList() && downcast<HTMLSelectElement>(box.element())->multiple()) {
722         int size = 2;
723         int count = 3;
724         int padding = 3;
725
726         FloatRect ellipse(buttonClip.x() + (buttonClip.width() - count * (size + padding) + padding) / 2.0, buttonClip.maxY() - 10.0, size, size);
727
728         for (int i = 0; i < count; ++i) {
729             paintInfo.context().drawRaisedEllipse(ellipse, Color::white, makeSimpleColorFromFloats(0.0f, 0.0f, 0.0f, 0.5f));
730             ellipse.move(size + padding, 0);
731         }
732     }  else {
733         float centerX = floorf(buttonClip.x() + buttonClip.width() / 2.0) - 0.5;
734         float centerY = floorf(buttonClip.y() + buttonClip.height() * 3.0 / 8.0);
735
736         Vector<FloatPoint> arrow = {
737             { centerX - MenuListArrowWidth / 2, centerY },
738             { centerX + MenuListArrowWidth / 2, centerY },
739             { centerX, centerY + MenuListArrowHeight }
740         };
741
742         Vector<FloatPoint> shadow = {
743             { arrow[0].x(), arrow[0].y() + 1 },
744             { arrow[1].x(), arrow[1].y() + 1 },
745             { arrow[2].x(), arrow[2].y() + 1 }
746         };
747
748         float opacity = isReadOnlyControl(box) ? 0.2 : 0.5;
749         paintInfo.context().setStrokeColor(makeSimpleColorFromFloats(0.0f, 0.0f, 0.0f, opacity));
750         paintInfo.context().setFillColor(makeSimpleColorFromFloats(0.0f, 0.0f, 0.0f, opacity));
751         paintInfo.context().drawPath(Path::polygonPathFromPoints(shadow));
752
753         paintInfo.context().setStrokeColor(Color::white);
754         paintInfo.context().setFillColor(Color::white);
755         paintInfo.context().drawPath(Path::polygonPathFromPoints(arrow));
756     }
757
758     return false;
759 }
760
761 const CGFloat kTrackThickness = 4.0;
762 const CGFloat kTrackRadius = kTrackThickness / 2.0;
763 const int kDefaultSliderThumbSize = 16;
764
765 void RenderThemeIOS::adjustSliderTrackStyle(RenderStyle& style, const Element* element) const
766 {
767     RenderTheme::adjustSliderTrackStyle(style, element);
768
769     // FIXME: We should not be relying on border radius for the appearance of our controls <rdar://problem/7675493>.
770     int radius = static_cast<int>(kTrackRadius);
771     style.setBorderRadius({ { radius, Fixed }, { radius, Fixed } });
772 }
773
774 bool RenderThemeIOS::paintSliderTrack(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
775 {
776     IntRect trackClip = rect;
777     auto& style = box.style();
778
779     bool isHorizontal = true;
780     switch (style.appearance()) {
781     case SliderHorizontalPart:
782         isHorizontal = true;
783         // Inset slightly so the thumb covers the edge.
784         if (trackClip.width() > 2) {
785             trackClip.setWidth(trackClip.width() - 2);
786             trackClip.setX(trackClip.x() + 1);
787         }
788         trackClip.setHeight(static_cast<int>(kTrackThickness));
789         trackClip.setY(rect.y() + rect.height() / 2 - kTrackThickness / 2);
790         break;
791     case SliderVerticalPart:
792         isHorizontal = false;
793         // Inset slightly so the thumb covers the edge.
794         if (trackClip.height() > 2) {
795             trackClip.setHeight(trackClip.height() - 2);
796             trackClip.setY(trackClip.y() + 1);
797         }
798         trackClip.setWidth(kTrackThickness);
799         trackClip.setX(rect.x() + rect.width() / 2 - kTrackThickness / 2);
800         break;
801     default:
802         ASSERT_NOT_REACHED();
803     }
804
805     ASSERT(trackClip.width() >= 0);
806     ASSERT(trackClip.height() >= 0);
807     CGFloat cornerWidth = trackClip.width() < kTrackThickness ? trackClip.width() / 2.0f : kTrackRadius;
808     CGFloat cornerHeight = trackClip.height() < kTrackThickness ? trackClip.height() / 2.0f : kTrackRadius;
809
810     bool readonly = isReadOnlyControl(box);
811
812 #if ENABLE(DATALIST_ELEMENT)
813     paintSliderTicks(box, paintInfo, trackClip);
814 #endif
815
816     // Draw the track gradient.
817     {
818         GraphicsContextStateSaver stateSaver(paintInfo.context());
819
820         IntSize cornerSize(cornerWidth, cornerHeight);
821         FloatRoundedRect innerBorder(trackClip, cornerSize, cornerSize, cornerSize, cornerSize);
822         paintInfo.context().clipRoundedRect(innerBorder);
823
824         CGContextRef cgContext = paintInfo.context().platformContext();
825         IOSGradientRef gradient = readonly ? gradientWithName(ReadonlySliderTrackGradient) : gradientWithName(SliderTrackGradient);
826         if (isHorizontal)
827             drawAxialGradient(cgContext, gradient, trackClip.location(), FloatPoint(trackClip.x(), trackClip.maxY()), LinearInterpolation);
828         else
829             drawAxialGradient(cgContext, gradient, trackClip.location(), FloatPoint(trackClip.maxX(), trackClip.y()), LinearInterpolation);
830     }
831
832     // Draw the track border.
833     {
834         GraphicsContextStateSaver stateSaver(paintInfo.context());
835
836         CGContextRef cgContext = paintInfo.context().platformContext();
837         if (readonly)
838             paintInfo.context().setStrokeColor(makeSimpleColor(178, 178, 178));
839         else
840             paintInfo.context().setStrokeColor(makeSimpleColor(76, 76, 76));
841
842         RetainPtr<CGMutablePathRef> roundedRectPath = adoptCF(CGPathCreateMutable());
843         CGPathAddRoundedRect(roundedRectPath.get(), 0, trackClip, cornerWidth, cornerHeight);
844         CGContextAddPath(cgContext, roundedRectPath.get());
845         CGContextSetLineWidth(cgContext, 1);
846         CGContextStrokePath(cgContext);
847     }
848
849     return false;
850 }
851
852 void RenderThemeIOS::adjustSliderThumbSize(RenderStyle& style, const Element*) const
853 {
854     if (style.appearance() != SliderThumbHorizontalPart && style.appearance() != SliderThumbVerticalPart)
855         return;
856
857     // Enforce "border-radius: 50%".
858     style.setBorderRadius({ { 50, Percent }, { 50, Percent } });
859
860     // Enforce a 16x16 size if no size is provided.
861     if (style.width().isIntrinsicOrAuto() || style.height().isAuto()) {
862         style.setWidth({ kDefaultSliderThumbSize, Fixed });
863         style.setHeight({ kDefaultSliderThumbSize, Fixed });
864     }
865 }
866
867 bool RenderThemeIOS::paintSliderThumbDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
868 {
869     GraphicsContextStateSaver stateSaver(paintInfo.context());
870     FloatRect clip = addRoundedBorderClip(box, paintInfo.context(), rect);
871
872     CGContextRef cgContext = paintInfo.context().platformContext();
873     FloatPoint bottomCenter(clip.x() + clip.width() / 2.0f, clip.maxY());
874     if (isPressed(box))
875         drawAxialGradient(cgContext, gradientWithName(SliderThumbOpaquePressedGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
876     else {
877         drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
878         drawRadialGradient(cgContext, gradientWithName(ShineGradient), bottomCenter, 0.0f, bottomCenter, std::max(clip.width(), clip.height()), ExponentialInterpolation);
879     }
880
881     return false;
882 }
883
884 Seconds RenderThemeIOS::animationRepeatIntervalForProgressBar(RenderProgress&) const
885 {
886     return 0_s;
887 }
888
889 Seconds RenderThemeIOS::animationDurationForProgressBar(RenderProgress&) const
890 {
891     return 0_s;
892 }
893
894 bool RenderThemeIOS::paintProgressBar(const RenderObject& renderer, const PaintInfo& paintInfo, const IntRect& rect)
895 {
896     if (!is<RenderProgress>(renderer))
897         return true;
898
899     const int progressBarHeight = 9;
900     const float verticalOffset = (rect.height() - progressBarHeight) / 2.0;
901
902     GraphicsContextStateSaver stateSaver(paintInfo.context());
903     if (rect.width() < 10 || rect.height() < 9) {
904         // The rect is smaller than the standard progress bar. We clip to the element's rect to avoid
905         // leaking pixels outside the repaint rect.
906         paintInfo.context().clip(rect);
907     }
908
909     // 1) Draw the progress bar track.
910     // 1.1) Draw the white background with grey gradient border.
911     GraphicsContext& context = paintInfo.context();
912     context.setStrokeThickness(0.68);
913     context.setStrokeStyle(SolidStroke);
914
915     const float verticalRenderingPosition = rect.y() + verticalOffset;
916     auto strokeGradient = Gradient::create(Gradient::LinearData { FloatPoint(rect.x(), verticalRenderingPosition), FloatPoint(rect.x(), verticalRenderingPosition + progressBarHeight - 1) });
917     strokeGradient->addColorStop(0.0, makeSimpleColor(0x8d, 0x8d, 0x8d));
918     strokeGradient->addColorStop(0.45, makeSimpleColor(0xee, 0xee, 0xee));
919     strokeGradient->addColorStop(0.55, makeSimpleColor(0xee, 0xee, 0xee));
920     strokeGradient->addColorStop(1.0, makeSimpleColor(0x8d, 0x8d, 0x8d));
921     context.setStrokeGradient(WTFMove(strokeGradient));
922
923     context.setFillColor(Color::black);
924
925     Path trackPath;
926     FloatRect trackRect(rect.x() + 0.25, verticalRenderingPosition + 0.25, rect.width() - 0.5, progressBarHeight - 0.5);
927     FloatSize roundedCornerRadius(5, 4);
928     trackPath.addRoundedRect(trackRect, roundedCornerRadius);
929     context.drawPath(trackPath);
930
931     // 1.2) Draw top gradient on the upper half. It is supposed to overlay the fill from the background and darker the stroked path.
932     FloatRect border(rect.x(), rect.y() + verticalOffset, rect.width(), progressBarHeight);
933     paintInfo.context().clipRoundedRect(FloatRoundedRect(border, roundedCornerRadius, roundedCornerRadius, roundedCornerRadius, roundedCornerRadius));
934
935     float upperGradientHeight = progressBarHeight / 2.;
936     auto upperGradient = Gradient::create(Gradient::LinearData { FloatPoint(rect.x(), verticalRenderingPosition + 0.5), FloatPoint(rect.x(), verticalRenderingPosition + upperGradientHeight - 1.5) });
937     upperGradient->addColorStop(0.0, makeSimpleColor(133, 133, 133, 188));
938     upperGradient->addColorStop(1.0, makeSimpleColor(18, 18, 18, 51));
939     context.setFillGradient(WTFMove(upperGradient));
940
941     context.fillRect(FloatRect(rect.x(), verticalRenderingPosition, rect.width(), upperGradientHeight));
942
943     const auto& renderProgress = downcast<RenderProgress>(renderer);
944     if (renderProgress.isDeterminate()) {
945         // 2) Draw the progress bar.
946         double position = clampTo(renderProgress.position(), 0.0, 1.0);
947         double barWidth = position * rect.width();
948         auto barGradient = Gradient::create(Gradient::LinearData { FloatPoint(rect.x(), verticalRenderingPosition + 0.5), FloatPoint(rect.x(), verticalRenderingPosition + progressBarHeight - 1) });
949         barGradient->addColorStop(0.0, makeSimpleColor(195, 217, 247));
950         barGradient->addColorStop(0.45, makeSimpleColor(118, 164, 228));
951         barGradient->addColorStop(0.49, makeSimpleColor(118, 164, 228));
952         barGradient->addColorStop(0.51, makeSimpleColor(36, 114, 210));
953         barGradient->addColorStop(0.55, makeSimpleColor(36, 114, 210));
954         barGradient->addColorStop(1.0, makeSimpleColor(57, 142, 244));
955         context.setFillGradient(WTFMove(barGradient));
956
957         auto barStrokeGradient = Gradient::create(Gradient::LinearData { FloatPoint(rect.x(), verticalRenderingPosition), FloatPoint(rect.x(), verticalRenderingPosition + progressBarHeight - 1) });
958         barStrokeGradient->addColorStop(0.0, makeSimpleColor(95, 107, 183));
959         barStrokeGradient->addColorStop(0.5, makeSimpleColor(66, 106, 174, 240));
960         barStrokeGradient->addColorStop(1.0, makeSimpleColor(38, 104, 166));
961         context.setStrokeGradient(WTFMove(barStrokeGradient));
962
963         Path barPath;
964         int left = rect.x();
965         if (!renderProgress.style().isLeftToRightDirection())
966             left = rect.maxX() - barWidth;
967         FloatRect barRect(left + 0.25, verticalRenderingPosition + 0.25, std::max(barWidth - 0.5, 0.0), progressBarHeight - 0.5);
968         barPath.addRoundedRect(barRect, roundedCornerRadius);
969         context.drawPath(barPath);
970     }
971
972     return false;
973 }
974
975 #if ENABLE(DATALIST_ELEMENT)
976 IntSize RenderThemeIOS::sliderTickSize() const
977 {
978     // FIXME: <rdar://problem/12271791> MERGEBOT: Correct values for slider tick of <input type="range"> elements (requires ENABLE_DATALIST_ELEMENT)
979     return IntSize(1, 3);
980 }
981
982 int RenderThemeIOS::sliderTickOffsetFromTrackCenter() const
983 {
984     // FIXME: <rdar://problem/12271791> MERGEBOT: Correct values for slider tick of <input type="range"> elements (requires ENABLE_DATALIST_ELEMENT)
985     return -9;
986 }
987 #endif
988
989 void RenderThemeIOS::adjustSearchFieldStyle(RenderStyle& style, const Element* element) const
990 {
991     RenderTheme::adjustSearchFieldStyle(style, element);
992
993     if (!element)
994         return;
995
996     if (!style.hasBorder())
997         return;
998
999     RenderBox* box = element->renderBox();
1000     if (!box)
1001         return;
1002
1003     adjustRoundBorderRadius(style, *box);
1004 }
1005
1006 bool RenderThemeIOS::paintSearchFieldDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
1007 {
1008     return paintTextFieldDecorations(box, paintInfo, rect);
1009 }
1010
1011 void RenderThemeIOS::adjustButtonStyle(RenderStyle& style, const Element* element) const
1012 {
1013     RenderTheme::adjustButtonStyle(style, element);
1014
1015 #if ENABLE(INPUT_TYPE_COLOR)
1016     if (style.appearance() == ColorWellPart)
1017         return;
1018 #endif
1019
1020     // Set padding: 0 1.0em; on buttons.
1021     // CSSPrimitiveValue::computeLengthInt only needs the element's style to calculate em lengths.
1022     // Since the element might not be in a document, just pass nullptr for the root element style,
1023     // the parent element style, and the render view.
1024     auto emSize = CSSPrimitiveValue::create(1.0, CSSUnitType::CSS_EMS);
1025     int pixels = emSize->computeLength<int>(CSSToLengthConversionData(&style, nullptr, nullptr, nullptr, 1.0, WTF::nullopt));
1026     style.setPaddingBox(LengthBox(0, pixels, 0, pixels));
1027
1028     if (!element)
1029         return;
1030
1031     RenderBox* box = element->renderBox();
1032     if (!box)
1033         return;
1034
1035     adjustRoundBorderRadius(style, *box);
1036 }
1037
1038 bool RenderThemeIOS::paintButtonDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
1039 {
1040     return paintPushButtonDecorations(box, paintInfo, rect);
1041 }
1042
1043 bool RenderThemeIOS::paintPushButtonDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
1044 {
1045     GraphicsContextStateSaver stateSaver(paintInfo.context());
1046     FloatRect clip = addRoundedBorderClip(box, paintInfo.context(), rect);
1047
1048     CGContextRef cgContext = paintInfo.context().platformContext();
1049     if (box.style().visitedDependentColor(CSSPropertyBackgroundColor).isDark())
1050         drawAxialGradient(cgContext, gradientWithName(ConvexGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
1051     else {
1052         drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
1053         drawAxialGradient(cgContext, gradientWithName(ShineGradient), FloatPoint(clip.x(), clip.maxY()), clip.location(), ExponentialInterpolation);
1054     }
1055     return false;
1056 }
1057
1058 void RenderThemeIOS::setButtonSize(RenderStyle& style) const
1059 {
1060     // If the width and height are both specified, then we have nothing to do.
1061     if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
1062         return;
1063
1064     // Use the font size to determine the intrinsic width of the control.
1065     style.setHeight(Length(static_cast<int>(ControlBaseHeight / ControlBaseFontSize * style.fontDescription().computedSize()), Fixed));
1066 }
1067
1068 const int kThumbnailBorderStrokeWidth = 1;
1069 const int kThumbnailBorderCornerRadius = 1;
1070 const int kVisibleBackgroundImageWidth = 1;
1071 const int kMultipleThumbnailShrinkSize = 2;
1072
1073 bool RenderThemeIOS::paintFileUploadIconDecorations(const RenderObject&, const RenderObject& buttonRenderer, const PaintInfo& paintInfo, const IntRect& rect, Icon* icon, FileUploadDecorations fileUploadDecorations)
1074 {
1075     GraphicsContextStateSaver stateSaver(paintInfo.context());
1076
1077     IntSize cornerSize(kThumbnailBorderCornerRadius, kThumbnailBorderCornerRadius);
1078     Color pictureFrameColor = buttonRenderer.style().visitedDependentColor(CSSPropertyBorderTopColor);
1079
1080     IntRect thumbnailPictureFrameRect = rect;
1081     IntRect thumbnailRect = rect;
1082     thumbnailRect.contract(2 * kThumbnailBorderStrokeWidth, 2 * kThumbnailBorderStrokeWidth);
1083     thumbnailRect.move(kThumbnailBorderStrokeWidth, kThumbnailBorderStrokeWidth);
1084
1085     if (fileUploadDecorations == MultipleFiles) {
1086         // Smaller thumbnails for multiple selection appearance.
1087         thumbnailPictureFrameRect.contract(kMultipleThumbnailShrinkSize, kMultipleThumbnailShrinkSize);
1088         thumbnailRect.contract(kMultipleThumbnailShrinkSize, kMultipleThumbnailShrinkSize);
1089
1090         // Background picture frame and simple background icon with a gradient matching the button.
1091         Color backgroundImageColor = buttonRenderer.style().visitedDependentColor(CSSPropertyBackgroundColor);
1092         paintInfo.context().fillRoundedRect(FloatRoundedRect(thumbnailPictureFrameRect, cornerSize, cornerSize, cornerSize, cornerSize), pictureFrameColor);
1093         paintInfo.context().fillRect(thumbnailRect, backgroundImageColor);
1094         {
1095             GraphicsContextStateSaver stateSaver2(paintInfo.context());
1096             CGContextRef cgContext = paintInfo.context().platformContext();
1097             paintInfo.context().clip(thumbnailRect);
1098             if (backgroundImageColor.isDark())
1099                 drawAxialGradient(cgContext, gradientWithName(ConvexGradient), thumbnailRect.location(), FloatPoint(thumbnailRect.x(), thumbnailRect.maxY()), LinearInterpolation);
1100             else {
1101                 drawAxialGradient(cgContext, gradientWithName(ShadeGradient), thumbnailRect.location(), FloatPoint(thumbnailRect.x(), thumbnailRect.maxY()), LinearInterpolation);
1102                 drawAxialGradient(cgContext, gradientWithName(ShineGradient), FloatPoint(thumbnailRect.x(), thumbnailRect.maxY()), thumbnailRect.location(), ExponentialInterpolation);
1103             }
1104         }
1105
1106         // Move the rects for the Foreground picture frame and icon.
1107         int inset = kVisibleBackgroundImageWidth + kThumbnailBorderStrokeWidth;
1108         thumbnailPictureFrameRect.move(inset, inset);
1109         thumbnailRect.move(inset, inset);
1110     }
1111
1112     // Foreground picture frame and icon.
1113     paintInfo.context().fillRoundedRect(FloatRoundedRect(thumbnailPictureFrameRect, cornerSize, cornerSize, cornerSize, cornerSize), pictureFrameColor);
1114     icon->paint(paintInfo.context(), thumbnailRect);
1115
1116     return false;
1117 }
1118
1119 Color RenderThemeIOS::platformActiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1120 {
1121     return Color::transparent;
1122 }
1123
1124 Color RenderThemeIOS::platformInactiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1125 {
1126     return Color::transparent;
1127 }
1128
1129 static Optional<Color>& cachedFocusRingColor()
1130 {
1131     static NeverDestroyed<Optional<Color>> color;
1132     return color;
1133 }
1134
1135 #if ENABLE(FULL_KEYBOARD_ACCESS)
1136 Color RenderThemeIOS::platformFocusRingColor(OptionSet<StyleColor::Options>) const
1137 {
1138     if (cachedFocusRingColor().hasValue())
1139         return *cachedFocusRingColor();
1140
1141     // FIXME: Should be using -keyboardFocusIndicatorColor. For now, work around <rdar://problem/50838886>.
1142     return colorFromUIColor([PAL::getUIColorClass() systemBlueColor]);
1143 }
1144 #endif
1145
1146 bool RenderThemeIOS::shouldHaveSpinButton(const HTMLInputElement&) const
1147 {
1148     return false;
1149 }
1150
1151 bool RenderThemeIOS::supportsFocusRing(const RenderStyle&) const
1152 {
1153     return false;
1154 }
1155
1156 FontCascadeDescription& RenderThemeIOS::cachedSystemFontDescription(CSSValueID valueID) const
1157 {
1158     static NeverDestroyed<FontCascadeDescription> systemFont;
1159     static NeverDestroyed<FontCascadeDescription> headlineFont;
1160     static NeverDestroyed<FontCascadeDescription> bodyFont;
1161     static NeverDestroyed<FontCascadeDescription> subheadlineFont;
1162     static NeverDestroyed<FontCascadeDescription> footnoteFont;
1163     static NeverDestroyed<FontCascadeDescription> caption1Font;
1164     static NeverDestroyed<FontCascadeDescription> caption2Font;
1165     static NeverDestroyed<FontCascadeDescription> shortHeadlineFont;
1166     static NeverDestroyed<FontCascadeDescription> shortBodyFont;
1167     static NeverDestroyed<FontCascadeDescription> shortSubheadlineFont;
1168     static NeverDestroyed<FontCascadeDescription> shortFootnoteFont;
1169     static NeverDestroyed<FontCascadeDescription> shortCaption1Font;
1170     static NeverDestroyed<FontCascadeDescription> tallBodyFont;
1171 #if HAVE(SYSTEM_FONT_STYLE_TITLE_0)
1172     static NeverDestroyed<FontCascadeDescription> title0Font;
1173 #endif
1174     static NeverDestroyed<FontCascadeDescription> title1Font;
1175     static NeverDestroyed<FontCascadeDescription> title2Font;
1176     static NeverDestroyed<FontCascadeDescription> title3Font;
1177 #if HAVE(SYSTEM_FONT_STYLE_TITLE_4)
1178     static NeverDestroyed<FontCascadeDescription> title4Font;
1179 #endif
1180
1181     static CFStringRef userTextSize = contentSizeCategory();
1182
1183     if (userTextSize != contentSizeCategory()) {
1184         userTextSize = contentSizeCategory();
1185
1186         headlineFont.get().setIsAbsoluteSize(false);
1187         bodyFont.get().setIsAbsoluteSize(false);
1188         subheadlineFont.get().setIsAbsoluteSize(false);
1189         footnoteFont.get().setIsAbsoluteSize(false);
1190         caption1Font.get().setIsAbsoluteSize(false);
1191         caption2Font.get().setIsAbsoluteSize(false);
1192         shortHeadlineFont.get().setIsAbsoluteSize(false);
1193         shortBodyFont.get().setIsAbsoluteSize(false);
1194         shortSubheadlineFont.get().setIsAbsoluteSize(false);
1195         shortFootnoteFont.get().setIsAbsoluteSize(false);
1196         shortCaption1Font.get().setIsAbsoluteSize(false);
1197         tallBodyFont.get().setIsAbsoluteSize(false);
1198     }
1199
1200     switch (valueID) {
1201     case CSSValueAppleSystemHeadline:
1202         return headlineFont;
1203     case CSSValueAppleSystemBody:
1204         return bodyFont;
1205 #if HAVE(SYSTEM_FONT_STYLE_TITLE_0)
1206     case CSSValueAppleSystemTitle0:
1207         return title0Font;
1208 #endif
1209     case CSSValueAppleSystemTitle1:
1210         return title1Font;
1211     case CSSValueAppleSystemTitle2:
1212         return title2Font;
1213     case CSSValueAppleSystemTitle3:
1214         return title3Font;
1215 #if HAVE(SYSTEM_FONT_STYLE_TITLE_4)
1216     case CSSValueAppleSystemTitle4:
1217         return title4Font;
1218 #endif
1219     case CSSValueAppleSystemSubheadline:
1220         return subheadlineFont;
1221     case CSSValueAppleSystemFootnote:
1222         return footnoteFont;
1223     case CSSValueAppleSystemCaption1:
1224         return caption1Font;
1225     case CSSValueAppleSystemCaption2:
1226         return caption2Font;
1227         // Short version.
1228     case CSSValueAppleSystemShortHeadline:
1229         return shortHeadlineFont;
1230     case CSSValueAppleSystemShortBody:
1231         return shortBodyFont;
1232     case CSSValueAppleSystemShortSubheadline:
1233         return shortSubheadlineFont;
1234     case CSSValueAppleSystemShortFootnote:
1235         return shortFootnoteFont;
1236     case CSSValueAppleSystemShortCaption1:
1237         return shortCaption1Font;
1238         // Tall version.
1239     case CSSValueAppleSystemTallBody:
1240         return tallBodyFont;
1241     default:
1242         return systemFont;
1243     }
1244 }
1245
1246 static inline FontSelectionValue cssWeightOfSystemFont(CTFontRef font)
1247 {
1248     RetainPtr<CFDictionaryRef> traits = adoptCF(CTFontCopyTraits(font));
1249     CFNumberRef resultRef = (CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontWeightTrait);
1250     float result = 0;
1251     CFNumberGetValue(resultRef, kCFNumberFloatType, &result);
1252     // These numbers were experimentally gathered from weights of the system font.
1253     static float weightThresholds[] = { -0.6, -0.365, -0.115, 0.130, 0.235, 0.350, 0.5, 0.7 };
1254     for (unsigned i = 0; i < WTF_ARRAY_LENGTH(weightThresholds); ++i) {
1255         if (result < weightThresholds[i])
1256             return FontSelectionValue((static_cast<int>(i) + 1) * 100);
1257     }
1258     return FontSelectionValue(900);
1259 }
1260
1261 void RenderThemeIOS::updateCachedSystemFontDescription(CSSValueID valueID, FontCascadeDescription& fontDescription) const
1262 {
1263     RetainPtr<CTFontDescriptorRef> fontDescriptor;
1264     CFStringRef textStyle;
1265     switch (valueID) {
1266     case CSSValueAppleSystemHeadline:
1267         textStyle = kCTUIFontTextStyleHeadline;
1268         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1269         break;
1270     case CSSValueAppleSystemBody:
1271         textStyle = kCTUIFontTextStyleBody;
1272         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1273         break;
1274 #if HAVE(SYSTEM_FONT_STYLE_TITLE_0)
1275     case CSSValueAppleSystemTitle0:
1276         textStyle = kCTUIFontTextStyleTitle0;
1277         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1278         break;
1279 #endif
1280     case CSSValueAppleSystemTitle1:
1281         textStyle = kCTUIFontTextStyleTitle1;
1282         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1283         break;
1284     case CSSValueAppleSystemTitle2:
1285         textStyle = kCTUIFontTextStyleTitle2;
1286         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1287         break;
1288     case CSSValueAppleSystemTitle3:
1289         textStyle = kCTUIFontTextStyleTitle3;
1290         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1291         break;
1292 #if HAVE(SYSTEM_FONT_STYLE_TITLE_4)
1293     case CSSValueAppleSystemTitle4:
1294         textStyle = kCTUIFontTextStyleTitle4;
1295         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1296         break;
1297 #endif
1298     case CSSValueAppleSystemSubheadline:
1299         textStyle = kCTUIFontTextStyleSubhead;
1300         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1301         break;
1302     case CSSValueAppleSystemFootnote:
1303         textStyle = kCTUIFontTextStyleFootnote;
1304         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1305         break;
1306     case CSSValueAppleSystemCaption1:
1307         textStyle = kCTUIFontTextStyleCaption1;
1308         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1309         break;
1310     case CSSValueAppleSystemCaption2:
1311         textStyle = kCTUIFontTextStyleCaption2;
1312         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1313         break;
1314
1315     // Short version.
1316     case CSSValueAppleSystemShortHeadline:
1317         textStyle = kCTUIFontTextStyleShortHeadline;
1318         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1319         break;
1320     case CSSValueAppleSystemShortBody:
1321         textStyle = kCTUIFontTextStyleShortBody;
1322         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1323         break;
1324     case CSSValueAppleSystemShortSubheadline:
1325         textStyle = kCTUIFontTextStyleShortSubhead;
1326         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1327         break;
1328     case CSSValueAppleSystemShortFootnote:
1329         textStyle = kCTUIFontTextStyleShortFootnote;
1330         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1331         break;
1332     case CSSValueAppleSystemShortCaption1:
1333         textStyle = kCTUIFontTextStyleShortCaption1;
1334         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1335         break;
1336
1337     // Tall version.
1338     case CSSValueAppleSystemTallBody:
1339         textStyle = kCTUIFontTextStyleTallBody;
1340         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1341         break;
1342
1343     default:
1344         textStyle = kCTFontDescriptorTextStyleEmphasized;
1345         fontDescriptor = adoptCF(CTFontDescriptorCreateForUIType(kCTFontUIFontSystem, 0, nullptr));
1346     }
1347
1348     ASSERT(fontDescriptor);
1349     RetainPtr<CTFontRef> font = adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor.get(), 0, nullptr));
1350     fontDescription.setIsAbsoluteSize(true);
1351     fontDescription.setOneFamily(textStyle);
1352     fontDescription.setSpecifiedSize(CTFontGetSize(font.get()));
1353     fontDescription.setWeight(cssWeightOfSystemFont(font.get()));
1354     fontDescription.setItalic(normalItalicValue());
1355 }
1356
1357 String RenderThemeIOS::mediaControlsStyleSheet()
1358 {
1359     if (m_legacyMediaControlsStyleSheet.isEmpty())
1360         m_legacyMediaControlsStyleSheet = [NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"mediaControlsiOS" ofType:@"css"] encoding:NSUTF8StringEncoding error:nil];
1361     return m_legacyMediaControlsStyleSheet;
1362 }
1363
1364 String RenderThemeIOS::modernMediaControlsStyleSheet()
1365 {
1366     if (RuntimeEnabledFeatures::sharedFeatures().modernMediaControlsEnabled()) {
1367         if (m_mediaControlsStyleSheet.isEmpty())
1368             m_mediaControlsStyleSheet = [NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"modern-media-controls" ofType:@"css" inDirectory:@"modern-media-controls"] encoding:NSUTF8StringEncoding error:nil];
1369         return m_mediaControlsStyleSheet;
1370     }
1371     return emptyString();
1372 }
1373
1374 void RenderThemeIOS::purgeCaches()
1375 {
1376     m_legacyMediaControlsScript.clearImplIfNotShared();
1377     m_mediaControlsScript.clearImplIfNotShared();
1378     m_legacyMediaControlsStyleSheet.clearImplIfNotShared();
1379     m_mediaControlsStyleSheet.clearImplIfNotShared();
1380 }
1381
1382 String RenderThemeIOS::mediaControlsScript()
1383 {
1384     if (RuntimeEnabledFeatures::sharedFeatures().modernMediaControlsEnabled()) {
1385         if (m_mediaControlsScript.isEmpty()) {
1386             NSBundle *bundle = [NSBundle bundleForClass:[WebCoreRenderThemeBundle class]];
1387
1388             StringBuilder scriptBuilder;
1389             scriptBuilder.append("window.isIOSFamily = true;");
1390             scriptBuilder.append([NSString stringWithContentsOfFile:[bundle pathForResource:@"modern-media-controls-localized-strings" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1391             scriptBuilder.append([NSString stringWithContentsOfFile:[bundle pathForResource:@"modern-media-controls" ofType:@"js" inDirectory:@"modern-media-controls"] encoding:NSUTF8StringEncoding error:nil]);
1392             m_mediaControlsScript = scriptBuilder.toString();
1393         }
1394         return m_mediaControlsScript;
1395     }
1396
1397     if (m_legacyMediaControlsScript.isEmpty()) {
1398         NSBundle *bundle = [NSBundle bundleForClass:[WebCoreRenderThemeBundle class]];
1399
1400         StringBuilder scriptBuilder;
1401         scriptBuilder.append([NSString stringWithContentsOfFile:[bundle pathForResource:@"mediaControlsLocalizedStrings" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1402         scriptBuilder.append([NSString stringWithContentsOfFile:[bundle pathForResource:@"mediaControlsApple" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1403         scriptBuilder.append([NSString stringWithContentsOfFile:[bundle pathForResource:@"mediaControlsiOS" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1404
1405         m_legacyMediaControlsScript = scriptBuilder.toString();
1406     }
1407     return m_legacyMediaControlsScript;
1408 }
1409
1410 String RenderThemeIOS::mediaControlsBase64StringForIconNameAndType(const String& iconName, const String& iconType)
1411 {
1412     if (!RuntimeEnabledFeatures::sharedFeatures().modernMediaControlsEnabled())
1413         return emptyString();
1414
1415     String directory = "modern-media-controls/images";
1416     NSBundle *bundle = [NSBundle bundleForClass:[WebCoreRenderThemeBundle class]];
1417     return [[NSData dataWithContentsOfFile:[bundle pathForResource:iconName ofType:iconType inDirectory:directory]] base64EncodedStringWithOptions:0];
1418 }
1419
1420 struct CSSValueIDAndSelector {
1421     CSSValueID cssValueID;
1422     SEL selector;
1423 };
1424
1425 static const Vector<CSSValueIDAndSelector>& cssValueIDSelectorList()
1426 {
1427     static NeverDestroyed<Vector<CSSValueIDAndSelector>> cssValueIDSelectorList;
1428
1429     static std::once_flag initializeOnce;
1430     std::call_once(
1431         initializeOnce,
1432         [] {
1433         cssValueIDSelectorList.get() = Vector(std::initializer_list<CSSValueIDAndSelector> {
1434 #if HAVE(OS_DARK_MODE_SUPPORT)
1435             { CSSValueText, @selector(labelColor) },
1436             { CSSValueAppleSystemLabel, @selector(labelColor) },
1437             { CSSValueAppleSystemHeaderText, @selector(labelColor) },
1438             { CSSValueAppleSystemSecondaryLabel, @selector(secondaryLabelColor) },
1439             { CSSValueAppleSystemTertiaryLabel, @selector(tertiaryLabelColor) },
1440             { CSSValueAppleSystemQuaternaryLabel, @selector(quaternaryLabelColor) },
1441             { CSSValueAppleSystemPlaceholderText, @selector(placeholderTextColor) },
1442             { CSSValueWebkitControlBackground, @selector(systemBackgroundColor) },
1443             { CSSValueAppleSystemControlBackground, @selector(systemBackgroundColor) },
1444             { CSSValueAppleSystemTextBackground, @selector(systemBackgroundColor) },
1445             { CSSValueAppleSystemBackground, @selector(systemBackgroundColor) },
1446             { CSSValueAppleSystemSecondaryBackground, @selector(secondarySystemBackgroundColor) },
1447             { CSSValueAppleSystemTertiaryBackground, @selector(tertiarySystemBackgroundColor) },
1448             { CSSValueAppleSystemGroupedBackground, @selector(systemGroupedBackgroundColor) },
1449             { CSSValueAppleSystemSecondaryGroupedBackground, @selector(secondarySystemGroupedBackgroundColor) },
1450             { CSSValueAppleSystemTertiaryGroupedBackground, @selector(tertiarySystemGroupedBackgroundColor) },
1451             { CSSValueAppleSystemGrid, @selector(separatorColor) },
1452             { CSSValueAppleSystemSeparator, @selector(separatorColor) },
1453             { CSSValueAppleSystemContainerBorder, @selector(separatorColor) },
1454             { CSSValueAppleSystemSelectedContentBackground, @selector(tableCellDefaultSelectionTintColor) },
1455             { CSSValueAppleSystemUnemphasizedSelectedContentBackground, @selector(tableCellDefaultSelectionTintColor) },
1456             { CSSValueAppleSystemBrown, @selector(systemBrownColor) },
1457             { CSSValueAppleSystemIndigo, @selector(systemIndigoColor) },
1458 #endif
1459             { CSSValueAppleSystemTeal, @selector(systemTealColor) },
1460             { CSSValueAppleWirelessPlaybackTargetActive, @selector(systemBlueColor) },
1461             { CSSValueAppleSystemBlue, @selector(systemBlueColor) },
1462             { CSSValueAppleSystemGray, @selector(systemGrayColor) },
1463             { CSSValueAppleSystemGreen, @selector(systemGreenColor) },
1464             { CSSValueAppleSystemOrange, @selector(systemOrangeColor) },
1465             { CSSValueAppleSystemPink, @selector(systemPinkColor) },
1466             { CSSValueAppleSystemPurple, @selector(systemPurpleColor) },
1467             { CSSValueAppleSystemRed, @selector(systemRedColor) },
1468             { CSSValueAppleSystemYellow, @selector(systemYellowColor) }
1469         });
1470     });
1471
1472     return cssValueIDSelectorList;
1473 }
1474
1475 static Optional<Color> systemColorFromCSSValueID(CSSValueID cssValueID, bool useDarkAppearance, bool useElevatedUserInterfaceLevel)
1476 {
1477     LocalCurrentTraitCollection localTraitCollection(useDarkAppearance, useElevatedUserInterfaceLevel);
1478
1479     auto cssColorToSelector = [cssValueID] () -> SEL {
1480         for (auto& cssValueIDSelector : cssValueIDSelectorList()) {
1481             if (cssValueIDSelector.cssValueID == cssValueID)
1482                 return cssValueIDSelector.selector;
1483         }
1484         return nullptr;
1485     };
1486
1487     if (auto selector = cssColorToSelector()) {
1488         if (auto color = wtfObjCMsgSend<UIColor *>(PAL::getUIColorClass(), selector))
1489             return Color(color.CGColor, Color::Semantic);
1490     }
1491     return WTF::nullopt;
1492 }
1493
1494
1495 static RenderThemeIOS::CSSValueToSystemColorMap& globalCSSValueToSystemColorMap()
1496 {
1497     static NeverDestroyed<RenderThemeIOS::CSSValueToSystemColorMap> colorMap;
1498     return colorMap;
1499 }
1500
1501 const RenderThemeIOS::CSSValueToSystemColorMap& RenderThemeIOS::cssValueToSystemColorMap()
1502 {
1503     static NeverDestroyed<CSSValueToSystemColorMap> map;
1504
1505     static std::once_flag onceFlag;
1506     std::call_once(
1507         onceFlag,
1508         [] {
1509         for (auto& cssValueIDSelector : cssValueIDSelectorList()) {
1510             for (bool useDarkAppearance : { false, true }) {
1511                 for (bool useElevatedUserInterfaceLevel : { false, true }) {
1512                     if (auto color = systemColorFromCSSValueID(cssValueIDSelector.cssValueID, useDarkAppearance, useElevatedUserInterfaceLevel))
1513                         map.get().add(CSSValueKey { cssValueIDSelector.cssValueID, useDarkAppearance, useElevatedUserInterfaceLevel }, *color);
1514                 }
1515             }
1516         }
1517     });
1518
1519     return map;
1520 }
1521
1522 void RenderThemeIOS::setCSSValueToSystemColorMap(CSSValueToSystemColorMap&& colorMap)
1523 {
1524     globalCSSValueToSystemColorMap() = WTFMove(colorMap);
1525 }
1526
1527 void RenderThemeIOS::setFocusRingColor(const Color& color)
1528 {
1529     cachedFocusRingColor() = color;
1530 }
1531
1532 Color RenderThemeIOS::systemColor(CSSValueID cssValueID, OptionSet<StyleColor::Options> options) const
1533 {
1534     const bool forVisitedLink = options.contains(StyleColor::Options::ForVisitedLink);
1535
1536     // The system color cache below can't handle visited links. The only color value
1537     // that cares about visited links is CSSValueWebkitLink, so handle it here by
1538     // calling through to RenderTheme's base implementation.
1539     if (forVisitedLink && cssValueID == CSSValueWebkitLink)
1540         return RenderTheme::systemColor(cssValueID, options);
1541
1542     ASSERT(!forVisitedLink);
1543
1544     auto& cache = colorCache(options);
1545     return cache.systemStyleColors.ensure(cssValueID, [this, cssValueID, options] () -> Color {
1546         const bool useDarkAppearance = options.contains(StyleColor::Options::UseDarkAppearance);
1547         const bool useElevatedUserInterfaceLevel = options.contains(StyleColor::Options::UseElevatedUserInterfaceLevel);
1548         if (!globalCSSValueToSystemColorMap().isEmpty()) {
1549             auto it = globalCSSValueToSystemColorMap().find(CSSValueKey { cssValueID, useDarkAppearance, useElevatedUserInterfaceLevel });
1550             if (it == globalCSSValueToSystemColorMap().end())
1551                 return RenderTheme::systemColor(cssValueID, options);
1552             return it->value.semanticColor();
1553         }
1554         auto color = systemColorFromCSSValueID(cssValueID, useDarkAppearance, useElevatedUserInterfaceLevel);
1555         if (color)
1556             return *color;
1557         return RenderTheme::systemColor(cssValueID, options);
1558     }).iterator->value;
1559 }
1560
1561 #if ENABLE(ATTACHMENT_ELEMENT)
1562
1563 const CGSize attachmentSize = { 160, 119 };
1564
1565 const CGFloat attachmentBorderRadius = 16;
1566 constexpr SimpleColor attachmentBorderColor = makeSimpleColor(204, 204, 204);
1567 static CGFloat attachmentBorderThickness = 1;
1568
1569 constexpr SimpleColor attachmentProgressColor = makeSimpleColor(222, 222, 222);
1570 const CGFloat attachmentProgressBorderThickness = 3;
1571
1572 const CGFloat attachmentProgressSize = 36;
1573 const CGFloat attachmentIconSize = 48;
1574
1575 const CGFloat attachmentItemMargin = 8;
1576
1577 const CGFloat attachmentWrappingTextMaximumWidth = 140;
1578 const CFIndex attachmentWrappingTextMaximumLineCount = 2;
1579
1580 static RetainPtr<CTFontRef> attachmentActionFont()
1581 {
1582     RetainPtr<CTFontDescriptorRef> fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(kCTUIFontTextStyleShortFootnote, RenderThemeIOS::contentSizeCategory(), 0));
1583     RetainPtr<CTFontDescriptorRef> emphasizedFontDescriptor = adoptCF(CTFontDescriptorCreateCopyWithAttributes(fontDescriptor.get(),
1584         (CFDictionaryRef)@{
1585             (id)kCTFontDescriptorTextStyleAttribute: (id)kCTFontDescriptorTextStyleEmphasized
1586     }));
1587     return adoptCF(CTFontCreateWithFontDescriptor(emphasizedFontDescriptor.get(), 0, nullptr));
1588 }
1589
1590 static UIColor *attachmentActionColor(const RenderAttachment& attachment)
1591 {
1592     return [PAL::getUIColorClass() colorWithCGColor:cachedCGColor(attachment.style().visitedDependentColor(CSSPropertyColor))];
1593 }
1594
1595 static RetainPtr<CTFontRef> attachmentTitleFont()
1596 {
1597     RetainPtr<CTFontDescriptorRef> fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(kCTUIFontTextStyleShortCaption1, RenderThemeIOS::contentSizeCategory(), 0));
1598     return adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor.get(), 0, nullptr));
1599 }
1600
1601 static UIColor *attachmentTitleColor() { return [PAL::getUIColorClass() systemGrayColor]; }
1602
1603 static RetainPtr<CTFontRef> attachmentSubtitleFont() { return attachmentTitleFont(); }
1604 static UIColor *attachmentSubtitleColor() { return [PAL::getUIColorClass() systemGrayColor]; }
1605
1606 struct RenderAttachmentInfo {
1607     explicit RenderAttachmentInfo(const RenderAttachment&);
1608
1609     FloatRect iconRect;
1610     FloatRect attachmentRect;
1611     FloatRect progressRect;
1612
1613     BOOL hasProgress { NO };
1614     float progress;
1615
1616     RetainPtr<UIImage> icon;
1617     RefPtr<Image> thumbnailIcon;
1618
1619     int baseline { 0 };
1620
1621     struct LabelLine {
1622         FloatRect rect;
1623         RetainPtr<CTLineRef> line;
1624     };
1625     Vector<LabelLine> lines;
1626
1627     CGFloat contentYOrigin { 0 };
1628
1629 private:
1630     void buildWrappedLines(const String&, CTFontRef, UIColor *, unsigned maximumLineCount);
1631     void buildSingleLine(const String&, CTFontRef, UIColor *);
1632
1633     void addLine(CTLineRef);
1634 };
1635
1636 void RenderAttachmentInfo::addLine(CTLineRef line)
1637 {
1638     CGRect lineBounds = CTLineGetBoundsWithOptions(line, kCTLineBoundsExcludeTypographicLeading);
1639     CGFloat trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(line);
1640     CGFloat lineWidthIgnoringTrailingWhitespace = lineBounds.size.width - trailingWhitespaceWidth;
1641     CGFloat lineHeight = CGCeiling(lineBounds.size.height + lineBounds.origin.y);
1642
1643     CGFloat xOffset = (attachmentRect.width() / 2) - (lineWidthIgnoringTrailingWhitespace / 2);
1644     LabelLine labelLine;
1645     labelLine.line = line;
1646     labelLine.rect = FloatRect(xOffset, 0, lineWidthIgnoringTrailingWhitespace, lineHeight);
1647
1648     lines.append(labelLine);
1649 }
1650
1651 void RenderAttachmentInfo::buildWrappedLines(const String& text, CTFontRef font, UIColor *color, unsigned maximumLineCount)
1652 {
1653     if (text.isEmpty())
1654         return;
1655
1656     NSDictionary *textAttributes = @{
1657         (id)kCTFontAttributeName: (id)font,
1658         (id)kCTForegroundColorAttributeName: color
1659     };
1660     RetainPtr<NSAttributedString> attributedText = adoptNS([[NSAttributedString alloc] initWithString:text attributes:textAttributes]);
1661     RetainPtr<CTFramesetterRef> framesetter = adoptCF(CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedText.get()));
1662
1663     CFRange fitRange;
1664     CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter.get(), CFRangeMake(0, 0), nullptr, CGSizeMake(attachmentWrappingTextMaximumWidth, CGFLOAT_MAX), &fitRange);
1665
1666     RetainPtr<CGPathRef> textPath = adoptCF(CGPathCreateWithRect(CGRectMake(0, 0, textSize.width, textSize.height), nullptr));
1667     RetainPtr<CTFrameRef> textFrame = adoptCF(CTFramesetterCreateFrame(framesetter.get(), fitRange, textPath.get(), nullptr));
1668
1669     CFArrayRef ctLines = CTFrameGetLines(textFrame.get());
1670     CFIndex lineCount = CFArrayGetCount(ctLines);
1671     if (!lineCount)
1672         return;
1673
1674     // Lay out and record the first (maximumLineCount - 1) lines.
1675     CFIndex lineIndex = 0;
1676     CFIndex nonTruncatedLineCount = std::min<CFIndex>(maximumLineCount - 1, lineCount);
1677     for (; lineIndex < nonTruncatedLineCount; ++lineIndex)
1678         addLine((CTLineRef)CFArrayGetValueAtIndex(ctLines, lineIndex));
1679
1680     if (lineIndex == lineCount)
1681         return;
1682
1683     // We had text that didn't fit in the first (maximumLineCount - 1) lines.
1684     // Combine it into one last line, and center-truncate it.
1685     CTLineRef firstRemainingLine = (CTLineRef)CFArrayGetValueAtIndex(ctLines, lineIndex);
1686     CFIndex remainingRangeStart = CTLineGetStringRange(firstRemainingLine).location;
1687     CFRange remainingRange = CFRangeMake(remainingRangeStart, [attributedText length] - remainingRangeStart);
1688     RetainPtr<CGPathRef> remainingPath = adoptCF(CGPathCreateWithRect(CGRectMake(0, 0, CGFLOAT_MAX, CGFLOAT_MAX), nullptr));
1689     RetainPtr<CTFrameRef> remainingFrame = adoptCF(CTFramesetterCreateFrame(framesetter.get(), remainingRange, remainingPath.get(), nullptr));
1690     RetainPtr<NSAttributedString> ellipsisString = adoptNS([[NSAttributedString alloc] initWithString:@"\u2026" attributes:textAttributes]);
1691     RetainPtr<CTLineRef> ellipsisLine = adoptCF(CTLineCreateWithAttributedString((CFAttributedStringRef)ellipsisString.get()));
1692     CTLineRef remainingLine = (CTLineRef)CFArrayGetValueAtIndex(CTFrameGetLines(remainingFrame.get()), 0);
1693     RetainPtr<CTLineRef> truncatedLine = adoptCF(CTLineCreateTruncatedLine(remainingLine, attachmentWrappingTextMaximumWidth, kCTLineTruncationMiddle, ellipsisLine.get()));
1694
1695     if (!truncatedLine)
1696         truncatedLine = remainingLine;
1697
1698     addLine(truncatedLine.get());
1699 }
1700
1701 void RenderAttachmentInfo::buildSingleLine(const String& text, CTFontRef font, UIColor *color)
1702 {
1703     if (text.isEmpty())
1704         return;
1705
1706     NSDictionary *textAttributes = @{
1707         (id)kCTFontAttributeName: (id)font,
1708         (id)kCTForegroundColorAttributeName: color
1709     };
1710     RetainPtr<NSAttributedString> attributedText = adoptNS([[NSAttributedString alloc] initWithString:text attributes:textAttributes]);
1711
1712     addLine(adoptCF(CTLineCreateWithAttributedString((CFAttributedStringRef)attributedText.get())).get());
1713 }
1714
1715 static BOOL getAttachmentProgress(const RenderAttachment& attachment, float& progress)
1716 {
1717     auto& progressString = attachment.attachmentElement().attributeWithoutSynchronization(progressAttr);
1718     if (progressString.isEmpty())
1719         return NO;
1720     bool validProgress;
1721     progress = std::max<float>(std::min<float>(progressString.toFloat(&validProgress), 1), 0);
1722     return validProgress;
1723 }
1724
1725 static RetainPtr<UIImage> iconForAttachment(const RenderAttachment& attachment, FloatSize& size)
1726 {
1727     ALLOW_DEPRECATED_DECLARATIONS_BEGIN
1728     auto documentInteractionController = adoptNS([PAL::allocUIDocumentInteractionControllerInstance() init]);
1729     ALLOW_DEPRECATED_DECLARATIONS_END
1730
1731     String fileName;
1732     if (File* file = attachment.attachmentElement().file())
1733         fileName = file->name();
1734
1735     if (fileName.isEmpty())
1736         fileName = attachment.attachmentElement().attachmentTitle();
1737     [documentInteractionController setName:fileName];
1738
1739     String attachmentType = attachment.attachmentElement().attachmentType();
1740     if (!attachmentType.isEmpty()) {
1741         String UTI;
1742         if (isDeclaredUTI(attachmentType))
1743             UTI = attachmentType;
1744         else
1745             UTI = UTIFromMIMEType(attachmentType);
1746
1747 #if PLATFORM(IOS)
1748         [documentInteractionController setUTI:static_cast<NSString *>(UTI)];
1749 #endif
1750     }
1751
1752     RetainPtr<UIImage> result;
1753 #if PLATFORM(IOS)
1754     NSArray *icons = [documentInteractionController icons];
1755     if (!icons.count)
1756         return nil;
1757
1758     result = icons.lastObject;
1759
1760     BOOL useHeightForClosestMatch = [result size].height > [result size].width;
1761     CGFloat bestMatchRatio = -1;
1762
1763     for (UIImage *icon in icons) {
1764         CGFloat iconSize = useHeightForClosestMatch ? icon.size.height : icon.size.width;
1765
1766         CGFloat matchRatio = (attachmentIconSize / iconSize) - 1.0f;
1767         if (matchRatio < 0.3f) {
1768             matchRatio = CGFAbs(matchRatio);
1769             if ((bestMatchRatio == -1) || (matchRatio < bestMatchRatio)) {
1770                 result = icon;
1771                 bestMatchRatio = matchRatio;
1772             }
1773         }
1774     }
1775 #endif
1776     CGFloat iconAspect = [result size].width / [result size].height;
1777     size = largestRectWithAspectRatioInsideRect(iconAspect, FloatRect(0, 0, attachmentIconSize, attachmentIconSize)).size();
1778
1779     return result;
1780 }
1781
1782 RenderAttachmentInfo::RenderAttachmentInfo(const RenderAttachment& attachment)
1783 {
1784     attachmentRect = FloatRect(0, 0, attachment.width().toFloat(), attachment.height().toFloat());
1785
1786     hasProgress = getAttachmentProgress(attachment, progress);
1787
1788     String title = attachment.attachmentElement().attachmentTitleForDisplay();
1789     String action = attachment.attachmentElement().attributeWithoutSynchronization(actionAttr);
1790     String subtitle = attachment.attachmentElement().attributeWithoutSynchronization(subtitleAttr);
1791
1792     CGFloat yOffset = 0;
1793
1794     if (hasProgress) {
1795         progressRect = FloatRect((attachmentRect.width() / 2) - (attachmentProgressSize / 2), 0, attachmentProgressSize, attachmentProgressSize);
1796         yOffset += attachmentProgressSize + attachmentItemMargin;
1797     }
1798
1799     if (action.isEmpty() && !hasProgress) {
1800         FloatSize iconSize;
1801         icon = iconForAttachment(attachment, iconSize);
1802         thumbnailIcon = attachment.attachmentElement().thumbnail();
1803         
1804         if (thumbnailIcon || icon) {
1805             auto visibleIconSize = thumbnailIcon ? FloatSize(attachmentIconSize, attachmentIconSize) : iconSize;
1806             iconRect = FloatRect(FloatPoint((attachmentRect.width() / 2) - (visibleIconSize.width() / 2), 0), visibleIconSize);
1807             yOffset += iconRect.height() + attachmentItemMargin;
1808         }
1809     } else
1810         buildWrappedLines(action, attachmentActionFont().get(), attachmentActionColor(attachment), attachmentWrappingTextMaximumLineCount);
1811
1812     bool forceSingleLineTitle = !action.isEmpty() || !subtitle.isEmpty() || hasProgress;
1813     buildWrappedLines(title, attachmentTitleFont().get(), attachmentTitleColor(), forceSingleLineTitle ? 1 : attachmentWrappingTextMaximumLineCount);
1814     buildSingleLine(subtitle, attachmentSubtitleFont().get(), attachmentSubtitleColor());
1815
1816     if (!lines.isEmpty()) {
1817         for (auto& line : lines) {
1818             line.rect.setY(yOffset);
1819             yOffset += line.rect.height() + attachmentItemMargin;
1820         }
1821     }
1822
1823     yOffset -= attachmentItemMargin;
1824
1825     contentYOrigin = (attachmentRect.height() / 2) - (yOffset / 2);
1826 }
1827
1828 LayoutSize RenderThemeIOS::attachmentIntrinsicSize(const RenderAttachment&) const
1829 {
1830     return LayoutSize(FloatSize(attachmentSize));
1831 }
1832
1833 int RenderThemeIOS::attachmentBaseline(const RenderAttachment& attachment) const
1834 {
1835     RenderAttachmentInfo info(attachment);
1836     return info.baseline;
1837 }
1838
1839 static void paintAttachmentIcon(GraphicsContext& context, RenderAttachmentInfo& info)
1840 {
1841     RefPtr<Image> iconImage;
1842     if (info.thumbnailIcon)
1843         iconImage = info.thumbnailIcon;
1844     else if (info.icon)
1845         iconImage = BitmapImage::create([info.icon CGImage]);
1846     
1847     context.drawImage(*iconImage, info.iconRect);
1848 }
1849
1850 static void paintAttachmentText(GraphicsContext& context, RenderAttachmentInfo& info)
1851 {
1852     for (const auto& line : info.lines) {
1853         GraphicsContextStateSaver saver(context);
1854
1855         context.translate(toFloatSize(line.rect.minXMaxYCorner()));
1856         context.scale(FloatSize(1, -1));
1857
1858         CGContextSetTextPosition(context.platformContext(), 0, 0);
1859         CTLineDraw(line.line.get(), context.platformContext());
1860     }
1861 }
1862
1863 static void paintAttachmentProgress(GraphicsContext& context, RenderAttachmentInfo& info)
1864 {
1865     GraphicsContextStateSaver saver(context);
1866
1867     context.setStrokeThickness(attachmentProgressBorderThickness);
1868     context.setStrokeColor(attachmentProgressColor);
1869     context.setFillColor(attachmentProgressColor);
1870     context.strokeEllipse(info.progressRect);
1871
1872     FloatPoint center = info.progressRect.center();
1873
1874     Path progressPath;
1875     progressPath.moveTo(center);
1876     progressPath.addLineTo(FloatPoint(center.x(), info.progressRect.y()));
1877     progressPath.addArc(center, info.progressRect.width() / 2, -M_PI_2, info.progress * 2 * M_PI - M_PI_2, 0);
1878     progressPath.closeSubpath();
1879     context.fillPath(progressPath);
1880 }
1881
1882 static Path attachmentBorderPath(RenderAttachmentInfo& info)
1883 {
1884     auto insetAttachmentRect = info.attachmentRect;
1885     insetAttachmentRect.inflate(-attachmentBorderThickness / 2);
1886
1887     Path borderPath;
1888     borderPath.addRoundedRect(insetAttachmentRect, FloatSize(attachmentBorderRadius, attachmentBorderRadius));
1889     return borderPath;
1890 }
1891
1892 static void paintAttachmentBorder(GraphicsContext& context, Path& borderPath)
1893 {
1894     context.setStrokeColor(attachmentBorderColor);
1895     context.setStrokeThickness(attachmentBorderThickness);
1896     context.strokePath(borderPath);
1897 }
1898
1899 bool RenderThemeIOS::paintAttachment(const RenderObject& renderer, const PaintInfo& paintInfo, const IntRect& paintRect)
1900 {
1901     if (!is<RenderAttachment>(renderer))
1902         return false;
1903
1904     const RenderAttachment& attachment = downcast<RenderAttachment>(renderer);
1905
1906     RenderAttachmentInfo info(attachment);
1907
1908     GraphicsContext& context = paintInfo.context();
1909     GraphicsContextStateSaver saver(context);
1910
1911     context.translate(toFloatSize(paintRect.location()));
1912
1913     if (attachment.shouldDrawBorder()) {
1914         auto borderPath = attachmentBorderPath(info);
1915         paintAttachmentBorder(context, borderPath);
1916         context.clipPath(borderPath);
1917     }
1918
1919     context.translate(FloatSize(0, info.contentYOrigin));
1920
1921     if (info.hasProgress)
1922         paintAttachmentProgress(context, info);
1923     else if (info.icon || info.thumbnailIcon)
1924         paintAttachmentIcon(context, info);
1925
1926     paintAttachmentText(context, info);
1927
1928     return true;
1929 }
1930
1931 #endif // ENABLE(ATTACHMENT_ELEMENT)
1932
1933 #if PLATFORM(WATCHOS)
1934
1935 String RenderThemeIOS::extraDefaultStyleSheet()
1936 {
1937     return "* { -webkit-text-size-adjust: auto; -webkit-hyphens: auto !important; }"_s;
1938 }
1939
1940 #endif
1941
1942 #if USE(SYSTEM_PREVIEW)
1943 static NSBundle *arKitBundle()
1944 {
1945     static NSBundle *arKitBundle = []() {
1946 #if PLATFORM(IOS_FAMILY_SIMULATOR)
1947         dlopen("/System/Library/PrivateFrameworks/AssetViewer.framework/AssetViewer", RTLD_NOW);
1948         return [NSBundle bundleForClass:NSClassFromString(@"ASVThumbnailView")];
1949 #else
1950         return [NSBundle bundleWithURL:[NSURL fileURLWithPath:@"/System/Library/PrivateFrameworks/AssetViewer.framework"]];
1951 #endif
1952     }();
1953
1954     return arKitBundle;
1955 }
1956
1957 static RetainPtr<CGPDFPageRef> loadARKitPDFPage(NSString *imageName)
1958 {
1959     NSURL *url = [arKitBundle() URLForResource:imageName withExtension:@"pdf"];
1960
1961     if (!url)
1962         return nullptr;
1963
1964     auto document = adoptCF(CGPDFDocumentCreateWithURL((CFURLRef)url));
1965     if (!document)
1966         return nullptr;
1967
1968     if (!CGPDFDocumentGetNumberOfPages(document.get()))
1969         return nullptr;
1970
1971     return CGPDFDocumentGetPage(document.get(), 1);
1972 }
1973
1974 static CGPDFPageRef systemPreviewLogo()
1975 {
1976     static CGPDFPageRef logoPage = loadARKitPDFPage(@"ARKitBadge").leakRef();
1977     return logoPage;
1978 }
1979
1980 void RenderThemeIOS::paintSystemPreviewBadge(Image& image, const PaintInfo& paintInfo, const FloatRect& rect)
1981 {
1982     static const int largeBadgeDimension = 70;
1983     static const int largeBadgeOffset = 20;
1984
1985     static const int smallBadgeDimension = 35;
1986     static const int smallBadgeOffset = 8;
1987
1988     static const int minimumSizeForLargeBadge = 240;
1989
1990     bool useSmallBadge = rect.width() < minimumSizeForLargeBadge || rect.height() < minimumSizeForLargeBadge;
1991     int badgeOffset = useSmallBadge ? smallBadgeOffset : largeBadgeOffset;
1992     int badgeDimension = useSmallBadge ? smallBadgeDimension : largeBadgeDimension;
1993
1994     int minimumDimension = badgeDimension + 2 * badgeOffset;
1995     if (rect.width() < minimumDimension || rect.height() < minimumDimension)
1996         return;
1997
1998     CGRect absoluteBadgeRect = CGRectMake(rect.x() + rect.width() - badgeDimension - badgeOffset, rect.y() + badgeOffset, badgeDimension, badgeDimension);
1999     CGRect insetBadgeRect = CGRectMake(rect.width() - badgeDimension - badgeOffset, badgeOffset, badgeDimension, badgeDimension);
2000     CGRect badgeRect = CGRectMake(0, 0, badgeDimension, badgeDimension);
2001
2002     CIImage *inputImage = [CIImage imageWithCGImage:image.nativeImage().get()];
2003
2004     // Create a circle to be used for the clipping path in the badge, as well as the drop shadow.
2005     RetainPtr<CGPathRef> circle = adoptCF(CGPathCreateWithRoundedRect(absoluteBadgeRect, badgeDimension / 2, badgeDimension / 2, nullptr));
2006
2007     auto& graphicsContext = paintInfo.context();
2008     if (graphicsContext.paintingDisabled())
2009         return;
2010
2011     GraphicsContextStateSaver stateSaver(graphicsContext);
2012
2013     CGContextRef ctx = graphicsContext.platformContext();
2014     if (!ctx)
2015         return;
2016
2017     CGContextSaveGState(ctx);
2018
2019     // Draw a drop shadow around the circle.
2020     // Use the GraphicsContext function, because it calculates the blur radius in context space,
2021     // rather than screen space.
2022     auto shadowColor = makeSimpleColorFromFloats(0.f, 0.f, 0.f, 0.1f);
2023     graphicsContext.setShadow(FloatSize { }, 16, shadowColor);
2024
2025     // The circle must have an alpha channel value of 1 for the shadow color to appear.
2026     CGFloat circleColorComponents[4] = { 0, 0, 0, 1 };
2027     RetainPtr<CGColorRef> circleColor = adoptCF(CGColorCreate(sRGBColorSpaceRef(), circleColorComponents));
2028     CGContextSetFillColorWithColor(ctx, circleColor.get());
2029
2030     // Clip out the circle to only show the shadow.
2031     CGContextBeginPath(ctx);
2032     CGContextAddRect(ctx, rect);
2033     CGContextAddPath(ctx, circle.get());
2034     CGContextClosePath(ctx);
2035     CGContextEOClip(ctx);
2036
2037     // Draw a slightly smaller circle with a shadow, otherwise we'll see a fringe of the solid
2038     // black circle around the edges of the clipped path below.
2039     CGContextBeginPath(ctx);
2040     CGRect slightlySmallerAbsoluteBadgeRect = CGRectMake(absoluteBadgeRect.origin.x + 0.5, absoluteBadgeRect.origin.y + 0.5, badgeDimension - 1, badgeDimension - 1);
2041     RetainPtr<CGPathRef> slightlySmallerCircle = adoptCF(CGPathCreateWithRoundedRect(slightlySmallerAbsoluteBadgeRect, slightlySmallerAbsoluteBadgeRect.size.width / 2, slightlySmallerAbsoluteBadgeRect.size.height / 2, nullptr));
2042     CGContextAddPath(ctx, slightlySmallerCircle.get());
2043     CGContextClosePath(ctx);
2044     CGContextFillPath(ctx);
2045
2046     CGContextRestoreGState(ctx);
2047
2048     // Draw the blurred backdrop. Scale from intrinsic size to render size.
2049     CGAffineTransform transform = CGAffineTransformIdentity;
2050     transform = CGAffineTransformScale(transform, rect.width() / image.width(), rect.height() / image.height());
2051     CIImage *scaledImage = [inputImage imageByApplyingTransform:transform];
2052
2053     // CoreImage coordinates are y-up, so we need to flip the badge rectangle within the image frame.
2054     CGRect flippedInsetBadgeRect = CGRectMake(insetBadgeRect.origin.x, rect.height() - insetBadgeRect.origin.y - insetBadgeRect.size.height, badgeDimension, badgeDimension);
2055
2056     // Create a cropped region with pixel values extending outwards.
2057     CIImage *clampedImage = [scaledImage imageByClampingToRect:flippedInsetBadgeRect];
2058
2059     // Blur.
2060     CIImage *blurredImage = [clampedImage imageByApplyingGaussianBlurWithSigma:10];
2061
2062     // Saturate.
2063     CIFilter *saturationFilter = [CIFilter filterWithName:@"CIColorControls"];
2064     [saturationFilter setValue:blurredImage forKey:kCIInputImageKey];
2065     [saturationFilter setValue:@1.8 forKey:kCIInputSaturationKey];
2066
2067     // Tint.
2068     CIFilter *tintFilter1 = [CIFilter filterWithName:@"CIConstantColorGenerator"];
2069     CIColor *tintColor1 = [CIColor colorWithRed:1 green:1 blue:1 alpha:0.18];
2070     [tintFilter1 setValue:tintColor1 forKey:kCIInputColorKey];
2071
2072     // Blend the tint with the saturated output.
2073     CIFilter *sourceOverFilter = [CIFilter filterWithName:@"CISourceOverCompositing"];
2074     [sourceOverFilter setValue:tintFilter1.outputImage forKey:kCIInputImageKey];
2075     [sourceOverFilter setValue:saturationFilter.outputImage forKey:kCIInputBackgroundImageKey];
2076
2077     if (!m_ciContext)
2078         m_ciContext = [CIContext context];
2079
2080     RetainPtr<CGImageRef> cgImage;
2081 #if HAVE(IOSURFACE_COREIMAGE_SUPPORT)
2082     // Crop the result to the badge location.
2083     CIImage *croppedImage = [sourceOverFilter.outputImage imageByCroppingToRect:flippedInsetBadgeRect];
2084     CIImage *translatedImage = [croppedImage imageByApplyingTransform:CGAffineTransformMakeTranslation(-flippedInsetBadgeRect.origin.x, -flippedInsetBadgeRect.origin.y)];
2085     IOSurfaceRef surface;
2086     if (useSmallBadge) {
2087         if (!m_smallBadgeSurface)
2088             m_smallBadgeSurface = IOSurface::create({ smallBadgeDimension, smallBadgeDimension }, sRGBColorSpaceRef());
2089         surface = m_smallBadgeSurface->surface();
2090     } else {
2091         if (!m_largeBadgeSurface)
2092             m_largeBadgeSurface = IOSurface::create({ largeBadgeDimension, largeBadgeDimension }, sRGBColorSpaceRef());
2093         surface = m_largeBadgeSurface->surface();
2094     }
2095     [m_ciContext.get() render:translatedImage toIOSurface:surface bounds:badgeRect colorSpace:sRGBColorSpaceRef()];
2096     cgImage = useSmallBadge ? m_smallBadgeSurface->createImage() : m_largeBadgeSurface->createImage();
2097 #else
2098     cgImage = adoptCF([m_ciContext.get() createCGImage:sourceOverFilter.outputImage fromRect:flippedInsetBadgeRect]);
2099 #endif
2100
2101     // Before we render the result, we should clip to a circle around the badge rectangle.
2102     CGContextSaveGState(ctx);
2103     CGContextBeginPath(ctx);
2104     CGContextAddPath(ctx, circle.get());
2105     CGContextClosePath(ctx);
2106     CGContextClip(ctx);
2107
2108     CGContextTranslateCTM(ctx, absoluteBadgeRect.origin.x, absoluteBadgeRect.origin.y);
2109     CGContextTranslateCTM(ctx, 0, badgeDimension);
2110     CGContextScaleCTM(ctx, 1, -1);
2111     CGContextDrawImage(ctx, badgeRect, cgImage.get());
2112
2113     if (auto logo = systemPreviewLogo()) {
2114         CGSize pdfSize = CGPDFPageGetBoxRect(logo, kCGPDFMediaBox).size;
2115         CGFloat scaleX = badgeDimension / pdfSize.width;
2116         CGFloat scaleY = badgeDimension / pdfSize.height;
2117         CGContextScaleCTM(ctx, scaleX, scaleY);
2118         CGContextDrawPDFPage(ctx, logo);
2119     }
2120
2121     CGContextFlush(ctx);
2122     CGContextRestoreGState(ctx);
2123 }
2124 #endif
2125
2126 } // namespace WebCore
2127
2128 #endif //PLATFORM(IOS_FAMILY)