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
28#if ENABLE(VIDEO_TRACK)
29
30#include "TextTrackLoader.h"
31
32#include "CachedResourceLoader.h"
33#include "CachedResourceRequest.h"
34#include "CachedTextTrack.h"
35#include "CrossOriginAccessControl.h"
36#include "Document.h"
37#include "Logging.h"
38#include "ResourceBuffer.h"
39#include "ScriptCallStack.h"
40#include "SecurityOrigin.h"
41#include "WebVTTParser.h"
42
43namespace WebCore {
44
45TextTrackLoader::TextTrackLoader(TextTrackLoaderClient* client, ScriptExecutionContext* context)
46    : m_client(client)
47    , m_scriptExecutionContext(context)
48    , m_cueLoadTimer(this, &TextTrackLoader::cueLoadTimerFired)
49    , m_state(Idle)
50    , m_parseOffset(0)
51    , m_newCuesAvailable(false)
52{
53}
54
55TextTrackLoader::~TextTrackLoader()
56{
57    if (m_cachedCueData)
58        m_cachedCueData->removeClient(this);
59}
60
61void TextTrackLoader::cueLoadTimerFired(Timer<TextTrackLoader>* timer)
62{
63    ASSERT_UNUSED(timer, timer == &m_cueLoadTimer);
64
65    if (m_newCuesAvailable) {
66        m_newCuesAvailable = false;
67        m_client->newCuesAvailable(this);
68    }
69
70    if (m_state >= Finished)
71        m_client->cueLoadingCompleted(this, m_state == Failed);
72}
73
74void TextTrackLoader::cancelLoad()
75{
76    if (m_cachedCueData) {
77        m_cachedCueData->removeClient(this);
78        m_cachedCueData = 0;
79    }
80}
81
82void TextTrackLoader::processNewCueData(CachedResource* resource)
83{
84    ASSERT(m_cachedCueData == resource);
85
86    if (m_state == Failed || !resource->resourceBuffer())
87        return;
88
89    ResourceBuffer* buffer = resource->resourceBuffer();
90    if (m_parseOffset == buffer->size())
91        return;
92
93    if (!m_cueParser)
94        m_cueParser = WebVTTParser::create(this, m_scriptExecutionContext);
95
96    const char* data;
97    unsigned length;
98
99    while ((length = buffer->getSomeData(data, m_parseOffset))) {
100        m_cueParser->parseBytes(data, length);
101        m_parseOffset += length;
102    }
103}
104
105// FIXME: This is a very unusual pattern, no other CachedResourceClient does this. Refactor to use notifyFinished() instead.
106void TextTrackLoader::deprecatedDidReceiveCachedResource(CachedResource* resource)
107{
108    ASSERT(m_cachedCueData == resource);
109
110    if (!resource->resourceBuffer())
111        return;
112
113    processNewCueData(resource);
114}
115
116void TextTrackLoader::corsPolicyPreventedLoad()
117{
118    DEFINE_STATIC_LOCAL(String, consoleMessage, (ASCIILiteral("Cross-origin text track load denied by Cross-Origin Resource Sharing policy.")));
119    Document* document = toDocument(m_scriptExecutionContext);
120    document->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, consoleMessage);
121    m_state = Failed;
122}
123
124void TextTrackLoader::notifyFinished(CachedResource* resource)
125{
126    ASSERT(m_cachedCueData == resource);
127
128    Document* document = toDocument(m_scriptExecutionContext);
129    if (!m_crossOriginMode.isNull()
130        && !document->securityOrigin()->canRequest(resource->response().url())
131        && !resource->passesAccessControlCheck(document->securityOrigin())) {
132
133        corsPolicyPreventedLoad();
134    }
135
136    if (m_state != Failed) {
137        processNewCueData(resource);
138        if (m_state != Failed)
139            m_state = resource->errorOccurred() ? Failed : Finished;
140    }
141
142    if (!m_cueLoadTimer.isActive())
143        m_cueLoadTimer.startOneShot(0);
144
145    cancelLoad();
146}
147
148bool TextTrackLoader::load(const KURL& url, const String& crossOriginMode)
149{
150    cancelLoad();
151
152    if (!m_client->shouldLoadCues(this))
153        return false;
154
155    ASSERT(m_scriptExecutionContext->isDocument());
156    Document* document = toDocument(m_scriptExecutionContext);
157    CachedResourceRequest cueRequest(ResourceRequest(document->completeURL(url)));
158
159    if (!crossOriginMode.isNull()) {
160        m_crossOriginMode = crossOriginMode;
161        StoredCredentials allowCredentials = equalIgnoringCase(crossOriginMode, "use-credentials") ? AllowStoredCredentials : DoNotAllowStoredCredentials;
162        updateRequestForAccessControl(cueRequest.mutableResourceRequest(), document->securityOrigin(), allowCredentials);
163    } else {
164        // Cross-origin resources that are not suitably CORS-enabled may not load.
165        if (!document->securityOrigin()->canRequest(url)) {
166            corsPolicyPreventedLoad();
167            return false;
168        }
169    }
170
171    CachedResourceLoader* cachedResourceLoader = document->cachedResourceLoader();
172    m_cachedCueData = cachedResourceLoader->requestTextTrack(cueRequest);
173    if (m_cachedCueData)
174        m_cachedCueData->addClient(this);
175
176    m_client->cueLoadingStarted(this);
177
178    return true;
179}
180
181void TextTrackLoader::newCuesParsed()
182{
183    if (m_cueLoadTimer.isActive())
184        return;
185
186    m_newCuesAvailable = true;
187    m_cueLoadTimer.startOneShot(0);
188}
189
190#if ENABLE(WEBVTT_REGIONS)
191void TextTrackLoader::newRegionsParsed()
192{
193    m_client->newRegionsAvailable(this);
194}
195#endif
196
197void TextTrackLoader::fileFailedToParse()
198{
199    LOG(Media, "TextTrackLoader::fileFailedToParse");
200
201    m_state = Failed;
202
203    if (!m_cueLoadTimer.isActive())
204        m_cueLoadTimer.startOneShot(0);
205
206    cancelLoad();
207}
208
209void TextTrackLoader::getNewCues(Vector<RefPtr<TextTrackCue> >& outputCues)
210{
211    ASSERT(m_cueParser);
212    if (m_cueParser)
213        m_cueParser->getNewCues(outputCues);
214}
215
216#if ENABLE(WEBVTT_REGIONS)
217void TextTrackLoader::getNewRegions(Vector<RefPtr<TextTrackRegion> >& outputRegions)
218{
219    ASSERT(m_cueParser);
220    if (m_cueParser)
221        m_cueParser->getNewRegions(outputRegions);
222}
223#endif
224}
225
226#endif
227