Revert of Improve linearized pdf load/show time. (patchset #1 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 #1 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 #18 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:
pdf
BUILD.gnDEPSchunk_stream.ccchunk_stream.hchunk_stream_unittest.ccdocument_loader.ccdocument_loader.hdocument_loader_unittest.ccout_of_process_instance.ccout_of_process_instance.hpdf_engine.h
pdfium
preview_mode_client.ccpreview_mode_client.hrange_set.ccrange_set.hrange_set_unittest.ccrun_all_unittests.cctimer.cctimer.hurl_loader_wrapper.hurl_loader_wrapper_impl.ccurl_loader_wrapper_impl.h
30
pdf/BUILD.gn
30
pdf/BUILD.gn
@ -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",
|
||||
]
|
||||
}
|
||||
|
1
pdf/DEPS
1
pdf/DEPS
@ -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
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:
|
||||
|
253
pdf/range_set.cc
253
pdf/range_set.cc
@ -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)));
|
||||
}
|
31
pdf/timer.cc
31
pdf/timer.cc
@ -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
|
35
pdf/timer.h
35
pdf/timer.h
@ -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_
|
Reference in New Issue
Block a user