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