summaryrefslogtreecommitdiffstats
path: root/webkit
diff options
context:
space:
mode:
Diffstat (limited to 'webkit')
-rw-r--r--webkit/glue/media/buffered_data_source.cc734
-rw-r--r--webkit/glue/media/buffered_data_source.h286
-rw-r--r--webkit/glue/media/buffered_data_source_unittest.cc627
-rw-r--r--webkit/glue/media/media_resource_loader_bridge_factory.h5
-rw-r--r--webkit/glue/media/mock_media_resource_loader_bridge_factory.h36
-rw-r--r--webkit/glue/media/simple_data_source.cc31
-rw-r--r--webkit/glue/media/simple_data_source.h6
-rw-r--r--webkit/glue/media/simple_data_source_unittest.cc246
-rw-r--r--webkit/glue/mock_resource_loader_bridge.h40
-rw-r--r--webkit/tools/test_shell/test_shell.gyp6
-rw-r--r--webkit/tools/test_shell/test_webview_delegate.cc4
-rw-r--r--webkit/webkit.gyp2
12 files changed, 2000 insertions, 23 deletions
diff --git a/webkit/glue/media/buffered_data_source.cc b/webkit/glue/media/buffered_data_source.cc
new file mode 100644
index 0000000..76e6e9d
--- /dev/null
+++ b/webkit/glue/media/buffered_data_source.cc
@@ -0,0 +1,734 @@
+// 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 "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 "chrome/common/extensions/url_pattern.h"
+#include "media/base/filter_host.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/webappcachecontext.h"
+
+namespace {
+
+const char kHttpScheme[] = "http";
+const char kHttpsScheme[] = "https";
+const int64 kPositionNotSpecified = -1;
+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 maximum offset to seek to, this value limits the range of seek to
+// prevent corruption of calculations. The buffer has a maximum size of 12MB
+// so this is enough to contain every valid operations.
+const int kMaxSeek = 100 * 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;
+
+// Defines how long we should wait for more data before we declare a connection
+// timeout and start a new request.
+// TODO(hclam): Use this value when retry is implemented.
+// TODO(hclam): Set it to 5s, calibrate this value later.
+const int kDataTransferTimeoutSeconds = 5;
+
+// Defines how many times we should try to read from a buffered resource loader
+// before we declare a read error. After each failure of read from a buffered
+// resource loader, a new one is created to be read.
+// TODO(hclam): Use this value when retry is implemented.
+const int kReadTrials = 3;
+
+// BufferedDataSource has an intermediate buffer, this value governs the initial
+// size of that buffer. It is set to 32KB because this is a typical read size
+// of FFmpeg.
+const int kInitialReadBufferSize = 32768;
+
+// A helper method that accepts only HTTP, HTTPS and FILE protocol.
+bool IsSchemeSupported(const GURL& url) {
+ return url.SchemeIs(kHttpScheme) ||
+ url.SchemeIs(kHttpsScheme) ||
+ url.SchemeIsFile();
+}
+
+} // 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),
+ completed_(false),
+ range_requested_(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),
+ 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) {
+ // Make sure we have not started.
+ DCHECK(!bridge_.get());
+ DCHECK(!start_callback_.get());
+ DCHECK(start_callback);
+
+ start_callback_.reset(start_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_BYPASS_CACHE,
+ first_byte_position_,
+ last_byte_position_));
+
+ // And start the resource loading.
+ bridge_->Start(this);
+}
+
+void BufferedResourceLoader::Stop() {
+ // Reset callbacks.
+ start_callback_.reset();
+ read_callback_.reset();
+
+ // Destroy internal buffer.
+ buffer_.reset();
+
+ if (bridge_.get()) {
+ // Cancel the resource request.
+ bridge_->Cancel();
+ bridge_.reset();
+ }
+}
+
+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);
+
+ // Saves the parameter of reading.
+ read_callback_.reset(read_callback);
+ read_position_ = position;
+ read_size_ = read_size;
+ read_buffer_ = buffer;
+
+ // Check that the read parameters are within the range that we can handle.
+ // If the read request is made too far from the current offset, report that
+ // we cannot serve the request.
+ if (VerifyRead()) {
+ // 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);
+ return;
+ }
+
+ // TODO(hclam): We should report a better error code than just 0.
+ DoneRead(0);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedResourceLoader,
+// webkit_glue::ResourceLoaderBridge::Peer implementations
+void BufferedResourceLoader::OnReceivedRedirect(const GURL& new_url) {
+ DCHECK(bridge_.get());
+ DCHECK(start_callback_.get());
+
+ // Saves the new URL.
+ url_ = new_url;
+
+ // If we got redirected to an unsupported protocol then stop.
+ if (!IsSchemeSupported(new_url)) {
+ DoneStart(net::ERR_ADDRESS_INVALID);
+ Stop();
+ }
+}
+
+void BufferedResourceLoader::OnReceivedResponse(
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool content_filtered) {
+ DCHECK(bridge_.get());
+ DCHECK(start_callback_.get());
+
+ int64 first_byte_position = -1;
+ int64 last_byte_position = -1;
+ int64 instance_size = -1;
+
+ // The file:// protocol should be able to serve any request we want, so we
+ // take an exception for file protocol.
+ if (!url_.SchemeIsFile()) {
+ 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 (range_requested_) {
+ if (info.headers->response_code() != kHttpPartialContent ||
+ !info.headers->GetContentRange(&first_byte_position,
+ &last_byte_position,
+ &instance_size)) {
+ // We requested a range, but server didn't reply with partial content or
+ // the "Content-Range" header is corrupted.
+ // TODO(hclam): should also make sure this is the range we requested.
+ 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;
+ }
+ }
+
+ // |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;
+
+ // We only care about the first byte position if it's given by the server.
+ // TODO(hclam): If server replies with a different offset, consider failing
+ // here.
+ if (first_byte_position != kPositionNotSpecified)
+ offset_ = first_byte_position;
+
+ // Calls with a successful response.
+ DoneStart(net::OK);
+}
+
+void BufferedResourceLoader::OnReceivedData(const char* data, int len) {
+ DCHECK(bridge_.get());
+ DCHECK(buffer_.get());
+
+ // Writes more data to |buffer_|.
+ buffer_->Append(len, reinterpret_cast<const uint8*>(data));
+
+ // If there is an active read request, try to fulfill the request.
+ if (HasPendingRead() && CanFulfillRead()) {
+ ReadInternal();
+ }
+
+ // At last see if the buffer is full and we need to defer the downloading.
+ EnableDeferIfNeeded();
+}
+
+void BufferedResourceLoader::OnCompletedRequest(
+ const URLRequestStatus& status, const std::string& security_info) {
+ DCHECK(bridge_.get());
+ DCHECK(buffer_.get());
+
+ // Saves the information that the request has completed.
+ completed_ = true;
+
+ // After the response has completed, we don't need the bridge any more.
+ bridge_.reset();
+
+ // 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()) {
+ // If the request has failed, then fail the read.
+ if (!status.is_success()) {
+ DoneRead(net::ERR_FAILED);
+ return;
+ }
+
+ // Otherwise 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(!read_callback_.get());
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedResourceLoader, private
+void BufferedResourceLoader::EnableDeferIfNeeded() {
+ if (!deferred_ &&
+ buffer_->forward_bytes() >= buffer_->forward_capacity()) {
+ deferred_ = true;
+
+ if (bridge_.get())
+ bridge_->SetDefersLoading(true);
+ }
+}
+
+void BufferedResourceLoader::DisableDeferIfNeeded() {
+ if (deferred_ &&
+ buffer_->forward_bytes() < buffer_->forward_capacity() / 2) {
+ deferred_ = false;
+
+ if (bridge_.get())
+ bridge_->SetDefersLoading(false);
+ }
+}
+
+bool BufferedResourceLoader::CanFulfillRead() {
+ // If we are reading too far in the backward direction.
+ if (first_offset_ < 0 &&
+ first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0)
+ return false;
+
+ // If the start offset is too far ahead.
+ if (first_offset_ >= static_cast<int>(buffer_->forward_bytes()))
+ return false;
+
+ // At the point, we verified that first byte requested is within the buffer.
+ // If the request has completed, then just returns with what we have now.
+ if (completed_)
+ return true;
+
+ // If the resource request is still active, make sure the whole requested
+ // range is covered.
+ if (last_offset_ > static_cast<int>(buffer_->forward_bytes()))
+ return false;
+
+ return true;
+}
+
+bool BufferedResourceLoader::WillFulfillRead() {
+ // Reading too far in the backward direction.
+ if (first_offset_ < 0 &&
+ first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0)
+ return false;
+
+ // Try to read too far ahead.
+ if (last_offset_ > kForwardWaitThreshold)
+ return false;
+
+ // The resource request has completed, there's no way we can fulfill the
+ // read request.
+ if (completed_)
+ return false;
+
+ return true;
+}
+
+bool BufferedResourceLoader::VerifyRead() {
+ // Make sure |offset_| and |read_position_| does not differ by a large
+ // amount
+ if (read_position_ > offset_ + kMaxSeek)
+ return false;
+ else if (read_position_ < offset_ - kMaxSeek)
+ return false;
+
+ // If we can manage the read request with int32 math, then prepare the
+ // parameters.
+ first_offset_ = static_cast<int>(read_position_ - offset_);
+ last_offset_ = first_offset_ + read_size_;
+ 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_size_, read_buffer_));
+ offset_ += first_offset_ + read;
+
+ // And report with what we have read.
+ DoneRead(read);
+}
+
+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();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, protected
+BufferedDataSource::BufferedDataSource(
+ MessageLoop* render_loop,
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory)
+ : total_bytes_(kPositionNotSpecified),
+ bridge_factory_(bridge_factory),
+ loader_(NULL),
+ read_callback_(NULL),
+ read_position_(0),
+ read_size_(0),
+ read_buffer_(NULL),
+ intermediate_read_buffer_(new uint8[kInitialReadBufferSize]),
+ intermediate_read_buffer_size_(kInitialReadBufferSize),
+ render_loop_(render_loop),
+ initialize_callback_(NULL),
+ stopped_(false) {
+}
+
+BufferedDataSource::~BufferedDataSource() {
+}
+
+// A factory method to create BufferedResourceLoader using the read parameters.
+// This method can be overrided to inject mock BufferedResourceLoader object
+// for testing purpose.
+BufferedResourceLoader* BufferedDataSource::CreateLoader(
+ int64 first_byte_position, int64 last_byte_position) {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ return new BufferedResourceLoader(bridge_factory_.get(), url_,
+ first_byte_position,
+ last_byte_position);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, media::MediaFilter implementation
+void BufferedDataSource::Initialize(const std::string& url,
+ media::FilterCallback* callback) {
+ DCHECK(callback);
+ initialize_callback_.reset(callback);
+
+ // Saves the url.
+ url_ = GURL(url);
+
+ if (!IsSchemeSupported(url_)) {
+ host()->SetError(media::PIPELINE_ERROR_NETWORK);
+ DoneInitialization();
+ return;
+ }
+
+ media_format_.SetAsString(media::MediaFormat::kMimeType,
+ media::mime_type::kApplicationOctetStream);
+ media_format_.SetAsString(media::MediaFormat::kURL, url);
+
+ // Post a task to complete the initialization task.
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &BufferedDataSource::InitializeTask));
+}
+
+void BufferedDataSource::Stop() {
+ {
+ AutoLock auto_lock(lock_);
+ stopped_ = true;
+ }
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &BufferedDataSource::StopTask));
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, media::DataSource implementation
+void BufferedDataSource::Read(int64 position, size_t size,
+ uint8* data,
+ media::DataSource::ReadCallback* read_callback) {
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &BufferedDataSource::ReadTask,
+ position, static_cast<int>(size), data, read_callback));
+}
+
+bool BufferedDataSource::GetSize(int64* size_out) {
+ if (total_bytes_ != kPositionNotSpecified) {
+ *size_out = total_bytes_;
+ return true;
+ }
+ *size_out = 0;
+ return false;
+}
+
+bool BufferedDataSource::IsSeekable() {
+ return total_bytes_ != kPositionNotSpecified;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, render thread tasks
+void BufferedDataSource::InitializeTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(!loader_.get());
+
+ // Creates a new resource loader with the full range.
+ loader_.reset(CreateLoader(-1, -1));
+
+ // And then start the resource request.
+ loader_->Start(NewCallback(this,
+ &BufferedDataSource::InitializeStartCallback));
+}
+
+void BufferedDataSource::ReadTask(
+ int64 position, int read_size, uint8* buffer,
+ media::DataSource::ReadCallback* read_callback) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(!read_callback_.get());
+ DCHECK(read_callback);
+
+ // Saves the read parameters.
+ read_position_ = position;
+ read_size_ = read_size;
+ read_callback_.reset(read_callback);
+ read_buffer_ = buffer;
+
+ // Call to read internal to perform the actual read.
+ ReadInternal();
+}
+
+void BufferedDataSource::StopTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ // We just need to stop the loader, so it stops activity.
+ if (loader_.get()) {
+ loader_->Stop();
+ loader_.reset();
+ }
+}
+
+void BufferedDataSource::SwapLoaderTask(BufferedResourceLoader* loader) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader);
+
+ loader_.reset(loader);
+ loader_->Start(NewCallback(this,
+ &BufferedDataSource::PartialReadStartCallback));
+}
+
+// This method is the place where actual read happens, |loader_| must be valid
+// prior to make this method call.
+void BufferedDataSource::ReadInternal() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ // First we prepare the intermediate read buffer for BufferedResourceLoader
+ // to write to.
+ if (read_size_ > intermediate_read_buffer_size_) {
+ intermediate_read_buffer_.reset(new uint8[read_size_]);
+ }
+
+ // Perform the actual read with BufferedResourceLoader.
+ loader_->Read(read_position_, read_size_,
+ intermediate_read_buffer_.get(),
+ NewCallback(this, &BufferedDataSource::ReadCallback));
+}
+
+// Method to report the results of the current read request. Also reset all
+// the read parameters.
+void BufferedDataSource::DoneRead(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ lock_.AssertAcquired();
+
+ if (error >= 0) {
+ read_callback_->RunWithParams(Tuple1<size_t>(error));
+ } else {
+ read_callback_->RunWithParams(
+ Tuple1<size_t>(static_cast<size_t>(media::DataSource::kReadError)));
+ }
+
+ read_callback_.reset();
+ read_position_ = 0;
+ read_size_ = 0;
+ read_buffer_ = 0;
+}
+
+void BufferedDataSource::DoneInitialization() {
+ DCHECK(initialize_callback_.get());
+ lock_.AssertAcquired();
+
+ initialize_callback_->Run();
+ initialize_callback_.reset();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, callback methods.
+// These methods are called on the render thread for the events reported by
+// BufferedResourceLoader.
+void BufferedDataSource::InitializeStartCallback(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ // We need to prevent calling to filter host and running the callback if
+ // we have received the stop signal. We need to lock down the whole callback
+ // method to prevent bad things from happening. The reason behind this is
+ // that we cannot guarantee tasks on render thread have completely stopped
+ // when we receive the Stop() method call. The only way to solve this is to
+ // let tasks on render thread to run but make sure they don't call outside
+ // this object when Stop() method is ever called. Locking this method is safe
+ // because |lock_| is only acquired in tasks on render thread.
+ AutoLock auto_lock(lock_);
+ if (stopped_)
+ return;
+
+ DCHECK(loader_.get());
+
+ if (error == net::OK) {
+ total_bytes_ = loader_->content_length();
+ // TODO(hclam): Figure out what to do when total bytes is not known.
+ if (total_bytes_ >= 0) {
+ host()->SetTotalBytes(total_bytes_);
+
+ // This value governs the range that we can seek to.
+ // TODO(hclam): Report the correct value of buffered bytes.
+ host()->SetBufferedBytes(total_bytes_);
+ }
+ } else {
+ // TODO(hclam): In case of failure, we can retry several times.
+ // Also it might be bad to access host() here.
+ host()->SetError(media::PIPELINE_ERROR_NETWORK);
+
+ // Stops the loader, just to be safe.
+ loader_->Stop();
+ }
+
+ DoneInitialization();
+}
+
+void BufferedDataSource::PartialReadStartCallback(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ if (error == net::OK) {
+ // Once the range request has start successfully, we can proceed with
+ // reading from it.
+ ReadInternal();
+ } else {
+ // We need to prevent calling to filter host and running the callback if
+ // we have received the stop signal. We need to lock down the whole callback
+ // method to prevent bad things from happening. The reason behind this is
+ // that we cannot guarantee tasks on render thread have completely stopped
+ // when we receive the Stop() method call. So only way to solve this is to
+ // let tasks on render thread to run but make sure they don't call outside
+ // this object when Stop() method is ever called. Locking this method is
+ // safe because |lock_| is only acquired in tasks on render thread.
+ AutoLock auto_lock(lock_);
+ if (stopped_)
+ return;
+
+ // TODO(hclam): It may be bad to access host() here.
+ host()->SetError(media::PIPELINE_ERROR_NETWORK);
+
+ // Kill the loader just to be safe.
+ loader_->Stop();
+ }
+}
+
+void BufferedDataSource::ReadCallback(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ // We need to prevent calling to filter host and running the callback if
+ // we have received the stop signal. We need to lock down the whole callback
+ // method to prevent bad things from happening. The reason behind this is
+ // that we cannot guarantee tasks on render thread have completely stopped
+ // when we receive the Stop() method call. So only way to solve this is to
+ // let tasks on render thread to run but make sure they don't call outside
+ // this object when Stop() method is ever called. Locking this method is safe
+ // because |lock_| is only acquired in tasks on render thread.
+ AutoLock auto_lock(lock_);
+ if (stopped_)
+ return;
+
+ DCHECK(loader_.get());
+ DCHECK(read_callback_.get());
+
+ if (error >= 0) {
+ // If a position error code is received, read was successful. So copy
+ // from intermediate read buffer to the target read buffer.
+ memcpy(read_buffer_, intermediate_read_buffer_.get(), error);
+
+ DoneRead(error);
+ } else if (error == net::ERR_CACHE_MISS) {
+ // If the current loader cannot serve this read request, we need to create
+ // a new one.
+ // We have the following conditions:
+ // 1. Read is beyond the content length of the file (if known).
+ // 2. We have tried too many times (TODO here).
+ if (read_position_ >= total_bytes_) {
+ DoneRead(0);
+ return;
+ }
+
+ // TODO(hclam): we need to count how many times it failed to prevent
+ // excessive trials.
+
+ // Stops the current resource loader.
+ loader_->Stop();
+
+ // Since this method is called from the current buffered resource loader,
+ // we cannot delete it. So we need to post a task to swap in a new
+ // resource loader and starts it.
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &BufferedDataSource::SwapLoaderTask,
+ CreateLoader(read_position_, -1)));
+ } else {
+ // The read has finished with error.
+ DoneRead(error);
+
+ // TODO(hclam): It may be bad to access host() here.
+ host()->SetError(media::PIPELINE_ERROR_NETWORK);
+
+ // Stops the laoder.
+ loader_->Stop();
+ }
+}
+
+} // namespace webkit_glue
diff --git a/webkit/glue/media/buffered_data_source.h b/webkit/glue/media/buffered_data_source.h
new file mode 100644
index 0000000..5d6f1c3
--- /dev/null
+++ b/webkit/glue/media/buffered_data_source.h
@@ -0,0 +1,286 @@
+// 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_BUFFERED_DATA_SOURCE_H_
+#define WEBKIT_GLUE_MEDIA_BUFFERED_DATA_SOURCE_H_
+
+#include <string>
+
+#include "base/lock.h"
+#include "base/scoped_ptr.h"
+#include "base/condition_variable.h"
+#include "googleurl/src/gurl.h"
+#include "media/base/factory.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"
+
+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 webkit_glue::ResourceLoaderBridge::Peer {
+ public:
+ // |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);
+ virtual ~BufferedResourceLoader();
+
+ // 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 |start_callback| is called with the result.
+ virtual void Start(net::CompletionCallback* 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.
+ virtual void Read(int64 position, int read_size,
+ uint8* buffer, net::CompletionCallback* callback);
+
+ // Gets the content length in bytes of the instance after this loader has been
+ // started.
+ virtual int64 content_length() { return content_length_; }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // webkit_glue::ResourceLoaderBridge::Peer implementations.
+ virtual void OnUploadProgress(uint64 position, uint64 size) {}
+ virtual void OnReceivedRedirect(const GURL& new_url);
+ virtual void OnReceivedResponse(
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool content_filtered);
+ virtual void OnReceivedData(const char* data, int len);
+ virtual void OnCompletedRequest(const URLRequestStatus& status,
+ const std::string& security_info);
+ std::string GetURLForDebugging() { return url_.spec(); }
+
+ protected:
+ // An empty constructor so mock classes can be constructed.
+ BufferedResourceLoader() {
+ }
+
+ private:
+ // 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();
+
+ // Checks parameters and make sure they are valid.
+ bool VerifyRead();
+
+ // Method that does the actual read and calls the |read_callbac_|, assuming
+ // the request range is in |buffer_|.
+ void ReadInternal();
+
+ // 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);
+
+ bool HasPendingRead() { return read_callback_.get() != NULL; }
+
+ // A sliding window of buffer.
+ scoped_ptr<media::SeekableBuffer> buffer_;
+
+ bool deferred_;
+ bool completed_;
+ bool range_requested_;
+
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory_;
+ GURL url_;
+ int64 first_byte_position_;
+ int64 last_byte_position_;
+
+ // Members used during request start.
+ scoped_ptr<net::CompletionCallback> start_callback_;
+ scoped_ptr<webkit_glue::ResourceLoaderBridge> bridge_;
+ int64 offset_;
+ int64 content_length_;
+
+ // 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 media::DataSource {
+ public:
+ // Methods called from pipeline thread
+ // Static methods for creating this class.
+ static media::FilterFactory* CreateFactory(
+ MessageLoop* message_loop,
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory) {
+ return new media::FilterFactoryImpl2<
+ BufferedDataSource,
+ MessageLoop*,
+ webkit_glue::MediaResourceLoaderBridgeFactory*>(
+ message_loop, bridge_factory);
+ }
+
+ // media::MediaFilter implementation.
+ virtual void Initialize(const std::string& url,
+ media::FilterCallback* callback);
+ virtual void Stop();
+
+ // media::DataSource implementation.
+ // Called from demuxer thread.
+ virtual void Read(int64 position, size_t size,
+ uint8* data,
+ media::DataSource::ReadCallback* read_callback);
+ virtual bool GetSize(int64* size_out);
+ virtual bool IsSeekable();
+
+ const media::MediaFormat& media_format() {
+ return media_format_;
+ }
+
+ protected:
+ BufferedDataSource(
+ MessageLoop* render_loop,
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory);
+ virtual ~BufferedDataSource();
+
+ // A factory method to create a BufferedResourceLoader based on the read
+ // parameters. We can override this file to object a mock
+ // BufferedResourceLoader for testing.
+ virtual BufferedResourceLoader* CreateLoader(int64 first_byte_position,
+ int64 last_byte_position);
+
+ private:
+ friend class media::FilterFactoryImpl2<
+ BufferedDataSource,
+ MessageLoop*,
+ webkit_glue::MediaResourceLoaderBridgeFactory*>;
+
+ // Posted to perform initialization on render thread.
+ void InitializeTask();
+
+ // Task posted to perform resource loading and actual reading on the render
+ // thread.
+ void ReadTask(int64 position, int read_size,
+ uint8* read_buffer,
+ media::DataSource::ReadCallback* read_callback);
+
+ // Task posted when Stop() is called.
+ void StopTask();
+
+ // Reset |loader_| with |loader| and starts it. This task is posted from
+ // callback method from the current buffered resource loader.
+ void SwapLoaderTask(BufferedResourceLoader* loader);
+
+ // The method that performs actual read. This method can only be executed on
+ // the render thread.
+ void ReadInternal();
+
+ // Calls |read_callback_| and reset all read parameters.
+ void DoneRead(int error);
+
+ // Calls |initialize_callback_| and reset it.
+ void DoneInitialization();
+
+ // Callback method to perform BufferedResourceLoader::Start() during
+ // initialization.
+ void InitializeStartCallback(int error);
+
+ // Callback method to be passed to BufferedResourceLoader during range
+ // request. Once a resource request has started, this method will be called
+ // with the error code. This method will be executed on the thread
+ // BufferedResourceLoader lives, i.e. render thread.
+ void PartialReadStartCallback(int error);
+
+ // Callback method for making a read request to BufferedResourceLoader.
+ // If data arrives or the request has failed, this method is called with
+ // the error code or the number of bytes read.
+ void ReadCallback(int error);
+
+ media::MediaFormat media_format_;
+
+ // URL of the resource requested.
+ GURL url_;
+
+ // Members for total bytes of the requested object. It is written once on
+ // render thread but may be read from any thread. However reading of this
+ // member is guaranteed to happen after it is first written, so we don't
+ // need to protect it.
+ int64 total_bytes_;
+
+ // A factory object to produce ResourceLoaderBridge.
+ scoped_ptr<webkit_glue::MediaResourceLoaderBridgeFactory> bridge_factory_;
+
+ // A downloader object for loading the media resource.
+ scoped_ptr<BufferedResourceLoader> loader_;
+
+ // Read parameters received from the Read() method call.
+ scoped_ptr<media::DataSource::ReadCallback> read_callback_;
+ int64 read_position_;
+ int read_size_;
+ uint8* read_buffer_;
+
+ // This buffer is intermediate, we use it for BufferedResourceLoader to write
+ // to. And when read in BufferedResourceLoader is done, we copy data from
+ // this buffer to |read_buffer_|. The reason for an additional copy is that
+ // we don't own |read_buffer_|. But since the read operation is asynchronous,
+ // |read_buffer| can be destroyed at any time, so we only copy into
+ // |read_buffer| in the final step when it is safe.
+ // Memory is allocated for this member during initialization of this object
+ // because we want buffer to be passed into BufferedResourceLoader to be
+ // always non-null. And by initializing this member with a default size we can
+ // avoid creating zero-sized buffered if the first read has zero size.
+ scoped_array<uint8> intermediate_read_buffer_;
+ int intermediate_read_buffer_size_;
+
+ // The message loop of the render thread.
+ MessageLoop* render_loop_;
+
+ // Filter callbacks.
+ scoped_ptr<media::FilterCallback> initialize_callback_;
+
+ // Protects |stopped_|.
+ Lock lock_;
+
+ // Stop signal to suppressing activities.
+ bool stopped_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedDataSource);
+};
+
+} // namespace webkit_glue
+
+#endif // WEBKIT_GLUE_MEDIA_BUFFERED_DATA_SOURCE_H_
diff --git a/webkit/glue/media/buffered_data_source_unittest.cc b/webkit/glue/media/buffered_data_source_unittest.cc
new file mode 100644
index 0000000..40c0b41
--- /dev/null
+++ b/webkit/glue/media/buffered_data_source_unittest.cc
@@ -0,0 +1,627 @@
+// 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 <algorithm>
+
+#include "base/string_util.h"
+#include "media/base/filters.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 "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"
+
+using ::testing::_;
+using ::testing::Assign;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
+using ::testing::StrictMock;
+using ::testing::WithArgs;
+
+namespace {
+
+const char* kHttpUrl = "http://test";
+const int kDataSize = 1024;
+
+} // namespace
+
+namespace webkit_glue {
+
+class BufferedResourceLoaderTest : public testing::Test {
+ 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_.reset(new BufferedResourceLoader(&bridge_factory_, gurl_,
+ first_position_, last_position_));
+ EXPECT_EQ(gurl_.spec(), loader_->GetURLForDebugging());
+ }
+
+ 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));
+ }
+
+ void FullResponse(int64 content_length) {
+ EXPECT_CALL(*this, StartCallback(net::OK));
+ ResourceLoaderBridge::ResponseInfo info;
+ std::string header = StringPrintf("HTTP/1.1 200 OK\n"
+ "Content-Length: %lld", content_length);
+ 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());
+ }
+
+ void PartialResponse(int64 content_length) {
+ EXPECT_CALL(*this, StartCallback(net::OK));
+ ResourceLoaderBridge::ResponseInfo info;
+ std::string header = StringPrintf("HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes %lld-%lld/%lld",
+ first_position_,
+ last_position_,
+ content_length);
+ replace(header.begin(), header.end(), '\n', '\0');
+ info.headers = new net::HttpResponseHeaders(header);
+ info.content_length = content_length;
+ loader_->OnReceivedResponse(info, false);
+ // TODO(hclam): Right now BufferedResourceLoader doesn't care about the
+ // partial range replied by the server. Do the check here.
+ }
+
+ void StopWhenLoad() {
+ InSequence s;
+ EXPECT_CALL(*bridge_, Cancel());
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+ loader_->Stop();
+ }
+
+ void ReleaseBridge() {
+ bridge_.release();
+ }
+
+ // Helper method to write to |loader_| from |data_|.
+ void WriteLoader(int position, int size) {
+ 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));
+ }
+
+ MOCK_METHOD1(StartCallback, void(int error));
+ MOCK_METHOD1(ReadCallback, void(int error));
+
+ protected:
+ GURL gurl_;
+ int64 first_position_;
+ int64 last_position_;
+
+ scoped_ptr<BufferedResourceLoader> loader_;
+ StrictMock<MockMediaResourceLoaderBridgeFactory> bridge_factory_;
+ scoped_ptr<StrictMock<MockResourceLoaderBridge> > bridge_;
+
+ uint8 data_[kDataSize];
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoaderTest);
+};
+
+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());
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+
+ ResourceLoaderBridge::ResponseInfo 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());
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+
+ ResourceLoaderBridge::ResponseInfo info;
+ info.headers = new net::HttpResponseHeaders("HTTP/1.1 404 Bot Found\n");
+ loader_->OnReceivedResponse(info, false);
+}
+
+// Tests that partial content is requested but not fulfilled.
+TEST_F(BufferedResourceLoaderTest, NotPartialRange) {
+ Initialize(kHttpUrl, 100, -1);
+ Start();
+
+ EXPECT_CALL(*this, StartCallback(net::ERR_INVALID_RESPONSE));
+ EXPECT_CALL(*bridge_, Cancel());
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+
+ ResourceLoaderBridge::ResponseInfo info;
+ info.headers = new net::HttpResponseHeaders("HTTP/1.1 200 OK\n");
+ loader_->OnReceivedResponse(info, false);
+}
+
+// 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(1024);
+ StopWhenLoad();
+}
+
+// Tests the logic of sliding window for data buffering and reading.
+TEST_F(BufferedResourceLoaderTest, BufferAndRead) {
+ Initialize(kHttpUrl, 10, 29);
+ Start();
+ PartialResponse(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 backwith outside buffer.
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(9, 10, buffer);
+
+ // Response has completed.
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::SUCCESS);
+ loader_->OnCompletedRequest(status, "");
+
+ // 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);
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(30, 10, buffer);
+}
+
+TEST_F(BufferedResourceLoaderTest, ReadOutsideBuffer) {
+ Initialize(kHttpUrl, 10, 0x00FFFFFF);
+ Start();
+ PartialResponse(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(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+ EXPECT_CALL(*this, ReadCallback(5));
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::SUCCESS);
+ loader_->OnCompletedRequest(status, "");
+}
+
+TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) {
+ Initialize(kHttpUrl, 10, 29);
+ Start();
+ PartialResponse(30);
+
+ uint8 buffer[10];
+ InSequence s;
+
+ ReadLoader(10, 10, buffer);
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+ EXPECT_CALL(*this, ReadCallback(net::ERR_FAILED));
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::FAILED);
+ loader_->OnCompletedRequest(status, "");
+}
+
+// TODO(hclam): add unit test for defer loading.
+
+class MockBufferedResourceLoader : public BufferedResourceLoader {
+ public:
+ MockBufferedResourceLoader() : BufferedResourceLoader() {
+ }
+
+ ~MockBufferedResourceLoader() {
+ OnDestroy();
+ }
+
+ MOCK_METHOD1(Start, void(net::CompletionCallback* read_callback));
+ MOCK_METHOD0(Stop, void());
+ MOCK_METHOD4(Read, void(int64 position, int read_size, uint8* buffer,
+ net::CompletionCallback* callback));
+ MOCK_METHOD0(content_length, int64());
+ MOCK_METHOD0(OnDestroy, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockBufferedResourceLoader);
+};
+
+// A mock BufferedDataSource to inject mock BufferedResourceLoader through
+// CreateLoader() method.
+class MockBufferedDataSource : public BufferedDataSource {
+ public:
+ // Static methods for creating this class.
+ static media::FilterFactory* CreateFactory(
+ MessageLoop* message_loop,
+ MediaResourceLoaderBridgeFactory* bridge_factory) {
+ return new media::FilterFactoryImpl2<
+ MockBufferedDataSource,
+ MessageLoop*,
+ MediaResourceLoaderBridgeFactory*>(message_loop,
+ bridge_factory);
+ }
+
+ MOCK_METHOD2(CreateLoader, BufferedResourceLoader*(int64 first_position,
+ int64 last_position));
+
+ protected:
+ MockBufferedDataSource(
+ MessageLoop* message_loop,
+ MediaResourceLoaderBridgeFactory* factory)
+ : BufferedDataSource(message_loop, factory) {
+ }
+
+ private:
+ friend class media::FilterFactoryImpl2<
+ MockBufferedDataSource,
+ MessageLoop*,
+ MediaResourceLoaderBridgeFactory*>;
+
+ DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSource);
+};
+
+class BufferedDataSourceTest : public testing::Test {
+ public:
+ BufferedDataSourceTest() {
+ message_loop_.reset(MessageLoop::current());
+ bridge_factory_.reset(
+ new StrictMock<MockMediaResourceLoaderBridgeFactory>());
+ ReleaseLoader();
+ factory_ = MockBufferedDataSource::CreateFactory(message_loop_.get(),
+ bridge_factory_.get());
+
+ // Prepare test data.
+ for (size_t i = 0; i < sizeof(data_); ++i) {
+ data_[i] = i;
+ }
+ }
+
+ ~BufferedDataSourceTest() {
+ if (data_source_) {
+ // Expects bridge factory to be destroyed along with data source.
+ EXPECT_CALL(*bridge_factory_, OnDestroy())
+ .WillOnce(Invoke(this,
+ &BufferedDataSourceTest::ReleaseBridgeFactory));
+ }
+
+ // We don't own the message loop so release it.
+ message_loop_.release();
+ }
+
+ void InitializeDataSource(const char* url, int error, int64 content_length) {
+ // 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_ = factory_->Create<MockBufferedDataSource>(url_format);
+ CHECK(data_source_);
+
+ // There is no need to provide a message loop to data source.
+ data_source_->set_host(&host_);
+
+ // Creates the first mock loader to be injected.
+ loader_.reset(new StrictMock<MockBufferedResourceLoader>());
+
+ InSequence s;
+ StrictMock<media::MockFilterCallback> callback;
+ EXPECT_CALL(*data_source_, CreateLoader(-1, -1))
+ .WillOnce(Return(loader_.get()));
+ EXPECT_CALL(*loader_, Start(NotNull()))
+ .WillOnce(DoAll(Assign(&error_, error),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeStartCallback)));
+ if (error != net::OK) {
+ EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK));
+ EXPECT_CALL(*loader_, Stop());
+ } else {
+ EXPECT_CALL(*loader_, content_length())
+ .WillOnce(Return(content_length));
+ EXPECT_CALL(host_, SetTotalBytes(content_length));
+ EXPECT_CALL(host_, SetBufferedBytes(content_length));
+ }
+ EXPECT_CALL(callback, OnFilterCallback());
+ EXPECT_CALL(callback, OnCallbackDestroyed());
+
+ data_source_->Initialize(url, callback.NewCallback());
+ message_loop_->RunAllPending();
+
+ if (error == net::OK) {
+ int64 size;
+ EXPECT_TRUE(data_source_->GetSize(&size));
+ EXPECT_EQ(content_length, size);
+ }
+ }
+
+ void StopDataSource() {
+ if (loader_.get()) {
+ InSequence s;
+ EXPECT_CALL(*loader_, Stop());
+ EXPECT_CALL(*loader_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedDataSourceTest::ReleaseLoader));
+ }
+
+ data_source_->Stop();
+ message_loop_->RunAllPending();
+ }
+
+ void ReleaseBridgeFactory() {
+ bridge_factory_.release();
+ }
+
+ void ReleaseLoader() {
+ loader_.release();
+ }
+
+ void InvokeStartCallback(net::CompletionCallback* callback) {
+ callback->RunWithParams(Tuple1<int>(error_));
+ delete callback;
+ }
+
+ void InvokeReadCallback(int64 position, int size, uint8* buffer,
+ net::CompletionCallback* callback) {
+ if (error_ > 0)
+ memcpy(buffer, data_ + static_cast<int>(position), error_);
+ callback->RunWithParams(Tuple1<int>(error_));
+ delete callback;
+ }
+
+ void ReadDataSourceHit(int64 position, int size, int read_size) {
+ EXPECT_TRUE(loader_.get() != NULL);
+
+ InSequence s;
+ // Expect the read is delegated to the resource loader.
+ EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull()))
+ .WillOnce(DoAll(Assign(&error_, read_size),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeReadCallback)));
+
+ // The read has succeeded, so read callback will be called.
+ EXPECT_CALL(*this, ReadCallback(read_size));
+
+ data_source_->Read(
+ position, size, buffer_,
+ NewCallback(this, &BufferedDataSourceTest::ReadCallback));
+ message_loop_->RunAllPending();
+
+ // Make sure data is correct.
+ EXPECT_EQ(0,
+ memcmp(buffer_, data_ + static_cast<int>(position), read_size));
+ }
+
+ void ReadDataSourceMiss(int64 position, int size) {
+ EXPECT_TRUE(loader_.get() != NULL);
+
+ InSequence s;
+ // 1. Reply with a cache miss for the read.
+ EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull()))
+ .WillOnce(DoAll(Assign(&error_, net::ERR_CACHE_MISS),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeReadCallback)));
+
+ // 2. Then the current loader will be stop and destroyed.
+ StrictMock<MockBufferedResourceLoader> *new_loader =
+ new StrictMock<MockBufferedResourceLoader>();
+ EXPECT_CALL(*loader_, Stop());
+ EXPECT_CALL(*data_source_, CreateLoader(position, -1))
+ .WillOnce(Return(new_loader));
+ EXPECT_CALL(*loader_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedDataSourceTest::ReleaseLoader));
+
+ // 3. Then the new loader will be started.
+ EXPECT_CALL(*new_loader, Start(NotNull()))
+ .WillOnce(DoAll(Assign(&error_, net::OK),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeStartCallback)));
+
+ // 4. Then again a read request is made to the new loader.
+ EXPECT_CALL(*new_loader, Read(position, size, NotNull(), NotNull()))
+ .WillOnce(DoAll(Assign(&error_, size),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeReadCallback)));
+
+ EXPECT_CALL(*this, ReadCallback(size));
+
+ data_source_->Read(
+ position, size, buffer_,
+ NewCallback(this, &BufferedDataSourceTest::ReadCallback));
+ message_loop_->RunAllPending();
+
+ // Make sure data is correct.
+ EXPECT_EQ(0, memcmp(buffer_, data_ + static_cast<int>(position), size));
+
+ EXPECT_TRUE(loader_.get() == NULL);
+ loader_.reset(new_loader);
+ }
+
+ void ReadDataSourceFailed(int64 position, int size, int error) {
+ EXPECT_TRUE(loader_.get() != NULL);
+
+ InSequence s;
+ // 1. Expect the read is delegated to the resource loader.
+ EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull()))
+ .WillOnce(DoAll(Assign(&error_, error),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeReadCallback)));
+
+ // 2. The read has failed, so read callback will be called.
+ EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError));
+
+ // 3. Host will then receive an error.
+ EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK));
+
+ // 4. The the loader is destroyed.
+ EXPECT_CALL(*loader_, Stop());
+
+ data_source_->Read(
+ position, size, buffer_,
+ NewCallback(this, &BufferedDataSourceTest::ReadCallback));
+
+ message_loop_->RunAllPending();
+ }
+
+ MOCK_METHOD1(ReadCallback, void(size_t size));
+
+ scoped_ptr<StrictMock<MockMediaResourceLoaderBridgeFactory> >
+ bridge_factory_;
+ scoped_ptr<StrictMock<MockBufferedResourceLoader> > loader_;
+ scoped_refptr<MockBufferedDataSource > data_source_;
+ scoped_refptr<media::FilterFactory> factory_;
+
+ StrictMock<media::MockFilterHost> host_;
+ GURL gurl_;
+ scoped_ptr<MessageLoop> message_loop_;
+
+ int error_;
+ uint8 buffer_[1024];
+ uint8 data_[1024];
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceTest);
+};
+
+TEST_F(BufferedDataSourceTest, InitializationSuccess) {
+ InitializeDataSource(kHttpUrl, net::OK, 1024);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, InitiailizationFailed) {
+ InitializeDataSource(kHttpUrl, net::ERR_FILE_NOT_FOUND, 0);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, ReadCacheHit) {
+ InitializeDataSource(kHttpUrl, net::OK, 25);
+
+ // Performs read with cache hit.
+ ReadDataSourceHit(10, 10, 10);
+
+ // Performs read with cache hit but partially filled.
+ ReadDataSourceHit(20, 10, 5);
+
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, ReadCacheMiss) {
+ InitializeDataSource(kHttpUrl, net::OK, 1024);
+ ReadDataSourceMiss(1000, 10);
+ ReadDataSourceMiss(20, 10);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, ReadFailed) {
+ InitializeDataSource(kHttpUrl, net::OK, 1024);
+ ReadDataSourceHit(10, 10, 10);
+ ReadDataSourceFailed(10, 10, net::ERR_CONNECTION_RESET);
+ StopDataSource();
+}
+
+} // 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
index 80c0ed2..453f655 100644
--- a/webkit/glue/media/media_resource_loader_bridge_factory.h
+++ b/webkit/glue/media/media_resource_loader_bridge_factory.h
@@ -38,6 +38,11 @@ class MediaResourceLoaderBridgeFactory {
int64 first_byte_position,
int64 last_byte_position);
+ protected:
+ // An empty constructor only used by inherited classes.
+ MediaResourceLoaderBridgeFactory() {
+ }
+
private:
FRIEND_TEST(MediaResourceLoaderBridgeFactoryTest, GenerateHeaders);
diff --git a/webkit/glue/media/mock_media_resource_loader_bridge_factory.h b/webkit/glue/media/mock_media_resource_loader_bridge_factory.h
new file mode 100644
index 0000000..7bb27fe
--- /dev/null
+++ b/webkit/glue/media/mock_media_resource_loader_bridge_factory.h
@@ -0,0 +1,36 @@
+// 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_MOCK_RESOURCE_LOADER_BRIDGE_H_
+#define WEBKIT_GLUE_MOCK_RESOURCE_LOADER_BRIDGE_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_RESOURCE_LOADER_BRIDGE_H_
diff --git a/webkit/glue/media/simple_data_source.cc b/webkit/glue/media/simple_data_source.cc
index f426a14..27d286e 100644
--- a/webkit/glue/media/simple_data_source.cc
+++ b/webkit/glue/media/simple_data_source.cc
@@ -36,7 +36,6 @@ SimpleDataSource::SimpleDataSource(
: render_loop_(render_loop),
bridge_factory_(bridge_factory),
size_(-1),
- position_(0),
state_(UNINITIALIZED) {
DCHECK(render_loop);
}
@@ -81,24 +80,20 @@ const media::MediaFormat& SimpleDataSource::media_format() {
return media_format_;
}
-size_t SimpleDataSource::Read(uint8* data, size_t size) {
+void SimpleDataSource::Read(int64 position,
+ size_t size,
+ uint8* data,
+ ReadCallback* read_callback) {
DCHECK_GE(size_, 0);
- size_t copied = std::min(size, static_cast<size_t>(size_ - position_));
- memcpy(data, data_.c_str() + position_, copied);
- position_ += copied;
- return copied;
-}
-
-bool SimpleDataSource::GetPosition(int64* position_out) {
- *position_out = position_;
- return true;
-}
-
-bool SimpleDataSource::SetPosition(int64 position) {
- if (position < 0 || position > size_)
- return false;
- position_ = position;
- return true;
+ if (position >= size_) {
+ read_callback->RunWithParams(Tuple1<size_t>(0));
+ delete read_callback;
+ } else {
+ size_t copied = std::min(size, static_cast<size_t>(size_ - position));
+ memcpy(data, data_.c_str() + position, copied);
+ read_callback->RunWithParams(Tuple1<size_t>(copied));
+ delete read_callback;
+ }
}
bool SimpleDataSource::GetSize(int64* size_out) {
diff --git a/webkit/glue/media/simple_data_source.h b/webkit/glue/media/simple_data_source.h
index 8a63df4..1d15389 100644
--- a/webkit/glue/media/simple_data_source.h
+++ b/webkit/glue/media/simple_data_source.h
@@ -41,9 +41,8 @@ class SimpleDataSource : public media::DataSource,
virtual void Initialize(const std::string& url,
media::FilterCallback* callback);
virtual const media::MediaFormat& media_format();
- virtual size_t Read(uint8* data, size_t size);
- virtual bool GetPosition(int64* position_out);
- virtual bool SetPosition(int64 position);
+ virtual void Read(int64 position, size_t size,
+ uint8* data, ReadCallback* read_callback);
virtual bool GetSize(int64* size_out);
virtual bool IsSeekable();
@@ -91,7 +90,6 @@ class SimpleDataSource : public media::DataSource,
GURL url_;
std::string data_;
int64 size_;
- int64 position_;
// Simple state tracking variable.
enum State {
diff --git a/webkit/glue/media/simple_data_source_unittest.cc b/webkit/glue/media/simple_data_source_unittest.cc
new file mode 100644
index 0000000..9d367aa
--- /dev/null
+++ b/webkit/glue/media/simple_data_source_unittest.cc
@@ -0,0 +1,246 @@
+// 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 "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 "webkit/glue/media/simple_data_source.h"
+#include "webkit/glue/mock_resource_loader_bridge.h"
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
+using ::testing::StrictMock;
+using ::testing::WithArgs;
+
+namespace {
+
+const int kDataSize = 1024;
+const char kHttpUrl[] = "http://test";
+const char kFtpUrl[] = "ftp://test";
+const char kHttpsUrl[] = "https://test";
+const char kFileUrl[] = "file://test";
+const char kInvalidUrl[] = "whatever://test";
+
+} // namespace
+
+namespace webkit_glue {
+
+class SimpleDataSourceTest : public testing::Test {
+ public:
+ SimpleDataSourceTest() {
+ bridge_factory_.reset(
+ new StrictMock<MockMediaResourceLoaderBridgeFactory>());
+ bridge_.reset(new StrictMock<MockResourceLoaderBridge>());
+ factory_ = SimpleDataSource::CreateFactory(MessageLoop::current(),
+ bridge_factory_.get());
+
+ for (int i = 0; i < kDataSize; ++i) {
+ data_[i] = i;
+ }
+ }
+
+ virtual ~SimpleDataSourceTest() {
+ if (bridge_.get()) {
+ EXPECT_CALL(*bridge_, OnDestroy());
+ }
+ }
+
+ void InitializeDataSource(const char* url) {
+ // 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_ = factory_->Create<SimpleDataSource>(url_format);
+ CHECK(data_source_);
+
+ // There is no need to provide a message loop to data source.
+ data_source_->set_host(&host_);
+
+ // First a bridge is created.
+ InSequence s;
+ EXPECT_CALL(*bridge_factory_, CreateBridge(gurl_, _, -1, -1))
+ .WillOnce(Return(bridge_.get()));
+ EXPECT_CALL(*bridge_, Start(data_source_.get()))
+ .WillOnce(Return(true));
+
+ // TODO(hclam): need to add expectations to initialization callback.
+ data_source_->Initialize(url, callback_.NewCallback());
+
+ MessageLoop::current()->RunAllPending();
+ }
+
+ void RequestSucceeded() {
+ ResourceLoaderBridge::ResponseInfo info;
+ info.content_length = kDataSize;
+
+ data_source_->OnReceivedResponse(info, false);
+ 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);
+
+ 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, "");
+
+ // Let the tasks to be executed.
+ MessageLoop::current()->RunAllPending();
+ }
+
+ 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, "");
+
+ // Let the tasks to be executed.
+ MessageLoop::current()->RunAllPending();
+ }
+
+ void DestroyDataSource() {
+ EXPECT_CALL(*bridge_factory_, OnDestroy())
+ .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridgeFactory));
+
+ data_source_->Stop();
+ MessageLoop::current()->RunAllPending();
+
+ data_source_ = NULL;
+ }
+
+ void AsyncRead() {
+ for (int i = 0; i < kDataSize; ++i) {
+ uint8 buffer[1];
+
+ EXPECT_CALL(*this, ReadCallback(1));
+ data_source_->Read(
+ i, 1, buffer,
+ NewCallback(this, &SimpleDataSourceTest::ReadCallback));
+ EXPECT_EQ(static_cast<uint8>(data_[i]), buffer[0]);
+ }
+ }
+
+ void ReleaseBridge() {
+ bridge_.release();
+ }
+
+ void ReleaseBridgeFactory() {
+ bridge_factory_.release();
+ }
+
+ MOCK_METHOD1(ReadCallback, void(size_t size));
+
+ protected:
+ scoped_ptr<MessageLoop> message_loop_;
+ scoped_ptr<StrictMock<MockMediaResourceLoaderBridgeFactory> > bridge_factory_;
+ scoped_ptr<StrictMock<MockResourceLoaderBridge> > bridge_;
+ scoped_refptr<media::FilterFactory> factory_;
+ scoped_refptr<SimpleDataSource> data_source_;
+ StrictMock<media::MockFilterHost> host_;
+ StrictMock<media::MockFilterCallback> callback_;
+ GURL gurl_;
+ char data_[kDataSize];
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleDataSourceTest);
+};
+
+TEST_F(SimpleDataSourceTest, InitializeHTTP) {
+ InitializeDataSource(kHttpUrl);
+ RequestSucceeded();
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeHTTPS) {
+ InitializeDataSource(kHttpsUrl);
+ RequestSucceeded();
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeFTP) {
+ InitializeDataSource(kFtpUrl);
+ RequestSucceeded();
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeFile) {
+ InitializeDataSource(kFileUrl);
+ RequestSucceeded();
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeInvalid) {
+ StrictMock<media::MockFilterCallback> callback;
+ media::MediaFormat url_format;
+ url_format.SetAsString(media::MediaFormat::kMimeType,
+ media::mime_type::kURL);
+ url_format.SetAsString(media::MediaFormat::kURL, kInvalidUrl);
+ data_source_ = factory_->Create<SimpleDataSource>(url_format);
+ CHECK(data_source_);
+
+ // There is no need to provide a message loop to data source.
+ data_source_->set_host(&host_);
+
+ EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK));
+ EXPECT_CALL(callback, OnFilterCallback());
+ EXPECT_CALL(callback, OnCallbackDestroyed());
+
+ data_source_->Initialize(kInvalidUrl, callback.NewCallback());
+ data_source_->Stop();
+ MessageLoop::current()->RunAllPending();
+
+ EXPECT_CALL(*bridge_factory_, OnDestroy())
+ .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridgeFactory));
+ data_source_ = NULL;
+}
+
+TEST_F(SimpleDataSourceTest, RequestFailed) {
+ InitializeDataSource(kHttpUrl);
+ RequestFailed();
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, StopWhenDownloading) {
+ InitializeDataSource(kHttpUrl);
+
+ EXPECT_CALL(*bridge_, Cancel());
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridge));
+ EXPECT_CALL(callback_, OnCallbackDestroyed());
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, AsyncRead) {
+ InitializeDataSource(kFileUrl);
+ RequestSucceeded();
+ AsyncRead();
+ DestroyDataSource();
+}
+
+} // namespace webkit_glue
diff --git a/webkit/glue/mock_resource_loader_bridge.h b/webkit/glue/mock_resource_loader_bridge.h
new file mode 100644
index 0000000..a3c40ba
--- /dev/null
+++ b/webkit/glue/mock_resource_loader_bridge.h
@@ -0,0 +1,40 @@
+// 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_MOCK_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_
+#define WEBKIT_GLUE_MEDIA_MOCK_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_
+
+#include "base/file_path.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "webkit/glue/resource_loader_bridge.h"
+
+namespace webkit_glue {
+
+class MockResourceLoaderBridge : public webkit_glue::ResourceLoaderBridge {
+ public:
+ MockResourceLoaderBridge() {
+ }
+
+ virtual ~MockResourceLoaderBridge() {
+ OnDestroy();
+ }
+
+ MOCK_METHOD2(AppendDataToUpload, void(const char* data, int data_len));
+ MOCK_METHOD3(AppendFileRangeToUpload, void(const FilePath& file_path,
+ uint64 offset,
+ uint64 length));
+ MOCK_METHOD1(SetUploadIdentifier, void(int64 identifier));
+ MOCK_METHOD1(Start, bool(ResourceLoaderBridge::Peer* peer));
+ MOCK_METHOD0(Cancel, void());
+ MOCK_METHOD1(SetDefersLoading, void(bool value));
+ MOCK_METHOD1(SyncLoad, void(SyncLoadResponse* response));
+ MOCK_METHOD0(OnDestroy, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockResourceLoaderBridge);
+};
+
+} // namespace webkit_glue
+
+#endif // WEBKIT_GLUE_MEDIA_MOCK_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_
diff --git a/webkit/tools/test_shell/test_shell.gyp b/webkit/tools/test_shell/test_shell.gyp
index 2746641..539ad34 100644
--- a/webkit/tools/test_shell/test_shell.gyp
+++ b/webkit/tools/test_shell/test_shell.gyp
@@ -35,6 +35,7 @@
'../../../media/media.gyp:media',
'../../../net/net.gyp:net',
'../../../skia/skia.gyp:skia',
+ '../../../testing/gmock.gyp:gmock',
'../../../testing/gtest.gyp:gtest',
'../../../third_party/npapi/npapi.gyp:npapi',
'../../webkit.gyp:glue',
@@ -449,6 +450,7 @@
'dependencies': [
'test_shell_common',
'../../../skia/skia.gyp:skia',
+ '../../../testing/gmock.gyp:gmock',
'../../../testing/gtest.gyp:gtest',
],
'sources': [
@@ -467,8 +469,12 @@
'../../glue/dom_serializer_unittest.cc',
'../../glue/glue_serialize_unittest.cc',
'../../glue/iframe_redirect_unittest.cc',
+ '../../glue/media/buffered_data_source_unittest.cc',
'../../glue/media/media_resource_loader_bridge_factory_unittest.cc',
+ '../../glue/media/mock_media_resource_loader_bridge_factory.h',
+ '../../glue/media/simple_data_source_unittest.cc',
'../../glue/mimetype_unittest.cc',
+ '../../glue/mock_resource_loader_bridge.h',
'../../glue/multipart_response_delegate_unittest.cc',
'../../glue/password_autocomplete_listener_unittest.cc',
'../../glue/regular_expression_unittest.cc',
diff --git a/webkit/tools/test_shell/test_webview_delegate.cc b/webkit/tools/test_shell/test_webview_delegate.cc
index 6fbaa1f..55a2d8b 100644
--- a/webkit/tools/test_shell/test_webview_delegate.cc
+++ b/webkit/tools/test_shell/test_webview_delegate.cc
@@ -29,6 +29,7 @@
#include "webkit/api/public/WebURLError.h"
#include "webkit/api/public/WebURLRequest.h"
#include "webkit/glue/glue_serialize.h"
+#include "webkit/glue/media/buffered_data_source.h"
#include "webkit/glue/media/media_resource_loader_bridge_factory.h"
#include "webkit/glue/media/simple_data_source.h"
#include "webkit/glue/webappcachecontext.h"
@@ -148,8 +149,9 @@ WebKit::WebMediaPlayer* TestWebViewDelegate::CreateWebMediaPlayer(
base::GetCurrentProcId(),
WebAppCacheContext::kNoAppCacheContextId,
0);
- factory->AddFactory(webkit_glue::SimpleDataSource::CreateFactory(
+ factory->AddFactory(webkit_glue::BufferedDataSource::CreateFactory(
MessageLoop::current(), bridge_factory));
+ // TODO(hclam): Use command line switch to determine which data source to use.
return new webkit_glue::WebMediaPlayerImpl(client, factory);
}
diff --git a/webkit/webkit.gyp b/webkit/webkit.gyp
index 0b16eb4..c27323d 100644
--- a/webkit/webkit.gyp
+++ b/webkit/webkit.gyp
@@ -1242,6 +1242,8 @@
'glue/devtools/dom_agent_impl.cc',
'glue/devtools/dom_agent_impl.h',
'glue/devtools/tools_agent.h',
+ 'glue/media/buffered_data_source.cc',
+ 'glue/media/buffered_data_source.h',
'glue/media/media_resource_loader_bridge_factory.cc',
'glue/media/media_resource_loader_bridge_factory.h',
'glue/media/simple_data_source.cc',