0

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

Review-Url: https://codereview.chromium.org/2349753003
Cr-Commit-Position: refs/heads/master@{#427752}
This commit is contained in:
art-snake
2016-10-26 11:25:39 -07:00
committed by Commit bot
parent 46a480d09e
commit 7fd7423cde
24 changed files with 3005 additions and 763 deletions

@ -3,6 +3,7 @@
# found in the LICENSE file.
import("//build/config/features.gni")
import("//testing/test.gni")
import("//third_party/pdfium/pdfium.gni")
assert(enable_pdf)
@ -17,10 +18,10 @@ static_library("pdf") {
"//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",
@ -37,6 +38,13 @@ static_library("pdf") {
"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",
]
# TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
@ -70,3 +78,23 @@ static_library("pdf") {
defines += [ "PDF_ENABLE_XFA" ]
}
}
test("pdf_unittests") {
sources = [
"chunk_stream_unittest.cc",
"document_loader_unittest.cc",
"range_set_unittest.cc",
"run_all_unittests.cc",
]
deps = [
":pdf",
"//base",
"//base/test:test_support",
"//ppapi/c",
"//ppapi/cpp",
"//testing/gmock",
"//testing/gtest",
"//ui/gfx/range",
]
}

@ -4,5 +4,6 @@ include_rules = [
"+ppapi",
"+ui/base/window_open_disposition.h",
"+ui/events/keycodes/keyboard_codes.h",
"+ui/gfx/range/range.h",
"+v8/include/v8.h"
]

@ -1,175 +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>
#define __STDC_LIMIT_MACROS
#ifdef _WIN32
#include <limits.h>
#else
#include <stdint.h>
#endif
#include <algorithm>
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 (SIZE_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;
}
std::map<size_t, size_t>::iterator 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.
std::map<size_t, size_t>::iterator end = chunks_.upper_bound(offset + size);
if (start == end) { // No chunks to merge.
chunks_[offset] = size;
return true;
}
--end;
size_t new_offset = std::min<size_t>(start->first, offset);
size_t new_size =
std::max<size_t>(end->first + end->second, 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;
}
std::map<size_t, size_t>::const_iterator 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.
std::map<size_t, size_t>::const_iterator 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;
std::map<size_t, size_t>::const_iterator it;
for (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 (SIZE_MAX - size < offset)
return false;
std::map<size_t, size_t>::const_iterator 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;
std::map<size_t, size_t>::const_iterator begin = chunks_.begin();
return begin->first > 0 ? 0 : begin->second;
}
size_t ChunkStream::GetFirstMissingByteInInterval(size_t offset) const {
if (chunks_.empty())
return 0;
std::map<size_t, size_t>::const_iterator 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;
std::map<size_t, size_t>::const_iterator it = chunks_.upper_bound(offset);
if (it == chunks_.end())
return stream_size_ - 1;
return it->first - 1;
}
} // namespace chrome_pdf

@ -6,46 +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::vector<unsigned char> data_;
// Pair, first - begining of the chunk, second - 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

@ -0,0 +1,99 @@
// 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/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;
std::unique_ptr<TestChunkStream::ChunkData> CreateChunkData() {
return base::MakeUnique<TestChunkStream::ChunkData>();
}
}
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

