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