Home | History | Annotate | Download | only in html
      1 /*
      2  * Copyright (C) 2011 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "core/html/HTMLTrackElement.h"
     28 
     29 #include "HTMLNames.h"
     30 #include "bindings/v8/ExceptionStatePlaceholder.h"
     31 #include "core/dom/Event.h"
     32 #include "core/html/HTMLMediaElement.h"
     33 #include "core/page/ContentSecurityPolicy.h"
     34 #include "RuntimeEnabledFeatures.h"
     35 #include "core/platform/Logging.h"
     36 
     37 using namespace std;
     38 
     39 namespace WebCore {
     40 
     41 using namespace HTMLNames;
     42 
     43 #if !LOG_DISABLED
     44 static String urlForLoggingTrack(const KURL& url)
     45 {
     46     static const unsigned maximumURLLengthForLogging = 128;
     47 
     48     if (url.string().length() < maximumURLLengthForLogging)
     49         return url.string();
     50     return url.string().substring(0, maximumURLLengthForLogging) + "...";
     51 }
     52 #endif
     53 
     54 inline HTMLTrackElement::HTMLTrackElement(const QualifiedName& tagName, Document* document)
     55     : HTMLElement(tagName, document)
     56     , m_loadTimer(this, &HTMLTrackElement::loadTimerFired)
     57 {
     58     LOG(Media, "HTMLTrackElement::HTMLTrackElement - %p", this);
     59     ASSERT(hasTagName(trackTag));
     60     ScriptWrappable::init(this);
     61 }
     62 
     63 HTMLTrackElement::~HTMLTrackElement()
     64 {
     65     if (m_track)
     66         m_track->clearClient();
     67 }
     68 
     69 PassRefPtr<HTMLTrackElement> HTMLTrackElement::create(const QualifiedName& tagName, Document* document)
     70 {
     71     return adoptRef(new HTMLTrackElement(tagName, document));
     72 }
     73 
     74 Node::InsertionNotificationRequest HTMLTrackElement::insertedInto(ContainerNode* insertionPoint)
     75 {
     76     LOG(Media, "HTMLTrackElement::insertedInto");
     77 
     78     // Since we've moved to a new parent, we may now be able to load.
     79     scheduleLoad();
     80 
     81     HTMLElement::insertedInto(insertionPoint);
     82     HTMLMediaElement* parent = mediaElement();
     83     if (insertionPoint == parent)
     84         parent->didAddTrack(this);
     85     return InsertionDone;
     86 }
     87 
     88 void HTMLTrackElement::removedFrom(ContainerNode* insertionPoint)
     89 {
     90     if (!parentNode() && WebCore::isMediaElement(insertionPoint))
     91         toMediaElement(insertionPoint)->didRemoveTrack(this);
     92     HTMLElement::removedFrom(insertionPoint);
     93 }
     94 
     95 void HTMLTrackElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
     96 {
     97     if (RuntimeEnabledFeatures::videoTrackEnabled()) {
     98         if (name == srcAttr) {
     99             if (!value.isEmpty())
    100                 scheduleLoad();
    101             else if (m_track)
    102                 m_track->removeAllCues();
    103 
    104         // 4.8.10.12.3 Sourcing out-of-band text tracks
    105         // As the kind, label, and srclang attributes are set, changed, or removed, the text track must update accordingly...
    106         } else if (name == kindAttr)
    107             track()->setKind(value.lower());
    108         else if (name == labelAttr)
    109             track()->setLabel(value);
    110         else if (name == srclangAttr)
    111             track()->setLanguage(value);
    112         else if (name == defaultAttr)
    113             track()->setIsDefault(!value.isNull());
    114     }
    115 
    116     HTMLElement::parseAttribute(name, value);
    117 }
    118 
    119 KURL HTMLTrackElement::src() const
    120 {
    121     return document()->completeURL(getAttribute(srcAttr));
    122 }
    123 
    124 void HTMLTrackElement::setSrc(const String& url)
    125 {
    126     setAttribute(srcAttr, url);
    127 }
    128 
    129 String HTMLTrackElement::kind()
    130 {
    131     return track()->kind();
    132 }
    133 
    134 void HTMLTrackElement::setKind(const String& kind)
    135 {
    136     setAttribute(kindAttr, kind);
    137 }
    138 
    139 String HTMLTrackElement::srclang() const
    140 {
    141     return getAttribute(srclangAttr);
    142 }
    143 
    144 void HTMLTrackElement::setSrclang(const String& srclang)
    145 {
    146     setAttribute(srclangAttr, srclang);
    147 }
    148 
    149 String HTMLTrackElement::label() const
    150 {
    151     return getAttribute(labelAttr);
    152 }
    153 
    154 void HTMLTrackElement::setLabel(const String& label)
    155 {
    156     setAttribute(labelAttr, label);
    157 }
    158 
    159 bool HTMLTrackElement::isDefault() const
    160 {
    161     return fastHasAttribute(defaultAttr);
    162 }
    163 
    164 void HTMLTrackElement::setIsDefault(bool isDefault)
    165 {
    166     setBooleanAttribute(defaultAttr, isDefault);
    167 }
    168 
    169 LoadableTextTrack* HTMLTrackElement::ensureTrack()
    170 {
    171     if (!m_track) {
    172         // The kind attribute is an enumerated attribute, limited only to know values. It defaults to 'subtitles' if missing or invalid.
    173         String kind = getAttribute(kindAttr).lower();
    174         if (!TextTrack::isValidKindKeyword(kind))
    175             kind = TextTrack::subtitlesKeyword();
    176         m_track = LoadableTextTrack::create(this, kind, label(), srclang());
    177     }
    178     return m_track.get();
    179 }
    180 
    181 TextTrack* HTMLTrackElement::track()
    182 {
    183     return ensureTrack();
    184 }
    185 
    186 bool HTMLTrackElement::isURLAttribute(const Attribute& attribute) const
    187 {
    188     return attribute.name() == srcAttr || HTMLElement::isURLAttribute(attribute);
    189 }
    190 
    191 void HTMLTrackElement::scheduleLoad()
    192 {
    193     LOG(Media, "HTMLTrackElement::scheduleLoad");
    194 
    195     // 1. If another occurrence of this algorithm is already running for this text track and its track element,
    196     // abort these steps, letting that other algorithm take care of this element.
    197     if (m_loadTimer.isActive())
    198         return;
    199 
    200     if (!RuntimeEnabledFeatures::videoTrackEnabled())
    201         return;
    202 
    203     // 2. If the text track's text track mode is not set to one of hidden or showing, abort these steps.
    204     if (ensureTrack()->mode() != TextTrack::hiddenKeyword() && ensureTrack()->mode() != TextTrack::showingKeyword())
    205         return;
    206 
    207     // 3. If the text track's track element does not have a media element as a parent, abort these steps.
    208     if (!mediaElement())
    209         return;
    210 
    211     // 4. Run the remainder of these steps asynchronously, allowing whatever caused these steps to run to continue.
    212     m_loadTimer.startOneShot(0);
    213 }
    214 
    215 void HTMLTrackElement::loadTimerFired(Timer<HTMLTrackElement>*)
    216 {
    217     if (!fastHasAttribute(srcAttr))
    218         return;
    219 
    220     LOG(Media, "HTMLTrackElement::loadTimerFired");
    221 
    222     // 6. Set the text track readiness state to loading.
    223     setReadyState(HTMLTrackElement::LOADING);
    224 
    225     // 7. Let URL be the track URL of the track element.
    226     KURL url = getNonEmptyURLAttribute(srcAttr);
    227 
    228     // 8. If the track element's parent is a media element then let CORS mode be the state of the parent media
    229     // element's crossorigin content attribute. Otherwise, let CORS mode be No CORS.
    230     if (!canLoadUrl(url)) {
    231         didCompleteLoad(ensureTrack(), HTMLTrackElement::Failure);
    232         return;
    233     }
    234 
    235     ensureTrack()->scheduleLoad(url);
    236 }
    237 
    238 bool HTMLTrackElement::canLoadUrl(const KURL& url)
    239 {
    240     if (!RuntimeEnabledFeatures::videoTrackEnabled())
    241         return false;
    242 
    243     HTMLMediaElement* parent = mediaElement();
    244     if (!parent)
    245         return false;
    246 
    247     // 4.8.10.12.3 Sourcing out-of-band text tracks
    248 
    249     // 4. Download: If URL is not the empty string, perform a potentially CORS-enabled fetch of URL, with the
    250     // mode being the state of the media element's crossorigin content attribute, the origin being the
    251     // origin of the media element's Document, and the default origin behaviour set to fail.
    252     if (url.isEmpty())
    253         return false;
    254 
    255     if (!document()->contentSecurityPolicy()->allowMediaFromSource(url)) {
    256         LOG(Media, "HTMLTrackElement::canLoadUrl(%s) -> rejected by Content Security Policy", urlForLoggingTrack(url).utf8().data());
    257         return false;
    258     }
    259 
    260     return dispatchBeforeLoadEvent(url.string());
    261 }
    262 
    263 void HTMLTrackElement::didCompleteLoad(LoadableTextTrack*, LoadStatus status)
    264 {
    265     // 4.8.10.12.3 Sourcing out-of-band text tracks (continued)
    266 
    267     // 4. Download: ...
    268     // If the fetching algorithm fails for any reason (network error, the server returns an error
    269     // code, a cross-origin check fails, etc), or if URL is the empty string or has the wrong origin
    270     // as determined by the condition at the start of this step, or if the fetched resource is not in
    271     // a supported format, then queue a task to first change the text track readiness state to failed
    272     // to load and then fire a simple event named error at the track element; and then, once that task
    273     // is queued, move on to the step below labeled monitoring.
    274 
    275     if (status == Failure) {
    276         setReadyState(HTMLTrackElement::TRACK_ERROR);
    277         dispatchEvent(Event::create(eventNames().errorEvent, false, false), IGNORE_EXCEPTION);
    278         return;
    279     }
    280 
    281     // If the fetching algorithm does not fail, then the final task that is queued by the networking
    282     // task source must run the following steps:
    283     //     1. Change the text track readiness state to loaded.
    284     setReadyState(HTMLTrackElement::LOADED);
    285 
    286     //     2. If the file was successfully processed, fire a simple event named load at the
    287     //        track element.
    288     dispatchEvent(Event::create(eventNames().loadEvent, false, false), IGNORE_EXCEPTION);
    289 }
    290 
    291 // NOTE: The values in the TextTrack::ReadinessState enum must stay in sync with those in HTMLTrackElement::ReadyState.
    292 COMPILE_ASSERT(HTMLTrackElement::NONE == static_cast<HTMLTrackElement::ReadyState>(TextTrack::NotLoaded), TextTrackEnumNotLoaded_Is_Wrong_Should_Be_HTMLTrackElementEnumNONE);
    293 COMPILE_ASSERT(HTMLTrackElement::LOADING == static_cast<HTMLTrackElement::ReadyState>(TextTrack::Loading), TextTrackEnumLoadingIsWrong_ShouldBe_HTMLTrackElementEnumLOADING);
    294 COMPILE_ASSERT(HTMLTrackElement::LOADED == static_cast<HTMLTrackElement::ReadyState>(TextTrack::Loaded), TextTrackEnumLoaded_Is_Wrong_Should_Be_HTMLTrackElementEnumLOADED);
    295 COMPILE_ASSERT(HTMLTrackElement::TRACK_ERROR == static_cast<HTMLTrackElement::ReadyState>(TextTrack::FailedToLoad), TextTrackEnumFailedToLoad_Is_Wrong_Should_Be_HTMLTrackElementEnumTRACK_ERROR);
    296 
    297 void HTMLTrackElement::setReadyState(ReadyState state)
    298 {
    299     ensureTrack()->setReadinessState(static_cast<TextTrack::ReadinessState>(state));
    300     if (HTMLMediaElement* parent = mediaElement())
    301         return parent->textTrackReadyStateChanged(m_track.get());
    302 }
    303 
    304 HTMLTrackElement::ReadyState HTMLTrackElement::readyState()
    305 {
    306     return static_cast<ReadyState>(ensureTrack()->readinessState());
    307 }
    308 
    309 const AtomicString& HTMLTrackElement::mediaElementCrossOriginAttribute() const
    310 {
    311     if (HTMLMediaElement* parent = mediaElement())
    312         return parent->fastGetAttribute(HTMLNames::crossoriginAttr);
    313 
    314     return nullAtom;
    315 }
    316 
    317 void HTMLTrackElement::textTrackKindChanged(TextTrack* track)
    318 {
    319     if (HTMLMediaElement* parent = mediaElement())
    320         return parent->textTrackKindChanged(track);
    321 }
    322 
    323 void HTMLTrackElement::textTrackModeChanged(TextTrack* track)
    324 {
    325     // Since we've moved to a new parent, we may now be able to load.
    326     if (readyState() == HTMLTrackElement::NONE)
    327         scheduleLoad();
    328 
    329     if (HTMLMediaElement* parent = mediaElement())
    330         return parent->textTrackModeChanged(track);
    331 }
    332 
    333 void HTMLTrackElement::textTrackAddCues(TextTrack* track, const TextTrackCueList* cues)
    334 {
    335     if (HTMLMediaElement* parent = mediaElement())
    336         return parent->textTrackAddCues(track, cues);
    337 }
    338 
    339 void HTMLTrackElement::textTrackRemoveCues(TextTrack* track, const TextTrackCueList* cues)
    340 {
    341     if (HTMLMediaElement* parent = mediaElement())
    342         return parent->textTrackRemoveCues(track, cues);
    343 }
    344 
    345 void HTMLTrackElement::textTrackAddCue(TextTrack* track, PassRefPtr<TextTrackCue> cue)
    346 {
    347     if (HTMLMediaElement* parent = mediaElement())
    348         return parent->textTrackAddCue(track, cue);
    349 }
    350 
    351 void HTMLTrackElement::textTrackRemoveCue(TextTrack* track, PassRefPtr<TextTrackCue> cue)
    352 {
    353     if (HTMLMediaElement* parent = mediaElement())
    354         return parent->textTrackRemoveCue(track, cue);
    355 }
    356 
    357 HTMLMediaElement* HTMLTrackElement::mediaElement() const
    358 {
    359     Element* parent = parentElement();
    360     if (parent && parent->isMediaElement())
    361         return static_cast<HTMLMediaElement*>(parentNode());
    362 
    363     return 0;
    364 }
    365 
    366 }
    367 
    368