@ -7,66 +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")) {
std::string type = base::ToLowerASCII(it.values());
if (base::StartsWith(type, "multipart/", base::CompareCase::SENSITIVE)) {
const char* boundary = strstr(type.c_str(), "boundary=");
if (!boundary) {
NOTREACHED();
break;
}
return std::string(boundary + 9);
}
}
}
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;
bool IsValidContentType(const std::string& type) {
return (base::EndsWith(type, "/pdf", base::CompareCase::INSENSITIVE_ASCII) ||
@ -85,43 +44,31 @@ bool IsValidContentType(const std::string& type) {
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());
url_ = url;
loader_ = loader;
DCHECK(!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://",
@ -129,109 +76,85 @@ bool DocumentLoader::Init(const pp::URLLoader& loader,
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) { // If document size is 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_.erase(pending_requests_.begin(),
pending_requests_.end());
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
@ -240,307 +163,230 @@ void DocumentLoader::RequestData(uint32_t position, uint32_t size) {
// 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();
}
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::DownloadPendingRequests() {
if (request_pending_)
return;
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();
pos = pending_requests_.front().first;
size = pending_requests_.front().second;
if (IsDataAvailable(pos, size)) {
ReadComplete();
return;
}
}
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;
}
current_request_offset_ = pos;
current_request_size_ = size;
// 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);
}
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) {
if (result != PP_OK) {
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);
}
int32_t http_code = loader_.GetResponseInfo().GetStatusCode();
void DocumentLoader::SetPartialLoadingEnabled(bool enabled) {
partial_loading_enabled_ = enabled;
if (!enabled) {
is_partial_loader_active_ = false;
}
}
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);
}
void DocumentLoader::ContinueDownload() {
if (!ShouldCancelLoading())
return ReadMore();
DCHECK(partial_loading_enabled_);
DCHECK(!IsDocumentComplete());
DCHECK(GetDocumentSize());
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();
}
loader_.reset();
chunk_.Clear();
if (!is_partial_loader_active_) {
client_->CancelBrowserDownload();
is_partial_loader_active_ = true;
}
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);
loader_ = client_->CreateURLLoader();
loader_->OpenRange(
url_, url_, start, length,
loader_factory_.NewCallback(&DocumentLoader::DidOpenPartial));
}
void DocumentLoader::DidOpenPartial(int32_t result) {
if (result != PP_OK) {
return ReadComplete();
}
int32_t http_code = loader_->GetStatusCode();
if (http_code >= 400 && http_code < 500) {
// Error accessing resource. 4xx error indicate subsequent requests
// will fail too.
// E.g. resource has been removed from the server while loading it.
// https://code.google.com/p/chromium/issues/detail?id=414827
return;
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, 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 ((buffer_[i - 1] == '\n' && buffer_[i - 2] == '\n') ||
(i >= 4 && buffer_[i - 1] == '\n' && buffer_[i - 2] == '\r' &&
buffer_[i - 3] == '\n' && buffer_[i - 4] == '\r')) {
uint32_t start_pos, 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;
}
if (IsDocumentComplete())
return true;
if (!chunk_saved)
return false;
if (loading_pending_request &&
!pending_requests_.Contains(chunk_.chunk_index)) {
client_->OnPendingRequestComplete();
}
client_->OnNewDataAvailable();
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);
}
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));
}
document_size_ = current_pos_;
client_->OnDocumentComplete();
return;
}
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;
float DocumentLoader::GetProgress() const {
if (!GetDocumentSize())
return -1;
if (IsDocumentComplete())
return 1;
return static_cast<float>(chunk_stream_.filled_chunks_count()) /
chunk_stream_.total_chunks_count();
}
} // namespace chrome_pdf

@ -9,16 +9,18 @@
#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:
class Client {
@ -28,25 +30,23 @@ class DocumentLoader {
// 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 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;
@ -58,77 +58,63 @@ class DocumentLoader {
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_; }
float GetProgress() const;
// 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_; }
static uint32_t default_request_size() { return kDefaultRequestSize; }
private:
// Number was chosen in crbug.com/78264#c8
static constexpr uint32_t kDefaultRequestSize = 65536;
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_;
typedef std::list<std::pair<size_t, size_t> > PendingRequests;
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;
char buffer_[DataStream::kChunkSize];
Chunk chunk_;
RangeSet pending_requests_;
uint32_t count_of_bytes_received_ = 0;
};
} // namespace chrome_pdf

File diff suppressed because it is too large Load Diff

