Replace some auto* with RefPtr within WebCore/html
[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 "Document.h"
38 #include "Event.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             RefPtr<TextTrackCue> cue = m_cues->item(i);
239             if (cue->isRenderable())
240                 toVTTCue(cue.get())->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->clear();
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 { InvalidNodeTypeError };
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 = makeRefPtr(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 { NotFoundError };
337     if (!m_cues)
338         return Exception { InvalidStateError };
339
340     DEBUG_LOG(LOGIDENTIFIER, cue);
341
342     // 2. Remove cue from the method's TextTrack object's text track's text track list of cues.
343     m_cues->remove(cue);
344     cue.setIsActive(false);
345     cue.setTrack(nullptr);
346     if (m_client)
347         m_client->textTrackRemoveCue(*this, cue);
348
349     return { };
350 }
351
352 VTTRegionList& TextTrack::ensureVTTRegionList()
353 {
354     if (!m_regions)
355         m_regions = VTTRegionList::create();
356
357     return *m_regions;
358 }
359
360 VTTRegionList* TextTrack::regions()
361 {
362     // If the text track mode of the text track that the TextTrack object
363     // represents is not the text track disabled mode, then the regions
364     // attribute must return a live VTTRegionList object that represents
365     // the text track list of regions of the text track. Otherwise, it must
366     // return null. When an object is returned, the same object must be returned
367     // each time.
368     if (m_mode == Mode::Disabled)
369         return nullptr;
370     return &ensureVTTRegionList();
371 }
372
373 void TextTrack::addRegion(RefPtr<VTTRegion>&& region)
374 {
375     if (!region)
376         return;
377
378     auto& regionList = ensureVTTRegionList();
379
380     // 1. If the given region is in a text track list of regions, then remove
381     // region from that text track list of regions.
382     auto regionTrack = makeRefPtr(region->track());
383     if (regionTrack && regionTrack != this)
384         regionTrack->removeRegion(region.get());
385
386     // 2. If the method's TextTrack object's text track list of regions contains
387     // a region with the same identifier as region replace the values of that
388     // region's width, height, anchor point, viewport anchor point and scroll
389     // attributes with those of region.
390     auto existingRegion = makeRefPtr(regionList.getRegionById(region->id()));
391     if (existingRegion) {
392         existingRegion->updateParametersFromRegion(*region);
393         return;
394     }
395
396     // Otherwise: add region to the method's TextTrack object's text track list of regions.
397     region->setTrack(this);
398     regionList.add(region.releaseNonNull());
399 }
400
401 ExceptionOr<void> TextTrack::removeRegion(VTTRegion* region)
402 {
403     if (!region)
404         return { };
405
406     // 1. If the given region is not currently listed in the method's TextTrack
407     // object's text track list of regions, then throw a NotFoundError exception.
408     if (region->track() != this)
409         return Exception { NotFoundError };
410
411     ASSERT(m_regions);
412     m_regions->remove(*region);
413     region->setTrack(nullptr);
414     return { };
415 }
416
417 void TextTrack::cueWillChange(TextTrackCue* cue)
418 {
419     if (!m_client)
420         return;
421
422     // The cue may need to be repositioned in the media element's interval tree, may need to
423     // be re-rendered, etc, so remove it before the modification...
424     m_client->textTrackRemoveCue(*this, *cue);
425 }
426
427 void TextTrack::cueDidChange(TextTrackCue* cue)
428 {
429     if (!m_client)
430         return;
431
432     // Make sure the TextTrackCueList order is up-to-date.
433     ensureTextTrackCueList().updateCueIndex(*cue);
434
435     // ... and add it back again.
436     m_client->textTrackAddCue(*this, *cue);
437 }
438
439 int TextTrack::trackIndex()
440 {
441     ASSERT(m_mediaElement);
442     if (!m_trackIndex)
443         m_trackIndex = m_mediaElement->textTracks().getTrackIndex(*this);
444     return m_trackIndex.value();
445 }
446
447 void TextTrack::invalidateTrackIndex()
448 {
449     m_trackIndex = std::nullopt;
450     m_renderedTrackIndex = std::nullopt;
451 }
452
453 bool TextTrack::isRendered()
454 {
455     return (m_kind == Kind::Captions || m_kind == Kind::Subtitles || m_kind == Kind::Forced)
456         && m_mode == Mode::Showing;
457 }
458
459 TextTrackCueList& TextTrack::ensureTextTrackCueList()
460 {
461     if (!m_cues)
462         m_cues = TextTrackCueList::create();
463     return *m_cues;
464 }
465
466 int TextTrack::trackIndexRelativeToRenderedTracks()
467 {
468     ASSERT(m_mediaElement);
469     if (!m_renderedTrackIndex)
470         m_renderedTrackIndex = m_mediaElement->textTracks().getTrackIndexRelativeToRenderedTracks(*this);
471     return m_renderedTrackIndex.value();
472 }
473
474 bool TextTrack::hasCue(TextTrackCue* cue, TextTrackCue::CueMatchRules match)
475 {
476     if (cue->startMediaTime() < MediaTime::zeroTime() || cue->endMediaTime() < MediaTime::zeroTime())
477         return false;
478     
479     if (!m_cues || !m_cues->length())
480         return false;
481     
482     size_t searchStart = 0;
483     size_t searchEnd = m_cues->length();
484     
485     while (1) {
486         ASSERT(searchStart <= m_cues->length());
487         ASSERT(searchEnd <= m_cues->length());
488         
489         RefPtr<TextTrackCue> existingCue;
490         
491         // Cues in the TextTrackCueList are maintained in start time order.
492         if (searchStart == searchEnd) {
493             if (!searchStart)
494                 return false;
495
496             // If there is more than one cue with the same start time, back up to first one so we
497             // consider all of them.
498             while (searchStart >= 2 && cue->hasEquivalentStartTime(*m_cues->item(searchStart - 2)))
499                 --searchStart;
500             
501             bool firstCompare = true;
502             while (1) {
503                 if (!firstCompare)
504                     ++searchStart;
505                 firstCompare = false;
506                 if (searchStart > m_cues->length())
507                     return false;
508
509                 existingCue = m_cues->item(searchStart - 1);
510                 if (!existingCue)
511                     return false;
512
513                 if (cue->startMediaTime() > (existingCue->startMediaTime() + startTimeVariance()))
514                     return false;
515
516                 if (existingCue->isEqual(*cue, match))
517                     return true;
518             }
519         }
520         
521         size_t index = (searchStart + searchEnd) / 2;
522         existingCue = m_cues->item(index);
523         if ((cue->startMediaTime() + startTimeVariance()) < existingCue->startMediaTime() || (match != TextTrackCue::IgnoreDuration && cue->hasEquivalentStartTime(*existingCue) && cue->endMediaTime() > existingCue->endMediaTime()))
524             searchEnd = index;
525         else
526             searchStart = index + 1;
527     }
528     
529     ASSERT_NOT_REACHED();
530     return false;
531 }
532
533 bool TextTrack::isMainProgramContent() const
534 {
535     // "Main program" content is intrinsic to the presentation of the media file, regardless of locale. Content such as
536     // directors commentary is not "main program" because it is not essential for the presentation. HTML5 doesn't have
537     // a way to express this in a machine-reable form, it is typically done with the track label, so we assume that caption
538     // tracks are main content and all other track types are not.
539     return m_kind == Kind::Captions;
540 }
541
542 bool TextTrack::containsOnlyForcedSubtitles() const
543 {
544     return m_kind == Kind::Forced;
545 }
546
547 #if ENABLE(MEDIA_SOURCE)
548 void TextTrack::setLanguage(const AtomicString& language)
549 {
550     // 11.1 language, on setting:
551     // 1. If the value being assigned to this attribute is not an empty string or a BCP 47 language
552     // tag[BCP47], then abort these steps.
553     // BCP 47 validation is done in TrackBase::setLanguage() which is
554     // shared between all tracks that support setting language.
555
556     // 2. Update this attribute to the new value.
557     TrackBase::setLanguage(language);
558
559     // 3. If the sourceBuffer attribute on this track is not null, then queue a task to fire a simple
560     // event named change at sourceBuffer.textTracks.
561     if (m_sourceBuffer)
562         m_sourceBuffer->textTracks().scheduleChangeEvent();
563
564     // 4. Queue a task to fire a simple event named change at the TextTrackList object referenced by
565     // the textTracks attribute on the HTMLMediaElement.
566     if (mediaElement())
567         mediaElement()->textTracks().scheduleChangeEvent();
568 }
569 #endif
570
571 } // namespace WebCore
572
573 #endif