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