diff options
author | Kristian Monsen <kristianm@google.com> | 2011-05-11 20:53:37 +0100 |
---|---|---|
committer | Kristian Monsen <kristianm@google.com> | 2011-05-16 13:54:48 +0100 |
commit | 21d179b334e59e9a3bfcaed4c4430bef1bc5759d (patch) | |
tree | 64e2bb6da27af6a5c93ca34f6051584aafbfcb9e /webkit/glue/media | |
parent | 0c63f00edd6ed0482fd5cbcea937ca088baf7858 (diff) | |
download | external_chromium-21d179b334e59e9a3bfcaed4c4430bef1bc5759d.zip external_chromium-21d179b334e59e9a3bfcaed4c4430bef1bc5759d.tar.gz external_chromium-21d179b334e59e9a3bfcaed4c4430bef1bc5759d.tar.bz2 |
Merge Chromium at 10.0.621.0: Initial merge by git.
Change-Id: I070cc91c608dfa4a968a5a54c173260765ac8097
Diffstat (limited to 'webkit/glue/media')
-rw-r--r-- | webkit/glue/media/audio_decoder.cc | 76 | ||||
-rw-r--r-- | webkit/glue/media/audio_decoder.h | 20 | ||||
-rw-r--r-- | webkit/glue/media/buffered_data_source.cc | 590 | ||||
-rw-r--r-- | webkit/glue/media/buffered_data_source.h | 219 | ||||
-rw-r--r-- | webkit/glue/media/buffered_data_source_unittest.cc | 548 | ||||
-rw-r--r-- | webkit/glue/media/buffered_resource_loader.cc | 578 | ||||
-rw-r--r-- | webkit/glue/media/buffered_resource_loader.h | 246 | ||||
-rw-r--r-- | webkit/glue/media/buffered_resource_loader_unittest.cc | 486 | ||||
-rw-r--r-- | webkit/glue/media/media_resource_loader_bridge_factory.cc | 82 | ||||
-rw-r--r-- | webkit/glue/media/media_resource_loader_bridge_factory.h | 76 | ||||
-rw-r--r-- | webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc | 44 | ||||
-rw-r--r-- | webkit/glue/media/mock_media_resource_loader_bridge_factory.h | 36 | ||||
-rw-r--r-- | webkit/glue/media/simple_data_source.cc | 142 | ||||
-rw-r--r-- | webkit/glue/media/simple_data_source.h | 76 | ||||
-rw-r--r-- | webkit/glue/media/simple_data_source_unittest.cc | 95 |
15 files changed, 1679 insertions, 1635 deletions
diff --git a/webkit/glue/media/audio_decoder.cc b/webkit/glue/media/audio_decoder.cc new file mode 100644 index 0000000..3fc05c9 --- /dev/null +++ b/webkit/glue/media/audio_decoder.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2010 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/audio_decoder.h" + +#include <vector> +#include "base/basictypes.h" +#include "base/string_util.h" +#include "base/time.h" +#include "media/filters/audio_file_reader.h" +#include "third_party/WebKit/WebKit/chromium/public/WebAudioBus.h" + +using media::AudioFileReader; +using media::InMemoryDataReader; +using std::vector; +using WebKit::WebAudioBus; + +namespace webkit_glue { + +// Decode in-memory audio file data. +bool DecodeAudioFileData( + WebKit::WebAudioBus* destination_bus, + const char* data, size_t data_size, double sample_rate) { + DCHECK(destination_bus); + if (!destination_bus) + return false; + + // Uses the FFmpeg library for audio file reading. + InMemoryDataReader data_reader(data, data_size); + AudioFileReader reader(&data_reader); + + if (!reader.Open()) + return false; + + size_t number_of_channels = reader.channels(); + double file_sample_rate = reader.sample_rate(); + double duration = reader.duration().InSecondsF(); + size_t number_of_frames = static_cast<size_t>(reader.number_of_frames()); + + // TODO(crogers) : do sample-rate conversion with FFmpeg. + // For now, we're ignoring the requested 'sample_rate' and returning + // the WebAudioBus at the file's sample-rate. + // double destination_sample_rate = + // (sample_rate != 0.0) ? sample_rate : file_sample_rate; + double destination_sample_rate = file_sample_rate; + + DLOG(INFO) << "Decoding file data -" + << " data: " << data + << " data size: " << data_size + << " duration: " << duration + << " number of frames: " << number_of_frames + << " sample rate: " << file_sample_rate + << " number of channels: " << number_of_channels; + + // Change to destination sample-rate. + number_of_frames = static_cast<size_t>(number_of_frames * + (destination_sample_rate / file_sample_rate)); + + // Allocate and configure the output audio channel data. + destination_bus->initialize(number_of_channels, + number_of_frames, + destination_sample_rate); + + // Wrap the channel pointers which will receive the decoded PCM audio. + vector<float*> audio_data; + audio_data.reserve(number_of_channels); + for (size_t i = 0; i < number_of_channels; ++i) { + audio_data.push_back(destination_bus->channelData(i)); + } + + // Decode the audio file data. + return reader.Read(audio_data, number_of_frames); +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/audio_decoder.h b/webkit/glue/media/audio_decoder.h new file mode 100644 index 0000000..57cc90b --- /dev/null +++ b/webkit/glue/media/audio_decoder.h @@ -0,0 +1,20 @@ +// Copyright (c) 2010 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. + +#ifndef WEBKIT_GLUE_MEDIA_AUDIO_DECODER_H_ +#define WEBKIT_GLUE_MEDIA_AUDIO_DECODER_H_ + +#include "base/basictypes.h" + +namespace WebKit { class WebAudioBus; } + +namespace webkit_glue { + +// Decode in-memory audio file data. +bool DecodeAudioFileData(WebKit::WebAudioBus* destination_bus, const char* data, + size_t data_size, double sample_rate); + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_MEDIA_AUDIO_DECODER_H_ diff --git a/webkit/glue/media/buffered_data_source.cc b/webkit/glue/media/buffered_data_source.cc index 29c86ac..b6efa16 100644 --- a/webkit/glue/media/buffered_data_source.cc +++ b/webkit/glue/media/buffered_data_source.cc @@ -2,46 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/callback.h" -#include "base/compiler_specific.h" -#include "base/message_loop.h" -#include "base/process_util.h" -#include "base/stl_util-inl.h" -#include "base/string_util.h" +#include "webkit/glue/media/buffered_data_source.h" + #include "media/base/filter_host.h" -#include "media/base/media_format.h" -#include "net/base/load_flags.h" #include "net/base/net_errors.h" -#include "net/http/http_response_headers.h" -#include "webkit/glue/media/buffered_data_source.h" #include "webkit/glue/webkit_glue.h" -#include "webkit/glue/webmediaplayer_impl.h" - -namespace { -const char kHttpScheme[] = "http"; -const char kHttpsScheme[] = "https"; -const char kDataScheme[] = "data"; -const int64 kPositionNotSpecified = -1; -const int kHttpOK = 200; -const int kHttpPartialContent = 206; +using WebKit::WebFrame; -// Define the number of bytes in a megabyte. -const size_t kMegabyte = 1024 * 1024; - -// Backward capacity of the buffer, by default 2MB. -const size_t kBackwardCapcity = 2 * kMegabyte; - -// Forward capacity of the buffer, by default 10MB. -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. -const int kForwardWaitThreshold = 2 * kMegabyte; +namespace { // Defines how long we should wait for more data before we declare a connection // timeout and start a new request. @@ -58,479 +27,18 @@ const int kReadTrials = 3; // of FFmpeg. const int kInitialReadBufferSize = 32768; -// Returns true if |url| operates on HTTP protocol. -bool IsHttpProtocol(const GURL& url) { - return url.SchemeIs(kHttpScheme) || url.SchemeIs(kHttpsScheme); -} - -bool IsDataProtocol(const GURL& url) { - return url.SchemeIs(kDataScheme); -} - -} // namespace +} // namespace namespace webkit_glue { -///////////////////////////////////////////////////////////////////////////// -// BufferedResourceLoader -BufferedResourceLoader::BufferedResourceLoader( - webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory, - const GURL& url, - int64 first_byte_position, - int64 last_byte_position) - : buffer_(new media::SeekableBuffer(kBackwardCapcity, kForwardCapacity)), - deferred_(false), - defer_allowed_(true), - completed_(false), - range_requested_(false), - partial_response_(false), - bridge_factory_(bridge_factory), - url_(url), - first_byte_position_(first_byte_position), - last_byte_position_(last_byte_position), - start_callback_(NULL), - bridge_(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) { -} - -BufferedResourceLoader::~BufferedResourceLoader() { -} - -void BufferedResourceLoader::Start(net::CompletionCallback* start_callback, - NetworkEventCallback* event_callback) { - // Make sure we have not started. - DCHECK(!bridge_.get()); - DCHECK(!start_callback_.get()); - DCHECK(!event_callback_.get()); - DCHECK(start_callback); - DCHECK(event_callback); - - start_callback_.reset(start_callback); - event_callback_.reset(event_callback); - - if (first_byte_position_ != kPositionNotSpecified) { - range_requested_ = true; - // TODO(hclam): server may not support range request so |offset_| may not - // equal to |first_byte_position_|. - offset_ = first_byte_position_; - } - - // Creates the bridge on render thread since we can only access - // ResourceDispatcher on this thread. - bridge_.reset( - bridge_factory_->CreateBridge( - url_, - net::LOAD_NORMAL, - first_byte_position_, - last_byte_position_)); - - // Increment the reference count right before we start the request. This - // reference will be release when this request has ended. - AddRef(); - - // And start the resource loading. - bridge_->Start(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 (bridge_.get()) { - // Cancel the request. This method call will cancel the request - // asynchronously. We may still get data or messages until we receive - // a response completed message. - if (deferred_) - bridge_->SetDefersLoading(false); - deferred_ = false; - bridge_->Cancel(); - } -} - -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(); - DisableDeferIfNeeded(); - return; - } - - // 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::GetBufferedFirstBytePosition() { - if (buffer_.get()) - return offset_ - static_cast<int>(buffer_->backward_bytes()); - return kPositionNotSpecified; -} - -int64 BufferedResourceLoader::GetBufferedLastBytePosition() { - if (buffer_.get()) - return offset_ + static_cast<int>(buffer_->forward_bytes()) - 1; - return kPositionNotSpecified; -} - -void BufferedResourceLoader::SetAllowDefer(bool is_allowed) { - defer_allowed_ = is_allowed; - DisableDeferIfNeeded(); -} - -///////////////////////////////////////////////////////////////////////////// -// BufferedResourceLoader, -// webkit_glue::ResourceLoaderBridge::Peer implementations -bool BufferedResourceLoader::OnReceivedRedirect( - const GURL& new_url, - const webkit_glue::ResourceResponseInfo& info, - bool* has_new_first_party_for_cookies, - GURL* new_first_party_for_cookies) { - DCHECK(bridge_.get()); - - // Save the new URL. - url_ = new_url; - // TODO(wtc): should we return a new first party for cookies URL? - *has_new_first_party_for_cookies = false; - - // The load may have been stopped and |start_callback| is destroyed. - // In this case we shouldn't do anything. - if (!start_callback_.get()) - return true; - - if (!IsProtocolSupportedForMedia(new_url)) { - DoneStart(net::ERR_ADDRESS_INVALID); - Stop(); - return false; - } - return true; -} - -void BufferedResourceLoader::OnReceivedResponse( - const webkit_glue::ResourceResponseInfo& info, - bool content_filtered) { - DCHECK(bridge_.get()); - - // The loader may have been stopped and |start_callback| is destroyed. - // In this case we shouldn't do anything. - if (!start_callback_.get()) - return; - - // 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 (IsHttpProtocol(url_)) { - int error = net::OK; - if (!info.headers) { - // We expect to receive headers because this is a HTTP or HTTPS protocol, - // if not report failure. - error = net::ERR_INVALID_RESPONSE; - } else { - if (info.headers->response_code() == kHttpPartialContent) - partial_response_ = true; - - if (range_requested_ && partial_response_) { - // If we have verified the partial response and it is correct, we will - // return net::OK. - if (!VerifyPartialResponse(info)) - error = net::ERR_INVALID_RESPONSE; - } else if (info.headers->response_code() != 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_; - } - - // |info.content_length| can be -1, in that case |content_length_| is - // not specified and this is a streaming response. - content_length_ = info.content_length; - - // 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::OnReceivedData(const char* data, int len) { - DCHECK(bridge_.get()); - - // 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), len); - - // If there is an active read request, try to fulfill the request. - if (HasPendingRead() && CanFulfillRead()) { - ReadInternal(); - } else if (!defer_allowed_) { - // If we're not allowed to defer, slide the buffer window forward instead - // of deferring. - 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; - } - } - - // At last see if the buffer is full and we need to defer the downloading. - EnableDeferIfNeeded(); - - // Notify that we have received some data. - NotifyNetworkEvent(); -} - -void BufferedResourceLoader::OnCompletedRequest( - const URLRequestStatus& status, - const std::string& security_info, - const base::Time& completion_time) { - DCHECK(bridge_.get()); - - // Saves the information that the request has completed. - completed_ = true; - - // If there is a start callback, calls it. - if (start_callback_.get()) { - DoneStart(status.os_error()); - } - - // 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()); - - if (status.is_success()) { - // Try to fulfill with what is in the buffer. - if (CanFulfillRead()) - ReadInternal(); - else - DoneRead(net::ERR_CACHE_MISS); - } else { - // If the request has failed, then fail the read. - DoneRead(net::ERR_FAILED); - } - } - - // There must not be any outstanding read request. - DCHECK(!HasPendingRead()); - - // Notify that network response is completed. - NotifyNetworkEvent(); - - // We incremented the reference count when the loader was started. We balance - // that reference here so that we get destroyed. This is also the only safe - // place to destroy the ResourceLoaderBridge. - bridge_.reset(); - Release(); -} - -///////////////////////////////////////////////////////////////////////////// -// BufferedResourceLoader, private -void BufferedResourceLoader::EnableDeferIfNeeded() { - if (!defer_allowed_) - return; - - if (!deferred_ && - buffer_->forward_bytes() >= buffer_->forward_capacity()) { - deferred_ = true; - - if (bridge_.get()) - bridge_->SetDefersLoading(true); - - NotifyNetworkEvent(); - } -} - -void BufferedResourceLoader::DisableDeferIfNeeded() { - if (deferred_ && - (!defer_allowed_ || - buffer_->forward_bytes() < buffer_->forward_capacity() / 2)) { - deferred_ = false; - - if (bridge_.get()) - bridge_->SetDefersLoading(false); - - NotifyNetworkEvent(); - } -} - -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 ResourceResponseInfo& info) { - int64 first_byte_position, last_byte_position, instance_size; - if (!info.headers->GetContentRange(&first_byte_position, - &last_byte_position, - &instance_size)) { - return false; - } - - if (instance_size != kPositionNotSpecified) - instance_size_ = instance_size; - - if (first_byte_position_ != -1 && - 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; -} - -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(); -} - -///////////////////////////////////////////////////////////////////////////// -// BufferedDataSource BufferedDataSource::BufferedDataSource( MessageLoop* render_loop, - webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory) + WebFrame* frame) : total_bytes_(kPositionNotSpecified), loaded_(false), streaming_(false), + frame_(frame), single_origin_(true), - bridge_factory_(bridge_factory), loader_(NULL), network_activity_(false), initialize_callback_(NULL), @@ -558,7 +66,7 @@ BufferedResourceLoader* BufferedDataSource::CreateResourceLoader( int64 first_byte_position, int64 last_byte_position) { DCHECK(MessageLoop::current() == render_loop_); - return new BufferedResourceLoader(bridge_factory_.get(), url_, + return new BufferedResourceLoader(url_, first_byte_position, last_byte_position); } @@ -571,7 +79,7 @@ base::TimeDelta BufferedDataSource::GetTimeoutMilliseconds() { } ///////////////////////////////////////////////////////////////////////////// -// BufferedDataSource, media::MediaFilter implementation +// media::Filter implementation. void BufferedDataSource::Initialize(const std::string& url, media::FilterCallback* callback) { // Saves the url. @@ -602,7 +110,7 @@ bool BufferedDataSource::IsUrlSupported(const std::string& url) { GURL gurl(url); // This data source doesn't support data:// protocol so reject it. - return IsProtocolSupportedForMedia(gurl) && !IsDataProtocol(gurl); + return IsProtocolSupportedForMedia(gurl) && !gurl.SchemeIs(kDataScheme); } void BufferedDataSource::Stop(media::FilterCallback* callback) { @@ -626,7 +134,7 @@ void BufferedDataSource::SetPlaybackRate(float playback_rate) { } ///////////////////////////////////////////////////////////////////////////// -// BufferedDataSource, media::DataSource implementation +// media::DataSource implementation. void BufferedDataSource::Read(int64 position, size_t size, uint8* data, media::DataSource::ReadCallback* read_callback) { render_loop_->PostTask(FROM_HERE, @@ -658,20 +166,21 @@ void BufferedDataSource::Abort() { // If we are told to abort, immediately return from any pending read // with an error. if (read_callback_.get()) { - { AutoLock auto_lock(lock_); DoneRead_Locked(net::ERR_FAILED); - } - CleanupTask(); } + + CleanupTask(); + frame_ = NULL; } ///////////////////////////////////////////////////////////////////////////// -// BufferedDataSource, render thread tasks +// Render thread tasks. void BufferedDataSource::InitializeTask() { DCHECK(MessageLoop::current() == render_loop_); DCHECK(!loader_.get()); - DCHECK(!stopped_on_render_loop_); + if (stopped_on_render_loop_) + return; // Kick starts the watch dog task that will handle connection timeout. // We run the watch dog 2 times faster the actual timeout so as to catch @@ -681,24 +190,24 @@ void BufferedDataSource::InitializeTask() { this, &BufferedDataSource::WatchDogTask); - if (IsHttpProtocol(url_)) { - // Fetch only first 1024 bytes as this usually covers the header portion - // of a media file that gives enough information about the codecs, etc. - // This also serve as a probe to determine server capability to serve - // range request. - // TODO(hclam): Do some experiments for the best approach. - loader_ = CreateResourceLoader(0, 1024); + if (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) { + // Do an unbounded range request starting at the beginning. If the server + // responds with 200 instead of 206 we'll fall back into a streaming mode. + loader_ = CreateResourceLoader(0, kPositionNotSpecified); loader_->Start( NewCallback(this, &BufferedDataSource::HttpInitialStartCallback), - NewCallback(this, &BufferedDataSource::NetworkEventCallback)); + NewCallback(this, &BufferedDataSource::NetworkEventCallback), + frame_); } else { // For all other protocols, assume they support range request. We fetch // the full range of the resource to obtain the instance size because // we won't be served HTTP headers. - loader_ = CreateResourceLoader(-1, -1); + loader_ = CreateResourceLoader(kPositionNotSpecified, + kPositionNotSpecified); loader_->Start( NewCallback(this, &BufferedDataSource::NonHttpInitialStartCallback), - NewCallback(this, &BufferedDataSource::NetworkEventCallback)); + NewCallback(this, &BufferedDataSource::NetworkEventCallback), + frame_); } } @@ -706,11 +215,6 @@ void BufferedDataSource::ReadTask( int64 position, int read_size, uint8* buffer, media::DataSource::ReadCallback* read_callback) { DCHECK(MessageLoop::current() == render_loop_); - - // If CleanupTask() was executed we should return immediately. We check this - // variable to prevent doing any actual work after clean up was done. We do - // not check |stop_signal_received_| because anything use of it has to be - // within |lock_| which is not desirable. if (stopped_on_render_loop_) return; @@ -731,8 +235,6 @@ void BufferedDataSource::ReadTask( void BufferedDataSource::CleanupTask() { DCHECK(MessageLoop::current() == render_loop_); - - // If we have already stopped, do nothing. if (stopped_on_render_loop_) return; @@ -757,13 +259,6 @@ void BufferedDataSource::CleanupTask() { void BufferedDataSource::RestartLoadingTask() { DCHECK(MessageLoop::current() == render_loop_); - - // This variable is set in CleanupTask(). We check this and do an early - // return. The sequence of actions which enable this conditions is: - // 1. Stop() is called from the pipeline. - // 2. ReadCallback() is called from the resource loader. - // 3. CleanupTask() is executed. - // 4. RestartLoadingTask() is executed. if (stopped_on_render_loop_) return; @@ -771,16 +266,18 @@ void BufferedDataSource::RestartLoadingTask() { if (!read_callback_.get()) return; - loader_ = CreateResourceLoader(read_position_, -1); + loader_ = CreateResourceLoader(read_position_, kPositionNotSpecified); loader_->SetAllowDefer(!media_is_paused_); loader_->Start( NewCallback(this, &BufferedDataSource::PartialReadStartCallback), - NewCallback(this, &BufferedDataSource::NetworkEventCallback)); + NewCallback(this, &BufferedDataSource::NetworkEventCallback), + frame_); } void BufferedDataSource::WatchDogTask() { DCHECK(MessageLoop::current() == render_loop_); - DCHECK(!stopped_on_render_loop_); + if (stopped_on_render_loop_) + return; // We only care if there is an active read request. if (!read_callback_.get()) @@ -802,11 +299,12 @@ void BufferedDataSource::WatchDogTask() { // Stops the current loader and creates a new resource loader and // retry the request. loader_->Stop(); - loader_ = CreateResourceLoader(read_position_, -1); + loader_ = CreateResourceLoader(read_position_, kPositionNotSpecified); loader_->SetAllowDefer(!media_is_paused_); loader_->Start( NewCallback(this, &BufferedDataSource::PartialReadStartCallback), - NewCallback(this, &BufferedDataSource::NetworkEventCallback)); + NewCallback(this, &BufferedDataSource::NetworkEventCallback), + frame_); } void BufferedDataSource::SetPlaybackRateTask(float playback_rate) { @@ -829,7 +327,7 @@ void BufferedDataSource::SetPlaybackRateTask(float playback_rate) { // prior to make this method call. void BufferedDataSource::ReadInternal() { DCHECK(MessageLoop::current() == render_loop_); - DCHECK(loader_.get()); + DCHECK(loader_); // First we prepare the intermediate read buffer for BufferedResourceLoader // to write to. @@ -872,9 +370,7 @@ void BufferedDataSource::DoneInitialization_Locked() { } ///////////////////////////////////////////////////////////////////////////// -// BufferedDataSource, callback methods. -// These methods are called on the render thread for the events reported by -// BufferedResourceLoader. +// BufferedResourceLoader callback methods. void BufferedDataSource::HttpInitialStartCallback(int error) { DCHECK(MessageLoop::current() == render_loop_); DCHECK(loader_.get()); @@ -901,10 +397,12 @@ void BufferedDataSource::HttpInitialStartCallback(int error) { // Assuming that the Range header was causing the problem. Retry without // the Range header. using_range_request_ = false; - loader_ = CreateResourceLoader(-1, -1); + loader_ = CreateResourceLoader(kPositionNotSpecified, + kPositionNotSpecified); loader_->Start( NewCallback(this, &BufferedDataSource::HttpInitialStartCallback), - NewCallback(this, &BufferedDataSource::NetworkEventCallback)); + NewCallback(this, &BufferedDataSource::NetworkEventCallback), + frame_); return; } @@ -1058,10 +556,10 @@ void BufferedDataSource::NetworkEventCallback() { return; bool network_activity = loader_->network_activity(); - int64 buffered_last_byte_position = loader_->GetBufferedLastBytePosition(); + int64 buffered_position = loader_->GetBufferedPosition(); // If we get an unspecified value, return immediately. - if (buffered_last_byte_position == kPositionNotSpecified) + if (buffered_position == kPositionNotSpecified) return; // We need to prevent calling to filter host and running the callback if @@ -1080,7 +578,7 @@ void BufferedDataSource::NetworkEventCallback() { network_activity_ = network_activity; host()->SetNetworkActivity(network_activity); } - host()->SetBufferedBytes(buffered_last_byte_position + 1); + host()->SetBufferedBytes(buffered_position + 1); } } // namespace webkit_glue diff --git a/webkit/glue/media/buffered_data_source.h b/webkit/glue/media/buffered_data_source.h index 2af9e84..1c4e3fa 100644 --- a/webkit/glue/media/buffered_data_source.h +++ b/webkit/glue/media/buffered_data_source.h @@ -10,220 +10,18 @@ #include "base/callback.h" #include "base/lock.h" #include "base/scoped_ptr.h" -#include "base/timer.h" -#include "base/condition_variable.h" -#include "googleurl/src/gurl.h" -#include "media/base/filters.h" -#include "media/base/media_format.h" -#include "media/base/pipeline.h" -#include "media/base/seekable_buffer.h" -#include "net/base/completion_callback.h" -#include "net/base/file_stream.h" -#include "webkit/glue/media/media_resource_loader_bridge_factory.h" -#include "webkit/glue/media/web_data_source.h" -#include "webkit/glue/webmediaplayer_impl.h" +#include "webkit/glue/media/buffered_resource_loader.h" namespace webkit_glue { -///////////////////////////////////////////////////////////////////////////// -// BufferedResourceLoader -// This class works inside demuxer thread and render thread. It contains a -// resource loader bridge and does the actual resource loading. This object -// does buffering internally, it defers the resource loading if buffer is -// full and un-defers the resource loading if it is under buffered. -class BufferedResourceLoader : - public base::RefCountedThreadSafe<BufferedResourceLoader>, - public webkit_glue::ResourceLoaderBridge::Peer { - public: - typedef Callback0::Type NetworkEventCallback; - - // |bridge_factory| - Factory to create a ResourceLoaderBridge. - // |url| - URL for the resource to be loaded. - // |first_byte_position| - First byte to start loading from, -1 for not - // specified. - // |last_byte_position| - Last byte to be loaded, -1 for not specified. - BufferedResourceLoader( - webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory, - const GURL& url, - int64 first_byte_position, - int64 last_byte_position); - - // Start the resource loading with the specified URL and range. - // This method operates in asynchronous mode. Once there's a response from the - // server, success or fail |callback| is called with the result. - // |callback| is called with the following values: - // - net::OK - // The request has started successfully. - // - net::ERR_FAILED - // The request has failed because of an error with the network. - // - net::ERR_INVALID_RESPONSE - // An invalid response is received from the server. - // - (Anything else) - // An error code that indicates the request has failed. - // |event_callback| is called when the response is completed, data is - // received, the request is suspended or resumed. - virtual void Start(net::CompletionCallback* callback, - NetworkEventCallback* event_callback); - - // Stop this loader, cancels and request and release internal buffer. - virtual void Stop(); - - // Reads the specified |read_size| from |position| into |buffer| and when - // the operation is done invoke |callback| with number of bytes read or an - // error code. - // |callback| is called with the following values: - // - (Anything greater than or equal 0) - // Read was successful with the indicated number of bytes read. - // - net::ERR_FAILED - // The read has failed because of an error with the network. - // - net::ERR_CACHE_MISS - // The read was made too far away from the current buffered position. - virtual void Read(int64 position, int read_size, - uint8* buffer, net::CompletionCallback* callback); - - // Returns the position of the first byte buffered. Returns -1 if such value - // is not available. - virtual int64 GetBufferedFirstBytePosition(); - - // Returns the position of the last byte buffered. Returns -1 if such value - // is not available. - virtual int64 GetBufferedLastBytePosition(); - - // Sets whether deferring data is allowed or disallowed. - virtual void SetAllowDefer(bool is_allowed); - - // Gets the content length in bytes of the instance after this loader has been - // started. If this value is -1, then content length is unknown. - virtual int64 content_length() { return content_length_; } - - // Gets the original size of the file requested. If this value is -1, then - // the size is unknown. - virtual int64 instance_size() { return instance_size_; } - - // Returns true if the response for this loader is a partial response. - // It means a 206 response in HTTP/HTTPS protocol. - virtual bool partial_response() { return partial_response_; } - - // Returns true if network is currently active. - virtual bool network_activity() { return !completed_ && !deferred_; } - - // Returns resulting URL. - virtual const GURL& url() { return url_; } - - ///////////////////////////////////////////////////////////////////////////// - // webkit_glue::ResourceLoaderBridge::Peer implementations. - virtual void OnUploadProgress(uint64 position, uint64 size) {} - virtual bool OnReceivedRedirect( - const GURL& new_url, - const webkit_glue::ResourceResponseInfo& info, - bool* has_new_first_party_for_cookies, - GURL* new_first_party_for_cookies); - virtual void OnReceivedResponse( - const webkit_glue::ResourceResponseInfo& info, - bool content_filtered); - virtual void OnDownloadedData(int len) {} - virtual void OnReceivedData(const char* data, int len); - virtual void OnCompletedRequest( - const URLRequestStatus& status, - const std::string& security_info, - const base::Time& completion_time); - - protected: - friend class base::RefCountedThreadSafe<BufferedResourceLoader>; - - virtual ~BufferedResourceLoader(); - - private: - friend class BufferedResourceLoaderTest; - - // Defer the resource loading if the buffer is full. - void EnableDeferIfNeeded(); - - // Disable defer loading if we are under-buffered. - void DisableDeferIfNeeded(); - - // Returns true if the current read request can be fulfilled by what is in - // the buffer. - bool CanFulfillRead(); - - // Returns true if the current read request will be fulfilled in the future. - bool WillFulfillRead(); - - // Method that does the actual read and calls the |read_callbac_|, assuming - // the request range is in |buffer_|. - void ReadInternal(); - - // If we have made a range request, verify the response from the server. - bool VerifyPartialResponse(const ResourceResponseInfo& info); - - // Done with read. Invokes the read callback and reset parameters for the - // read request. - void DoneRead(int error); - - // Done with start. Invokes the start callback and reset it. - void DoneStart(int error); - - // Calls |event_callback_| in terms of a network event. - void NotifyNetworkEvent(); - - bool HasPendingRead() { return read_callback_.get() != NULL; } - - // A sliding window of buffer. - scoped_ptr<media::SeekableBuffer> buffer_; - - // True if resource loading was deferred. - bool deferred_; - - // True if resource loader is allowed to defer, false otherwise. - bool defer_allowed_; - - // True if resource loading has completed. - bool completed_; - - // True if a range request was made. - bool range_requested_; - - // True if response data received is a partial range. - bool partial_response_; - - webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory_; - GURL url_; - int64 first_byte_position_; - int64 last_byte_position_; - - // Callback method that listens to network events. - scoped_ptr<NetworkEventCallback> event_callback_; - - // Members used during request start. - scoped_ptr<net::CompletionCallback> start_callback_; - scoped_ptr<webkit_glue::ResourceLoaderBridge> bridge_; - int64 offset_; - int64 content_length_; - int64 instance_size_; - - // Members used during a read operation. They should be reset after each - // read has completed or failed. - scoped_ptr<net::CompletionCallback> read_callback_; - int64 read_position_; - int read_size_; - uint8* read_buffer_; - - // Offsets of the requested first byte and last byte in |buffer_|. They are - // written by VerifyRead(). - int first_offset_; - int last_offset_; - - DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoader); -}; class BufferedDataSource : public WebDataSource { public: - BufferedDataSource( - MessageLoop* render_loop, - webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory); + BufferedDataSource(MessageLoop* render_loop, + WebKit::WebFrame* frame); virtual ~BufferedDataSource(); - // media::MediaFilter implementation. + // media::Filter implementation. virtual void Initialize(const std::string& url, media::FilterCallback* callback); virtual bool IsUrlSupported(const std::string& url); @@ -247,7 +45,6 @@ class BufferedDataSource : public WebDataSource { virtual void Abort(); protected: - // A factory method to create a BufferedResourceLoader based on the read // parameters. We can override this file to object a mock // BufferedResourceLoader for testing. @@ -338,12 +135,12 @@ class BufferedDataSource : public WebDataSource { // i.e. range request is not supported. bool streaming_; + // A webframe for loading. + WebKit::WebFrame* frame_; + // True if the media resource has a single origin. bool single_origin_; - // A factory object to produce ResourceLoaderBridge. - scoped_ptr<webkit_glue::MediaResourceLoaderBridgeFactory> bridge_factory_; - // A resource loader for the media resource. scoped_refptr<BufferedResourceLoader> loader_; @@ -385,7 +182,7 @@ class BufferedDataSource : public WebDataSource { bool stop_signal_received_; // This variable is set by CleanupTask() that indicates this object is stopped - // on the render thread. + // on the render thread and work should no longer progress. bool stopped_on_render_loop_; // This variable is true when we are in a paused state and false when we diff --git a/webkit/glue/media/buffered_data_source_unittest.cc b/webkit/glue/media/buffered_data_source_unittest.cc index 81103b2..dcb11ec 100644 --- a/webkit/glue/media/buffered_data_source_unittest.cc +++ b/webkit/glue/media/buffered_data_source_unittest.cc @@ -4,22 +4,18 @@ #include <algorithm> -#include "base/callback.h" -#include "base/format_macros.h" -#include "base/message_loop.h" -#include "base/string_util.h" -#include "base/stringprintf.h" -#include "media/base/filters.h" +#include "base/test/test_timeouts.h" #include "media/base/mock_filter_host.h" #include "media/base/mock_filters.h" #include "net/base/net_errors.h" -#include "net/http/http_response_headers.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLError.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" #include "webkit/glue/media/buffered_data_source.h" -#include "webkit/glue/media/mock_media_resource_loader_bridge_factory.h" -#include "webkit/glue/mock_resource_loader_bridge.h" +#include "webkit/mocks/mock_webframe.h" using ::testing::_; using ::testing::Assign; +using ::testing::AtLeast; using ::testing::DeleteArg; using ::testing::DoAll; using ::testing::InSequence; @@ -49,476 +45,36 @@ enum NetworkState { namespace webkit_glue { -// Submit a request completed event to the resource loader due to request -// being canceled. Pretending the event is from external. -ACTION_P(RequestCanceled, loader) { - URLRequestStatus status; - status.set_status(URLRequestStatus::CANCELED); - status.set_os_error(net::ERR_ABORTED); - loader->OnCompletedRequest(status, "", base::Time()); -} - -class BufferedResourceLoaderTest : public testing::Test { +// A mock BufferedDataSource to inject mock BufferedResourceLoader through +// CreateResourceLoader() method. +class MockBufferedDataSource : public BufferedDataSource { public: - BufferedResourceLoaderTest() { - bridge_.reset(new StrictMock<MockResourceLoaderBridge>()); - - for (int i = 0; i < kDataSize; ++i) - data_[i] = i; - } - - ~BufferedResourceLoaderTest() { - if (bridge_.get()) - EXPECT_CALL(*bridge_, OnDestroy()); - EXPECT_CALL(bridge_factory_, OnDestroy()); - } - - void Initialize(const char* url, int first_position, int last_position) { - gurl_ = GURL(url); - first_position_ = first_position; - last_position_ = last_position; - - loader_ = new BufferedResourceLoader(&bridge_factory_, gurl_, - first_position_, last_position_); - } - - void SetLoaderBuffer(size_t forward_capacity, size_t backward_capacity) { - loader_->buffer_.reset( - new media::SeekableBuffer(backward_capacity, forward_capacity)); - } - - void Start() { - InSequence s; - EXPECT_CALL(bridge_factory_, - CreateBridge(gurl_, _, first_position_, last_position_)) - .WillOnce(Return(bridge_.get())); - EXPECT_CALL(*bridge_, Start(loader_.get())); - loader_->Start( - NewCallback(this, &BufferedResourceLoaderTest::StartCallback), - NewCallback(this, &BufferedResourceLoaderTest::NetworkCallback)); - } - - void FullResponse(int64 instance_size) { - EXPECT_CALL(*this, StartCallback(net::OK)); - ResourceResponseInfo info; - std::string header = base::StringPrintf("HTTP/1.1 200 OK\n" - "Content-Length: %" PRId64, - instance_size); - replace(header.begin(), header.end(), '\n', '\0'); - info.headers = new net::HttpResponseHeaders(header); - info.content_length = instance_size; - loader_->OnReceivedResponse(info, false); - EXPECT_EQ(instance_size, loader_->content_length()); - EXPECT_EQ(instance_size, loader_->instance_size()); - EXPECT_FALSE(loader_->partial_response()); - } - - void PartialResponse(int64 first_position, int64 last_position, - int64 instance_size) { - EXPECT_CALL(*this, StartCallback(net::OK)); - int64 content_length = last_position - first_position + 1; - ResourceResponseInfo info; - std::string header = base::StringPrintf("HTTP/1.1 206 Partial Content\n" - "Content-Range: bytes " - "%" PRId64 "-%" PRId64 "/%" PRId64, - first_position, - last_position, - instance_size); - replace(header.begin(), header.end(), '\n', '\0'); - info.headers = new net::HttpResponseHeaders(header); - info.content_length = content_length; - loader_->OnReceivedResponse(info, false); - EXPECT_EQ(content_length, loader_->content_length()); - EXPECT_EQ(instance_size, loader_->instance_size()); - EXPECT_TRUE(loader_->partial_response()); - } - - void StopWhenLoad() { - InSequence s; - EXPECT_CALL(*bridge_, Cancel()) - .WillOnce(RequestCanceled(loader_)); - EXPECT_CALL(*bridge_, OnDestroy()) - .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); - loader_->Stop(); - } - - void ReleaseBridge() { - ignore_result(bridge_.release()); - } - - // Helper method to write to |loader_| from |data_|. - void WriteLoader(int position, int size) { - EXPECT_CALL(*this, NetworkCallback()) - .RetiresOnSaturation(); - loader_->OnReceivedData(reinterpret_cast<char*>(data_ + position), size); - } - - // Helper method to read from |loader_|. - void ReadLoader(int64 position, int size, uint8* buffer) { - loader_->Read(position, size, buffer, - NewCallback(this, &BufferedResourceLoaderTest::ReadCallback)); - } - - // Verifis that data in buffer[0...size] is equal to data_[pos...pos+size]. - void VerifyBuffer(uint8* buffer, int pos, int size) { - EXPECT_EQ(0, memcmp(buffer, data_ + pos, size)); - } - - // Helper method to disallow deferring in |loader_|. - void DisallowLoaderDefer() { - if (loader_->deferred_) { - EXPECT_CALL(*bridge_, SetDefersLoading(false)); - EXPECT_CALL(*this, NetworkCallback()); - } - loader_->SetAllowDefer(false); + MockBufferedDataSource( + MessageLoop* message_loop, WebFrame* frame) + : BufferedDataSource(message_loop, frame) { } - // Helper method to allow deferring in |loader_|. - void AllowLoaderDefer() { - loader_->SetAllowDefer(true); + virtual base::TimeDelta GetTimeoutMilliseconds() { + return base::TimeDelta::FromMilliseconds( + TestTimeouts::tiny_timeout_ms()); } - MOCK_METHOD1(StartCallback, void(int error)); - MOCK_METHOD1(ReadCallback, void(int error)); - MOCK_METHOD0(NetworkCallback, void()); - - protected: - GURL gurl_; - int64 first_position_; - int64 last_position_; - - scoped_refptr<BufferedResourceLoader> loader_; - StrictMock<MockMediaResourceLoaderBridgeFactory> bridge_factory_; - scoped_ptr<StrictMock<MockResourceLoaderBridge> > bridge_; - - uint8 data_[kDataSize]; + MOCK_METHOD2(CreateResourceLoader, + BufferedResourceLoader*(int64 first_position, + int64 last_position)); private: - DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoaderTest); + DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSource); }; -TEST_F(BufferedResourceLoaderTest, StartStop) { - Initialize(kHttpUrl, -1, -1); - Start(); - StopWhenLoad(); -} - -// Tests that HTTP header is missing in the response. -TEST_F(BufferedResourceLoaderTest, MissingHttpHeader) { - Initialize(kHttpUrl, -1, -1); - Start(); - - EXPECT_CALL(*this, StartCallback(net::ERR_INVALID_RESPONSE)); - EXPECT_CALL(*bridge_, Cancel()) - .WillOnce(RequestCanceled(loader_)); - EXPECT_CALL(*bridge_, OnDestroy()) - .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); - - ResourceResponseInfo info; - loader_->OnReceivedResponse(info, false); -} - -// Tests that a bad HTTP response is recived, e.g. file not found. -TEST_F(BufferedResourceLoaderTest, BadHttpResponse) { - Initialize(kHttpUrl, -1, -1); - Start(); - - EXPECT_CALL(*this, StartCallback(net::ERR_FAILED)); - EXPECT_CALL(*bridge_, Cancel()) - .WillOnce(RequestCanceled(loader_)); - EXPECT_CALL(*bridge_, OnDestroy()) - .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); - - ResourceResponseInfo info; - info.headers = new net::HttpResponseHeaders("HTTP/1.1 404 Not Found\n"); - loader_->OnReceivedResponse(info, false); -} - -// Tests that partial content is requested but not fulfilled. -TEST_F(BufferedResourceLoaderTest, NotPartialResponse) { - Initialize(kHttpUrl, 100, -1); - Start(); - FullResponse(1024); - StopWhenLoad(); -} - -// Tests that a 200 response is received. -TEST_F(BufferedResourceLoaderTest, FullResponse) { - Initialize(kHttpUrl, -1, -1); - Start(); - FullResponse(1024); - StopWhenLoad(); -} - -// Tests that a partial content response is received. -TEST_F(BufferedResourceLoaderTest, PartialResponse) { - Initialize(kHttpUrl, 100, 200); - Start(); - PartialResponse(100, 200, 1024); - StopWhenLoad(); -} - -// Tests that an invalid partial response is received. -TEST_F(BufferedResourceLoaderTest, InvalidPartialResponse) { - Initialize(kHttpUrl, 0, 10); - Start(); - - EXPECT_CALL(*this, StartCallback(net::ERR_INVALID_RESPONSE)); - EXPECT_CALL(*bridge_, Cancel()) - .WillOnce(RequestCanceled(loader_)); - EXPECT_CALL(*bridge_, OnDestroy()) - .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); - - ResourceResponseInfo info; - std::string header = base::StringPrintf("HTTP/1.1 206 Partial Content\n" - "Content-Range: bytes %d-%d/%d", - 1, 10, 1024); - replace(header.begin(), header.end(), '\n', '\0'); - info.headers = new net::HttpResponseHeaders(header); - info.content_length = 10; - loader_->OnReceivedResponse(info, false); -} - -// Tests the logic of sliding window for data buffering and reading. -TEST_F(BufferedResourceLoaderTest, BufferAndRead) { - Initialize(kHttpUrl, 10, 29); - Start(); - PartialResponse(10, 29, 30); - - uint8 buffer[10]; - InSequence s; - - // Writes 10 bytes and read them back. - WriteLoader(10, 10); - EXPECT_CALL(*this, ReadCallback(10)); - ReadLoader(10, 10, buffer); - VerifyBuffer(buffer, 10, 10); - - // Writes 10 bytes and read 2 times. - WriteLoader(20, 10); - EXPECT_CALL(*this, ReadCallback(5)); - ReadLoader(20, 5, buffer); - VerifyBuffer(buffer, 20, 5); - EXPECT_CALL(*this, ReadCallback(5)); - ReadLoader(25, 5, buffer); - VerifyBuffer(buffer, 25, 5); - - // Read backward within buffer. - EXPECT_CALL(*this, ReadCallback(10)); - ReadLoader(10, 10, buffer); - VerifyBuffer(buffer, 10, 10); - - // Read backward outside buffer. - EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); - ReadLoader(9, 10, buffer); - - // Response has completed. - EXPECT_CALL(*this, NetworkCallback()); - EXPECT_CALL(*bridge_, OnDestroy()) - .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); - URLRequestStatus status; - status.set_status(URLRequestStatus::SUCCESS); - loader_->OnCompletedRequest(status, "", base::Time()); - - // Try to read 10 from position 25 will just return with 5 bytes. - EXPECT_CALL(*this, ReadCallback(5)); - ReadLoader(25, 10, buffer); - VerifyBuffer(buffer, 25, 5); - - // Try to read outside buffered range after request has completed. - EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); - ReadLoader(5, 10, buffer); - - // Try to read beyond the instance size. - EXPECT_CALL(*this, ReadCallback(0)); - ReadLoader(30, 10, buffer); -} - -TEST_F(BufferedResourceLoaderTest, ReadOutsideBuffer) { - Initialize(kHttpUrl, 10, 0x00FFFFFF); - Start(); - PartialResponse(10, 0x00FFFFFF, 0x01000000); - - uint8 buffer[10]; - InSequence s; - - // Read very far aheard will get a cache miss. - EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); - ReadLoader(0x00FFFFFF, 1, buffer); - - // The following call will not call ReadCallback() because it is waiting for - // data to arrive. - ReadLoader(10, 10, buffer); - - // Writing to loader will fulfill the read request. - EXPECT_CALL(*this, ReadCallback(10)); - WriteLoader(10, 20); - VerifyBuffer(buffer, 10, 10); - - // The following call cannot be fulfilled now. - ReadLoader(25, 10, buffer); - - EXPECT_CALL(*this, ReadCallback(5)); - EXPECT_CALL(*this, NetworkCallback()); - EXPECT_CALL(*bridge_, OnDestroy()) - .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); - URLRequestStatus status; - status.set_status(URLRequestStatus::SUCCESS); - loader_->OnCompletedRequest(status, "", base::Time()); -} - -TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) { - Initialize(kHttpUrl, 10, 29); - Start(); - PartialResponse(10, 29, 30); - - uint8 buffer[10]; - InSequence s; - - ReadLoader(10, 10, buffer); - EXPECT_CALL(*this, ReadCallback(net::ERR_FAILED)); - EXPECT_CALL(*this, NetworkCallback()); - EXPECT_CALL(*bridge_, OnDestroy()) - .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); - URLRequestStatus status; - status.set_status(URLRequestStatus::FAILED); - loader_->OnCompletedRequest(status, "", base::Time()); -} - -// Tests the logic of caching data to disk when media is paused. -TEST_F(BufferedResourceLoaderTest, AllowDefer_NoDataReceived) { - Initialize(kHttpUrl, 10, 99); - SetLoaderBuffer(10, 20); - Start(); - PartialResponse(10, 99, 100); - - // Start in undeferred state, then disallow defer, then allow defer - // without receiving data in between. - DisallowLoaderDefer(); - AllowLoaderDefer(); - StopWhenLoad(); -} - -TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadSameWindow) { - Initialize(kHttpUrl, 10, 99); - SetLoaderBuffer(10, 20); - Start(); - PartialResponse(10, 99, 100); - - uint8 buffer[10]; - - // Start in undeferred state, disallow defer, receive data but don't shift - // buffer window, then allow defer and read. - DisallowLoaderDefer(); - WriteLoader(10, 10); - AllowLoaderDefer(); - - EXPECT_CALL(*this, ReadCallback(10)); - ReadLoader(10, 10, buffer); - VerifyBuffer(buffer, 10, 10); - StopWhenLoad(); -} - -TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadPastWindow) { - Initialize(kHttpUrl, 10, 99); - SetLoaderBuffer(10, 20); - Start(); - PartialResponse(10, 99, 100); - - uint8 buffer[10]; - - // Not deferred, disallow defer, received data and shift buffer window, - // allow defer, then read in area outside of buffer window. - DisallowLoaderDefer(); - WriteLoader(10, 10); - WriteLoader(20, 50); - AllowLoaderDefer(); - - EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); - ReadLoader(10, 10, buffer); - StopWhenLoad(); -} - -TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredNoDataReceived) { - Initialize(kHttpUrl, 10, 99); - SetLoaderBuffer(10, 20); - Start(); - PartialResponse(10, 99, 100); - - uint8 buffer[10]; - - // Start in deferred state, then disallow defer, receive no data, and - // allow defer and read. - EXPECT_CALL(*bridge_, SetDefersLoading(true)); - EXPECT_CALL(*this, NetworkCallback()); - WriteLoader(10, 40); - - DisallowLoaderDefer(); - AllowLoaderDefer(); - - EXPECT_CALL(*this, ReadCallback(10)); - ReadLoader(20, 10, buffer); - VerifyBuffer(buffer, 20, 10); - StopWhenLoad(); -} - -TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadSameWindow) { - Initialize(kHttpUrl, 10, 99); - SetLoaderBuffer(10, 20); - Start(); - PartialResponse(10, 99, 100); - - uint8 buffer[10]; - - // Start in deferred state, disallow defer, receive data and shift buffer - // window, allow defer, and read in a place that's still in the window. - EXPECT_CALL(*bridge_, SetDefersLoading(true)); - EXPECT_CALL(*this, NetworkCallback()); - WriteLoader(10, 30); - - DisallowLoaderDefer(); - WriteLoader(40, 5); - AllowLoaderDefer(); - - EXPECT_CALL(*this, ReadCallback(10)); - ReadLoader(20, 10, buffer); - VerifyBuffer(buffer, 20, 10); - StopWhenLoad(); -} - -TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadPastWindow) { - Initialize(kHttpUrl, 10, 99); - SetLoaderBuffer(10, 20); - Start(); - PartialResponse(10, 99, 100); - - uint8 buffer[10]; - - // Start in deferred state, disallow defer, receive data and shift buffer - // window, allow defer, and read outside of the buffer window. - EXPECT_CALL(*bridge_, SetDefersLoading(true)); - EXPECT_CALL(*this, NetworkCallback()); - WriteLoader(10, 40); - - DisallowLoaderDefer(); - WriteLoader(50, 20); - WriteLoader(70, 40); - AllowLoaderDefer(); - - EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); - ReadLoader(20, 5, buffer); - StopWhenLoad(); -} - -// TODO(hclam): add unit test for defer loading. - class MockBufferedResourceLoader : public BufferedResourceLoader { public: - MockBufferedResourceLoader() : BufferedResourceLoader(NULL, GURL(), 0, 0) { + MockBufferedResourceLoader() : BufferedResourceLoader(GURL(), 0, 0) { } - MOCK_METHOD2(Start, void(net::CompletionCallback* read_callback, - NetworkEventCallback* network_callback)); + MOCK_METHOD3(Start, void(net::CompletionCallback* read_callback, + NetworkEventCallback* network_callback, + WebFrame* frame)); MOCK_METHOD0(Stop, void()); MOCK_METHOD4(Read, void(int64 position, int read_size, uint8* buffer, net::CompletionCallback* callback)); @@ -536,33 +92,10 @@ class MockBufferedResourceLoader : public BufferedResourceLoader { DISALLOW_COPY_AND_ASSIGN(MockBufferedResourceLoader); }; -// A mock BufferedDataSource to inject mock BufferedResourceLoader through -// CreateResourceLoader() method. -class MockBufferedDataSource : public BufferedDataSource { - public: - MockBufferedDataSource( - MessageLoop* message_loop, MediaResourceLoaderBridgeFactory* factory) - : BufferedDataSource(message_loop, factory) { - } - - virtual base::TimeDelta GetTimeoutMilliseconds() { - // It is 100 ms because we don't want the test to run too long. - return base::TimeDelta::FromMilliseconds(100); - } - - MOCK_METHOD2(CreateResourceLoader, BufferedResourceLoader*( - int64 first_position, int64 last_position)); - - private: - DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSource); -}; - class BufferedDataSourceTest : public testing::Test { public: BufferedDataSourceTest() { message_loop_ = MessageLoop::current(); - bridge_factory_.reset( - new StrictMock<MockMediaResourceLoaderBridgeFactory>()); // Prepare test data. for (size_t i = 0; i < sizeof(data_); ++i) { @@ -571,20 +104,14 @@ class BufferedDataSourceTest : public testing::Test { } virtual ~BufferedDataSourceTest() { - if (data_source_) { - // Release the bridge factory because we don't own it. - // Expects bridge factory to be destroyed along with data source. - EXPECT_CALL(*bridge_factory_, OnDestroy()) - .WillOnce(Invoke(this, - &BufferedDataSourceTest::ReleaseBridgeFactory)); - } + ignore_result(frame_.release()); } void ExpectCreateAndStartResourceLoader(int start_error) { EXPECT_CALL(*data_source_, CreateResourceLoader(_, _)) .WillOnce(Return(loader_.get())); - EXPECT_CALL(*loader_, Start(NotNull(), NotNull())) + EXPECT_CALL(*loader_, Start(NotNull(), NotNull(), NotNull())) .WillOnce( DoAll(Assign(&error_, start_error), Invoke(this, @@ -597,15 +124,10 @@ class BufferedDataSourceTest : public testing::Test { // Saves the url first. gurl_ = GURL(url); - media::MediaFormat url_format; - url_format.SetAsString(media::MediaFormat::kMimeType, - media::mime_type::kURL); - url_format.SetAsString(media::MediaFormat::kURL, url); - data_source_ = new MockBufferedDataSource(MessageLoop::current(), - bridge_factory_.get()); - CHECK(data_source_); + frame_.reset(new NiceMock<MockWebFrame>()); - // There is no need to provide a message loop to data source. + data_source_ = new MockBufferedDataSource(MessageLoop::current(), + frame_.get()); data_source_->set_host(&host_); scoped_refptr<NiceMock<MockBufferedResourceLoader> > first_loader( @@ -631,7 +153,7 @@ class BufferedDataSourceTest : public testing::Test { // Replace loader_ with a new instance. loader_ = new NiceMock<MockBufferedResourceLoader>(); - // Create and start Make sure Start() is called the new loader. + // Create and start. Make sure Start() is called on the new loader. ExpectCreateAndStartResourceLoader(net::OK); // Update initialization variable since we know the second loader will @@ -705,13 +227,10 @@ class BufferedDataSourceTest : public testing::Test { message_loop_->RunAllPending(); } - void ReleaseBridgeFactory() { - ignore_result(bridge_factory_.release()); - } - void InvokeStartCallback( net::CompletionCallback* callback, - BufferedResourceLoader::NetworkEventCallback* network_callback) { + BufferedResourceLoader::NetworkEventCallback* network_callback, + WebFrame* frame) { callback->RunWithParams(Tuple1<int>(error_)); delete callback; // TODO(hclam): Save this callback. @@ -790,7 +309,7 @@ class BufferedDataSourceTest : public testing::Test { .WillOnce(Return(new_loader)); // 3. Then the new loader will be started. - EXPECT_CALL(*new_loader, Start(NotNull(), NotNull())) + EXPECT_CALL(*new_loader, Start(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(Assign(&error_, net::OK), Invoke(this, &BufferedDataSourceTest::InvokeStartCallback))); @@ -856,7 +375,7 @@ class BufferedDataSourceTest : public testing::Test { // 3. Then the new loader will be started and respond to queries about // whether this is a partial response using the value of the previous // loader. - EXPECT_CALL(*new_loader, Start(NotNull(), NotNull())) + EXPECT_CALL(*new_loader, Start(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(Assign(&error_, net::OK), Invoke(this, &BufferedDataSourceTest::InvokeStartCallback))); @@ -889,10 +408,9 @@ class BufferedDataSourceTest : public testing::Test { MOCK_METHOD1(ReadCallback, void(size_t size)); - scoped_ptr<StrictMock<MockMediaResourceLoaderBridgeFactory> > - bridge_factory_; scoped_refptr<NiceMock<MockBufferedResourceLoader> > loader_; scoped_refptr<MockBufferedDataSource> data_source_; + scoped_ptr<NiceMock<MockWebFrame> > frame_; StrictMock<media::MockFilterHost> host_; GURL gurl_; diff --git a/webkit/glue/media/buffered_resource_loader.cc b/webkit/glue/media/buffered_resource_loader.cc new file mode 100644 index 0000000..61aac72 --- /dev/null +++ b/webkit/glue/media/buffered_resource_loader.cc @@ -0,0 +1,578 @@ +// Copyright (c) 2010 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/WebKit/chromium/public/WebKit.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKitClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/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 { + +const int kHttpOK = 200; +const int kHttpPartialContent = 206; + +// Define the number of bytes in a megabyte. +const size_t kMegabyte = 1024 * 1024; + +// Backward capacity of the buffer, by default 2MB. +const size_t kBackwardCapcity = 2 * kMegabyte; + +// Forward capacity of the buffer, by default 10MB. +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. +const int kForwardWaitThreshold = 2 * kMegabyte; + +} // namespace + +namespace webkit_glue { + +BufferedResourceLoader::BufferedResourceLoader( + const GURL& url, + int64 first_byte_position, + int64 last_byte_position) + : buffer_(new media::SeekableBuffer(kBackwardCapcity, kForwardCapacity)), + deferred_(false), + defer_allowed_(true), + completed_(false), + range_requested_(false), + partial_response_(false), + url_(url), + first_byte_position_(first_byte_position), + last_byte_position_(last_byte_position), + 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) { + range_requested_ = true; + // 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); + 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(); + DisableDeferIfNeeded(); + return; + } + + // 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; +} + +void BufferedResourceLoader::SetAllowDefer(bool is_allowed) { + defer_allowed_ = is_allowed; + DisableDeferIfNeeded(); +} + +int64 BufferedResourceLoader::content_length() { + return content_length_; +} + +int64 BufferedResourceLoader::instance_size() { + return instance_size_; +} + +bool BufferedResourceLoader::partial_response() { + return partial_response_; +} + +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; + } + + 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); + Stop(); + 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; + + // 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; + + if (response.httpStatusCode() == kHttpPartialContent) + partial_response_ = true; + + if (range_requested_ && partial_response_) { + // If we have verified the partial response and it is correct, we will + // return net::OK. + if (!VerifyPartialResponse(response)) + 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) { + 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(); + } else if (!defer_allowed_) { + // If we're not allowed to defer, slide the buffer window forward instead + // of deferring. + 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; + } + } + + // At last see if the buffer is full and we need to defer the downloading. + EnableDeferIfNeeded(); + + // 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 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(); +} + +///////////////////////////////////////////////////////////////////////////// +// Helper methods. +void BufferedResourceLoader::EnableDeferIfNeeded() { + if (!defer_allowed_) + return; + + if (!deferred_ && + buffer_->forward_bytes() >= buffer_->forward_capacity()) { + deferred_ = true; + + if (url_loader_.get()) + url_loader_->setDefersLoading(true); + + NotifyNetworkEvent(); + } +} + +void BufferedResourceLoader::DisableDeferIfNeeded() { + if (deferred_ && + (!defer_allowed_ || + buffer_->forward_bytes() < buffer_->forward_capacity() / 2)) { + deferred_ = false; + + if (url_loader_.get()) + url_loader_->setDefersLoading(false); + + NotifyNetworkEvent(); + } +} + +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(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/buffered_resource_loader.h b/webkit/glue/media/buffered_resource_loader.h new file mode 100644 index 0000000..09b05e6 --- /dev/null +++ b/webkit/glue/media/buffered_resource_loader.h @@ -0,0 +1,246 @@ +// Copyright (c) 2010 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. + +#ifndef WEBKIT_GLUE_MEDIA_BUFFERED_RESOURCE_LOADER_H_ +#define WEBKIT_GLUE_MEDIA_BUFFERED_RESOURCE_LOADER_H_ + +#include <string> + +#include "base/callback.h" +#include "base/lock.h" +#include "base/scoped_ptr.h" +#include "base/timer.h" +#include "googleurl/src/gurl.h" +#include "media/base/seekable_buffer.h" +#include "net/base/file_stream.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoader.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoaderClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "webkit/glue/media/web_data_source.h" +#include "webkit/glue/webmediaplayer_impl.h" + +namespace webkit_glue { + +const int64 kPositionNotSpecified = -1; + +const char kHttpScheme[] = "http"; +const char kHttpsScheme[] = "https"; +const char kDataScheme[] = "data"; + +// This class works inside demuxer thread and render thread. It contains a +// WebURLLoader and does the actual resource loading. This object does +// buffering internally, it defers the resource loading if buffer is full +// and un-defers the resource loading if it is under buffered. +class BufferedResourceLoader : + public base::RefCountedThreadSafe<BufferedResourceLoader>, + public WebKit::WebURLLoaderClient { + public: + typedef Callback0::Type NetworkEventCallback; + + // |url| - URL for the resource to be loaded. + // |first_byte_position| - First byte to start loading from, + // |kPositionNotSpecified| for not specified. + // |last_byte_position| - Last byte to be loaded, + // |kPositionNotSpecified| for not specified. + BufferedResourceLoader(const GURL& url, + int64 first_byte_position, + int64 last_byte_position); + + // Start the resource loading with the specified URL and range. + // This method operates in asynchronous mode. Once there's a response from the + // server, success or fail |callback| is called with the result. + // |callback| is called with the following values: + // - net::OK + // The request has started successfully. + // - net::ERR_FAILED + // The request has failed because of an error with the network. + // - net::ERR_INVALID_RESPONSE + // An invalid response is received from the server. + // - (Anything else) + // An error code that indicates the request has failed. + // |event_callback| is called when the response is completed, data is + // received, the request is suspended or resumed. + virtual void Start(net::CompletionCallback* callback, + NetworkEventCallback* event_callback, + WebKit::WebFrame* frame); + + // Stop this loader, cancels and request and release internal buffer. + virtual void Stop(); + + // Reads the specified |read_size| from |position| into |buffer| and when + // the operation is done invoke |callback| with number of bytes read or an + // error code. + // |callback| is called with the following values: + // - (Anything greater than or equal 0) + // Read was successful with the indicated number of bytes read. + // - net::ERR_FAILED + // The read has failed because of an error with the network. + // - net::ERR_CACHE_MISS + // The read was made too far away from the current buffered position. + virtual void Read(int64 position, int read_size, + uint8* buffer, net::CompletionCallback* callback); + + // Returns the position of the last byte buffered. Returns + // |kPositionNotSpecified| if such value is not available. + virtual int64 GetBufferedPosition(); + + // Sets whether deferring data is allowed or disallowed. + virtual void SetAllowDefer(bool is_allowed); + + // Gets the content length in bytes of the instance after this loader has been + // started. If this value is |kPositionNotSpecified|, then content length is + // unknown. + virtual int64 content_length(); + + // Gets the original size of the file requested. If this value is + // |kPositionNotSpecified|, then the size is unknown. + virtual int64 instance_size(); + + // Returns true if the response for this loader is a partial response. + // It means a 206 response in HTTP/HTTPS protocol. + virtual bool partial_response(); + + // Returns true if network is currently active. + virtual bool network_activity(); + + // Returns resulting URL. + virtual const GURL& url(); + + // Used to inject a mock used for unittests. + virtual void SetURLLoaderForTest(WebKit::WebURLLoader* mock_loader); + + // WebKit::WebURLLoaderClient implementation. + virtual void willSendRequest( + WebKit::WebURLLoader* loader, + WebKit::WebURLRequest& newRequest, + const WebKit::WebURLResponse& redirectResponse); + virtual void didSendData( + WebKit::WebURLLoader* loader, + unsigned long long bytesSent, + unsigned long long totalBytesToBeSent); + virtual void didReceiveResponse( + WebKit::WebURLLoader* loader, + const WebKit::WebURLResponse& response); + virtual void didDownloadData( + WebKit::WebURLLoader* loader, + int dataLength); + virtual void didReceiveData( + WebKit::WebURLLoader* loader, + const char* data, + int dataLength); + virtual void didReceiveCachedMetadata( + WebKit::WebURLLoader* loader, + const char* data, int dataLength); + virtual void didFinishLoading( + WebKit::WebURLLoader* loader, + double finishTime); + virtual void didFail( + WebKit::WebURLLoader* loader, + const WebKit::WebURLError&); + + protected: + friend class base::RefCountedThreadSafe<BufferedResourceLoader>; + + virtual ~BufferedResourceLoader(); + + private: + friend class BufferedResourceLoaderTest; + + // Defer the resource loading if the buffer is full. + void EnableDeferIfNeeded(); + + // Disable defer loading if we are under-buffered. + void DisableDeferIfNeeded(); + + // Returns true if the current read request can be fulfilled by what is in + // the buffer. + bool CanFulfillRead(); + + // Returns true if the current read request will be fulfilled in the future. + bool WillFulfillRead(); + + // Method that does the actual read and calls the |read_callback_|, assuming + // the request range is in |buffer_|. + void ReadInternal(); + + // If we have made a range request, verify the response from the server. + bool VerifyPartialResponse(const WebKit::WebURLResponse& response); + + // Returns the value for a range request header using parameters + // |first_byte_position| and |last_byte_position|. Negative numbers other + // than |kPositionNotSpecified| are not allowed for |first_byte_position| and + // |last_byte_position|. |first_byte_position| should always be less than or + // equal to |last_byte_position| if they are both not |kPositionNotSpecified|. + // Empty string is returned on invalid parameters. + std::string GenerateHeaders(int64 first_byte_position, + int64 last_byte_position); + + // Done with read. Invokes the read callback and reset parameters for the + // read request. + void DoneRead(int error); + + // Done with start. Invokes the start callback and reset it. + void DoneStart(int error); + + // Calls |event_callback_| in terms of a network event. + void NotifyNetworkEvent(); + + bool HasPendingRead() { return read_callback_.get() != NULL; } + + // A sliding window of buffer. + scoped_ptr<media::SeekableBuffer> buffer_; + + // True if resource loading was deferred. + bool deferred_; + + // True if resource loader is allowed to defer, false otherwise. + bool defer_allowed_; + + // True if resource loading has completed. + bool completed_; + + // True if a range request was made. + bool range_requested_; + + // True if response data received is a partial range. + bool partial_response_; + + // Does the work of loading and sends data back to this client. + scoped_ptr<WebKit::WebURLLoader> url_loader_; + + GURL url_; + int64 first_byte_position_; + int64 last_byte_position_; + + // Callback method that listens to network events. + scoped_ptr<NetworkEventCallback> event_callback_; + + // Members used during request start. + scoped_ptr<net::CompletionCallback> start_callback_; + int64 offset_; + int64 content_length_; + int64 instance_size_; + + // Members used during a read operation. They should be reset after each + // read has completed or failed. + scoped_ptr<net::CompletionCallback> read_callback_; + int64 read_position_; + int read_size_; + uint8* read_buffer_; + + // Offsets of the requested first byte and last byte in |buffer_|. They are + // written by Read(). + int first_offset_; + int last_offset_; + + // Used to ensure mocks for unittests are used instead of reset in Start(). + bool keep_test_loader_; + + DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoader); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_MEDIA_BUFFERED_RESOURCE_LOADER_H_ diff --git a/webkit/glue/media/buffered_resource_loader_unittest.cc b/webkit/glue/media/buffered_resource_loader_unittest.cc new file mode 100644 index 0000000..8e64c26 --- /dev/null +++ b/webkit/glue/media/buffered_resource_loader_unittest.cc @@ -0,0 +1,486 @@ +// Copyright (c) 2010 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 <algorithm> + +#include "base/format_macros.h" +#include "base/stringprintf.h" +#include "net/base/net_errors.h" +#include "net/http/http_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrameClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLError.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/media/buffered_resource_loader.h" +#include "webkit/mocks/mock_webframe.h" +#include "webkit/mocks/mock_weburlloader.h" + +using ::testing::_; +using ::testing::Assign; +using ::testing::AtLeast; +using ::testing::DeleteArg; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::SetArgumentPointee; +using ::testing::StrictMock; +using ::testing::NiceMock; +using ::testing::WithArgs; + +using WebKit::WebURLError; +using WebKit::WebFrameClient; +using WebKit::WebURLResponse; +using WebKit::WebView; + +namespace { + +const char* kHttpUrl = "http://test"; +const int kDataSize = 1024; +const int kHttpOK = 200; +const int kHttpPartialContent = 206; + +enum NetworkState { + NONE, + LOADED, + LOADING +}; + +} // namespace + +namespace webkit_glue { + +// Submit a request completed event to the resource loader due to request +// being canceled. Pretending the event is from external. +ACTION_P(RequestCanceled, loader) { + WebURLError error; + error.reason = net::ERR_ABORTED; + error.domain = WebString::fromUTF8(net::kErrorDomain); + loader->didFail(NULL, error); +} + +class BufferedResourceLoaderTest : public testing::Test { + public: + BufferedResourceLoaderTest() { + url_loader_ = new NiceMock<MockWebURLLoader>(); + + for (int i = 0; i < kDataSize; ++i) + data_[i] = i; + } + + virtual ~BufferedResourceLoaderTest() { + ignore_result(frame_.release()); + } + + void Initialize(const char* url, int first_position, int last_position) { + gurl_ = GURL(url); + first_position_ = first_position; + last_position_ = last_position; + + frame_.reset(new NiceMock<MockWebFrame>()); + + loader_ = new BufferedResourceLoader(gurl_, + first_position_, last_position_); + loader_->SetURLLoaderForTest(url_loader_); + } + + void SetLoaderBuffer(size_t forward_capacity, size_t backward_capacity) { + loader_->buffer_.reset( + new media::SeekableBuffer(backward_capacity, forward_capacity)); + } + + void Start() { + InSequence s; + EXPECT_CALL(*url_loader_, loadAsynchronously(_, loader_.get())); + loader_->Start( + NewCallback(this, &BufferedResourceLoaderTest::StartCallback), + NewCallback(this, &BufferedResourceLoaderTest::NetworkCallback), + frame_.get()); + } + + void FullResponse(int64 instance_size) { + EXPECT_CALL(*this, StartCallback(net::OK)); + + WebURLResponse response(gurl_); + response.setHTTPHeaderField(WebString::fromUTF8("Content-Length"), + WebString::fromUTF8(base::StringPrintf("%" + PRId64, instance_size))); + response.setExpectedContentLength(instance_size); + response.setHTTPStatusCode(kHttpOK); + loader_->didReceiveResponse(url_loader_, response); + EXPECT_EQ(instance_size, loader_->content_length()); + EXPECT_EQ(instance_size, loader_->instance_size()); + EXPECT_FALSE(loader_->partial_response()); + } + + void PartialResponse(int64 first_position, int64 last_position, + int64 instance_size) { + EXPECT_CALL(*this, StartCallback(net::OK)); + int64 content_length = last_position - first_position + 1; + + WebURLResponse response(gurl_); + response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"), + WebString::fromUTF8(base::StringPrintf("bytes " + "%" PRId64 "-%" PRId64 "/%" PRId64, + first_position, + last_position, + instance_size))); + response.setExpectedContentLength(content_length); + response.setHTTPStatusCode(kHttpPartialContent); + loader_->didReceiveResponse(url_loader_, response); + EXPECT_EQ(content_length, loader_->content_length()); + EXPECT_EQ(instance_size, loader_->instance_size()); + EXPECT_TRUE(loader_->partial_response()); + } + + void StopWhenLoad() { + InSequence s; + EXPECT_CALL(*url_loader_, cancel()) + .WillOnce(RequestCanceled(loader_)); + loader_->Stop(); + } + + // Helper method to write to |loader_| from |data_|. + void WriteLoader(int position, int size) { + EXPECT_CALL(*this, NetworkCallback()) + .RetiresOnSaturation(); + loader_->didReceiveData(url_loader_, + reinterpret_cast<char*>(data_ + position), size); + } + + // Helper method to read from |loader_|. + void ReadLoader(int64 position, int size, uint8* buffer) { + loader_->Read(position, size, buffer, + NewCallback(this, &BufferedResourceLoaderTest::ReadCallback)); + } + + // Verifis that data in buffer[0...size] is equal to data_[pos...pos+size]. + void VerifyBuffer(uint8* buffer, int pos, int size) { + EXPECT_EQ(0, memcmp(buffer, data_ + pos, size)); + } + + // Helper method to disallow deferring in |loader_|. + void DisallowLoaderDefer() { + if (loader_->deferred_) { + EXPECT_CALL(*url_loader_, setDefersLoading(false)); + EXPECT_CALL(*this, NetworkCallback()); + } + loader_->SetAllowDefer(false); + } + + // Helper method to allow deferring in |loader_|. + void AllowLoaderDefer() { + loader_->SetAllowDefer(true); + } + + MOCK_METHOD1(StartCallback, void(int error)); + MOCK_METHOD1(ReadCallback, void(int error)); + MOCK_METHOD0(NetworkCallback, void()); + + protected: + GURL gurl_; + int64 first_position_; + int64 last_position_; + + scoped_refptr<BufferedResourceLoader> loader_; + NiceMock<MockWebURLLoader>* url_loader_; + scoped_ptr<NiceMock<MockWebFrame> > frame_; + + uint8 data_[kDataSize]; + + private: + DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoaderTest); +}; + +TEST_F(BufferedResourceLoaderTest, StartStop) { + Initialize(kHttpUrl, -1, -1); + Start(); + StopWhenLoad(); +} + +// Tests that a bad HTTP response is recived, e.g. file not found. +TEST_F(BufferedResourceLoaderTest, BadHttpResponse) { + Initialize(kHttpUrl, -1, -1); + Start(); + + EXPECT_CALL(*this, StartCallback(net::ERR_FAILED)); + EXPECT_CALL(*url_loader_, cancel()) + .WillOnce(RequestCanceled(loader_)); + + WebURLResponse response(gurl_); + response.setHTTPStatusCode(404); + response.setHTTPStatusText("Not Found\n"); + loader_->didReceiveResponse(url_loader_, response); +} + +// Tests that partial content is requested but not fulfilled. +TEST_F(BufferedResourceLoaderTest, NotPartialResponse) { + Initialize(kHttpUrl, 100, -1); + Start(); + FullResponse(1024); + StopWhenLoad(); +} + +// Tests that a 200 response is received. +TEST_F(BufferedResourceLoaderTest, FullResponse) { + Initialize(kHttpUrl, -1, -1); + Start(); + FullResponse(1024); + StopWhenLoad(); +} + +// Tests that a partial content response is received. +TEST_F(BufferedResourceLoaderTest, PartialResponse) { + Initialize(kHttpUrl, 100, 200); + Start(); + PartialResponse(100, 200, 1024); + StopWhenLoad(); +} + +// Tests that an invalid partial response is received. +TEST_F(BufferedResourceLoaderTest, InvalidPartialResponse) { + Initialize(kHttpUrl, 0, 10); + Start(); + + EXPECT_CALL(*this, StartCallback(net::ERR_INVALID_RESPONSE)); + EXPECT_CALL(*url_loader_, cancel()) + .WillOnce(RequestCanceled(loader_)); + + WebURLResponse response(gurl_); + response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"), + WebString::fromUTF8(base::StringPrintf("bytes " + "%d-%d/%d", 1, 10, 1024))); + response.setExpectedContentLength(10); + response.setHTTPStatusCode(kHttpPartialContent); + loader_->didReceiveResponse(url_loader_, response); +} + +// Tests the logic of sliding window for data buffering and reading. +TEST_F(BufferedResourceLoaderTest, BufferAndRead) { + Initialize(kHttpUrl, 10, 29); + Start(); + PartialResponse(10, 29, 30); + + uint8 buffer[10]; + InSequence s; + + // Writes 10 bytes and read them back. + WriteLoader(10, 10); + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(10, 10, buffer); + VerifyBuffer(buffer, 10, 10); + + // Writes 10 bytes and read 2 times. + WriteLoader(20, 10); + EXPECT_CALL(*this, ReadCallback(5)); + ReadLoader(20, 5, buffer); + VerifyBuffer(buffer, 20, 5); + EXPECT_CALL(*this, ReadCallback(5)); + ReadLoader(25, 5, buffer); + VerifyBuffer(buffer, 25, 5); + + // Read backward within buffer. + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(10, 10, buffer); + VerifyBuffer(buffer, 10, 10); + + // Read backward outside buffer. + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(9, 10, buffer); + + // Response has completed. + EXPECT_CALL(*this, NetworkCallback()); + loader_->didFinishLoading(url_loader_, 0); + + // Try to read 10 from position 25 will just return with 5 bytes. + EXPECT_CALL(*this, ReadCallback(5)); + ReadLoader(25, 10, buffer); + VerifyBuffer(buffer, 25, 5); + + // Try to read outside buffered range after request has completed. + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(5, 10, buffer); + + // Try to read beyond the instance size. + EXPECT_CALL(*this, ReadCallback(0)); + ReadLoader(30, 10, buffer); +} + +TEST_F(BufferedResourceLoaderTest, ReadOutsideBuffer) { + Initialize(kHttpUrl, 10, 0x00FFFFFF); + Start(); + PartialResponse(10, 0x00FFFFFF, 0x01000000); + + uint8 buffer[10]; + InSequence s; + + // Read very far aheard will get a cache miss. + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(0x00FFFFFF, 1, buffer); + + // The following call will not call ReadCallback() because it is waiting for + // data to arrive. + ReadLoader(10, 10, buffer); + + // Writing to loader will fulfill the read request. + EXPECT_CALL(*this, ReadCallback(10)); + WriteLoader(10, 20); + VerifyBuffer(buffer, 10, 10); + + // The following call cannot be fulfilled now. + ReadLoader(25, 10, buffer); + + EXPECT_CALL(*this, ReadCallback(5)); + EXPECT_CALL(*this, NetworkCallback()); + loader_->didFinishLoading(url_loader_, 0); +} + +TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) { + Initialize(kHttpUrl, 10, 29); + Start(); + PartialResponse(10, 29, 30); + + uint8 buffer[10]; + InSequence s; + + ReadLoader(10, 10, buffer); + EXPECT_CALL(*this, ReadCallback(net::ERR_FAILED)); + EXPECT_CALL(*this, NetworkCallback()); + WebURLError error; + error.reason = net::ERR_FAILED; + loader_->didFail(url_loader_, error); +} + +// Tests the logic of caching data to disk when media is paused. +TEST_F(BufferedResourceLoaderTest, AllowDefer_NoDataReceived) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + // Start in undeferred state, then disallow defer, then allow defer + // without receiving data in between. + DisallowLoaderDefer(); + AllowLoaderDefer(); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadSameWindow) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Start in undeferred state, disallow defer, receive data but don't shift + // buffer window, then allow defer and read. + DisallowLoaderDefer(); + WriteLoader(10, 10); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(10, 10, buffer); + VerifyBuffer(buffer, 10, 10); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadPastWindow) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Not deferred, disallow defer, received data and shift buffer window, + // allow defer, then read in area outside of buffer window. + DisallowLoaderDefer(); + WriteLoader(10, 10); + WriteLoader(20, 50); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(10, 10, buffer); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredNoDataReceived) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Start in deferred state, then disallow defer, receive no data, and + // allow defer and read. + EXPECT_CALL(*url_loader_, setDefersLoading(true)); + EXPECT_CALL(*this, NetworkCallback()); + WriteLoader(10, 40); + + DisallowLoaderDefer(); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(20, 10, buffer); + VerifyBuffer(buffer, 20, 10); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadSameWindow) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Start in deferred state, disallow defer, receive data and shift buffer + // window, allow defer, and read in a place that's still in the window. + EXPECT_CALL(*url_loader_, setDefersLoading(true)); + EXPECT_CALL(*this, NetworkCallback()); + WriteLoader(10, 30); + + DisallowLoaderDefer(); + WriteLoader(40, 5); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(20, 10, buffer); + VerifyBuffer(buffer, 20, 10); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadPastWindow) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Start in deferred state, disallow defer, receive data and shift buffer + // window, allow defer, and read outside of the buffer window. + EXPECT_CALL(*url_loader_, setDefersLoading(true)); + EXPECT_CALL(*this, NetworkCallback()); + WriteLoader(10, 40); + + DisallowLoaderDefer(); + WriteLoader(50, 20); + WriteLoader(70, 40); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(20, 5, buffer); + StopWhenLoad(); +} +// TODO(hclam): add unit test for defer loading. + +} // namespace webkit_glue + diff --git a/webkit/glue/media/media_resource_loader_bridge_factory.cc b/webkit/glue/media/media_resource_loader_bridge_factory.cc deleted file mode 100644 index 3fb9d65..0000000 --- a/webkit/glue/media/media_resource_loader_bridge_factory.cc +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2010 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/media_resource_loader_bridge_factory.h" - -#include "base/format_macros.h" -#include "base/string_util.h" -#include "base/stringprintf.h" - -namespace { - -// A constant for an unknown position. -const int64 kPositionNotSpecified = -1; - -} // namespace - -namespace webkit_glue { - -MediaResourceLoaderBridgeFactory::MediaResourceLoaderBridgeFactory( - const GURL& referrer, - const std::string& frame_origin, - const std::string& main_frame_origin, - int origin_pid, - int appcache_host_id, - int32 routing_id) - : referrer_(referrer), - frame_origin_(frame_origin), - main_frame_origin_(main_frame_origin), - origin_pid_(origin_pid), - appcache_host_id_(appcache_host_id), - routing_id_(routing_id) { -} - -MediaResourceLoaderBridgeFactory::~MediaResourceLoaderBridgeFactory() {} - -ResourceLoaderBridge* MediaResourceLoaderBridgeFactory::CreateBridge( - const GURL& url, - int load_flags, - int64 first_byte_position, - int64 last_byte_position) { - webkit_glue::ResourceLoaderBridge::RequestInfo request_info; - request_info.method = "GET"; - request_info.url = url; - request_info.first_party_for_cookies = url; - request_info.referrer = referrer_; - request_info.frame_origin = frame_origin_; - request_info.main_frame_origin = main_frame_origin_; - request_info.headers = GenerateHeaders(first_byte_position, - last_byte_position); - request_info.load_flags = load_flags; - request_info.requestor_pid = origin_pid_; - request_info.request_type = ResourceType::MEDIA; - request_info.appcache_host_id = appcache_host_id_; - request_info.routing_id = routing_id_; - return webkit_glue::ResourceLoaderBridge::Create(request_info); -} - -MediaResourceLoaderBridgeFactory::MediaResourceLoaderBridgeFactory() {} - -// static -const std::string MediaResourceLoaderBridgeFactory::GenerateHeaders ( - int64 first_byte_position, int64 last_byte_position) { - // Construct 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("Range: bytes=%" PRId64 "-%" PRId64, - first_byte_position, - last_byte_position); - } - } else if (first_byte_position > kPositionNotSpecified) { - header = base::StringPrintf("Range: bytes=%" PRId64 "-", - first_byte_position); - } else if (last_byte_position > kPositionNotSpecified) { - NOTIMPLEMENTED() << "Suffix range not implemented"; - } - return header; -} - -} // namespace webkit_glue diff --git a/webkit/glue/media/media_resource_loader_bridge_factory.h b/webkit/glue/media/media_resource_loader_bridge_factory.h deleted file mode 100644 index ccacdc6..0000000 --- a/webkit/glue/media/media_resource_loader_bridge_factory.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2009 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. - -#ifndef WEBKIT_GLUE_MEDIA_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ -#define WEBKIT_GLUE_MEDIA_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ - -#include "base/gtest_prod_util.h" -#include "webkit/glue/resource_loader_bridge.h" - -namespace webkit_glue { - -// A factory used to create a ResourceLoaderBridge for the media player. -// This factory is used also for testing. Testing code can use this class and -// override CreateBridge() to inject a mock ResourceLoaderBridge for code that -// interacts with it, e.g. BufferedDataSource. -class MediaResourceLoaderBridgeFactory { - public: - MediaResourceLoaderBridgeFactory( - const GURL& referrer, - const std::string& frame_origin, - const std::string& main_frame_origin, - int origin_pid, - int appcache_host_id, - int32 routing_id); - - virtual ~MediaResourceLoaderBridgeFactory(); - - // Factory method to create a ResourceLoaderBridge with the following - // parameters: - // |url| - URL of the resource to be loaded. - // |load_flags| - Load flags for this loading. - // |first_byte_position| - First byte position for a range request, -1 if not. - // |last_byte_position| - Last byte position for a range request, -1 if not. - virtual ResourceLoaderBridge* CreateBridge( - const GURL& url, - int load_flags, - int64 first_byte_position, - int64 last_byte_position); - - protected: - // An empty constructor only used by inherited classes. - MediaResourceLoaderBridgeFactory(); - - private: - FRIEND_TEST_ALL_PREFIXES(MediaResourceLoaderBridgeFactoryTest, - GenerateHeaders); - - // Returns a range request header using parameters |first_byte_position| and - // |last_byte_position|. - // Negative numbers other than -1 are not allowed for |first_byte_position| - // and |last_byte_position|. |first_byte_position| should always be less than - // or equal to |last_byte_position| if they are both not -1. - // Here's a list of valid parameters: - // |first_byte_position| |last_byte_position| - // 0 1000 - // 4096 4096 - // 0 -1 - // -1 -1 - // Empty string is returned on invalid parameters. - static const std::string GenerateHeaders(int64 first_byte_position, - int64 last_byte_position); - - GURL first_party_for_cookies_; - GURL referrer_; - std::string frame_origin_; - std::string main_frame_origin_; - std::string headers_; - int origin_pid_; - int appcache_host_id_; - int32 routing_id_; -}; - -} // namespace webkit_glue - -#endif // WEBKIT_GLUE_MEDIA_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ diff --git a/webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc b/webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc deleted file mode 100644 index 4c0126b..0000000 --- a/webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2009 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 "net/http/http_util.h" -#include "webkit/glue/media/media_resource_loader_bridge_factory.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace webkit_glue { - -TEST(MediaResourceLoaderBridgeFactoryTest, GenerateHeaders) { - static const struct { - const bool success; - const int64 first_byte_position; - const int64 last_byte_position; - } data[] = { - { false, -1, -1 }, - { false, -5, 0 }, - { false, 100, 0 }, - { true, 0, -1 }, - { true, 0, 0 }, - { true, 100, 100 }, - { true, 50, -1 }, - { true, 10000, -1 }, - { true, 50, 100 }, - }; - - for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { - std::string headers = MediaResourceLoaderBridgeFactory::GenerateHeaders( - data[i].first_byte_position, data[i].last_byte_position); - std::vector<net::HttpByteRange> ranges; - bool ret = net::HttpUtil::ParseRanges(headers, &ranges); - EXPECT_EQ(data[i].success, ret); - if (ret) { - EXPECT_EQ(1u, ranges.size()); - EXPECT_EQ(data[i].first_byte_position, - ranges[0].first_byte_position()); - EXPECT_EQ(data[i].last_byte_position, - ranges[0].last_byte_position()); - } - } -} - -} // namespace webkit_glue diff --git a/webkit/glue/media/mock_media_resource_loader_bridge_factory.h b/webkit/glue/media/mock_media_resource_loader_bridge_factory.h deleted file mode 100644 index 3c0a3ae..0000000 --- a/webkit/glue/media/mock_media_resource_loader_bridge_factory.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2010 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. - -#ifndef WEBKIT_GLUE_MEDIA_MOCK_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ -#define WEBKIT_GLUE_MEDIA_MOCK_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ - -#include "testing/gmock/include/gmock/gmock.h" -#include "webkit/glue/media/media_resource_loader_bridge_factory.h" - -namespace webkit_glue { - -class MockMediaResourceLoaderBridgeFactory - : public webkit_glue::MediaResourceLoaderBridgeFactory { - public: - MockMediaResourceLoaderBridgeFactory() { - } - - virtual ~MockMediaResourceLoaderBridgeFactory() { - OnDestroy(); - } - - MOCK_METHOD4(CreateBridge, - webkit_glue::ResourceLoaderBridge*(const GURL& url, - int load_flags, - int64 first_byte_position, - int64 last_byte_position)); - MOCK_METHOD0(OnDestroy, void()); - - private: - DISALLOW_COPY_AND_ASSIGN(MockMediaResourceLoaderBridgeFactory); -}; - -} // namespace webkit_glue - -#endif // WEBKIT_GLUE_MEDIA_MOCK_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ diff --git a/webkit/glue/media/simple_data_source.cc b/webkit/glue/media/simple_data_source.cc index 291928e..8869e28 100644 --- a/webkit/glue/media/simple_data_source.cc +++ b/webkit/glue/media/simple_data_source.cc @@ -2,40 +2,35 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "webkit/glue/media/simple_data_source.h" + #include "base/message_loop.h" #include "base/process_util.h" #include "media/base/filter_host.h" -#include "net/base/load_flags.h" #include "net/base/data_url.h" -#include "net/http/http_response_headers.h" +#include "net/base/load_flags.h" #include "net/url_request/url_request_status.h" -#include "webkit/glue/media/simple_data_source.h" -#include "webkit/glue/resource_loader_bridge.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKit.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKitClient.h" #include "webkit/glue/webkit_glue.h" namespace { -const char kHttpScheme[] = "http"; -const char kHttpsScheme[] = "https"; const char kDataScheme[] = "data"; -// A helper method that accepts only HTTP, HTTPS and FILE protocol. -bool IsDataProtocol(const GURL& url) { - return url.SchemeIs(kDataScheme); -} - } // namespace namespace webkit_glue { SimpleDataSource::SimpleDataSource( MessageLoop* render_loop, - webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory) + WebKit::WebFrame* frame) : render_loop_(render_loop), - bridge_factory_(bridge_factory), + frame_(frame), size_(-1), single_origin_(true), - state_(UNINITIALIZED) { + state_(UNINITIALIZED), + keep_test_loader_(false) { DCHECK(render_loop); } @@ -108,34 +103,59 @@ bool SimpleDataSource::IsStreaming() { return false; } -bool SimpleDataSource::OnReceivedRedirect( - const GURL& new_url, - const webkit_glue::ResourceResponseInfo& info, - bool* has_new_first_party_for_cookies, - GURL* new_first_party_for_cookies) { +void SimpleDataSource::SetURLLoaderForTest(WebKit::WebURLLoader* mock_loader) { + url_loader_.reset(mock_loader); + keep_test_loader_ = true; +} + +void SimpleDataSource::willSendRequest( + WebKit::WebURLLoader* loader, + WebKit::WebURLRequest& newRequest, + const WebKit::WebURLResponse& redirectResponse) { DCHECK(MessageLoop::current() == render_loop_); - single_origin_ = url_.GetOrigin() == new_url.GetOrigin(); + single_origin_ = url_.GetOrigin() == GURL(newRequest.url()).GetOrigin(); - // TODO(wtc): should we return a new first party for cookies URL? - *has_new_first_party_for_cookies = false; - return true; + url_ = newRequest.url(); } -void SimpleDataSource::OnReceivedResponse( - const webkit_glue::ResourceResponseInfo& info, - bool content_filtered) { +void SimpleDataSource::didSendData( + WebKit::WebURLLoader* loader, + unsigned long long bytesSent, + unsigned long long totalBytesToBeSent) { + NOTIMPLEMENTED(); +} + +void SimpleDataSource::didReceiveResponse( + WebKit::WebURLLoader* loader, + const WebKit::WebURLResponse& response) { DCHECK(MessageLoop::current() == render_loop_); - size_ = info.content_length; + size_ = response.expectedContentLength(); +} + +void SimpleDataSource::didDownloadData( + WebKit::WebURLLoader* loader, + int dataLength) { + NOTIMPLEMENTED(); } -void SimpleDataSource::OnReceivedData(const char* data, int len) { +void SimpleDataSource::didReceiveData( + WebKit::WebURLLoader* loader, + const char* data, + int data_length) { DCHECK(MessageLoop::current() == render_loop_); - data_.append(data, len); + data_.append(data, data_length); } -void SimpleDataSource::OnCompletedRequest(const URLRequestStatus& status, - const std::string& security_info, - const base::Time& completion_time) { +void SimpleDataSource::didReceiveCachedMetadata( + WebKit::WebURLLoader* loader, + const char* data, + int dataLength) { + NOTIMPLEMENTED(); +} + +void SimpleDataSource::didFinishLoading( + WebKit::WebURLLoader* loader, + double finishTime) { DCHECK(MessageLoop::current() == render_loop_); AutoLock auto_lock(lock_); // It's possible this gets called after Stop(), in which case |host_| is no @@ -143,10 +163,8 @@ void SimpleDataSource::OnCompletedRequest(const URLRequestStatus& status, if (state_ == STOPPED) return; - // Otherwise we should be initializing and have created a bridge. + // Otherwise we should be initializing and have created a WebURLLoader. DCHECK_EQ(state_, INITIALIZING); - DCHECK(bridge_.get()); - bridge_.reset(); // If we don't get a content length or the request has failed, report it // as a network error. @@ -154,7 +172,29 @@ void SimpleDataSource::OnCompletedRequest(const URLRequestStatus& status, size_ = data_.length(); DCHECK(static_cast<size_t>(size_) == data_.length()); - DoneInitialization_Locked(status.is_success()); + DoneInitialization_Locked(true); +} + +void SimpleDataSource::didFail( + WebKit::WebURLLoader* loader, + const WebKit::WebURLError& error) { + DCHECK(MessageLoop::current() == render_loop_); + AutoLock auto_lock(lock_); + // It's possible this gets called after Stop(), in which case |host_| is no + // longer valid. + if (state_ == STOPPED) + return; + + // Otherwise we should be initializing and have created a WebURLLoader. + DCHECK_EQ(state_, INITIALIZING); + + // If we don't get a content length or the request has failed, report it + // as a network error. + if (size_ == -1) + size_ = data_.length(); + DCHECK(static_cast<size_t>(size_) == data_.length()); + + DoneInitialization_Locked(false); } bool SimpleDataSource::HasSingleOrigin() { @@ -164,7 +204,7 @@ bool SimpleDataSource::HasSingleOrigin() { void SimpleDataSource::Abort() { DCHECK(MessageLoop::current() == render_loop_); - NOTIMPLEMENTED(); + frame_ = NULL; } void SimpleDataSource::SetURL(const GURL& url) { @@ -183,9 +223,11 @@ void SimpleDataSource::StartTask() { if (state_ == STOPPED) return; + CHECK(frame_); + DCHECK_EQ(state_, INITIALIZING); - if (IsDataProtocol(url_)) { + if (url_.SchemeIs(kDataScheme)) { // If this using data protocol, we just need to decode it. std::string mime_type, charset; bool success = net::DataURL::Parse(url_, &mime_type, &charset, &data_); @@ -194,10 +236,18 @@ void SimpleDataSource::StartTask() { size_ = data_.length(); DoneInitialization_Locked(success); } else { - // Create our bridge and start loading the resource. - bridge_.reset(bridge_factory_->CreateBridge( - url_, net::LOAD_BYPASS_CACHE, -1, -1)); - bridge_->Start(this); + // Prepare the request. + WebKit::WebURLRequest request(url_); + request.setTargetType(WebKit::WebURLRequest::TargetIsMedia); + + 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); } } @@ -207,9 +257,9 @@ void SimpleDataSource::CancelTask() { DCHECK_EQ(state_, STOPPED); // Cancel any pending requests. - if (bridge_.get()) { - bridge_->Cancel(); - bridge_.reset(); + if (url_loader_.get()) { + url_loader_->cancel(); + url_loader_.reset(); } } @@ -220,7 +270,7 @@ void SimpleDataSource::DoneInitialization_Locked(bool success) { host()->SetTotalBytes(size_); host()->SetBufferedBytes(size_); // If scheme is file or data, say we are loaded. - host()->SetLoaded(url_.SchemeIsFile() || IsDataProtocol(url_)); + host()->SetLoaded(url_.SchemeIsFile() || url_.SchemeIs(kDataScheme)); } else { host()->SetError(media::PIPELINE_ERROR_NETWORK); } diff --git a/webkit/glue/media/simple_data_source.h b/webkit/glue/media/simple_data_source.h index ff1e247..f4c83e6 100644 --- a/webkit/glue/media/simple_data_source.h +++ b/webkit/glue/media/simple_data_source.h @@ -10,10 +10,17 @@ #ifndef WEBKIT_GLUE_MEDIA_SIMPLE_DATA_SOURCE_H_ #define WEBKIT_GLUE_MEDIA_SIMPLE_DATA_SOURCE_H_ +#include <algorithm> +#include <string> + #include "base/message_loop.h" #include "base/scoped_ptr.h" #include "media/base/filters.h" -#include "webkit/glue/media/media_resource_loader_bridge_factory.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoader.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoaderClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" #include "webkit/glue/media/web_data_source.h" class MessageLoop; @@ -22,17 +29,15 @@ class WebMediaPlayerDelegateImpl; namespace webkit_glue { class SimpleDataSource : public WebDataSource, - public webkit_glue::ResourceLoaderBridge::Peer { + public WebKit::WebURLLoaderClient { public: - SimpleDataSource( - MessageLoop* render_loop, - webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory); + SimpleDataSource(MessageLoop* render_loop, WebKit::WebFrame* frame); virtual ~SimpleDataSource(); - // MediaFilter implementation. + // media::Filter implementation. virtual void Stop(media::FilterCallback* callback); - // DataSource implementation. + // media::DataSource implementation. virtual void Initialize(const std::string& url, media::FilterCallback* callback); virtual const media::MediaFormat& media_format(); @@ -41,21 +46,37 @@ class SimpleDataSource : public WebDataSource, virtual bool GetSize(int64* size_out); virtual bool IsStreaming(); - // webkit_glue::ResourceLoaderBridge::Peer implementation. - virtual void OnUploadProgress(uint64 position, uint64 size) {} - virtual bool OnReceivedRedirect( - const GURL& new_url, - const webkit_glue::ResourceResponseInfo& info, - bool* has_new_first_party_for_cookies, - GURL* new_first_party_for_cookies); - virtual void OnReceivedResponse( - const webkit_glue::ResourceResponseInfo& info, - bool content_filtered); - virtual void OnDownloadedData(int len) {} - virtual void OnReceivedData(const char* data, int len); - virtual void OnCompletedRequest(const URLRequestStatus& status, - const std::string& security_info, - const base::Time& completion_time); + // Used to inject a mock used for unittests. + virtual void SetURLLoaderForTest(WebKit::WebURLLoader* mock_loader); + + // WebKit::WebURLLoaderClient implementations. + virtual void willSendRequest( + WebKit::WebURLLoader* loader, + WebKit::WebURLRequest& newRequest, + const WebKit::WebURLResponse& redirectResponse); + virtual void didSendData( + WebKit::WebURLLoader* loader, + unsigned long long bytesSent, + unsigned long long totalBytesToBeSent); + virtual void didReceiveResponse( + WebKit::WebURLLoader* loader, + const WebKit::WebURLResponse& response); + virtual void didDownloadData( + WebKit::WebURLLoader* loader, + int dataLength); + virtual void didReceiveData( + WebKit::WebURLLoader* loader, + const char* data, + int dataLength); + virtual void didReceiveCachedMetadata( + WebKit::WebURLLoader* loader, + const char* data, int dataLength); + virtual void didFinishLoading( + WebKit::WebURLLoader* loader, + double finishTime); + virtual void didFail( + WebKit::WebURLLoader* loader, + const WebKit::WebURLError&); // webkit_glue::WebDataSource implementation. virtual bool HasSingleOrigin(); @@ -77,11 +98,11 @@ class SimpleDataSource : public WebDataSource, // Primarily used for asserting the bridge is loading on the render thread. MessageLoop* render_loop_; - // Factory to create a bridge. - scoped_ptr<webkit_glue::MediaResourceLoaderBridgeFactory> bridge_factory_; + // A webframe for loading. + WebKit::WebFrame* frame_; - // Bridge used to load the media resource. - scoped_ptr<webkit_glue::ResourceLoaderBridge> bridge_; + // Does the work of loading and sends data back to this client. + scoped_ptr<WebKit::WebURLLoader> url_loader_; media::MediaFormat media_format_; GURL url_; @@ -104,6 +125,9 @@ class SimpleDataSource : public WebDataSource, // Filter callbacks. scoped_ptr<media::FilterCallback> initialize_callback_; + // Used to ensure mocks for unittests are used instead of reset in Start(). + bool keep_test_loader_; + DISALLOW_COPY_AND_ASSIGN(SimpleDataSource); }; diff --git a/webkit/glue/media/simple_data_source_unittest.cc b/webkit/glue/media/simple_data_source_unittest.cc index 537798f..55dc913 100644 --- a/webkit/glue/media/simple_data_source_unittest.cc +++ b/webkit/glue/media/simple_data_source_unittest.cc @@ -6,9 +6,15 @@ #include "media/base/filters.h" #include "media/base/mock_filter_host.h" #include "media/base/mock_filters.h" -#include "webkit/glue/media/mock_media_resource_loader_bridge_factory.h" +#include "net/base/net_errors.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLError.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoader.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" #include "webkit/glue/media/simple_data_source.h" -#include "webkit/glue/mock_resource_loader_bridge.h" +#include "webkit/mocks/mock_webframe.h" +#include "webkit/mocks/mock_weburlloader.h" using ::testing::_; using ::testing::DoAll; @@ -21,6 +27,11 @@ using ::testing::SetArgumentPointee; using ::testing::StrictMock; using ::testing::WithArgs; +using WebKit::WebURLError; +using WebKit::WebURLLoader; +using WebKit::WebURLRequest; +using WebKit::WebURLResponse; + namespace { const int kDataSize = 1024; @@ -39,68 +50,56 @@ namespace webkit_glue { class SimpleDataSourceTest : public testing::Test { public: SimpleDataSourceTest() { - bridge_factory_.reset( - new NiceMock<MockMediaResourceLoaderBridgeFactory>()); - bridge_.reset(new NiceMock<MockResourceLoaderBridge>()); - for (int i = 0; i < kDataSize; ++i) { data_[i] = i; } } virtual ~SimpleDataSourceTest() { - if (bridge_.get()) - EXPECT_CALL(*bridge_, OnDestroy()); - if (bridge_factory_.get()) - EXPECT_CALL(*bridge_factory_, OnDestroy()); + ignore_result(frame_.release()); } void InitializeDataSource(const char* url) { + gurl_ = GURL(url); + + frame_.reset(new NiceMock<MockWebFrame>()); + url_loader_ = new NiceMock<MockWebURLLoader>(); + data_source_ = new SimpleDataSource(MessageLoop::current(), - bridge_factory_.get()); - CHECK(data_source_); + frame_.get()); // There is no need to provide a message loop to data source. data_source_->set_host(&host_); + data_source_->SetURLLoaderForTest(url_loader_); - // First a bridge is created. InSequence s; - EXPECT_CALL(*bridge_factory_, CreateBridge(GURL(url), _, -1, -1)) - .WillOnce(Return(bridge_.get())); - EXPECT_CALL(*bridge_, Start(data_source_.get())) - .WillOnce(Return(true)); data_source_->Initialize(url, callback_.NewCallback()); - MessageLoop::current()->RunAllPending(); } void RequestSucceeded(bool is_loaded) { - ResourceResponseInfo info; - info.content_length = kDataSize; + WebURLResponse response(gurl_); + response.setExpectedContentLength(kDataSize); - data_source_->OnReceivedResponse(info, false); + data_source_->didReceiveResponse(NULL, response); int64 size; EXPECT_TRUE(data_source_->GetSize(&size)); EXPECT_EQ(kDataSize, size); - for (int i = 0; i < kDataSize; ++i) - data_source_->OnReceivedData(data_ + i, 1); + for (int i = 0; i < kDataSize; ++i) { + data_source_->didReceiveData(NULL, data_ + i, 1); + } EXPECT_CALL(host_, SetLoaded(is_loaded)); InSequence s; - EXPECT_CALL(*bridge_, OnDestroy()) - .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridge)); EXPECT_CALL(host_, SetTotalBytes(kDataSize)); EXPECT_CALL(host_, SetBufferedBytes(kDataSize)); EXPECT_CALL(callback_, OnFilterCallback()); EXPECT_CALL(callback_, OnCallbackDestroyed()); - URLRequestStatus status; - status.set_status(URLRequestStatus::SUCCESS); - status.set_os_error(0); - data_source_->OnCompletedRequest(status, "", base::Time()); + data_source_->didFinishLoading(NULL, 0); // Let the tasks to be executed. MessageLoop::current()->RunAllPending(); @@ -108,28 +107,23 @@ class SimpleDataSourceTest : public testing::Test { void RequestFailed() { InSequence s; - EXPECT_CALL(*bridge_, OnDestroy()) - .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridge)); EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK)); EXPECT_CALL(callback_, OnFilterCallback()); EXPECT_CALL(callback_, OnCallbackDestroyed()); - URLRequestStatus status; - status.set_status(URLRequestStatus::FAILED); - status.set_os_error(100); - data_source_->OnCompletedRequest(status, "", base::Time()); + WebURLError error; + error.reason = net::ERR_FAILED; + data_source_->didFail(NULL, error); // Let the tasks to be executed. MessageLoop::current()->RunAllPending(); } void DestroyDataSource() { - EXPECT_CALL(*bridge_factory_, OnDestroy()) - .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridgeFactory)); - StrictMock<media::MockFilterCallback> callback; EXPECT_CALL(callback, OnFilterCallback()); EXPECT_CALL(callback, OnCallbackDestroyed()); + data_source_->Stop(callback.NewCallback()); MessageLoop::current()->RunAllPending(); @@ -148,23 +142,17 @@ class SimpleDataSourceTest : public testing::Test { } } - void ReleaseBridge() { - ignore_result(bridge_.release()); - } - - void ReleaseBridgeFactory() { - ignore_result(bridge_factory_.release()); - } - MOCK_METHOD1(ReadCallback, void(size_t size)); protected: + GURL gurl_; scoped_ptr<MessageLoop> message_loop_; - scoped_ptr<NiceMock<MockMediaResourceLoaderBridgeFactory> > bridge_factory_; - scoped_ptr<NiceMock<MockResourceLoaderBridge> > bridge_; + NiceMock<MockWebURLLoader>* url_loader_; scoped_refptr<SimpleDataSource> data_source_; StrictMock<media::MockFilterHost> host_; StrictMock<media::MockFilterCallback> callback_; + scoped_ptr<NiceMock<MockWebFrame> > frame_; + char data_[kDataSize]; DISALLOW_COPY_AND_ASSIGN(SimpleDataSourceTest); @@ -189,13 +177,16 @@ TEST_F(SimpleDataSourceTest, InitializeFile) { } TEST_F(SimpleDataSourceTest, InitializeData) { + frame_.reset(new NiceMock<MockWebFrame>()); + url_loader_ = new NiceMock<MockWebURLLoader>(); + data_source_ = new SimpleDataSource(MessageLoop::current(), - bridge_factory_.get()); + frame_.get()); EXPECT_TRUE(data_source_->IsUrlSupported(kDataUrl)); - CHECK(data_source_); // There is no need to provide a message loop to data source. data_source_->set_host(&host_); + data_source_->SetURLLoaderForTest(url_loader_); EXPECT_CALL(host_, SetLoaded(true)); EXPECT_CALL(host_, SetTotalBytes(sizeof(kDataUrlDecoded))); @@ -218,9 +209,7 @@ TEST_F(SimpleDataSourceTest, RequestFailed) { TEST_F(SimpleDataSourceTest, StopWhenDownloading) { InitializeDataSource(kHttpUrl); - EXPECT_CALL(*bridge_, Cancel()); - EXPECT_CALL(*bridge_, OnDestroy()) - .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridge)); + EXPECT_CALL(*url_loader_, cancel()); EXPECT_CALL(callback_, OnCallbackDestroyed()); DestroyDataSource(); } |