cfea10c4d9ceea34cbdfd210b53f71aa7f81be44
[WebKit-https.git] / Source / WebCore / page / CaptionUserPreferences.cpp
1 /*
2  * Copyright (C) 2013-2016 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 "UserContentTypes.h"
39 #include "UserStyleSheet.h"
40 #include "UserStyleSheetTypes.h"
41 #include <heap/HeapInlines.h>
42 #include <runtime/JSCellInlines.h>
43 #include <runtime/StructureInlines.h>
44 #include <wtf/NeverDestroyed.h>
45
46 namespace WebCore {
47
48 CaptionUserPreferences::CaptionUserPreferences(PageGroup& group)
49     : m_pageGroup(group)
50     , m_displayMode(ForcedOnly)
51     , m_timer(*this, &CaptionUserPreferences::timerFired)
52 {
53 }
54
55 CaptionUserPreferences::~CaptionUserPreferences()
56 {
57 }
58
59 void CaptionUserPreferences::timerFired()
60 {
61     captionPreferencesChanged();
62 }
63
64 void CaptionUserPreferences::beginBlockingNotifications()
65 {
66     ++m_blockNotificationsCounter;
67 }
68
69 void CaptionUserPreferences::endBlockingNotifications()
70 {
71     ASSERT(m_blockNotificationsCounter);
72     --m_blockNotificationsCounter;
73 }
74
75 void CaptionUserPreferences::notify()
76 {
77     if (m_blockNotificationsCounter)
78         return;
79
80     m_havePreferences = true;
81     if (!m_timer.isActive())
82         m_timer.startOneShot(0);
83 }
84
85 CaptionUserPreferences::CaptionDisplayMode CaptionUserPreferences::captionDisplayMode() const
86 {
87     return m_displayMode;
88 }
89
90 void CaptionUserPreferences::setCaptionDisplayMode(CaptionUserPreferences::CaptionDisplayMode mode)
91 {
92     m_displayMode = mode;
93     if (m_testingMode && mode != AlwaysOn) {
94         setUserPrefersCaptions(false);
95         setUserPrefersSubtitles(false);
96     }
97     notify();
98 }
99
100 Page* CaptionUserPreferences::currentPage() const
101 {
102     if (m_pageGroup.pages().isEmpty())
103         return nullptr;
104
105     return *(m_pageGroup.pages().begin());
106 }
107
108 bool CaptionUserPreferences::userPrefersCaptions() const
109 {
110     Page* page = currentPage();
111     if (!page)
112         return false;
113
114     return page->settings().shouldDisplayCaptions();
115 }
116
117 void CaptionUserPreferences::setUserPrefersCaptions(bool preference)
118 {
119     Page* page = currentPage();
120     if (!page)
121         return;
122
123     page->settings().setShouldDisplayCaptions(preference);
124     notify();
125 }
126
127 bool CaptionUserPreferences::userPrefersSubtitles() const
128 {
129     Page* page = currentPage();
130     if (!page)
131         return false;
132
133     return page->settings().shouldDisplaySubtitles();
134 }
135
136 void CaptionUserPreferences::setUserPrefersSubtitles(bool preference)
137 {
138     Page* page = currentPage();
139     if (!page)
140         return;
141
142     page->settings().setShouldDisplaySubtitles(preference);
143     notify();
144 }
145
146 bool CaptionUserPreferences::userPrefersTextDescriptions() const
147 {
148     Page* page = currentPage();
149     if (!page)
150         return false;
151     
152     return page->settings().shouldDisplayTextDescriptions();
153 }
154
155 void CaptionUserPreferences::setUserPrefersTextDescriptions(bool preference)
156 {
157     Page* page = currentPage();
158     if (!page)
159         return;
160     
161     page->settings().setShouldDisplayTextDescriptions(preference);
162     notify();
163 }
164
165 void CaptionUserPreferences::captionPreferencesChanged()
166 {
167     m_pageGroup.captionPreferencesChanged();
168 }
169
170 Vector<String> CaptionUserPreferences::preferredLanguages() const
171 {
172     Vector<String> languages = userPreferredLanguages();
173     if (m_testingMode && !m_userPreferredLanguage.isEmpty())
174         languages.insert(0, m_userPreferredLanguage);
175
176     return languages;
177 }
178
179 void CaptionUserPreferences::setPreferredLanguage(const String& language)
180 {
181     m_userPreferredLanguage = language;
182     notify();
183 }
184
185 void CaptionUserPreferences::setPreferredAudioCharacteristic(const String& characteristic)
186 {
187     m_userPreferredAudioCharacteristic = characteristic;
188     notify();
189 }
190
191 Vector<String> CaptionUserPreferences::preferredAudioCharacteristics() const
192 {
193     Vector<String> characteristics;
194     if (!m_userPreferredAudioCharacteristic.isEmpty())
195         characteristics.append(m_userPreferredAudioCharacteristic);
196     return characteristics;
197 }
198
199 static String trackDisplayName(TextTrack* track)
200 {
201     if (track == TextTrack::captionMenuOffItem())
202         return textTrackOffMenuItemText();
203     if (track == TextTrack::captionMenuAutomaticItem())
204         return textTrackAutomaticMenuItemText();
205
206     if (track->label().isEmpty() && track->language().isEmpty())
207         return textTrackNoLabelText();
208     if (!track->label().isEmpty())
209         return track->label();
210     return track->language();
211 }
212
213 String CaptionUserPreferences::displayNameForTrack(TextTrack* track) const
214 {
215     return trackDisplayName(track);
216 }
217     
218 Vector<RefPtr<TextTrack>> CaptionUserPreferences::sortedTrackListForMenu(TextTrackList* trackList)
219 {
220     ASSERT(trackList);
221
222     Vector<RefPtr<TextTrack>> tracksForMenu;
223
224     for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
225         TextTrack* track = trackList->item(i);
226         auto kind = track->kind();
227         if (kind == TextTrack::Kind::Captions || kind == TextTrack::Kind::Descriptions || kind == TextTrack::Kind::Subtitles)
228             tracksForMenu.append(track);
229     }
230
231     std::sort(tracksForMenu.begin(), tracksForMenu.end(), [](auto& a, auto& b) {
232         return codePointCompare(trackDisplayName(a.get()), trackDisplayName(b.get())) < 0;
233     });
234
235     tracksForMenu.insert(0, TextTrack::captionMenuOffItem());
236     tracksForMenu.insert(1, TextTrack::captionMenuAutomaticItem());
237
238     return tracksForMenu;
239 }
240
241 static String trackDisplayName(AudioTrack* track)
242 {
243     if (track->label().isEmpty() && track->language().isEmpty())
244         return audioTrackNoLabelText();
245     if (!track->label().isEmpty())
246         return track->label();
247     return track->language();
248 }
249
250 String CaptionUserPreferences::displayNameForTrack(AudioTrack* track) const
251 {
252     return trackDisplayName(track);
253 }
254
255 Vector<RefPtr<AudioTrack>> CaptionUserPreferences::sortedTrackListForMenu(AudioTrackList* trackList)
256 {
257     ASSERT(trackList);
258
259     Vector<RefPtr<AudioTrack>> tracksForMenu;
260
261     for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
262         AudioTrack* track = trackList->item(i);
263         tracksForMenu.append(track);
264     }
265
266     std::sort(tracksForMenu.begin(), tracksForMenu.end(), [](auto& a, auto& b) {
267         return codePointCompare(trackDisplayName(a.get()), trackDisplayName(b.get())) < 0;
268     });
269
270     return tracksForMenu;
271 }
272
273 int CaptionUserPreferences::textTrackSelectionScore(TextTrack* track, HTMLMediaElement*) const
274 {
275     if (track->kind() != TextTrack::Kind::Captions && track->kind() != TextTrack::Kind::Subtitles)
276         return 0;
277     
278     if (!userPrefersSubtitles() && !userPrefersCaptions())
279         return 0;
280     
281     return textTrackLanguageSelectionScore(track, preferredLanguages()) + 1;
282 }
283
284 int CaptionUserPreferences::textTrackLanguageSelectionScore(TextTrack* track, const Vector<String>& preferredLanguages) const
285 {
286     if (track->language().isEmpty())
287         return 0;
288
289     bool exactMatch;
290     size_t languageMatchIndex = indexOfBestMatchingLanguageInList(track->language(), preferredLanguages, exactMatch);
291     if (languageMatchIndex >= preferredLanguages.size())
292         return 0;
293
294     // Matching a track language is more important than matching track type, so this multiplier must be
295     // greater than the maximum value returned by textTrackSelectionScore.
296     int bonus = exactMatch ? 1 : 0;
297     return (preferredLanguages.size() + bonus - languageMatchIndex) * 10;
298 }
299
300 void CaptionUserPreferences::setCaptionsStyleSheetOverride(const String& override)
301 {
302     m_captionsStyleSheetOverride = override;
303     updateCaptionStyleSheetOverride();
304 }
305
306 void CaptionUserPreferences::updateCaptionStyleSheetOverride()
307 {
308     String captionsOverrideStyleSheet = captionsStyleSheetOverride();
309     for (auto& page : m_pageGroup.pages())
310         page->setCaptionUserPreferencesStyleSheet(captionsOverrideStyleSheet);
311 }
312
313 String CaptionUserPreferences::primaryAudioTrackLanguageOverride() const
314 {
315     if (!m_primaryAudioTrackLanguageOverride.isEmpty())
316         return m_primaryAudioTrackLanguageOverride;
317     return defaultLanguage();
318 }
319     
320 }
321
322 #endif // ENABLE(VIDEO_TRACK)