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 45namespace blink { 46 47static const int invalidTrackIndex = -1; 48 49const AtomicString& TextTrack::subtitlesKeyword() 50{ 51 DEFINE_STATIC_LOCAL(const AtomicString, subtitles, ("subtitles", AtomicString::ConstructFromLiteral)); 52 return subtitles; 53} 54 55const AtomicString& TextTrack::captionsKeyword() 56{ 57 DEFINE_STATIC_LOCAL(const AtomicString, captions, ("captions", AtomicString::ConstructFromLiteral)); 58 return captions; 59} 60 61const AtomicString& TextTrack::descriptionsKeyword() 62{ 63 DEFINE_STATIC_LOCAL(const AtomicString, descriptions, ("descriptions", AtomicString::ConstructFromLiteral)); 64 return descriptions; 65} 66 67const AtomicString& TextTrack::chaptersKeyword() 68{ 69 DEFINE_STATIC_LOCAL(const AtomicString, chapters, ("chapters", AtomicString::ConstructFromLiteral)); 70 return chapters; 71} 72 73const AtomicString& TextTrack::metadataKeyword() 74{ 75 DEFINE_STATIC_LOCAL(const AtomicString, metadata, ("metadata", AtomicString::ConstructFromLiteral)); 76 return metadata; 77} 78 79const AtomicString& TextTrack::disabledKeyword() 80{ 81 DEFINE_STATIC_LOCAL(const AtomicString, open, ("disabled", AtomicString::ConstructFromLiteral)); 82 return open; 83} 84 85const AtomicString& TextTrack::hiddenKeyword() 86{ 87 DEFINE_STATIC_LOCAL(const AtomicString, closed, ("hidden", AtomicString::ConstructFromLiteral)); 88 return closed; 89} 90 91const AtomicString& TextTrack::showingKeyword() 92{ 93 DEFINE_STATIC_LOCAL(const AtomicString, ended, ("showing", AtomicString::ConstructFromLiteral)); 94 return ended; 95} 96 97TextTrack::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 112TextTrack::~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 128bool 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 144void 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 153void 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 162void 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 186TextTrackCueList* 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 198void 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 212TextTrackCueList* 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 225void 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 254void 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 281VTTRegionList* TextTrack::ensureVTTRegionList() 282{ 283 if (!m_regions) 284 m_regions = VTTRegionList::create(); 285 286 return m_regions.get(); 287} 288 289VTTRegionList* 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 302void 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 332void 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 352void 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 362void 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 374int 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 384void TextTrack::invalidateTrackIndex() 385{ 386 m_trackIndex = invalidTrackIndex; 387 m_renderedTrackIndex = invalidTrackIndex; 388} 389 390bool 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 401TextTrackCueList* TextTrack::ensureTextTrackCueList() 402{ 403 if (!m_cues) 404 m_cues = TextTrackCueList::create(); 405 406 return m_cues.get(); 407} 408 409int 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 419const AtomicString& TextTrack::interfaceName() const 420{ 421 return EventTargetNames::TextTrack; 422} 423 424ExecutionContext* TextTrack::executionContext() const 425{ 426 HTMLMediaElement* owner = mediaElement(); 427 return owner ? owner->executionContext() : 0; 428} 429 430HTMLMediaElement* TextTrack::mediaElement() const 431{ 432 return m_trackList ? m_trackList->owner() : 0; 433} 434 435Node* TextTrack::owner() const 436{ 437 return mediaElement(); 438} 439 440void 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