blob: 8d7f1dd76a02b38df265628851bf693ea4db27b5 [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "webkit/glue/media/buffered_resource_loader.h"
#include "base/format_macros.h"
#include "base/string_util.h"
#include "net/base/net_errors.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebKitClient.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h"
#include "webkit/glue/multipart_response_delegate.h"
#include "webkit/glue/webkit_glue.h"
using WebKit::WebFrame;
using WebKit::WebString;
using WebKit::WebURLError;
using WebKit::WebURLLoader;
using WebKit::WebURLRequest;
using WebKit::WebURLResponse;
using webkit_glue::MultipartResponseDelegate;
namespace webkit_glue {
static const int kHttpOK = 200;
static const int kHttpPartialContent = 206;
// Define the number of bytes in a megabyte.
static const size_t kMegabyte = 1024 * 1024;
// Backward capacity of the buffer, by default 2MB.
static const size_t kBackwardCapcity = 2 * kMegabyte;
// Forward capacity of the buffer, by default 10MB.
static const size_t kForwardCapacity = 10 * kMegabyte;
// The threshold of bytes that we should wait until the data arrives in the
// future instead of restarting a new connection. This number is defined in the
// number of bytes, we should determine this value from typical connection speed
// and amount of time for a suitable wait. Now I just make a guess for this
// number to be 2MB.
// TODO(hclam): determine a better value for this.
static const int kForwardWaitThreshold = 2 * kMegabyte;
BufferedResourceLoader::BufferedResourceLoader(
const GURL& url,
int64 first_byte_position,
int64 last_byte_position)
: buffer_(new media::SeekableBuffer(kBackwardCapcity, kForwardCapacity)),
deferred_(false),
defer_strategy_(kReadThenDefer),
completed_(false),
range_requested_(false),
range_supported_(false),
url_(url),
first_byte_position_(first_byte_position),
last_byte_position_(last_byte_position),
single_origin_(true),
start_callback_(NULL),
offset_(0),
content_length_(kPositionNotSpecified),
instance_size_(kPositionNotSpecified),
read_callback_(NULL),
read_position_(0),
read_size_(0),
read_buffer_(NULL),
first_offset_(0),
last_offset_(0),
keep_test_loader_(false) {
}
BufferedResourceLoader::~BufferedResourceLoader() {
if (!completed_ && url_loader_.get())
url_loader_->cancel();
}
void BufferedResourceLoader::Start(net::CompletionCallback* start_callback,
NetworkEventCallback* event_callback,
WebFrame* frame) {
// Make sure we have not started.
DCHECK(!start_callback_.get());
DCHECK(!event_callback_.get());
DCHECK(start_callback);
DCHECK(event_callback);
CHECK(frame);
start_callback_.reset(start_callback);
event_callback_.reset(event_callback);
if (first_byte_position_ != kPositionNotSpecified) {
// TODO(hclam): server may not support range request so |offset_| may not
// equal to |first_byte_position_|.
offset_ = first_byte_position_;
}
// Increment the reference count right before we start the request. This
// reference will be release when this request has ended.
AddRef();
// Prepare the request.
WebURLRequest request(url_);
request.setTargetType(WebURLRequest::TargetIsMedia);
if (IsRangeRequest()) {
range_requested_ = true;
request.setHTTPHeaderField(WebString::fromUTF8("Range"),
WebString::fromUTF8(GenerateHeaders(
first_byte_position_,
last_byte_position_)));
}
frame->setReferrerForRequest(request, WebKit::WebURL());
// This flag is for unittests as we don't want to reset |url_loader|
if (!keep_test_loader_)
url_loader_.reset(frame->createAssociatedURLLoader());
// Start the resource loading.
url_loader_->loadAsynchronously(request, this);
}
void BufferedResourceLoader::Stop() {
// Reset callbacks.
start_callback_.reset();
event_callback_.reset();
read_callback_.reset();
// Use the internal buffer to signal that we have been stopped.
// TODO(hclam): Not so pretty to do this.
if (!buffer_.get())
return;
// Destroy internal buffer.
buffer_.reset();
if (url_loader_.get()) {
if (deferred_)
url_loader_->setDefersLoading(false);
deferred_ = false;
if (!completed_) {
url_loader_->cancel();
completed_ = true;
}
}
}
void BufferedResourceLoader::Read(int64 position,
int read_size,
uint8* buffer,
net::CompletionCallback* read_callback) {
DCHECK(!read_callback_.get());
DCHECK(buffer_.get());
DCHECK(read_callback);
DCHECK(buffer);
// Save the parameter of reading.
read_callback_.reset(read_callback);
read_position_ = position;
read_size_ = read_size;
read_buffer_ = buffer;
// If read position is beyond the instance size, we cannot read there.
if (instance_size_ != kPositionNotSpecified &&
instance_size_ <= read_position_) {
DoneRead(0);
return;
}
// Make sure |offset_| and |read_position_| does not differ by a large
// amount.
if (read_position_ > offset_ + kint32max ||
read_position_ < offset_ + kint32min) {
DoneRead(net::ERR_CACHE_MISS);
return;
}
// Prepare the parameters.
first_offset_ = static_cast<int>(read_position_ - offset_);
last_offset_ = first_offset_ + read_size_;
// If we can serve the request now, do the actual read.
if (CanFulfillRead()) {
ReadInternal();
UpdateDeferBehavior();
return;
}
// If you're deferred and you can't fulfill the read because you don't have
// enough data, you will never fulfill the read.
// Update defer behavior to re-enable deferring if need be.
UpdateDeferBehavior();
// If we expected the read request to be fulfilled later, returns
// immediately and let more data to flow in.
if (WillFulfillRead())
return;
// Make a callback to report failure.
DoneRead(net::ERR_CACHE_MISS);
}
int64 BufferedResourceLoader::GetBufferedPosition() {
if (buffer_.get())
return offset_ + static_cast<int>(buffer_->forward_bytes()) - 1;
return kPositionNotSpecified;
}
int64 BufferedResourceLoader::content_length() {
return content_length_;
}
int64 BufferedResourceLoader::instance_size() {
return instance_size_;
}
bool BufferedResourceLoader::range_supported() {
return range_supported_;
}
bool BufferedResourceLoader::network_activity() {
return !completed_ && !deferred_;
}
const GURL& BufferedResourceLoader::url() {
return url_;
}
void BufferedResourceLoader::SetURLLoaderForTest(WebURLLoader* mock_loader) {
url_loader_.reset(mock_loader);
keep_test_loader_ = true;
}
/////////////////////////////////////////////////////////////////////////////
// WebKit::WebURLLoaderClient implementation.
void BufferedResourceLoader::willSendRequest(
WebURLLoader* loader,
WebURLRequest& newRequest,
const WebURLResponse& redirectResponse) {
// The load may have been stopped and |start_callback| is destroyed.
// In this case we shouldn't do anything.
if (!start_callback_.get()) {
// Set the url in the request to an invalid value (empty url).
newRequest.setURL(WebKit::WebURL());
return;
}
// Only allow |single_origin_| if we haven't seen a different origin yet.
if (single_origin_)
single_origin_ = url_.GetOrigin() == GURL(newRequest.url()).GetOrigin();
if (!IsProtocolSupportedForMedia(newRequest.url())) {
// Set the url in the request to an invalid value (empty url).
newRequest.setURL(WebKit::WebURL());
DoneStart(net::ERR_ADDRESS_INVALID);
return;
}
url_ = newRequest.url();
}
void BufferedResourceLoader::didSendData(
WebURLLoader* loader,
unsigned long long bytes_sent,
unsigned long long total_bytes_to_be_sent) {
NOTIMPLEMENTED();
}
void BufferedResourceLoader::didReceiveResponse(
WebURLLoader* loader,
const WebURLResponse& response) {
// The loader may have been stopped and |start_callback| is destroyed.
// In this case we shouldn't do anything.
if (!start_callback_.get())
return;
bool partial_response = false;
// We make a strong assumption that when we reach here we have either
// received a response from HTTP/HTTPS protocol or the request was
// successful (in particular range request). So we only verify the partial
// response for HTTP and HTTPS protocol.
if (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) {
int error = net::OK;
// Check to see whether the server supports byte ranges.
std::string accept_ranges =
response.httpHeaderField("Accept-Ranges").utf8();
range_supported_ = (accept_ranges.find("bytes") != std::string::npos);
partial_response = (response.httpStatusCode() == kHttpPartialContent);
if (range_requested_) {
// If we have verified the partial response and it is correct, we will
// return net::OK. It's also possible for a server to support range
// requests without advertising Accept-Ranges: bytes.
if (partial_response && VerifyPartialResponse(response))
range_supported_ = true;
else
error = net::ERR_INVALID_RESPONSE;
} else if (response.httpStatusCode() != kHttpOK) {
// We didn't request a range but server didn't reply with "200 OK".
error = net::ERR_FAILED;
}
if (error != net::OK) {
DoneStart(error);
Stop();
return;
}
} else {
// For any protocol other than HTTP and HTTPS, assume range request is
// always fulfilled.
partial_response = range_requested_;
}
// Expected content length can be |kPositionNotSpecified|, in that case
// |content_length_| is not specified and this is a streaming response.
content_length_ = response.expectedContentLength();
// If we have not requested a range, then the size of the instance is equal
// to the content length.
if (!partial_response)
instance_size_ = content_length_;
// Calls with a successful response.
DoneStart(net::OK);
}
void BufferedResourceLoader::didReceiveData(
WebURLLoader* loader,
const char* data,
int data_length,
int encoded_data_length) {
DCHECK(!completed_);
DCHECK_GT(data_length, 0);
// If this loader has been stopped, |buffer_| would be destroyed.
// In this case we shouldn't do anything.
if (!buffer_.get())
return;
// Writes more data to |buffer_|.
buffer_->Append(reinterpret_cast<const uint8*>(data), data_length);
// If there is an active read request, try to fulfill the request.
if (HasPendingRead() && CanFulfillRead())
ReadInternal();
// At last see if the buffer is full and we need to defer the downloading.
UpdateDeferBehavior();
// Consume excess bytes from our in-memory buffer if necessary.
if (buffer_->forward_bytes() > buffer_->forward_capacity()) {
size_t excess = buffer_->forward_bytes() - buffer_->forward_capacity();
bool success = buffer_->Seek(excess);
DCHECK(success);
offset_ += first_offset_ + excess;
}
// Notify that we have received some data.
NotifyNetworkEvent();
}
void BufferedResourceLoader::didDownloadData(
WebKit::WebURLLoader* loader,
int dataLength) {
NOTIMPLEMENTED();
}
void BufferedResourceLoader::didReceiveCachedMetadata(
WebURLLoader* loader,
const char* data,
int data_length) {
NOTIMPLEMENTED();
}
void BufferedResourceLoader::didFinishLoading(
WebURLLoader* loader,
double finishTime) {
DCHECK(!completed_);
completed_ = true;
// If we didn't know the |instance_size_| we do now.
if (instance_size_ == kPositionNotSpecified) {
instance_size_ = offset_ + buffer_->forward_bytes();
}
// If there is a start callback, calls it.
if (start_callback_.get()) {
DoneStart(net::OK);
}
// If there is a pending read but the request has ended, returns with what
// we have.
if (HasPendingRead()) {
// Make sure we have a valid buffer before we satisfy a read request.
DCHECK(buffer_.get());
// Try to fulfill with what is in the buffer.
if (CanFulfillRead())
ReadInternal();
else
DoneRead(net::ERR_CACHE_MISS);
}
// There must not be any outstanding read request.
DCHECK(!HasPendingRead());
// Notify that network response is completed.
NotifyNetworkEvent();
url_loader_.reset();
Release();
}
void BufferedResourceLoader::didFail(
WebURLLoader* loader,
const WebURLError& error) {
DCHECK(!completed_);
completed_ = true;
// If there is a start callback, calls it.
if (start_callback_.get()) {
DoneStart(error.reason);
}
// If there is a pending read but the request failed, return with the
// reason for the error.
if (HasPendingRead()) {
DoneRead(error.reason);
}
// Notify that network response is completed.
NotifyNetworkEvent();
url_loader_.reset();
Release();
}
bool BufferedResourceLoader::HasSingleOrigin() const {
return single_origin_;
}
/////////////////////////////////////////////////////////////////////////////
// Helper methods.
void BufferedResourceLoader::UpdateDeferBehavior() {
if (!url_loader_.get() || !buffer_.get())
return;
if ((deferred_ && ShouldDisableDefer()) ||
(!deferred_ && ShouldEnableDefer())) {
bool eventOccurred = ToggleDeferring();
if (eventOccurred)
NotifyNetworkEvent();
}
}
void BufferedResourceLoader::UpdateDeferStrategy(DeferStrategy strategy) {
defer_strategy_ = strategy;
UpdateDeferBehavior();
}
bool BufferedResourceLoader::ShouldEnableDefer() {
// If we're already deferring, then enabling makes no sense.
if (deferred_)
return false;
switch(defer_strategy_) {
// Never defer at all, so never enable defer.
case kNeverDefer:
return false;
// Defer if nothing is being requested.
case kReadThenDefer:
return !read_callback_.get();
// Defer if we've reached the max capacity of the threshold.
case kThresholdDefer:
return buffer_->forward_bytes() >= buffer_->forward_capacity();
}
// Otherwise don't enable defer.
return false;
}
bool BufferedResourceLoader::ShouldDisableDefer() {
// If we're not deferring, then disabling makes no sense.
if (!deferred_)
return false;
switch(defer_strategy_) {
// Always disable deferring.
case kNeverDefer:
return true;
// We have an outstanding read request, and we have not buffered enough
// yet to fulfill the request; disable defer to get more data.
case kReadThenDefer: {
size_t amount_buffered = buffer_->forward_bytes();
size_t amount_to_read = static_cast<size_t>(read_size_);
return read_callback_.get() && amount_buffered < amount_to_read;
}
// We have less than half the capacity of our threshold, so
// disable defer to get more data.
case kThresholdDefer: {
size_t amount_buffered = buffer_->forward_bytes();
size_t half_capacity = buffer_->forward_capacity() / 2;
return amount_buffered < half_capacity;
}
}
// Otherwise keep deferring.
return false;
}
bool BufferedResourceLoader::ToggleDeferring() {
deferred_ = !deferred_;
if (url_loader_.get()) {
url_loader_->setDefersLoading(deferred_);
return true;
}
return false;
}
bool BufferedResourceLoader::CanFulfillRead() {
// If we are reading too far in the backward direction.
if (first_offset_ < 0 &&
first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0)
return false;
// If the start offset is too far ahead.
if (first_offset_ >= static_cast<int>(buffer_->forward_bytes()))
return false;
// At the point, we verified that first byte requested is within the buffer.
// If the request has completed, then just returns with what we have now.
if (completed_)
return true;
// If the resource request is still active, make sure the whole requested
// range is covered.
if (last_offset_ > static_cast<int>(buffer_->forward_bytes()))
return false;
return true;
}
bool BufferedResourceLoader::WillFulfillRead() {
// Reading too far in the backward direction.
if (first_offset_ < 0 &&
first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0)
return false;
// Try to read too far ahead.
if (last_offset_ > kForwardWaitThreshold)
return false;
// The resource request has completed, there's no way we can fulfill the
// read request.
if (completed_)
return false;
return true;
}
void BufferedResourceLoader::ReadInternal() {
// Seek to the first byte requested.
bool ret = buffer_->Seek(first_offset_);
DCHECK(ret);
// Then do the read.
int read = static_cast<int>(buffer_->Read(read_buffer_, read_size_));
offset_ += first_offset_ + read;
// And report with what we have read.
DoneRead(read);
}
bool BufferedResourceLoader::VerifyPartialResponse(
const WebURLResponse& response) {
int first_byte_position, last_byte_position, instance_size;
if (!MultipartResponseDelegate::ReadContentRanges(response,
&first_byte_position,
&last_byte_position,
&instance_size)) {
return false;
}
if (instance_size != kPositionNotSpecified) {
instance_size_ = instance_size;
}
if (first_byte_position_ != kPositionNotSpecified &&
first_byte_position_ != first_byte_position) {
return false;
}
// TODO(hclam): I should also check |last_byte_position|, but since
// we will never make such a request that it is ok to leave it unimplemented.
return true;
}
std::string BufferedResourceLoader::GenerateHeaders(
int64 first_byte_position,
int64 last_byte_position) {
// Construct the value for the range header.
std::string header;
if (first_byte_position > kPositionNotSpecified &&
last_byte_position > kPositionNotSpecified) {
if (first_byte_position <= last_byte_position) {
header = base::StringPrintf("bytes=%" PRId64 "-%" PRId64,
first_byte_position,
last_byte_position);
}
} else if (first_byte_position > kPositionNotSpecified) {
header = base::StringPrintf("bytes=%" PRId64 "-",
first_byte_position);
} else if (last_byte_position > kPositionNotSpecified) {
NOTIMPLEMENTED() << "Suffix range not implemented";
}
return header;
}
void BufferedResourceLoader::DoneRead(int error) {
read_callback_->RunWithParams(Tuple1<int>(error));
read_callback_.reset();
read_position_ = 0;
read_size_ = 0;
read_buffer_ = NULL;
first_offset_ = 0;
last_offset_ = 0;
}
void BufferedResourceLoader::DoneStart(int error) {
start_callback_->RunWithParams(Tuple1<int>(error));
start_callback_.reset();
}
void BufferedResourceLoader::NotifyNetworkEvent() {
if (event_callback_.get())
event_callback_->Run();
}
bool BufferedResourceLoader::IsRangeRequest() const {
return first_byte_position_ != kPositionNotSpecified;
}
} // namespace webkit_glue