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