diff options
Diffstat (limited to 'webkit')
-rw-r--r-- | webkit/glue/media/buffered_data_source.cc | 734 | ||||
-rw-r--r-- | webkit/glue/media/buffered_data_source.h | 286 | ||||
-rw-r--r-- | webkit/glue/media/buffered_data_source_unittest.cc | 627 | ||||
-rw-r--r-- | webkit/glue/media/media_resource_loader_bridge_factory.h | 5 | ||||
-rw-r--r-- | webkit/glue/media/mock_media_resource_loader_bridge_factory.h | 36 | ||||
-rw-r--r-- | webkit/glue/media/simple_data_source.cc | 31 | ||||
-rw-r--r-- | webkit/glue/media/simple_data_source.h | 6 | ||||
-rw-r--r-- | webkit/glue/media/simple_data_source_unittest.cc | 246 | ||||
-rw-r--r-- | webkit/glue/mock_resource_loader_bridge.h | 40 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_shell.gyp | 6 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_webview_delegate.cc | 4 | ||||
-rw-r--r-- | webkit/webkit.gyp | 2 |
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', |