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 <JavaScriptCore/HeapInlines.h> |
44 | #include <JavaScriptCore/JSCellInlines.h> |
45 | #include <JavaScriptCore/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::() const |
128 | { |
129 | Page* page = currentPage(); |
130 | if (!page) |
131 | return false; |
132 | |
133 | return page->settings().shouldDisplaySubtitles(); |
134 | } |
135 | |
136 | void CaptionUserPreferences::(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::(TextTrackList* trackList) |
229 | { |
230 | ASSERT(trackList); |
231 | |
232 | Vector<RefPtr<TextTrack>> ; |
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::(AudioTrackList* trackList) |
271 | { |
272 | ASSERT(trackList); |
273 | |
274 | Vector<RefPtr<AudioTrack>> ; |
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 | if (override == m_captionsStyleSheetOverride) |
318 | return; |
319 | |
320 | m_captionsStyleSheetOverride = override; |
321 | updateCaptionStyleSheetOverride(); |
322 | if (!m_timer.isActive()) |
323 | m_timer.startOneShot(0_s); |
324 | } |
325 | |
326 | void CaptionUserPreferences::updateCaptionStyleSheetOverride() |
327 | { |
328 | String captionsOverrideStyleSheet = captionsStyleSheetOverride(); |
329 | for (auto& page : m_pageGroup.pages()) |
330 | page->setCaptionUserPreferencesStyleSheet(captionsOverrideStyleSheet); |
331 | } |
332 | |
333 | String CaptionUserPreferences::primaryAudioTrackLanguageOverride() const |
334 | { |
335 | if (!m_primaryAudioTrackLanguageOverride.isEmpty()) |
336 | return m_primaryAudioTrackLanguageOverride; |
337 | return defaultLanguage(); |
338 | } |
339 | |
340 | } |
341 | |
342 | #endif // ENABLE(VIDEO_TRACK) |
343 | |