1/*
2 * Copyright (C) 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 "CaptionUserPreferences.h"
31#include "DOMWrapperWorld.h"
32#include "Page.h"
33#include "PageGroup.h"
34#include "Settings.h"
35#include "TextTrackList.h"
36#include "UserContentController.h"
37#include "UserStyleSheetTypes.h"
38
39namespace WebCore {
40
41CaptionUserPreferences::CaptionUserPreferences(PageGroup& group)
42    : m_pageGroup(group)
43    , m_displayMode(ForcedOnly)
44    , m_timer(this, &CaptionUserPreferences::timerFired)
45    , m_testingMode(false)
46    , m_havePreferences(false)
47{
48}
49
50CaptionUserPreferences::~CaptionUserPreferences()
51{
52}
53
54void CaptionUserPreferences::timerFired(Timer<CaptionUserPreferences>&)
55{
56    captionPreferencesChanged();
57}
58
59void CaptionUserPreferences::notify()
60{
61    m_havePreferences = true;
62    if (!m_timer.isActive())
63        m_timer.startOneShot(0);
64}
65
66CaptionUserPreferences::CaptionDisplayMode CaptionUserPreferences::captionDisplayMode() const
67{
68    return m_displayMode;
69}
70
71void CaptionUserPreferences::setCaptionDisplayMode(CaptionUserPreferences::CaptionDisplayMode mode)
72{
73    m_displayMode = mode;
74    if (m_testingMode && mode != AlwaysOn) {
75        setUserPrefersCaptions(false);
76        setUserPrefersSubtitles(false);
77    }
78    notify();
79}
80
81bool CaptionUserPreferences::userPrefersCaptions() const
82{
83    Page* page = *(m_pageGroup.pages().begin());
84    if (!page)
85        return false;
86
87    return page->settings().shouldDisplayCaptions();
88}
89
90void CaptionUserPreferences::setUserPrefersCaptions(bool preference)
91{
92    Page* page = *(m_pageGroup.pages().begin());
93    if (!page)
94        return;
95
96    page->settings().setShouldDisplayCaptions(preference);
97    notify();
98}
99
100bool CaptionUserPreferences::userPrefersSubtitles() const
101{
102    Page* page = *(pageGroup().pages().begin());
103    if (!page)
104        return false;
105
106    return page->settings().shouldDisplaySubtitles();
107}
108
109void CaptionUserPreferences::setUserPrefersSubtitles(bool preference)
110{
111    Page* page = *(m_pageGroup.pages().begin());
112    if (!page)
113        return;
114
115    page->settings().setShouldDisplaySubtitles(preference);
116    notify();
117}
118
119bool CaptionUserPreferences::userPrefersTextDescriptions() const
120{
121    Page* page = *(m_pageGroup.pages().begin());
122    if (!page)
123        return false;
124
125    return page->settings().shouldDisplayTextDescriptions();
126}
127
128void CaptionUserPreferences::setUserPrefersTextDescriptions(bool preference)
129{
130    Page* page = *(m_pageGroup.pages().begin());
131    if (!page)
132        return;
133
134    page->settings().setShouldDisplayTextDescriptions(preference);
135    notify();
136}
137
138void CaptionUserPreferences::captionPreferencesChanged()
139{
140    m_pageGroup.captionPreferencesChanged();
141}
142
143Vector<String> CaptionUserPreferences::preferredLanguages() const
144{
145    Vector<String> languages = userPreferredLanguages();
146    if (m_testingMode && !m_userPreferredLanguage.isEmpty())
147        languages.insert(0, m_userPreferredLanguage);
148
149    return languages;
150}
151
152void CaptionUserPreferences::setPreferredLanguage(const String& language)
153{
154    m_userPreferredLanguage = language;
155    notify();
156}
157
158static String trackDisplayName(TextTrack* track)
159{
160    if (track == TextTrack::captionMenuOffItem())
161        return textTrackOffMenuItemText();
162    if (track == TextTrack::captionMenuAutomaticItem())
163        return textTrackAutomaticMenuItemText();
164
165    if (track->label().isEmpty() && track->language().isEmpty())
166        return textTrackNoLabelText();
167    if (!track->label().isEmpty())
168        return track->label();
169    return track->language();
170}
171
172String CaptionUserPreferences::displayNameForTrack(TextTrack* track) const
173{
174    return trackDisplayName(track);
175}
176
177Vector<RefPtr<TextTrack>> CaptionUserPreferences::sortedTrackListForMenu(TextTrackList* trackList)
178{
179    ASSERT(trackList);
180
181    Vector<RefPtr<TextTrack>> tracksForMenu;
182
183    for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
184        TextTrack* track = trackList->item(i);
185        const AtomicString& kind = track->kind();
186        if (kind == TextTrack::captionsKeyword() || kind == TextTrack::descriptionsKeyword() || kind == TextTrack::subtitlesKeyword())
187            tracksForMenu.append(track);
188    }
189
190    std::sort(tracksForMenu.begin(), tracksForMenu.end(), [](const RefPtr<TextTrack>& a, const RefPtr<TextTrack>& b) {
191        return codePointCompare(trackDisplayName(a.get()), trackDisplayName(b.get())) < 0;
192    });
193
194    tracksForMenu.insert(0, TextTrack::captionMenuOffItem());
195    tracksForMenu.insert(1, TextTrack::captionMenuAutomaticItem());
196
197    return tracksForMenu;
198}
199
200int CaptionUserPreferences::textTrackSelectionScore(TextTrack* track, HTMLMediaElement*) const
201{
202    int trackScore = 0;
203
204    if (track->kind() != TextTrack::captionsKeyword() && track->kind() != TextTrack::subtitlesKeyword())
205        return trackScore;
206
207    if (!userPrefersSubtitles() && !userPrefersCaptions())
208        return trackScore;
209
210    if (track->kind() == TextTrack::subtitlesKeyword() && userPrefersSubtitles())
211        trackScore = 1;
212    else if (track->kind() == TextTrack::captionsKeyword() && userPrefersCaptions())
213        trackScore = 1;
214
215    return trackScore + textTrackLanguageSelectionScore(track, preferredLanguages());
216}
217
218int CaptionUserPreferences::textTrackLanguageSelectionScore(TextTrack* track, const Vector<String>& preferredLanguages) const
219{
220    if (track->language().isEmpty())
221        return 0;
222
223    size_t languageMatchIndex = indexOfBestMatchingLanguageInList(track->language(), preferredLanguages);
224    if (languageMatchIndex >= preferredLanguages.size())
225        return 0;
226
227    // Matching a track language is more important than matching track type, so this multiplier must be
228    // greater than the maximum value returned by textTrackSelectionScore.
229    return (preferredLanguages.size() - languageMatchIndex) * 10;
230}
231
232void CaptionUserPreferences::setCaptionsStyleSheetOverride(const String& override)
233{
234    m_captionsStyleSheetOverride = override;
235    updateCaptionStyleSheetOveride();
236}
237
238void CaptionUserPreferences::updateCaptionStyleSheetOveride()
239{
240    // Identify our override style sheet with a unique URL - a new scheme and a UUID.
241    DEPRECATED_DEFINE_STATIC_LOCAL(URL, captionsStyleSheetURL, (ParsedURLString, "user-captions-override:01F6AF12-C3B0-4F70-AF5E-A3E00234DC23"));
242
243    auto& pages = m_pageGroup.pages();
244    for (auto& page : pages) {
245        if (auto* pageUserContentController = page->userContentController())
246            pageUserContentController->removeUserStyleSheet(mainThreadNormalWorld(), captionsStyleSheetURL);
247    }
248
249    String captionsOverrideStyleSheet = captionsStyleSheetOverride();
250    if (captionsOverrideStyleSheet.isEmpty())
251        return;
252
253    for (auto& page : pages) {
254        if (auto* pageUserContentController = page->userContentController()) {
255            auto userStyleSheet = std::make_unique<UserStyleSheet>(captionsOverrideStyleSheet, captionsStyleSheetURL, Vector<String>(), Vector<String>(), InjectInAllFrames, UserStyleAuthorLevel);
256            pageUserContentController->addUserStyleSheet(mainThreadNormalWorld(), std::move(userStyleSheet), InjectInExistingDocuments);
257        }
258    }
259}
260
261String CaptionUserPreferences::primaryAudioTrackLanguageOverride() const
262{
263    if (!m_primaryAudioTrackLanguageOverride.isEmpty())
264        return m_primaryAudioTrackLanguageOverride;
265    return defaultLanguage();
266}
267
268}
269
270#endif // ENABLE(VIDEO_TRACK)
271