Use "= default" to denote default constructor or destructor
[WebKit-https.git] / Source / WebCore / page / CaptionUserPreferences.cpp
1 /*
2  * Copyright (C) 2013-2017 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 "LocalizedStrings.h"
34 #include "MediaSelectionOption.h"
35 #include "Page.h"
36 #include "PageGroup.h"
37 #include "Settings.h"
38 #include "TextTrackList.h"
39 #include "UserContentController.h"
40 #include "UserContentTypes.h"
41 #include "UserStyleSheet.h"
42 #include "UserStyleSheetTypes.h"
43 #include <heap/HeapInlines.h>
44 #include <runtime/JSCellInlines.h>
45 #include <runtime/StructureInlines.h>
46 #include <wtf/Language.h>
47
48 namespace WebCore {
49
50 CaptionUserPreferences::CaptionUserPreferences(PageGroup& group)
51     : m_pageGroup(group)
52     , m_displayMode(ForcedOnly)
53     , m_timer(*this, &CaptionUserPreferences::timerFired)
54 {
55 }
56
57 CaptionUserPreferences::~CaptionUserPreferences() = default;
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_s);
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->validBCP47Language().isEmpty())
207         return textTrackNoLabelText();
208     if (!track->label().isEmpty())
209         return track->label();
210     return track->validBCP47Language();
211 }
212
213 String CaptionUserPreferences::displayNameForTrack(TextTrack* track) const
214 {
215     return trackDisplayName(track);
216 }
217
218 MediaSelectionOption CaptionUserPreferences::mediaSelectionOptionForTrack(TextTrack* track) const
219 {
220     auto type = MediaSelectionOption::Type::Regular;
221     if (track == TextTrack::captionMenuOffItem())
222         type = MediaSelectionOption::Type::LegibleOff;
223     else if (track == TextTrack::captionMenuAutomaticItem())
224         type = MediaSelectionOption::Type::LegibleAuto;
225     return { displayNameForTrack(track), type };
226 }
227     
228 Vector<RefPtr<TextTrack>> CaptionUserPreferences::sortedTrackListForMenu(TextTrackList* trackList)
229 {
230     ASSERT(trackList);
231
232     Vector<RefPtr<TextTrack>> tracksForMenu;
233
234     for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
235         TextTrack* track = trackList->item(i);
236         auto kind = track->kind();
237         if (kind == TextTrack::Kind::Captions || kind == TextTrack::Kind::Descriptions || kind == TextTrack::Kind::Subtitles)
238             tracksForMenu.append(track);
239     }
240
241     std::sort(tracksForMenu.begin(), tracksForMenu.end(), [](auto& a, auto& b) {
242         return codePointCompare(trackDisplayName(a.get()), trackDisplayName(b.get())) < 0;
243     });
244
245     tracksForMenu.insert(0, TextTrack::captionMenuOffItem());
246     tracksForMenu.insert(1, TextTrack::captionMenuAutomaticItem());
247
248     return tracksForMenu;
249 }
250
251 static String trackDisplayName(AudioTrack* track)
252 {
253     if (track->label().isEmpty() && track->validBCP47Language().isEmpty())
254         return audioTrackNoLabelText();
255     if (!track->label().isEmpty())
256         return track->label();
257     return track->validBCP47Language();
258 }
259
260 String CaptionUserPreferences::displayNameForTrack(AudioTrack* track) const
261 {
262     return trackDisplayName(track);
263 }
264
265 MediaSelectionOption CaptionUserPreferences::mediaSelectionOptionForTrack(AudioTrack* track) const
266 {
267     return { displayNameForTrack(track), MediaSelectionOption::Type::Regular };
268 }
269
270 Vector<RefPtr<AudioTrack>> CaptionUserPreferences::sortedTrackListForMenu(AudioTrackList* trackList)
271 {
272     ASSERT(trackList);
273
274     Vector<RefPtr<AudioTrack>> tracksForMenu;
275
276     for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
277         AudioTrack* track = trackList->item(i);
278         tracksForMenu.append(track);
279     }
280
281     std::sort(tracksForMenu.begin(), tracksForMenu.end(), [](auto& a, auto& b) {
282         return codePointCompare(trackDisplayName(a.get()), trackDisplayName(b.get())) < 0;
283     });
284
285     return tracksForMenu;
286 }
287
288 int CaptionUserPreferences::textTrackSelectionScore(TextTrack* track, HTMLMediaElement*) const
289 {
290     if (track->kind() != TextTrack::Kind::Captions && track->kind() != TextTrack::Kind::Subtitles)
291         return 0;
292     
293     if (!userPrefersSubtitles() && !userPrefersCaptions())
294         return 0;
295     
296     return textTrackLanguageSelectionScore(track, preferredLanguages()) + 1;
297 }
298
299 int CaptionUserPreferences::textTrackLanguageSelectionScore(TextTrack* track, const Vector<String>& preferredLanguages) const
300 {
301     if (track->validBCP47Language().isEmpty())
302         return 0;
303
304     bool exactMatch;
305     size_t languageMatchIndex = indexOfBestMatchingLanguageInList(track->validBCP47Language(), preferredLanguages, exactMatch);
306     if (languageMatchIndex >= preferredLanguages.size())
307         return 0;
308
309     // Matching a track language is more important than matching track type, so this multiplier must be
310     // greater than the maximum value returned by textTrackSelectionScore.
311     int bonus = exactMatch ? 1 : 0;
312     return (preferredLanguages.size() + bonus - languageMatchIndex) * 10;
313 }
314
315 void CaptionUserPreferences::setCaptionsStyleSheetOverride(const String& override)
316 {
317     m_captionsStyleSheetOverride = override;
318     updateCaptionStyleSheetOverride();
319 }
320
321 void CaptionUserPreferences::updateCaptionStyleSheetOverride()
322 {
323     String captionsOverrideStyleSheet = captionsStyleSheetOverride();
324     for (auto& page : m_pageGroup.pages())
325         page->setCaptionUserPreferencesStyleSheet(captionsOverrideStyleSheet);
326 }
327
328 String CaptionUserPreferences::primaryAudioTrackLanguageOverride() const
329 {
330     if (!m_primaryAudioTrackLanguageOverride.isEmpty())
331         return m_primaryAudioTrackLanguageOverride;
332     return defaultLanguage();
333 }
334     
335 }
336
337 #endif // ENABLE(VIDEO_TRACK)