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 "bindings/core/v8/ExceptionStatePlaceholder.h"
30#include "core/HTMLNames.h"
31#include "core/dom/Document.h"
32#include "core/events/Event.h"
33#include "core/frame/csp/ContentSecurityPolicy.h"
34#include "core/html/HTMLMediaElement.h"
35#include "platform/Logging.h"
36
37namespace blink {
38
39using namespace HTMLNames;
40
41#if !LOG_DISABLED
42static String urlForLoggingTrack(const KURL& url)
43{
44    static const unsigned maximumURLLengthForLogging = 128;
45
46    if (url.string().length() < maximumURLLengthForLogging)
47        return url.string();
48    return url.string().substring(0, maximumURLLengthForLogging) + "...";
49}
50#endif
51
52inline HTMLTrackElement::HTMLTrackElement(Document& document)
53    : HTMLElement(trackTag, document)
54    , m_loadTimer(this, &HTMLTrackElement::loadTimerFired)
55{
56    WTF_LOG(Media, "HTMLTrackElement::HTMLTrackElement - %p", this);
57}
58
59DEFINE_NODE_FACTORY(HTMLTrackElement)
60
61HTMLTrackElement::~HTMLTrackElement()
62{
63#if !ENABLE(OILPAN)
64    if (m_track)
65        m_track->clearTrackElement();
66#endif
67}
68
69Node::InsertionNotificationRequest HTMLTrackElement::insertedInto(ContainerNode* insertionPoint)
70{
71    WTF_LOG(Media, "HTMLTrackElement::insertedInto");
72
73    // Since we've moved to a new parent, we may now be able to load.
74    scheduleLoad();
75
76    HTMLElement::insertedInto(insertionPoint);
77    HTMLMediaElement* parent = mediaElement();
78    if (insertionPoint == parent)
79        parent->didAddTrackElement(this);
80    return InsertionDone;
81}
82
83void HTMLTrackElement::removedFrom(ContainerNode* insertionPoint)
84{
85    if (!parentNode() && isHTMLMediaElement(*insertionPoint))
86        toHTMLMediaElement(insertionPoint)->didRemoveTrackElement(this);
87    HTMLElement::removedFrom(insertionPoint);
88}
89
90void HTMLTrackElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
91{
92    if (name == srcAttr) {
93        if (!value.isEmpty())
94            scheduleLoad();
95        else if (m_track)
96            m_track->removeAllCues();
97
98    // 4.8.10.12.3 Sourcing out-of-band text tracks
99    // As the kind, label, and srclang attributes are set, changed, or removed, the text track must update accordingly...
100    } else if (name == kindAttr) {
101        track()->setKind(value.lower());
102    } else if (name == labelAttr) {
103        track()->setLabel(value);
104    } else if (name == srclangAttr) {
105        track()->setLanguage(value);
106    } else if (name == idAttr) {
107        track()->setId(value);
108    } else if (name == defaultAttr) {
109        track()->setIsDefault(!value.isNull());
110    }
111
112    HTMLElement::parseAttribute(name, value);
113}
114
115const AtomicString& HTMLTrackElement::kind()
116{
117    return track()->kind();
118}
119
120void HTMLTrackElement::setKind(const AtomicString& kind)
121{
122    setAttribute(kindAttr, kind);
123}
124
125LoadableTextTrack* HTMLTrackElement::ensureTrack()
126{
127    if (!m_track) {
128        // kind, label and language are updated by parseAttribute
129        m_track = LoadableTextTrack::create(this);
130    }
131    return m_track.get();
132}
133
134TextTrack* HTMLTrackElement::track()
135{
136    return ensureTrack();
137}
138
139bool HTMLTrackElement::isURLAttribute(const Attribute& attribute) const
140{
141    return attribute.name() == srcAttr || HTMLElement::isURLAttribute(attribute);
142}
143
144void HTMLTrackElement::scheduleLoad()
145{
146    WTF_LOG(Media, "HTMLTrackElement::scheduleLoad");
147
148    // 1. If another occurrence of this algorithm is already running for this text track and its track element,
149    // abort these steps, letting that other algorithm take care of this element.
150    if (m_loadTimer.isActive())
151        return;
152
153    // 2. If the text track's text track mode is not set to one of hidden or showing, abort these steps.
154    if (ensureTrack()->mode() != TextTrack::hiddenKeyword() && ensureTrack()->mode() != TextTrack::showingKeyword())
155        return;
156
157    // 3. If the text track's track element does not have a media element as a parent, abort these steps.
158    if (!mediaElement())
159        return;
160
161    // 4. Run the remainder of these steps asynchronously, allowing whatever caused these steps to run to continue.
162    m_loadTimer.startOneShot(0, FROM_HERE);
163}
164
165void HTMLTrackElement::loadTimerFired(Timer<HTMLTrackElement>*)
166{
167    if (!fastHasAttribute(srcAttr))
168        return;
169
170    WTF_LOG(Media, "HTMLTrackElement::loadTimerFired");
171
172    // 6. Set the text track readiness state to loading.
173    setReadyState(HTMLTrackElement::LOADING);
174
175    // 7. Let URL be the track URL of the track element.
176    KURL url = getNonEmptyURLAttribute(srcAttr);
177
178    // 8. If the track element's parent is a media element then let CORS mode be the state of the parent media
179    // element's crossorigin content attribute. Otherwise, let CORS mode be No CORS.
180    if (!canLoadUrl(url)) {
181        didCompleteLoad(HTMLTrackElement::Failure);
182        return;
183    }
184
185    ensureTrack()->scheduleLoad(url);
186}
187
188bool HTMLTrackElement::canLoadUrl(const KURL& url)
189{
190    HTMLMediaElement* parent = mediaElement();
191    if (!parent)
192        return false;
193
194    // 4.8.10.12.3 Sourcing out-of-band text tracks
195
196    // 4. Download: If URL is not the empty string, perform a potentially CORS-enabled fetch of URL, with the
197    // mode being the state of the media element's crossorigin content attribute, the origin being the
198    // origin of the media element's Document, and the default origin behaviour set to fail.
199    if (url.isEmpty())
200        return false;
201
202    if (!document().contentSecurityPolicy()->allowMediaFromSource(url)) {
203        WTF_LOG(Media, "HTMLTrackElement::canLoadUrl(%s) -> rejected by Content Security Policy", urlForLoggingTrack(url).utf8().data());
204        return false;
205    }
206
207    return true;
208}
209
210void HTMLTrackElement::didCompleteLoad(LoadStatus status)
211{
212    // 4.8.10.12.3 Sourcing out-of-band text tracks (continued)
213
214    // 4. Download: ...
215    // If the fetching algorithm fails for any reason (network error, the server returns an error
216    // code, a cross-origin check fails, etc), or if URL is the empty string or has the wrong origin
217    // as determined by the condition at the start of this step, or if the fetched resource is not in
218    // a supported format, then queue a task to first change the text track readiness state to failed
219    // to load and then fire a simple event named error at the track element; and then, once that task
220    // is queued, move on to the step below labeled monitoring.
221
222    if (status == Failure) {
223        setReadyState(HTMLTrackElement::TRACK_ERROR);
224        dispatchEvent(Event::create(EventTypeNames::error), IGNORE_EXCEPTION);
225        return;
226    }
227
228    // If the fetching algorithm does not fail, then the final task that is queued by the networking
229    // task source must run the following steps:
230    //     1. Change the text track readiness state to loaded.
231    setReadyState(HTMLTrackElement::LOADED);
232
233    //     2. If the file was successfully processed, fire a simple event named load at the
234    //        track element.
235    dispatchEvent(Event::create(EventTypeNames::load), IGNORE_EXCEPTION);
236}
237
238// NOTE: The values in the TextTrack::ReadinessState enum must stay in sync with those in HTMLTrackElement::ReadyState.
239COMPILE_ASSERT(HTMLTrackElement::NONE == static_cast<HTMLTrackElement::ReadyState>(TextTrack::NotLoaded), TextTrackEnumNotLoaded_Is_Wrong_Should_Be_HTMLTrackElementEnumNONE);
240COMPILE_ASSERT(HTMLTrackElement::LOADING == static_cast<HTMLTrackElement::ReadyState>(TextTrack::Loading), TextTrackEnumLoadingIsWrong_ShouldBe_HTMLTrackElementEnumLOADING);
241COMPILE_ASSERT(HTMLTrackElement::LOADED == static_cast<HTMLTrackElement::ReadyState>(TextTrack::Loaded), TextTrackEnumLoaded_Is_Wrong_Should_Be_HTMLTrackElementEnumLOADED);
242COMPILE_ASSERT(HTMLTrackElement::TRACK_ERROR == static_cast<HTMLTrackElement::ReadyState>(TextTrack::FailedToLoad), TextTrackEnumFailedToLoad_Is_Wrong_Should_Be_HTMLTrackElementEnumTRACK_ERROR);
243
244void HTMLTrackElement::setReadyState(ReadyState state)
245{
246    ensureTrack()->setReadinessState(static_cast<TextTrack::ReadinessState>(state));
247    if (HTMLMediaElement* parent = mediaElement())
248        return parent->textTrackReadyStateChanged(m_track.get());
249}
250
251HTMLTrackElement::ReadyState HTMLTrackElement::readyState()
252{
253    return static_cast<ReadyState>(ensureTrack()->readinessState());
254}
255
256const AtomicString& HTMLTrackElement::mediaElementCrossOriginAttribute() const
257{
258    if (HTMLMediaElement* parent = mediaElement())
259        return parent->fastGetAttribute(HTMLNames::crossoriginAttr);
260
261    return nullAtom;
262}
263
264HTMLMediaElement* HTMLTrackElement::mediaElement() const
265{
266    Element* parent = parentElement();
267    if (isHTMLMediaElement(parent))
268        return toHTMLMediaElement(parent);
269    return 0;
270}
271
272void HTMLTrackElement::trace(Visitor* visitor)
273{
274    visitor->trace(m_track);
275    HTMLElement::trace(visitor);
276}
277
278}
279