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_