CaptionUserPreferencesMac.mm 30.7 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
36
#import "DOMWrapperWorld.h"
#import "FloatConversion.h"
#import "KURL.h"
37
38
#import "Language.h"
#import "LocalizedStrings.h"
39
#import "Logging.h"
40
#import "MediaControlElements.h"
41
#import "PageGroup.h"
42
#import "SoftLinking.h"
43
#import "TextTrackCue.h"
44
#import "TextTrackList.h"
45
#import "UserStyleSheetTypes.h"
46
#import <wtf/NonCopyingSort.h>
47
48
49
#import <wtf/RetainPtr.h>
#import <wtf/text/StringBuilder.h>

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

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

58
#if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
59

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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));
76
SOFT_LINK(MediaAccessibility, MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics,  CFArrayRef, (MACaptionAppearanceDomain domain), (domain));
77
78
79
80

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

81
82
#endif

83
84
85
86
using namespace std;

namespace WebCore {

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

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

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

116
#if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
117
118

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

123
    return MACaptionAppearanceGetShowCaptions(kMACaptionAppearanceDomainUser);
124
125
}

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

133
    MACaptionAppearanceSetShowCaptions(kMACaptionAppearanceDomainUser, preference);
134
135
}

136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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()));
}

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

161
    return true;
162
163
}

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

169
    if (!kMAXCaptionAppearanceSettingsChangedNotification)
170
        return;
171

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

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

185
    CaptionUserPreferences::captionPreferencesChanged();
186
187
}

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

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

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

    if (!opacity)
        return windowStyle;

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

214
String CaptionUserPreferencesMac::captionsBackgroundCSS() const
215
216
217
218
{
    // 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)));
219
220
221
222

    MACaptionAppearanceBehavior behavior;

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

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

    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();
245
246
}

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

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

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

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

    return builder.toString();
}
    
291
292
293
294
295
296
297
298
299
300
301
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();
}

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

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

332
String CaptionUserPreferencesMac::captionsTextEdgeCSS() const
333
334
335
336
337
{
    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));
338
339
340

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

    MACaptionAppearanceBehavior behavior;
    MACaptionAppearanceTextEdgeStyle textEdgeStyle = MACaptionAppearanceGetTextEdgeStyle(kMACaptionAppearanceDomainUser, &behavior);
347
    switch (textEdgeStyle) {
348
349
        case kMACaptionAppearanceTextEdgeStyleUndefined:
        case kMACaptionAppearanceTextEdgeStyleNone:
350
351
            return emptyString();
            
352
353
354
355
356
357
358
359
        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);
360
361
362
363
364
365
366
367
368
            
        default:
            ASSERT_NOT_REACHED();
            break;
    }
    
    return emptyString();
}

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

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

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

399
    StringBuilder captionsOverrideStyleSheet;
400
401
402

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

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

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

        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('{');

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

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

445
446
447
    return captionsOverrideStyleSheet.toString();
}

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

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

    if (!scaleAdjustment)
458
        return characterScale;
459
460

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

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);
}

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

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

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

498
499
500
501
502
503
504
505
506
507
508
    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;
        }
    }

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

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

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

522
523
    userPreferredLanguages.append(platformLanguages);

524
525
    return userPreferredLanguages;
}
526
527
528
#endif  // HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
    
static String trackDisplayName(TextTrack* track)
529
530
{
    StringBuilder displayName;
531
532
533
534
535
536
537
538
    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()) {
539
540
541
        if (language.isEmpty() || label.contains(language)) {
            displayName.append(label);
        } else {
542
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
            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())
570
        displayName.append(textTrackNoLabelText());
571
572
573
574
575
    
    if (track->isEasyToRead())
        return easyReaderTrackMenuItemText(displayName.toString());
    
    if (track->kind() != track->captionsKeyword())
576
        return displayName.toString();
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
    
    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();
595
596
597

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

603
604
605
606
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
int CaptionUserPreferencesMac::textTrackSelectionScore(TextTrack* track) const
{
    if (!shouldShowCaptions())
        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);
}

637
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
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;
666
667
    }

668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
    // ... 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());
683

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
        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);
718
719
    }

720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
    // 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);
        }
736
737
    }

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

740
741
742
    return tracksForMenu;
}
    
743
744
}

745
#endif // ENABLE(VIDEO_TRACK)