@ -855,15 +855,6 @@ void OutOfProcessInstance::DidOpen(int32_t result) {
NOTREACHED();
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) {
@ -1501,6 +1492,12 @@ uint32_t OutOfProcessInstance::GetBackgroundColor() {
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);

@ -135,6 +135,7 @@ class OutOfProcessInstance : public pp::Instance,
void FormTextFieldFocusChange(bool in_focus) override;
bool IsPrintPreview() override;
uint32_t GetBackgroundColor() override;
void CancelBrowserDownload() override;
void IsSelectingChanged(bool is_selecting) override;
// PreviewModeClient::Client implementation.

@ -186,6 +186,9 @@ class PDFEngine {
// 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) {}
};
@ -298,8 +301,6 @@ class PDFEngine {
virtual void SetScrollPosition(const pp::Point& position) = 0;
#endif
virtual bool IsProgressiveLoad() = 0;
virtual std::string GetMetadata(const std::string& key) = 0;
};

@ -31,6 +31,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"
@ -538,7 +539,6 @@ PDFiumEngine::PDFiumEngine(PDFEngine::Client* client)
: client_(client),
current_zoom_(1.0),
current_rotation_(0),
doc_loader_(this),
password_tries_remaining_(0),
doc_(nullptr),
form_(nullptr),
@ -565,15 +565,15 @@ PDFiumEngine::PDFiumEngine(PDFEngine::Client* client)
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
@ -843,22 +843,22 @@ int PDFiumEngine::Form_GetLanguage(FPDF_FORMFILLINFO* param,
int PDFiumEngine::GetBlock(void* param, 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,
size_t offset, 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,
size_t offset, 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,
@ -993,15 +993,27 @@ void PDFiumEngine::PostPaint() {
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) {
@ -1026,35 +1038,32 @@ void PDFiumEngine::SetScrollPosition(const pp::Point& position) {
}
#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());
return;
}
LoadDocument();
}
void PDFiumEngine::OnPendingRequestComplete() {
if (!doc_ || !form_) {
DCHECK(fpdf_availability_);
if (!doc_) {
LoadDocument();
return;
}
@ -1076,26 +1085,51 @@ void PDFiumEngine::OnPendingRequestComplete() {
}
void PDFiumEngine::OnNewDataAvailable() {
client_->DocumentLoadProgress(doc_loader_.GetAvailableData(),
doc_loader_.document_size());
const float progress = doc_loader_->GetProgress();
if (progress < 0.001) {
client_->DocumentLoadProgress(0, 0);
} else {
client_->DocumentLoadProgress(progress * 10000, 10000);
}
}
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() {
OnDocumentComplete();
}
void PDFiumEngine::CancelBrowserDownload() {
client_->CancelBrowserDownload();
}
void PDFiumEngine::FinishLoadingDocument() {
DCHECK(doc_loader_.IsDocumentComplete() && doc_);
DCHECK(doc_loader_->IsDocumentComplete() && doc_);
if (!form_) {
int form_status =
FPDFAvail_IsFormAvail(fpdf_availability_, &download_hints_);
if (form_status != PDF_FORM_NOTAVAIL) {
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);
}
}
bool need_update = false;
for (size_t i = 0; i < pages_.size(); ++i) {
@ -1307,7 +1341,7 @@ pp::Buffer_Dev PDFiumEngine::PrintPagesAsRasterPDF(
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();
@ -2451,7 +2485,7 @@ void PDFiumEngine::AppendBlankPages(int num_pages) {
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;
}
@ -2490,7 +2524,7 @@ bool PDFiumEngine::TryLoadingDoc(const std::string& password,
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 {
@ -2548,26 +2582,7 @@ void PDFiumEngine::ContinueLoadingDocument(const std::string& password) {
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;
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()) {
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.
@ -2576,7 +2591,7 @@ void PDFiumEngine::ContinueLoadingDocument(const std::string& password) {
LoadPageInfo(false);
if (doc_loader_.IsDocumentComplete())
if (doc_loader_->IsDocumentComplete())
FinishLoadingDocument();
}
@ -2586,7 +2601,7 @@ void PDFiumEngine::LoadPageInfo(bool reload) {
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) {
@ -2643,10 +2658,12 @@ void PDFiumEngine::LoadPageInfo(bool reload) {
}
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_);
@ -2707,7 +2724,7 @@ void PDFiumEngine::ScrollToPage(int page) {
}
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());

@ -108,16 +108,16 @@ class PDFiumEngine : public PDFEngine,
#if defined(PDF_ENABLE_XFA)
void SetScrollPosition(const pp::Point& position) override;
#endif
bool IsProgressiveLoad() override;
std::string GetMetadata(const std::string& key) override;
// 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 OnDocumentComplete() override;
void OnDocumentCanceled() override;
void CancelBrowserDownload() override;
void UnsupportedFeature(int type);
void FontSubstituted();
@ -191,11 +191,11 @@ class PDFiumEngine : public PDFEngine,
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.
@ -602,7 +602,7 @@ class PDFiumEngine : public PDFEngine,
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_;
@ -731,6 +731,11 @@ class PDFiumEngine : public PDFEngine,
// 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;
DISALLOW_COPY_AND_ASSIGN(PDFiumEngine);
};

@ -162,6 +162,8 @@ bool PreviewModeClient::IsPrintPreview() {
return false;
}
void PreviewModeClient::CancelBrowserDownload() {}
uint32_t PreviewModeClient::GetBackgroundColor() {
NOTREACHED();
return 0;

@ -71,6 +71,7 @@ class PreviewModeClient : public PDFEngine::Client {
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:

253
pdf/range_set.cc Normal file

@ -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());
}

77
pdf/range_set.h Normal file

@ -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_

303
pdf/range_set_unittest.cc Normal file

@ -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

24
pdf/run_all_unittests.cc Normal file

@ -0,0 +1,24 @@
// 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 "base/test/launcher/unit_test_launcher.h"
#include "base/test/test_suite.h"
// ppapi_cpp won't link w/o this.
namespace pp {
class Module;
Module* CreateModule() {
return nullptr;
}
} // namespace pp
int main(int argc, char** argv) {
base::TestSuite test_suite(argc, argv);
return base::LaunchUnitTests(
argc, argv,
base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
}

31
pdf/timer.cc Normal file

@ -0,0 +1,31 @@
// 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

35
pdf/timer.h Normal file

@ -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_

62
pdf/url_loader_wrapper.h Normal file

@ -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_

@ -0,0 +1,318 @@
// 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();
}
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_.Run(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_.Run(result);
return;
}
if (result <= 2) {
// TODO(art-snake): Accumulate data for parse headers.
did_read_callback_.Run(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_.Run(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

@ -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_