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