0

Revert of Improve linearized pdf load/show time. (patchset id:1 of https://codereview.chromium.org/2449973004/ )

Reason for revert:
Still breaks the tree. The initial revert failed due to funny merging by the CQ in reaction to r427683.

Original issue's description:
> Reland of Improve linearized pdf load/show time. (patchset  id:1 of https://codereview.chromium.org/2458493002/ )
>
> Reason for revert:
> As suggested FindIt, this reverting CL is also causing the tree closure.
>
> Original issue's description:
> > Revert of Improve linearized pdf load/show time. (patchset  id:340001 of https://codereview.chromium.org/2349753003/ )
> >
> > Reason for revert:
> > https://build.chromium.org/p/chromium/builders/Win%20x64/builds/5423/steps/compile/logs/stdio
> >
> > FAILED: obj/pdf/pdf_unittests/document_loader_unittest.obj
> > pdf\document_loader_unittest.cc(631): error C2131: expression did not evaluate to a constant
> > pdf\document_loader_unittest.cc(631): note: failure was caused by call of undefined function or one not declared 'constexpr'
> > pdf\document_loader_unittest.cc(631): note: see usage of 'chrome_pdf::DocumentLoader::default_request_size'
> >
> >
> > Original issue's 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
> > >
> > > Committed: https://crrev.com/7fd7423cdee0dba84faf480d10dd66dcb57110d9
> > > Cr-Commit-Position: refs/heads/master@{#427752}
> >
> > TBR=jochen@chromium.org,raymes@chromium.org,spelchat@chromium.org,rsesek@chromium.org,art-snake@yandex-team.ru
> > # Skipping CQ checks because original CL landed less than 1 days ago.
> > NOPRESUBMIT=true
> > NOTREECHECKS=true
> > NOTRY=true
>
> TBR=jochen@chromium.org,raymes@chromium.org,spelchat@chromium.org,rsesek@chromium.org,art-snake@yandex-team.ru,thestig@chromium.org
> # Skipping CQ checks because original CL landed less than 1 days ago.
> NOPRESUBMIT=true
> NOTREECHECKS=true
> NOTRY=true
>
> Committed: https://crrev.com/47b9a19cb7219538a26e0f1388fd01adac709b98
> Cr-Commit-Position: refs/heads/master@{#427782}

TBR=jochen@chromium.org,raymes@chromium.org,spelchat@chromium.org,rsesek@chromium.org,art-snake@yandex-team.ru,hongchan@chromium.org
# Skipping CQ checks because original CL landed less than 1 days ago.
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true

Review-Url: https://codereview.chromium.org/2456653002
Cr-Commit-Position: refs/heads/master@{#427784}
This commit is contained in:
thestig
2016-10-26 13:44:38 -07:00
committed by Commit bot
parent 5eedf6dbcf
commit ca52440d2d
24 changed files with 764 additions and 3007 deletions

@ -3,7 +3,6 @@
# found in the LICENSE file.
import("//build/config/features.gni")
import("//testing/test.gni")
import("//third_party/pdfium/pdfium.gni")
assert(enable_pdf)
@ -18,10 +17,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",
@ -38,13 +37,6 @@ 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.
@ -78,23 +70,3 @@ 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,6 +4,5 @@ include_rules = [
"+ppapi",
"+ui/base/window_open_disposition.h",
"+ui/events/keycodes/keyboard_codes.h",
"+ui/gfx/range/range.h",
"+v8/include/v8.h"
]

175
pdf/chunk_stream.cc Normal file

@ -0,0 +1,175 @@
// 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,103 +6,46 @@
#define PDF_CHUNK_STREAM_H_
#include <stddef.h>
#include <string.h>
#include <algorithm>
#include <array>
#include <memory>
#include <map>
#include <utility>
#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:
static constexpr uint32_t kChunkSize = N;
using ChunkData = typename std::array<unsigned char, N>;
ChunkStream();
~ChunkStream();
ChunkStream() {}
~ChunkStream() {}
void Clear();
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));
}
void Preallocate(size_t stream_size);
size_t GetSize() 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;
}
bool WriteData(size_t offset, void* buffer, size_t size);
bool ReadData(size_t offset, size_t size, void* buffer) const;
uint32_t GetChunkIndex(uint32_t offset) const { return offset / kChunkSize; }
// 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;
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);
}
// 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;
private:
std::vector<std::unique_ptr<ChunkData>> data_;
uint32_t eof_pos_ = 0;
RangeSet filled_chunks_;
uint32_t filled_chunks_count_ = 0;
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_;
};
}; // namespace chrome_pdf

