Remove the Timer parameters from timer callbacks
[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_updateStyleSheetTimer(*this, &CaptionUserPreferencesMediaAF::updateTimerFired)
145     , m_listeningForPreferenceChanges(false)
146 #endif
147 {
148 }
149
150 CaptionUserPreferencesMediaAF::~CaptionUserPreferencesMediaAF()
151 {
152 #if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
153     if (kMAXCaptionAppearanceSettingsChangedNotification)
154         CFNotificationCenterRemoveObserver(CFNotificationCenterGetLocalCenter(), this, kMAXCaptionAppearanceSettingsChangedNotification, 0);
155 #endif
156 }
157
158 #if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
159
160 CaptionUserPreferences::CaptionDisplayMode CaptionUserPreferencesMediaAF::captionDisplayMode() const
161 {
162     if (testingMode() || !MediaAccessibilityLibrary())
163         return CaptionUserPreferences::captionDisplayMode();
164
165     MACaptionAppearanceDisplayType displayType = MACaptionAppearanceGetDisplayType(kMACaptionAppearanceDomainUser);
166     switch (displayType) {
167     case kMACaptionAppearanceDisplayTypeForcedOnly:
168         return ForcedOnly;
169
170     case kMACaptionAppearanceDisplayTypeAutomatic:
171         return Automatic;
172
173     case kMACaptionAppearanceDisplayTypeAlwaysOn:
174         return AlwaysOn;
175     }
176
177     ASSERT_NOT_REACHED();
178     return ForcedOnly;
179 }
180     
181 void CaptionUserPreferencesMediaAF::setCaptionDisplayMode(CaptionUserPreferences::CaptionDisplayMode mode)
182 {
183     if (testingMode() || !MediaAccessibilityLibrary()) {
184         CaptionUserPreferences::setCaptionDisplayMode(mode);
185         return;
186     }
187
188     MACaptionAppearanceDisplayType displayType = kMACaptionAppearanceDisplayTypeForcedOnly;
189     switch (mode) {
190     case Automatic:
191         displayType = kMACaptionAppearanceDisplayTypeAutomatic;
192         break;
193     case ForcedOnly:
194         displayType = kMACaptionAppearanceDisplayTypeForcedOnly;
195         break;
196     case AlwaysOn:
197         displayType = kMACaptionAppearanceDisplayTypeAlwaysOn;
198         break;
199     default:
200         ASSERT_NOT_REACHED();
201         break;
202     }
203
204     MACaptionAppearanceSetDisplayType(kMACaptionAppearanceDomainUser, displayType);
205 }
206
207 bool CaptionUserPreferencesMediaAF::userPrefersCaptions() const
208 {
209     bool captionSetting = CaptionUserPreferences::userPrefersCaptions();
210     if (captionSetting || testingMode() || !MediaAccessibilityLibrary())
211         return captionSetting;
212     
213     RetainPtr<CFArrayRef> captioningMediaCharacteristics = adoptCF(MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser));
214     return captioningMediaCharacteristics && CFArrayGetCount(captioningMediaCharacteristics.get());
215 }
216
217 bool CaptionUserPreferencesMediaAF::userPrefersSubtitles() const
218 {
219     bool subtitlesSetting = CaptionUserPreferences::userPrefersSubtitles();
220     if (subtitlesSetting || testingMode() || !MediaAccessibilityLibrary())
221         return subtitlesSetting;
222     
223     RetainPtr<CFArrayRef> captioningMediaCharacteristics = adoptCF(MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser));
224     return !(captioningMediaCharacteristics && CFArrayGetCount(captioningMediaCharacteristics.get()));
225 }
226
227 void CaptionUserPreferencesMediaAF::updateTimerFired()
228 {
229     updateCaptionStyleSheetOveride();
230 }
231
232 void CaptionUserPreferencesMediaAF::setInterestedInCaptionPreferenceChanges()
233 {
234     if (!MediaAccessibilityLibrary())
235         return;
236
237     if (!kMAXCaptionAppearanceSettingsChangedNotification)
238         return;
239
240     if (!m_listeningForPreferenceChanges) {
241         m_listeningForPreferenceChanges = true;
242         CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), this, userCaptionPreferencesChangedNotificationCallback, kMAXCaptionAppearanceSettingsChangedNotification, 0, CFNotificationSuspensionBehaviorCoalesce);
243     }
244
245     // Generating and registering the caption stylesheet can be expensive and this method is called indirectly when the parser creates an audio or
246     // video element, so do it after a brief pause.
247     if (m_updateStyleSheetTimer.isActive())
248         m_updateStyleSheetTimer.stop();
249     m_updateStyleSheetTimer.startOneShot(0);
250 }
251
252 void CaptionUserPreferencesMediaAF::captionPreferencesChanged()
253 {
254     if (m_listeningForPreferenceChanges)
255         updateCaptionStyleSheetOveride();
256
257     CaptionUserPreferences::captionPreferencesChanged();
258 }
259
260 String CaptionUserPreferencesMediaAF::captionsWindowCSS() const
261 {
262     MACaptionAppearanceBehavior behavior;
263     RetainPtr<CGColorRef> color = adoptCF(MACaptionAppearanceCopyWindowColor(kMACaptionAppearanceDomainUser, &behavior));
264
265     Color windowColor(color.get());
266     if (!windowColor.isValid())
267         windowColor = Color::transparent;
268
269     bool important = behavior == kMACaptionAppearanceBehaviorUseValue;
270     CGFloat opacity = MACaptionAppearanceGetWindowOpacity(kMACaptionAppearanceDomainUser, &behavior);
271     if (!important)
272         important = behavior == kMACaptionAppearanceBehaviorUseValue;
273     String windowStyle = colorPropertyCSS(CSSPropertyBackgroundColor, Color(windowColor.red(), windowColor.green(), windowColor.blue(), static_cast<int>(opacity * 255)), important);
274
275     if (!opacity)
276         return windowStyle;
277
278     StringBuilder builder;
279     builder.append(windowStyle);
280     builder.append(getPropertyNameString(CSSPropertyPadding));
281     builder.append(": .4em !important;");
282
283     return builder.toString();
284 }
285
286 String CaptionUserPreferencesMediaAF::captionsBackgroundCSS() const
287 {
288     // This default value must be the same as the one specified in mediaControls.css for -webkit-media-text-track-past-nodes
289     // and webkit-media-text-track-future-nodes.
290     DEPRECATED_DEFINE_STATIC_LOCAL(Color, defaultBackgroundColor, (Color(0, 0, 0, 0.8 * 255)));
291
292     MACaptionAppearanceBehavior behavior;
293
294     RetainPtr<CGColorRef> color = adoptCF(MACaptionAppearanceCopyBackgroundColor(kMACaptionAppearanceDomainUser, &behavior));
295     Color backgroundColor(color.get());
296     if (!backgroundColor.isValid())
297         backgroundColor = defaultBackgroundColor;
298
299     bool important = behavior == kMACaptionAppearanceBehaviorUseValue;
300     CGFloat opacity = MACaptionAppearanceGetBackgroundOpacity(kMACaptionAppearanceDomainUser, &behavior);
301     if (!important)
302         important = behavior == kMACaptionAppearanceBehaviorUseValue;
303     return colorPropertyCSS(CSSPropertyBackgroundColor, Color(backgroundColor.red(), backgroundColor.green(), backgroundColor.blue(), static_cast<int>(opacity * 255)), important);
304 }
305
306 Color CaptionUserPreferencesMediaAF::captionsTextColor(bool& important) const
307 {
308     MACaptionAppearanceBehavior behavior;
309     RetainPtr<CGColorRef> color = adoptCF(MACaptionAppearanceCopyForegroundColor(kMACaptionAppearanceDomainUser, &behavior));
310     Color textColor(color.get());
311     if (!textColor.isValid())
312         // This default value must be the same as the one specified in mediaControls.css for -webkit-media-text-track-container.
313         textColor = Color::white;
314     
315     important = behavior == kMACaptionAppearanceBehaviorUseValue;
316     CGFloat opacity = MACaptionAppearanceGetForegroundOpacity(kMACaptionAppearanceDomainUser, &behavior);
317     if (!important)
318         important = behavior == kMACaptionAppearanceBehaviorUseValue;
319     return Color(textColor.red(), textColor.green(), textColor.blue(), static_cast<int>(opacity * 255));
320 }
321     
322 String CaptionUserPreferencesMediaAF::captionsTextColorCSS() const
323 {
324     bool important;
325     Color textColor = captionsTextColor(important);
326
327     if (!textColor.isValid())
328         return emptyString();
329
330     return colorPropertyCSS(CSSPropertyColor, textColor, important);
331 }
332     
333 String CaptionUserPreferencesMediaAF::windowRoundedCornerRadiusCSS() const
334 {
335     MACaptionAppearanceBehavior behavior;
336     CGFloat radius = MACaptionAppearanceGetWindowRoundedCornerRadius(kMACaptionAppearanceDomainUser, &behavior);
337     if (!radius)
338         return emptyString();
339
340     StringBuilder builder;
341     builder.append(getPropertyNameString(CSSPropertyBorderRadius));
342     builder.append(String::format(":%.02fpx", radius));
343     if (behavior == kMACaptionAppearanceBehaviorUseValue)
344         builder.append(" !important");
345     builder.append(';');
346
347     return builder.toString();
348 }
349     
350 Color CaptionUserPreferencesMediaAF::captionsEdgeColorForTextColor(const Color& textColor) const
351 {
352     int distanceFromWhite = differenceSquared(textColor, Color::white);
353     int distanceFromBlack = differenceSquared(textColor, Color::black);
354     
355     if (distanceFromWhite < distanceFromBlack)
356         return textColor.dark();
357     
358     return textColor.light();
359 }
360
361 String CaptionUserPreferencesMediaAF::cssPropertyWithTextEdgeColor(CSSPropertyID id, const String& value, const Color& textColor, bool important) const
362 {
363     StringBuilder builder;
364     
365     builder.append(getPropertyNameString(id));
366     builder.append(':');
367     builder.append(value);
368     builder.append(' ');
369     builder.append(captionsEdgeColorForTextColor(textColor).serialized());
370     if (important)
371         builder.append(" !important");
372     builder.append(';');
373     
374     return builder.toString();
375 }
376
377 String CaptionUserPreferencesMediaAF::colorPropertyCSS(CSSPropertyID id, const Color& color, bool important) const
378 {
379     StringBuilder builder;
380     
381     builder.append(getPropertyNameString(id));
382     builder.append(':');
383     builder.append(color.serialized());
384     if (important)
385         builder.append(" !important");
386     builder.append(';');
387     
388     return builder.toString();
389 }
390
391 String CaptionUserPreferencesMediaAF::captionsTextEdgeCSS() const
392 {
393     DEPRECATED_DEFINE_STATIC_LOCAL(const String, edgeStyleRaised, (" -.05em -.05em 0 ", String::ConstructFromLiteral));
394     DEPRECATED_DEFINE_STATIC_LOCAL(const String, edgeStyleDepressed, (" .05em .05em 0 ", String::ConstructFromLiteral));
395     DEPRECATED_DEFINE_STATIC_LOCAL(const String, edgeStyleDropShadow, (" .075em .075em 0 ", String::ConstructFromLiteral));
396     DEPRECATED_DEFINE_STATIC_LOCAL(const String, edgeStyleUniform, (" .03em ", String::ConstructFromLiteral));
397
398     bool unused;
399     Color color = captionsTextColor(unused);
400     if (!color.isValid())
401         color.setNamedColor("black");
402     color = captionsEdgeColorForTextColor(color);
403
404     MACaptionAppearanceBehavior behavior;
405     MACaptionAppearanceTextEdgeStyle textEdgeStyle = MACaptionAppearanceGetTextEdgeStyle(kMACaptionAppearanceDomainUser, &behavior);
406     switch (textEdgeStyle) {
407     case kMACaptionAppearanceTextEdgeStyleUndefined:
408     case kMACaptionAppearanceTextEdgeStyleNone:
409         return emptyString();
410             
411     case kMACaptionAppearanceTextEdgeStyleRaised:
412         return cssPropertyWithTextEdgeColor(CSSPropertyTextShadow, edgeStyleRaised, color, behavior == kMACaptionAppearanceBehaviorUseValue);
413     case kMACaptionAppearanceTextEdgeStyleDepressed:
414         return cssPropertyWithTextEdgeColor(CSSPropertyTextShadow, edgeStyleDepressed, color, behavior == kMACaptionAppearanceBehaviorUseValue);
415     case kMACaptionAppearanceTextEdgeStyleDropShadow:
416         return cssPropertyWithTextEdgeColor(CSSPropertyTextShadow, edgeStyleDropShadow, color, behavior == kMACaptionAppearanceBehaviorUseValue);
417     case kMACaptionAppearanceTextEdgeStyleUniform:
418         return cssPropertyWithTextEdgeColor(CSSPropertyWebkitTextStroke, edgeStyleUniform, color, behavior == kMACaptionAppearanceBehaviorUseValue);
419             
420     default:
421         ASSERT_NOT_REACHED();
422         break;
423     }
424     
425     return emptyString();
426 }
427
428 String CaptionUserPreferencesMediaAF::captionsDefaultFontCSS() const
429 {
430     MACaptionAppearanceBehavior behavior;
431     
432     RetainPtr<CTFontDescriptorRef> font = adoptCF(MACaptionAppearanceCopyFontDescriptorForStyle(kMACaptionAppearanceDomainUser, &behavior, kMACaptionAppearanceFontStyleDefault));
433     if (!font)
434         return emptyString();
435
436     RetainPtr<CFTypeRef> name = adoptCF(CTFontDescriptorCopyAttribute(font.get(), kCTFontNameAttribute));
437     if (!name)
438         return emptyString();
439     
440     StringBuilder builder;
441     
442     builder.append(getPropertyNameString(CSSPropertyFontFamily));
443     builder.append(": \"");
444     builder.append(static_cast<CFStringRef>(name.get()));
445     builder.append('"');
446     if (behavior == kMACaptionAppearanceBehaviorUseValue)
447         builder.append(" !important");
448     builder.append(';');
449     
450     return builder.toString();
451 }
452
453 float CaptionUserPreferencesMediaAF::captionFontSizeScaleAndImportance(bool& important) const
454 {
455     if (testingMode() || !MediaAccessibilityLibrary())
456         return CaptionUserPreferences::captionFontSizeScaleAndImportance(important);
457
458     MACaptionAppearanceBehavior behavior;
459     CGFloat characterScale = CaptionUserPreferences::captionFontSizeScaleAndImportance(important);
460     CGFloat scaleAdjustment = MACaptionAppearanceGetRelativeCharacterSize(kMACaptionAppearanceDomainUser, &behavior);
461
462     if (!scaleAdjustment)
463         return characterScale;
464
465     important = behavior == kMACaptionAppearanceBehaviorUseValue;
466 #if defined(__LP64__) && __LP64__
467     return narrowPrecisionToFloat(scaleAdjustment * characterScale);
468 #else
469     return scaleAdjustment * characterScale;
470 #endif
471 }
472
473 void CaptionUserPreferencesMediaAF::setPreferredLanguage(const String& language)
474 {
475     if (testingMode() || !MediaAccessibilityLibrary()) {
476         CaptionUserPreferences::setPreferredLanguage(language);
477         return;
478     }
479
480     MACaptionAppearanceAddSelectedLanguage(kMACaptionAppearanceDomainUser, language.createCFString().get());
481 }
482
483 Vector<String> CaptionUserPreferencesMediaAF::preferredLanguages() const
484 {
485     if (testingMode() || !MediaAccessibilityLibrary())
486         return CaptionUserPreferences::preferredLanguages();
487
488     Vector<String> platformLanguages = platformUserPreferredLanguages();
489     Vector<String> override = userPreferredLanguagesOverride();
490     if (!override.isEmpty()) {
491         if (platformLanguages.size() != override.size())
492             return override;
493         for (size_t i = 0; i < override.size(); i++) {
494             if (override[i] != platformLanguages[i])
495                 return override;
496         }
497     }
498
499     CFIndex languageCount = 0;
500     RetainPtr<CFArrayRef> languages = adoptCF(MACaptionAppearanceCopySelectedLanguages(kMACaptionAppearanceDomainUser));
501     if (languages)
502         languageCount = CFArrayGetCount(languages.get());
503
504     if (!languageCount)
505         return CaptionUserPreferences::preferredLanguages();
506
507     Vector<String> userPreferredLanguages;
508     userPreferredLanguages.reserveCapacity(languageCount + platformLanguages.size());
509     for (CFIndex i = 0; i < languageCount; i++)
510         userPreferredLanguages.append(static_cast<CFStringRef>(CFArrayGetValueAtIndex(languages.get(), i)));
511
512     userPreferredLanguages.appendVector(platformLanguages);
513
514     return userPreferredLanguages;
515 }
516 #endif // HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
517
518 String CaptionUserPreferencesMediaAF::captionsStyleSheetOverride() const
519 {
520     if (testingMode())
521         return CaptionUserPreferences::captionsStyleSheetOverride();
522     
523     StringBuilder captionsOverrideStyleSheet;
524
525 #if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
526     if (!MediaAccessibilityLibrary())
527         return CaptionUserPreferences::captionsStyleSheetOverride();
528     
529     String captionsColor = captionsTextColorCSS();
530     String edgeStyle = captionsTextEdgeCSS();
531     String fontName = captionsDefaultFontCSS();
532     String background = captionsBackgroundCSS();
533     if (!background.isEmpty() || !captionsColor.isEmpty() || !edgeStyle.isEmpty() || !fontName.isEmpty()) {
534         captionsOverrideStyleSheet.append(" video::");
535         captionsOverrideStyleSheet.append(TextTrackCue::cueShadowPseudoId());
536         captionsOverrideStyleSheet.append('{');
537         
538         if (!background.isEmpty())
539             captionsOverrideStyleSheet.append(background);
540         if (!captionsColor.isEmpty())
541             captionsOverrideStyleSheet.append(captionsColor);
542         if (!edgeStyle.isEmpty())
543             captionsOverrideStyleSheet.append(edgeStyle);
544         if (!fontName.isEmpty())
545             captionsOverrideStyleSheet.append(fontName);
546         
547         captionsOverrideStyleSheet.append('}');
548     }
549     
550     String windowColor = captionsWindowCSS();
551     String windowCornerRadius = windowRoundedCornerRadiusCSS();
552     if (!windowColor.isEmpty() || !windowCornerRadius.isEmpty()) {
553         captionsOverrideStyleSheet.append(" video::");
554         captionsOverrideStyleSheet.append(VTTCue::cueBackdropShadowPseudoId());
555         captionsOverrideStyleSheet.append('{');
556         
557         if (!windowColor.isEmpty())
558             captionsOverrideStyleSheet.append(windowColor);
559         if (!windowCornerRadius.isEmpty()) {
560             captionsOverrideStyleSheet.append(windowCornerRadius);
561         }
562         
563         captionsOverrideStyleSheet.append('}');
564     }
565 #endif // HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
566
567     LOG(Media, "CaptionUserPreferencesMediaAF::captionsStyleSheetOverrideSetting sytle to:\n%s", captionsOverrideStyleSheet.toString().utf8().data());
568
569     return captionsOverrideStyleSheet.toString();
570 }
571
572 static String languageIdentifier(const String& languageCode)
573 {
574     if (languageCode.isEmpty())
575         return languageCode;
576
577     String lowercaseLanguageCode = languageCode.lower();
578
579     // Need 2U here to disambiguate String::operator[] from operator(NSString*, int)[] in a production build.
580     if (lowercaseLanguageCode.length() >= 3 && (lowercaseLanguageCode[2U] == '_' || lowercaseLanguageCode[2U] == '-'))
581         lowercaseLanguageCode.truncate(2);
582
583     return lowercaseLanguageCode;
584 }
585
586 static String trackDisplayName(TextTrack* track)
587 {
588     if (track == TextTrack::captionMenuOffItem())
589         return textTrackOffMenuItemText();
590     if (track == TextTrack::captionMenuAutomaticItem())
591         return textTrackAutomaticMenuItemText();
592
593     StringBuilder displayName;
594     String label = track->label();
595     String trackLanguageIdentifier = track->language();
596
597     RetainPtr<CFLocaleRef> currentLocale = adoptCF(CFLocaleCopyCurrent());
598     RetainPtr<CFStringRef> localeIdentifier = adoptCF(CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault, trackLanguageIdentifier.createCFString().get()));
599     RetainPtr<CFStringRef> languageCF = adoptCF(CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleLanguageCode, localeIdentifier.get()));
600     String language = languageCF.get();
601     if (!label.isEmpty()) {
602         if (language.isEmpty() || label.contains(language))
603             displayName.append(label);
604         else {
605             RetainPtr<CFDictionaryRef> localeDict = adoptCF(CFLocaleCreateComponentsFromLocaleIdentifier(kCFAllocatorDefault, localeIdentifier.get()));
606             if (localeDict) {
607                 CFStringRef countryCode = 0;
608                 String countryName;
609                 
610                 CFDictionaryGetValueIfPresent(localeDict.get(), kCFLocaleCountryCode, (const void **)&countryCode);
611                 if (countryCode) {
612                     RetainPtr<CFStringRef> countryNameCF = adoptCF(CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleCountryCode, countryCode));
613                     countryName = countryNameCF.get();
614                 }
615                 
616                 if (!countryName.isEmpty())
617                     displayName.append(textTrackCountryAndLanguageMenuItemText(label, countryName, language));
618                 else
619                     displayName.append(textTrackLanguageMenuItemText(label, language));
620             }
621         }
622     } else {
623         String languageAndLocale = adoptCF(CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleIdentifier, trackLanguageIdentifier.createCFString().get())).get();
624         if (!languageAndLocale.isEmpty())
625             displayName.append(languageAndLocale);
626         else if (!language.isEmpty())
627             displayName.append(language);
628         else
629             displayName.append(localeIdentifier.get());
630     }
631     
632     if (displayName.isEmpty())
633         displayName.append(textTrackNoLabelText());
634     
635     if (track->isEasyToRead())
636         return easyReaderTrackMenuItemText(displayName.toString());
637     
638     if (track->isClosedCaptions())
639         return closedCaptionTrackMenuItemText(displayName.toString());
640
641     if (track->isSDH())
642         return sdhTrackMenuItemText(displayName.toString());
643
644     return displayName.toString();
645 }
646
647 String CaptionUserPreferencesMediaAF::displayNameForTrack(TextTrack* track) const
648 {
649     return trackDisplayName(track);
650 }
651
652 int CaptionUserPreferencesMediaAF::textTrackSelectionScore(TextTrack* track, HTMLMediaElement* mediaElement) const
653 {
654     CaptionDisplayMode displayMode = captionDisplayMode();
655     bool legacyOverride = mediaElement->webkitClosedCaptionsVisible();
656     if (displayMode == AlwaysOn && (!userPrefersSubtitles() && !userPrefersCaptions() && !legacyOverride))
657         return 0;
658     if (track->kind() != TextTrack::captionsKeyword() && track->kind() != TextTrack::subtitlesKeyword() && track->kind() != TextTrack::forcedKeyword())
659         return 0;
660     if (!track->isMainProgramContent())
661         return 0;
662
663     bool trackHasOnlyForcedSubtitles = track->containsOnlyForcedSubtitles();
664     if (!legacyOverride && ((trackHasOnlyForcedSubtitles && displayMode != ForcedOnly) || (!trackHasOnlyForcedSubtitles && displayMode == ForcedOnly)))
665         return 0;
666
667     Vector<String> userPreferredCaptionLanguages = preferredLanguages();
668
669     if ((displayMode == Automatic && !legacyOverride) || trackHasOnlyForcedSubtitles) {
670
671         if (!mediaElement || !mediaElement->player())
672             return 0;
673
674         String textTrackLanguage = track->language();
675         if (textTrackLanguage.isEmpty())
676             return 0;
677
678         Vector<String> languageList;
679         languageList.reserveCapacity(1);
680
681         String audioTrackLanguage;
682         if (testingMode())
683             audioTrackLanguage = primaryAudioTrackLanguageOverride();
684         else
685             audioTrackLanguage = mediaElement->player()->languageOfPrimaryAudioTrack();
686
687         if (audioTrackLanguage.isEmpty())
688             return 0;
689
690         if (trackHasOnlyForcedSubtitles) {
691             languageList.append(audioTrackLanguage);
692             size_t offset = indexOfBestMatchingLanguageInList(textTrackLanguage, languageList);
693
694             // Only consider a forced-only track if it IS in the same language as the primary audio track.
695             if (offset)
696                 return 0;
697         } else {
698             languageList.append(defaultLanguage());
699
700             // Only enable a text track if the current audio track is NOT in the user's preferred language ...
701             size_t offset = indexOfBestMatchingLanguageInList(audioTrackLanguage, languageList);
702             if (!offset)
703                 return 0;
704
705             // and the text track matches the user's preferred language.
706             offset = indexOfBestMatchingLanguageInList(textTrackLanguage, languageList);
707             if (offset)
708                 return 0;
709         }
710
711         userPreferredCaptionLanguages = languageList;
712     }
713
714     int trackScore = 0;
715
716     if (userPrefersCaptions()) {
717         // When the user prefers accessibility tracks, rank is SDH, then CC, then subtitles.
718         if (track->kind() == track->subtitlesKeyword())
719             trackScore = 1;
720         else if (track->isClosedCaptions())
721             trackScore = 2;
722         else
723             trackScore = 3;
724     } else {
725         // When the user prefers translation tracks, rank is subtitles, then SDH, then CC tracks.
726         if (track->kind() == track->subtitlesKeyword())
727             trackScore = 3;
728         else if (!track->isClosedCaptions())
729             trackScore = 2;
730         else
731             trackScore = 1;
732     }
733
734     return trackScore + textTrackLanguageSelectionScore(track, userPreferredCaptionLanguages);
735 }
736
737 static bool textTrackCompare(const RefPtr<TextTrack>& a, const RefPtr<TextTrack>& b)
738 {
739     String preferredLanguageDisplayName = displayNameForLanguageLocale(languageIdentifier(defaultLanguage()));
740     String aLanguageDisplayName = displayNameForLanguageLocale(languageIdentifier(a->language()));
741     String bLanguageDisplayName = displayNameForLanguageLocale(languageIdentifier(b->language()));
742
743     // Tracks in the user's preferred language are always at the top of the menu.
744     bool aIsPreferredLanguage = !codePointCompare(aLanguageDisplayName, preferredLanguageDisplayName);
745     bool bIsPreferredLanguage = !codePointCompare(bLanguageDisplayName, preferredLanguageDisplayName);
746     if ((aIsPreferredLanguage || bIsPreferredLanguage) && (aIsPreferredLanguage != bIsPreferredLanguage))
747         return aIsPreferredLanguage;
748
749     // Tracks not in the user's preferred language sort first by language ...
750     if (codePointCompare(aLanguageDisplayName, bLanguageDisplayName))
751         return codePointCompare(aLanguageDisplayName, bLanguageDisplayName) < 0;
752
753     // ... but when tracks have the same language, main program content sorts next highest ...
754     bool aIsMainContent = a->isMainProgramContent();
755     bool bIsMainContent = b->isMainProgramContent();
756     if ((aIsMainContent || bIsMainContent) && (aIsMainContent != bIsMainContent))
757         return aIsMainContent;
758
759     // ... and main program trakcs sort higher than CC tracks ...
760     bool aIsCC = a->isClosedCaptions();
761     bool bIsCC = b->isClosedCaptions();
762     if ((aIsCC || bIsCC) && (aIsCC != bIsCC)) {
763         if (aIsCC)
764             return aIsMainContent;
765         return bIsMainContent;
766     }
767
768     // ... and tracks of the same type and language sort by the menu item text.
769     return codePointCompare(trackDisplayName(a.get()), trackDisplayName(b.get())) < 0;
770 }
771
772 Vector<RefPtr<TextTrack>> CaptionUserPreferencesMediaAF::sortedTrackListForMenu(TextTrackList* trackList)
773 {
774     ASSERT(trackList);
775
776     Vector<RefPtr<TextTrack>> tracksForMenu;
777     HashSet<String> languagesIncluded;
778     bool prefersAccessibilityTracks = userPrefersCaptions();
779     bool filterTrackList = shouldFilterTrackMenu();
780
781     for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
782         TextTrack* track = trackList->item(i);
783         String language = displayNameForLanguageLocale(track->language());
784
785         const AtomicString& kind = track->kind();
786         if (kind != TextTrack::captionsKeyword() && kind != TextTrack::descriptionsKeyword() && kind != TextTrack::subtitlesKeyword())
787             continue;
788
789         if (track->containsOnlyForcedSubtitles()) {
790             LOG(Media, "CaptionUserPreferencesMac::sortedTrackListForMenu - skipping '%s' track with language '%s' because it contains only forced subtitles", track->kind().string().utf8().data(), language.utf8().data());
791             continue;
792         }
793         
794         if (track->isEasyToRead()) {
795             LOG(Media, "CaptionUserPreferencesMac::sortedTrackListForMenu - adding '%s' track with language '%s' because it is 'easy to read'", track->kind().string().utf8().data(), language.utf8().data());
796             if (!language.isEmpty())
797                 languagesIncluded.add(language);
798             tracksForMenu.append(track);
799             continue;
800         }
801
802         if (track->mode() == TextTrack::showingKeyword()) {
803             LOG(Media, "CaptionUserPreferencesMac::sortedTrackListForMenu - adding '%s' track with language '%s' because it is already visible", track->kind().string().utf8().data(), language.utf8().data());
804             if (!language.isEmpty())
805                 languagesIncluded.add(language);
806             tracksForMenu.append(track);
807             continue;
808         }
809
810         if (!language.isEmpty() && track->isMainProgramContent()) {
811             bool isAccessibilityTrack = track->kind() == track->captionsKeyword();
812             if (prefersAccessibilityTracks) {
813                 // In the first pass, include only caption tracks if the user prefers accessibility tracks.
814                 if (!isAccessibilityTrack && filterTrackList) {
815                     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());
816                     continue;
817                 }
818             } else {
819                 // In the first pass, only include the first non-CC or SDH track with each language if the user prefers translation tracks.
820                 if (isAccessibilityTrack && filterTrackList) {
821                     LOG(Media, "CaptionUserPreferencesMediaAF::sortedTrackListForMenu - skipping '%s' track with language '%s' because it is an accessibility track", track->kind().string().utf8().data(), language.utf8().data());
822                     continue;
823                 }
824                 if (languagesIncluded.contains(language)  && filterTrackList) {
825                     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());
826                     continue;
827                 }
828             }
829         }
830
831         if (!language.isEmpty())
832             languagesIncluded.add(language);
833         tracksForMenu.append(track);
834
835         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");
836     }
837
838     // Now that we have filtered for the user's accessibility/translation preference, add  all tracks with a unique language without regard to track type.
839     for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
840         TextTrack* track = trackList->item(i);
841         String language = displayNameForLanguageLocale(track->language());
842
843         const AtomicString& kind = track->kind();
844         if (kind != TextTrack::captionsKeyword() && kind != TextTrack::descriptionsKeyword() && kind != TextTrack::subtitlesKeyword())
845             continue;
846
847         // All candidates with no languge were added the first time through.
848         if (language.isEmpty())
849             continue;
850
851         if (track->containsOnlyForcedSubtitles())
852             continue;
853
854         if (!languagesIncluded.contains(language) && track->isMainProgramContent()) {
855             languagesIncluded.add(language);
856             tracksForMenu.append(track);
857             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());
858         }
859     }
860
861     std::sort(tracksForMenu.begin(), tracksForMenu.end(), textTrackCompare);
862
863     tracksForMenu.insert(0, TextTrack::captionMenuOffItem());
864     tracksForMenu.insert(1, TextTrack::captionMenuAutomaticItem());
865
866     return tracksForMenu;
867 }
868     
869 }
870
871 #endif // ENABLE(VIDEO_TRACK)