Tighten WebContent sandbox
[WebKit-https.git] / Source / WebCore / page / CaptionUserPreferencesMediaAF.cpp
1 /*
2  * Copyright (C) 2012, 2013 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27
28 #if ENABLE(VIDEO_TRACK)
29
30 #include "CaptionUserPreferencesMediaAF.h"
31
32 #if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
33 #include "CoreText/CoreText.h"
34 #endif
35 #include "FloatConversion.h"
36 #include "HTMLMediaElement.h"
37 #include "URL.h"
38 #include "Language.h"
39 #include "LocalizedStrings.h"
40 #include "Logging.h"
41 #include "MediaControlElements.h"
42 #include "SoftLinking.h"
43 #include "TextTrackList.h"
44 #include "UserStyleSheetTypes.h"
45 #include "VTTCue.h"
46 #include <wtf/RetainPtr.h>
47 #include <wtf/text/CString.h>
48 #include <wtf/text/StringBuilder.h>
49
50 #if PLATFORM(IOS)
51 #import "WebCoreThreadRun.h"
52 #endif
53
54 #if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
55 #include <MediaAccessibility/MediaAccessibility.h>
56 #endif
57
58 #if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
59
60 #if !PLATFORM(WIN)
61 #define SOFT_LINK_AVF_FRAMEWORK(Lib) SOFT_LINK_FRAMEWORK_OPTIONAL(Lib)
62 #define SOFT_LINK_AVF(Lib, Name, Type) SOFT_LINK(Lib, Name, Type)
63 #define SOFT_LINK_AVF_POINTER(Lib, Name, Type) SOFT_LINK_POINTER_OPTIONAL(Lib, Name, Type)
64 #define SOFT_LINK_AVF_FRAMEWORK_IMPORT(Lib, Fun, ReturnType, Arguments, Signature) SOFT_LINK(Lib, Fun, ReturnType, Arguments, Signature)
65 #else
66
67 #ifdef DEBUG_ALL
68 #define SOFT_LINK_AVF_FRAMEWORK(Lib) SOFT_LINK_DEBUG_LIBRARY(Lib)
69 #else
70 #define SOFT_LINK_AVF_FRAMEWORK(Lib) SOFT_LINK_LIBRARY(Lib)
71 #endif
72
73 #define SOFT_LINK_AVF(Lib, Name, Type) SOFT_LINK_DLL_IMPORT(Lib, Name, Type)
74 #define SOFT_LINK_AVF_POINTER(Lib, Name, Type) SOFT_LINK_VARIABLE_DLL_IMPORT_OPTIONAL(Lib, Name, Type)
75 #define SOFT_LINK_AVF_FRAMEWORK_IMPORT(Lib, Fun, ReturnType, Arguments, Signature) SOFT_LINK_DLL_IMPORT(Lib, Fun, ReturnType, __cdecl, Arguments, Signature)
76 #endif
77
78 SOFT_LINK_AVF_FRAMEWORK(MediaAccessibility)
79 SOFT_LINK_AVF_FRAMEWORK(CoreText)
80
81 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceGetDisplayType, MACaptionAppearanceDisplayType, (MACaptionAppearanceDomain domain), (domain))
82 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceSetDisplayType, void, (MACaptionAppearanceDomain domain, MACaptionAppearanceDisplayType displayType), (domain, displayType))
83 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceCopyForegroundColor, CGColorRef, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
84 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceCopyBackgroundColor, CGColorRef, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
85 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceCopyWindowColor, CGColorRef, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
86 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceGetForegroundOpacity, CGFloat, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
87 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceGetBackgroundOpacity, CGFloat, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
88 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceGetWindowOpacity, CGFloat, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
89 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceGetWindowRoundedCornerRadius, CGFloat, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
90 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceCopyFontDescriptorForStyle, CTFontDescriptorRef, (MACaptionAppearanceDomain domain,  MACaptionAppearanceBehavior *behavior, MACaptionAppearanceFontStyle fontStyle), (domain, behavior, fontStyle))
91 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceGetRelativeCharacterSize, CGFloat, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
92 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceGetTextEdgeStyle, MACaptionAppearanceTextEdgeStyle, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
93 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceAddSelectedLanguage, bool, (MACaptionAppearanceDomain domain, CFStringRef language), (domain, language));
94 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceCopySelectedLanguages, CFArrayRef, (MACaptionAppearanceDomain domain), (domain));
95 SOFT_LINK_AVF_FRAMEWORK_IMPORT(MediaAccessibility, MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics,  CFArrayRef, (MACaptionAppearanceDomain domain), (domain));
96
97 SOFT_LINK_AVF_FRAMEWORK_IMPORT(CoreText, CTFontDescriptorCopyAttribute,  CFTypeRef, (CTFontDescriptorRef descriptor, CFStringRef attribute), (descriptor, attribute));
98
99 #if PLATFORM(WIN)
100 // These are needed on Windows due to the way DLLs work. We do not need them on other platforms
101 #define MACaptionAppearanceGetDisplayType softLink_MACaptionAppearanceGetDisplayType
102 #define MACaptionAppearanceSetDisplayType softLink_MACaptionAppearanceSetDisplayType
103 #define MACaptionAppearanceCopyForegroundColor softLink_MACaptionAppearanceCopyForegroundColor
104 #define MACaptionAppearanceCopyBackgroundColor softLink_MACaptionAppearanceCopyBackgroundColor
105 #define MACaptionAppearanceCopyWindowColor softLink_MACaptionAppearanceCopyWindowColor
106 #define MACaptionAppearanceGetForegroundOpacity softLink_MACaptionAppearanceGetForegroundOpacity
107 #define MACaptionAppearanceGetBackgroundOpacity softLink_MACaptionAppearanceGetBackgroundOpacity
108 #define MACaptionAppearanceGetWindowOpacity softLink_MACaptionAppearanceGetWindowOpacity
109 #define MACaptionAppearanceGetWindowRoundedCornerRadius softLink_MACaptionAppearanceGetWindowRoundedCornerRadius
110 #define MACaptionAppearanceCopyFontDescriptorForStyle softLink_MACaptionAppearanceCopyFontDescriptorForStyle
111 #define MACaptionAppearanceGetRelativeCharacterSize softLink_MACaptionAppearanceGetRelativeCharacterSize
112 #define MACaptionAppearanceGetTextEdgeStyle softLink_MACaptionAppearanceGetTextEdgeStyle
113 #define MACaptionAppearanceAddSelectedLanguage softLink_MACaptionAppearanceAddSelectedLanguage
114 #define MACaptionAppearanceCopySelectedLanguages softLink_MACaptionAppearanceCopySelectedLanguages
115 #define MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics softLink_MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics
116 #define CTFontDescriptorCopyAttribute softLink_CTFontDescriptorCopyAttribute
117 #endif
118
119 SOFT_LINK_AVF_POINTER(MediaAccessibility, kMAXCaptionAppearanceSettingsChangedNotification, CFStringRef)
120 #define kMAXCaptionAppearanceSettingsChangedNotification getkMAXCaptionAppearanceSettingsChangedNotification()
121
122 SOFT_LINK_AVF_POINTER(CoreText, kCTFontNameAttribute, CFStringRef)
123 #define kCTFontNameAttribute getkCTFontNameAttribute()
124 #endif
125
126 namespace WebCore {
127
128 #if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
129 static void userCaptionPreferencesChangedNotificationCallback(CFNotificationCenterRef, void* observer, CFStringRef, const void *, CFDictionaryRef)
130 {
131 #if !PLATFORM(IOS)
132     static_cast<CaptionUserPreferencesMediaAF*>(observer)->captionPreferencesChanged();
133 #else
134     WebThreadRun(^{
135         static_cast<CaptionUserPreferencesMediaAF*>(observer)->captionPreferencesChanged();
136     });
137 #endif
138 }
139 #endif
140
141 CaptionUserPreferencesMediaAF::CaptionUserPreferencesMediaAF(PageGroup& group)
142     : CaptionUserPreferences(group)
143 #if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
144     , m_listeningForPreferenceChanges(false)
145 #endif
146 {
147 }
148
149 CaptionUserPreferencesMediaAF::~CaptionUserPreferencesMediaAF()
150 {
151 #if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
152     if (kMAXCaptionAppearanceSettingsChangedNotification)
153         CFNotificationCenterRemoveObserver(CFNotificationCenterGetLocalCenter(), this, kMAXCaptionAppearanceSettingsChangedNotification, 0);
154 #endif
155 }
156
157 #if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
158
159 CaptionUserPreferences::CaptionDisplayMode CaptionUserPreferencesMediaAF::captionDisplayMode() const
160 {
161     if (testingMode() || !MediaAccessibilityLibrary())
162         return CaptionUserPreferences::captionDisplayMode();
163
164     MACaptionAppearanceDisplayType displayType = MACaptionAppearanceGetDisplayType(kMACaptionAppearanceDomainUser);
165     switch (displayType) {
166     case kMACaptionAppearanceDisplayTypeForcedOnly:
167         return ForcedOnly;
168
169     case kMACaptionAppearanceDisplayTypeAutomatic:
170         return Automatic;
171
172     case kMACaptionAppearanceDisplayTypeAlwaysOn:
173         return AlwaysOn;
174     }
175
176     ASSERT_NOT_REACHED();
177     return ForcedOnly;
178 }
179     
180 void CaptionUserPreferencesMediaAF::setCaptionDisplayMode(CaptionUserPreferences::CaptionDisplayMode mode)
181 {
182     if (testingMode() || !MediaAccessibilityLibrary()) {
183         CaptionUserPreferences::setCaptionDisplayMode(mode);
184         return;
185     }
186
187     MACaptionAppearanceDisplayType displayType = kMACaptionAppearanceDisplayTypeForcedOnly;
188     switch (mode) {
189     case Automatic:
190         displayType = kMACaptionAppearanceDisplayTypeAutomatic;
191         break;
192     case ForcedOnly:
193         displayType = kMACaptionAppearanceDisplayTypeForcedOnly;
194         break;
195     case AlwaysOn:
196         displayType = kMACaptionAppearanceDisplayTypeAlwaysOn;
197         break;
198     default:
199         ASSERT_NOT_REACHED();
200         break;
201     }
202
203     MACaptionAppearanceSetDisplayType(kMACaptionAppearanceDomainUser, displayType);
204 }
205
206 bool CaptionUserPreferencesMediaAF::userPrefersCaptions() const
207 {
208     bool captionSetting = CaptionUserPreferences::userPrefersCaptions();
209     if (captionSetting || testingMode() || !MediaAccessibilityLibrary())
210         return captionSetting;
211     
212     RetainPtr<CFArrayRef> captioningMediaCharacteristics = adoptCF(MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser));
213     return captioningMediaCharacteristics && CFArrayGetCount(captioningMediaCharacteristics.get());
214 }
215
216 bool CaptionUserPreferencesMediaAF::userPrefersSubtitles() const
217 {
218     bool subtitlesSetting = CaptionUserPreferences::userPrefersSubtitles();
219     if (subtitlesSetting || testingMode() || !MediaAccessibilityLibrary())
220         return subtitlesSetting;
221     
222     RetainPtr<CFArrayRef> captioningMediaCharacteristics = adoptCF(MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser));
223     return !(captioningMediaCharacteristics && CFArrayGetCount(captioningMediaCharacteristics.get()));
224 }
225
226 void CaptionUserPreferencesMediaAF::setInterestedInCaptionPreferenceChanges()
227 {
228     if (!MediaAccessibilityLibrary())
229         return;
230
231     if (!kMAXCaptionAppearanceSettingsChangedNotification)
232         return;
233
234     if (!m_listeningForPreferenceChanges) {
235         m_listeningForPreferenceChanges = true;
236         CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), this, userCaptionPreferencesChangedNotificationCallback, kMAXCaptionAppearanceSettingsChangedNotification, 0, CFNotificationSuspensionBehaviorCoalesce);
237         updateCaptionStyleSheetOveride();
238     }
239 }
240
241 void CaptionUserPreferencesMediaAF::captionPreferencesChanged()
242 {
243     if (m_listeningForPreferenceChanges)
244         updateCaptionStyleSheetOveride();
245
246     CaptionUserPreferences::captionPreferencesChanged();
247 }
248
249 String CaptionUserPreferencesMediaAF::captionsWindowCSS() const
250 {
251     MACaptionAppearanceBehavior behavior;
252     RetainPtr<CGColorRef> color = adoptCF(MACaptionAppearanceCopyWindowColor(kMACaptionAppearanceDomainUser, &behavior));
253
254     Color windowColor(color.get());
255     if (!windowColor.isValid())
256         windowColor = Color::transparent;
257
258     bool important = behavior == kMACaptionAppearanceBehaviorUseValue;
259     CGFloat opacity = MACaptionAppearanceGetWindowOpacity(kMACaptionAppearanceDomainUser, &behavior);
260     if (!important)
261         important = behavior == kMACaptionAppearanceBehaviorUseValue;
262     String windowStyle = colorPropertyCSS(CSSPropertyBackgroundColor, Color(windowColor.red(), windowColor.green(), windowColor.blue(), static_cast<int>(opacity * 255)), important);
263
264     if (!opacity)
265         return windowStyle;
266
267     StringBuilder builder;
268     builder.append(windowStyle);
269     builder.append(getPropertyNameString(CSSPropertyPadding));
270     builder.append(": .4em !important;");
271
272     return builder.toString();
273 }
274
275 String CaptionUserPreferencesMediaAF::captionsBackgroundCSS() const
276 {
277     // This default value must be the same as the one specified in mediaControls.css for -webkit-media-text-track-past-nodes
278     // and webkit-media-text-track-future-nodes.
279     DEPRECATED_DEFINE_STATIC_LOCAL(Color, defaultBackgroundColor, (Color(0, 0, 0, 0.8 * 255)));
280
281     MACaptionAppearanceBehavior behavior;
282
283     RetainPtr<CGColorRef> color = adoptCF(MACaptionAppearanceCopyBackgroundColor(kMACaptionAppearanceDomainUser, &behavior));
284     Color backgroundColor(color.get());
285     if (!backgroundColor.isValid())
286         backgroundColor = defaultBackgroundColor;
287
288     bool important = behavior == kMACaptionAppearanceBehaviorUseValue;
289     CGFloat opacity = MACaptionAppearanceGetBackgroundOpacity(kMACaptionAppearanceDomainUser, &behavior);
290     if (!important)
291         important = behavior == kMACaptionAppearanceBehaviorUseValue;
292     return colorPropertyCSS(CSSPropertyBackgroundColor, Color(backgroundColor.red(), backgroundColor.green(), backgroundColor.blue(), static_cast<int>(opacity * 255)), important);
293 }
294
295 Color CaptionUserPreferencesMediaAF::captionsTextColor(bool& important) const
296 {
297     MACaptionAppearanceBehavior behavior;
298     RetainPtr<CGColorRef> color = adoptCF(MACaptionAppearanceCopyForegroundColor(kMACaptionAppearanceDomainUser, &behavior));
299     Color textColor(color.get());
300     if (!textColor.isValid())
301         // This default value must be the same as the one specified in mediaControls.css for -webkit-media-text-track-container.
302         textColor = Color::white;
303     
304     important = behavior == kMACaptionAppearanceBehaviorUseValue;
305     CGFloat opacity = MACaptionAppearanceGetForegroundOpacity(kMACaptionAppearanceDomainUser, &behavior);
306     if (!important)
307         important = behavior == kMACaptionAppearanceBehaviorUseValue;
308     return Color(textColor.red(), textColor.green(), textColor.blue(), static_cast<int>(opacity * 255));
309 }
310     
311 String CaptionUserPreferencesMediaAF::captionsTextColorCSS() const
312 {
313     bool important;
314     Color textColor = captionsTextColor(important);
315
316     if (!textColor.isValid())
317         return emptyString();
318
319     return colorPropertyCSS(CSSPropertyColor, textColor, important);
320 }
321     
322 String CaptionUserPreferencesMediaAF::windowRoundedCornerRadiusCSS() const
323 {
324     MACaptionAppearanceBehavior behavior;
325     CGFloat radius = MACaptionAppearanceGetWindowRoundedCornerRadius(kMACaptionAppearanceDomainUser, &behavior);
326     if (!radius)
327         return emptyString();
328
329     StringBuilder builder;
330     builder.append(getPropertyNameString(CSSPropertyBorderRadius));
331     builder.append(String::format(":%.02fpx", radius));
332     if (behavior == kMACaptionAppearanceBehaviorUseValue)
333         builder.append(" !important");
334     builder.append(';');
335
336     return builder.toString();
337 }
338     
339 Color CaptionUserPreferencesMediaAF::captionsEdgeColorForTextColor(const Color& textColor) const
340 {
341     int distanceFromWhite = differenceSquared(textColor, Color::white);
342     int distanceFromBlack = differenceSquared(textColor, Color::black);
343     
344     if (distanceFromWhite < distanceFromBlack)
345         return textColor.dark();
346     
347     return textColor.light();
348 }
349
350 String CaptionUserPreferencesMediaAF::cssPropertyWithTextEdgeColor(CSSPropertyID id, const String& value, const Color& textColor, bool important) const
351 {
352     StringBuilder builder;
353     
354     builder.append(getPropertyNameString(id));
355     builder.append(':');
356     builder.append(value);
357     builder.append(' ');
358     builder.append(captionsEdgeColorForTextColor(textColor).serialized());
359     if (important)
360         builder.append(" !important");
361     builder.append(';');
362     
363     return builder.toString();
364 }
365
366 String CaptionUserPreferencesMediaAF::colorPropertyCSS(CSSPropertyID id, const Color& color, bool important) const
367 {
368     StringBuilder builder;
369     
370     builder.append(getPropertyNameString(id));
371     builder.append(':');
372     builder.append(color.serialized());
373     if (important)
374         builder.append(" !important");
375     builder.append(';');
376     
377     return builder.toString();
378 }
379
380 String CaptionUserPreferencesMediaAF::captionsTextEdgeCSS() const
381 {
382     DEPRECATED_DEFINE_STATIC_LOCAL(const String, edgeStyleRaised, (" -.05em -.05em 0 ", String::ConstructFromLiteral));
383     DEPRECATED_DEFINE_STATIC_LOCAL(const String, edgeStyleDepressed, (" .05em .05em 0 ", String::ConstructFromLiteral));
384     DEPRECATED_DEFINE_STATIC_LOCAL(const String, edgeStyleDropShadow, (" .075em .075em 0 ", String::ConstructFromLiteral));
385     DEPRECATED_DEFINE_STATIC_LOCAL(const String, edgeStyleUniform, (" .03em ", String::ConstructFromLiteral));
386
387     bool unused;
388     Color color = captionsTextColor(unused);
389     if (!color.isValid())
390         color.setNamedColor("black");
391     color = captionsEdgeColorForTextColor(color);
392
393     MACaptionAppearanceBehavior behavior;
394     MACaptionAppearanceTextEdgeStyle textEdgeStyle = MACaptionAppearanceGetTextEdgeStyle(kMACaptionAppearanceDomainUser, &behavior);
395     switch (textEdgeStyle) {
396     case kMACaptionAppearanceTextEdgeStyleUndefined:
397     case kMACaptionAppearanceTextEdgeStyleNone:
398         return emptyString();
399             
400     case kMACaptionAppearanceTextEdgeStyleRaised:
401         return cssPropertyWithTextEdgeColor(CSSPropertyTextShadow, edgeStyleRaised, color, behavior == kMACaptionAppearanceBehaviorUseValue);
402     case kMACaptionAppearanceTextEdgeStyleDepressed:
403         return cssPropertyWithTextEdgeColor(CSSPropertyTextShadow, edgeStyleDepressed, color, behavior == kMACaptionAppearanceBehaviorUseValue);
404     case kMACaptionAppearanceTextEdgeStyleDropShadow:
405         return cssPropertyWithTextEdgeColor(CSSPropertyTextShadow, edgeStyleDropShadow, color, behavior == kMACaptionAppearanceBehaviorUseValue);
406     case kMACaptionAppearanceTextEdgeStyleUniform:
407         return cssPropertyWithTextEdgeColor(CSSPropertyWebkitTextStroke, edgeStyleUniform, color, behavior == kMACaptionAppearanceBehaviorUseValue);
408             
409     default:
410         ASSERT_NOT_REACHED();
411         break;
412     }
413     
414     return emptyString();
415 }
416
417 String CaptionUserPreferencesMediaAF::captionsDefaultFontCSS() const
418 {
419     MACaptionAppearanceBehavior behavior;
420     
421     RetainPtr<CTFontDescriptorRef> font = adoptCF(MACaptionAppearanceCopyFontDescriptorForStyle(kMACaptionAppearanceDomainUser, &behavior, kMACaptionAppearanceFontStyleDefault));
422     if (!font)
423         return emptyString();
424
425     RetainPtr<CFTypeRef> name = adoptCF(CTFontDescriptorCopyAttribute(font.get(), kCTFontNameAttribute));
426     if (!name)
427         return emptyString();
428     
429     StringBuilder builder;
430     
431     builder.append(getPropertyNameString(CSSPropertyFontFamily));
432     builder.append(": \"");
433     builder.append(static_cast<CFStringRef>(name.get()));
434     builder.append('"');
435     if (behavior == kMACaptionAppearanceBehaviorUseValue)
436         builder.append(" !important");
437     builder.append(';');
438     
439     return builder.toString();
440 }
441
442 float CaptionUserPreferencesMediaAF::captionFontSizeScaleAndImportance(bool& important) const
443 {
444     if (testingMode() || !MediaAccessibilityLibrary())
445         return CaptionUserPreferences::captionFontSizeScaleAndImportance(important);
446
447     MACaptionAppearanceBehavior behavior;
448     CGFloat characterScale = CaptionUserPreferences::captionFontSizeScaleAndImportance(important);
449     CGFloat scaleAdjustment = MACaptionAppearanceGetRelativeCharacterSize(kMACaptionAppearanceDomainUser, &behavior);
450
451     if (!scaleAdjustment)
452         return characterScale;
453
454     important = behavior == kMACaptionAppearanceBehaviorUseValue;
455 #if defined(__LP64__) && __LP64__
456     return narrowPrecisionToFloat(scaleAdjustment * characterScale);
457 #else
458     return scaleAdjustment * characterScale;
459 #endif
460 }
461
462 void CaptionUserPreferencesMediaAF::setPreferredLanguage(const String& language)
463 {
464     if (testingMode() || !MediaAccessibilityLibrary()) {
465         CaptionUserPreferences::setPreferredLanguage(language);
466         return;
467     }
468
469     MACaptionAppearanceAddSelectedLanguage(kMACaptionAppearanceDomainUser, language.createCFString().get());
470 }
471
472 Vector<String> CaptionUserPreferencesMediaAF::preferredLanguages() const
473 {
474     if (testingMode() || !MediaAccessibilityLibrary())
475         return CaptionUserPreferences::preferredLanguages();
476
477     Vector<String> platformLanguages = platformUserPreferredLanguages();
478     Vector<String> override = userPreferredLanguagesOverride();
479     if (!override.isEmpty()) {
480         if (platformLanguages.size() != override.size())
481             return override;
482         for (size_t i = 0; i < override.size(); i++) {
483             if (override[i] != platformLanguages[i])
484                 return override;
485         }
486     }
487
488     CFIndex languageCount = 0;
489     RetainPtr<CFArrayRef> languages = adoptCF(MACaptionAppearanceCopySelectedLanguages(kMACaptionAppearanceDomainUser));
490     if (languages)
491         languageCount = CFArrayGetCount(languages.get());
492
493     if (!languageCount)
494         return CaptionUserPreferences::preferredLanguages();
495
496     Vector<String> userPreferredLanguages;
497     userPreferredLanguages.reserveCapacity(languageCount + platformLanguages.size());
498     for (CFIndex i = 0; i < languageCount; i++)
499         userPreferredLanguages.append(static_cast<CFStringRef>(CFArrayGetValueAtIndex(languages.get(), i)));
500
501     userPreferredLanguages.appendVector(platformLanguages);
502
503     return userPreferredLanguages;
504 }
505 #endif // HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
506
507 String CaptionUserPreferencesMediaAF::captionsStyleSheetOverride() const
508 {
509     if (testingMode())
510         return CaptionUserPreferences::captionsStyleSheetOverride();
511     
512     StringBuilder captionsOverrideStyleSheet;
513
514 #if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
515     if (!MediaAccessibilityLibrary())
516         return CaptionUserPreferences::captionsStyleSheetOverride();
517     
518     String captionsColor = captionsTextColorCSS();
519     String edgeStyle = captionsTextEdgeCSS();
520     String fontName = captionsDefaultFontCSS();
521     String background = captionsBackgroundCSS();
522     if (!background.isEmpty() || !captionsColor.isEmpty() || !edgeStyle.isEmpty() || !fontName.isEmpty()) {
523         captionsOverrideStyleSheet.append(" video::");
524         captionsOverrideStyleSheet.append(TextTrackCue::cueShadowPseudoId());
525         captionsOverrideStyleSheet.append('{');
526         
527         if (!background.isEmpty())
528             captionsOverrideStyleSheet.append(background);
529         if (!captionsColor.isEmpty())
530             captionsOverrideStyleSheet.append(captionsColor);
531         if (!edgeStyle.isEmpty())
532             captionsOverrideStyleSheet.append(edgeStyle);
533         if (!fontName.isEmpty())
534             captionsOverrideStyleSheet.append(fontName);
535         
536         captionsOverrideStyleSheet.append('}');
537     }
538     
539     String windowColor = captionsWindowCSS();
540     String windowCornerRadius = windowRoundedCornerRadiusCSS();
541     if (!windowColor.isEmpty() || !windowCornerRadius.isEmpty()) {
542         captionsOverrideStyleSheet.append(" video::");
543         captionsOverrideStyleSheet.append(VTTCueBox::vttCueBoxShadowPseudoId());
544         captionsOverrideStyleSheet.append('{');
545         
546         if (!windowColor.isEmpty())
547             captionsOverrideStyleSheet.append(windowColor);
548         if (!windowCornerRadius.isEmpty())
549             captionsOverrideStyleSheet.append(windowCornerRadius);
550         
551         captionsOverrideStyleSheet.append('}');
552     }
553 #endif // HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
554
555     LOG(Media, "CaptionUserPreferencesMediaAF::captionsStyleSheetOverrideSetting sytle to:\n%s", captionsOverrideStyleSheet.toString().utf8().data());
556
557     return captionsOverrideStyleSheet.toString();
558 }
559
560 static String languageIdentifier(const String& languageCode)
561 {
562     if (languageCode.isEmpty())
563         return languageCode;
564
565     String lowercaseLanguageCode = languageCode.lower();
566
567     // Need 2U here to disambiguate String::operator[] from operator(NSString*, int)[] in a production build.
568     if (lowercaseLanguageCode.length() >= 3 && (lowercaseLanguageCode[2U] == '_' || lowercaseLanguageCode[2U] == '-'))
569         lowercaseLanguageCode.truncate(2);
570
571     return lowercaseLanguageCode;
572 }
573
574 static String trackDisplayName(TextTrack* track)
575 {
576     if (track == TextTrack::captionMenuOffItem())
577         return textTrackOffMenuItemText();
578     if (track == TextTrack::captionMenuAutomaticItem())
579         return textTrackAutomaticMenuItemText();
580
581     StringBuilder displayName;
582     String label = track->label();
583     String trackLanguageIdentifier = track->language();
584
585     RetainPtr<CFLocaleRef> currentLocale = adoptCF(CFLocaleCopyCurrent());
586     RetainPtr<CFStringRef> localeIdentifier = adoptCF(CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault, trackLanguageIdentifier.createCFString().get()));
587     RetainPtr<CFStringRef> languageCF = adoptCF(CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleLanguageCode, localeIdentifier.get()));
588     String language = languageCF.get();
589     if (!label.isEmpty()) {
590         if (language.isEmpty() || label.contains(language))
591             displayName.append(label);
592         else {
593             RetainPtr<CFDictionaryRef> localeDict = adoptCF(CFLocaleCreateComponentsFromLocaleIdentifier(kCFAllocatorDefault, localeIdentifier.get()));
594             if (localeDict) {
595                 CFStringRef countryCode = 0;
596                 String countryName;
597                 
598                 CFDictionaryGetValueIfPresent(localeDict.get(), kCFLocaleCountryCode, (const void **)&countryCode);
599                 if (countryCode) {
600                     RetainPtr<CFStringRef> countryNameCF = adoptCF(CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleCountryCode, countryCode));
601                     countryName = countryNameCF.get();
602                 }
603                 
604                 if (!countryName.isEmpty())
605                     displayName.append(textTrackCountryAndLanguageMenuItemText(label, countryName, language));
606                 else
607                     displayName.append(textTrackLanguageMenuItemText(label, language));
608             }
609         }
610     } else {
611         String languageAndLocale = adoptCF(CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleIdentifier, trackLanguageIdentifier.createCFString().get())).get();
612         if (!languageAndLocale.isEmpty())
613             displayName.append(languageAndLocale);
614         else if (!language.isEmpty())
615             displayName.append(language);
616         else
617             displayName.append(localeIdentifier.get());
618     }
619     
620     if (displayName.isEmpty())
621         displayName.append(textTrackNoLabelText());
622     
623     if (track->isEasyToRead())
624         return easyReaderTrackMenuItemText(displayName.toString());
625     
626     if (track->isClosedCaptions())
627         return closedCaptionTrackMenuItemText(displayName.toString());
628
629     if (track->isSDH())
630         return sdhTrackMenuItemText(displayName.toString());
631
632     return displayName.toString();
633 }
634
635 String CaptionUserPreferencesMediaAF::displayNameForTrack(TextTrack* track) const
636 {
637     return trackDisplayName(track);
638 }
639
640 int CaptionUserPreferencesMediaAF::textTrackSelectionScore(TextTrack* track, HTMLMediaElement* mediaElement) const
641 {
642     CaptionDisplayMode displayMode = captionDisplayMode();
643     bool legacyOverride = mediaElement->webkitClosedCaptionsVisible();
644     if (displayMode == AlwaysOn && (!userPrefersSubtitles() && !userPrefersCaptions() && !legacyOverride))
645         return 0;
646     if (track->kind() != TextTrack::captionsKeyword() && track->kind() != TextTrack::subtitlesKeyword() && track->kind() != TextTrack::forcedKeyword())
647         return 0;
648     if (!track->isMainProgramContent())
649         return 0;
650
651     bool trackHasOnlyForcedSubtitles = track->containsOnlyForcedSubtitles();
652     if (!legacyOverride && ((trackHasOnlyForcedSubtitles && displayMode != ForcedOnly) || (!trackHasOnlyForcedSubtitles && displayMode == ForcedOnly)))
653         return 0;
654
655     Vector<String> userPreferredCaptionLanguages = preferredLanguages();
656
657     if ((displayMode == Automatic && !legacyOverride) || trackHasOnlyForcedSubtitles) {
658
659         if (!mediaElement || !mediaElement->player())
660             return 0;
661
662         String textTrackLanguage = track->language();
663         if (textTrackLanguage.isEmpty())
664             return 0;
665
666         Vector<String> languageList;
667         languageList.reserveCapacity(1);
668
669         String audioTrackLanguage;
670         if (testingMode())
671             audioTrackLanguage = primaryAudioTrackLanguageOverride();
672         else
673             audioTrackLanguage = mediaElement->player()->languageOfPrimaryAudioTrack();
674
675         if (audioTrackLanguage.isEmpty())
676             return 0;
677
678         if (trackHasOnlyForcedSubtitles) {
679             languageList.append(audioTrackLanguage);
680             size_t offset = indexOfBestMatchingLanguageInList(textTrackLanguage, languageList);
681
682             // Only consider a forced-only track if it IS in the same language as the primary audio track.
683             if (offset)
684                 return 0;
685         } else {
686             languageList.append(defaultLanguage());
687
688             // Only enable a text track if the current audio track is NOT in the user's preferred language ...
689             size_t offset = indexOfBestMatchingLanguageInList(audioTrackLanguage, languageList);
690             if (!offset)
691                 return 0;
692
693             // and the text track matches the user's preferred language.
694             offset = indexOfBestMatchingLanguageInList(textTrackLanguage, languageList);
695             if (offset)
696                 return 0;
697         }
698
699         userPreferredCaptionLanguages = languageList;
700     }
701
702     int trackScore = 0;
703
704     if (userPrefersCaptions()) {
705         // When the user prefers accessiblity tracks, rank is SDH, then CC, then subtitles.
706         if (track->kind() == track->subtitlesKeyword())
707             trackScore = 1;
708         else if (track->isClosedCaptions())
709             trackScore = 2;
710         else
711             trackScore = 3;
712     } else {
713         // When the user prefers translation tracks, rank is subtitles, then SDH, then CC tracks.
714         if (track->kind() == track->subtitlesKeyword())
715             trackScore = 3;
716         else if (!track->isClosedCaptions())
717             trackScore = 2;
718         else
719             trackScore = 1;
720     }
721
722     return trackScore + textTrackLanguageSelectionScore(track, userPreferredCaptionLanguages);
723 }
724
725 static bool textTrackCompare(const RefPtr<TextTrack>& a, const RefPtr<TextTrack>& b)
726 {
727     String preferredLanguageDisplayName = displayNameForLanguageLocale(languageIdentifier(defaultLanguage()));
728     String aLanguageDisplayName = displayNameForLanguageLocale(languageIdentifier(a->language()));
729     String bLanguageDisplayName = displayNameForLanguageLocale(languageIdentifier(b->language()));
730
731     // Tracks in the user's preferred language are always at the top of the menu.
732     bool aIsPreferredLanguage = !codePointCompare(aLanguageDisplayName, preferredLanguageDisplayName);
733     bool bIsPreferredLanguage = !codePointCompare(bLanguageDisplayName, preferredLanguageDisplayName);
734     if ((aIsPreferredLanguage || bIsPreferredLanguage) && (aIsPreferredLanguage != bIsPreferredLanguage))
735         return aIsPreferredLanguage;
736
737     // Tracks not in the user's preferred language sort first by language ...
738     if (codePointCompare(aLanguageDisplayName, bLanguageDisplayName))
739         return codePointCompare(aLanguageDisplayName, bLanguageDisplayName) < 0;
740
741     // ... but when tracks have the same language, main program content sorts next highest ...
742     bool aIsMainContent = a->isMainProgramContent();
743     bool bIsMainContent = b->isMainProgramContent();
744     if ((aIsMainContent || bIsMainContent) && (aIsMainContent != bIsMainContent))
745         return aIsMainContent;
746
747     // ... and main program trakcs sort higher than CC tracks ...
748     bool aIsCC = a->isClosedCaptions();
749     bool bIsCC = b->isClosedCaptions();
750     if ((aIsCC || bIsCC) && (aIsCC != bIsCC)) {
751         if (aIsCC)
752             return aIsMainContent;
753         return bIsMainContent;
754     }
755
756     // ... and tracks of the same type and language sort by the menu item text.
757     return codePointCompare(trackDisplayName(a.get()), trackDisplayName(b.get())) < 0;
758 }
759
760 Vector<RefPtr<TextTrack>> CaptionUserPreferencesMediaAF::sortedTrackListForMenu(TextTrackList* trackList)
761 {
762     ASSERT(trackList);
763
764     Vector<RefPtr<TextTrack>> tracksForMenu;
765     HashSet<String> languagesIncluded;
766     bool prefersAccessibilityTracks = userPrefersCaptions();
767     bool filterTrackList = shouldFilterTrackMenu();
768
769     for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
770         TextTrack* track = trackList->item(i);
771         String language = displayNameForLanguageLocale(track->language());
772
773         const AtomicString& kind = track->kind();
774         if (kind != TextTrack::captionsKeyword() && kind != TextTrack::descriptionsKeyword() && kind != TextTrack::subtitlesKeyword())
775             continue;
776
777         if (track->containsOnlyForcedSubtitles()) {
778             LOG(Media, "CaptionUserPreferencesMac::sortedTrackListForMenu - skipping '%s' track with language '%s' because it contains only forced subtitles", track->kind().string().utf8().data(), language.utf8().data());
779             continue;
780         }
781         
782         if (track->isEasyToRead()) {
783             LOG(Media, "CaptionUserPreferencesMac::sortedTrackListForMenu - adding '%s' track with language '%s' because it is 'easy to read'", track->kind().string().utf8().data(), language.utf8().data());
784             if (!language.isEmpty())
785                 languagesIncluded.add(language);
786             tracksForMenu.append(track);
787             continue;
788         }
789
790         if (track->mode() == TextTrack::showingKeyword()) {
791             LOG(Media, "CaptionUserPreferencesMac::sortedTrackListForMenu - adding '%s' track with language '%s' because it is already visible", track->kind().string().utf8().data(), language.utf8().data());
792             if (!language.isEmpty())
793                 languagesIncluded.add(language);
794             tracksForMenu.append(track);
795             continue;
796         }
797
798         if (!language.isEmpty() && track->isMainProgramContent()) {
799             bool isAccessibilityTrack = track->kind() == track->captionsKeyword();
800             if (prefersAccessibilityTracks) {
801                 // In the first pass, include only caption tracks if the user prefers accessibility tracks.
802                 if (!isAccessibilityTrack && filterTrackList) {
803                     LOG(Media, "CaptionUserPreferencesMediaAF::sortedTrackListForMenu - skipping '%s' track with language '%s' because it is NOT an accessibility track", track->kind().string().utf8().data(), language.utf8().data());
804                     continue;
805                 }
806             } else {
807                 // In the first pass, only include the first non-CC or SDH track with each language if the user prefers translation tracks.
808                 if (isAccessibilityTrack && filterTrackList) {
809                     LOG(Media, "CaptionUserPreferencesMediaAF::sortedTrackListForMenu - skipping '%s' track with language '%s' because it is an accessibility track", track->kind().string().utf8().data(), language.utf8().data());
810                     continue;
811                 }
812                 if (languagesIncluded.contains(language)  && filterTrackList) {
813                     LOG(Media, "CaptionUserPreferencesMediaAF::sortedTrackListForMenu - skipping '%s' track with language '%s' because it is not the first with this language", track->kind().string().utf8().data(), language.utf8().data());
814                     continue;
815                 }
816             }
817         }
818
819         if (!language.isEmpty())
820             languagesIncluded.add(language);
821         tracksForMenu.append(track);
822
823         LOG(Media, "CaptionUserPreferencesMac::sortedTrackListForMenu - adding '%s' track with language '%s', is%s main program content", track->kind().string().utf8().data(), language.utf8().data(), track->isMainProgramContent() ? "" : " NOT");
824     }
825
826     // Now that we have filtered for the user's accessibility/translation preference, add  all tracks with a unique language without regard to track type.
827     for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
828         TextTrack* track = trackList->item(i);
829         String language = displayNameForLanguageLocale(track->language());
830
831         const AtomicString& kind = track->kind();
832         if (kind != TextTrack::captionsKeyword() && kind != TextTrack::descriptionsKeyword() && kind != TextTrack::subtitlesKeyword())
833             continue;
834
835         // All candidates with no languge were added the first time through.
836         if (language.isEmpty())
837             continue;
838
839         if (track->containsOnlyForcedSubtitles())
840             continue;
841
842         if (!languagesIncluded.contains(language) && track->isMainProgramContent()) {
843             languagesIncluded.add(language);
844             tracksForMenu.append(track);
845             LOG(Media, "CaptionUserPreferencesMediaAF::sortedTrackListForMenu - adding '%s' track with language '%s' because it is the only track with this language", track->kind().string().utf8().data(), language.utf8().data());
846         }
847     }
848
849     std::sort(tracksForMenu.begin(), tracksForMenu.end(), textTrackCompare);
850
851     tracksForMenu.insert(0, TextTrack::captionMenuOffItem());
852     tracksForMenu.insert(1, TextTrack::captionMenuAutomaticItem());
853
854     return tracksForMenu;
855 }
856     
857 }
858
859 #endif // ENABLE(VIDEO_TRACK)