@ -1,99 +0,0 @@
// 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,25 +7,66 @@
#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 "pdf/url_loader_wrapper.h"
#include "net/http/http_util.h"
#include "ppapi/c/pp_errors.h"
#include "ui/gfx/range/range.h"
#include "ppapi/cpp/url_loader.h"
#include "ppapi/cpp/url_request_info.h"
#include "ppapi/cpp/url_response_info.h"
namespace chrome_pdf {
namespace {
// 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;
// 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();
}
bool IsValidContentType(const std::string& type) {
return (base::EndsWith(type, "/pdf", base::CompareCase::INSENSITIVE_ASCII) ||
@ -44,31 +85,43 @@ bool IsValidContentType(const std::string& type) {
DocumentLoader::Client::~Client() {
}
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) {}
: 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::~DocumentLoader() {
}
bool DocumentLoader::Init(std::unique_ptr<URLLoaderWrapper> loader,
const std::string& url) {
bool DocumentLoader::Init(const pp::URLLoader& loader,
const std::string& url,
const std::string& headers) {
DCHECK(url_.empty());
DCHECK(!loader_);
url_ = url;
loader_ = loader;
std::string type = loader->GetContentType();
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;
// This happens for PDFs not loaded from http(s) sources.
if (type == "text/plain") {
if (response_headers == "Content-Type: text/plain") {
if (!base::StartsWith(url, "http://",
base::CompareCase::INSENSITIVE_ASCII) &&
!base::StartsWith(url, "https://",
@ -76,85 +129,109 @@ bool DocumentLoader::Init(std::unique_ptr<URLLoaderWrapper> 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(loader->GetContentDisposition(), "attachment",
if (base::StartsWith(disposition, "attachment",
base::CompareCase::INSENSITIVE_ASCII))
return false;
url_ = url;
loader_ = std::move(loader);
if (content_length > 0)
chunk_stream_.Preallocate(content_length);
if (!loader_->IsContentEncoded()) {
chunk_stream_.set_eof_pos(std::max(0, loader_->GetContentLength()));
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();
}
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;
}
bool DocumentLoader::IsDocumentComplete() const {
return chunk_stream_.IsComplete();
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();
}
uint32_t DocumentLoader::GetDocumentSize() const {
return chunk_stream_.eof_pos();
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_);
}
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;
}
void DocumentLoader::ClearPendingRequests() {
pending_requests_.Clear();
pending_requests_.erase(pending_requests_.begin(),
pending_requests_.end());
}
bool DocumentLoader::GetBlock(uint32_t position,
uint32_t size,
void* buf) const {
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);
return chunk_stream_.ReadData(position, size, buf);
}
bool DocumentLoader::IsDataAvailable(uint32_t position, uint32_t size) const {
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()));
return chunk_stream_.IsRangeAvailable(position, size);
}
void DocumentLoader::RequestData(uint32_t position, uint32_t size) {
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;
}
DCHECK(partial_document_);
// We have some artefact request from
// PDFiumEngine::OnDocumentComplete() -> FPDFAvail_IsPageAvail after
@ -163,230 +240,307 @@ 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 (!loader_)
if (IsDocumentComplete())
return;
RangeSet requested_chunks(chunk_stream_.GetChunksRange(position, size));
requested_chunks.Subtract(chunk_stream_.filled_chunks());
if (requested_chunks.IsEmpty()) {
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) {
NOTREACHED();
return;
}
pending_requests_.Union(requested_chunks);
}
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();
int32_t http_code = loader_.GetResponseInfo().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.
return ReadComplete();
// https://code.google.com/p/chromium/issues/detail?id=414827
return;
}
// Leave position untouched for multiparted responce for now, when we read the
// data we'll get it.
if (!loader_->IsMultipart()) {
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 {
// 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
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);
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;
} else {
SetPartialLoadingEnabled(false);
partial_document_ = false;
}
return ContinueDownload();
}
// Needs more data to calc chunk index.
return ReadMore();
ReadMore();
}
void DocumentLoader::ReadMore() {
loader_->ReadResponseBody(
buffer_, sizeof(buffer_),
loader_factory_.NewCallback(&DocumentLoader::DidRead));
pp::CompletionCallback callback =
loader_factory_.NewCallback(&DocumentLoader::DidRead);
int rv = loader_.ReadResponseBody(buffer_, sizeof(buffer_), callback);
if (rv != PP_OK_COMPLETIONPENDING)
callback.Run(rv);
}
void DocumentLoader::DidRead(int32_t result) {
if (result < 0) {
// An error occurred.
// The renderer will detect that we're missing data and will display a
// message.
return ReadComplete();
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) {
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();
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;
}
}
DCHECK(!chunk_.chunk_data);
chunk_.chunk_index = chunk_stream_.GetChunkIndex(start_pos);
// 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;
}
if (!SaveChunkData(buffer_, result)) {
return ReadMore();
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 (IsDocumentComplete()) {
return ReadComplete();
// Only call the renderer if we allow partial loading.
if (!partial_document_) {
ReadMore();
return;
}
return ContinueDownload();
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();
}
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;
bool DocumentLoader::SatisfyingRequest(size_t offset, size_t size) const {
return offset <= current_pos_ + kDefaultRequestSize &&
current_pos_ < offset + size;
}
void DocumentLoader::ReadComplete() {
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));
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();
}
document_size_ = current_pos_;
client_->OnDocumentComplete();
return;
}
loader_.reset();
request_pending_ = false;
if (IsDocumentComplete()) {
client_->OnDocumentComplete();
} else {
client_->OnDocumentCanceled();
return;
}
UpdateRendering();
DownloadPendingRequests();
}
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();
void DocumentLoader::UpdateRendering() {
if (header_request_)
client_->OnPartialDocumentLoaded();
else
client_->OnPendingRequestComplete();
header_request_ = false;
}
} // namespace chrome_pdf

