summaryrefslogtreecommitdiffstats
path: root/android_webview/browser/net
diff options
context:
space:
mode:
authormkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-22 16:21:26 +0000
committermkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-22 16:21:26 +0000
commit17373866b8912a64bba105e6387b3b7cbe48301a (patch)
tree59e2ae08a8a6cd07b75b3fd4f28f70ebc56c09e6 /android_webview/browser/net
parent3fd370b5b2201628fe1b9c108b9917de93977257 (diff)
downloadchromium_src-17373866b8912a64bba105e6387b3b7cbe48301a.zip
chromium_src-17373866b8912a64bba105e6387b3b7cbe48301a.tar.gz
chromium_src-17373866b8912a64bba105e6387b3b7cbe48301a.tar.bz2
[android_webview] Don't block the IO thread when reading from an InputStream.
This breaks up the functionality in the AndroidStreamReader..Job into three separate classes, adds native unittests and makes the Job read the InputStream on a background thread. This change adds a separate unittestjava folder because the code under javatests can't be compiled to be a part of a native unittest APK due to resource dependencies. TEST=AndroidWebviewTests,android_webview_unittests BUG=None Review URL: https://codereview.chromium.org/11363123 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@169274 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'android_webview/browser/net')
-rw-r--r--android_webview/browser/net/android_stream_reader_url_request_job.cc189
-rw-r--r--android_webview/browser/net/android_stream_reader_url_request_job.h100
-rw-r--r--android_webview/browser/net/android_stream_reader_url_request_job_unittest.cc311
-rw-r--r--android_webview/browser/net/input_stream_reader.cc98
-rw-r--r--android_webview/browser/net/input_stream_reader.h66
-rw-r--r--android_webview/browser/net/input_stream_reader_unittest.cc172
6 files changed, 936 insertions, 0 deletions
diff --git a/android_webview/browser/net/android_stream_reader_url_request_job.cc b/android_webview/browser/net/android_stream_reader_url_request_job.cc
new file mode 100644
index 0000000..030f80a
--- /dev/null
+++ b/android_webview/browser/net/android_stream_reader_url_request_job.cc
@@ -0,0 +1,189 @@
+// Copyright (c) 2012 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 "android_webview/browser/net/android_stream_reader_url_request_job.h"
+
+#include "android_webview/browser/input_stream.h"
+#include "android_webview/browser/net/input_stream_reader.h"
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop.h"
+#include "base/task_runner.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/threading/thread.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/io_buffer.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job_manager.h"
+
+using android_webview::InputStream;
+using android_webview::InputStreamReader;
+using base::android::AttachCurrentThread;
+using base::PostTaskAndReplyWithResult;
+using content::BrowserThread;
+
+AndroidStreamReaderURLRequestJob::AndroidStreamReaderURLRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ scoped_ptr<Delegate> delegate)
+ : URLRequestJob(request, network_delegate),
+ delegate_(delegate.Pass()),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
+ DCHECK(delegate_.get());
+}
+
+AndroidStreamReaderURLRequestJob::~AndroidStreamReaderURLRequestJob() {
+}
+
+void AndroidStreamReaderURLRequestJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &AndroidStreamReaderURLRequestJob::StartAsync,
+ weak_factory_.GetWeakPtr()));
+}
+
+void AndroidStreamReaderURLRequestJob::Kill() {
+ weak_factory_.InvalidateWeakPtrs();
+ URLRequestJob::Kill();
+}
+
+scoped_refptr<InputStreamReader>
+AndroidStreamReaderURLRequestJob::CreateStreamReader(InputStream* stream) {
+ return make_scoped_refptr(new InputStreamReader(stream));
+}
+
+void AndroidStreamReaderURLRequestJob::StartAsync() {
+ JNIEnv* env = AttachCurrentThread();
+ DCHECK(env);
+
+ // This could be done in the InputStreamReader but would force more
+ // complex synchronization in the delegate.
+ stream_ = delegate_->OpenInputStream(env, request());
+ if (!stream_) {
+ NotifyDone(
+ net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED));
+ return;
+ }
+
+ DCHECK(!input_stream_reader_);
+ input_stream_reader_ = CreateStreamReader(stream_.get());
+ CHECK(input_stream_reader_);
+
+ PostTaskAndReplyWithResult(
+ GetWorkerThreadRunner(),
+ FROM_HERE,
+ base::Bind(&InputStreamReader::Seek, input_stream_reader_, byte_range_),
+ base::Bind(&AndroidStreamReaderURLRequestJob::OnReaderSeekCompleted,
+ weak_factory_.GetWeakPtr()));
+}
+
+void AndroidStreamReaderURLRequestJob::OnReaderSeekCompleted(
+ int result) {
+ // Clear the IO_PENDING status set in Start().
+ SetStatus(net::URLRequestStatus());
+ if (result >= 0) {
+ set_expected_content_size(result);
+ NotifyHeadersComplete();
+ } else {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
+ }
+}
+
+void AndroidStreamReaderURLRequestJob::OnReaderReadCompleted(
+ int result) {
+ // The URLRequest API contract requires that:
+ // * NotifyDone be called once, to set the status code, indicate the job is
+ // finished (there will be no further IO),
+ // * NotifyReadComplete be called if false is returned from ReadRawData to
+ // indicate that the IOBuffer will not be used by the job anymore.
+ // There might be multiple calls to ReadRawData (and thus multiple calls to
+ // NotifyReadComplete), which is why NotifyDone is called only on errors
+ // (result < 0) and end of data (result == 0).
+ if (result == 0) {
+ NotifyDone(net::URLRequestStatus());
+ } else if (result < 0) {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
+ } else {
+ // Clear the IO_PENDING status.
+ SetStatus(net::URLRequestStatus());
+ }
+ NotifyReadComplete(result);
+}
+
+base::TaskRunner* AndroidStreamReaderURLRequestJob::GetWorkerThreadRunner() {
+ return static_cast<base::TaskRunner*>(BrowserThread::GetBlockingPool());
+}
+
+bool AndroidStreamReaderURLRequestJob::ReadRawData(net::IOBuffer* dest,
+ int dest_size,
+ int* bytes_read) {
+ DCHECK(input_stream_reader_);
+
+ PostTaskAndReplyWithResult(
+ GetWorkerThreadRunner(),
+ FROM_HERE,
+ base::Bind(&InputStreamReader::ReadRawData,
+ input_stream_reader_,
+ base::Unretained(dest),
+ dest_size),
+ base::Bind(&AndroidStreamReaderURLRequestJob::OnReaderReadCompleted,
+ weak_factory_.GetWeakPtr()));
+
+ SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
+ return false;
+}
+
+bool AndroidStreamReaderURLRequestJob::GetMimeType(
+ std::string* mime_type) const {
+ JNIEnv* env = AttachCurrentThread();
+ DCHECK(env);
+
+ if (!stream_)
+ return false;
+
+ return delegate_->GetMimeType(env, request(), *stream_, mime_type);
+}
+
+bool AndroidStreamReaderURLRequestJob::GetCharset(
+ std::string* charset) {
+ JNIEnv* env = AttachCurrentThread();
+ DCHECK(env);
+
+ if (!stream_)
+ return false;
+
+ return delegate_->GetCharset(env, request(), *stream_, charset);
+}
+
+void AndroidStreamReaderURLRequestJob::SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) {
+ std::string range_header;
+ if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
+ // We only extract the "Range" header so that we know how many bytes in the
+ // stream to skip and how many to read after that.
+ std::vector<net::HttpByteRange> ranges;
+ if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
+ if (ranges.size() == 1) {
+ byte_range_ = ranges[0];
+ } else {
+ // We don't support multiple range requests in one single URL request,
+ // because we need to do multipart encoding here.
+ NotifyDone(net::URLRequestStatus(
+ net::URLRequestStatus::FAILED,
+ net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ }
+ }
+ }
+}
diff --git a/android_webview/browser/net/android_stream_reader_url_request_job.h b/android_webview/browser/net/android_stream_reader_url_request_job.h
new file mode 100644
index 0000000..1053d21
--- /dev/null
+++ b/android_webview/browser/net/android_stream_reader_url_request_job.h
@@ -0,0 +1,100 @@
+// Copyright (c) 2012 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 ANDROID_WEBVIEW_NATIVE_ANDROID_STREAM_READER_URL_REQUEST_JOB_H_
+#define ANDROID_WEBVIEW_NATIVE_ANDROID_STREAM_READER_URL_REQUEST_JOB_H_
+
+#include "base/android/scoped_java_ref.h"
+#include "base/location.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/http/http_byte_range.h"
+#include "net/url_request/url_request_job.h"
+
+namespace android_webview {
+class InputStream;
+class InputStreamReader;
+}
+
+namespace base {
+class TaskRunner;
+}
+
+namespace net {
+class URLRequest;
+}
+
+// A request job that reads data from a Java InputStream.
+class AndroidStreamReaderURLRequestJob : public net::URLRequestJob {
+ public:
+ /*
+ * We use a delegate so that we can share code for this job in slightly
+ * different contexts.
+ */
+ class Delegate {
+ public:
+ virtual scoped_ptr<android_webview::InputStream> OpenInputStream(
+ JNIEnv* env,
+ net::URLRequest* request) = 0;
+
+ virtual bool GetMimeType(
+ JNIEnv* env,
+ net::URLRequest* request,
+ const android_webview::InputStream& stream,
+ std::string* mime_type) = 0;
+
+ virtual bool GetCharset(
+ JNIEnv* env,
+ net::URLRequest* request,
+ const android_webview::InputStream& stream,
+ std::string* charset) = 0;
+
+ virtual ~Delegate() {}
+ };
+
+ AndroidStreamReaderURLRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ scoped_ptr<Delegate> delegate);
+
+ // URLRequestJob:
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual bool ReadRawData(net::IOBuffer* buf,
+ int buf_size,
+ int* bytes_read) OVERRIDE;
+ virtual void SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual bool GetCharset(std::string* charset) OVERRIDE;
+
+ protected:
+ virtual ~AndroidStreamReaderURLRequestJob();
+
+ // Gets the TaskRunner for the worker thread.
+ // Overridden in unittests.
+ virtual base::TaskRunner* GetWorkerThreadRunner();
+
+ // Creates an InputStreamReader instance.
+ // Overridden in unittests to return a mock.
+ virtual scoped_refptr<android_webview::InputStreamReader>
+ CreateStreamReader(android_webview::InputStream* stream);
+
+ private:
+ void StartAsync();
+
+ void OnReaderSeekCompleted(int content_size);
+ void OnReaderReadCompleted(int bytes_read);
+
+ net::HttpByteRange byte_range_;
+ scoped_ptr<Delegate> delegate_;
+ scoped_refptr<android_webview::InputStreamReader> input_stream_reader_;
+ scoped_ptr<android_webview::InputStream> stream_;
+ base::WeakPtrFactory<AndroidStreamReaderURLRequestJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(AndroidStreamReaderURLRequestJob);
+};
+
+#endif // ANDROID_WEBVIEW_NATIVE_ANDROID_STREAM_READER_URL_REQUEST_JOB_H_
diff --git a/android_webview/browser/net/android_stream_reader_url_request_job_unittest.cc b/android_webview/browser/net/android_stream_reader_url_request_job_unittest.cc
new file mode 100644
index 0000000..0678394
--- /dev/null
+++ b/android_webview/browser/net/android_stream_reader_url_request_job_unittest.cc
@@ -0,0 +1,311 @@
+// Copyright (c) 2012 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 "android_webview/browser/input_stream.h"
+#include "android_webview/browser/net/android_stream_reader_url_request_job.h"
+#include "android_webview/browser/net/aw_url_request_job_factory.h"
+#include "android_webview/browser/net/input_stream_reader.h"
+#include "base/format_macros.h"
+#include "base/message_loop.h"
+#include "base/run_loop.h"
+#include "base/stringprintf.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "net/url_request/url_request_test_util.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using android_webview::InputStream;
+using android_webview::InputStreamReader;
+using net::TestDelegate;
+using net::TestJobInterceptor;
+using net::TestNetworkDelegate;
+using net::TestURLRequestContext;
+using net::TestURLRequest;
+using testing::DoAll;
+using testing::Ge;
+using testing::Gt;
+using testing::InSequence;
+using testing::Invoke;
+using testing::InvokeWithoutArgs;
+using testing::NotNull;
+using testing::Return;
+using testing::SaveArg;
+using testing::SetArgPointee;
+using testing::StrictMock;
+using testing::Test;
+using testing::WithArg;
+using testing::WithArgs;
+using testing::_;
+
+// Some of the classes will DCHECK on a null InputStream (which is desirable).
+// The workaround is to use this class. None of the methods need to be
+// implemented as the mock InputStreamReader should never forward calls to the
+// InputStream.
+class NotImplInputStream : public InputStream {
+ public:
+ NotImplInputStream() {}
+ virtual ~NotImplInputStream() {}
+ virtual bool BytesAvailable(int* bytes_available) const {
+ NOTIMPLEMENTED();
+ return false;
+ }
+ virtual bool Skip(int64_t n, int64_t* bytes_skipped) {
+ NOTIMPLEMENTED();
+ return false;
+ }
+ virtual bool Read(net::IOBuffer* dest, int length, int* bytes_read) {
+ NOTIMPLEMENTED();
+ return false;
+ }
+};
+
+// Required in order to create an instance of AndroidStreamReaderURLRequestJob.
+class StreamReaderDelegate :
+ public AndroidStreamReaderURLRequestJob::Delegate {
+ public:
+ StreamReaderDelegate() {}
+
+ virtual scoped_ptr<InputStream> OpenInputStream(
+ JNIEnv* env,
+ net::URLRequest* request) {
+ return make_scoped_ptr<InputStream>(new NotImplInputStream());
+ }
+
+ virtual bool GetMimeType(
+ JNIEnv* env,
+ net::URLRequest* request,
+ const android_webview::InputStream& stream,
+ std::string* mime_type) {
+ return false;
+ }
+
+ virtual bool GetCharset(
+ JNIEnv* env,
+ net::URLRequest* request,
+ const android_webview::InputStream& stream,
+ std::string* charset) {
+ return false;
+ }
+};
+
+class MockInputStreamReader : public InputStreamReader {
+ public:
+ MockInputStreamReader() : InputStreamReader(new NotImplInputStream()) {}
+
+ MOCK_METHOD1(Seek, int(const net::HttpByteRange& byte_range));
+
+ MOCK_METHOD2(ReadRawData, int(net::IOBuffer* buffer, int buffer_size));
+ protected:
+ ~MockInputStreamReader() {}
+};
+
+
+class TestStreamReaderJob : public AndroidStreamReaderURLRequestJob {
+ public:
+ TestStreamReaderJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ scoped_ptr<Delegate> delegate,
+ scoped_refptr<InputStreamReader> stream_reader)
+ : AndroidStreamReaderURLRequestJob(request,
+ network_delegate,
+ delegate.Pass()),
+ stream_reader_(stream_reader) {
+ message_loop_proxy_ = base::MessageLoopProxy::current();
+ }
+
+ virtual scoped_refptr<InputStreamReader> CreateStreamReader(
+ InputStream* stream) {
+ return stream_reader_;
+ }
+ protected:
+ virtual ~TestStreamReaderJob() {}
+
+ virtual base::TaskRunner* GetWorkerThreadRunner() {
+ return message_loop_proxy_.get();
+ }
+
+ scoped_refptr<InputStreamReader> stream_reader_;
+ scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
+};
+
+class AndroidStreamReaderURLRequestJobTest : public Test {
+ public:
+ AndroidStreamReaderURLRequestJobTest()
+ : loop_(MessageLoop::TYPE_IO) {
+ }
+ protected:
+ virtual void SetUp() {
+ context_.set_job_factory(&factory_);
+ context_.set_network_delegate(&network_delegate_);
+ req_.reset(
+ new TestURLRequest(GURL("content://foo"),
+ &url_request_delegate_,
+ &context_));
+ req_->set_method("GET");
+ }
+
+ void SetRange(net::URLRequest* req, int first_byte, int last_byte) {
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kRange,
+ base::StringPrintf(
+ "bytes=%" PRIuS "-%" PRIuS,
+ first_byte, last_byte));
+ req->SetExtraRequestHeaders(headers);
+ }
+
+ void SetUpTestJob(InputStreamReader* stream_reader) {
+ scoped_ptr<AndroidStreamReaderURLRequestJob::Delegate>
+ stream_reader_delegate(new StreamReaderDelegate());
+ TestStreamReaderJob* test_stream_reader_job =
+ new TestStreamReaderJob(
+ req_.get(),
+ &network_delegate_,
+ stream_reader_delegate.Pass(),
+ stream_reader);
+ // The Interceptor is owned by the |factory_|.
+ TestJobInterceptor* interceptor = new TestJobInterceptor;
+ interceptor->set_main_intercept_job(test_stream_reader_job);
+ factory_.AddInterceptor(interceptor);
+ }
+
+ MessageLoop loop_;
+ TestURLRequestContext context_;
+ android_webview::AwURLRequestJobFactory factory_;
+ TestDelegate url_request_delegate_;
+ TestNetworkDelegate network_delegate_;
+ scoped_ptr<TestURLRequest> req_;
+};
+
+TEST_F(AndroidStreamReaderURLRequestJobTest, ReadEmptyStream) {
+ scoped_refptr<StrictMock<MockInputStreamReader> > stream_reader =
+ new StrictMock<MockInputStreamReader>();
+ {
+ InSequence s;
+ EXPECT_CALL(*stream_reader, Seek(_))
+ .WillOnce(Return(0));
+ EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Gt(0)))
+ .WillOnce(Return(0));
+ }
+
+ SetUpTestJob(stream_reader);
+
+ req_->Start();
+
+ // The TestDelegate will quit the message loop on request completion.
+ MessageLoop::current()->Run();
+
+ EXPECT_FALSE(url_request_delegate_.request_failed());
+ EXPECT_EQ(1, network_delegate_.completed_requests());
+}
+
+TEST_F(AndroidStreamReaderURLRequestJobTest, ReadPartOfStream) {
+ const int bytes_available = 128;
+ const int offset = 32;
+ const int bytes_to_read = bytes_available - offset;
+ scoped_refptr<StrictMock<MockInputStreamReader> > stream_reader =
+ new StrictMock<MockInputStreamReader>();
+ {
+ InSequence s;
+ EXPECT_CALL(*stream_reader, Seek(_))
+ .WillOnce(Return(bytes_available));
+ EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
+ .WillOnce(Return(bytes_to_read/2));
+ EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
+ .WillOnce(Return(bytes_to_read/2));
+ EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
+ .WillOnce(Return(0));
+ }
+
+ SetUpTestJob(stream_reader);
+
+ SetRange(req_.get(), offset, bytes_available);
+ req_->Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_FALSE(url_request_delegate_.request_failed());
+ EXPECT_EQ(bytes_to_read, url_request_delegate_.bytes_received());
+ EXPECT_EQ(1, network_delegate_.completed_requests());
+}
+
+TEST_F(AndroidStreamReaderURLRequestJobTest,
+ ReadStreamWithMoreAvailableThanActual) {
+ const int bytes_available_reported = 190;
+ const int bytes_available = 128;
+ const int offset = 0;
+ const int bytes_to_read = bytes_available - offset;
+ scoped_refptr<StrictMock<MockInputStreamReader> > stream_reader =
+ new StrictMock<MockInputStreamReader>();
+ {
+ InSequence s;
+ EXPECT_CALL(*stream_reader, Seek(_))
+ .WillOnce(Return(bytes_available_reported));
+ EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
+ .WillOnce(Return(bytes_available));
+ EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
+ .WillOnce(Return(0));
+ }
+
+ SetUpTestJob(stream_reader);
+
+ SetRange(req_.get(), offset, bytes_available_reported);
+ req_->Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_FALSE(url_request_delegate_.request_failed());
+ EXPECT_EQ(bytes_to_read, url_request_delegate_.bytes_received());
+ EXPECT_EQ(1, network_delegate_.completed_requests());
+}
+
+TEST_F(AndroidStreamReaderURLRequestJobTest, DeleteJobMidWaySeek) {
+ const int offset = 20;
+ const int bytes_available = 128;
+ base::RunLoop loop;
+ scoped_refptr<StrictMock<MockInputStreamReader> > stream_reader =
+ new StrictMock<MockInputStreamReader>();
+ EXPECT_CALL(*stream_reader, Seek(_))
+ .WillOnce(DoAll(InvokeWithoutArgs(&loop, &base::RunLoop::Quit),
+ Return(bytes_available)));
+ ON_CALL(*stream_reader, ReadRawData(_, _))
+ .WillByDefault(Return(0));
+
+ SetUpTestJob(stream_reader);
+
+ SetRange(req_.get(), offset, bytes_available);
+ req_->Start();
+
+ loop.Run();
+
+ EXPECT_EQ(0, network_delegate_.completed_requests());
+ req_->Cancel();
+ EXPECT_EQ(1, network_delegate_.completed_requests());
+}
+
+TEST_F(AndroidStreamReaderURLRequestJobTest, DeleteJobMidWayRead) {
+ const int offset = 20;
+ const int bytes_available = 128;
+ base::RunLoop loop;
+ scoped_refptr<StrictMock<MockInputStreamReader> > stream_reader =
+ new StrictMock<MockInputStreamReader>();
+ net::CompletionCallback read_completion_callback;
+ EXPECT_CALL(*stream_reader, Seek(_))
+ .WillOnce(Return(bytes_available));
+ EXPECT_CALL(*stream_reader, ReadRawData(_, _))
+ .WillOnce(DoAll(InvokeWithoutArgs(&loop, &base::RunLoop::Quit),
+ Return(bytes_available)));
+
+ SetUpTestJob(stream_reader);
+
+ SetRange(req_.get(), offset, bytes_available);
+ req_->Start();
+
+ loop.Run();
+
+ EXPECT_EQ(0, network_delegate_.completed_requests());
+ req_->Cancel();
+ EXPECT_EQ(1, network_delegate_.completed_requests());
+}
diff --git a/android_webview/browser/net/input_stream_reader.cc b/android_webview/browser/net/input_stream_reader.cc
new file mode 100644
index 0000000..f927112
--- /dev/null
+++ b/android_webview/browser/net/input_stream_reader.cc
@@ -0,0 +1,98 @@
+// Copyright (c) 2012 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 "android_webview/browser/net/input_stream_reader.h"
+
+#include "android_webview/browser/input_stream.h"
+#include "base/message_loop.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_byte_range.h"
+
+using content::BrowserThread;
+
+namespace android_webview {
+
+InputStreamReader::InputStreamReader(android_webview::InputStream* stream)
+ : stream_(stream) {
+ DCHECK(stream);
+}
+
+InputStreamReader::~InputStreamReader() {
+}
+
+int InputStreamReader::Seek(const net::HttpByteRange& byte_range) {
+ int content_size = 0;
+ net::HttpByteRange verified_byte_range(byte_range);
+
+ int error_code = VerifyRequestedRange(&verified_byte_range, &content_size);
+ if (error_code != net::OK)
+ return error_code;
+
+ error_code = SkipToRequestedRange(verified_byte_range);
+ if (error_code != net::OK)
+ return error_code;
+
+ DCHECK_GE(content_size, 0);
+ return content_size;
+}
+
+int InputStreamReader::ReadRawData(net::IOBuffer* dest, int dest_size) {
+ if (!dest_size)
+ return 0;
+
+ DCHECK_GT(dest_size, 0);
+
+ int bytes_read = 0;
+ if (!stream_->Read(dest, dest_size, &bytes_read))
+ return net::ERR_FAILED;
+ else
+ return bytes_read;
+}
+
+int InputStreamReader::VerifyRequestedRange(net::HttpByteRange* byte_range,
+ int* content_size) {
+ DCHECK(content_size);
+ int32_t size = 0;
+ if (!stream_->BytesAvailable(&size))
+ return net::ERR_FAILED;
+
+ if (size <= 0)
+ return net::OK;
+
+ // Check that the requested range was valid.
+ if (!byte_range->ComputeBounds(size))
+ return net::ERR_REQUEST_RANGE_NOT_SATISFIABLE;
+
+ size = byte_range->last_byte_position() -
+ byte_range->first_byte_position() + 1;
+ DCHECK_GE(size, 0);
+ *content_size = size;
+
+ return net::OK;
+}
+
+int InputStreamReader::SkipToRequestedRange(
+ const net::HttpByteRange& byte_range) {
+ // Skip to the start of the requested data. This has to be done in a loop
+ // because the underlying InputStream is not guaranteed to skip the requested
+ // number of bytes.
+ if (byte_range.IsValid() && byte_range.first_byte_position() > 0) {
+ int64_t bytes_to_skip = byte_range.first_byte_position();
+ do {
+ int64_t skipped = 0;
+ if (!stream_->Skip(bytes_to_skip, &skipped))
+ return net::ERR_FAILED;
+
+ if (skipped <= 0)
+ return net::ERR_REQUEST_RANGE_NOT_SATISFIABLE;
+ DCHECK(skipped <= bytes_to_skip);
+
+ bytes_to_skip -= skipped;
+ } while (bytes_to_skip > 0);
+ }
+ return net::OK;
+}
+
+} // namespace android_webview
diff --git a/android_webview/browser/net/input_stream_reader.h b/android_webview/browser/net/input_stream_reader.h
new file mode 100644
index 0000000..897040e
--- /dev/null
+++ b/android_webview/browser/net/input_stream_reader.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 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 ANDROID_WEBVIEW_NATIVE_INPUT_STREAM_READER_H_
+#define ANDROID_WEBVIEW_NATIVE_INPUT_STREAM_READER_H_
+
+#include "base/memory/ref_counted.h"
+
+namespace net {
+class HttpByteRange;
+class IOBuffer;
+}
+
+namespace android_webview {
+
+class InputStream;
+
+// Class responsible for reading the InputStream.
+class InputStreamReader
+ : public base::RefCountedThreadSafe<InputStreamReader> {
+ public:
+ // The constructor is called on the IO thread, not on the worker thread.
+ InputStreamReader(android_webview::InputStream* stream);
+
+ // Perform a seek operation on the InputStream associated with this job.
+ // On successful completion the InputStream would have skipped reading the
+ // number of bytes equal to the lower range of |byte_range|.
+ // This method should be called on the |g_worker_thread| thread.
+ //
+ // |byte_range| is the range of bytes to be read from |stream|
+ //
+ // A negative return value will indicate an error code, a positive value
+ // will indicate the expected size of the content.
+ virtual int Seek(const net::HttpByteRange& byte_range);
+
+ // Read data from |stream_|. This method should be called on the
+ // |g_worker_thread| thread.
+ //
+ // A negative return value will indicate an error code, a positive value
+ // will indicate the expected size of the content.
+ virtual int ReadRawData(net::IOBuffer* buffer, int buffer_size);
+
+ protected:
+ virtual ~InputStreamReader();
+
+ private:
+ friend class base::RefCountedThreadSafe<InputStreamReader>;
+
+ // Verify the requested range against the stream size.
+ // net::OK is returned on success, the error code otherwise.
+ int VerifyRequestedRange(net::HttpByteRange* byte_range,
+ int* content_size);
+
+ // Skip to the first byte of the requested read range.
+ // net::OK is returned on success, the error code otherwise.
+ int SkipToRequestedRange(const net::HttpByteRange& byte_range);
+
+ android_webview::InputStream* stream_;
+
+ DISALLOW_COPY_AND_ASSIGN(InputStreamReader);
+};
+
+} // namespace android_webview
+
+#endif // ANDROID_WEBVIEW_NATIVE_INPUT_STREAM_READER_H_
diff --git a/android_webview/browser/net/input_stream_reader_unittest.cc b/android_webview/browser/net/input_stream_reader_unittest.cc
new file mode 100644
index 0000000..a218d78
--- /dev/null
+++ b/android_webview/browser/net/input_stream_reader_unittest.cc
@@ -0,0 +1,172 @@
+// Copyright (c) 2012 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 "android_webview/browser/input_stream.h"
+#include "android_webview/browser/net/input_stream_reader.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/io_buffer.h"
+#include "net/http/http_byte_range.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using android_webview::InputStream;
+using android_webview::InputStreamReader;
+using testing::DoAll;
+using testing::Ge;
+using testing::InSequence;
+using testing::Lt;
+using testing::Ne;
+using testing::NotNull;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+class MockInputStream : public InputStream {
+ public:
+ MockInputStream() {}
+ virtual ~MockInputStream() {}
+
+ MOCK_CONST_METHOD1(BytesAvailable, bool(int* bytes_available));
+ MOCK_METHOD2(Skip, bool(int64_t n, int64_t* bytes_skipped));
+ MOCK_METHOD3(Read, bool(net::IOBuffer* dest, int length, int* bytes_read));
+};
+
+class InputStreamReaderTest : public Test {
+ public:
+ InputStreamReaderTest()
+ : input_stream_reader_(new InputStreamReader(&input_stream_)) {
+ }
+ protected:
+ int SeekRange(int first_byte, int last_byte) {
+ net::HttpByteRange byte_range;
+ byte_range.set_first_byte_position(first_byte);
+ byte_range.set_last_byte_position(last_byte);
+ return input_stream_reader_->Seek(byte_range);
+ }
+
+ int ReadRawData(scoped_refptr<net::IOBuffer> buffer, int bytesToRead) {
+ return input_stream_reader_->ReadRawData(buffer.get(), bytesToRead);
+ }
+
+ MockInputStream input_stream_;
+ scoped_refptr<InputStreamReader> input_stream_reader_;
+};
+
+TEST_F(InputStreamReaderTest, BytesAvailableFailurePropagationOnSeek) {
+ EXPECT_CALL(input_stream_, BytesAvailable(NotNull()))
+ .WillOnce(Return(false));
+
+ ASSERT_GT(0, SeekRange(0, 0));
+}
+
+TEST_F(InputStreamReaderTest, SkipFailurePropagationOnSeek) {
+ const int streamSize = 10;
+ const int bytesToSkip = 5;
+
+ EXPECT_CALL(input_stream_, BytesAvailable(NotNull()))
+ .WillOnce(DoAll(SetArgPointee<0>(streamSize),
+ Return(true)));
+
+ EXPECT_CALL(input_stream_, Skip(bytesToSkip, NotNull()))
+ .WillOnce(Return(false));
+
+ ASSERT_GT(0, SeekRange(bytesToSkip, streamSize - 1));
+}
+
+TEST_F(InputStreamReaderTest, SeekToMiddle) {
+ const int streamSize = 10;
+ const int bytesToSkip = 5;
+
+ EXPECT_CALL(input_stream_, BytesAvailable(NotNull()))
+ .WillOnce(DoAll(SetArgPointee<0>(streamSize),
+ Return(true)));
+
+ EXPECT_CALL(input_stream_, Skip(bytesToSkip, NotNull()))
+ .WillOnce(DoAll(SetArgPointee<1>(bytesToSkip),
+ Return(true)));
+
+ ASSERT_EQ(bytesToSkip, SeekRange(bytesToSkip, streamSize - 1));
+}
+
+TEST_F(InputStreamReaderTest, SeekToMiddleInSteps) {
+ const int streamSize = 10;
+ const int bytesToSkip = 5;
+
+ EXPECT_CALL(input_stream_, BytesAvailable(NotNull()))
+ .Times(1)
+ .WillOnce(DoAll(SetArgPointee<0>(streamSize),
+ Return(true)));
+
+ EXPECT_CALL(input_stream_, Skip(_, _))
+ .Times(0);
+ {
+ InSequence s;
+ EXPECT_CALL(input_stream_, Skip(bytesToSkip, NotNull()))
+ .WillOnce(DoAll(SetArgPointee<1>(bytesToSkip - 3),
+ Return(true)))
+ .RetiresOnSaturation();
+ EXPECT_CALL(input_stream_, Skip(3, NotNull()))
+ .WillOnce(DoAll(SetArgPointee<1>(1),
+ Return(true)))
+ .RetiresOnSaturation();
+ EXPECT_CALL(input_stream_, Skip(2, NotNull()))
+ .WillOnce(DoAll(SetArgPointee<1>(2),
+ Return(true)))
+ .RetiresOnSaturation();
+ }
+
+ ASSERT_EQ(bytesToSkip, SeekRange(bytesToSkip, streamSize - 1));
+}
+
+TEST_F(InputStreamReaderTest, SeekEmpty) {
+ EXPECT_CALL(input_stream_, BytesAvailable(NotNull()))
+ .WillOnce(DoAll(SetArgPointee<0>(0),
+ Return(true)));
+
+ ASSERT_EQ(0, SeekRange(0, 0));
+}
+
+TEST_F(InputStreamReaderTest, SeekMoreThanAvailable) {
+ const int bytesAvailable = 256;
+ EXPECT_CALL(input_stream_, BytesAvailable(NotNull()))
+ .WillOnce(DoAll(SetArgPointee<0>(bytesAvailable),
+ Return(true)));
+
+ ASSERT_GT(0, SeekRange(bytesAvailable, 2 * bytesAvailable));
+}
+
+TEST_F(InputStreamReaderTest, ReadFailure) {
+ const int bytesToRead = 128;
+ scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(bytesToRead);
+ EXPECT_CALL(input_stream_, Read(buffer.get(), bytesToRead, NotNull()))
+ .WillOnce(Return(false));
+
+ ASSERT_GT(0, ReadRawData(buffer, bytesToRead));
+}
+
+TEST_F(InputStreamReaderTest, ReadNothing) {
+ const int bytesToRead = 0;
+ // Size of net::IOBuffer can't be 0.
+ scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(1);
+ EXPECT_CALL(input_stream_, Read(buffer.get(), bytesToRead, NotNull()))
+ .Times(0);
+
+ ASSERT_EQ(0, ReadRawData(buffer, bytesToRead));
+}
+
+TEST_F(InputStreamReaderTest, ReadSuccess) {
+ const int bytesToRead = 128;
+ scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(bytesToRead);
+
+ EXPECT_CALL(input_stream_, Read(buffer.get(), bytesToRead, NotNull()))
+ .WillOnce(DoAll(SetArgPointee<2>(bytesToRead),
+ Return(true)));
+
+ ASSERT_EQ(bytesToRead, ReadRawData(buffer, bytesToRead));
+}