| // 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 |