@ -9,18 +9,16 @@
#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 {
@ -30,23 +28,25 @@ class DocumentLoader {
// Gets the pp::Instance object.
virtual pp::Instance* GetPluginInstance() = 0;
// Creates new URLLoader based on client settings.
virtual std::unique_ptr<URLLoaderWrapper> CreateURLLoader() = 0;
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;
// 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(std::unique_ptr<URLLoaderWrapper> loader, const std::string& url);
bool Init(const pp::URLLoader& loader,
const std::string& url,
const std::string& headers);
// Data access interface. Return true is successful.
bool GetBlock(uint32_t position, uint32_t size, void* buf) const;
@ -58,63 +58,77 @@ class DocumentLoader {
void RequestData(uint32_t position, uint32_t size);
bool IsDocumentComplete() const;
uint32_t GetDocumentSize() const;
uint32_t count_of_bytes_received() const { return count_of_bytes_received_; }
float GetProgress() const;
uint32_t document_size() const { return document_size_; }
// Return number of bytes available.
uint32_t GetAvailableData() const;
// Clear pending requests from the queue.
void ClearPendingRequests();
void SetPartialLoadingEnabled(bool enabled);
bool is_partial_loader_active() const { return is_partial_loader_active_; }
static uint32_t default_request_size() { return kDefaultRequestSize; }
bool is_partial_document() const { return partial_document_; }
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 DidOpenPartial(int32_t result);
void DidOpen(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);
bool ShouldCancelLoading() const;
void ContinueDownload();
// Called when we complete server request.
// 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.
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();
bool SaveChunkData(char* input, uint32_t input_size);
// 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 };
Client* const client_;
std::string url_;
std::unique_ptr<URLLoaderWrapper> loader_;
pp::URLLoader loader_;
pp::CompletionCallbackFactory<DocumentLoader> loader_factory_;
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;
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_;
};
} // namespace chrome_pdf

File diff suppressed because it is too large Load Diff

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

