| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "core/html/HTMLTrackElement.h" |
| |
| #include "core/HTMLNames.h" |
| #include "core/dom/Document.h" |
| #include "core/events/Event.h" |
| #include "core/frame/csp/ContentSecurityPolicy.h" |
| #include "core/html/CrossOriginAttribute.h" |
| #include "core/html/HTMLMediaElement.h" |
| #include "core/html/track/LoadableTextTrack.h" |
| |
| #define TRACK_LOG_LEVEL 3 |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| static String urlForLoggingTrack(const KURL& url) { |
| static const unsigned maximumURLLengthForLogging = 128; |
| |
| if (url.getString().length() < maximumURLLengthForLogging) |
| return url.getString(); |
| return url.getString().substring(0, maximumURLLengthForLogging) + "..."; |
| } |
| |
| inline HTMLTrackElement::HTMLTrackElement(Document& document) |
| : HTMLElement(trackTag, document), |
| m_loadTimer(this, &HTMLTrackElement::loadTimerFired) { |
| DVLOG(TRACK_LOG_LEVEL) << "HTMLTrackElement - " << (void*)this; |
| } |
| |
| DEFINE_NODE_FACTORY(HTMLTrackElement) |
| |
| HTMLTrackElement::~HTMLTrackElement() {} |
| |
| Node::InsertionNotificationRequest HTMLTrackElement::insertedInto( |
| ContainerNode* insertionPoint) { |
| DVLOG(TRACK_LOG_LEVEL) << "insertedInto"; |
| |
| // Since we've moved to a new parent, we may now be able to load. |
| scheduleLoad(); |
| |
| HTMLElement::insertedInto(insertionPoint); |
| HTMLMediaElement* parent = mediaElement(); |
| if (insertionPoint == parent) |
| parent->didAddTrackElement(this); |
| return InsertionDone; |
| } |
| |
| void HTMLTrackElement::removedFrom(ContainerNode* insertionPoint) { |
| if (!parentNode() && isHTMLMediaElement(*insertionPoint)) |
| toHTMLMediaElement(insertionPoint)->didRemoveTrackElement(this); |
| HTMLElement::removedFrom(insertionPoint); |
| } |
| |
| void HTMLTrackElement::parseAttribute(const QualifiedName& name, |
| const AtomicString& oldValue, |
| const AtomicString& value) { |
| if (name == srcAttr) { |
| if (!value.isEmpty()) |
| scheduleLoad(); |
| else if (m_track) |
| m_track->removeAllCues(); |
| |
| // 4.8.10.12.3 Sourcing out-of-band text tracks |
| // As the kind, label, and srclang attributes are set, changed, or removed, |
| // the text track must update accordingly... |
| } else if (name == kindAttr) { |
| AtomicString lowerCaseValue = value.lower(); |
| // 'missing value default' ("subtitles") |
| if (lowerCaseValue.isNull()) |
| lowerCaseValue = TextTrack::subtitlesKeyword(); |
| // 'invalid value default' ("metadata") |
| else if (!TextTrack::isValidKindKeyword(lowerCaseValue)) |
| lowerCaseValue = TextTrack::metadataKeyword(); |
| |
| track()->setKind(lowerCaseValue); |
| } else if (name == labelAttr) { |
| track()->setLabel(value); |
| } else if (name == srclangAttr) { |
| track()->setLanguage(value); |
| } else if (name == idAttr) { |
| track()->setId(value); |
| } |
| |
| HTMLElement::parseAttribute(name, oldValue, value); |
| } |
| |
| const AtomicString& HTMLTrackElement::kind() { |
| return track()->kind(); |
| } |
| |
| void HTMLTrackElement::setKind(const AtomicString& kind) { |
| setAttribute(kindAttr, kind); |
| } |
| |
| LoadableTextTrack* HTMLTrackElement::ensureTrack() { |
| if (!m_track) { |
| // kind, label and language are updated by parseAttribute |
| m_track = LoadableTextTrack::create(this); |
| } |
| return m_track.get(); |
| } |
| |
| TextTrack* HTMLTrackElement::track() { |
| return ensureTrack(); |
| } |
| |
| bool HTMLTrackElement::isURLAttribute(const Attribute& attribute) const { |
| return attribute.name() == srcAttr || HTMLElement::isURLAttribute(attribute); |
| } |
| |
| void HTMLTrackElement::scheduleLoad() { |
| DVLOG(TRACK_LOG_LEVEL) << "scheduleLoad"; |
| |
| // 1. If another occurrence of this algorithm is already running for this text |
| // track and its track element, abort these steps, letting that other |
| // algorithm take care of this element. |
| if (m_loadTimer.isActive()) |
| return; |
| |
| // 2. If the text track's text track mode is not set to one of hidden or |
| // showing, abort these steps. |
| if (ensureTrack()->mode() != TextTrack::hiddenKeyword() && |
| ensureTrack()->mode() != TextTrack::showingKeyword()) |
| return; |
| |
| // 3. If the text track's track element does not have a media element as a |
| // parent, abort these steps. |
| if (!mediaElement()) |
| return; |
| |
| // 4. Run the remainder of these steps in parallel, allowing whatever caused |
| // these steps to run to continue. |
| m_loadTimer.startOneShot(0, BLINK_FROM_HERE); |
| |
| // 5. Top: Await a stable state. The synchronous section consists of the |
| // following steps. (The steps in the synchronous section are marked with [X]) |
| // FIXME: We use a timer to approximate a "stable state" - i.e. this is not |
| // 100% per spec. |
| } |
| |
| void HTMLTrackElement::loadTimerFired(TimerBase*) { |
| DVLOG(TRACK_LOG_LEVEL) << "loadTimerFired"; |
| |
| // 6. [X] Set the text track readiness state to loading. |
| setReadyState(kLoading); |
| |
| // 7. [X] Let URL be the track URL of the track element. |
| KURL url = getNonEmptyURLAttribute(srcAttr); |
| |
| // 8. [X] If the track element's parent is a media element then let CORS mode |
| // be the state of the parent media element's crossorigin content attribute. |
| // Otherwise, let CORS mode be No CORS. |
| const AtomicString& corsMode = mediaElementCrossOriginAttribute(); |
| |
| // 9. End the synchronous section, continuing the remaining steps in parallel. |
| |
| // 10. If URL is not the empty string, perform a potentially CORS-enabled |
| // fetch of URL, with the mode being CORS mode, the origin being the origin of |
| // the track element's node document, and the default origin behaviour set to |
| // fail. |
| if (!canLoadUrl(url)) { |
| didCompleteLoad(Failure); |
| return; |
| } |
| |
| if (url == m_url) { |
| DCHECK(m_loader); |
| switch (m_loader->loadState()) { |
| case TextTrackLoader::Idle: |
| case TextTrackLoader::Loading: |
| // If loading of the resource from this URL is in progress, return |
| // early. |
| break; |
| case TextTrackLoader::Finished: |
| didCompleteLoad(Success); |
| break; |
| case TextTrackLoader::Failed: |
| didCompleteLoad(Failure); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| return; |
| } |
| |
| m_url = url; |
| |
| if (m_loader) |
| m_loader->cancelLoad(); |
| |
| m_loader = TextTrackLoader::create(*this, document()); |
| if (!m_loader->load(m_url, crossOriginAttributeValue(corsMode))) |
| didCompleteLoad(Failure); |
| } |
| |
| bool HTMLTrackElement::canLoadUrl(const KURL& url) { |
| HTMLMediaElement* parent = mediaElement(); |
| if (!parent) |
| return false; |
| |
| if (url.isEmpty()) |
| return false; |
| |
| if (!document().contentSecurityPolicy()->allowMediaFromSource(url)) { |
| DVLOG(TRACK_LOG_LEVEL) << "canLoadUrl(" << urlForLoggingTrack(url) |
| << ") -> rejected by Content Security Policy"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void HTMLTrackElement::didCompleteLoad(LoadStatus status) { |
| // 10. ... (continued) |
| |
| // If the fetching algorithm fails for any reason (network error, the server |
| // returns an error code, a cross-origin check fails, etc), or if URL is the |
| // empty string, then queue a task to first change the text track readiness |
| // state to failed to load and then fire a simple event named error at the |
| // track element. This task must use the DOM manipulation task source. |
| // |
| // (Note: We don't "queue a task" here because this method will only be called |
| // from a timer - m_loadTimer or TextTrackLoader::m_cueLoadTimer - which |
| // should be a reasonable, and hopefully non-observable, approximation of the |
| // spec text. I.e we could consider this to be run from the "networking task |
| // source".) |
| // |
| // If the fetching algorithm does not fail, but the type of the resource is |
| // not a supported text track format, or the file was not successfully |
| // processed (e.g. the format in question is an XML format and the file |
| // contained a well-formedness error that the XML specification requires be |
| // detected and reported to the application), then the task that is queued by |
| // the networking task source in which the aforementioned problem is found |
| // must change the text track readiness state to failed to load and fire a |
| // simple event named error at the track element. |
| if (status == Failure) { |
| setReadyState(kError); |
| dispatchEvent(Event::create(EventTypeNames::error)); |
| return; |
| } |
| |
| // If the fetching algorithm does not fail, and the file was successfully |
| // processed, then the final task that is queued by the networking task |
| // source, after it has finished parsing the data, must change the text track |
| // readiness state to loaded, and fire a simple event named load at the track |
| // element. |
| setReadyState(kLoaded); |
| dispatchEvent(Event::create(EventTypeNames::load)); |
| } |
| |
| void HTMLTrackElement::newCuesAvailable(TextTrackLoader* loader) { |
| DCHECK_EQ(m_loader, loader); |
| DCHECK(m_track); |
| |
| HeapVector<Member<TextTrackCue>> newCues; |
| m_loader->getNewCues(newCues); |
| |
| m_track->addListOfCues(newCues); |
| } |
| |
| void HTMLTrackElement::newRegionsAvailable(TextTrackLoader* loader) { |
| DCHECK_EQ(m_loader, loader); |
| DCHECK(m_track); |
| |
| HeapVector<Member<VTTRegion>> newRegions; |
| m_loader->getNewRegions(newRegions); |
| |
| m_track->addRegions(newRegions); |
| } |
| |
| void HTMLTrackElement::cueLoadingCompleted(TextTrackLoader* loader, |
| bool loadingFailed) { |
| DCHECK_EQ(m_loader, loader); |
| |
| didCompleteLoad(loadingFailed ? Failure : Success); |
| } |
| |
| // NOTE: The values in the TextTrack::ReadinessState enum must stay in sync with |
| // those in HTMLTrackElement::ReadyState. |
| static_assert( |
| HTMLTrackElement::kNone == |
| static_cast<HTMLTrackElement::ReadyState>(TextTrack::NotLoaded), |
| "HTMLTrackElement::kNone should be in sync with TextTrack::NotLoaded"); |
| static_assert( |
| HTMLTrackElement::kLoading == |
| static_cast<HTMLTrackElement::ReadyState>(TextTrack::Loading), |
| "HTMLTrackElement::kLoading should be in sync with TextTrack::Loading"); |
| static_assert( |
| HTMLTrackElement::kLoaded == |
| static_cast<HTMLTrackElement::ReadyState>(TextTrack::Loaded), |
| "HTMLTrackElement::kLoaded should be in sync with TextTrack::Loaded"); |
| static_assert( |
| HTMLTrackElement::kError == |
| static_cast<HTMLTrackElement::ReadyState>(TextTrack::FailedToLoad), |
| "HTMLTrackElement::kError should be in sync with TextTrack::FailedToLoad"); |
| |
| void HTMLTrackElement::setReadyState(ReadyState state) { |
| ensureTrack()->setReadinessState( |
| static_cast<TextTrack::ReadinessState>(state)); |
| if (HTMLMediaElement* parent = mediaElement()) |
| return parent->textTrackReadyStateChanged(m_track.get()); |
| } |
| |
| HTMLTrackElement::ReadyState HTMLTrackElement::getReadyState() { |
| return static_cast<ReadyState>(ensureTrack()->getReadinessState()); |
| } |
| |
| const AtomicString& HTMLTrackElement::mediaElementCrossOriginAttribute() const { |
| if (HTMLMediaElement* parent = mediaElement()) |
| return parent->fastGetAttribute(HTMLNames::crossoriginAttr); |
| |
| return nullAtom; |
| } |
| |
| HTMLMediaElement* HTMLTrackElement::mediaElement() const { |
| Element* parent = parentElement(); |
| if (isHTMLMediaElement(parent)) |
| return toHTMLMediaElement(parent); |
| return nullptr; |
| } |
| |
| DEFINE_TRACE(HTMLTrackElement) { |
| visitor->trace(m_track); |
| visitor->trace(m_loader); |
| HTMLElement::trace(visitor); |
| } |
| |
| } // namespace blink |