Reland of Improve linearized pdf load/show time."

Use received bytes count as the value for progress, not chunks count.

Reason for reland: 
The fix of revert reason has been added.

Bug: 755061

Original change's description:
> Revert "Reland of Improve linearized pdf load/show time."
>
> This reverts commit 9a6d1487991b9824b499aff0ce1c9b68834a14cd.
>
> Reason for revert: https://crbug.com/755061. This appears to be
> causing a serious regression in loading. The loading bar does not
> appear as expected and appears all at once at the end as the screen
> displays. There is 5-6seconds of nothing happening which makes it look
> like the browser has stopped working.
>
>
> Original change's description:
> > Reland of Improve linearized pdf load/show time.
> >
> > XFA forms loading has been fixed.
> > Now for document with single non XFA page, the form load first.
> > This is necessary for correct loading pages, because in XFA document
> > the page count and them contents may be changed after loading form.
> >
> > See
> > https://codereview.chromium.org/2558573002/
> >
> > For test this:
> >  build chromium pdf with XFA support
> >  and open any document from
> >  https://www.idrsolutions.com/jpdfforms/xfa-html5-example-conversions/
> >
> > Original CL:
> >  https://codereview.chromium.org/2455403002/
> >
> > Original description:
> >  Improve linearized pdf load/show time.
> >  Reduce Pdf Plugin's count of reconnects.
> >  Add tests for PDFPlugin DocumentLoader.
> >
> >  DocumentLoader was splitted into separate components, and missing tests was added for them.
> >
> >  The main ideas in this CL are:
> >
> >  1) Do not reset browser initiated connection at start (includes case when we can use range requests), if we request data near current downloading position.
> >  2) Request as much data as we can on each request, and continue loading data using current range request. (like tape rewind)
> >  3) Isolate RangeRequest logic into DocumentLoader. Method OnPendingRequestComplete is called, when we receive requested data (main connection, or Range connection). (like tape playing without rewing).
> >  4) Fill this logic by tests.
> >
> >  Example URL:
> >  http://www.major-landrover.ru/upload/attachments/f/9/f96aab07dab04ae89c8a509ec1ef2b31.pdf
> >  Comparison of changes:
> >  https://drive.google.com/file/d/0BzWfMBOuik2QNGg0SG93Y3lpUlE/view?usp=sharing
> >
> > Change-Id: I97bb25d2e82bcb4ba2e060af8128f49b9c0680d9
> > Reviewed-on: https://chromium-review.googlesource.com/581292
> > Reviewed-by: Robert Sesek <[email protected]>
> > Reviewed-by: Dan Sinclair <[email protected]>
> > Commit-Queue: Art Snake <[email protected]>
> > Cr-Commit-Position: refs/heads/master@{#489755}
>
>

[email protected]

Change-Id: I78e10565f639c26faae29b3cf854419208af8665
Reviewed-on: https://chromium-review.googlesource.com/615302
Commit-Queue: Lei Zhang <[email protected]>
Reviewed-by: (000 09-08 - 09-18) dsinclair <[email protected]>
Cr-Commit-Position: refs/heads/master@{#501344}
diff --git a/pdf/BUILD.gn b/pdf/BUILD.gn
index 0871c62..28949f8 100644
--- a/pdf/BUILD.gn
+++ b/pdf/BUILD.gn
@@ -25,10 +25,10 @@
       "//ppapi/cpp:objects",
       "//ppapi/cpp/private:internal_module",
       "//ui/base",
+      "//ui/gfx/range",
     ]
 
     sources = [
-      "chunk_stream.cc",
       "chunk_stream.h",
       "document_loader.cc",
       "document_loader.h",
@@ -45,6 +45,13 @@
       "pdf_engine.h",
       "preview_mode_client.cc",
       "preview_mode_client.h",
+      "range_set.cc",
+      "range_set.h",
+      "timer.cc",
+      "timer.h",
+      "url_loader_wrapper.h",
+      "url_loader_wrapper_impl.cc",
+      "url_loader_wrapper_impl.h",
     ]
 
     if (pdf_engine == 0) {
@@ -80,6 +87,8 @@
   test("pdf_unittests") {
     sources = [
       "chunk_stream_unittest.cc",
+      "document_loader_unittest.cc",
+      "range_set_unittest.cc",
       "run_all_unittests.cc",
     ]
 
@@ -91,6 +100,7 @@
       "//ppapi/cpp",
       "//testing/gmock",
       "//testing/gtest",
+      "//ui/gfx/range",
     ]
   }
 } else {
diff --git a/pdf/DEPS b/pdf/DEPS
index b7a8a17..d088805 100644
--- a/pdf/DEPS
+++ b/pdf/DEPS
@@ -4,5 +4,6 @@
   "+ppapi",
   "+ui/base/window_open_disposition.h",
   "+ui/events/keycodes/keyboard_codes.h",
+  "+ui/gfx/range/range.h",
   "+v8/include/v8.h"
 ]
diff --git a/pdf/chunk_stream.cc b/pdf/chunk_stream.cc
deleted file mode 100644
index 4eae4a9..0000000
--- a/pdf/chunk_stream.cc
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "pdf/chunk_stream.h"
-
-#include <stddef.h>
-#include <string.h>
-
-#include <algorithm>
-#include <limits>
-
-namespace chrome_pdf {
-
-ChunkStream::ChunkStream() : stream_size_(0) {}
-
-ChunkStream::~ChunkStream() {}
-
-void ChunkStream::Clear() {
-  chunks_.clear();
-  data_.clear();
-  stream_size_ = 0;
-}
-
-void ChunkStream::Preallocate(size_t stream_size) {
-  data_.reserve(stream_size);
-  stream_size_ = stream_size;
-}
-
-size_t ChunkStream::GetSize() const {
-  return data_.size();
-}
-
-bool ChunkStream::WriteData(size_t offset, void* buffer, size_t size) {
-  if (std::numeric_limits<size_t>::max() - size < offset)
-    return false;
-
-  if (data_.size() < offset + size)
-    data_.resize(offset + size);
-
-  memcpy(&data_[offset], buffer, size);
-
-  if (chunks_.empty()) {
-    chunks_[offset] = size;
-    return true;
-  }
-
-  auto start = GetStartChunk(offset);
-  auto end = chunks_.upper_bound(offset + size);
-  if (start == end) {  // No chunks to merge.
-    chunks_[offset] = size;
-    return true;
-  }
-
-  auto prev = end;
-  --prev;
-  size_t prev_size = prev->first + prev->second;
-
-  size_t new_offset = std::min<size_t>(start->first, offset);
-  size_t new_size = std::max<size_t>(prev_size, offset + size) - new_offset;
-
-  chunks_.erase(start, end);
-  chunks_[new_offset] = new_size;
-  return true;
-}
-
-bool ChunkStream::ReadData(size_t offset, size_t size, void* buffer) const {
-  if (!IsRangeAvailable(offset, size))
-    return false;
-
-  memcpy(buffer, &data_[offset], size);
-  return true;
-}
-
-bool ChunkStream::GetMissedRanges(
-    size_t offset,
-    size_t size,
-    std::vector<std::pair<size_t, size_t>>* ranges) const {
-  if (IsRangeAvailable(offset, size))
-    return false;
-
-  ranges->clear();
-  if (chunks_.empty()) {
-    ranges->push_back(std::pair<size_t, size_t>(offset, size));
-    return true;
-  }
-
-  auto start = GetStartChunk(offset);
-  auto end = chunks_.upper_bound(offset + size);
-  if (start == end) {  // No data in the current range available.
-    ranges->push_back(std::pair<size_t, size_t>(offset, size));
-    return true;
-  }
-
-  size_t cur_offset = offset;
-  for (auto it = start; it != end; ++it) {
-    if (cur_offset < it->first) {
-      size_t new_size = it->first - cur_offset;
-      ranges->push_back(std::pair<size_t, size_t>(cur_offset, new_size));
-      cur_offset = it->first + it->second;
-    } else if (cur_offset < it->first + it->second) {
-      cur_offset = it->first + it->second;
-    }
-  }
-
-  // Add last chunk.
-  if (cur_offset < offset + size) {
-    ranges->push_back(
-        std::pair<size_t, size_t>(cur_offset, offset + size - cur_offset));
-  }
-
-  return true;
-}
-
-bool ChunkStream::IsRangeAvailable(size_t offset, size_t size) const {
-  if (chunks_.empty())
-    return false;
-
-  if (std::numeric_limits<size_t>::max() - size < offset)
-    return false;
-
-  auto it = chunks_.upper_bound(offset);
-  if (it == chunks_.begin())
-    return false;  // No chunks includes offset byte.
-
-  --it;  // Now it starts equal or before offset.
-  return it->first + it->second >= offset + size;
-}
-
-size_t ChunkStream::GetFirstMissingByte() const {
-  if (chunks_.empty())
-    return 0;
-  auto begin = chunks_.begin();
-  return begin->first > 0 ? 0 : begin->second;
-}
-
-size_t ChunkStream::GetFirstMissingByteInInterval(size_t offset) const {
-  if (chunks_.empty())
-    return 0;
-  auto it = chunks_.upper_bound(offset);
-  if (it == chunks_.begin())
-    return 0;
-  --it;
-  return it->first + it->second;
-}
-
-size_t ChunkStream::GetLastMissingByteInInterval(size_t offset) const {
-  if (chunks_.empty())
-    return stream_size_ - 1;
-  auto it = chunks_.upper_bound(offset);
-  if (it == chunks_.end())
-    return stream_size_ - 1;
-  return it->first - 1;
-}
-
-std::map<size_t, size_t>::const_iterator ChunkStream::GetStartChunk(
-    size_t offset) const {
-  auto start = chunks_.upper_bound(offset);
-  if (start != chunks_.begin())
-    --start;  // start now points to the key equal or lower than offset.
-  if (start->first + start->second < offset)
-    ++start;  // start element is entirely before current chunk, skip it.
-  return start;
-}
-
-}  // namespace chrome_pdf
diff --git a/pdf/chunk_stream.h b/pdf/chunk_stream.h
index 762bc39..b8b3c83 100644
--- a/pdf/chunk_stream.h
+++ b/pdf/chunk_stream.h
@@ -6,50 +6,103 @@
 #define PDF_CHUNK_STREAM_H_
 
 #include <stddef.h>
+#include <string.h>
 
-#include <map>
-#include <utility>
+#include <algorithm>
+#include <array>
+#include <memory>
 #include <vector>
 
+#include "pdf/range_set.h"
+
 namespace chrome_pdf {
 
 // This class collects a chunks of data into one data stream. Client can check
 // if data in certain range is available, and get missing chunks of data.
+template <uint32_t N>
 class ChunkStream {
  public:
-  ChunkStream();
-  ~ChunkStream();
+  static constexpr uint32_t kChunkSize = N;
+  using ChunkData = typename std::array<unsigned char, N>;
 
-  void Clear();
+  ChunkStream() {}
+  ~ChunkStream() {}
 
-  void Preallocate(size_t stream_size);
-  size_t GetSize() const;
+  void SetChunkData(uint32_t chunk_index, std::unique_ptr<ChunkData> data) {
+    if (!data)
+      return;
+    if (chunk_index >= data_.size()) {
+      data_.resize(chunk_index + 1);
+    }
+    if (!data_[chunk_index]) {
+      ++filled_chunks_count_;
+    }
+    data_[chunk_index] = std::move(data);
+    filled_chunks_.Union(gfx::Range(chunk_index, chunk_index + 1));
+  }
 
-  bool WriteData(size_t offset, void* buffer, size_t size);
-  bool ReadData(size_t offset, size_t size, void* buffer) const;
+  bool ReadData(const gfx::Range& range, void* buffer) const {
+    if (!IsRangeAvailable(range)) {
+      return false;
+    }
+    unsigned char* data_buffer = static_cast<unsigned char*>(buffer);
+    uint32_t start = range.start();
+    while (start != range.end()) {
+      const uint32_t chunk_index = GetChunkIndex(start);
+      const uint32_t chunk_start = start % kChunkSize;
+      const uint32_t len =
+          std::min(kChunkSize - chunk_start, range.end() - start);
+      memcpy(data_buffer, data_[chunk_index]->data() + chunk_start, len);
+      data_buffer += len;
+      start += len;
+    }
+    return true;
+  }
 
-  // Returns vector of pairs where first is an offset, second is a size.
-  bool GetMissedRanges(size_t offset,
-                       size_t size,
-                       std::vector<std::pair<size_t, size_t>>* ranges) const;
-  bool IsRangeAvailable(size_t offset, size_t size) const;
-  size_t GetFirstMissingByte() const;
+  uint32_t GetChunkIndex(uint32_t offset) const { return offset / kChunkSize; }
 
-  // Finds the first byte of the missing byte interval that offset belongs to.
-  size_t GetFirstMissingByteInInterval(size_t offset) const;
-  // Returns the last byte of the missing byte interval that offset belongs to.
-  size_t GetLastMissingByteInInterval(size_t offset) const;
+  gfx::Range GetChunksRange(uint32_t offset, uint32_t size) const {
+    return gfx::Range(GetChunkIndex(offset),
+                      GetChunkIndex(offset + size + kChunkSize - 1));
+  }
+
+  bool IsRangeAvailable(const gfx::Range& range) const {
+    if (!range.IsValid() || range.is_reversed() ||
+        (eof_pos_ > 0 && eof_pos_ < range.end()))
+      return false;
+    if (range.is_empty())
+      return true;
+    const gfx::Range chunks_range(GetChunkIndex(range.start()),
+                                  GetChunkIndex(range.end() + kChunkSize - 1));
+    return filled_chunks_.Contains(chunks_range);
+  }
+
+  void set_eof_pos(uint32_t eof_pos) { eof_pos_ = eof_pos; }
+  uint32_t eof_pos() const { return eof_pos_; }
+
+  const RangeSet& filled_chunks() const { return filled_chunks_; }
+
+  bool IsComplete() const {
+    return eof_pos_ > 0 && IsRangeAvailable(gfx::Range(0, eof_pos_));
+  }
+
+  void Clear() {
+    data_.clear();
+    eof_pos_ = 0;
+    filled_chunks_.Clear();
+    filled_chunks_count_ = 0;
+  }
+
+  uint32_t filled_chunks_count() const { return filled_chunks_count_; }
+  uint32_t total_chunks_count() const {
+    return GetChunkIndex(eof_pos_ + kChunkSize - 1);
+  }
 
  private:
-  std::map<size_t, size_t>::const_iterator GetStartChunk(size_t offset) const;
-
-  std::vector<unsigned char> data_;
-
-  // Key: offset of the chunk.
-  // Value: size of the chunk.
-  std::map<size_t, size_t> chunks_;
-
-  size_t stream_size_;
+  std::vector<std::unique_ptr<ChunkData>> data_;
+  uint32_t eof_pos_ = 0;
+  RangeSet filled_chunks_;
+  uint32_t filled_chunks_count_ = 0;
 };
 
 };  // namespace chrome_pdf
diff --git a/pdf/chunk_stream_unittest.cc b/pdf/chunk_stream_unittest.cc
index af768502..a031da2 100644
--- a/pdf/chunk_stream_unittest.cc
+++ b/pdf/chunk_stream_unittest.cc
@@ -4,14 +4,96 @@
 
 #include "pdf/chunk_stream.h"
 
+#include <array>
+#include <memory>
+
+#include "base/memory/ptr_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace chrome_pdf {
+namespace {
+typedef ChunkStream<10> TestChunkStream;
 
-TEST(ChunkStreamTest, Simple) {
-  ChunkStream stream;
-  stream.Preallocate(1000);
-  EXPECT_FALSE(stream.IsRangeAvailable(100, 500));
+std::unique_ptr<TestChunkStream::ChunkData> CreateChunkData() {
+  return base::MakeUnique<TestChunkStream::ChunkData>();
+}
+}  // namespace
+
+TEST(ChunkStreamTest, InRow) {
+  TestChunkStream stream;
+  EXPECT_FALSE(stream.IsComplete());
+  EXPECT_FALSE(stream.IsRangeAvailable(gfx::Range(0, 10)));
+  stream.SetChunkData(0, CreateChunkData());
+  EXPECT_TRUE(stream.IsRangeAvailable(gfx::Range(0, 10)));
+  EXPECT_FALSE(stream.IsRangeAvailable(gfx::Range(0, 20)));
+  stream.SetChunkData(1, CreateChunkData());
+  EXPECT_TRUE(stream.IsRangeAvailable(gfx::Range(0, 20)));
+  EXPECT_FALSE(stream.IsRangeAvailable(gfx::Range(0, 30)));
+  stream.SetChunkData(2, CreateChunkData());
+  EXPECT_TRUE(stream.IsRangeAvailable(gfx::Range(0, 30)));
+  stream.set_eof_pos(25);
+  EXPECT_FALSE(stream.IsRangeAvailable(gfx::Range(0, 30)));
+  EXPECT_TRUE(stream.IsRangeAvailable(gfx::Range(0, 25)));
+  EXPECT_TRUE(stream.IsComplete());
 }
 
+TEST(ChunkStreamTest, InBackRow) {
+  TestChunkStream stream;
+  stream.set_eof_pos(25);
+  EXPECT_FALSE(stream.IsComplete());
+  EXPECT_FALSE(stream.IsRangeAvailable(gfx::Range(20, 25)));
+  stream.SetChunkData(2, CreateChunkData());
+  EXPECT_TRUE(stream.IsRangeAvailable(gfx::Range(20, 25)));
+  EXPECT_FALSE(stream.IsRangeAvailable(gfx::Range(10, 20)));
+  stream.SetChunkData(1, CreateChunkData());
+  EXPECT_TRUE(stream.IsRangeAvailable(gfx::Range(10, 20)));
+  EXPECT_FALSE(stream.IsRangeAvailable(gfx::Range(0, 10)));
+  stream.SetChunkData(0, CreateChunkData());
+  EXPECT_TRUE(stream.IsRangeAvailable(gfx::Range(0, 10)));
+  EXPECT_TRUE(stream.IsComplete());
+}
+
+TEST(ChunkStreamTest, FillGap) {
+  TestChunkStream stream;
+  stream.set_eof_pos(25);
+  EXPECT_FALSE(stream.IsComplete());
+  stream.SetChunkData(0, CreateChunkData());
+  stream.SetChunkData(2, CreateChunkData());
+  EXPECT_TRUE(stream.IsRangeAvailable(gfx::Range(0, 10)));
+  EXPECT_TRUE(stream.IsRangeAvailable(gfx::Range(20, 25)));
+  EXPECT_FALSE(stream.IsRangeAvailable(gfx::Range(0, 25)));
+  stream.SetChunkData(1, CreateChunkData());
+  EXPECT_TRUE(stream.IsRangeAvailable(gfx::Range(0, 25)));
+  EXPECT_TRUE(stream.IsComplete());
+}
+
+TEST(ChunkStreamTest, Read) {
+  TestChunkStream stream;
+  stream.set_eof_pos(25);
+  const unsigned char start_value = 33;
+  unsigned char value = start_value;
+  auto chunk_0 = CreateChunkData();
+  for (auto& it : *chunk_0) {
+    it = ++value;
+  }
+  auto chunk_1 = CreateChunkData();
+  for (auto& it : *chunk_1) {
+    it = ++value;
+  }
+  auto chunk_2 = CreateChunkData();
+  for (auto& it : *chunk_2) {
+    it = ++value;
+  }
+  stream.SetChunkData(0, std::move(chunk_0));
+  stream.SetChunkData(2, std::move(chunk_2));
+  stream.SetChunkData(1, std::move(chunk_1));
+
+  std::array<unsigned char, 25> result_data;
+  EXPECT_TRUE(stream.ReadData(gfx::Range(0, 25), result_data.data()));
+
+  value = start_value;
+  for (const auto& it : result_data) {
+    EXPECT_EQ(++value, it);
+  }
+}
 }  // namespace chrome_pdf