@ -17,7 +17,6 @@
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/numerics/safe_conversions.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
@ -31,7 +30,6 @@
#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"
@ -539,6 +537,7 @@ 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 +564,15 @@ PDFiumEngine::PDFiumEngine(PDFEngine::Client* client)
file_access_.m_FileLen = 0;
file_access_.m_GetBlock = &GetBlock;
file_access_.m_Param = this;
file_access_.m_Param = &doc_loader_;
file_availability_.version = 1;
file_availability_.IsDataAvail = &IsDataAvail;
file_availability_.engine = this;
file_availability_.loader = &doc_loader_;
download_hints_.version = 1;
download_hints_.AddSegment = &AddSegment;
download_hints_.engine = this;
download_hints_.loader = &doc_loader_;
// Initialize FPDF_FORMFILLINFO member variables. Deriving from this struct
// allows the static callbacks to be able to cast the FPDF_FORMFILLINFO in
@ -843,22 +842,22 @@ int PDFiumEngine::Form_GetLanguage(FPDF_FORMFILLINFO* param,
int PDFiumEngine::GetBlock(void* param, unsigned long position,
unsigned char* buffer, unsigned long size) {
PDFiumEngine* engine = static_cast<PDFiumEngine*>(param);
return engine->doc_loader_->GetBlock(position, size, buffer);
DocumentLoader* loader = static_cast<DocumentLoader*>(param);
return 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->engine->doc_loader_->IsDataAvailable(offset, size);
return file_avail->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->engine->doc_loader_->RequestData(offset, size);
return download_hints->loader->RequestData(offset, size);
}
bool PDFiumEngine::New(const char* url,
@ -993,27 +992,15 @@ void PDFiumEngine::PostPaint() {
bool PDFiumEngine::HandleDocumentLoad(const pp::URLLoader& loader) {
password_tries_remaining_ = kMaxPasswordTries;
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;
return doc_loader_.Init(loader, url_, headers_);
}
pp::Instance* PDFiumEngine::GetPluginInstance() {
return client_->GetPluginInstance();
}
std::unique_ptr<URLLoaderWrapper> PDFiumEngine::CreateURLLoader() {
return base::MakeUnique<URLLoaderWrapperImpl>(GetPluginInstance(),
client_->CreateURLLoader());
pp::URLLoader PDFiumEngine::CreateURLLoader() {
return client_->CreateURLLoader();
}
void PDFiumEngine::AppendPage(PDFEngine* engine, int index) {
@ -1038,32 +1025,35 @@ 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::OnPendingRequestComplete() {
if (!process_when_pending_request_complete_)
return;
void PDFiumEngine::OnPartialDocumentLoaded() {
file_access_.m_FileLen = doc_loader_.document_size();
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;
}
}
if (!doc_) {
// 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_);
LoadDocument();
return;
}
@ -1085,51 +1075,26 @@ void PDFiumEngine::OnPendingRequestComplete() {
}
void PDFiumEngine::OnNewDataAvailable() {
const float progress = doc_loader_->GetProgress();
if (progress < 0.001) {
client_->DocumentLoadProgress(0, 0);
} else {
client_->DocumentLoadProgress(progress * 10000, 10000);
}
client_->DocumentLoadProgress(doc_loader_.GetAvailableData(),
doc_loader_.document_size());
}
void PDFiumEngine::OnDocumentComplete() {
if (doc_) {
return FinishLoadingDocument();
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;
}
file_access_.m_FileLen = doc_loader_->GetDocumentSize();
if (!fpdf_availability_) {
fpdf_availability_ = FPDFAvail_Create(&file_availability_, &file_access_);
DCHECK(fpdf_availability_);
}
LoadDocument();
}
void PDFiumEngine::OnDocumentCanceled() {
OnDocumentComplete();
}
void PDFiumEngine::CancelBrowserDownload() {
client_->CancelBrowserDownload();
FinishLoadingDocument();
}
void PDFiumEngine::FinishLoadingDocument() {
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);
}
}
DCHECK(doc_loader_.IsDocumentComplete() && doc_);
bool need_update = false;
for (size_t i = 0; i < pages_.size(); ++i) {
@ -1341,7 +1306,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();
@ -2485,7 +2450,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;
}
@ -2524,7 +2489,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 {
@ -2582,7 +2547,26 @@ void PDFiumEngine::ContinueLoadingDocument(const std::string& password) {
permissions_ = FPDF_GetDocPermissions(doc_);
permissions_handler_revision_ = FPDF_GetSecurityHandlerRevision(doc_);
if (!doc_loader_->IsDocumentComplete()) {
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()) {
// 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.
@ -2591,7 +2575,7 @@ void PDFiumEngine::ContinueLoadingDocument(const std::string& password) {
LoadPageInfo(false);
if (doc_loader_->IsDocumentComplete())
if (doc_loader_.IsDocumentComplete())
FinishLoadingDocument();
}
@ -2601,7 +2585,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) {
@ -2658,12 +2642,10 @@ 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_);
@ -2724,7 +2706,7 @@ void PDFiumEngine::ScrollToPage(int page) {
}
bool PDFiumEngine::CheckPageAvailable(int index, std::vector<int>* pending) {
if (!doc_)
if (!doc_ || !form_)
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;
std::unique_ptr<URLLoaderWrapper> CreateURLLoader() override;
pp::URLLoader CreateURLLoader() override;
void OnPartialDocumentLoaded() 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 {
PDFiumEngine* engine;
DocumentLoader* loader;
};
struct DownloadHints : public FX_DOWNLOADHINTS {
PDFiumEngine* engine;
DocumentLoader* loader;
};
// PDFium interface to get block of data.
@ -602,7 +602,7 @@ class PDFiumEngine : public PDFEngine,
double current_zoom_;
unsigned int current_rotation_;
std::unique_ptr<DocumentLoader> doc_loader_; // Main document's loader.
DocumentLoader doc_loader_; // Main document's loader.
std::string url_;
std::string headers_;
pp::CompletionCallbackFactory<PDFiumEngine> find_factory_;
@ -731,11 +731,6 @@ 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,8 +162,6 @@ bool PreviewModeClient::IsPrintPreview() {
return false;
}
void PreviewModeClient::CancelBrowserDownload() {}
uint32_t PreviewModeClient::GetBackgroundColor() {
NOTREACHED();
return 0;

@ -71,7 +71,6 @@ 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:

@ -1,253 +0,0 @@
// 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());
}

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

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

@ -1,24 +0,0 @@
// 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)));
}

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

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

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

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

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