1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/renderer/media/android/media_info_loader.h"
6
7#include "base/bits.h"
8#include "base/callback_helpers.h"
9#include "base/metrics/histogram.h"
10#include "third_party/WebKit/public/platform/WebURLError.h"
11#include "third_party/WebKit/public/platform/WebURLLoader.h"
12#include "third_party/WebKit/public/platform/WebURLResponse.h"
13#include "third_party/WebKit/public/web/WebFrame.h"
14
15using blink::WebFrame;
16using blink::WebURLError;
17using blink::WebURLLoader;
18using blink::WebURLLoaderOptions;
19using blink::WebURLRequest;
20using blink::WebURLResponse;
21
22namespace content {
23
24static const int kHttpOK = 200;
25static const int kHttpPartialContentOK = 206;
26
27MediaInfoLoader::MediaInfoLoader(
28    const GURL& url,
29    blink::WebMediaPlayer::CORSMode cors_mode,
30    const ReadyCB& ready_cb)
31    : loader_failed_(false),
32      url_(url),
33      allow_stored_credentials_(false),
34      cors_mode_(cors_mode),
35      single_origin_(true),
36      ready_cb_(ready_cb) {}
37
38MediaInfoLoader::~MediaInfoLoader() {}
39
40void MediaInfoLoader::Start(blink::WebFrame* frame) {
41  // Make sure we have not started.
42  DCHECK(!ready_cb_.is_null());
43  CHECK(frame);
44
45  start_time_ = base::TimeTicks::Now();
46  first_party_url_ = frame->document().firstPartyForCookies();
47
48  // Prepare the request.
49  WebURLRequest request(url_);
50  // TODO(mkwst): Split this into video/audio.
51  request.setRequestContext(WebURLRequest::RequestContextVideo);
52  frame->setReferrerForRequest(request, blink::WebURL());
53
54  // Since we don't actually care about the media data at this time, use a two
55  // byte range request to avoid unnecessarily downloading resources.  Not all
56  // servers support HEAD unfortunately, so use a range request; which is no
57  // worse than the previous request+cancel code.  See http://crbug.com/400788
58  request.addHTTPHeaderField("Range", "bytes=0-1");
59
60  scoped_ptr<WebURLLoader> loader;
61  if (test_loader_) {
62    loader = test_loader_.Pass();
63  } else {
64    WebURLLoaderOptions options;
65    if (cors_mode_ == blink::WebMediaPlayer::CORSModeUnspecified) {
66      options.allowCredentials = true;
67      options.crossOriginRequestPolicy =
68          WebURLLoaderOptions::CrossOriginRequestPolicyAllow;
69      allow_stored_credentials_ = true;
70    } else {
71      options.exposeAllResponseHeaders = true;
72      // The author header set is empty, no preflight should go ahead.
73      options.preflightPolicy = WebURLLoaderOptions::PreventPreflight;
74      options.crossOriginRequestPolicy =
75          WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl;
76      if (cors_mode_ == blink::WebMediaPlayer::CORSModeUseCredentials) {
77        options.allowCredentials = true;
78        allow_stored_credentials_ = true;
79      }
80    }
81    loader.reset(frame->createAssociatedURLLoader(options));
82  }
83
84  // Start the resource loading.
85  loader->loadAsynchronously(request, this);
86  active_loader_.reset(new media::ActiveLoader(loader.Pass()));
87}
88
89/////////////////////////////////////////////////////////////////////////////
90// blink::WebURLLoaderClient implementation.
91void MediaInfoLoader::willSendRequest(
92    WebURLLoader* loader,
93    WebURLRequest& newRequest,
94    const WebURLResponse& redirectResponse) {
95  // The load may have been stopped and |ready_cb| is destroyed.
96  // In this case we shouldn't do anything.
97  if (ready_cb_.is_null()) {
98    // Set the url in the request to an invalid value (empty url).
99    newRequest.setURL(blink::WebURL());
100    return;
101  }
102
103  // Only allow |single_origin_| if we haven't seen a different origin yet.
104  if (single_origin_)
105    single_origin_ = url_.GetOrigin() == GURL(newRequest.url()).GetOrigin();
106
107  url_ = newRequest.url();
108  first_party_url_ = newRequest.firstPartyForCookies();
109  allow_stored_credentials_ = newRequest.allowStoredCredentials();
110}
111
112void MediaInfoLoader::didSendData(
113    WebURLLoader* loader,
114    unsigned long long bytes_sent,
115    unsigned long long total_bytes_to_be_sent) {
116  NOTIMPLEMENTED();
117}
118
119void MediaInfoLoader::didReceiveResponse(
120    WebURLLoader* loader,
121    const WebURLResponse& response) {
122  DVLOG(1) << "didReceiveResponse: HTTP/"
123           << (response.httpVersion() == WebURLResponse::HTTP_0_9 ? "0.9" :
124               response.httpVersion() == WebURLResponse::HTTP_1_0 ? "1.0" :
125               response.httpVersion() == WebURLResponse::HTTP_1_1 ? "1.1" :
126               "Unknown")
127           << " " << response.httpStatusCode();
128  DCHECK(active_loader_.get());
129  if (!url_.SchemeIs(url::kHttpScheme) && !url_.SchemeIs(url::kHttpsScheme)) {
130      DidBecomeReady(kOk);
131      return;
132  }
133  if (response.httpStatusCode() == kHttpOK ||
134      response.httpStatusCode() == kHttpPartialContentOK) {
135    DidBecomeReady(kOk);
136    return;
137  }
138  loader_failed_ = true;
139  DidBecomeReady(kFailed);
140}
141
142void MediaInfoLoader::didReceiveData(
143    WebURLLoader* loader,
144    const char* data,
145    int data_length,
146    int encoded_data_length) {
147  // Ignored.
148}
149
150void MediaInfoLoader::didDownloadData(
151    blink::WebURLLoader* loader,
152    int dataLength,
153    int encodedDataLength) {
154  NOTIMPLEMENTED();
155}
156
157void MediaInfoLoader::didReceiveCachedMetadata(
158    WebURLLoader* loader,
159    const char* data,
160    int data_length) {
161  NOTIMPLEMENTED();
162}
163
164void MediaInfoLoader::didFinishLoading(
165    WebURLLoader* loader,
166    double finishTime,
167    int64_t total_encoded_data_length) {
168  DCHECK(active_loader_.get());
169  DidBecomeReady(kOk);
170}
171
172void MediaInfoLoader::didFail(
173    WebURLLoader* loader,
174    const WebURLError& error) {
175  DVLOG(1) << "didFail: reason=" << error.reason
176           << ", isCancellation=" << error.isCancellation
177           << ", domain=" << error.domain.utf8().data()
178           << ", localizedDescription="
179           << error.localizedDescription.utf8().data();
180  DCHECK(active_loader_.get());
181  loader_failed_ = true;
182  DidBecomeReady(kFailed);
183}
184
185bool MediaInfoLoader::HasSingleOrigin() const {
186  DCHECK(ready_cb_.is_null())
187      << "Must become ready before calling HasSingleOrigin()";
188  return single_origin_;
189}
190
191bool MediaInfoLoader::DidPassCORSAccessCheck() const {
192  DCHECK(ready_cb_.is_null())
193      << "Must become ready before calling DidPassCORSAccessCheck()";
194  return !loader_failed_ &&
195      cors_mode_ != blink::WebMediaPlayer::CORSModeUnspecified;
196}
197
198/////////////////////////////////////////////////////////////////////////////
199// Helper methods.
200
201void MediaInfoLoader::DidBecomeReady(Status status) {
202  UMA_HISTOGRAM_TIMES("Media.InfoLoadDelay",
203                      base::TimeTicks::Now() - start_time_);
204  active_loader_.reset();
205  if (!ready_cb_.is_null())
206    base::ResetAndReturn(&ready_cb_).Run(status, url_, first_party_url_,
207                                         allow_stored_credentials_);
208}
209
210}  // namespace content
211