diff --git a/pdf/document_loader.cc b/pdf/document_loader.cc
index 8fbe12fc..ca9fe8e 100644
--- a/pdf/document_loader.cc
+++ b/pdf/document_loader.cc
@@ -7,69 +7,25 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <algorithm>
+
 #include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/numerics/safe_math.h"
 #include "base/strings/string_util.h"
-#include "net/http/http_util.h"
+#include "pdf/url_loader_wrapper.h"
 #include "ppapi/c/pp_errors.h"
-#include "ppapi/cpp/url_loader.h"
-#include "ppapi/cpp/url_request_info.h"
-#include "ppapi/cpp/url_response_info.h"
+#include "ui/gfx/range/range.h"
 
 namespace chrome_pdf {
 
 namespace {
 
-// If the headers have a byte-range response, writes the start and end
-// positions and returns true if at least the start position was parsed.
-// The end position will be set to 0 if it was not found or parsed from the
-// response.
-// Returns false if not even a start position could be parsed.
-bool GetByteRange(const std::string& headers, uint32_t* start, uint32_t* end) {
-  net::HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\n");
-  while (it.GetNext()) {
-    if (base::LowerCaseEqualsASCII(it.name(), "content-range")) {
-      std::string range = it.values().c_str();
-      if (base::StartsWith(range, "bytes",
-                           base::CompareCase::INSENSITIVE_ASCII)) {
-        range = range.substr(strlen("bytes"));
-        std::string::size_type pos = range.find('-');
-        std::string range_end;
-        if (pos != std::string::npos)
-          range_end = range.substr(pos + 1);
-        base::TrimWhitespaceASCII(range, base::TRIM_LEADING, &range);
-        base::TrimWhitespaceASCII(range_end, base::TRIM_LEADING, &range_end);
-        *start = atoi(range.c_str());
-        *end = atoi(range_end.c_str());
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-// If the headers have a multi-part response, returns the boundary name.
-// Otherwise returns an empty string.
-std::string GetMultiPartBoundary(const std::string& headers) {
-  net::HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\n");
-  while (it.GetNext()) {
-    if (!base::LowerCaseEqualsASCII(it.name(), "content-type"))
-      continue;
-
-    std::string type = base::ToLowerASCII(it.values());
-    if (!base::StartsWith(type, "multipart/", base::CompareCase::SENSITIVE))
-      continue;
-
-    static constexpr char kBoundary[] = "boundary=";
-    const char* boundary = strstr(type.c_str(), kBoundary);
-    if (!boundary) {
-      NOTREACHED();
-      return std::string();
-    }
-
-    return std::string(boundary + strlen(kBoundary));
-  }
-  return std::string();
-}
+// The distance from last received chunk, when we wait requesting data, using
+// current connection (like playing a cassette tape) and do not send new range
+// request (like rewind a cassette tape, and continue playing after).
+// Experimentally chosen value.
+const int kChunkCloseDistance = 10;
 
 // Return true if the HTTP response of |loader| is a successful one and loading
 // should continue. 4xx error indicate subsequent requests will fail too.
@@ -77,81 +33,55 @@
 // indicates a redirect was returned which won't be successful because we
 // disable following redirects for PDF loading (we assume they are already
 // resolved by the browser.
-bool ResponseStatusSuccess(const pp::URLLoader& loader) {
-  int32_t http_code = loader.GetResponseInfo().GetStatusCode();
+bool ResponseStatusSuccess(const URLLoaderWrapper* loader) {
+  int32_t http_code = loader->GetStatusCode();
   return (http_code < 400 && http_code != 301) || http_code >= 500;
 }
 
 bool IsValidContentType(const std::string& type) {
-  return base::EndsWith(type, "/pdf", base::CompareCase::INSENSITIVE_ASCII) ||
-         base::EndsWith(type, ".pdf", base::CompareCase::INSENSITIVE_ASCII) ||
-         base::EndsWith(type, "/x-pdf", base::CompareCase::INSENSITIVE_ASCII) ||
-         base::EndsWith(type, "/*", base::CompareCase::INSENSITIVE_ASCII) ||
-         base::EndsWith(type, "/acrobat",
-                        base::CompareCase::INSENSITIVE_ASCII) ||
-         base::EndsWith(type, "/unknown", base::CompareCase::INSENSITIVE_ASCII);
-}
-
-bool IsDoubleNewlines(const char* buffer, int index) {
-  DCHECK_GE(index, 2);
-  static constexpr char kLF2[] = "\n\n";
-  static constexpr char kCRLF2[] = "\r\n\r\n";
-  if (strncmp(&buffer[index - 2], kLF2, strlen(kLF2)) == 0)
-    return true;
-  return index >= 4 && strncmp(&buffer[index - 4], kCRLF2, strlen(kCRLF2)) == 0;
+  return (
+      base::EndsWith(type, "/pdf", base::CompareCase::INSENSITIVE_ASCII) ||
+      base::EndsWith(type, ".pdf", base::CompareCase::INSENSITIVE_ASCII) ||
+      base::EndsWith(type, "/x-pdf", base::CompareCase::INSENSITIVE_ASCII) ||
+      base::EndsWith(type, "/*", base::CompareCase::INSENSITIVE_ASCII) ||
+      base::EndsWith(type, "/octet-stream",
+                     base::CompareCase::INSENSITIVE_ASCII) ||
+      base::EndsWith(type, "/acrobat", base::CompareCase::INSENSITIVE_ASCII) ||
+      base::EndsWith(type, "/unknown", base::CompareCase::INSENSITIVE_ASCII));
 }
 
 }  // namespace
 
 DocumentLoader::Client::~Client() {}
 
-DocumentLoader::DocumentLoader(Client* client)
-    : client_(client),
-      partial_document_(false),
-      request_pending_(false),
-      current_pos_(0),
-      current_chunk_size_(0),
-      current_chunk_read_(0),
-      document_size_(0),
-      header_request_(true),
-      is_multipart_(false) {
-  loader_factory_.Initialize(this);
+DocumentLoader::Chunk::Chunk() {}
+
+DocumentLoader::Chunk::~Chunk() {}
+
+void DocumentLoader::Chunk::Clear() {
+  chunk_index = 0;
+  data_size = 0;
+  chunk_data.reset();
 }
 
+DocumentLoader::DocumentLoader(Client* client)
+    : client_(client), loader_factory_(this) {}
+
 DocumentLoader::~DocumentLoader() {}
 
-bool DocumentLoader::Init(const pp::URLLoader& loader,
-                          const std::string& url,
-                          const std::string& headers) {
+bool DocumentLoader::Init(std::unique_ptr<URLLoaderWrapper> loader,
+                          const std::string& url) {
   DCHECK(url_.empty());
+  DCHECK(!loader_);
 
   // Check that the initial response status is a valid one.
-  if (!ResponseStatusSuccess(loader))
+  if (!ResponseStatusSuccess(loader.get()))
     return false;
 
-  url_ = url;
-  loader_ = loader;
-
-  std::string response_headers;
-  if (!headers.empty()) {
-    response_headers = headers;
-  } else {
-    pp::URLResponseInfo response = loader_.GetResponseInfo();
-    pp::Var headers_var = response.GetHeaders();
-
-    if (headers_var.is_string()) {
-      response_headers = headers_var.AsString();
-    }
-  }
-
-  bool accept_ranges_bytes = false;
-  bool content_encoded = false;
-  uint32_t content_length = 0;
-  std::string type;
-  std::string disposition;
+  std::string type = loader->GetContentType();
 
   // This happens for PDFs not loaded from http(s) sources.
-  if (response_headers == "Content-Type: text/plain") {
+  if (type == "text/plain") {
     if (!base::StartsWith(url, "http://",
                           base::CompareCase::INSENSITIVE_ASCII) &&
         !base::StartsWith(url, "https://",
@@ -159,106 +89,85 @@
       type = "application/pdf";
     }
   }
-  if (type.empty() && !response_headers.empty()) {
-    net::HttpUtil::HeadersIterator it(response_headers.begin(),
-                                      response_headers.end(), "\n");
-    while (it.GetNext()) {
-      if (base::LowerCaseEqualsASCII(it.name(), "content-length")) {
-        content_length = atoi(it.values().c_str());
-      } else if (base::LowerCaseEqualsASCII(it.name(), "accept-ranges")) {
-        accept_ranges_bytes = base::LowerCaseEqualsASCII(it.values(), "bytes");
-      } else if (base::LowerCaseEqualsASCII(it.name(), "content-encoding")) {
-        content_encoded = true;
-      } else if (base::LowerCaseEqualsASCII(it.name(), "content-type")) {
-        type = it.values();
-        size_t semi_colon_pos = type.find(';');
-        if (semi_colon_pos != std::string::npos) {
-          type = type.substr(0, semi_colon_pos);
-        }
-        TrimWhitespaceASCII(type, base::TRIM_ALL, &type);
-      } else if (base::LowerCaseEqualsASCII(it.name(), "content-disposition")) {
-        disposition = it.values();
-      }
-    }
-  }
   if (!type.empty() && !IsValidContentType(type))
     return false;
-  if (base::StartsWith(disposition, "attachment",
+
+  if (base::StartsWith(loader->GetContentDisposition(), "attachment",
                        base::CompareCase::INSENSITIVE_ASCII))
     return false;
 
-  if (content_length > 0)
-    chunk_stream_.Preallocate(content_length);
+  url_ = url;
+  loader_ = std::move(loader);
 
-  document_size_ = content_length;
-  requests_count_ = 0;
-
-  // Enable partial loading only if file size is above the threshold.
-  // It will allow avoiding latency for multiple requests.
-  if (content_length > kMinFileSize && accept_ranges_bytes &&
-      !content_encoded) {
-    LoadPartialDocument();
-  } else {
-    LoadFullDocument();
+  if (!loader_->IsContentEncoded()) {
+    chunk_stream_.set_eof_pos(std::max(0, loader_->GetContentLength()));
   }
+  int64_t bytes_received = 0;
+  int64_t total_bytes_to_be_received = 0;
+  if (!chunk_stream_.eof_pos() &&
+      loader_->GetDownloadProgress(&bytes_received,
+                                   &total_bytes_to_be_received)) {
+    chunk_stream_.set_eof_pos(
+        std::max(0, static_cast<int>(total_bytes_to_be_received)));
+  }
+
+  SetPartialLoadingEnabled(
+      partial_loading_enabled_ &&
+      !base::StartsWith(url, "file://", base::CompareCase::INSENSITIVE_ASCII) &&
+      loader_->IsAcceptRangesBytes() && !loader_->IsContentEncoded() &&
+      GetDocumentSize());
+
+  ReadMore();
   return true;
 }
 
-void DocumentLoader::LoadPartialDocument() {
-  // The current request is a full request (not a range request) so it starts at
-  // 0 and ends at |document_size_|.
-  current_chunk_size_ = document_size_;
-  current_pos_ = 0;
-  current_request_offset_ = 0;
-  current_request_size_ = 0;
-  current_request_extended_size_ = document_size_;
-  request_pending_ = true;
-
-  partial_document_ = true;
-  header_request_ = true;
-  ReadMore();
-}
-
-void DocumentLoader::LoadFullDocument() {
-  partial_document_ = false;
-  chunk_buffer_.clear();
-  ReadMore();
-}
-
 bool DocumentLoader::IsDocumentComplete() const {
-  if (document_size_ == 0)  // Document size unknown.
-    return false;
-  return IsDataAvailable(0, document_size_);
+  return chunk_stream_.IsComplete();
 }
 
-uint32_t DocumentLoader::GetAvailableData() const {
-  if (document_size_ == 0)  // Document size unknown.
-    return current_pos_;
-
-  std::vector<std::pair<size_t, size_t>> ranges;
-  chunk_stream_.GetMissedRanges(0, document_size_, &ranges);
-  uint32_t available = document_size_;
-  for (const auto& range : ranges)
-    available -= range.second;
-  return available;
+uint32_t DocumentLoader::GetDocumentSize() const {
+  return chunk_stream_.eof_pos();
 }
 
 void DocumentLoader::ClearPendingRequests() {
-  pending_requests_.clear();
+  pending_requests_.Clear();
 }
 
 bool DocumentLoader::GetBlock(uint32_t position,
                               uint32_t size,
                               void* buf) const {
-  return chunk_stream_.ReadData(position, size, buf);
+  base::CheckedNumeric<uint32_t> addition_result = position;
+  addition_result += size;
+  if (!addition_result.IsValid())
+    return false;
+  return chunk_stream_.ReadData(
+      gfx::Range(position, addition_result.ValueOrDie()), buf);
 }
 
 bool DocumentLoader::IsDataAvailable(uint32_t position, uint32_t size) const {
-  return chunk_stream_.IsRangeAvailable(position, size);
+  base::CheckedNumeric<uint32_t> addition_result = position;
+  addition_result += size;
+  if (!addition_result.IsValid())
+    return false;
+  return chunk_stream_.IsRangeAvailable(
+      gfx::Range(position, addition_result.ValueOrDie()));
 }
 
 void DocumentLoader::RequestData(uint32_t position, uint32_t size) {
-  DCHECK(partial_document_);
+  if (!size || IsDataAvailable(position, size)) {
+    return;
+  }
+  {
+    // Check integer overflow.
+    base::CheckedNumeric<uint32_t> addition_result = position;
+    addition_result += size;
+    if (!addition_result.IsValid())
+      return;
+  }
+
+  if (GetDocumentSize() && (position + size > GetDocumentSize())) {
+    return;
+  }
 
   // We have some artefact request from
   // PDFiumEngine::OnDocumentComplete() -> FPDFAvail_IsPageAvail after
@@ -267,303 +176,217 @@
   // Bug: http://code.google.com/p/chromium/issues/detail?id=79996
   // Test url:
   // http://www.icann.org/en/correspondence/holtzman-to-jeffrey-02mar11-en.pdf
-  if (IsDocumentComplete())
+  if (!loader_)
     return;
 
-  pending_requests_.push_back(std::pair<size_t, size_t>(position, size));
-  DownloadPendingRequests();
+  RangeSet requested_chunks(chunk_stream_.GetChunksRange(position, size));
+  requested_chunks.Subtract(chunk_stream_.filled_chunks());
+  if (requested_chunks.IsEmpty()) {
+    NOTREACHED();
+    return;
+  }
+  pending_requests_.Union(requested_chunks);
 }
 
-void DocumentLoader::RemoveCompletedRanges() {
-  // Split every request that has been partially downloaded already into smaller
-  // requests.
-  std::vector<std::pair<size_t, size_t>> ranges;
-  auto it = pending_requests_.begin();
-  while (it != pending_requests_.end()) {
-    chunk_stream_.GetMissedRanges(it->first, it->second, &ranges);
-    pending_requests_.insert(it, ranges.begin(), ranges.end());
-    ranges.clear();
-    pending_requests_.erase(it++);
+void DocumentLoader::SetPartialLoadingEnabled(bool enabled) {
+  partial_loading_enabled_ = enabled;
+  if (!enabled) {
+    is_partial_loader_active_ = false;
   }
 }
 
-void DocumentLoader::DownloadPendingRequests() {
-  if (request_pending_)
-    return;
+bool DocumentLoader::ShouldCancelLoading() const {
+  if (!loader_)
+    return true;
+  if (!partial_loading_enabled_ || pending_requests_.IsEmpty())
+    return false;
+  const gfx::Range current_range(chunk_.chunk_index,
+                                 chunk_.chunk_index + kChunkCloseDistance);
+  return !pending_requests_.Intersects(current_range);
+}
 
-  uint32_t pos;
-  uint32_t size;
-  if (pending_requests_.empty()) {
-    // If the document is not complete and we have no outstanding requests,
-    // download what's left for as long as no other request gets added to
-    // |pending_requests_|.
-    pos = chunk_stream_.GetFirstMissingByte();
-    if (pos >= document_size_) {
-      // We're done downloading the document.
-      return;
-    }
-    // Start with size 0, we'll set |current_request_extended_size_| to > 0.
-    // This way this request will get cancelled as soon as the renderer wants
-    // another portion of the document.
-    size = 0;
-  } else {
-    RemoveCompletedRanges();
+void DocumentLoader::ContinueDownload() {
+  if (!ShouldCancelLoading())
+    return ReadMore();
+  DCHECK(partial_loading_enabled_);
+  DCHECK(!IsDocumentComplete());
+  DCHECK(GetDocumentSize());
 
-    pos = pending_requests_.front().first;
-    size = pending_requests_.front().second;
-    if (IsDataAvailable(pos, size)) {
-      ReadComplete();
-      return;
-    }
+  const uint32_t range_start =
+      pending_requests_.IsEmpty() ? 0 : pending_requests_.First().start();
+  RangeSet candidates_for_request(
+      gfx::Range(range_start, chunk_stream_.total_chunks_count()));
+  candidates_for_request.Subtract(chunk_stream_.filled_chunks());
+  DCHECK(!candidates_for_request.IsEmpty());
+  gfx::Range next_request = candidates_for_request.First();
+  if (candidates_for_request.Size() == 1 &&
+      next_request.length() < kChunkCloseDistance) {
+    // We have only request at the end, try to enlarge it to improve back order
+    // reading.
+    const int additional_chunks_count =
+        kChunkCloseDistance - next_request.length();
+    int new_start = std::max(
+        0, static_cast<int>(next_request.start()) - additional_chunks_count);
+    candidates_for_request =
+        RangeSet(gfx::Range(new_start, next_request.end()));
+    candidates_for_request.Subtract(chunk_stream_.filled_chunks());
+    next_request = candidates_for_request.Last();
   }
 
-  size_t last_byte_before = chunk_stream_.GetFirstMissingByteInInterval(pos);
-  if (size < kDefaultRequestSize) {
-    // Try to extend before pos, up to size |kDefaultRequestSize|.
-    if (pos + size - last_byte_before > kDefaultRequestSize) {
-      pos += size - kDefaultRequestSize;
-      size = kDefaultRequestSize;
-    } else {
-      size += pos - last_byte_before;
-      pos = last_byte_before;
-    }
-  }
-  if (pos - last_byte_before < kDefaultRequestSize) {
-    // Don't leave a gap smaller than |kDefaultRequestSize|.
-    size += pos - last_byte_before;
-    pos = last_byte_before;
+  loader_.reset();
+  chunk_.Clear();
+  if (!is_partial_loader_active_) {
+    client_->CancelBrowserDownload();
+    is_partial_loader_active_ = true;
   }
 
-  current_request_offset_ = pos;
-  current_request_size_ = size;
+  const uint32_t start = next_request.start() * DataStream::kChunkSize;
+  const uint32_t length =
+      std::min(chunk_stream_.eof_pos() - start,
+               next_request.length() * DataStream::kChunkSize);
 
-  // Extend the request until the next downloaded byte or the end of the
-  // document.
-  size_t last_missing_byte =
-      chunk_stream_.GetLastMissingByteInInterval(pos + size - 1);
-  current_request_extended_size_ = last_missing_byte - pos + 1;
-
-  request_pending_ = true;
-
-  // Start downloading first pending request.
-  loader_.Close();
   loader_ = client_->CreateURLLoader();
-  pp::CompletionCallback callback =
-      loader_factory_.NewCallback(&DocumentLoader::DidOpen);
-  pp::URLRequestInfo request = GetRequest(pos, current_request_extended_size_);
-  requests_count_++;
-  int rv = loader_.Open(request, callback);
-  if (rv != PP_OK_COMPLETIONPENDING)
-    callback.Run(rv);
+
+  loader_->OpenRange(
+      url_, url_, start, length,
+      loader_factory_.NewCallback(&DocumentLoader::DidOpenPartial));
 }
 
-pp::URLRequestInfo DocumentLoader::GetRequest(uint32_t position,
-                                              uint32_t size) const {
-  pp::URLRequestInfo request(client_->GetPluginInstance());
-  request.SetURL(url_);
-  request.SetMethod("GET");
-  request.SetFollowRedirects(false);
-  request.SetCustomReferrerURL(url_);
-
-  const size_t kBufSize = 100;
-  char buf[kBufSize];
-  // According to rfc2616, byte range specifies position of the first and last
-  // bytes in the requested range inclusively. Therefore we should subtract 1
-  // from the position + size, to get index of the last byte that needs to be
-  // downloaded.
-  base::snprintf(buf, kBufSize, "Range: bytes=%d-%d", position,
-                 position + size - 1);
-  pp::Var header(buf);
-  request.SetHeaders(header);
-
-  return request;
-}
-
-void DocumentLoader::DidOpen(int32_t result) {
+void DocumentLoader::DidOpenPartial(int32_t result) {
   if (result != PP_OK) {
-    client_->OnDocumentFailed();
-    return;
+    return ReadComplete();
   }
 
-  if (!ResponseStatusSuccess(loader_)) {
-    client_->OnDocumentFailed();
-    return;
-  }
+  if (!ResponseStatusSuccess(loader_.get()))
+    return ReadComplete();
 
-  is_multipart_ = false;
-  current_chunk_size_ = 0;
-  current_chunk_read_ = 0;
-
-  pp::Var headers_var = loader_.GetResponseInfo().GetHeaders();
-  std::string headers;
-  if (headers_var.is_string())
-    headers = headers_var.AsString();
-
-  std::string boundary = GetMultiPartBoundary(headers);
-  if (!boundary.empty()) {
-    // Leave position untouched for now, when we read the data we'll get it.
-    is_multipart_ = true;
-    multipart_boundary_ = boundary;
-  } else {
+  // Leave position untouched for multiparted responce for now, when we read the
+  // data we'll get it.
+  if (!loader_->IsMultipart()) {
     // Need to make sure that the server returned a byte-range, since it's
     // possible for a server to just ignore our byte-range request and just
     // return the entire document even if it supports byte-range requests.
     // i.e. sniff response to
     // http://www.act.org/compass/sample/pdf/geometry.pdf
-    current_pos_ = 0;
-    uint32_t start_pos;
-    uint32_t end_pos;
-    if (GetByteRange(headers, &start_pos, &end_pos)) {
-      current_pos_ = start_pos;
-      if (end_pos && end_pos > start_pos)
-        current_chunk_size_ = end_pos - start_pos + 1;
+    int start_pos = 0;
+    int end_pos = 0;
+    if (loader_->GetByteRange(&start_pos, &end_pos)) {
+      if (start_pos % DataStream::kChunkSize != 0) {
+        return ReadComplete();
+      }
+      DCHECK(!chunk_.chunk_data);
+      chunk_.chunk_index = chunk_stream_.GetChunkIndex(start_pos);
     } else {
-      partial_document_ = false;
+      SetPartialLoadingEnabled(false);
     }
+    return ContinueDownload();
   }
-
-  ReadMore();
+  // Needs more data to calc chunk index.
+  return ReadMore();
 }
 
 void DocumentLoader::ReadMore() {
-  pp::CompletionCallback callback =
-      loader_factory_.NewCallback(&DocumentLoader::DidRead);
-  int rv = loader_.ReadResponseBody(buffer_, sizeof(buffer_), callback);
-  if (rv != PP_OK_COMPLETIONPENDING)
-    callback.Run(rv);
+  loader_->ReadResponseBody(
+      buffer_, sizeof(buffer_),
+      loader_factory_.NewCallback(&DocumentLoader::DidRead));
 }
 
 void DocumentLoader::DidRead(int32_t result) {
-  if (result <= 0) {
-    // If |result| == PP_OK, the document was loaded, otherwise an error was
-    // encountered. Either way we want to stop processing the response. In the
-    // case where an error occurred, the renderer will detect that we're missing
-    // data and will display a message.
-    ReadComplete();
-    return;
+  if (result < 0) {
+    // An error occurred.
+    // The renderer will detect that we're missing data and will display a
+    // message.
+    return ReadComplete();
   }
-
-  char* start = buffer_;
-  size_t length = result;
-  if (is_multipart_ && result > 2) {
-    for (int i = 2; i < result; ++i) {
-      if (IsDoubleNewlines(buffer_, i)) {
-        uint32_t start_pos;
-        uint32_t end_pos;
-        if (GetByteRange(std::string(buffer_, i), &start_pos, &end_pos)) {
-          current_pos_ = start_pos;
-          start += i;
-          length -= i;
-          if (end_pos && end_pos > start_pos)
-            current_chunk_size_ = end_pos - start_pos + 1;
-        }
-        break;
-      }
+  if (result == 0) {
+    loader_.reset();
+    if (!is_partial_loader_active_)
+      return ReadComplete();
+    return ContinueDownload();
+  }
+  if (loader_->IsMultipart()) {
+    int start_pos = 0;
+    int end_pos = 0;
+    if (!loader_->GetByteRange(&start_pos, &end_pos)) {
+      return ReadComplete();
     }
-
-    // Reset this flag so we don't look inside the buffer in future calls of
-    // DidRead for this response.  Note that this code DOES NOT handle multi-
-    // part responses with more than one part (we don't issue them at the
-    // moment, so they shouldn't arrive).
-    is_multipart_ = false;
+    DCHECK(!chunk_.chunk_data);
+    chunk_.chunk_index = chunk_stream_.GetChunkIndex(start_pos);
   }
-
-  if (current_chunk_size_ && current_chunk_read_ + length > current_chunk_size_)
-    length = current_chunk_size_ - current_chunk_read_;
-
-  if (length) {
-    if (document_size_ > 0) {
-      chunk_stream_.WriteData(current_pos_, start, length);
-    } else {
-      // If we did not get content-length in the response, we can't
-      // preallocate buffer for the entire document. Resizing array causing
-      // memory fragmentation issues on the large files and OOM exceptions.
-      // To fix this, we collect all chunks of the file to the list and
-      // concatenate them together after request is complete.
-      std::vector<unsigned char> buf(length);
-      memcpy(buf.data(), start, length);
-      chunk_buffer_.push_back(std::move(buf));
-    }
-    current_pos_ += length;
-    current_chunk_read_ += length;
-    client_->OnNewDataAvailable();
+  if (!SaveChunkData(buffer_, result)) {
+    return ReadMore();
   }
-
-  // Only call the renderer if we allow partial loading.
-  if (!partial_document_) {
-    ReadMore();
-    return;
+  if (IsDocumentComplete()) {
+    return ReadComplete();
   }
-
-  UpdateRendering();
-  RemoveCompletedRanges();
-
-  if (!pending_requests_.empty()) {
-    // If there are pending requests and the current content we're downloading
-    // doesn't satisfy any of these requests, cancel the current request to
-    // fullfill those more important requests.
-    bool satisfying_pending_request =
-        SatisfyingRequest(current_request_offset_, current_request_size_);
-    for (const auto& pending_request : pending_requests_) {
-      if (SatisfyingRequest(pending_request.first, pending_request.second)) {
-        satisfying_pending_request = true;
-        break;
-      }
-    }
-    // Cancel the request as it's not satisfying any request from the
-    // renderer, unless the current request is finished in which case we let
-    // it finish cleanly.
-    if (!satisfying_pending_request &&
-        current_pos_ <
-            current_request_offset_ + current_request_extended_size_) {
-      loader_.Close();
-    }
-  }
-
-  ReadMore();
+  return ContinueDownload();
 }
 
-bool DocumentLoader::SatisfyingRequest(size_t offset, size_t size) const {
-  return offset <= current_pos_ + kDefaultRequestSize &&
-         current_pos_ < offset + size;
+bool DocumentLoader::SaveChunkData(char* input, uint32_t input_size) {
+  count_of_bytes_received_ += input_size;
+  bool chunk_saved = false;
+  bool loading_pending_request = pending_requests_.Contains(chunk_.chunk_index);
+  while (input_size > 0) {
+    if (chunk_.data_size == 0) {
+      chunk_.chunk_data = base::MakeUnique<DataStream::ChunkData>();
+    }
+    const uint32_t new_chunk_data_len =
+        std::min(DataStream::kChunkSize - chunk_.data_size, input_size);
+    memcpy(chunk_.chunk_data->data() + chunk_.data_size, input,
+           new_chunk_data_len);
+    chunk_.data_size += new_chunk_data_len;
+    if (chunk_.data_size == DataStream::kChunkSize ||
+        chunk_stream_.eof_pos() ==
+            chunk_.chunk_index * DataStream::kChunkSize + chunk_.data_size) {
+      chunk_stream_.SetChunkData(chunk_.chunk_index,
+                                 std::move(chunk_.chunk_data));
+      pending_requests_.Subtract(
+          gfx::Range(chunk_.chunk_index, chunk_.chunk_index + 1));
+      chunk_.data_size = 0;
+      ++(chunk_.chunk_index);
+      chunk_saved = true;
+    }
+
+    input += new_chunk_data_len;
+    input_size -= new_chunk_data_len;
+  }
+
+  client_->OnNewDataReceived();
+
+  if (IsDocumentComplete())
+    return true;
+
+  if (!chunk_saved)
+    return false;
+
+  if (loading_pending_request &&
+      !pending_requests_.Contains(chunk_.chunk_index)) {
+    client_->OnPendingRequestComplete();
+  }
+  return true;
 }
 
 void DocumentLoader::ReadComplete() {
-  if (!partial_document_) {
-    if (document_size_ == 0) {
-      // For the document with no 'content-length" specified we've collected all
-      // the chunks already. Let's allocate final document buffer and copy them
-      // over.
-      chunk_stream_.Preallocate(current_pos_);
-      uint32_t pos = 0;
-      for (auto& chunk : chunk_buffer_) {
-        chunk_stream_.WriteData(pos, chunk.data(), chunk.size());
-        pos += chunk.size();
-      }
-      chunk_buffer_.clear();
+  if (!GetDocumentSize()) {
+    uint32_t eof =
+        chunk_.chunk_index * DataStream::kChunkSize + chunk_.data_size;
+    if (!chunk_stream_.filled_chunks().IsEmpty()) {
+      eof = std::max(
+          chunk_stream_.filled_chunks().Last().end() * DataStream::kChunkSize,
+          eof);
     }
-    document_size_ = current_pos_;
-    client_->OnDocumentComplete();
-    return;
+    chunk_stream_.set_eof_pos(eof);
+    if (eof == chunk_.chunk_index * DataStream::kChunkSize + chunk_.data_size) {
+      chunk_stream_.SetChunkData(chunk_.chunk_index,
+                                 std::move(chunk_.chunk_data));
+    }
   }
-
-  request_pending_ = false;
-
+  loader_.reset();
   if (IsDocumentComplete()) {
     client_->OnDocumentComplete();
-    return;
+  } else {
+    client_->OnDocumentCanceled();
   }
-
-  UpdateRendering();
-  DownloadPendingRequests();
-}
-
-void DocumentLoader::UpdateRendering() {
-  if (header_request_)
-    client_->OnPartialDocumentLoaded();
-  else
-    client_->OnPendingRequestComplete();
-  header_request_ = false;
 }
 
 }  // namespace chrome_pdf
diff --git a/pdf/document_loader.h b/pdf/document_loader.h
index 127ca5d0..52a5070 100644
--- a/pdf/document_loader.h
+++ b/pdf/document_loader.h
@@ -9,18 +9,23 @@
 #include <stdint.h>
 
 #include <list>
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
 
 #include "pdf/chunk_stream.h"
-#include "ppapi/cpp/url_loader.h"
 #include "ppapi/utility/completion_callback_factory.h"
 
 namespace chrome_pdf {
 
+class URLLoaderWrapper;
+
 class DocumentLoader {
  public:
+  // Number was chosen in crbug.com/78264#c8
+  static constexpr uint32_t kDefaultRequestSize = 65536;
+
   class Client {
    public:
     virtual ~Client();
@@ -28,27 +33,23 @@
     // Gets the pp::Instance object.
     virtual pp::Instance* GetPluginInstance() = 0;
     // Creates new URLLoader based on client settings.
-    virtual pp::URLLoader CreateURLLoader() = 0;
-    // Notification called when partial information about document is available.
-    // Only called for urls that returns full content size and supports byte
-    // range requests.
-    virtual void OnPartialDocumentLoaded() = 0;
+    virtual std::unique_ptr<URLLoaderWrapper> CreateURLLoader() = 0;
     // Notification called when all outstanding pending requests are complete.
     virtual void OnPendingRequestComplete() = 0;
     // Notification called when new data is available.
-    virtual void OnNewDataAvailable() = 0;
-    // Notification called if document failed to load.
-    virtual void OnDocumentFailed() = 0;
+    virtual void OnNewDataReceived() = 0;
     // Notification called when document is fully loaded.
     virtual void OnDocumentComplete() = 0;
+    // Notification called when document loading is canceled.
+    virtual void OnDocumentCanceled() = 0;
+    // Called when initial loader was closed.
+    virtual void CancelBrowserDownload() = 0;
   };
 
   explicit DocumentLoader(Client* client);
   ~DocumentLoader();
 
-  bool Init(const pp::URLLoader& loader,
-            const std::string& url,
-            const std::string& headers);
+  bool Init(std::unique_ptr<URLLoaderWrapper> loader, const std::string& url);
 
   // Data access interface. Return true is successful.
   bool GetBlock(uint32_t position, uint32_t size, void* buf) const;
@@ -60,77 +61,59 @@
   void RequestData(uint32_t position, uint32_t size);
 
   bool IsDocumentComplete() const;
-  uint32_t document_size() const { return document_size_; }
-
-  // Return number of bytes available.
-  uint32_t GetAvailableData() const;
+  uint32_t GetDocumentSize() const;
+  uint32_t count_of_bytes_received() const { return count_of_bytes_received_; }
 
   // Clear pending requests from the queue.
   void ClearPendingRequests();
 
-  bool is_partial_document() const { return partial_document_; }
+  void SetPartialLoadingEnabled(bool enabled);
+
+  bool is_partial_loader_active() const { return is_partial_loader_active_; }
 
  private:
+  using DataStream = ChunkStream<kDefaultRequestSize>;
+  struct Chunk {
+    Chunk();
+    ~Chunk();
+
+    void Clear();
+
+    uint32_t chunk_index = 0;
+    uint32_t data_size = 0;
+    std::unique_ptr<DataStream::ChunkData> chunk_data;
+  };
+
   // Called by the completion callback of the document's URLLoader.
-  void DidOpen(int32_t result);
+  void DidOpenPartial(int32_t result);
   // Call to read data from the document's URLLoader.
   void ReadMore();
   // Called by the completion callback of the document's URLLoader.
   void DidRead(int32_t result);
 
-  // Called when we detect that partial document load is possible.
-  void LoadPartialDocument();
-  // Called when we have to load full document.
-  void LoadFullDocument();
-  // Download pending requests.
-  void DownloadPendingRequests();
-  // Remove completed ranges.
-  void RemoveCompletedRanges();
-  // Returns true if we are already in progress satisfying the request, or just
-  // about ready to start. This helps us avoid expensive jumping around, and
-  // even worse leaving tiny gaps in the byte stream that might have to be
-  // filled later.
-  bool SatisfyingRequest(size_t pos, size_t size) const;
-  // Called when we complete server request and read all data from it.
+  bool ShouldCancelLoading() const;
+  void ContinueDownload();
+  // Called when we complete server request.
   void ReadComplete();
-  // Creates request to download size byte of data data starting from position.
-  pp::URLRequestInfo GetRequest(uint32_t position, uint32_t size) const;
-  // Updates the rendering by the Client.
-  void UpdateRendering();
 
-  // Document below size will be downloaded in one chunk.
-  static const uint32_t kMinFileSize = 64 * 1024;
-  // Number was chosen in crbug.com/78264#c8
-  enum { kDefaultRequestSize = 65536 };
+  bool SaveChunkData(char* input, uint32_t input_size);
 
   Client* const client_;
   std::string url_;
-  pp::URLLoader loader_;
+  std::unique_ptr<URLLoaderWrapper> loader_;
+
   pp::CompletionCallbackFactory<DocumentLoader> loader_factory_;
-  ChunkStream chunk_stream_;
-  bool partial_document_;
-  bool request_pending_;
-  using PendingRequests = std::list<std::pair<size_t, size_t>>;
-  PendingRequests pending_requests_;
-  // The starting position of the HTTP request currently being processed.
-  size_t current_request_offset_;
-  // The size of the byte range the current HTTP request must download before
-  // being cancelled.
-  size_t current_request_size_;
-  // The actual byte range size of the current HTTP request. This may be larger
-  // than |current_request_size_| and the request may be cancelled before
-  // reaching |current_request_offset_| + |current_request_extended_size_|.
-  size_t current_request_extended_size_;
-  char buffer_[kDefaultRequestSize];
-  uint32_t current_pos_;
-  uint32_t current_chunk_size_;
-  uint32_t current_chunk_read_;
-  uint32_t document_size_;
-  bool header_request_;
-  bool is_multipart_;
-  std::string multipart_boundary_;
-  uint32_t requests_count_;
-  std::vector<std::vector<unsigned char>> chunk_buffer_;
+
+  DataStream chunk_stream_;
+  bool partial_loading_enabled_ = true;
+  bool is_partial_loader_active_ = false;
+
+  static constexpr uint32_t kReadBufferSize = 256 * 1024;
+  char buffer_[kReadBufferSize];
+
+  Chunk chunk_;
+  RangeSet pending_requests_;
+  uint32_t count_of_bytes_received_ = 0;
 };
 
 }  // namespace chrome_pdf
diff --git a/pdf/document_loader_unittest.cc b/pdf/document_loader_unittest.cc
new file mode 100644
index 0000000..4952d0f
--- /dev/null
+++ b/pdf/document_loader_unittest.cc
@@ -0,0 +1,1147 @@
+// Copyright 2016 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 "pdf/document_loader.h"
+
+#include <memory>
+#include <string>
+
+#include "base/logging.h"
+#include "pdf/url_loader_wrapper.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/range/range.h"
+
+using ::testing::_;
+using ::testing::Mock;
+using ::testing::Sequence;
+using ::testing::NiceMock;
+using ::testing::Return;
+
+namespace chrome_pdf {
+
+namespace {
+
+class TestURLLoader : public URLLoaderWrapper {
+ public:
+  class LoaderData {
+   public:
+    LoaderData() {}
+    ~LoaderData() {
+      // We should call callbacks to prevent memory leaks.
+      // The callbacks don't do anything, because the objects that created the
+      // callbacks have been destroyed.
+      if (IsWaitRead())
+        CallReadCallback(-1);
+      if (IsWaitOpen())
+        CallOpenCallback(-1);
+    }
+
+    int content_length() const { return content_length_; }
+    void set_content_length(int content_length) {
+      content_length_ = content_length;
+    }
+    bool accept_ranges_bytes() const { return accept_ranges_bytes_; }
+    void set_accept_ranges_bytes(bool accept_ranges_bytes) {
+      accept_ranges_bytes_ = accept_ranges_bytes;
+    }
+    bool content_encoded() const { return content_encoded_; }
+    void set_content_encoded(bool content_encoded) {
+      content_encoded_ = content_encoded;
+    }
+    const std::string& content_type() const { return content_type_; }
+    void set_content_type(const std::string& content_type) {
+      content_type_ = content_type;
+    }
+    const std::string& content_disposition() const {
+      return content_disposition_;
+    }
+    void set_content_disposition(const std::string& content_disposition) {
+      content_disposition_ = content_disposition;
+    }
+    const std::string& multipart_boundary() const {
+      return multipart_boundary_;
+    }
+    void set_multipart_boundary(const std::string& multipart_boundary) {
+      multipart_boundary_ = multipart_boundary;
+    }
+    const gfx::Range& byte_range() const { return byte_range_; }
+    void set_byte_range(const gfx::Range& byte_range) {
+      byte_range_ = byte_range;
+    }
+    bool is_multipart() const { return is_multipart_; }
+    void set_is_multipart(bool is_multipart) { is_multipart_ = is_multipart; }
+    int status_code() const { return status_code_; }
+    void set_status_code(int status_code) { status_code_ = status_code; }
+    bool closed() const { return closed_; }
+    void set_closed(bool closed) { closed_ = closed; }
+    const gfx::Range& open_byte_range() const { return open_byte_range_; }
+    void set_open_byte_range(const gfx::Range& open_byte_range) {
+      open_byte_range_ = open_byte_range;
+    }
+
+    bool IsWaitRead() const { return !did_read_callback_.IsOptional(); }
+    bool IsWaitOpen() const { return !did_open_callback_.IsOptional(); }
+    char* buffer() const { return buffer_; }
+    int buffer_size() const { return buffer_size_; }
+
+    void SetReadCallback(const pp::CompletionCallback& read_callback,
+                         char* buffer,
+                         int buffer_size) {
+      did_read_callback_ = read_callback;
+      buffer_ = buffer;
+      buffer_size_ = buffer_size;
+    }
+
+    void SetOpenCallback(const pp::CompletionCallback& open_callback,
+                         gfx::Range req_byte_range) {
+      did_open_callback_ = open_callback;
+      set_open_byte_range(req_byte_range);
+    }
+
+    void CallOpenCallback(int result) {
+      DCHECK(IsWaitOpen());
+      did_open_callback_.RunAndClear(result);
+    }
+
+    void CallReadCallback(int result) {
+      DCHECK(IsWaitRead());
+      did_read_callback_.RunAndClear(result);
+    }
+
+   private:
+    pp::CompletionCallback did_open_callback_;
+    pp::CompletionCallback did_read_callback_;
+    char* buffer_ = nullptr;
+    int buffer_size_ = 0;
+
+    int content_length_ = -1;
+    bool accept_ranges_bytes_ = false;
+    bool content_encoded_ = false;
+    std::string content_type_;
+    std::string content_disposition_;
+    std::string multipart_boundary_;
+    gfx::Range byte_range_ = gfx::Range::InvalidRange();
+    bool is_multipart_ = false;
+    int status_code_ = 0;
+    bool closed_ = true;
+    gfx::Range open_byte_range_ = gfx::Range::InvalidRange();
+
+    DISALLOW_COPY_AND_ASSIGN(LoaderData);
+  };
+
+  explicit TestURLLoader(LoaderData* data) : data_(data) {
+    data_->set_closed(false);
+  }
+
+  ~TestURLLoader() override { Close(); }
+
+  int GetContentLength() const override { return data_->content_length(); }
+
+  bool IsAcceptRangesBytes() const override {
+    return data_->accept_ranges_bytes();
+  }
+
+  bool IsContentEncoded() const override { return data_->content_encoded(); }
+
+  std::string GetContentType() const override { return data_->content_type(); }
+
+  std::string GetContentDisposition() const override {
+    return data_->content_disposition();
+  }
+
+  int GetStatusCode() const override { return data_->status_code(); }
+
+  bool IsMultipart() const override { return data_->is_multipart(); }
+
+  bool GetByteRange(int* start, int* end) const override {
+    *start = data_->byte_range().start();
+    *end = data_->byte_range().end();
+    return data_->byte_range().IsValid();
+  }
+
+  void Close() override { data_->set_closed(true); }
+
+  void OpenRange(const std::string& url,
+                 const std::string& referrer_url,
+                 uint32_t position,
+                 uint32_t size,
+                 const pp::CompletionCallback& cc) override {
+    data_->SetOpenCallback(cc, gfx::Range(position, position + size));
+  }
+
+  void ReadResponseBody(char* buffer,
+                        int buffer_size,
+                        const pp::CompletionCallback& cc) override {
+    data_->SetReadCallback(cc, buffer, buffer_size);
+  }
+
+  bool GetDownloadProgress(int64_t* bytes_received,
+                           int64_t* total_bytes_to_be_received) const override {
+    return false;
+  }
+
+ private:
+  LoaderData* data_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestURLLoader);
+};
+
+class TestClient : public DocumentLoader::Client {
+ public:
+  TestClient() { full_page_loader_data()->set_content_type("application/pdf"); }
+  ~TestClient() override {}
+
+  // DocumentLoader::Client overrides:
+  pp::Instance* GetPluginInstance() override { return nullptr; }
+  std::unique_ptr<URLLoaderWrapper> CreateURLLoader() override {
+    return std::unique_ptr<URLLoaderWrapper>(
+        new TestURLLoader(partial_loader_data()));
+  }
+  void OnPendingRequestComplete() override {}
+  void OnNewDataReceived() override {}
+  void OnDocumentComplete() override {}
+  void OnDocumentCanceled() override {}
+  void CancelBrowserDownload() override {}
+
+  std::unique_ptr<URLLoaderWrapper> CreateFullPageLoader() {
+    return std::unique_ptr<URLLoaderWrapper>(
+        new TestURLLoader(full_page_loader_data()));
+  }
+
+  TestURLLoader::LoaderData* full_page_loader_data() {
+    return &full_page_loader_data_;
+  }
+  TestURLLoader::LoaderData* partial_loader_data() {
+    return &partial_loader_data_;
+  }
+
+  void SetCanUsePartialLoading() {
+    full_page_loader_data()->set_content_length(10 * 1024 * 1024);
+    full_page_loader_data()->set_content_encoded(false);
+    full_page_loader_data()->set_accept_ranges_bytes(true);
+  }
+
+  void SendAllPartialData() {
+    partial_loader_data_.set_byte_range(partial_loader_data_.open_byte_range());
+    partial_loader_data_.CallOpenCallback(0);
+    uint32_t length = partial_loader_data_.byte_range().length();
+    while (length > 0) {
+      const uint32_t max_part_len = DocumentLoader::kDefaultRequestSize;
+      const uint32_t part_len = std::min(length, max_part_len);
+      partial_loader_data_.CallReadCallback(part_len);
+      length -= part_len;
+    }
+    if (partial_loader_data_.IsWaitRead()) {
+      partial_loader_data_.CallReadCallback(0);
+    }
+  }
+
+ private:
+  TestURLLoader::LoaderData full_page_loader_data_;
+  TestURLLoader::LoaderData partial_loader_data_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestClient);
+};
+
+class MockClient : public TestClient {
+ public:
+  MockClient() {}
+
+  MOCK_METHOD0(OnPendingRequestComplete, void());
+  MOCK_METHOD0(OnNewDataReceived, void());
+  MOCK_METHOD0(OnDocumentComplete, void());
+  MOCK_METHOD0(OnDocumentCanceled, void());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockClient);
+};
+
+}  // namespace
+
+using DocumentLoaderTest = ::testing::Test;
+
+TEST_F(DocumentLoaderTest, PartialLoadingEnabled) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  loader.RequestData(1000000, 1);
+  EXPECT_FALSE(loader.is_partial_loader_active());
+  // Always send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_TRUE(loader.is_partial_loader_active());
+}
+
+TEST_F(DocumentLoaderTest, PartialLoadingDisabledOnSmallFiles) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 2);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  loader.RequestData(1000000, 1);
+  EXPECT_FALSE(loader.is_partial_loader_active());
+  // Always send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_FALSE(loader.is_partial_loader_active());
+}
+
+TEST_F(DocumentLoaderTest, PartialLoadingDisabledIfContentEncoded) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_encoded(true);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  loader.RequestData(1000000, 1);
+  EXPECT_FALSE(loader.is_partial_loader_active());
+  // Always send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_FALSE(loader.is_partial_loader_active());
+}
+
+TEST_F(DocumentLoaderTest, PartialLoadingDisabledNoAcceptRangeBytes) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_accept_ranges_bytes(false);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  loader.RequestData(1000000, 1);
+  EXPECT_FALSE(loader.is_partial_loader_active());
+  // Always send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_FALSE(loader.is_partial_loader_active());
+}
+
+TEST_F(DocumentLoaderTest, PartialLoadingReallyDisabledRequestFromBegin) {
+  TestClient client;
+  DocumentLoader loader(&client);
+  client.SetCanUsePartialLoading();
+  loader.SetPartialLoadingEnabled(false);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  // We should not start partial loading if requested data is beside full page
+  // loading position.
+  loader.RequestData(DocumentLoader::kDefaultRequestSize, 1);
+  EXPECT_FALSE(loader.is_partial_loader_active());
+  // Always send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_FALSE(loader.is_partial_loader_active());
+}
+
+TEST_F(DocumentLoaderTest, PartialLoadingReallyDisabledRequestFromMiddle) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  DocumentLoader loader(&client);
+  loader.SetPartialLoadingEnabled(false);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  loader.RequestData(1000000, 1);
+  EXPECT_FALSE(loader.is_partial_loader_active());
+  // Always send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_FALSE(loader.is_partial_loader_active());
+}
+
+TEST_F(DocumentLoaderTest, PartialLoadingSimple) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  // While we have no requests, we should not start partial loading.
+  EXPECT_FALSE(loader.is_partial_loader_active());
+
+  loader.RequestData(5000000, 1);
+
+  EXPECT_FALSE(client.partial_loader_data()->IsWaitOpen());
+  EXPECT_FALSE(loader.is_partial_loader_active());
+
+  // Always send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  // Partial loader should request headers.
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+  EXPECT_TRUE(loader.is_partial_loader_active());
+  // Loader should be stopped.
+  EXPECT_TRUE(client.full_page_loader_data()->closed());
+
+  EXPECT_EQ("{4980736,10485760}",
+            client.partial_loader_data()->open_byte_range().ToString());
+}
+
+TEST_F(DocumentLoaderTest, PartialLoadingBackOrder) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  // While we have no requests, we should not start partial loading.
+  EXPECT_FALSE(loader.is_partial_loader_active());
+
+  loader.RequestData(client.full_page_loader_data()->content_length() - 1, 1);
+
+  EXPECT_FALSE(client.partial_loader_data()->IsWaitOpen());
+  EXPECT_FALSE(loader.is_partial_loader_active());
+
+  // Always send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  // Partial loader should request headers.
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+  EXPECT_TRUE(loader.is_partial_loader_active());
+  // Loader should be stopped.
+  EXPECT_TRUE(client.full_page_loader_data()->closed());
+
+  // Requested range should be enlarged.
+  EXPECT_GT(client.partial_loader_data()->open_byte_range().length(), 1u);
+  EXPECT_EQ("{9830400,10485760}",
+            client.partial_loader_data()->open_byte_range().ToString());
+}
+
+TEST_F(DocumentLoaderTest, CompleteWithoutPartial) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  EXPECT_FALSE(client.full_page_loader_data()->closed());
+  while (client.full_page_loader_data()->IsWaitRead()) {
+    client.full_page_loader_data()->CallReadCallback(1000);
+  }
+  EXPECT_TRUE(loader.IsDocumentComplete());
+  EXPECT_TRUE(client.full_page_loader_data()->closed());
+}
+
+TEST_F(DocumentLoaderTest, ErrorDownloadFullDocument) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  EXPECT_TRUE(client.full_page_loader_data()->IsWaitRead());
+  EXPECT_FALSE(client.full_page_loader_data()->closed());
+  client.full_page_loader_data()->CallReadCallback(-3);
+  EXPECT_TRUE(client.full_page_loader_data()->closed());
+  EXPECT_FALSE(loader.IsDocumentComplete());
+}
+
+TEST_F(DocumentLoaderTest, CompleteNoContentLength) {
+  TestClient client;
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  EXPECT_FALSE(client.full_page_loader_data()->closed());
+  for (int i = 0; i < 10; ++i) {
+    EXPECT_TRUE(client.full_page_loader_data()->IsWaitRead());
+    client.full_page_loader_data()->CallReadCallback(1000);
+  }
+  EXPECT_TRUE(client.full_page_loader_data()->IsWaitRead());
+  client.full_page_loader_data()->CallReadCallback(0);
+  EXPECT_EQ(10000ul, loader.GetDocumentSize());
+  EXPECT_TRUE(loader.IsDocumentComplete());
+  EXPECT_TRUE(client.full_page_loader_data()->closed());
+}
+
+TEST_F(DocumentLoaderTest, CompleteWithPartial) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  loader.RequestData(19 * DocumentLoader::kDefaultRequestSize,
+                     DocumentLoader::kDefaultRequestSize);
+  EXPECT_FALSE(client.full_page_loader_data()->closed());
+  EXPECT_FALSE(client.partial_loader_data()->IsWaitRead());
+  EXPECT_FALSE(client.partial_loader_data()->IsWaitOpen());
+
+  // Always send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_TRUE(client.full_page_loader_data()->closed());
+  EXPECT_FALSE(client.partial_loader_data()->closed());
+
+  client.SendAllPartialData();
+  // Now we should send other document data.
+  client.SendAllPartialData();
+  EXPECT_TRUE(client.full_page_loader_data()->closed());
+  EXPECT_TRUE(client.partial_loader_data()->closed());
+}
+
+TEST_F(DocumentLoaderTest, PartialRequestLastChunk) {
+  const uint32_t kLastChunkSize = 300;
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20 + kLastChunkSize);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  loader.RequestData(20 * DocumentLoader::kDefaultRequestSize, 1);
+
+  // Always send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+  EXPECT_EQ(
+      static_cast<int>(client.partial_loader_data()->open_byte_range().end()),
+      client.full_page_loader_data()->content_length());
+  client.partial_loader_data()->set_byte_range(
+      client.partial_loader_data()->open_byte_range());
+  client.partial_loader_data()->CallOpenCallback(0);
+  uint32_t data_length = client.partial_loader_data()->byte_range().length();
+  while (data_length > DocumentLoader::kDefaultRequestSize) {
+    client.partial_loader_data()->CallReadCallback(
+        DocumentLoader::kDefaultRequestSize);
+    data_length -= DocumentLoader::kDefaultRequestSize;
+  }
+  EXPECT_EQ(kLastChunkSize, data_length);
+  client.partial_loader_data()->CallReadCallback(kLastChunkSize);
+  EXPECT_TRUE(loader.IsDataAvailable(DocumentLoader::kDefaultRequestSize * 20,
+                                     kLastChunkSize));
+}
+
+TEST_F(DocumentLoaderTest, DocumentSize) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(123456789);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  EXPECT_EQ(static_cast<int>(loader.GetDocumentSize()),
+            client.full_page_loader_data()->content_length());
+}
+
+TEST_F(DocumentLoaderTest, DocumentSizeNoContentLength) {
+  TestClient client;
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  EXPECT_EQ(0ul, loader.GetDocumentSize());
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  client.full_page_loader_data()->CallReadCallback(1000);
+  client.full_page_loader_data()->CallReadCallback(500);
+  client.full_page_loader_data()->CallReadCallback(0);
+  EXPECT_EQ(DocumentLoader::kDefaultRequestSize + 1000ul + 500ul,
+            loader.GetDocumentSize());
+  EXPECT_TRUE(loader.IsDocumentComplete());
+}
+
+TEST_F(DocumentLoaderTest, ClearPendingRequests) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 100 + 58383);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  loader.RequestData(17 * DocumentLoader::kDefaultRequestSize + 100, 10);
+  loader.ClearPendingRequests();
+  loader.RequestData(15 * DocumentLoader::kDefaultRequestSize + 200, 20);
+  // pending requests are accumulating, and will be processed after initial data
+  // load.
+  EXPECT_FALSE(client.partial_loader_data()->IsWaitOpen());
+
+  // Send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  {
+    EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+    const gfx::Range range_requested(15 * DocumentLoader::kDefaultRequestSize,
+                                     16 * DocumentLoader::kDefaultRequestSize);
+    EXPECT_EQ(range_requested.start(),
+              client.partial_loader_data()->open_byte_range().start());
+    EXPECT_LE(range_requested.end(),
+              client.partial_loader_data()->open_byte_range().end());
+    client.partial_loader_data()->set_byte_range(
+        client.partial_loader_data()->open_byte_range());
+  }
+  // clear requests before Open callback.
+  loader.ClearPendingRequests();
+  // Current request should continue loading.
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+  client.partial_loader_data()->CallOpenCallback(0);
+  client.partial_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_FALSE(client.partial_loader_data()->closed());
+  // Current request should continue loading, because no other request queued.
+
+  loader.RequestData(18 * DocumentLoader::kDefaultRequestSize + 200, 20);
+  // Requests queue is processed only on receiving data.
+  client.partial_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  // New request within close distance from the one currently loading. Loading
+  // isn't restarted.
+  EXPECT_FALSE(client.partial_loader_data()->IsWaitOpen());
+
+  loader.ClearPendingRequests();
+  // request again two.
+  loader.RequestData(60 * DocumentLoader::kDefaultRequestSize + 100, 10);
+  loader.RequestData(35 * DocumentLoader::kDefaultRequestSize + 200, 20);
+  // Requests queue is processed only on receiving data.
+  client.partial_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  {
+    // new requset not with in close distance from current loading.
+    // Loading should be restarted.
+    EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+    // The first requested chunk should be processed.
+    const gfx::Range range_requested(35 * DocumentLoader::kDefaultRequestSize,
+                                     36 * DocumentLoader::kDefaultRequestSize);
+    EXPECT_EQ(range_requested.start(),
+              client.partial_loader_data()->open_byte_range().start());
+    EXPECT_LE(range_requested.end(),
+              client.partial_loader_data()->open_byte_range().end());
+    client.partial_loader_data()->set_byte_range(
+        client.partial_loader_data()->open_byte_range());
+  }
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+  client.partial_loader_data()->CallOpenCallback(0);
+  // Override pending requests.
+  loader.ClearPendingRequests();
+  loader.RequestData(70 * DocumentLoader::kDefaultRequestSize + 100, 10);
+
+  // Requests queue is processed only on receiving data.
+  client.partial_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  {
+    // New requset not with in close distance from current loading.
+    // Loading should be restarted .
+    EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+    // The first requested chunk should be processed.
+    const gfx::Range range_requested(70 * DocumentLoader::kDefaultRequestSize,
+                                     71 * DocumentLoader::kDefaultRequestSize);
+    EXPECT_EQ(range_requested.start(),
+              client.partial_loader_data()->open_byte_range().start());
+    EXPECT_LE(range_requested.end(),
+              client.partial_loader_data()->open_byte_range().end());
+    client.partial_loader_data()->set_byte_range(
+        client.partial_loader_data()->open_byte_range());
+  }
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+}
+
+TEST_F(DocumentLoaderTest, GetBlock) {
+  std::vector<char> buffer(DocumentLoader::kDefaultRequestSize);
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20 + 58383);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  EXPECT_FALSE(loader.GetBlock(0, 1000, buffer.data()));
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_TRUE(loader.GetBlock(0, 1000, buffer.data()));
+  EXPECT_FALSE(loader.GetBlock(DocumentLoader::kDefaultRequestSize, 1500,
+                               buffer.data()));
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_TRUE(loader.GetBlock(DocumentLoader::kDefaultRequestSize, 1500,
+                              buffer.data()));
+
+  EXPECT_FALSE(loader.GetBlock(17 * DocumentLoader::kDefaultRequestSize, 3000,
+                               buffer.data()));
+  loader.RequestData(17 * DocumentLoader::kDefaultRequestSize + 100, 10);
+  EXPECT_FALSE(loader.GetBlock(17 * DocumentLoader::kDefaultRequestSize, 3000,
+                               buffer.data()));
+
+  // Requests queue is processed only on receiving data.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  client.SendAllPartialData();
+  EXPECT_TRUE(loader.GetBlock(17 * DocumentLoader::kDefaultRequestSize, 3000,
+                              buffer.data()));
+}
+
+TEST_F(DocumentLoaderTest, IsDataAvailable) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20 + 58383);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  EXPECT_FALSE(loader.IsDataAvailable(0, 1000));
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_TRUE(loader.IsDataAvailable(0, 1000));
+  EXPECT_FALSE(
+      loader.IsDataAvailable(DocumentLoader::kDefaultRequestSize, 1500));
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_TRUE(
+      loader.IsDataAvailable(DocumentLoader::kDefaultRequestSize, 1500));
+
+  EXPECT_FALSE(
+      loader.IsDataAvailable(17 * DocumentLoader::kDefaultRequestSize, 3000));
+  loader.RequestData(17 * DocumentLoader::kDefaultRequestSize + 100, 10);
+  EXPECT_FALSE(
+      loader.IsDataAvailable(17 * DocumentLoader::kDefaultRequestSize, 3000));
+
+  // Requests queue is processed only on receiving data.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  client.SendAllPartialData();
+  EXPECT_TRUE(
+      loader.IsDataAvailable(17 * DocumentLoader::kDefaultRequestSize, 3000));
+}
+
+TEST_F(DocumentLoaderTest, RequestData) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 100 + 58383);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  loader.RequestData(37 * DocumentLoader::kDefaultRequestSize + 200, 10);
+  loader.RequestData(25 * DocumentLoader::kDefaultRequestSize + 600, 100);
+  loader.RequestData(13 * DocumentLoader::kDefaultRequestSize + 900, 500);
+
+  // Send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  {
+    EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+    const gfx::Range range_requested(13 * DocumentLoader::kDefaultRequestSize,
+                                     14 * DocumentLoader::kDefaultRequestSize);
+    EXPECT_EQ(range_requested.start(),
+              client.partial_loader_data()->open_byte_range().start());
+    EXPECT_LE(range_requested.end(),
+              client.partial_loader_data()->open_byte_range().end());
+    client.partial_loader_data()->set_byte_range(
+        client.partial_loader_data()->open_byte_range());
+  }
+  client.partial_loader_data()->CallOpenCallback(0);
+  // Override pending requests.
+  loader.ClearPendingRequests();
+  loader.RequestData(38 * DocumentLoader::kDefaultRequestSize + 200, 10);
+  loader.RequestData(26 * DocumentLoader::kDefaultRequestSize + 600, 100);
+  // Requests queue is processed only on receiving data.
+  client.partial_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  {
+    EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+    const gfx::Range range_requested(26 * DocumentLoader::kDefaultRequestSize,
+                                     27 * DocumentLoader::kDefaultRequestSize);
+    EXPECT_EQ(range_requested.start(),
+              client.partial_loader_data()->open_byte_range().start());
+    EXPECT_LE(range_requested.end(),
+              client.partial_loader_data()->open_byte_range().end());
+    client.partial_loader_data()->set_byte_range(
+        client.partial_loader_data()->open_byte_range());
+  }
+  client.partial_loader_data()->CallOpenCallback(0);
+  // Override pending requests.
+  loader.ClearPendingRequests();
+  loader.RequestData(39 * DocumentLoader::kDefaultRequestSize + 200, 10);
+  // Requests queue is processed only on receiving data.
+  client.partial_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  {
+    EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+    const gfx::Range range_requested(39 * DocumentLoader::kDefaultRequestSize,
+                                     40 * DocumentLoader::kDefaultRequestSize);
+    EXPECT_EQ(range_requested.start(),
+              client.partial_loader_data()->open_byte_range().start());
+    EXPECT_LE(range_requested.end(),
+              client.partial_loader_data()->open_byte_range().end());
+    client.partial_loader_data()->set_byte_range(
+        client.partial_loader_data()->open_byte_range());
+  }
+  // Fill all gaps.
+  while (!loader.IsDocumentComplete()) {
+    client.SendAllPartialData();
+  }
+  EXPECT_TRUE(client.partial_loader_data()->closed());
+}
+
+TEST_F(DocumentLoaderTest, DoNotLoadAvailablePartialData) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20 + 58383);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  // Send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  // Send more data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  loader.RequestData(2 * DocumentLoader::kDefaultRequestSize + 200, 10);
+
+  // Send more data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  // Partial loading should not have started for already available data.
+  EXPECT_TRUE(client.partial_loader_data()->closed());
+}
+
+TEST_F(DocumentLoaderTest, DoNotLoadDataAfterComplete) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  for (int i = 0; i < 20; ++i) {
+    client.full_page_loader_data()->CallReadCallback(
+        DocumentLoader::kDefaultRequestSize);
+  }
+
+  EXPECT_TRUE(loader.IsDocumentComplete());
+
+  loader.RequestData(17 * DocumentLoader::kDefaultRequestSize + 200, 10);
+
+  EXPECT_TRUE(client.partial_loader_data()->closed());
+  EXPECT_TRUE(client.full_page_loader_data()->closed());
+}
+
+TEST_F(DocumentLoaderTest, DoNotLoadPartialDataAboveDocumentSize) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  loader.RequestData(20 * DocumentLoader::kDefaultRequestSize + 200, 10);
+
+  // Send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  EXPECT_TRUE(client.partial_loader_data()->closed());
+}
+
+TEST_F(DocumentLoaderTest, MergePendingRequests) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 50 + 58383);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+  loader.RequestData(17 * DocumentLoader::kDefaultRequestSize + 200, 10);
+  loader.RequestData(16 * DocumentLoader::kDefaultRequestSize + 600, 100);
+
+  // Send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  const gfx::Range range_requested(16 * DocumentLoader::kDefaultRequestSize,
+                                   18 * DocumentLoader::kDefaultRequestSize);
+  EXPECT_EQ(range_requested.start(),
+            client.partial_loader_data()->open_byte_range().start());
+  EXPECT_LE(range_requested.end(),
+            client.partial_loader_data()->open_byte_range().end());
+
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+
+  // Fill all gaps.
+  while (!loader.IsDocumentComplete()) {
+    client.SendAllPartialData();
+  }
+  EXPECT_TRUE(client.partial_loader_data()->closed());
+}
+
+TEST_F(DocumentLoaderTest, PartialStopOnStatusCodeError) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  loader.RequestData(17 * DocumentLoader::kDefaultRequestSize + 200, 10);
+
+  // Send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+  client.partial_loader_data()->set_status_code(404);
+  client.partial_loader_data()->CallOpenCallback(0);
+  EXPECT_TRUE(client.partial_loader_data()->closed());
+}
+
+TEST_F(DocumentLoaderTest,
+       PartialAsFullDocumentLoadingRangeRequestNoRangeField) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  loader.RequestData(17 * DocumentLoader::kDefaultRequestSize + 200, 10);
+
+  // Send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+  client.partial_loader_data()->set_byte_range(gfx::Range::InvalidRange());
+  client.partial_loader_data()->CallOpenCallback(0);
+  EXPECT_FALSE(client.partial_loader_data()->closed());
+  // Partial loader is used to load the whole page, like full page loader.
+  EXPECT_FALSE(loader.is_partial_loader_active());
+}
+
+TEST_F(DocumentLoaderTest, PartialMultiPart) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  loader.RequestData(17 * DocumentLoader::kDefaultRequestSize + 200, 10);
+
+  // Send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+  client.partial_loader_data()->set_is_multipart(true);
+  client.partial_loader_data()->CallOpenCallback(0);
+  client.partial_loader_data()->set_byte_range(
+      gfx::Range(17 * DocumentLoader::kDefaultRequestSize,
+                 18 * DocumentLoader::kDefaultRequestSize));
+  client.partial_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_TRUE(loader.IsDataAvailable(17 * DocumentLoader::kDefaultRequestSize,
+                                     DocumentLoader::kDefaultRequestSize));
+}
+
+TEST_F(DocumentLoaderTest, PartialMultiPartRangeError) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  loader.RequestData(17 * DocumentLoader::kDefaultRequestSize + 200, 10);
+
+  // Send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+  client.partial_loader_data()->set_is_multipart(true);
+  client.partial_loader_data()->CallOpenCallback(0);
+  client.partial_loader_data()->set_byte_range(gfx::Range::InvalidRange());
+  client.partial_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  EXPECT_FALSE(loader.IsDataAvailable(17 * DocumentLoader::kDefaultRequestSize,
+                                      DocumentLoader::kDefaultRequestSize));
+  EXPECT_TRUE(client.partial_loader_data()->closed());
+}
+
+TEST_F(DocumentLoaderTest, PartialConnectionErrorOnOpen) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  loader.RequestData(17 * DocumentLoader::kDefaultRequestSize + 200, 10);
+
+  // Send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+  client.partial_loader_data()->CallOpenCallback(-3);
+  EXPECT_TRUE(client.partial_loader_data()->closed());
+
+  // Partial loading should not restart after any error.
+  loader.RequestData(18 * DocumentLoader::kDefaultRequestSize + 200, 10);
+
+  EXPECT_FALSE(client.partial_loader_data()->IsWaitOpen());
+  EXPECT_TRUE(client.partial_loader_data()->closed());
+}
+
+TEST_F(DocumentLoaderTest, PartialConnectionErrorOnRead) {
+  TestClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  loader.RequestData(17 * DocumentLoader::kDefaultRequestSize + 200, 10);
+
+  // Send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitOpen());
+  client.partial_loader_data()->set_byte_range(
+      gfx::Range(17 * DocumentLoader::kDefaultRequestSize,
+                 18 * DocumentLoader::kDefaultRequestSize));
+  client.partial_loader_data()->CallOpenCallback(0);
+  EXPECT_TRUE(client.partial_loader_data()->IsWaitRead());
+  client.partial_loader_data()->CallReadCallback(-3);
+  EXPECT_TRUE(client.partial_loader_data()->closed());
+
+  // Partial loading should not restart after any error.
+  loader.RequestData(18 * DocumentLoader::kDefaultRequestSize + 200, 10);
+
+  EXPECT_FALSE(client.partial_loader_data()->IsWaitOpen());
+  EXPECT_TRUE(client.partial_loader_data()->closed());
+}
+
+TEST_F(DocumentLoaderTest, ClientCompleteCallbacks) {
+  MockClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  EXPECT_CALL(client, OnDocumentComplete()).Times(0);
+  for (int i = 0; i < 19; ++i) {
+    client.full_page_loader_data()->CallReadCallback(
+        DocumentLoader::kDefaultRequestSize);
+  }
+  Mock::VerifyAndClear(&client);
+
+  EXPECT_CALL(client, OnDocumentComplete()).Times(1);
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  Mock::VerifyAndClear(&client);
+}
+
+TEST_F(DocumentLoaderTest, ClientCompleteCallbacksNoContentLength) {
+  MockClient client;
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  EXPECT_CALL(client, OnDocumentCanceled()).Times(0);
+  EXPECT_CALL(client, OnDocumentComplete()).Times(0);
+  for (int i = 0; i < 20; ++i) {
+    client.full_page_loader_data()->CallReadCallback(
+        DocumentLoader::kDefaultRequestSize);
+  }
+  Mock::VerifyAndClear(&client);
+
+  EXPECT_CALL(client, OnDocumentCanceled()).Times(0);
+  EXPECT_CALL(client, OnDocumentComplete()).Times(1);
+  client.full_page_loader_data()->CallReadCallback(0);
+  Mock::VerifyAndClear(&client);
+}
+
+TEST_F(DocumentLoaderTest, ClientCancelCallback) {
+  MockClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  EXPECT_CALL(client, OnDocumentCanceled()).Times(0);
+  EXPECT_CALL(client, OnDocumentComplete()).Times(0);
+  for (int i = 0; i < 10; ++i) {
+    client.full_page_loader_data()->CallReadCallback(
+        DocumentLoader::kDefaultRequestSize);
+  }
+  Mock::VerifyAndClear(&client);
+
+  EXPECT_CALL(client, OnDocumentComplete()).Times(0);
+  EXPECT_CALL(client, OnDocumentCanceled()).Times(1);
+  client.full_page_loader_data()->CallReadCallback(-3);
+  Mock::VerifyAndClear(&client);
+}
+
+TEST_F(DocumentLoaderTest, NewDataAvailable) {
+  MockClient client;
+  client.SetCanUsePartialLoading();
+  client.full_page_loader_data()->set_content_length(
+      DocumentLoader::kDefaultRequestSize * 20);
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  EXPECT_CALL(client, OnNewDataReceived()).Times(1);
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  Mock::VerifyAndClear(&client);
+
+  EXPECT_CALL(client, OnNewDataReceived()).Times(1);
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize - 100);
+  Mock::VerifyAndClear(&client);
+
+  EXPECT_CALL(client, OnNewDataReceived()).Times(1);
+  client.full_page_loader_data()->CallReadCallback(100);
+  Mock::VerifyAndClear(&client);
+}
+
+TEST_F(DocumentLoaderTest, ClientPendingRequestCompleteFullLoader) {
+  MockClient client;
+  client.SetCanUsePartialLoading();
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  loader.RequestData(1000, 4000);
+
+  EXPECT_CALL(client, OnPendingRequestComplete()).Times(1);
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+  Mock::VerifyAndClear(&client);
+}
+
+TEST_F(DocumentLoaderTest, ClientPendingRequestCompletePartialLoader) {
+  MockClient client;
+  client.SetCanUsePartialLoading();
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  EXPECT_CALL(client, OnPendingRequestComplete()).Times(1);
+  loader.RequestData(15 * DocumentLoader::kDefaultRequestSize + 4000, 4000);
+
+  // Always send initial data from FullPageLoader.
+  client.full_page_loader_data()->CallReadCallback(
+      DocumentLoader::kDefaultRequestSize);
+
+  client.SendAllPartialData();
+  Mock::VerifyAndClear(&client);
+}
+
+TEST_F(DocumentLoaderTest, ClientPendingRequestCompletePartialAndFullLoader) {
+  MockClient client;
+  client.SetCanUsePartialLoading();
+  DocumentLoader loader(&client);
+  loader.Init(client.CreateFullPageLoader(), "http://url.com");
+
+  EXPECT_CALL(client, OnPendingRequestComplete()).Times(1);
+  loader.RequestData(16 * DocumentLoader::kDefaultRequestSize + 4000, 4000);
+  loader.RequestData(4 * DocumentLoader::kDefaultRequestSize + 4000, 4000);
+
+  for (int i = 0; i < 5; ++i) {
+    client.full_page_loader_data()->CallReadCallback(
+        DocumentLoader::kDefaultRequestSize);
+  }
+
+  Mock::VerifyAndClear(&client);
+
+  EXPECT_CALL(client, OnPendingRequestComplete()).Times(1);
+  client.SendAllPartialData();
+  Mock::VerifyAndClear(&client);
+}
+
+}  // namespace chrome_pdf
diff --git a/pdf/out_of_process_instance.cc b/pdf/out_of_process_instance.cc
index 3518bb7..fb0e1d7 100644
--- a/pdf/out_of_process_instance.cc
+++ b/pdf/out_of_process_instance.cc
@@ -1090,15 +1090,6 @@
   } else if (result != PP_ERROR_ABORTED) {  // Can happen in tests.
     DocumentLoadFailed();
   }
