Remove excessive include directives from WebCore/css
[WebKit-https.git] / Source / WebCore / html / track / TextTrack.cpp
1 /*
2  * Copyright (C) 2011, 2013 Google Inc. All rights reserved.
3  * Copyright (C) 2011-2017 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "config.h"
33 #include "TextTrack.h"
34
35 #if ENABLE(VIDEO_TRACK)
36
37 #include "Event.h"
38 #include "ExceptionCode.h"
39 #include "HTMLMediaElement.h"
40 #include "SourceBuffer.h"
41 #include "TextTrackCueList.h"
42 #include "TextTrackList.h"
43 #include "VTTRegion.h"
44 #include "VTTRegionList.h"
45 #include <wtf/NeverDestroyed.h>
46
47 namespace WebCore {
48
49 const AtomicString& TextTrack::subtitlesKeyword()
50 {
51     static NeverDestroyed<const AtomicString> subtitles("subtitles", AtomicString::ConstructFromLiteral);
52     return subtitles;
53 }
54
55 static const AtomicString& captionsKeyword()
56 {
57     static NeverDestroyed<const AtomicString> captions("captions", AtomicString::ConstructFromLiteral);
58     return captions;
59 }
60
61 static const AtomicString& descriptionsKeyword()
62 {
63     static NeverDestroyed<const AtomicString> descriptions("descriptions", AtomicString::ConstructFromLiteral);
64     return descriptions;
65 }
66
67 static const AtomicString& chaptersKeyword()
68 {
69     static NeverDestroyed<const AtomicString> chapters("chapters", AtomicString::ConstructFromLiteral);
70     return chapters;
71 }
72
73 static const AtomicString& metadataKeyword()
74 {
75     static NeverDestroyed<const AtomicString> metadata("metadata", AtomicString::ConstructFromLiteral);
76     return metadata;
77 }
78     
79 static const AtomicString& forcedKeyword()
80 {
81     static NeverDestroyed<const AtomicString> forced("forced", AtomicString::ConstructFromLiteral);
82     return forced;
83 }
84
85 TextTrack* TextTrack::captionMenuOffItem()
86 {
87     static TextTrack& off = TextTrack::create(nullptr, nullptr, "off menu item", emptyAtom, emptyAtom, emptyAtom).leakRef();
88     return &off;
89 }
90
91 TextTrack* TextTrack::captionMenuAutomaticItem()
92 {
93     static TextTrack& automatic = TextTrack::create(nullptr, nullptr, "automatic menu item", emptyAtom, emptyAtom, emptyAtom).leakRef();
94     return &automatic;
95 }
96
97 TextTrack::TextTrack(ScriptExecutionContext* context, TextTrackClient* client, const AtomicString& kind, const AtomicString& id, const AtomicString& label, const AtomicString& language, TextTrackType type)
98     : TrackBase(TrackBase::TextTrack, id, label, language)
99     , ContextDestructionObserver(context)
100     , m_client(client)
101     , m_trackType(type)
102 {
103     if (kind == captionsKeyword())
104         m_kind = Kind::Captions;
105     else if (kind == chaptersKeyword())
106         m_kind = Kind::Chapters;
107     else if (kind == descriptionsKeyword())
108         m_kind = Kind::Descriptions;
109     else if (kind == forcedKeyword())
110         m_kind = Kind::Forced;
111     else if (kind == metadataKeyword())
112         m_kind = Kind::Metadata;
113 }
114
115 TextTrack::~TextTrack()
116 {
117     if (m_cues) {
118         if (m_client)
119             m_client->textTrackRemoveCues(*this, *m_cues);
120         for (size_t i = 0; i < m_cues->length(); ++i)
121             m_cues->item(i)->setTrack(nullptr);
122     }
123     if (m_regions) {
124         for (size_t i = 0; i < m_regions->length(); ++i)
125             m_regions->item(i)->setTrack(nullptr);
126     }
127 }
128
129 bool TextTrack::enabled() const
130 {
131     return m_mode != Mode::Disabled;
132 }
133
134 bool TextTrack::isValidKindKeyword(const AtomicString& value)
135 {
136     if (value == subtitlesKeyword())
137         return true;
138     if (value == captionsKeyword())
139         return true;
140     if (value == descriptionsKeyword())
141         return true;
142     if (value == chaptersKeyword())
143         return true;
144     if (value == metadataKeyword())
145         return true;
146     if (value == forcedKeyword())
147         return true;
148
149     return false;
150 }
151
152 const AtomicString& TextTrack::kindKeyword() const
153 {
154     switch (m_kind) {
155     case Kind::Captions:
156         return captionsKeyword();
157     case Kind::Chapters:
158         return chaptersKeyword();
159     case Kind::Descriptions:
160         return descriptionsKeyword();
161     case Kind::Forced:
162         return forcedKeyword();
163     case Kind::Metadata:
164         return metadataKeyword();
165     case Kind::Subtitles:
166         return subtitlesKeyword();
167     }
168     ASSERT_NOT_REACHED();
169     return subtitlesKeyword();
170 }
171
172 void TextTrack::setKind(Kind newKind)
173 {
174     auto oldKind = m_kind;
175
176     // 10.1 kind, on setting:
177     // 1. If the value being assigned to this attribute does not match one of the text track kinds,
178     // then abort these steps.
179
180     // 2. Update this attribute to the new value.
181     m_kind = newKind;
182
183 #if ENABLE(MEDIA_SOURCE)
184     // 3. If the sourceBuffer attribute on this track is not null, then queue a task to fire a simple
185     // event named change at sourceBuffer.textTracks.
186     if (m_sourceBuffer)
187         m_sourceBuffer->textTracks().scheduleChangeEvent();
188
189     // 4. Queue a task to fire a simple event named change at the TextTrackList object referenced by
190     // the textTracks attribute on the HTMLMediaElement.
191     if (mediaElement())
192         mediaElement()->textTracks().scheduleChangeEvent();
193 #endif
194
195     if (m_client && oldKind != m_kind)
196         m_client->textTrackKindChanged(*this);
197 }
198
199 void TextTrack::setKindKeywordIgnoringASCIICase(StringView keyword)
200 {
201     if (keyword.isNull()) {
202         // The missing value default is the subtitles state.
203         setKind(Kind::Subtitles);
204         return;
205     }
206     if (equalLettersIgnoringASCIICase(keyword, "captions"))
207         setKind(Kind::Captions);
208     else if (equalLettersIgnoringASCIICase(keyword, "chapters"))
209         setKind(Kind::Chapters);
210     else if (equalLettersIgnoringASCIICase(keyword, "descriptions"))
211         setKind(Kind::Descriptions);
212     else if (equalLettersIgnoringASCIICase(keyword, "forced"))
213         setKind(Kind::Forced);
214     else if (equalLettersIgnoringASCIICase(keyword, "metadata"))
215         setKind(Kind::Metadata);
216     else if (equalLettersIgnoringASCIICase(keyword, "subtitles"))
217         setKind(Kind::Subtitles);
218     else {
219         // The invalid value default is the metadata state.
220         setKind(Kind::Metadata);
221     }
222 }
223
224 void TextTrack::setMode(Mode mode)
225 {
226     // On setting, if the new value isn't equal to what the attribute would currently
227     // return, the new value must be processed as follows ...
228     if (m_mode == mode)
229         return;
230
231     // If mode changes to disabled, remove this track's cues from the client
232     // because they will no longer be accessible from the cues() function.
233     if (mode == Mode::Disabled && m_client && m_cues)
234         m_client->textTrackRemoveCues(*this, *m_cues);
235
236     if (mode != Mode::Showing && m_cues) {
237         for (size_t i = 0; i < m_cues->length(); ++i) {
238             TextTrackCue* cue = m_cues->item(i);
239             if (cue->isRenderable())
240                 toVTTCue(cue)->removeDisplayTree();
241         }
242     }
243
244     m_mode = mode;
245
246     if (m_client)
247         m_client->textTrackModeChanged(*this);
248 }
249
250 TextTrackCueList* TextTrack::cues()
251 {
252     // 4.8.10.12.5 If the text track mode ... is not the text track disabled mode,
253     // then the cues attribute must return a live TextTrackCueList object ...
254     // Otherwise, it must return null. When an object is returned, the
255     // same object must be returned each time.
256     // http://www.whatwg.org/specs/web-apps/current-work/#dom-texttrack-cues
257     if (m_mode == Mode::Disabled)
258         return nullptr;
259     return &ensureTextTrackCueList();
260 }
261
262 void TextTrack::removeAllCues()
263 {
264     if (!m_cues)
265         return;
266
267     if (m_client)
268         m_client->textTrackRemoveCues(*this, *m_cues);
269     
270     for (size_t i = 0; i < m_cues->length(); ++i)
271         m_cues->item(i)->setTrack(nullptr);
272     
273     m_cues = nullptr;
274 }
275
276 TextTrackCueList* TextTrack::activeCues() const
277 {
278     // 4.8.10.12.5 If the text track mode ... is not the text track disabled mode,
279     // then the activeCues attribute must return a live TextTrackCueList object ...
280     // ... whose active flag was set when the script started, in text track cue
281     // order. Otherwise, it must return null. When an object is returned, the
282     // same object must be returned each time.
283     // http://www.whatwg.org/specs/web-apps/current-work/#dom-texttrack-activecues
284     if (!m_cues || m_mode == Mode::Disabled)
285         return nullptr;
286     return &m_cues->activeCues();
287 }
288
289 ExceptionOr<void> TextTrack::addCue(Ref<TextTrackCue>&& cue)
290 {
291     // 4.7.10.12.6 Text tracks exposing in-band metadata
292     // The UA will use DataCue to expose only text track cue objects that belong to a text track that has a text
293     // track kind of metadata.
294     // If a DataCue is added to a TextTrack via the addCue() method but the text track does not have its text
295     // track kind set to metadata, throw a InvalidNodeTypeError exception and don't add the cue to the TextTrackList
296     // of the TextTrack.
297     if (cue->cueType() == TextTrackCue::Data && m_kind != Kind::Metadata)
298         return Exception { INVALID_NODE_TYPE_ERR };
299
300     // TODO(93143): Add spec-compliant behavior for negative time values.
301     if (!cue->startMediaTime().isValid() || !cue->endMediaTime().isValid() || cue->startMediaTime() < MediaTime::zeroTime() || cue->endMediaTime() < MediaTime::zeroTime())
302         return { };
303
304     // 4.8.10.12.5 Text track API
305
306     // The addCue(cue) method of TextTrack objects, when invoked, must run the following steps:
307
308     auto* cueTrack = cue->track();
309     if (cueTrack == this)
310         return { };
311
312     // 1. If the given cue is in a text track list of cues, then remove cue from that text track
313     // list of cues.
314     if (cueTrack)
315         cueTrack->removeCue(cue.get());
316
317     // 2. Add cue to the method's TextTrack object's text track's text track list of cues.
318     cue->setTrack(this);
319     ensureTextTrackCueList().add(cue.copyRef());
320     
321     if (m_client)
322         m_client->textTrackAddCue(*this, cue);
323
324     return { };
325 }
326
327 ExceptionOr<void> TextTrack::removeCue(TextTrackCue& cue)
328 {
329     // 4.8.10.12.5 Text track API
330
331     // The removeCue(cue) method of TextTrack objects, when invoked, must run the following steps:
332
333     // 1. If the given cue is not currently listed in the method's TextTrack 
334     // object's text track's text track list of cues, then throw a NotFoundError exception.
335     if (cue.track() != this)
336         return Exception { NOT_FOUND_ERR };
337     if (!m_cues)
338         return Exception { INVALID_STATE_ERR };
339
340     // 2. Remove cue from the method's TextTrack object's text track's text track list of cues.
341     m_cues->remove(cue);
342     cue.setIsActive(false);
343     cue.setTrack(nullptr);
344     if (m_client)
345         m_client->textTrackRemoveCue(*this, cue);
346
347     return { };
348 }
349
350 VTTRegionList& TextTrack::ensureVTTRegionList()
351 {
352     if (!m_regions)
353         m_regions = VTTRegionList::create();
354
355     return *m_regions;
356 }
357
358 VTTRegionList* TextTrack::regions()
359 {
360     // If the text track mode of the text track that the TextTrack object
361     // represents is not the text track disabled mode, then the regions
362     // attribute must return a live VTTRegionList object that represents
363     // the text track list of regions of the text track. Otherwise, it must
364     // return null. When an object is returned, the same object must be returned
365     // each time.
366     if (m_mode == Mode::Disabled)
367         return nullptr;
368     return &ensureVTTRegionList();
369 }
370
371 void TextTrack::addRegion(RefPtr<VTTRegion>&& region)
372 {
373     if (!region)
374         return;
375
376     auto& regionList = ensureVTTRegionList();
377
378     // 1. If the given region is in a text track list of regions, then remove
379     // region from that text track list of regions.
380     auto* regionTrack = region->track();
381     if (regionTrack && regionTrack != this)
382         regionTrack->removeRegion(region.get());
383
384     // 2. If the method's TextTrack object's text track list of regions contains
385     // a region with the same identifier as region replace the values of that
386     // region's width, height, anchor point, viewport anchor point and scroll
387     // attributes with those of region.
388     auto* existingRegion = regionList.getRegionById(region->id());
389     if (existingRegion) {
390         existingRegion->updateParametersFromRegion(*region);
391         return;
392     }
393
394     // Otherwise: add region to the method's TextTrack object's text track list of regions.
395     region->setTrack(this);
396     regionList.add(region.releaseNonNull());
397 }
398
399 ExceptionOr<void> TextTrack::removeRegion(VTTRegion* region)
400 {
401     if (!region)
402         return { };
403
404     // 1. If the given region is not currently listed in the method's TextTrack
405     // object's text track list of regions, then throw a NotFoundError exception.
406     if (region->track() != this)
407         return Exception { NOT_FOUND_ERR };
408
409     ASSERT(m_regions);
410     m_regions->remove(*region);
411     region->setTrack(nullptr);
412     return { };
413 }
414
415 void TextTrack::cueWillChange(TextTrackCue* cue)
416 {
417     if (!m_client)
418         return;
419
420     // The cue may need to be repositioned in the media element's interval tree, may need to
421     // be re-rendered, etc, so remove it before the modification...
422     m_client->textTrackRemoveCue(*this, *cue);
423 }
424
425 void TextTrack::cueDidChange(TextTrackCue* cue)
426 {
427     if (!m_client)
428         return;
429
430     // Make sure the TextTrackCueList order is up-to-date.
431     ensureTextTrackCueList().updateCueIndex(*cue);
432
433     // ... and add it back again.
434     m_client->textTrackAddCue(*this, *cue);
435 }
436
437 int TextTrack::trackIndex()
438 {
439     ASSERT(m_mediaElement);
440     if (!m_trackIndex)
441         m_trackIndex = m_mediaElement->textTracks().getTrackIndex(*this);
442     return m_trackIndex.value();
443 }
444
445 void TextTrack::invalidateTrackIndex()
446 {
447     m_trackIndex = std::nullopt;
448     m_renderedTrackIndex = std::nullopt;
449 }
450
451 bool TextTrack::isRendered()
452 {
453     return (m_kind == Kind::Captions || m_kind == Kind::Subtitles || m_kind == Kind::Forced)
454         && m_mode == Mode::Showing;
455 }
456
457 TextTrackCueList& TextTrack::ensureTextTrackCueList()
458 {
459     if (!m_cues)
460         m_cues = TextTrackCueList::create();
461     return *m_cues;
462 }
463
464 int TextTrack::trackIndexRelativeToRenderedTracks()
465 {
466     ASSERT(m_mediaElement);
467     if (!m_renderedTrackIndex)
468         m_renderedTrackIndex = m_mediaElement->textTracks().getTrackIndexRelativeToRenderedTracks(*this);
469     return m_renderedTrackIndex.value();
470 }
471
472 bool TextTrack::hasCue(TextTrackCue* cue, TextTrackCue::CueMatchRules match)
473 {
474     if (cue->startMediaTime() < MediaTime::zeroTime() || cue->endMediaTime() < MediaTime::zeroTime())
475         return false;
476     
477     if (!m_cues || !m_cues->length())
478         return false;
479     
480     size_t searchStart = 0;
481     size_t searchEnd = m_cues->length();
482     
483     while (1) {
484         ASSERT(searchStart <= m_cues->length());
485         ASSERT(searchEnd <= m_cues->length());
486         
487         TextTrackCue* existingCue;
488         
489         // Cues in the TextTrackCueList are maintained in start time order.
490         if (searchStart == searchEnd) {
491             if (!searchStart)
492                 return false;
493
494             // If there is more than one cue with the same start time, back up to first one so we
495             // consider all of them.
496             while (searchStart >= 2 && cue->hasEquivalentStartTime(*m_cues->item(searchStart - 2)))
497                 --searchStart;
498             
499             bool firstCompare = true;
500             while (1) {
501                 if (!firstCompare)
502                     ++searchStart;
503                 firstCompare = false;
504                 if (searchStart > m_cues->length())
505                     return false;
506
507                 existingCue = m_cues->item(searchStart - 1);
508                 if (!existingCue)
509                     return false;
510
511                 if (cue->startMediaTime() > (existingCue->startMediaTime() + startTimeVariance()))
512                     return false;
513
514                 if (existingCue->isEqual(*cue, match))
515                     return true;
516             }
517         }
518         
519         size_t index = (searchStart + searchEnd) / 2;
520         existingCue = m_cues->item(index);
521         if ((cue->startMediaTime() + startTimeVariance()) < existingCue->startMediaTime() || (match != TextTrackCue::IgnoreDuration && cue->hasEquivalentStartTime(*existingCue) && cue->endMediaTime() > existingCue->endMediaTime()))
522             searchEnd = index;
523         else
524             searchStart = index + 1;
525     }
526     
527     ASSERT_NOT_REACHED();
528     return false;
529 }
530
531 bool TextTrack::isMainProgramContent() const
532 {
533     // "Main program" content is intrinsic to the presentation of the media file, regardless of locale. Content such as
534     // directors commentary is not "main program" because it is not essential for the presentation. HTML5 doesn't have
535     // a way to express this in a machine-reable form, it is typically done with the track label, so we assume that caption
536     // tracks are main content and all other track types are not.
537     return m_kind == Kind::Captions;
538 }
539
540 bool TextTrack::containsOnlyForcedSubtitles() const
541 {
542     return m_kind == Kind::Forced;
543 }
544
545 #if ENABLE(MEDIA_SOURCE)
546
547 void TextTrack::setLanguage(const AtomicString& language)
548 {
549     // 11.1 language, on setting:
550     // 1. If the value being assigned to this attribute is not an empty string or a BCP 47 language
551     // tag[BCP47], then abort these steps.
552     // BCP 47 validation is done in TrackBase::setLanguage() which is
553     // shared between all tracks that support setting language.
554
555     // 2. Update this attribute to the new value.
556     TrackBase::setLanguage(language);
557
558     // 3. If the sourceBuffer attribute on this track is not null, then queue a task to fire a simple
559     // event named change at sourceBuffer.textTracks.
560     if (m_sourceBuffer)
561         m_sourceBuffer->textTracks().scheduleChangeEvent();
562
563     // 4. Queue a task to fire a simple event named change at the TextTrackList object referenced by
564     // the textTracks attribute on the HTMLMediaElement.
565     if (mediaElement())
566         mediaElement()->textTracks().scheduleChangeEvent();
567 }
568
569 #endif
570
571 } // namespace WebCore
572
573 #endif