1 /* 2 * Copyright (C) 2011 Google Inc. All rights reserved. 3 * Copyright (C) 2011, 2012, 2013 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 "core/html/track/TextTrack.h" 34 35 #include "bindings/core/v8/ExceptionState.h" 36 #include "bindings/core/v8/ExceptionStatePlaceholder.h" 37 #include "core/dom/ExceptionCode.h" 38 #include "core/html/HTMLMediaElement.h" 39 #include "core/html/track/TextTrackCueList.h" 40 #include "core/html/track/TextTrackList.h" 41 #include "core/html/track/vtt/VTTRegion.h" 42 #include "core/html/track/vtt/VTTRegionList.h" 43 #include "platform/RuntimeEnabledFeatures.h" 44 45 namespace blink { 46 47 static const int invalidTrackIndex = -1; 48 49 const AtomicString& TextTrack::subtitlesKeyword() 50 { 51 DEFINE_STATIC_LOCAL(const AtomicString, subtitles, ("subtitles", AtomicString::ConstructFromLiteral)); 52 return subtitles; 53 } 54 55 const AtomicString& TextTrack::captionsKeyword() 56 { 57 DEFINE_STATIC_LOCAL(const AtomicString, captions, ("captions", AtomicString::ConstructFromLiteral)); 58 return captions; 59 } 60 61 const AtomicString& TextTrack::descriptionsKeyword() 62 { 63 DEFINE_STATIC_LOCAL(const AtomicString, descriptions, ("descriptions", AtomicString::ConstructFromLiteral)); 64 return descriptions; 65 } 66 67 const AtomicString& TextTrack::chaptersKeyword() 68 { 69 DEFINE_STATIC_LOCAL(const AtomicString, chapters, ("chapters", AtomicString::ConstructFromLiteral)); 70 return chapters; 71 } 72 73 const AtomicString& TextTrack::metadataKeyword() 74 { 75 DEFINE_STATIC_LOCAL(const AtomicString, metadata, ("metadata", AtomicString::ConstructFromLiteral)); 76 return metadata; 77 } 78 79 const AtomicString& TextTrack::disabledKeyword() 80 { 81 DEFINE_STATIC_LOCAL(const AtomicString, open, ("disabled", AtomicString::ConstructFromLiteral)); 82 return open; 83 } 84 85 const AtomicString& TextTrack::hiddenKeyword() 86 { 87 DEFINE_STATIC_LOCAL(const AtomicString, closed, ("hidden", AtomicString::ConstructFromLiteral)); 88 return closed; 89 } 90 91 const AtomicString& TextTrack::showingKeyword() 92 { 93 DEFINE_STATIC_LOCAL(const AtomicString, ended, ("showing", AtomicString::ConstructFromLiteral)); 94 return ended; 95 } 96 97 TextTrack::TextTrack(const AtomicString& kind, const AtomicString& label, const AtomicString& language, const AtomicString& id, TextTrackType type) 98 : TrackBase(TrackBase::TextTrack, label, language, id) 99 , m_cues(nullptr) 100 , m_regions(nullptr) 101 , m_trackList(nullptr) 102 , m_mode(disabledKeyword()) 103 , m_trackType(type) 104 , m_readinessState(NotLoaded) 105 , m_trackIndex(invalidTrackIndex) 106 , m_renderedTrackIndex(invalidTrackIndex) 107 , m_hasBeenConfigured(false) 108 { 109 setKind(kind); 110 } 111 112 TextTrack::~TextTrack() 113 { 114 #if !ENABLE(OILPAN) 115 ASSERT(!m_trackList); 116 117 if (m_cues) { 118 for (size_t i = 0; i < m_cues->length(); ++i) 119 m_cues->item(i)->setTrack(0); 120 } 121 if (m_regions) { 122 for (size_t i = 0; i < m_regions->length(); ++i) 123 m_regions->item(i)->setTrack(0); 124 } 125 #endif 126 } 127 128 bool TextTrack::isValidKindKeyword(const AtomicString& value) 129 { 130 if (value == subtitlesKeyword()) 131 return true; 132 if (value == captionsKeyword()) 133 return true; 134 if (value == descriptionsKeyword()) 135 return true; 136 if (value == chaptersKeyword()) 137 return true; 138 if (value == metadataKeyword()) 139 return true; 140 141 return false; 142 } 143 144 void TextTrack::setTrackList(TextTrackList* trackList) 145 { 146 if (!trackList && mediaElement() && m_cues) 147 mediaElement()->textTrackRemoveCues(this, m_cues.get()); 148 149 m_trackList = trackList; 150 invalidateTrackIndex(); 151 } 152 153 void TextTrack::setKind(const AtomicString& newKind) 154 { 155 AtomicString oldKind = kind(); 156 TrackBase::setKind(newKind); 157 158 if (mediaElement() && oldKind != kind()) 159 mediaElement()->textTrackKindChanged(this); 160 } 161 162 void TextTrack::setMode(const AtomicString& mode) 163 { 164 ASSERT(mode == disabledKeyword() || mode == hiddenKeyword() || mode == showingKeyword()); 165 166 // On setting, if the new value isn't equal to what the attribute would currently 167 // return, the new value must be processed as follows ... 168 if (m_mode == mode) 169 return; 170 171 // If mode changes to disabled, remove this track's cues from the client 172 // because they will no longer be accessible from the cues() function. 173 if (mode == disabledKeyword() && mediaElement() && m_cues) 174 mediaElement()->textTrackRemoveCues(this, m_cues.get()); 175 176 if (mode != showingKeyword() && m_cues) 177 for (size_t i = 0; i < m_cues->length(); ++i) 178 m_cues->item(i)->removeDisplayTree(); 179 180 m_mode = mode; 181 182 if (mediaElement()) 183 mediaElement()->textTrackModeChanged(this); 184 } 185 186 TextTrackCueList* TextTrack::cues() 187 { 188 // 4.8.10.12.5 If the text track mode ... is not the text track disabled mode, 189 // then the cues attribute must return a live TextTrackCueList object ... 190 // Otherwise, it must return null. When an object is returned, the 191 // same object must be returned each time. 192 // http://www.whatwg.org/specs/web-apps/current-work/#dom-texttrack-cues 193 if (m_mode != disabledKeyword()) 194 return ensureTextTrackCueList(); 195 return 0; 196 } 197 198 void TextTrack::removeAllCues() 199 { 200 if (!m_cues) 201 return; 202 203 if (mediaElement()) 204 mediaElement()->textTrackRemoveCues(this, m_cues.get()); 205 206 for (size_t i = 0; i < m_cues->length(); ++i) 207 m_cues->item(i)->setTrack(0); 208 209 m_cues = nullptr; 210 } 211 212 TextTrackCueList* TextTrack::activeCues() const 213 { 214 // 4.8.10.12.5 If the text track mode ... is not the text track disabled mode, 215 // then the activeCues attribute must return a live TextTrackCueList object ... 216 // ... whose active flag was set when the script started, in text track cue 217 // order. Otherwise, it must return null. When an object is returned, the 218 // same object must be returned each time. 219 // http://www.whatwg.org/specs/web-apps/current-work/#dom-texttrack-activecues 220 if (m_cues && m_mode != disabledKeyword()) 221 return m_cues->activeCues(); 222 return 0; 223 } 224 225 void TextTrack::addCue(PassRefPtrWillBeRawPtr<TextTrackCue> prpCue) 226 { 227 if (!prpCue) 228 return; 229 230 RefPtrWillBeRawPtr<TextTrackCue> cue = prpCue; 231 232 // TODO(93143): Add spec-compliant behavior for negative time values. 233 if (std::isnan(cue->startTime()) || std::isnan(cue->endTime()) || cue->startTime() < 0 || cue->endTime() < 0) 234 return; 235 236 // 4.8.10.12.5 Text track API 237 238 // The addCue(cue) method of TextTrack objects, when invoked, must run the following steps: 239 240 // 1. If the given cue is in a text track list of cues, then remove cue from that text track 241 // list of cues. 242 TextTrack* cueTrack = cue->track(); 243 if (cueTrack && cueTrack != this) 244 cueTrack->removeCue(cue.get(), ASSERT_NO_EXCEPTION); 245 246 // 2. Add cue to the method's TextTrack object's text track's text track list of cues. 247 cue->setTrack(this); 248 ensureTextTrackCueList()->add(cue); 249 250 if (mediaElement()) 251 mediaElement()->textTrackAddCue(this, cue.get()); 252 } 253 254 void TextTrack::removeCue(TextTrackCue* cue, ExceptionState& exceptionState) 255 { 256 if (!cue) 257 return; 258 259 // 4.8.10.12.5 Text track API 260 261 // The removeCue(cue) method of TextTrack objects, when invoked, must run the following steps: 262 263 // 1. If the given cue is not currently listed in the method's TextTrack 264 // object's text track's text track list of cues, then throw a NotFoundError exception. 265 if (cue->track() != this) { 266 exceptionState.throwDOMException(NotFoundError, "The specified cue is not listed in the TextTrack's list of cues."); 267 return; 268 } 269 270 // 2. Remove cue from the method's TextTrack object's text track's text track list of cues. 271 if (!m_cues || !m_cues->remove(cue)) { 272 exceptionState.throwDOMException(InvalidStateError, "Failed to remove the specified cue."); 273 return; 274 } 275 276 cue->setTrack(0); 277 if (mediaElement()) 278 mediaElement()->textTrackRemoveCue(this, cue); 279 } 280 281 VTTRegionList* TextTrack::ensureVTTRegionList() 282 { 283 if (!m_regions) 284 m_regions = VTTRegionList::create(); 285 286 return m_regions.get(); 287 } 288 289 VTTRegionList* TextTrack::regions() 290 { 291 // If the text track mode of the text track that the TextTrack object 292 // represents is not the text track disabled mode, then the regions 293 // attribute must return a live VTTRegionList object that represents 294 // the text track list of regions of the text track. Otherwise, it must 295 // return null. When an object is returned, the same object must be returned 296 // each time. 297 if (RuntimeEnabledFeatures::webVTTRegionsEnabled() && m_mode != disabledKeyword()) 298 return ensureVTTRegionList(); 299 return 0; 300 } 301 302 void TextTrack::addRegion(PassRefPtrWillBeRawPtr<VTTRegion> prpRegion) 303 { 304 if (!prpRegion) 305 return; 306 307 RefPtrWillBeRawPtr<VTTRegion> region = prpRegion; 308 VTTRegionList* regionList = ensureVTTRegionList(); 309 310 // 1. If the given region is in a text track list of regions, then remove 311 // region from that text track list of regions. 312 TextTrack* regionTrack = region->track(); 313 if (regionTrack && regionTrack != this) 314 regionTrack->removeRegion(region.get(), ASSERT_NO_EXCEPTION); 315 316 // 2. If the method's TextTrack object's text track list of regions contains 317 // a region with the same identifier as region replace the values of that 318 // region's width, height, anchor point, viewport anchor point and scroll 319 // attributes with those of region. 320 VTTRegion* existingRegion = regionList->getRegionById(region->id()); 321 if (existingRegion) { 322 existingRegion->updateParametersFromRegion(region.get()); 323 return; 324 } 325 326 // Otherwise: add region to the method's TextTrack object's text track 327 // list of regions. 328 region->setTrack(this); 329 regionList->add(region); 330 } 331 332 void TextTrack::removeRegion(VTTRegion* region, ExceptionState &exceptionState) 333 { 334 if (!region) 335 return; 336 337 // 1. If the given region is not currently listed in the method's TextTrack 338 // object's text track list of regions, then throw a NotFoundError exception. 339 if (region->track() != this) { 340 exceptionState.throwDOMException(NotFoundError, "The specified region is not listed in the TextTrack's list of regions."); 341 return; 342 } 343 344 if (!m_regions || !m_regions->remove(region)) { 345 exceptionState.throwDOMException(InvalidStateError, "Failed to remove the specified region."); 346 return; 347 } 348 349 region->setTrack(0); 350 } 351 352 void TextTrack::cueWillChange(TextTrackCue* cue) 353 { 354 if (!mediaElement()) 355 return; 356 357 // The cue may need to be repositioned in the media element's interval tree, may need to 358 // be re-rendered, etc, so remove it before the modification... 359 mediaElement()->textTrackRemoveCue(this, cue); 360 } 361 362 void TextTrack::cueDidChange(TextTrackCue* cue) 363 { 364 if (!mediaElement()) 365 return; 366 367 // Make sure the TextTrackCueList order is up-to-date. 368 ensureTextTrackCueList()->updateCueIndex(cue); 369 370 // ... and add it back again. 371 mediaElement()->textTrackAddCue(this, cue); 372 } 373 374 int TextTrack::trackIndex() 375 { 376 ASSERT(m_trackList); 377 378 if (m_trackIndex == invalidTrackIndex) 379 m_trackIndex = m_trackList->getTrackIndex(this); 380 381 return m_trackIndex; 382 } 383 384 void TextTrack::invalidateTrackIndex() 385 { 386 m_trackIndex = invalidTrackIndex; 387 m_renderedTrackIndex = invalidTrackIndex; 388 } 389 390 bool TextTrack::isRendered() 391 { 392 if (kind() != captionsKeyword() && kind() != subtitlesKeyword()) 393 return false; 394 395 if (m_mode != showingKeyword()) 396 return false; 397 398 return true; 399 } 400 401 TextTrackCueList* TextTrack::ensureTextTrackCueList() 402 { 403 if (!m_cues) 404 m_cues = TextTrackCueList::create(); 405 406 return m_cues.get(); 407 } 408 409 int TextTrack::trackIndexRelativeToRenderedTracks() 410 { 411 ASSERT(m_trackList); 412 413 if (m_renderedTrackIndex == invalidTrackIndex) 414 m_renderedTrackIndex = m_trackList->getTrackIndexRelativeToRenderedTracks(this); 415 416 return m_renderedTrackIndex; 417 } 418 419 const AtomicString& TextTrack::interfaceName() const 420 { 421 return EventTargetNames::TextTrack; 422 } 423 424 ExecutionContext* TextTrack::executionContext() const 425 { 426 HTMLMediaElement* owner = mediaElement(); 427 return owner ? owner->executionContext() : 0; 428 } 429 430 HTMLMediaElement* TextTrack::mediaElement() const 431 { 432 return m_trackList ? m_trackList->owner() : 0; 433 } 434 435 Node* TextTrack::owner() const 436 { 437 return mediaElement(); 438 } 439 440 void TextTrack::trace(Visitor* visitor) 441 { 442 visitor->trace(m_cues); 443 visitor->trace(m_regions); 444 visitor->trace(m_trackList); 445 TrackBase::trace(visitor); 446 EventTargetWithInlineData::trace(visitor); 447 } 448 449 } // namespace blink 450