-
-  // If it's a progressive load, cancel the stream URL request so that requests
-  // can be made on the original URL.
-  // TODO(raymes): Make this clearer once the in-process plugin is deleted.
-  if (engine_->IsProgressiveLoad()) {
-    pp::VarDictionary message;
-    message.Set(kType, kJSCancelStreamUrlType);
-    PostMessage(message);
-  }
 }
 
 void OutOfProcessInstance::DidOpenPreview(int32_t result) {
@@ -1719,6 +1710,12 @@
   return background_color_;
 }
 
+void OutOfProcessInstance::CancelBrowserDownload() {
+  pp::VarDictionary message;
+  message.Set(kType, kJSCancelStreamUrlType);
+  PostMessage(message);
+}
+
 void OutOfProcessInstance::IsSelectingChanged(bool is_selecting) {
   pp::VarDictionary message;
   message.Set(kType, kJSSetIsSelectingType);
diff --git a/pdf/out_of_process_instance.h b/pdf/out_of_process_instance.h
index 23a6b064..7dc781c 100644
--- a/pdf/out_of_process_instance.h
+++ b/pdf/out_of_process_instance.h
@@ -142,6 +142,7 @@
   void FormTextFieldFocusChange(bool in_focus) override;
   bool IsPrintPreview() override;
   uint32_t GetBackgroundColor() override;
+  void CancelBrowserDownload() override;
   void IsSelectingChanged(bool is_selecting) override;
   void SelectionChanged(const pp::Rect& left, const pp::Rect& right) override;
 
diff --git a/pdf/pdf_engine.h b/pdf/pdf_engine.h
index f40df90..742ad89 100644
--- a/pdf/pdf_engine.h
+++ b/pdf/pdf_engine.h
@@ -188,6 +188,9 @@
     // Get the background color of the PDF.
     virtual uint32_t GetBackgroundColor() = 0;
 
+    // Cancel browser initiated document download.
+    virtual void CancelBrowserDownload() = 0;
+
     // Sets selection status.
     virtual void IsSelectingChanged(bool is_selecting) {}
 
@@ -312,8 +315,6 @@
   virtual void SetScrollPosition(const pp::Point& position) = 0;
 #endif
 
-  virtual bool IsProgressiveLoad() = 0;
-
   virtual std::string GetMetadata(const std::string& key) = 0;
 
   virtual void SetCaretPosition(const pp::Point& position) = 0;
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index f420ac9..2d51ad9 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -34,6 +34,7 @@
 #include "pdf/pdfium/pdfium_api_string_buffer_adapter.h"
 #include "pdf/pdfium/pdfium_mem_buffer_file_read.h"
 #include "pdf/pdfium/pdfium_mem_buffer_file_write.h"
+#include "pdf/url_loader_wrapper_impl.h"
 #include "ppapi/c/pp_errors.h"
 #include "ppapi/c/pp_input_event.h"
 #include "ppapi/c/ppb_core.h"
@@ -746,7 +747,6 @@
     : client_(client),
       current_zoom_(1.0),
       current_rotation_(0),
-      doc_loader_(this),
       password_tries_remaining_(0),
       doc_(nullptr),
       form_(nullptr),
@@ -777,15 +777,15 @@
 
   file_access_.m_FileLen = 0;
   file_access_.m_GetBlock = &GetBlock;
-  file_access_.m_Param = &doc_loader_;
+  file_access_.m_Param = this;
 
   file_availability_.version = 1;
   file_availability_.IsDataAvail = &IsDataAvail;
-  file_availability_.loader = &doc_loader_;
+  file_availability_.engine = this;
 
   download_hints_.version = 1;
   download_hints_.AddSegment = &AddSegment;
-  download_hints_.loader = &doc_loader_;
+  download_hints_.engine = this;
 
   // Initialize FPDF_FORMFILLINFO member variables.  Deriving from this struct
   // allows the static callbacks to be able to cast the FPDF_FORMFILLINFO in
@@ -1031,8 +1031,8 @@
                            unsigned long position,
                            unsigned char* buffer,
                            unsigned long size) {
-  DocumentLoader* loader = static_cast<DocumentLoader*>(param);
-  return loader->GetBlock(position, size, buffer);
+  PDFiumEngine* engine = static_cast<PDFiumEngine*>(param);
+  return engine->doc_loader_->GetBlock(position, size, buffer);
 }
 
 FPDF_BOOL PDFiumEngine::IsDataAvail(FX_FILEAVAIL* param,
@@ -1040,7 +1040,7 @@
                                     size_t size) {
   PDFiumEngine::FileAvail* file_avail =
       static_cast<PDFiumEngine::FileAvail*>(param);
-  return file_avail->loader->IsDataAvailable(offset, size);
+  return file_avail->engine->doc_loader_->IsDataAvailable(offset, size);
 }
 
 void PDFiumEngine::AddSegment(FX_DOWNLOADHINTS* param,
@@ -1048,7 +1048,7 @@
                               size_t size) {
   PDFiumEngine::DownloadHints* download_hints =
       static_cast<PDFiumEngine::DownloadHints*>(param);
-  return download_hints->loader->RequestData(offset, size);
+  return download_hints->engine->doc_loader_->RequestData(offset, size);
 }
 
 bool PDFiumEngine::New(const char* url, const char* headers) {
@@ -1181,15 +1181,27 @@
 
 bool PDFiumEngine::HandleDocumentLoad(const pp::URLLoader& loader) {
   password_tries_remaining_ = kMaxPasswordTries;
-  return doc_loader_.Init(loader, url_, headers_);
+  process_when_pending_request_complete_ = true;
+  auto loader_wrapper =
+      base::MakeUnique<URLLoaderWrapperImpl>(GetPluginInstance(), loader);
+  loader_wrapper->SetResponseHeaders(headers_);
+
+  doc_loader_ = base::MakeUnique<DocumentLoader>(this);
+  if (doc_loader_->Init(std::move(loader_wrapper), url_)) {
+    // request initial data.
+    doc_loader_->RequestData(0, 1);
+    return true;
+  }
+  return false;
 }
 
 pp::Instance* PDFiumEngine::GetPluginInstance() {
   return client_->GetPluginInstance();
 }
 
-pp::URLLoader PDFiumEngine::CreateURLLoader() {
-  return client_->CreateURLLoader();
+std::unique_ptr<URLLoaderWrapper> PDFiumEngine::CreateURLLoader() {
+  return base::MakeUnique<URLLoaderWrapperImpl>(GetPluginInstance(),
+                                                client_->CreateURLLoader());
 }
 
 void PDFiumEngine::AppendPage(PDFEngine* engine, int index) {
@@ -1211,36 +1223,38 @@
 }
 #endif
 
-bool PDFiumEngine::IsProgressiveLoad() {
-  return doc_loader_.is_partial_document();
-}
-
 std::string PDFiumEngine::GetMetadata(const std::string& key) {
   return GetDocumentMetadata(doc(), key);
 }
 
-void PDFiumEngine::OnPartialDocumentLoaded() {
-  file_access_.m_FileLen = doc_loader_.document_size();
+void PDFiumEngine::OnPendingRequestComplete() {
+  if (!process_when_pending_request_complete_)
+    return;
   if (!fpdf_availability_) {
+    file_access_.m_FileLen = doc_loader_->GetDocumentSize();
     fpdf_availability_ = FPDFAvail_Create(&file_availability_, &file_access_);
     DCHECK(fpdf_availability_);
+    // Currently engine does not deal efficiently with some non-linearized
+    // files.
+    // See http://code.google.com/p/chromium/issues/detail?id=59400
+    // To improve user experience we download entire file for non-linearized
+    // PDF.
+    if (FPDFAvail_IsLinearized(fpdf_availability_) != PDF_LINEARIZED) {
+      // Wait complete document.
+      process_when_pending_request_complete_ = false;
+      FPDFAvail_Destroy(fpdf_availability_);
+      fpdf_availability_ = nullptr;
+      return;
+    }
   }
 
-  // Currently engine does not deal efficiently with some non-linearized files.
-  // See http://code.google.com/p/chromium/issues/detail?id=59400
-  // To improve user experience we download entire file for non-linearized PDF.
-  if (!FPDFAvail_IsLinearized(fpdf_availability_)) {
-    doc_loader_.RequestData(0, doc_loader_.document_size());
+  if (!doc_) {
+    LoadDocument();
     return;
   }
 
-  LoadDocument();
-}
-
-void PDFiumEngine::OnPendingRequestComplete() {
-  if (!doc_ || !form_) {
-    DCHECK(fpdf_availability_);
-    LoadDocument();
+  if (pages_.empty()) {
+    LoadBody();
     return;
   }
 
@@ -1260,31 +1274,38 @@
     LoadPageInfo(true);
 }
 
-void PDFiumEngine::OnNewDataAvailable() {
-  client_->DocumentLoadProgress(doc_loader_.GetAvailableData(),
-                                doc_loader_.document_size());
-}
-
-void PDFiumEngine::OnDocumentFailed() {
-  client_->DocumentLoadFailed();
+void PDFiumEngine::OnNewDataReceived() {
+  client_->DocumentLoadProgress(doc_loader_->count_of_bytes_received(),
+                                doc_loader_->GetDocumentSize());
 }
 
 void PDFiumEngine::OnDocumentComplete() {
-  if (!doc_ || !form_) {
-    file_access_.m_FileLen = doc_loader_.document_size();
-    if (!fpdf_availability_) {
-      fpdf_availability_ = FPDFAvail_Create(&file_availability_, &file_access_);
-      DCHECK(fpdf_availability_);
-    }
-    LoadDocument();
-    return;
+  if (doc_) {
+    return FinishLoadingDocument();
   }
+  file_access_.m_FileLen = doc_loader_->GetDocumentSize();
+  if (!fpdf_availability_) {
+    fpdf_availability_ = FPDFAvail_Create(&file_availability_, &file_access_);
+    DCHECK(fpdf_availability_);
+  }
+  LoadDocument();
+}
 
-  FinishLoadingDocument();
+void PDFiumEngine::OnDocumentCanceled() {
+  if (visible_pages_.empty())
+    client_->DocumentLoadFailed();
+  else
+    OnDocumentComplete();
+}
+
+void PDFiumEngine::CancelBrowserDownload() {
+  client_->CancelBrowserDownload();
 }
 
 void PDFiumEngine::FinishLoadingDocument() {
-  DCHECK(doc_loader_.IsDocumentComplete() && doc_);
+  DCHECK(doc_loader_->IsDocumentComplete() && doc_);
+
+  LoadBody();
 
   bool need_update = false;
   for (size_t i = 0; i < pages_.size(); ++i) {
@@ -1535,7 +1556,7 @@
     return pp::Buffer_Dev();
 
   // If document is not downloaded yet, disable printing.
-  if (doc_ && !doc_loader_.IsDocumentComplete())
+  if (doc_ && !doc_loader_->IsDocumentComplete())
     return pp::Buffer_Dev();
 
   FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument();
@@ -2827,7 +2848,7 @@
 void PDFiumEngine::LoadDocument() {
   // Check if the document is ready for loading. If it isn't just bail for now,
   // we will call LoadDocument() again later.
-  if (!doc_ && !doc_loader_.IsDocumentComplete() &&
+  if (!doc_ && !doc_loader_->IsDocumentComplete() &&
       !FPDFAvail_IsDocAvail(fpdf_availability_, &download_hints_)) {
     return;
   }
@@ -2866,13 +2887,12 @@
     password_cstr = password.c_str();
     password_tries_remaining_--;
   }
-  if (doc_loader_.IsDocumentComplete() &&
+  if (doc_loader_->IsDocumentComplete() &&
       !FPDFAvail_IsLinearized(fpdf_availability_)) {
     doc_ = FPDF_LoadCustomDocument(&file_access_, password_cstr);
   } else {
     doc_ = FPDFAvail_GetDocument(fpdf_availability_, password_cstr);
   }
-
   if (!doc_) {
     if (FPDF_GetLastError() == FPDF_ERR_PASSWORD)
       *needs_password = true;
@@ -2913,6 +2933,7 @@
     GetPasswordAndLoad();
     return;
   }
+
   if (!doc_) {
     client_->DocumentLoadFailed();
     return;
@@ -2924,45 +2945,23 @@
   permissions_ = FPDF_GetDocPermissions(doc_);
   permissions_handler_revision_ = FPDF_GetSecurityHandlerRevision(doc_);
 
-  if (!form_) {
-    int form_status =
-        FPDFAvail_IsFormAvail(fpdf_availability_, &download_hints_);
-    bool doc_complete = doc_loader_.IsDocumentComplete();
-    // Try again if the data is not available and the document hasn't finished
-    // downloading.
-    if (form_status == PDF_FORM_NOTAVAIL && !doc_complete)
-      return;
+  LoadBody();
 
-    form_ = FPDFDOC_InitFormFillEnvironment(
-        doc_, static_cast<FPDF_FORMFILLINFO*>(this));
-#if defined(PDF_ENABLE_XFA)
-    FPDF_LoadXFA(doc_);
-#endif
-
-    FPDF_SetFormFieldHighlightColor(form_, 0, kFormHighlightColor);
-    FPDF_SetFormFieldHighlightAlpha(form_, kFormHighlightAlpha);
-  }
-
-  if (!doc_loader_.IsDocumentComplete()) {
-    // Check if the first page is available.  In a linearized PDF, that is not
-    // always page 0.  Doing this gives us the default page size, since when the
-    // document is available, the first page is available as well.
-    CheckPageAvailable(FPDFAvail_GetFirstPageNum(doc_), &pending_pages_);
-  }
-
-  LoadPageInfo(false);
-
-  if (doc_loader_.IsDocumentComplete())
+  if (doc_loader_->IsDocumentComplete())
     FinishLoadingDocument();
 }
 
 void PDFiumEngine::LoadPageInfo(bool reload) {
+  if (!doc_loader_)
+    return;
+  if (pages_.empty() && reload)
+    return;
   pending_pages_.clear();
   pp::Size old_document_size = document_size_;
   document_size_ = pp::Size();
   std::vector<pp::Rect> page_rects;
   int page_count = FPDF_GetPageCount(doc_);
-  bool doc_complete = doc_loader_.IsDocumentComplete();
+  bool doc_complete = doc_loader_->IsDocumentComplete();
   bool is_linear = FPDFAvail_IsLinearized(fpdf_availability_) == PDF_LINEARIZED;
   for (int i = 0; i < page_count; ++i) {
     if (i != 0) {
@@ -3018,11 +3017,59 @@
     client_->DocumentSizeUpdated(document_size_);
 }
 
+void PDFiumEngine::LoadBody() {
+  DCHECK(doc_);
+  DCHECK(fpdf_availability_);
+  if (doc_loader_->IsDocumentComplete()) {
+    LoadForm();
+  } else if (FPDFAvail_IsLinearized(fpdf_availability_) == PDF_LINEARIZED &&
+             FPDF_GetPageCount(doc_) == 1) {
+    // If we have only one page we should load form first, bacause it is may be
+    // XFA document. And after loading form the page count and its contents may
+    // be changed.
+    LoadForm();
+    if (form_status_ == PDF_FORM_NOTAVAIL)
+      return;
+  }
+  LoadPages();
+}
+
+void PDFiumEngine::LoadPages() {
+  if (pages_.empty()) {
+    if (!doc_loader_->IsDocumentComplete()) {
+      // Check if the first page is available.  In a linearized PDF, that is not
+      // always page 0.  Doing this gives us the default page size, since when
+      // the document is available, the first page is available as well.
+      CheckPageAvailable(FPDFAvail_GetFirstPageNum(doc_), &pending_pages_);
+    }
+    LoadPageInfo(false);
+  }
+}
+
+void PDFiumEngine::LoadForm() {
+  if (form_)
+    return;
+  DCHECK(doc_);
+  form_status_ = FPDFAvail_IsFormAvail(fpdf_availability_, &download_hints_);
+  if (form_status_ != PDF_FORM_NOTAVAIL || doc_loader_->IsDocumentComplete()) {
+    form_ = FPDFDOC_InitFormFillEnvironment(
+        doc_, static_cast<FPDF_FORMFILLINFO*>(this));
+#if defined(PDF_ENABLE_XFA)
+    FPDF_LoadXFA(doc_);
+#endif
+
+    FPDF_SetFormFieldHighlightColor(form_, 0, kFormHighlightColor);
+    FPDF_SetFormFieldHighlightAlpha(form_, kFormHighlightAlpha);
+  }
+}  // namespace chrome_pdf
+
 void PDFiumEngine::CalculateVisiblePages() {
+  if (!doc_loader_)
+    return;
   // Clear pending requests queue, since it may contain requests to the pages
   // that are already invisible (after scrolling for example).
   pending_pages_.clear();
-  doc_loader_.ClearPendingRequests();
+  doc_loader_->ClearPendingRequests();
 
   visible_pages_.clear();
   pp::Rect visible_rect(plugin_size_);
@@ -3083,7 +3130,7 @@
 }
 
 bool PDFiumEngine::CheckPageAvailable(int index, std::vector<int>* pending) {
-  if (!doc_ || !form_)
+  if (!doc_)
     return false;
 
   const int num_pages = static_cast<int>(pages_.size());
diff --git a/pdf/pdfium/pdfium_engine.h b/pdf/pdfium/pdfium_engine.h
index 493c89b..bba86489 100644
--- a/pdf/pdfium/pdfium_engine.h
+++ b/pdf/pdfium/pdfium_engine.h
@@ -106,7 +106,6 @@
 #if defined(PDF_ENABLE_XFA)
   void SetScrollPosition(const pp::Point& position) override;
 #endif
-  bool IsProgressiveLoad() override;
   std::string GetMetadata(const std::string& key) override;
   void SetCaretPosition(const pp::Point& position) override;
   void MoveRangeSelectionExtent(const pp::Point& extent) override;
@@ -115,12 +114,12 @@
 
   // DocumentLoader::Client implementation.
   pp::Instance* GetPluginInstance() override;
-  pp::URLLoader CreateURLLoader() override;
-  void OnPartialDocumentLoaded() override;
+  std::unique_ptr<URLLoaderWrapper> CreateURLLoader() override;
   void OnPendingRequestComplete() override;
-  void OnNewDataAvailable() override;
-  void OnDocumentFailed() override;
+  void OnNewDataReceived() override;
   void OnDocumentComplete() override;
+  void OnDocumentCanceled() override;
+  void CancelBrowserDownload() override;
 
   void UnsupportedFeature(int type);
   void FontSubstituted();
@@ -199,11 +198,11 @@
   friend class SelectionChangeInvalidator;
 
   struct FileAvail : public FX_FILEAVAIL {
-    DocumentLoader* loader;
+    PDFiumEngine* engine;
   };
 
   struct DownloadHints : public FX_DOWNLOADHINTS {
-    DocumentLoader* loader;
+    PDFiumEngine* engine;
   };
 
   // PDFium interface to get block of data.
@@ -251,6 +250,12 @@
   // document size.
   void LoadPageInfo(bool reload);
 
+  void LoadBody();
+
+  void LoadPages();
+
+  void LoadForm();
+
   // Calculates which pages should be displayed right now.
   void CalculateVisiblePages();
 
@@ -638,7 +643,7 @@
   double current_zoom_;
   unsigned int current_rotation_;
 
-  DocumentLoader doc_loader_;  // Main document's loader.
+  std::unique_ptr<DocumentLoader> doc_loader_;  // Main document's loader.
   std::string url_;
   std::string headers_;
   pp::CompletionCallbackFactory<PDFiumEngine> find_factory_;
@@ -656,6 +661,9 @@
   // on the page.
   FPDF_FORMHANDLE form_;
 
+  // Current form availability status.
+  int form_status_ = PDF_FORM_NOTAVAIL;
+
   // The page(s) of the document.
   std::vector<std::unique_ptr<PDFiumPage>> pages_;
 
@@ -783,6 +791,11 @@
   // to false after the user finishes getting their password.
   bool getting_password_;
 
+  // While true, the document try to be opened and parsed after download each
+  // part. Else the document will be opened and parsed only on finish of
+  // downloading.
+  bool process_when_pending_request_complete_ = true;
+
   enum class RangeSelectionDirection { Left, Right };
   RangeSelectionDirection range_selection_direction_;
 
diff --git a/pdf/preview_mode_client.cc b/pdf/preview_mode_client.cc
index 677ce02..4d41a4ac 100644
--- a/pdf/preview_mode_client.cc
+++ b/pdf/preview_mode_client.cc
@@ -163,6 +163,8 @@
   return false;
 }
 
+void PreviewModeClient::CancelBrowserDownload() {}
+
 uint32_t PreviewModeClient::GetBackgroundColor() {
   NOTREACHED();
   return 0;
diff --git a/pdf/preview_mode_client.h b/pdf/preview_mode_client.h
index 82c0792..04f09b5 100644
--- a/pdf/preview_mode_client.h
+++ b/pdf/preview_mode_client.h
@@ -72,6 +72,7 @@
   void DocumentLoadProgress(uint32_t available, uint32_t doc_size) override;
   void FormTextFieldFocusChange(bool in_focus) override;
   bool IsPrintPreview() override;
+  void CancelBrowserDownload() override;
   uint32_t GetBackgroundColor() override;
 
  private:
diff --git a/pdf/range_set.cc b/pdf/range_set.cc
new file mode 100644
index 0000000..df53d660
--- /dev/null
+++ b/pdf/range_set.cc
@@ -0,0 +1,253 @@
+// Copyright 2016 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 "pdf/range_set.h"
+
+#include <algorithm>
+#include <sstream>
+#include <vector>
+
+namespace chrome_pdf {
+
+namespace {
+
+gfx::Range FixDirection(const gfx::Range& range) {
+  if (!range.IsValid() || !range.is_reversed())
+    return range;
+  return gfx::Range(range.end() + 1, range.start() + 1);
+}
+
+}  // namespace
+
+RangeSet::RangeSet() {}
+
+RangeSet::RangeSet(const gfx::Range& range) {
+  Union(range);
+}
+
+RangeSet::RangeSet(const RangeSet& range_set) : ranges_(range_set.ranges_) {}
+
+RangeSet::RangeSet(RangeSet&& range_set)
+    : ranges_(std::move(range_set.ranges_)) {}
+
+RangeSet& RangeSet::operator=(const RangeSet& other) {
+  ranges_ = other.ranges_;
+  return *this;
+}
+
+RangeSet::~RangeSet() {}
+
+bool RangeSet::operator==(const RangeSet& other) const {
+  return other.ranges_ == ranges_;
+}
+
+bool RangeSet::operator!=(const RangeSet& other) const {
+  return other.ranges_ != ranges_;
+}
+
+void RangeSet::Union(const gfx::Range& range) {
+  if (range.is_empty())
+    return;
+  gfx::Range fixed_range = FixDirection(range);
+  if (IsEmpty()) {
+    ranges_.insert(fixed_range);
+    return;
+  }
+
+  auto start = ranges_.upper_bound(fixed_range);
+  if (start != ranges_.begin())
+    --start;  // start now points to the key equal or lower than offset.
+  if (start->end() < fixed_range.start())
+    ++start;  // start element is entirely before current range, skip it.
+
+  auto end = ranges_.upper_bound(gfx::Range(fixed_range.end()));
+  if (start == end) {  // No ranges to merge.
+    ranges_.insert(fixed_range);
+    return;
+  }
+
+  --end;
+
+  int new_start = std::min<size_t>(start->start(), fixed_range.start());
+  int new_end = std::max(end->end(), fixed_range.end());
+
+  ranges_.erase(start, ++end);
+  ranges_.insert(gfx::Range(new_start, new_end));
+}
+
+void RangeSet::Union(const RangeSet& range_set) {
+  if (&range_set == this)
+    return;
+  for (const auto& it : range_set.ranges()) {
+    Union(it);
+  }
+}
+
+bool RangeSet::Contains(uint32_t point) const {
+  return Contains(gfx::Range(point, point + 1));
+}
+
+bool RangeSet::Contains(const gfx::Range& range) const {
+  if (range.is_empty())
+    return false;
+  const gfx::Range fixed_range = FixDirection(range);
+  auto it = ranges().upper_bound(fixed_range);
+  if (it == ranges().begin())
+    return false;  // No ranges includes range.start().
+
+  --it;  // Now it starts equal or before range.start().
+  return it->end() >= fixed_range.end();
+}
+
+bool RangeSet::Contains(const RangeSet& range_set) const {
+  for (const auto& it : range_set.ranges()) {
+    if (!Contains(it))
+      return false;
+  }
+  return true;
+}
+
+bool RangeSet::Intersects(const gfx::Range& range) const {
+  if (IsEmpty() || range.is_empty())
+    return false;
+  const gfx::Range fixed_range = FixDirection(range);
+  auto start = ranges_.upper_bound(fixed_range);
+  if (start != ranges_.begin()) {
+    --start;
+  }
+  // start now points to the key equal or lower than range.start().
+  if (start->end() < range.start()) {
+    // start element is entirely before current range, skip it.
+    ++start;
+  }
+  auto end = ranges_.upper_bound(gfx::Range(fixed_range.end()));
+  for (auto it = start; it != end; ++it) {
+    if (fixed_range.end() > it->start() && fixed_range.start() < it->end())
+      return true;
+  }
+  return false;
+}
+
+bool RangeSet::Intersects(const RangeSet& range_set) const {
+  for (const auto& it : range_set.ranges()) {
+    if (Intersects(it))
+      return true;
+  }
+  return false;
+}
+
+void RangeSet::Intersect(const gfx::Range& range) {
+  Intersect(RangeSet(range));
+}
+
+void RangeSet::Intersect(const RangeSet& range_set) {
+  if (IsEmpty())
+    return;
+  RangesContainer new_ranges;
+  for (const auto& range : range_set.ranges()) {
+    auto start = ranges_.upper_bound(range);
+    if (start != ranges_.begin())
+      --start;  // start now points to the key equal or lower than
+                // range.start().
+    if (start->end() < range.start())
+      ++start;  // start element is entirely before current range, skip it.
+    auto end = ranges_.upper_bound(gfx::Range(range.end()));
+    if (start == end) {  // No data in the current range available.
+      continue;
+    }
+    for (auto it = start; it != end; ++it) {
+      const gfx::Range new_range = range.Intersect(*it);
+      if (!new_range.is_empty()) {
+        new_ranges.insert(new_range);
+      }
+    }
+  }
+  new_ranges.swap(ranges_);
+}
+
+void RangeSet::Subtract(const gfx::Range& range) {
+  if (range.is_empty() || IsEmpty())
+    return;
+  const gfx::Range fixed_range = FixDirection(range);
+  auto start = ranges_.upper_bound(fixed_range);
+  if (start != ranges_.begin())
+    --start;  // start now points to the key equal or lower than
+              // range.start().
+  if (start->end() < fixed_range.start())
+    ++start;  // start element is entirely before current range, skip it.
+  auto end = ranges_.upper_bound(gfx::Range(fixed_range.end()));
+  if (start == end) {  // No data in the current range available.
+    return;
+  }
+  std::vector<gfx::Range> new_ranges;
+  for (auto it = start; it != end; ++it) {
+    const gfx::Range left(it->start(),
+                          std::min(it->end(), fixed_range.start()));
+    const gfx::Range right(std::max(it->start(), fixed_range.end()), it->end());
+    if (!left.is_empty() && !left.is_reversed()) {
+      new_ranges.push_back(left);
+    }
+    if (!right.is_empty() && !right.is_reversed() && right != left) {
+      new_ranges.push_back(right);
+    }
+  }
+  ranges_.erase(start, end);
+  for (const auto& it : new_ranges) {
+    ranges_.insert(it);
+  }
+}
+
+void RangeSet::Subtract(const RangeSet& range_set) {
+  if (&range_set == this) {
+    ranges_.clear();
+    return;
+  }
+  for (const auto& range : range_set.ranges()) {
+    Subtract(range);
+  }
+}
+
+void RangeSet::Xor(const gfx::Range& range) {
+  Xor(RangeSet(range));
+}
+
+void RangeSet::Xor(const RangeSet& range_set) {
+  RangeSet tmp = *this;
+  tmp.Intersect(range_set);
+  Union(range_set);
+  Subtract(tmp);
+}
+
+bool RangeSet::IsEmpty() const {
+  return ranges().empty();
+}
+
+void RangeSet::Clear() {
+  ranges_.clear();
+}
+
+gfx::Range RangeSet::First() const {
+  return *ranges().begin();
+}
+
+gfx::Range RangeSet::Last() const {
+  return *ranges().rbegin();
+}
+
+std::string RangeSet::ToString() const {
+  std::stringstream ss;
+  ss << "{";
+  for (const auto& it : ranges()) {
+    ss << "[" << it.start() << "," << it.end() << ")";
+  }
+  ss << "}";
+  return ss.str();
+}
+
+}  // namespace chrome_pdf
+
+std::ostream& operator<<(std::ostream& os,
+                         const chrome_pdf::RangeSet& range_set) {
+  return (os << range_set.ToString());
+}
diff --git a/pdf/range_set.h b/pdf/range_set.h
new file mode 100644
index 0000000..b615999
--- /dev/null
+++ b/pdf/range_set.h
@@ -0,0 +1,77 @@
+// Copyright 2016 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.
+
+// Defines a set of geometric ranges, and standard operations on it.
+
+#ifndef PDF_RANGE_SET_H_
+#define PDF_RANGE_SET_H_
+
+#include <ostream>
+#include <set>
+#include <string>
+
+#include "ui/gfx/range/range.h"
+
+namespace chrome_pdf {
+
+class RangeSet {
+ public:
+  RangeSet();
+  explicit RangeSet(const gfx::Range& range);
+  ~RangeSet();
+
+  RangeSet(const RangeSet& range_set);
+  RangeSet(RangeSet&& range_set);
+  RangeSet& operator=(const RangeSet& other);
+
+  bool operator==(const RangeSet& other) const;
+  bool operator!=(const RangeSet& other) const;
+
+  bool Contains(uint32_t point) const;
+  bool Contains(const gfx::Range& range) const;
+  bool Contains(const RangeSet& range_set) const;
+
+  bool Intersects(const gfx::Range& range) const;
+  bool Intersects(const RangeSet& range_set) const;
+
+  void Union(const gfx::Range& range);
+  void Union(const RangeSet& range_set);
+
+  void Intersect(const gfx::Range& range);
+  void Intersect(const RangeSet& range_set);
+
+  void Subtract(const gfx::Range& range);
+  void Subtract(const RangeSet& range_set);
+
+  void Xor(const gfx::Range& range);
+  void Xor(const RangeSet& range_set);
+
+  bool IsEmpty() const;
+  void Clear();
+
+  gfx::Range First() const;
+  gfx::Range Last() const;
+  std::string ToString() const;
+
+  struct range_compare {
+    bool operator()(const gfx::Range& lval, const gfx::Range& rval) const {
+      return lval.start() < rval.start();
+    }
+  };
+
+  using RangesContainer = std::set<gfx::Range, range_compare>;
+
+  const RangesContainer& ranges() const { return ranges_; }
+  size_t Size() const { return ranges_.size(); }
+
+ private:
+  RangesContainer ranges_;
+};
+
+}  // namespace chrome_pdf
+
+std::ostream& operator<<(std::ostream& os,
+                         const chrome_pdf::RangeSet& range_set);
+
+#endif  // PDF_RANGE_SET_H_
diff --git a/pdf/range_set_unittest.cc b/pdf/range_set_unittest.cc
new file mode 100644
index 0000000..75047dd
--- /dev/null
+++ b/pdf/range_set_unittest.cc
@@ -0,0 +1,303 @@
+// Copyright 2016 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 "pdf/range_set.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_pdf {
+
+TEST(RangeSetTest, Union) {
+  {
+    RangeSet range_set;
+    EXPECT_EQ("{}", range_set.ToString());
+    range_set.Union(gfx::Range(50, 100));
+    EXPECT_EQ("{[50,100)}", range_set.ToString());
+    range_set.Union(gfx::Range(80, 150));
+    EXPECT_EQ("{[50,150)}", range_set.ToString());
+    range_set.Union(gfx::Range(0, 70));
+    EXPECT_EQ("{[0,150)}", range_set.ToString());
+    range_set.Union(gfx::Range(70, 120));
+    EXPECT_EQ("{[0,150)}", range_set.ToString());
+    range_set.Union(gfx::Range(200, 150));
+    EXPECT_EQ("{[0,150)[151,201)}", range_set.ToString());
+    range_set.Union(gfx::Range(150, 151));
+    EXPECT_EQ("{[0,201)}", range_set.ToString());
+    range_set.Union(gfx::Range(0, 300));
+    EXPECT_EQ("{[0,300)}", range_set.ToString());
+    range_set.Union(gfx::Range(500, 600));
+    EXPECT_EQ("{[0,300)[500,600)}", range_set.ToString());
+  }
+  {
+    RangeSet range_set_1;
+    range_set_1.Union(gfx::Range(0, 10));
+    range_set_1.Union(gfx::Range(20, 30));
+    range_set_1.Union(gfx::Range(40, 50));
+
+    EXPECT_EQ("{[0,10)[20,30)[40,50)}", range_set_1.ToString());
+    range_set_1.Union(range_set_1);
+    EXPECT_EQ("{[0,10)[20,30)[40,50)}", range_set_1.ToString());
+
+    RangeSet range_set_2;
+    range_set_2.Union(gfx::Range(10, 20));
+    range_set_2.Union(gfx::Range(30, 40));
+    range_set_2.Union(gfx::Range(50, 60));
+
+    EXPECT_EQ("{[10,20)[30,40)[50,60)}", range_set_2.ToString());
+    range_set_1.Union(range_set_2);
+    EXPECT_EQ("{[0,60)}", range_set_1.ToString());
+    EXPECT_EQ(RangeSet(gfx::Range(0, 60)), range_set_1);
+  }
+}
+
+TEST(RangeSetTest, Contains) {
+  RangeSet range_set;
+  range_set.Union(gfx::Range(10, 20));
+  range_set.Union(gfx::Range(30, 40));
+  range_set.Union(gfx::Range(50, 60));
+  EXPECT_TRUE(range_set.Contains(range_set));
+
+  {
+    EXPECT_FALSE(range_set.Contains(9));
+    EXPECT_FALSE(range_set.Contains(29));
+    EXPECT_FALSE(range_set.Contains(49));
+
+    EXPECT_TRUE(range_set.Contains(10));
+    EXPECT_TRUE(range_set.Contains(30));
+    EXPECT_TRUE(range_set.Contains(50));
+
+    EXPECT_TRUE(range_set.Contains(15));
+    EXPECT_TRUE(range_set.Contains(35));
+    EXPECT_TRUE(range_set.Contains(55));
+
+    EXPECT_TRUE(range_set.Contains(19));
+    EXPECT_TRUE(range_set.Contains(39));
+    EXPECT_TRUE(range_set.Contains(59));
+
+    EXPECT_FALSE(range_set.Contains(20));
+    EXPECT_FALSE(range_set.Contains(40));
+    EXPECT_FALSE(range_set.Contains(60));
+  }
+  {
+    EXPECT_FALSE(range_set.Contains(gfx::Range(0, 10)));
+    EXPECT_FALSE(range_set.Contains(gfx::Range(20, 30)));
+    EXPECT_FALSE(range_set.Contains(gfx::Range(40, 50)));
+
+    EXPECT_FALSE(range_set.Contains(gfx::Range(5, 15)));
+    EXPECT_FALSE(range_set.Contains(gfx::Range(25, 35)));
+    EXPECT_FALSE(range_set.Contains(gfx::Range(45, 55)));
+
+    EXPECT_TRUE(range_set.Contains(gfx::Range(10, 15)));
+    EXPECT_TRUE(range_set.Contains(gfx::Range(30, 35)));
+    EXPECT_TRUE(range_set.Contains(gfx::Range(50, 55)));
+
+    EXPECT_TRUE(range_set.Contains(gfx::Range(15, 20)));
+    EXPECT_TRUE(range_set.Contains(gfx::Range(35, 40)));
+    EXPECT_TRUE(range_set.Contains(gfx::Range(55, 60)));
+
+    EXPECT_TRUE(range_set.Contains(gfx::Range(10, 20)));
+    EXPECT_TRUE(range_set.Contains(gfx::Range(30, 40)));
+    EXPECT_TRUE(range_set.Contains(gfx::Range(50, 60)));
+
+    EXPECT_FALSE(range_set.Contains(gfx::Range(15, 25)));
+    EXPECT_FALSE(range_set.Contains(gfx::Range(35, 45)));
+    EXPECT_FALSE(range_set.Contains(gfx::Range(55, 65)));
+
+    EXPECT_FALSE(range_set.Contains(gfx::Range(20, 25)));
+    EXPECT_FALSE(range_set.Contains(gfx::Range(40, 45)));
+    EXPECT_FALSE(range_set.Contains(gfx::Range(60, 65)));
+
+    EXPECT_FALSE(range_set.Contains(gfx::Range(0, 100)));
+  }
+  {
+    RangeSet range_set_2 = range_set;
+    EXPECT_TRUE(range_set_2.Contains(range_set));
+    range_set_2.Union(gfx::Range(100, 200));
+    EXPECT_TRUE(range_set_2.Contains(range_set));
+    EXPECT_FALSE(range_set.Contains(range_set_2));
+  }
+}
+
+TEST(RangeSetTest, Intersects) {
+  RangeSet range_set;
+  range_set.Union(gfx::Range(10, 20));
+  range_set.Union(gfx::Range(30, 40));
+  range_set.Union(gfx::Range(50, 60));
+  EXPECT_TRUE(range_set.Intersects(range_set));
+  {
+    EXPECT_FALSE(range_set.Intersects(gfx::Range(0, 10)));
+    EXPECT_FALSE(range_set.Intersects(gfx::Range(20, 30)));
+    EXPECT_FALSE(range_set.Intersects(gfx::Range(40, 50)));
+
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(5, 15)));
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(25, 35)));
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(45, 55)));
+
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(10, 15)));
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(30, 35)));
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(50, 55)));
+
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(15, 20)));
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(35, 40)));
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(55, 60)));
+
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(10, 20)));
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(30, 40)));
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(50, 60)));
+
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(15, 25)));
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(35, 45)));
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(55, 65)));
+
+    EXPECT_FALSE(range_set.Intersects(gfx::Range(20, 25)));
+    EXPECT_FALSE(range_set.Intersects(gfx::Range(40, 45)));
+    EXPECT_FALSE(range_set.Intersects(gfx::Range(60, 65)));
+
+    EXPECT_TRUE(range_set.Intersects(gfx::Range(0, 100)));
+  }
+  {
+    RangeSet range_set_2;
+    range_set_2.Union(gfx::Range(5, 15));
+    range_set_2.Union(gfx::Range(25, 35));
+    range_set_2.Union(gfx::Range(45, 55));
+    EXPECT_TRUE(range_set_2.Intersects(range_set));
+  }
+  {
+    RangeSet range_set_2;
+    range_set_2.Union(gfx::Range(5, 10));
+    range_set_2.Union(gfx::Range(25, 30));
+    range_set_2.Union(gfx::Range(45, 50));
+    EXPECT_FALSE(range_set_2.Intersects(range_set));
+  }
+}
+
+TEST(RangeSetTest, Intersect) {
+  {
+    RangeSet range_set;
+    range_set.Union(gfx::Range(10, 20));
+    range_set.Union(gfx::Range(30, 40));
+    range_set.Union(gfx::Range(50, 60));
+
+    EXPECT_EQ("{[10,20)[30,40)[50,60)}", range_set.ToString());
+    range_set.Intersect(range_set);
+    EXPECT_EQ("{[10,20)[30,40)[50,60)}", range_set.ToString());
+    range_set.Intersect(gfx::Range(0, 100));
+    EXPECT_EQ("{[10,20)[30,40)[50,60)}", range_set.ToString());
+    range_set.Intersect(gfx::Range(0, 55));
+    EXPECT_EQ("{[10,20)[30,40)[50,55)}", range_set.ToString());
+    range_set.Intersect(gfx::Range(15, 100));
+    EXPECT_EQ("{[15,20)[30,40)[50,55)}", range_set.ToString());
+    range_set.Intersect(gfx::Range(17, 53));
+    EXPECT_EQ("{[17,20)[30,40)[50,53)}", range_set.ToString());
+    range_set.Intersect(gfx::Range(19, 45));
+    EXPECT_EQ("{[19,20)[30,40)}", range_set.ToString());
+    range_set.Intersect(gfx::Range(30, 45));
+    EXPECT_EQ("{[30,40)}", range_set.ToString());
+    range_set.Intersect(gfx::Range(35, 40));
+    EXPECT_EQ("{[35,40)}", range_set.ToString());
+    range_set.Intersect(gfx::Range(35, 35));
+    EXPECT_TRUE(range_set.IsEmpty());
+  }
+  {
+    RangeSet range_set;
+    range_set.Union(gfx::Range(10, 20));
+    range_set.Union(gfx::Range(30, 40));
+    range_set.Union(gfx::Range(50, 60));
+
+    RangeSet range_set_2;
+    range_set_2.Union(gfx::Range(12, 17));
+    range_set_2.Union(gfx::Range(25, 35));
+    range_set_2.Union(gfx::Range(39, 55));
+    range_set_2.Union(gfx::Range(59, 100));
+
+    range_set.Intersect(range_set_2);
+    EXPECT_EQ("{[12,17)[30,35)[39,40)[50,55)[59,60)}", range_set.ToString());
+  }
+}
+
+TEST(RangeSetTest, Subtract) {
+  {
+    RangeSet range_set;
+    range_set.Union(gfx::Range(10, 20));
+    range_set.Union(gfx::Range(30, 40));
+    range_set.Union(gfx::Range(50, 60));
+
+    EXPECT_EQ("{[10,20)[30,40)[50,60)}", range_set.ToString());
+    range_set.Subtract(gfx::Range(35, 35));
+    EXPECT_EQ("{[10,20)[30,40)[50,60)}", range_set.ToString());
+    range_set.Subtract(gfx::Range(0, 5));
+    EXPECT_EQ("{[10,20)[30,40)[50,60)}", range_set.ToString());
+    range_set.Subtract(gfx::Range(70, 80));
+    EXPECT_EQ("{[10,20)[30,40)[50,60)}", range_set.ToString());
+    range_set.Subtract(gfx::Range(35, 39));
+    EXPECT_EQ("{[10,20)[30,35)[39,40)[50,60)}", range_set.ToString());
+    range_set.Subtract(gfx::Range(15, 32));
+    EXPECT_EQ("{[10,15)[32,35)[39,40)[50,60)}", range_set.ToString());
+    range_set.Subtract(gfx::Range(15, 55));
+    EXPECT_EQ("{[10,15)[55,60)}", range_set.ToString());
+    range_set.Subtract(gfx::Range(0, 100));
+    EXPECT_EQ("{}", range_set.ToString());
+  }
+  {
+    RangeSet range_set;
+    range_set.Union(gfx::Range(10, 20));
+    range_set.Union(gfx::Range(30, 40));
+    range_set.Union(gfx::Range(50, 60));
+    range_set.Subtract(range_set);
+    EXPECT_EQ("{}", range_set.ToString());
+  }
+  {
+    RangeSet range_set;
+    range_set.Union(gfx::Range(10, 20));
+    range_set.Union(gfx::Range(30, 40));
+    range_set.Union(gfx::Range(50, 60));
+
+    RangeSet range_set_2;
+    range_set_2.Union(gfx::Range(12, 17));
+    range_set_2.Union(gfx::Range(25, 35));
+    range_set_2.Union(gfx::Range(39, 55));
+    range_set_2.Union(gfx::Range(59, 100));
+
+    range_set.Subtract(range_set_2);
+    EXPECT_EQ("{[10,12)[17,20)[35,39)[55,59)}", range_set.ToString());
+  }
+}
+
+TEST(RangeSetTest, Xor) {
+  {
+    RangeSet range_set;
+    range_set.Union(gfx::Range(10, 20));
+    range_set.Union(gfx::Range(30, 40));
+    range_set.Union(gfx::Range(50, 60));
+    range_set.Xor(range_set);
+    EXPECT_EQ("{}", range_set.ToString());
+  }
+  {
+    RangeSet range_set;
+    range_set.Union(gfx::Range(10, 20));
+    range_set.Union(gfx::Range(30, 40));
+    range_set.Union(gfx::Range(50, 60));
+
+    RangeSet range_set_2;
+    range_set_2.Union(gfx::Range(12, 17));
+    range_set_2.Union(gfx::Range(25, 35));
+    range_set_2.Union(gfx::Range(39, 55));
+    range_set_2.Union(gfx::Range(59, 100));
+
+    range_set.Xor(range_set_2);
+    EXPECT_EQ("{[10,12)[17,20)[25,30)[35,39)[40,50)[55,59)[60,100)}",
+              range_set.ToString());
+  }
+}
+
+TEST(RangeSetTest, OperationsOnEmptySet) {
+  RangeSet range_set;
+  range_set.Intersect(gfx::Range(10, 20));
+  range_set.Intersects(gfx::Range(10, 20));
+  range_set.Subtract(gfx::Range(10, 20));
+  range_set.Xor(gfx::Range(30, 40));
+  range_set.Union(gfx::Range(10, 20));
+}
+
+}  // namespace chrome_pdf
diff --git a/pdf/timer.cc b/pdf/timer.cc
new file mode 100644
index 0000000..e5956744
--- /dev/null
+++ b/pdf/timer.cc
@@ -0,0 +1,30 @@
+// Copyright 2016 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 "pdf/timer.h"
+
+#include "ppapi/cpp/core.h"
+#include "ppapi/cpp/module.h"
+
+namespace chrome_pdf {
+
+Timer::Timer(int delay_in_milliseconds)
+    : delay_(delay_in_milliseconds), callback_factory_(this) {
+  PostCallback();
+}
+
+Timer::~Timer() {}
+
+void Timer::PostCallback() {
+  pp::CompletionCallback callback =
+      callback_factory_.NewCallback(&Timer::TimerProc);
+  pp::Module::Get()->core()->CallOnMainThread(delay_, callback, 0);
+}
+
+void Timer::TimerProc(int32_t /*result*/) {
+  PostCallback();
+  OnTimer();
+}
+
+}  // namespace chrome_pdf
diff --git a/pdf/timer.h b/pdf/timer.h
new file mode 100644
index 0000000..a27f78f3
--- /dev/null
+++ b/pdf/timer.h
@@ -0,0 +1,35 @@
+// Copyright 2016 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 PDF_TIMER_H_
+#define PDF_TIMER_H_
+
+#include "base/macros.h"
+#include "ppapi/utility/completion_callback_factory.h"
+
+namespace chrome_pdf {
+
+// Timer implementation for pepper plugins, based on pp::Core::CallOnMainThread.
+// We can not use base::Timer for plugins, because they have no
+// base::MessageLoop, on which it is based.
+class Timer {
+ public:
+  explicit Timer(int delay_in_milliseconds);
+  virtual ~Timer();
+
+  virtual void OnTimer() = 0;
+
+ private:
+  void PostCallback();
+  void TimerProc(int32_t result);
+
+  int delay_;
+  pp::CompletionCallbackFactory<Timer> callback_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(Timer);
+};
+
+}  // namespace chrome_pdf
+
+#endif  // PDF_TIMER_H_
diff --git a/pdf/url_loader_wrapper.h b/pdf/url_loader_wrapper.h
new file mode 100644
index 0000000..b95cfd2
--- /dev/null
+++ b/pdf/url_loader_wrapper.h
@@ -0,0 +1,62 @@
+// Copyright 2016 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 PDF_URL_LOADER_WRAPPER_H_
+#define PDF_URL_LOADER_WRAPPER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "ppapi/cpp/completion_callback.h"
+
+namespace chrome_pdf {
+
+class URLLoaderWrapper {
+ public:
+  virtual ~URLLoaderWrapper() {}
+
+  // Returns length of content, will be -1, if it is unknown.
+  virtual int GetContentLength() const = 0;
+  // Returns if the response headers contains "accept-ranges".
+  virtual bool IsAcceptRangesBytes() const = 0;
+  // Returns if the content encoded in response.
+  virtual bool IsContentEncoded() const = 0;
+  // Returns response content type.
+  virtual std::string GetContentType() const = 0;
+  // Returns response content disposition.
+  virtual std::string GetContentDisposition() const = 0;
+  // Returns response status code.
+  virtual int GetStatusCode() const = 0;
+  // Returns if the response contains multi parts.
+  virtual bool IsMultipart() const = 0;
+  // If true, [start,end] - is byte range contains in response (include end).
+  // If false, response contains full document, start/end will be undefined.
+  virtual bool GetByteRange(int* start, int* end) const = 0;
+
+  // Close connection.
+  virtual void Close() = 0;
+  // Open new connection and send http range request.
+  virtual void OpenRange(const std::string& url,
+                         const std::string& referrer_url,
+                         uint32_t position,
+                         uint32_t size,
+                         const pp::CompletionCallback& cc) = 0;
+  // Read the response body. The size of the buffer must be large enough to
+  // hold the specified number of bytes to read.
+  // This function might perform a partial read.
+  virtual void ReadResponseBody(char* buffer,
+                                int buffer_size,
+                                const pp::CompletionCallback& cc) = 0;
+  // Returns the current download progress.
+  // Progress only refers to the response body and does not include the headers.
+  // If false, progress is unknown, bytes_received/total_bytes_to_be_received
+  // will be undefined.
+  virtual bool GetDownloadProgress(
+      int64_t* bytes_received,
+      int64_t* total_bytes_to_be_received) const = 0;
+};
+
+}  // namespace chrome_pdf
+
+#endif  // PDF_URL_LOADER_WRAPPER_H_
diff --git a/pdf/url_loader_wrapper_impl.cc b/pdf/url_loader_wrapper_impl.cc
new file mode 100644
index 0000000..b7bc808
--- /dev/null
+++ b/pdf/url_loader_wrapper_impl.cc
@@ -0,0 +1,325 @@
+// Copyright 2016 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 "pdf/url_loader_wrapper_impl.h"
+
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/http/http_util.h"
+#include "pdf/timer.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/cpp/logging.h"
+#include "ppapi/cpp/url_request_info.h"
+#include "ppapi/cpp/url_response_info.h"
+
+namespace chrome_pdf {
+
+namespace {
+// We should read with delay to prevent block UI thread, and reduce CPU usage.
+const int kReadDelayMs = 2;
+
+pp::URLRequestInfo MakeRangeRequest(pp::Instance* plugin_instance,
+                                    const std::string& url,
+                                    const std::string& referrer_url,
+                                    uint32_t position,
+                                    uint32_t size) {
+  pp::URLRequestInfo request(plugin_instance);
+  request.SetURL(url);
+  request.SetMethod("GET");
+  request.SetFollowRedirects(false);
+  request.SetCustomReferrerURL(referrer_url);
+
+  // According to rfc2616, byte range specifies position of the first and last
+  // bytes in the requested range inclusively. Therefore we should subtract 1
+  // from the position + size, to get index of the last byte that needs to be
+  // downloaded.
+  std::string str_header =
+      base::StringPrintf("Range: bytes=%d-%d", position, position + size - 1);
+  pp::Var header(str_header.c_str());
+  request.SetHeaders(header);
+
+  return request;
+}
+
+bool GetByteRangeFromStr(const std::string& content_range_str,
+                         int* start,
+                         int* end) {
+  std::string range = content_range_str;
+  if (!base::StartsWith(range, "bytes", base::CompareCase::INSENSITIVE_ASCII))
+    return false;
+
+  range = range.substr(strlen("bytes"));
+  std::string::size_type pos = range.find('-');
+  std::string range_end;
+  if (pos != std::string::npos)
+    range_end = range.substr(pos + 1);
+  base::TrimWhitespaceASCII(range, base::TRIM_LEADING, &range);
+  base::TrimWhitespaceASCII(range_end, base::TRIM_LEADING, &range_end);
+  *start = atoi(range.c_str());
+  *end = atoi(range_end.c_str());
+  return true;
+}
+
+// If the headers have a byte-range response, writes the start and end
+// positions and returns true if at least the start position was parsed.
+// The end position will be set to 0 if it was not found or parsed from the
+// response.
+// Returns false if not even a start position could be parsed.
+bool GetByteRangeFromHeaders(const std::string& headers, int* start, int* end) {
+  net::HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\n");
+  while (it.GetNext()) {
+    if (base::LowerCaseEqualsASCII(it.name(), "content-range")) {
+      if (GetByteRangeFromStr(it.values().c_str(), start, end))
+        return true;
+    }
+  }
+  return false;
+}
+
+bool IsDoubleEndLineAtEnd(const char* buffer, int size) {
+  if (size < 2)
+    return false;
+
+  if (buffer[size - 1] == '\n' && buffer[size - 2] == '\n')
+    return true;
+
+  if (size < 4)
+    return false;
+
+  return buffer[size - 1] == '\n' && buffer[size - 2] == '\r' &&
+         buffer[size - 3] == '\n' && buffer[size - 4] == '\r';
+}
+
+}  // namespace
+
+class URLLoaderWrapperImpl::ReadStarter : public Timer {
+ public:
+  explicit ReadStarter(URLLoaderWrapperImpl* owner)
+      : Timer(kReadDelayMs), owner_(owner) {}
+  ~ReadStarter() override {}
+
+  // Timer overrides:
+  void OnTimer() override { owner_->ReadResponseBodyImpl(); }
+
+ private:
+  URLLoaderWrapperImpl* owner_;
+};
+
+URLLoaderWrapperImpl::URLLoaderWrapperImpl(pp::Instance* plugin_instance,
+                                           const pp::URLLoader& url_loader)
+    : plugin_instance_(plugin_instance),
+      url_loader_(url_loader),
+      callback_factory_(this) {
+  SetHeadersFromLoader();
+}
+
+URLLoaderWrapperImpl::~URLLoaderWrapperImpl() {
+  Close();
+  // We should call callbacks to prevent memory leaks.
+  // The callbacks don't do anything, because the objects that created the
+  // callbacks have been destroyed.
+  if (!did_open_callback_.IsOptional())
+    did_open_callback_.RunAndClear(-1);
+  if (!did_read_callback_.IsOptional())
+    did_read_callback_.RunAndClear(-1);
+}
+
+int URLLoaderWrapperImpl::GetContentLength() const {
+  return content_length_;
+}
+
+bool URLLoaderWrapperImpl::IsAcceptRangesBytes() const {
+  return accept_ranges_bytes_;
+}
+
+bool URLLoaderWrapperImpl::IsContentEncoded() const {
+  return content_encoded_;
+}
+
+std::string URLLoaderWrapperImpl::GetContentType() const {
+  return content_type_;
+}
+std::string URLLoaderWrapperImpl::GetContentDisposition() const {
+  return content_disposition_;
+}
+
+int URLLoaderWrapperImpl::GetStatusCode() const {
+  return url_loader_.GetResponseInfo().GetStatusCode();
+}
+
+bool URLLoaderWrapperImpl::IsMultipart() const {
+  return is_multipart_;
+}
+
+bool URLLoaderWrapperImpl::GetByteRange(int* start, int* end) const {
+  DCHECK(start);
+  DCHECK(end);
+  *start = byte_range_.start();
+  *end = byte_range_.end();
+  return byte_range_.IsValid();
+}
+
+bool URLLoaderWrapperImpl::GetDownloadProgress(
+    int64_t* bytes_received,
+    int64_t* total_bytes_to_be_received) const {
+  return url_loader_.GetDownloadProgress(bytes_received,
+                                         total_bytes_to_be_received);
+}
+
+void URLLoaderWrapperImpl::Close() {
+  url_loader_.Close();
+  read_starter_.reset();
+}
+
+void URLLoaderWrapperImpl::OpenRange(const std::string& url,
+                                     const std::string& referrer_url,
+                                     uint32_t position,
+                                     uint32_t size,
+                                     const pp::CompletionCallback& cc) {
+  did_open_callback_ = cc;
+  pp::CompletionCallback callback =
+      callback_factory_.NewCallback(&URLLoaderWrapperImpl::DidOpen);
+  int rv = url_loader_.Open(
+      MakeRangeRequest(plugin_instance_, url, referrer_url, position, size),
+      callback);
+  if (rv != PP_OK_COMPLETIONPENDING)
+    callback.Run(rv);
+}
+
+void URLLoaderWrapperImpl::ReadResponseBody(char* buffer,
+                                            int buffer_size,
+                                            const pp::CompletionCallback& cc) {
+  did_read_callback_ = cc;
+  buffer_ = buffer;
+  buffer_size_ = buffer_size;
+  read_starter_ = base::MakeUnique<ReadStarter>(this);
+}
+
+void URLLoaderWrapperImpl::ReadResponseBodyImpl() {
+  read_starter_.reset();
+  pp::CompletionCallback callback =
+      callback_factory_.NewCallback(&URLLoaderWrapperImpl::DidRead);
+  int rv = url_loader_.ReadResponseBody(buffer_, buffer_size_, callback);
+  if (rv != PP_OK_COMPLETIONPENDING) {
+    callback.Run(rv);
+  }
+}
+
+void URLLoaderWrapperImpl::SetResponseHeaders(
+    const std::string& response_headers) {
+  response_headers_ = response_headers;
+  ParseHeaders();
+}
+
+void URLLoaderWrapperImpl::ParseHeaders() {
+  content_length_ = -1;
+  accept_ranges_bytes_ = false;
+  content_encoded_ = false;
+  content_type_.clear();
+  content_disposition_.clear();
+  multipart_boundary_.clear();
+  byte_range_ = gfx::Range::InvalidRange();
+  is_multipart_ = false;
+
+  if (response_headers_.empty())
+    return;
+
+  net::HttpUtil::HeadersIterator it(response_headers_.begin(),
+                                    response_headers_.end(), "\n");
+  while (it.GetNext()) {
+    if (base::LowerCaseEqualsASCII(it.name(), "content-length")) {
+      content_length_ = atoi(it.values().c_str());
+    } else if (base::LowerCaseEqualsASCII(it.name(), "accept-ranges")) {
+      accept_ranges_bytes_ = base::LowerCaseEqualsASCII(it.values(), "bytes");
+    } else if (base::LowerCaseEqualsASCII(it.name(), "content-encoding")) {
+      content_encoded_ = true;
+    } else if (base::LowerCaseEqualsASCII(it.name(), "content-type")) {
+      content_type_ = it.values();
+      size_t semi_colon_pos = content_type_.find(';');
+      if (semi_colon_pos != std::string::npos) {
+        content_type_ = content_type_.substr(0, semi_colon_pos);
+      }
+      base::TrimWhitespaceASCII(content_type_, base::TRIM_ALL, &content_type_);
+      // multipart boundary.
+      std::string type = base::ToLowerASCII(it.values());
+      if (base::StartsWith(type, "multipart/", base::CompareCase::SENSITIVE)) {
+        const char* boundary = strstr(type.c_str(), "boundary=");
+        DCHECK(boundary);
+        if (boundary) {
+          multipart_boundary_ = std::string(boundary + 9);
+          is_multipart_ = !multipart_boundary_.empty();
+        }
+      }
+    } else if (base::LowerCaseEqualsASCII(it.name(), "content-disposition")) {
+      content_disposition_ = it.values();
+    } else if (base::LowerCaseEqualsASCII(it.name(), "content-range")) {
+      int start = 0;
+      int end = 0;
+      if (GetByteRangeFromStr(it.values().c_str(), &start, &end)) {
+        byte_range_ = gfx::Range(start, end);
+      }
+    }
+  }
+}
+
+void URLLoaderWrapperImpl::DidOpen(int32_t result) {
+  SetHeadersFromLoader();
+  did_open_callback_.RunAndClear(result);
+}
+
+void URLLoaderWrapperImpl::DidRead(int32_t result) {
+  if (multi_part_processed_) {
+    // Reset this flag so we look inside the buffer in calls of DidRead for this
+    // response only once.  Note that this code DOES NOT handle multi part
+    // responses with more than one part (we don't issue them at the moment, so
+    // they shouldn't arrive).
+    is_multipart_ = false;
+  }
+  if (result <= 0 || !is_multipart_) {
+    did_read_callback_.RunAndClear(result);
+    return;
+  }
+  if (result <= 2) {
+    // TODO(art-snake): Accumulate data for parse headers.
+    did_read_callback_.RunAndClear(result);
+    return;
+  }
+
+  char* start = buffer_;
+  size_t length = result;
+  multi_part_processed_ = true;
+  for (int i = 2; i < result; ++i) {
+    if (IsDoubleEndLineAtEnd(buffer_, i)) {
+      int start_pos = 0;
+      int end_pos = 0;
+      if (GetByteRangeFromHeaders(std::string(buffer_, i), &start_pos,
+                                  &end_pos)) {
+        byte_range_ = gfx::Range(start_pos, end_pos);
+        start += i;
+        length -= i;
+      }
+      break;
+    }
+  }
+  result = length;
+  if (result == 0) {
+    // Continue receiving.
+    return ReadResponseBodyImpl();
+  }
+  DCHECK(result > 0);
+  memmove(buffer_, start, result);
+
+  did_read_callback_.RunAndClear(result);
+}
+
+void URLLoaderWrapperImpl::SetHeadersFromLoader() {
+  pp::URLResponseInfo response = url_loader_.GetResponseInfo();
+  pp::Var headers_var = response.GetHeaders();
+
+  SetResponseHeaders(headers_var.is_string() ? headers_var.AsString() : "");
+}
+
+}  // namespace chrome_pdf
diff --git a/pdf/url_loader_wrapper_impl.h b/pdf/url_loader_wrapper_impl.h
new file mode 100644
index 0000000..b494818
--- /dev/null
+++ b/pdf/url_loader_wrapper_impl.h
@@ -0,0 +1,89 @@
+// Copyright 2016 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 PDF_URL_LOADER_WRAPPER_IMPL_H_
+#define PDF_URL_LOADER_WRAPPER_IMPL_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "pdf/url_loader_wrapper.h"
+#include "ppapi/cpp/url_loader.h"
+#include "ppapi/utility/completion_callback_factory.h"
+#include "ui/gfx/range/range.h"
+
+namespace pp {
+class Instance;
+};
+
+namespace chrome_pdf {
+
+class URLLoaderWrapperImpl : public URLLoaderWrapper {
+ public:
+  URLLoaderWrapperImpl(pp::Instance* plugin_instance,
+                       const pp::URLLoader& url_loader);
+  ~URLLoaderWrapperImpl() override;
+
+  // URLLoaderWrapper overrides:
+  int GetContentLength() const override;
+  bool IsAcceptRangesBytes() const override;
+  bool IsContentEncoded() const override;
+  std::string GetContentType() const override;
+  std::string GetContentDisposition() const override;
+  int GetStatusCode() const override;
+  bool IsMultipart() const override;
+  bool GetByteRange(int* start, int* end) const override;
+  bool GetDownloadProgress(int64_t* bytes_received,
+                           int64_t* total_bytes_to_be_received) const override;
+  void Close() override;
+  void OpenRange(const std::string& url,
+                 const std::string& referrer_url,
+                 uint32_t position,
+                 uint32_t size,
+                 const pp::CompletionCallback& cc) override;
+  void ReadResponseBody(char* buffer,
+                        int buffer_size,
+                        const pp::CompletionCallback& cc) override;
+
+  void SetResponseHeaders(const std::string& response_headers);
+
+ private:
+  class ReadStarter;
+
+  void SetHeadersFromLoader();
+  void ParseHeaders();
+  void DidOpen(int32_t result);
+  void DidRead(int32_t result);
+
+  void ReadResponseBodyImpl();
+
+  pp::Instance* const plugin_instance_;
+  pp::URLLoader url_loader_;
+  std::string response_headers_;
+
+  int content_length_ = -1;
+  bool accept_ranges_bytes_ = false;
+  bool content_encoded_ = false;
+  std::string content_type_;
+  std::string content_disposition_;
+  std::string multipart_boundary_;
+  gfx::Range byte_range_ = gfx::Range::InvalidRange();
+  bool is_multipart_ = false;
+  char* buffer_ = nullptr;
+  uint32_t buffer_size_ = 0;
+  bool multi_part_processed_ = false;
+
+  pp::CompletionCallback did_open_callback_;
+  pp::CompletionCallback did_read_callback_;
+  pp::CompletionCallbackFactory<URLLoaderWrapperImpl> callback_factory_;
+
+  std::unique_ptr<ReadStarter> read_starter_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLLoaderWrapperImpl);
+};
+
+}  // namespace chrome_pdf
+
+#endif  // PDF_URL_LOADER_WRAPPER_IMPL_H_