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