CaptionUserPreferencesMac.mm 30.8 KB
Newer Older
1
/*
2
 * Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#import "config.h"

28
#if ENABLE(VIDEO_TRACK)
29 30 31 32

#import "CaptionUserPreferencesMac.h"

#import "ColorMac.h"
33
#import "CoreText/CoreText.h"
34 35
#import "DOMWrapperWorld.h"
#import "FloatConversion.h"
36
#import "HTMLMediaElement.h"
37
#import "KURL.h"
38 39
#import "Language.h"
#import "LocalizedStrings.h"
40
#import "Logging.h"
41
#import "MediaControlElements.h"
42
#import "PageGroup.h"
43
#import "SoftLinking.h"
44
#import "TextTrackCue.h"
45
#import "TextTrackList.h"
46
#import "UserStyleSheetTypes.h"
47
#import <wtf/NonCopyingSort.h>
48 49 50
#import <wtf/RetainPtr.h>
#import <wtf/text/StringBuilder.h>

51 52 53 54
#if PLATFORM(IOS)
#import "WebCoreThreadRun.h"
#endif

55
#if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
56 57 58
#import "MediaAccessibility/MediaAccessibility.h"
#endif

59
#if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
60

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
SOFT_LINK_FRAMEWORK_OPTIONAL(MediaAccessibility)

SOFT_LINK(MediaAccessibility, MACaptionAppearanceGetShowCaptions, bool, (MACaptionAppearanceDomain domain), (domain))
SOFT_LINK(MediaAccessibility, MACaptionAppearanceSetShowCaptions, void, (MACaptionAppearanceDomain domain, bool showCaptions), (domain, showCaptions))
SOFT_LINK(MediaAccessibility, MACaptionAppearanceCopyForegroundColor, CGColorRef, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
SOFT_LINK(MediaAccessibility, MACaptionAppearanceCopyBackgroundColor, CGColorRef, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
SOFT_LINK(MediaAccessibility, MACaptionAppearanceCopyWindowColor, CGColorRef, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
SOFT_LINK(MediaAccessibility, MACaptionAppearanceGetForegroundOpacity, CGFloat, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
SOFT_LINK(MediaAccessibility, MACaptionAppearanceGetBackgroundOpacity, CGFloat, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
SOFT_LINK(MediaAccessibility, MACaptionAppearanceGetWindowOpacity, CGFloat, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
SOFT_LINK(MediaAccessibility, MACaptionAppearanceGetWindowRoundedCornerRadius, CGFloat, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
SOFT_LINK(MediaAccessibility, MACaptionAppearanceCopyFontDescriptorForStyle, CTFontDescriptorRef, (MACaptionAppearanceDomain domain,  MACaptionAppearanceBehavior *behavior, MACaptionAppearanceFontStyle fontStyle), (domain, behavior, fontStyle))
SOFT_LINK(MediaAccessibility, MACaptionAppearanceGetRelativeCharacterSize, CGFloat, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
SOFT_LINK(MediaAccessibility, MACaptionAppearanceGetTextEdgeStyle, MACaptionAppearanceTextEdgeStyle, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
SOFT_LINK(MediaAccessibility, MACaptionAppearanceAddSelectedLanguage, bool, (MACaptionAppearanceDomain domain, CFStringRef language), (domain, language));
SOFT_LINK(MediaAccessibility, MACaptionAppearanceCopySelectedLanguages, CFArrayRef, (MACaptionAppearanceDomain domain), (domain));
77
SOFT_LINK(MediaAccessibility, MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics,  CFArrayRef, (MACaptionAppearanceDomain domain), (domain));
78 79 80 81

SOFT_LINK_POINTER(MediaAccessibility, kMAXCaptionAppearanceSettingsChangedNotification, CFStringRef)
#define kMAXCaptionAppearanceSettingsChangedNotification getkMAXCaptionAppearanceSettingsChangedNotification()

82 83
#endif

84 85 86 87
using namespace std;

namespace WebCore {

88
#if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
89 90
static void userCaptionPreferencesChangedNotificationCallback(CFNotificationCenterRef, void* observer, CFStringRef, const void *, CFDictionaryRef)
{
91
#if !PLATFORM(IOS)
92
    static_cast<CaptionUserPreferencesMac*>(observer)->captionPreferencesChanged();
93 94 95 96 97
#else
    WebThreadRun(^{
        static_cast<CaptionUserPreferencesMac*>(observer)->captionPreferencesChanged();
    });
#endif
98
}
99
#endif
100 101 102

CaptionUserPreferencesMac::CaptionUserPreferencesMac(PageGroup* group)
    : CaptionUserPreferences(group)
103
#if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
104
    , m_listeningForPreferenceChanges(false)
105
#endif
106 107 108 109 110
{
}

CaptionUserPreferencesMac::~CaptionUserPreferencesMac()
{
111
#if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
112 113
    if (kMAXCaptionAppearanceSettingsChangedNotification)
        CFNotificationCenterRemoveObserver(CFNotificationCenterGetLocalCenter(), this, kMAXCaptionAppearanceSettingsChangedNotification, NULL);
114
#endif
115 116
}

117
#if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
118 119

bool CaptionUserPreferencesMac::shouldShowCaptions() const
120
{
121
    if (testingMode() || !MediaAccessibilityLibrary())
122
        return CaptionUserPreferences::shouldShowCaptions();
123

124
    return MACaptionAppearanceGetShowCaptions(kMACaptionAppearanceDomainUser);
125 126
}

127
void CaptionUserPreferencesMac::setShouldShowCaptions(bool preference)
128
{
129
    if (testingMode() || !MediaAccessibilityLibrary()) {
130
        CaptionUserPreferences::setShouldShowCaptions(preference);
131 132 133
        return;
    }

134
    MACaptionAppearanceSetShowCaptions(kMACaptionAppearanceDomainUser, preference);
135 136
}

137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
bool CaptionUserPreferencesMac::userPrefersCaptions() const
{
    bool captionSetting = CaptionUserPreferences::userPrefersCaptions();
    if (captionSetting || testingMode() || !MediaAccessibilityLibrary())
        return captionSetting;
    
    RetainPtr<CFArrayRef> captioningMediaCharacteristics(AdoptCF, MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser));
    return captioningMediaCharacteristics && CFArrayGetCount(captioningMediaCharacteristics.get());
}

bool CaptionUserPreferencesMac::userPrefersSubtitles() const
{
    bool subtitlesSetting = CaptionUserPreferences::userPrefersSubtitles();
    if (subtitlesSetting || testingMode() || !MediaAccessibilityLibrary())
        return subtitlesSetting;
    
    RetainPtr<CFArrayRef> captioningMediaCharacteristics(AdoptCF, MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser));
    return !(captioningMediaCharacteristics && CFArrayGetCount(captioningMediaCharacteristics.get()));
}

157 158
bool CaptionUserPreferencesMac::userHasCaptionPreferences() const
{
159
    if (testingMode() || !MediaAccessibilityLibrary())
160 161
        return CaptionUserPreferences::userHasCaptionPreferences();

162
    return true;
163 164
}

165
void CaptionUserPreferencesMac::setInterestedInCaptionPreferenceChanges()
166
{
167 168
    if (!MediaAccessibilityLibrary())
        return;
169

170
    if (!kMAXCaptionAppearanceSettingsChangedNotification)
171
        return;
172

173 174
    if (!m_listeningForPreferenceChanges) {
        m_listeningForPreferenceChanges = true;
175
        CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), this, userCaptionPreferencesChangedNotificationCallback, kMAXCaptionAppearanceSettingsChangedNotification, NULL, CFNotificationSuspensionBehaviorCoalesce);
176 177
    }
    
178
    updateCaptionStyleSheetOveride();
179 180
}

181
void CaptionUserPreferencesMac::captionPreferencesChanged()
182
{
183
    if (m_listeningForPreferenceChanges)
184
        updateCaptionStyleSheetOveride();
185

186
    CaptionUserPreferences::captionPreferencesChanged();
187 188
}

189
String CaptionUserPreferencesMac::captionsWindowCSS() const
190
{
191 192 193
    MACaptionAppearanceBehavior behavior;
    RetainPtr<CGColorRef> color(AdoptCF, MACaptionAppearanceCopyWindowColor(kMACaptionAppearanceDomainUser, &behavior));

194 195 196
    Color windowColor(color.get());
    if (!windowColor.isValid())
        windowColor = Color::transparent;
197

198
    bool important = behavior == kMACaptionAppearanceBehaviorUseValue;
199
    CGFloat opacity = MACaptionAppearanceGetWindowOpacity(kMACaptionAppearanceDomainUser, &behavior);
200 201 202
    if (!important)
        important = behavior == kMACaptionAppearanceBehaviorUseValue;
    String windowStyle = colorPropertyCSS(CSSPropertyBackgroundColor, Color(windowColor.red(), windowColor.green(), windowColor.blue(), static_cast<int>(opacity * 255)), important);
203 204 205 206 207 208 209

    if (!opacity)
        return windowStyle;

    StringBuilder builder;
    builder.append(windowStyle);
    builder.append(getPropertyNameString(CSSPropertyPadding));
210
    builder.append(": .4em !important;");
211 212
    
    return builder.toString();
213 214
}

215
String CaptionUserPreferencesMac::captionsBackgroundCSS() const
216 217 218 219
{
    // This default value must be the same as the one specified in mediaControls.css for -webkit-media-text-track-past-nodes
    // and webkit-media-text-track-future-nodes.
    DEFINE_STATIC_LOCAL(Color, defaultBackgroundColor, (Color(0, 0, 0, 0.8 * 255)));
220 221 222 223

    MACaptionAppearanceBehavior behavior;

    RetainPtr<CGColorRef> color(AdoptCF, MACaptionAppearanceCopyBackgroundColor(kMACaptionAppearanceDomainUser, &behavior));
224
    Color backgroundColor(color.get());
225
    if (!backgroundColor.isValid())
226
        backgroundColor = defaultBackgroundColor;
227

228
    bool important = behavior == kMACaptionAppearanceBehaviorUseValue;
229
    CGFloat opacity = MACaptionAppearanceGetBackgroundOpacity(kMACaptionAppearanceDomainUser, 0);
230 231 232
    if (!important)
        important = behavior == kMACaptionAppearanceBehaviorUseValue;
    String backgroundStyle = colorPropertyCSS(CSSPropertyBackgroundColor, Color(backgroundColor.red(), backgroundColor.green(), backgroundColor.blue(), static_cast<int>(opacity * 255)), important);
233 234 235 236 237 238 239 240 241 242 243 244 245

    if (!opacity)
        return backgroundStyle;
    
    StringBuilder builder;
    builder.append(backgroundStyle);
    builder.append(getPropertyNameString(CSSPropertyPadding));
    builder.append(": 0px");
    if (behavior == kMACaptionAppearanceBehaviorUseValue)
        builder.append(" !important");
    builder.append(';');
    
    return builder.toString();
246 247
}

248
Color CaptionUserPreferencesMac::captionsTextColor(bool& important) const
249
{
250 251
    MACaptionAppearanceBehavior behavior;
    RetainPtr<CGColorRef> color(AdoptCF, MACaptionAppearanceCopyForegroundColor(kMACaptionAppearanceDomainUser, &behavior));
252
    Color textColor(color.get());
253
    if (!textColor.isValid())
254 255 256
        // This default value must be the same as the one specified in mediaControls.css for -webkit-media-text-track-container.
        textColor = Color::white;
    
257
    important = behavior == kMACaptionAppearanceBehaviorUseValue;
258 259 260
    CGFloat opacity = MACaptionAppearanceGetForegroundOpacity(kMACaptionAppearanceDomainUser, &behavior);
    if (!important)
        important = behavior == kMACaptionAppearanceBehaviorUseValue;
261 262
    return Color(textColor.red(), textColor.green(), textColor.blue(), static_cast<int>(opacity * 255));
}
263
    
264 265 266 267 268 269 270 271 272
String CaptionUserPreferencesMac::captionsTextColorCSS() const
{
    bool important;
    Color textColor = captionsTextColor(important);

    if (!textColor.isValid())
        return emptyString();

    return colorPropertyCSS(CSSPropertyColor, textColor, important);
273
}
274 275 276 277 278 279 280
    
String CaptionUserPreferencesMac::windowRoundedCornerRadiusCSS() const
{
    MACaptionAppearanceBehavior behavior;
    CGFloat radius = MACaptionAppearanceGetWindowRoundedCornerRadius(kMACaptionAppearanceDomainUser, &behavior);
    if (!radius)
        return emptyString();
281

282 283
    StringBuilder builder;
    builder.append(getPropertyNameString(CSSPropertyBorderRadius));
284
    builder.append(String::format(":%.02fpx", radius));
285 286 287 288 289 290 291
    if (behavior == kMACaptionAppearanceBehaviorUseValue)
        builder.append(" !important");
    builder.append(';');

    return builder.toString();
}
    
292 293 294 295 296 297 298 299 300 301 302
Color CaptionUserPreferencesMac::captionsEdgeColorForTextColor(const Color& textColor) const
{
    int distanceFromWhite = differenceSquared(textColor, Color::white);
    int distanceFromBlack = differenceSquared(textColor, Color::black);
    
    if (distanceFromWhite < distanceFromBlack)
        return textColor.dark();
    
    return textColor.light();
}

303
String CaptionUserPreferencesMac::cssPropertyWithTextEdgeColor(CSSPropertyID id, const String& value, const Color& textColor, bool important) const
304 305 306 307 308 309 310 311
{
    StringBuilder builder;
    
    builder.append(getPropertyNameString(id));
    builder.append(':');
    builder.append(value);
    builder.append(' ');
    builder.append(captionsEdgeColorForTextColor(textColor).serialized());
312 313
    if (important)
        builder.append(" !important");
314 315 316 317 318
    builder.append(';');
    
    return builder.toString();
}

319
String CaptionUserPreferencesMac::colorPropertyCSS(CSSPropertyID id, const Color& color, bool important) const
320 321 322 323 324 325
{
    StringBuilder builder;
    
    builder.append(getPropertyNameString(id));
    builder.append(':');
    builder.append(color.serialized());
326 327
    if (important)
        builder.append(" !important");
328 329 330 331 332
    builder.append(';');
    
    return builder.toString();
}

333
String CaptionUserPreferencesMac::captionsTextEdgeCSS() const
334 335 336 337 338
{
    DEFINE_STATIC_LOCAL(const String, edgeStyleRaised, (" -.05em -.05em 0 ", String::ConstructFromLiteral));
    DEFINE_STATIC_LOCAL(const String, edgeStyleDepressed, (" .05em .05em 0 ", String::ConstructFromLiteral));
    DEFINE_STATIC_LOCAL(const String, edgeStyleDropShadow, (" .075em .075em 0 ", String::ConstructFromLiteral));
    DEFINE_STATIC_LOCAL(const String, edgeStyleUniform, (" .03em ", String::ConstructFromLiteral));
339 340 341

    bool unused;
    Color color = captionsTextColor(unused);
342 343 344
    if (!color.isValid())
        color.setNamedColor("black");
    color = captionsEdgeColorForTextColor(color);
345 346 347

    MACaptionAppearanceBehavior behavior;
    MACaptionAppearanceTextEdgeStyle textEdgeStyle = MACaptionAppearanceGetTextEdgeStyle(kMACaptionAppearanceDomainUser, &behavior);
348
    switch (textEdgeStyle) {
349 350
        case kMACaptionAppearanceTextEdgeStyleUndefined:
        case kMACaptionAppearanceTextEdgeStyleNone:
351 352
            return emptyString();
            
353 354 355 356 357 358 359 360
        case kMACaptionAppearanceTextEdgeStyleRaised:
            return cssPropertyWithTextEdgeColor(CSSPropertyTextShadow, edgeStyleRaised, color, behavior == kMACaptionAppearanceBehaviorUseValue);
        case kMACaptionAppearanceTextEdgeStyleDepressed:
            return cssPropertyWithTextEdgeColor(CSSPropertyTextShadow, edgeStyleDepressed, color, behavior == kMACaptionAppearanceBehaviorUseValue);
        case kMACaptionAppearanceTextEdgeStyleDropShadow:
            return cssPropertyWithTextEdgeColor(CSSPropertyTextShadow, edgeStyleDropShadow, color, behavior == kMACaptionAppearanceBehaviorUseValue);
        case kMACaptionAppearanceTextEdgeStyleUniform:
            return cssPropertyWithTextEdgeColor(CSSPropertyWebkitTextStroke, edgeStyleUniform, color, behavior == kMACaptionAppearanceBehaviorUseValue);
361 362 363 364 365 366 367 368 369
            
        default:
            ASSERT_NOT_REACHED();
            break;
    }
    
    return emptyString();
}

370
String CaptionUserPreferencesMac::captionsDefaultFontCSS() const
371
{
372 373 374
    MACaptionAppearanceBehavior behavior;
    
    RetainPtr<CTFontDescriptorRef> font(AdoptCF, MACaptionAppearanceCopyFontDescriptorForStyle(kMACaptionAppearanceDomainUser, &behavior, kMACaptionAppearanceFontStyleDefault));
375 376
    if (!font)
        return emptyString();
377 378

    RetainPtr<CFTypeRef> name(AdoptCF, CTFontDescriptorCopyAttribute(font.get(), kCTFontNameAttribute));
379 380 381 382 383 384 385
    if (!name)
        return emptyString();
    
    StringBuilder builder;
    
    builder.append(getPropertyNameString(CSSPropertyFontFamily));
    builder.append(": \"");
386 387 388 389 390
    builder.append(static_cast<CFStringRef>(name.get()));
    builder.append('"');
    if (behavior == kMACaptionAppearanceBehaviorUseValue)
        builder.append(" !important");
    builder.append(';');
391 392 393 394 395 396
    
    return builder.toString();
}

String CaptionUserPreferencesMac::captionsStyleSheetOverride() const
{
397
    if (testingMode() || !MediaAccessibilityLibrary())
398 399
        return CaptionUserPreferences::captionsStyleSheetOverride();

400
    StringBuilder captionsOverrideStyleSheet;
401 402 403

    String background = captionsBackgroundCSS();
    if (!background.isEmpty()) {
404
        captionsOverrideStyleSheet.append(" video::");
405
        captionsOverrideStyleSheet.append(TextTrackCue::cueShadowPseudoId());
406
        captionsOverrideStyleSheet.append('{');
407
        captionsOverrideStyleSheet.append(background);
408 409
        captionsOverrideStyleSheet.append('}');
    }
410 411 412

    String windowColor = captionsWindowCSS();
    String windowCornerRadius = windowRoundedCornerRadiusCSS();
413
    if (!windowColor.isEmpty() || !windowCornerRadius.isEmpty()) {
414 415 416
        captionsOverrideStyleSheet.append(" video::");
        captionsOverrideStyleSheet.append(TextTrackCueBox::textTrackCueBoxShadowPseudoId());
        captionsOverrideStyleSheet.append('{');
417 418 419 420 421

        if (!windowColor.isEmpty())
            captionsOverrideStyleSheet.append(windowColor);
        if (!windowCornerRadius.isEmpty())
            captionsOverrideStyleSheet.append(windowCornerRadius);
422 423 424 425 426 427 428 429 430 431 432 433

        captionsOverrideStyleSheet.append('}');
    }
    
    String captionsColor = captionsTextColorCSS();
    String edgeStyle = captionsTextEdgeCSS();
    String fontName = captionsDefaultFontCSS();
    if (!captionsColor.isEmpty() || !edgeStyle.isEmpty() || !fontName.isEmpty()) {
        captionsOverrideStyleSheet.append(" video::");
        captionsOverrideStyleSheet.append(MediaControlTextTrackContainerElement::textTrackContainerElementShadowPseudoId());
        captionsOverrideStyleSheet.append('{');

434 435
        if (!captionsColor.isEmpty())
            captionsOverrideStyleSheet.append(captionsColor);
436 437 438 439
        if (!edgeStyle.isEmpty())
            captionsOverrideStyleSheet.append(edgeStyle);
        if (!fontName.isEmpty())
            captionsOverrideStyleSheet.append(fontName);
440

441 442
        captionsOverrideStyleSheet.append('}');
    }
443
    
444 445
    LOG(Media, "CaptionUserPreferencesMac::captionsStyleSheetOverrideSetting sytle to:\n%s", captionsOverrideStyleSheet.toString().utf8().data());

446 447 448
    return captionsOverrideStyleSheet.toString();
}

449
float CaptionUserPreferencesMac::captionFontSizeScale(bool& important) const
450
{
451
    if (testingMode() || !MediaAccessibilityLibrary())
452 453
        return CaptionUserPreferences::captionFontSizeScale(important);

454 455 456 457 458
    MACaptionAppearanceBehavior behavior;
    CGFloat characterScale = CaptionUserPreferences::captionFontSizeScale(important);
    CGFloat scaleAdjustment = MACaptionAppearanceGetRelativeCharacterSize(kMACaptionAppearanceDomainUser, &behavior);

    if (!scaleAdjustment)
459
        return characterScale;
460 461

    important = behavior == kMACaptionAppearanceBehaviorUseValue;
462
#if defined(__LP64__) && __LP64__
463
    return narrowPrecisionToFloat(scaleAdjustment * characterScale);
464 465 466
#else
    return scaleAdjustment * characterScale;
#endif
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
}

void CaptionUserPreferencesMac::updateCaptionStyleSheetOveride()
{
    // Identify our override style sheet with a unique URL - a new scheme and a UUID.
    DEFINE_STATIC_LOCAL(KURL, captionsStyleSheetURL, (ParsedURLString, "user-captions-override:01F6AF12-C3B0-4F70-AF5E-A3E00234DC23"));
    
    pageGroup()->removeUserStyleSheetFromWorld(mainThreadNormalWorld(), captionsStyleSheetURL);
    
    String captionsOverrideStyleSheet = captionsStyleSheetOverride();
    if (captionsOverrideStyleSheet.isEmpty())
        return;
    
    pageGroup()->addUserStyleSheetToWorld(mainThreadNormalWorld(), captionsOverrideStyleSheet, captionsStyleSheetURL, Vector<String>(),
             Vector<String>(), InjectInAllFrames, UserStyleAuthorLevel, InjectInExistingDocuments);
}

484
void CaptionUserPreferencesMac::setPreferredLanguage(String language)
485
{
486
    if (testingMode() || !MediaAccessibilityLibrary()) {
487 488 489 490
        CaptionUserPreferences::setPreferredLanguage(language);
        return;
    }

491 492 493 494 495
    MACaptionAppearanceAddSelectedLanguage(kMACaptionAppearanceDomainUser, language.createCFString().get());
}

Vector<String> CaptionUserPreferencesMac::preferredLanguages() const
{
496
    if (testingMode() || !MediaAccessibilityLibrary())
497 498
        return CaptionUserPreferences::preferredLanguages();

499 500 501 502 503 504 505 506 507 508 509
    Vector<String> platformLanguages = platformUserPreferredLanguages();
    Vector<String> override = userPreferredLanguagesOverride();
    if (!override.isEmpty()) {
        if (platformLanguages.size() != override.size())
            return override;
        for (size_t i = 0; i < override.size(); i++) {
            if (override[i] != platformLanguages[i])
                return override;
        }
    }

510
    CFIndex languageCount = 0;
511
    RetainPtr<CFArrayRef> languages(AdoptCF, MACaptionAppearanceCopySelectedLanguages(kMACaptionAppearanceDomainUser));
512 513
    if (languages)
        languageCount = CFArrayGetCount(languages.get());
514 515 516 517

    if (!languageCount)
        return CaptionUserPreferences::preferredLanguages();

518 519
    Vector<String> userPreferredLanguages;
    userPreferredLanguages.reserveCapacity(languageCount + platformLanguages.size());
520 521 522
    for (CFIndex i = 0; i < languageCount; i++)
        userPreferredLanguages.append(static_cast<CFStringRef>(CFArrayGetValueAtIndex(languages.get(), i)));

523 524
    userPreferredLanguages.append(platformLanguages);

525 526
    return userPreferredLanguages;
}
527 528 529
#endif  // HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
    
static String trackDisplayName(TextTrack* track)
530 531
{
    StringBuilder displayName;
532 533 534 535 536 537 538 539
    String label = track->label();
    String trackLanguageIdentifier = track->language();

    RetainPtr<CFLocaleRef> currentLocale(AdoptCF, CFLocaleCopyCurrent());
    RetainPtr<CFStringRef> localeIdentifier(AdoptCF, CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault, trackLanguageIdentifier.createCFString().get()));
    RetainPtr<CFStringRef> languageCF(AdoptCF, CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleLanguageCode, localeIdentifier.get()));
    String language = languageCF.get();
    if (!label.isEmpty()) {
540
        if (language.isEmpty() || label.contains(language))
541
            displayName.append(label);
542
        else {
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
            RetainPtr<CFDictionaryRef> localeDict(AdoptCF, CFLocaleCreateComponentsFromLocaleIdentifier(kCFAllocatorDefault, localeIdentifier.get()));
            if (localeDict) {
                CFStringRef countryCode = 0;
                String countryName;
                
                CFDictionaryGetValueIfPresent(localeDict.get(), kCFLocaleCountryCode, (const void **)&countryCode);
                if (countryCode) {
                    RetainPtr<CFStringRef> countryNameCF(AdoptCF, CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleCountryCode, countryCode));
                    countryName = countryNameCF.get();
                }
                
                if (!countryName.isEmpty())
                    displayName.append(textTrackCountryAndLanguageMenuItemText(label, countryName, language));
                else
                    displayName.append(textTrackLanguageMenuItemText(label, language));
            }
        }
    } else {
        String languageAndLocale = CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleIdentifier, trackLanguageIdentifier.createCFString().get());
        if (!languageAndLocale.isEmpty())
            displayName.append(languageAndLocale);
        else if (!language.isEmpty())
            displayName.append(language);
        else
            displayName.append(localeIdentifier.get());
    }
    
    if (displayName.isEmpty())
571
        displayName.append(textTrackNoLabelText());
572 573 574 575 576
    
    if (track->isEasyToRead())
        return easyReaderTrackMenuItemText(displayName.toString());
    
    if (track->kind() != track->captionsKeyword())
577
        return displayName.toString();
578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
    
    if (track->isClosedCaptions())
        return closedCaptionTrackMenuItemText(displayName.toString());
    
    return sdhTrackMenuItemText(displayName.toString());
}

String CaptionUserPreferencesMac::displayNameForTrack(TextTrack* track) const
{
    return trackDisplayName(track);
}

static String languageIdentifier(const String& languageCode)
{
    if (languageCode.isEmpty())
        return languageCode;

    String lowercaseLanguageCode = languageCode.lower();
596 597 598

    // Need 2U here to disambiguate String::operator[] from operator(NSString*, int)[] in a production build.
    if (lowercaseLanguageCode.length() >= 3 && (lowercaseLanguageCode[2U] == '_' || lowercaseLanguageCode[2U] == '-'))
599 600 601 602 603
        lowercaseLanguageCode.truncate(2);
    
    return lowercaseLanguageCode;
}

604
int CaptionUserPreferencesMac::textTrackSelectionScore(TextTrack* track, HTMLMediaElement* mediaElement) const
605
{
606
    if (!shouldShowCaptions() && !mediaElement->webkitClosedCaptionsVisible())
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
        return 0;
    if (track->kind() != TextTrack::captionsKeyword() && track->kind() != TextTrack::subtitlesKeyword())
        return 0;
    if (track->containsOnlyForcedSubtitles())
        return 0;
    if (!track->isMainProgramContent())
        return 0;

    int trackScore = 0;

    if (userPrefersCaptions()) {
        // When the user prefers accessiblity tracks, rank is SDH, then CC, then subtitles.
        if (track->kind() == track->subtitlesKeyword())
            trackScore = 1;
        else if (track->isClosedCaptions())
            trackScore = 2;
        else
            trackScore = 3;
    } else {
        // When the user prefers translation tracks, rank is subtitles, then SDH, then CC tracks.
        if (track->kind() == track->subtitlesKeyword())
            trackScore = 3;
        else if (!track->isClosedCaptions())
            trackScore = 2;
        else
            trackScore = 1;
    }

    return trackScore + textTrackLanguageSelectionScore(track);
}

638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666
static bool textTrackCompare(const RefPtr<TextTrack>& a, const RefPtr<TextTrack>& b)
{
    String preferredLanguageDisplayName = displayNameForLanguageLocale(languageIdentifier(defaultLanguage()));
    String aLanguageDisplayName = displayNameForLanguageLocale(languageIdentifier(a->language()));
    String bLanguageDisplayName = displayNameForLanguageLocale(languageIdentifier(b->language()));

    // Tracks in the user's preferred language are always at the top of the menu.
    bool aIsPreferredLanguage = !codePointCompare(aLanguageDisplayName, preferredLanguageDisplayName);
    bool bIsPreferredLanguage = !codePointCompare(bLanguageDisplayName, preferredLanguageDisplayName);
    if ((aIsPreferredLanguage || bIsPreferredLanguage) && (aIsPreferredLanguage != bIsPreferredLanguage))
        return aIsPreferredLanguage;

    // Tracks not in the user's preferred language sort first by language ...
    if (codePointCompare(aLanguageDisplayName, bLanguageDisplayName))
        return codePointCompare(aLanguageDisplayName, bLanguageDisplayName) < 0;

    // ... but when tracks have the same language, main program content sorts next highest ...
    bool aIsMainContent = a->isMainProgramContent();
    bool bIsMainContent = b->isMainProgramContent();
    if ((aIsMainContent || bIsMainContent) && (aIsMainContent != bIsMainContent))
        return aIsMainContent;

    // ... and main program trakcs sort higher than CC tracks ...
    bool aIsCC = a->isClosedCaptions();
    bool bIsCC = b->isClosedCaptions();
    if ((aIsCC || bIsCC) && (aIsCC != bIsCC)) {
        if (aIsCC)
            return aIsMainContent;
        return bIsMainContent;
667 668
    }

669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
    // ... and tracks of the same type and language sort by the menu item text.
    return codePointCompare(trackDisplayName(a.get()), trackDisplayName(b.get())) < 0;
}

Vector<RefPtr<TextTrack> > CaptionUserPreferencesMac::sortedTrackListForMenu(TextTrackList* trackList)
{
    ASSERT(trackList);

    Vector<RefPtr<TextTrack> > tracksForMenu;
    HashSet<String> languagesIncluded;
    bool prefersAccessibilityTracks = userPrefersCaptions();

    for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
        TextTrack* track = trackList->item(i);
        String language = displayNameForLanguageLocale(track->language());
684

685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718
        if (track->isEasyToRead()) {
            if (!language.isEmpty())
                languagesIncluded.add(language);
            tracksForMenu.append(track);
            continue;
        }

        if (track->containsOnlyForcedSubtitles())
            continue;

        if (!language.isEmpty() && track->isMainProgramContent()) {
            bool isAccessibilityTrack = track->kind() == track->captionsKeyword();
            if (prefersAccessibilityTracks) {
                // In the first pass, include only caption tracks if the user prefers accessibility tracks.
                if (!isAccessibilityTrack) {
                    LOG(Media, "CaptionUserPreferencesMac::sortedTrackListForMenu - skipping %s because it is NOT an accessibility track", language.utf8().data());
                    continue;
                }
            } else {
                // In the first pass, only include the first non-CC or SDH track with each language if the user prefers translation tracks.
                if (isAccessibilityTrack) {
                    LOG(Media, "CaptionUserPreferencesMac::sortedTrackListForMenu - skipping %s because it is an accessibility track", language.utf8().data());
                    continue;
                }
                if (languagesIncluded.contains(language)) {
                    LOG(Media, "CaptionUserPreferencesMac::sortedTrackListForMenu - skipping %s because it is not the first with this language", language.utf8().data());
                    continue;
                }
            }
        }

        if (!language.isEmpty())
            languagesIncluded.add(language);
        tracksForMenu.append(track);
719 720
    }

721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
    // Now that we have filtered for the user's accessibility/translation preference, add  all tracks with a unique language without regard to track type.
    for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
        TextTrack* track = trackList->item(i);
        String language = displayNameForLanguageLocale(track->language());

        // All candidates with no languge were added the first time through.
        if (language.isEmpty())
            continue;

        if (track->containsOnlyForcedSubtitles())
            continue;
 
        if (!languagesIncluded.contains(language) && track->isMainProgramContent()) {
            languagesIncluded.add(language);
            tracksForMenu.append(track);
        }
737 738
    }

739
    nonCopyingSort(tracksForMenu.begin(), tracksForMenu.end(), textTrackCompare);
740

741 742 743
    return tracksForMenu;
}
    
744 745
}

746
#endif // ENABLE(VIDEO_TRACK)