blob: 8565b760d2d5b27704da0c0a8d8e298f0f041c03 [file] [log] [blame]
[email protected]1b1e9eff2014-05-20 01:56:401// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Lei Zhange98901a2018-04-25 23:23:595#include "pdf/document_loader_impl.h"
[email protected]1b1e9eff2014-05-20 01:56:406
avia7c09d52015-12-21 19:49:437#include <stddef.h>
8#include <stdint.h>
9
Artem Stryginaf1b43422017-09-12 18:48:2410#include <algorithm>
Lei Zhang533165f2018-02-27 19:09:1011#include <utility>
Artem Stryginaf1b43422017-09-12 18:48:2412
[email protected]1b1e9eff2014-05-20 01:56:4013#include "base/logging.h"
Artem Stryginaf1b43422017-09-12 18:48:2414#include "base/numerics/safe_math.h"
[email protected]1b1e9eff2014-05-20 01:56:4015#include "base/strings/string_util.h"
Artem Stryginaf1b43422017-09-12 18:48:2416#include "pdf/url_loader_wrapper.h"
[email protected]1b1e9eff2014-05-20 01:56:4017#include "ppapi/c/pp_errors.h"
Artem Stryginaf1b43422017-09-12 18:48:2418#include "ui/gfx/range/range.h"
[email protected]1b1e9eff2014-05-20 01:56:4019
20namespace chrome_pdf {
21
thestig945cd0cb2015-05-28 01:58:0522namespace {
23
Artem Stryginaf1b43422017-09-12 18:48:2424// The distance from last received chunk, when we wait requesting data, using
25// current connection (like playing a cassette tape) and do not send new range
26// request (like rewind a cassette tape, and continue playing after).
27// Experimentally chosen value.
Henrique Nakashima1c5da8e2018-10-05 21:56:4328constexpr int kChunkCloseDistance = 10;
thestig945cd0cb2015-05-28 01:58:0529
raymesa8563ff2016-11-15 23:50:0130// Return true if the HTTP response of |loader| is a successful one and loading
31// should continue. 4xx error indicate subsequent requests will fail too.
32// e.g. resource has been removed from the server while loading it. 301
33// indicates a redirect was returned which won't be successful because we
34// disable following redirects for PDF loading (we assume they are already
35// resolved by the browser.
Artem Stryginaf1b43422017-09-12 18:48:2436bool ResponseStatusSuccess(const URLLoaderWrapper* loader) {
37 int32_t http_code = loader->GetStatusCode();
raymesa8563ff2016-11-15 23:50:0138 return (http_code < 400 && http_code != 301) || http_code >= 500;
39}
40
thestig488102f2015-05-29 03:25:2641bool IsValidContentType(const std::string& type) {
Artem Stryginaf1b43422017-09-12 18:48:2442 return (
43 base::EndsWith(type, "/pdf", base::CompareCase::INSENSITIVE_ASCII) ||
44 base::EndsWith(type, ".pdf", base::CompareCase::INSENSITIVE_ASCII) ||
45 base::EndsWith(type, "/x-pdf", base::CompareCase::INSENSITIVE_ASCII) ||
46 base::EndsWith(type, "/*", base::CompareCase::INSENSITIVE_ASCII) ||
47 base::EndsWith(type, "/octet-stream",
48 base::CompareCase::INSENSITIVE_ASCII) ||
49 base::EndsWith(type, "/acrobat", base::CompareCase::INSENSITIVE_ASCII) ||
50 base::EndsWith(type, "/unknown", base::CompareCase::INSENSITIVE_ASCII));
thestig1d7b16f92017-05-30 20:25:3451}
52
thestig945cd0cb2015-05-28 01:58:0553} // namespace
54
Lei Zhange98901a2018-04-25 23:23:5955DocumentLoaderImpl::Chunk::Chunk() = default;
Artem Stryginaf1b43422017-09-12 18:48:2456
Lei Zhange98901a2018-04-25 23:23:5957DocumentLoaderImpl::Chunk::~Chunk() = default;
Artem Stryginaf1b43422017-09-12 18:48:2458
Lei Zhange98901a2018-04-25 23:23:5959void DocumentLoaderImpl::Chunk::Clear() {
Artem Stryginaf1b43422017-09-12 18:48:2460 chunk_index = 0;
61 data_size = 0;
62 chunk_data.reset();
dsinclair07a72bd2017-08-14 23:27:0463}
Artem Strygin9a6d1482017-07-26 21:08:5664
Lei Zhange98901a2018-04-25 23:23:5965DocumentLoaderImpl::DocumentLoaderImpl(Client* client)
Artem Stryginaf1b43422017-09-12 18:48:2466 : client_(client), loader_factory_(this) {}
67
Lei Zhange98901a2018-04-25 23:23:5968DocumentLoaderImpl::~DocumentLoaderImpl() = default;
[email protected]1b1e9eff2014-05-20 01:56:4069
Lei Zhange98901a2018-04-25 23:23:5970bool DocumentLoaderImpl::Init(std::unique_ptr<URLLoaderWrapper> loader,
71 const std::string& url) {
[email protected]1b1e9eff2014-05-20 01:56:4072 DCHECK(url_.empty());
Artem Stryginaf1b43422017-09-12 18:48:2473 DCHECK(!loader_);
[email protected]1b1e9eff2014-05-20 01:56:4074
raymesa8563ff2016-11-15 23:50:0175 // Check that the initial response status is a valid one.
Artem Stryginaf1b43422017-09-12 18:48:2476 if (!ResponseStatusSuccess(loader.get()))
raymesa8563ff2016-11-15 23:50:0177 return false;
78
Artem Stryginaf1b43422017-09-12 18:48:2479 std::string type = loader->GetContentType();
thestig488102f2015-05-29 03:25:2680
81 // This happens for PDFs not loaded from http(s) sources.
Artem Stryginaf1b43422017-09-12 18:48:2482 if (type == "text/plain") {
brettw95509312015-07-16 23:57:3383 if (!base::StartsWith(url, "http://",
84 base::CompareCase::INSENSITIVE_ASCII) &&
85 !base::StartsWith(url, "https://",
86 base::CompareCase::INSENSITIVE_ASCII)) {
thestig488102f2015-05-29 03:25:2687 type = "application/pdf";
88 }
89 }
thestig488102f2015-05-29 03:25:2690 if (!type.empty() && !IsValidContentType(type))
[email protected]1b1e9eff2014-05-20 01:56:4091 return false;
Artem Stryginaf1b43422017-09-12 18:48:2492
93 if (base::StartsWith(loader->GetContentDisposition(), "attachment",
brettw95509312015-07-16 23:57:3394 base::CompareCase::INSENSITIVE_ASCII))
[email protected]1b1e9eff2014-05-20 01:56:4095 return false;
[email protected]1b1e9eff2014-05-20 01:56:4096
Artem Stryginaf1b43422017-09-12 18:48:2497 url_ = url;
98 loader_ = std::move(loader);
[email protected]1b1e9eff2014-05-20 01:56:4099
Lei Zhang6fbfb092018-03-28 16:58:56100 if (!loader_->IsContentEncoded())
K. Moon6f6c6e42020-03-19 04:21:53101 chunk_stream_.set_eof_pos(std::max(0, loader_->GetContentLength()));
Lei Zhang6fbfb092018-03-28 16:58:56102
Artem Stryginaf1b43422017-09-12 18:48:24103 int64_t bytes_received = 0;
104 int64_t total_bytes_to_be_received = 0;
Lei Zhang6fbfb092018-03-28 16:58:56105 if (GetDocumentSize() == 0 &&
Artem Stryginaf1b43422017-09-12 18:48:24106 loader_->GetDownloadProgress(&bytes_received,
107 &total_bytes_to_be_received)) {
K. Moon6f6c6e42020-03-19 04:21:53108 chunk_stream_.set_eof_pos(
109 std::max(0, static_cast<int>(total_bytes_to_be_received)));
Artem Stryginaf1b43422017-09-12 18:48:24110 }
111
112 SetPartialLoadingEnabled(
113 partial_loading_enabled_ &&
114 !base::StartsWith(url, "file://", base::CompareCase::INSENSITIVE_ASCII) &&
115 loader_->IsAcceptRangesBytes() && !loader_->IsContentEncoded() &&
116 GetDocumentSize());
117
118 ReadMore();
[email protected]1b1e9eff2014-05-20 01:56:40119 return true;
120}
121
Lei Zhange98901a2018-04-25 23:23:59122bool DocumentLoaderImpl::IsDocumentComplete() const {
Artem Stryginaf1b43422017-09-12 18:48:24123 return chunk_stream_.IsComplete();
dsinclair07a72bd2017-08-14 23:27:04124}
125
Lei Zhange98901a2018-04-25 23:23:59126uint32_t DocumentLoaderImpl::GetDocumentSize() const {
Artem Stryginaf1b43422017-09-12 18:48:24127 return chunk_stream_.eof_pos();
[email protected]1b1e9eff2014-05-20 01:56:40128}
129
Lei Zhange98901a2018-04-25 23:23:59130uint32_t DocumentLoaderImpl::BytesReceived() const {
131 return bytes_received_;
132}
133
134void DocumentLoaderImpl::ClearPendingRequests() {
Artem Stryginaf1b43422017-09-12 18:48:24135 pending_requests_.Clear();
[email protected]1b1e9eff2014-05-20 01:56:40136}
137
Lei Zhange98901a2018-04-25 23:23:59138bool DocumentLoaderImpl::GetBlock(uint32_t position,
139 uint32_t size,
140 void* buf) const {
Artem Stryginaf1b43422017-09-12 18:48:24141 base::CheckedNumeric<uint32_t> addition_result = position;
142 addition_result += size;
143 if (!addition_result.IsValid())
144 return false;
145 return chunk_stream_.ReadData(
146 gfx::Range(position, addition_result.ValueOrDie()), buf);
[email protected]1b1e9eff2014-05-20 01:56:40147}
148
Lei Zhange98901a2018-04-25 23:23:59149bool DocumentLoaderImpl::IsDataAvailable(uint32_t position,
150 uint32_t size) const {
Artem Stryginaf1b43422017-09-12 18:48:24151 base::CheckedNumeric<uint32_t> addition_result = position;
152 addition_result += size;
153 if (!addition_result.IsValid())
154 return false;
155 return chunk_stream_.IsRangeAvailable(
156 gfx::Range(position, addition_result.ValueOrDie()));
[email protected]1b1e9eff2014-05-20 01:56:40157}
158
Lei Zhange98901a2018-04-25 23:23:59159void DocumentLoaderImpl::RequestData(uint32_t position, uint32_t size) {
Lei Zhang6fbfb092018-03-28 16:58:56160 if (size == 0 || IsDataAvailable(position, size))
Artem Stryginaf1b43422017-09-12 18:48:24161 return;
Lei Zhang6fbfb092018-03-28 16:58:56162
163 const uint32_t document_size = GetDocumentSize();
164 if (document_size != 0) {
165 // Check for integer overflow.
Artem Stryginaf1b43422017-09-12 18:48:24166 base::CheckedNumeric<uint32_t> addition_result = position;
167 addition_result += size;
168 if (!addition_result.IsValid())
169 return;
Lei Zhang6fbfb092018-03-28 16:58:56170
171 if (addition_result.ValueOrDie() > document_size)
172 return;
Artem Stryginaf1b43422017-09-12 18:48:24173 }
174
Lei Zhang6fbfb092018-03-28 16:58:56175 // We have some artifact request from
[email protected]1b1e9eff2014-05-20 01:56:40176 // PDFiumEngine::OnDocumentComplete() -> FPDFAvail_IsPageAvail after
177 // document is complete.
178 // We need this fix in PDFIum. Adding this as a work around.
179 // Bug: http://code.google.com/p/chromium/issues/detail?id=79996
180 // Test url:
181 // http://www.icann.org/en/correspondence/holtzman-to-jeffrey-02mar11-en.pdf
Artem Stryginaf1b43422017-09-12 18:48:24182 if (!loader_)
[email protected]1b1e9eff2014-05-20 01:56:40183 return;
184
Artem Stryginaf1b43422017-09-12 18:48:24185 RangeSet requested_chunks(chunk_stream_.GetChunksRange(position, size));
186 requested_chunks.Subtract(chunk_stream_.filled_chunks());
187 if (requested_chunks.IsEmpty()) {
188 NOTREACHED();
189 return;
190 }
191 pending_requests_.Union(requested_chunks);
dsinclair07a72bd2017-08-14 23:27:04192}
193
Lei Zhange98901a2018-04-25 23:23:59194void DocumentLoaderImpl::SetPartialLoadingEnabled(bool enabled) {
Artem Stryginaf1b43422017-09-12 18:48:24195 partial_loading_enabled_ = enabled;
196 if (!enabled) {
197 is_partial_loader_active_ = false;
dsinclair07a72bd2017-08-14 23:27:04198 }
199}
200
Lei Zhange98901a2018-04-25 23:23:59201bool DocumentLoaderImpl::ShouldCancelLoading() const {
Artem Stryginaf1b43422017-09-12 18:48:24202 if (!loader_)
203 return true;
Artem Strygin2cf20af42018-06-14 14:26:53204
205 if (!partial_loading_enabled_)
Artem Stryginaf1b43422017-09-12 18:48:24206 return false;
Artem Strygin2cf20af42018-06-14 14:26:53207
208 if (pending_requests_.IsEmpty()) {
209 // Cancel loading if this is unepected data from server.
210 return !chunk_stream_.IsValidChunkIndex(chunk_.chunk_index) ||
211 chunk_stream_.IsChunkAvailable(chunk_.chunk_index);
212 }
213
Artem Stryginaf1b43422017-09-12 18:48:24214 const gfx::Range current_range(chunk_.chunk_index,
215 chunk_.chunk_index + kChunkCloseDistance);
216 return !pending_requests_.Intersects(current_range);
217}
art-snake3704ccf02016-11-03 02:13:20218
Lei Zhange98901a2018-04-25 23:23:59219void DocumentLoaderImpl::ContinueDownload() {
Artem Stryginaf1b43422017-09-12 18:48:24220 if (!ShouldCancelLoading())
221 return ReadMore();
Lei Zhang6fbfb092018-03-28 16:58:56222
Artem Stryginaf1b43422017-09-12 18:48:24223 DCHECK(partial_loading_enabled_);
224 DCHECK(!IsDocumentComplete());
Lei Zhang6fbfb092018-03-28 16:58:56225 DCHECK_GT(GetDocumentSize(), 0U);
Artem Strygin9a6d1482017-07-26 21:08:56226
Artem Stryginaf1b43422017-09-12 18:48:24227 const uint32_t range_start =
228 pending_requests_.IsEmpty() ? 0 : pending_requests_.First().start();
229 RangeSet candidates_for_request(
230 gfx::Range(range_start, chunk_stream_.total_chunks_count()));
231 candidates_for_request.Subtract(chunk_stream_.filled_chunks());
232 DCHECK(!candidates_for_request.IsEmpty());
233 gfx::Range next_request = candidates_for_request.First();
234 if (candidates_for_request.Size() == 1 &&
235 next_request.length() < kChunkCloseDistance) {
236 // We have only request at the end, try to enlarge it to improve back order
237 // reading.
238 const int additional_chunks_count =
239 kChunkCloseDistance - next_request.length();
240 int new_start = std::max(
241 0, static_cast<int>(next_request.start()) - additional_chunks_count);
242 candidates_for_request =
243 RangeSet(gfx::Range(new_start, next_request.end()));
244 candidates_for_request.Subtract(chunk_stream_.filled_chunks());
245 next_request = candidates_for_request.Last();
dsinclairadf55f3f2016-12-07 14:00:41246 }
gene7cafb2ce62014-10-24 00:56:53247
Artem Stryginaf1b43422017-09-12 18:48:24248 loader_.reset();
249 chunk_.Clear();
Robbie McElrath44e2d302019-07-15 23:28:55250 is_partial_loader_active_ = true;
dsinclairadf55f3f2016-12-07 14:00:41251
Artem Stryginaf1b43422017-09-12 18:48:24252 const uint32_t start = next_request.start() * DataStream::kChunkSize;
253 const uint32_t length =
Lei Zhang6fbfb092018-03-28 16:58:56254 std::min(GetDocumentSize() - start,
Artem Stryginaf1b43422017-09-12 18:48:24255 next_request.length() * DataStream::kChunkSize);
dsinclairadf55f3f2016-12-07 14:00:41256
Artem Strygin9a6d1482017-07-26 21:08:56257 loader_ = client_->CreateURLLoader();
Artem Stryginaf1b43422017-09-12 18:48:24258
259 loader_->OpenRange(
260 url_, url_, start, length,
Lei Zhange98901a2018-04-25 23:23:59261 loader_factory_.NewCallback(&DocumentLoaderImpl::DidOpenPartial));
Artem Strygin9a6d1482017-07-26 21:08:56262}
263
Lei Zhange98901a2018-04-25 23:23:59264void DocumentLoaderImpl::DidOpenPartial(int32_t result) {
Artem Strygin9a6d1482017-07-26 21:08:56265 if (result != PP_OK) {
Artem Stryginaf1b43422017-09-12 18:48:24266 return ReadComplete();
Artem Strygin9a6d1482017-07-26 21:08:56267 }
268
Artem Stryginaf1b43422017-09-12 18:48:24269 if (!ResponseStatusSuccess(loader_.get()))
270 return ReadComplete();
Artem Strygin9a6d1482017-07-26 21:08:56271
Artem Stryginaf1b43422017-09-12 18:48:24272 // Leave position untouched for multiparted responce for now, when we read the
273 // data we'll get it.
Lei Zhang02a3a0f2017-12-11 20:25:48274 if (loader_->IsMultipart()) {
275 // Needs more data to calc chunk index.
276 return ReadMore();
[email protected]1b1e9eff2014-05-20 01:56:40277 }
Lei Zhang02a3a0f2017-12-11 20:25:48278
279 // Need to make sure that the server returned a byte-range, since it's
280 // possible for a server to just ignore our byte-range request and just
281 // return the entire document even if it supports byte-range requests.
282 // i.e. sniff response to
283 // http://www.act.org/compass/sample/pdf/geometry.pdf
284 int start_pos = 0;
285 if (loader_->GetByteRangeStart(&start_pos)) {
286 if (start_pos % DataStream::kChunkSize != 0)
287 return ReadComplete();
288
289 DCHECK(!chunk_.chunk_data);
290 chunk_.chunk_index = chunk_stream_.GetChunkIndex(start_pos);
291 } else {
292 SetPartialLoadingEnabled(false);
293 }
294 return ContinueDownload();
[email protected]1b1e9eff2014-05-20 01:56:40295}
296
Lei Zhange98901a2018-04-25 23:23:59297void DocumentLoaderImpl::ReadMore() {
Artem Stryginaf1b43422017-09-12 18:48:24298 loader_->ReadResponseBody(
299 buffer_, sizeof(buffer_),
Lei Zhange98901a2018-04-25 23:23:59300 loader_factory_.NewCallback(&DocumentLoaderImpl::DidRead));
[email protected]1b1e9eff2014-05-20 01:56:40301}
302
Lei Zhange98901a2018-04-25 23:23:59303void DocumentLoaderImpl::DidRead(int32_t result) {
Artem Stryginaf1b43422017-09-12 18:48:24304 if (result < 0) {
305 // An error occurred.
306 // The renderer will detect that we're missing data and will display a
307 // message.
308 return ReadComplete();
[email protected]1b1e9eff2014-05-20 01:56:40309 }
Artem Stryginaf1b43422017-09-12 18:48:24310 if (result == 0) {
311 loader_.reset();
312 if (!is_partial_loader_active_)
313 return ReadComplete();
314 return ContinueDownload();
315 }
316 if (loader_->IsMultipart()) {
317 int start_pos = 0;
Lei Zhang02a3a0f2017-12-11 20:25:48318 if (!loader_->GetByteRangeStart(&start_pos))
Artem Stryginaf1b43422017-09-12 18:48:24319 return ReadComplete();
Lei Zhang02a3a0f2017-12-11 20:25:48320
Artem Stryginaf1b43422017-09-12 18:48:24321 DCHECK(!chunk_.chunk_data);
322 chunk_.chunk_index = chunk_stream_.GetChunkIndex(start_pos);
spelchat5efbaf822016-05-24 23:29:56323 }
Lei Zhang32094aac2018-03-28 18:29:15324 if (!SaveBuffer(buffer_, result))
Artem Stryginaf1b43422017-09-12 18:48:24325 return ReadMore();
Lei Zhang02a3a0f2017-12-11 20:25:48326 if (IsDocumentComplete())
Artem Stryginaf1b43422017-09-12 18:48:24327 return ReadComplete();
Artem Stryginaf1b43422017-09-12 18:48:24328 return ContinueDownload();
[email protected]1b1e9eff2014-05-20 01:56:40329}
330
Lei Zhange98901a2018-04-25 23:23:59331bool DocumentLoaderImpl::SaveBuffer(char* input, uint32_t input_size) {
Lei Zhang32094aac2018-03-28 18:29:15332 const uint32_t document_size = GetDocumentSize();
Lei Zhang6fbfb092018-03-28 16:58:56333 bytes_received_ += input_size;
Artem Stryginaf1b43422017-09-12 18:48:24334 bool chunk_saved = false;
335 bool loading_pending_request = pending_requests_.Contains(chunk_.chunk_index);
336 while (input_size > 0) {
Lei Zhang6fbfb092018-03-28 16:58:56337 if (chunk_.data_size == 0)
Lei Zhang64aa552c2017-10-17 18:35:53338 chunk_.chunk_data = std::make_unique<DataStream::ChunkData>();
Lei Zhang6fbfb092018-03-28 16:58:56339
Artem Stryginaf1b43422017-09-12 18:48:24340 const uint32_t new_chunk_data_len =
341 std::min(DataStream::kChunkSize - chunk_.data_size, input_size);
342 memcpy(chunk_.chunk_data->data() + chunk_.data_size, input,
343 new_chunk_data_len);
344 chunk_.data_size += new_chunk_data_len;
345 if (chunk_.data_size == DataStream::kChunkSize ||
Artem Strygin2cf20af42018-06-14 14:26:53346 (document_size > 0 && document_size <= EndOfCurrentChunk())) {
Artem Stryginaf1b43422017-09-12 18:48:24347 pending_requests_.Subtract(
348 gfx::Range(chunk_.chunk_index, chunk_.chunk_index + 1));
Lei Zhang32094aac2018-03-28 18:29:15349 SaveChunkData();
Artem Stryginaf1b43422017-09-12 18:48:24350 chunk_saved = true;
351 }
352
353 input += new_chunk_data_len;
354 input_size -= new_chunk_data_len;
355 }
356
357 client_->OnNewDataReceived();
358
359 if (IsDocumentComplete())
360 return true;
361
362 if (!chunk_saved)
363 return false;
364
365 if (loading_pending_request &&
366 !pending_requests_.Contains(chunk_.chunk_index)) {
367 client_->OnPendingRequestComplete();
368 }
369 return true;
spelchat3ba2a2812015-12-10 00:44:15370}
371
Lei Zhange98901a2018-04-25 23:23:59372void DocumentLoaderImpl::SaveChunkData() {
Lei Zhang32094aac2018-03-28 18:29:15373 chunk_stream_.SetChunkData(chunk_.chunk_index, std::move(chunk_.chunk_data));
374 chunk_.data_size = 0;
375 ++chunk_.chunk_index;
376}
377
Lei Zhange98901a2018-04-25 23:23:59378uint32_t DocumentLoaderImpl::EndOfCurrentChunk() const {
Lei Zhang6fbfb092018-03-28 16:58:56379 return chunk_.chunk_index * DataStream::kChunkSize + chunk_.data_size;
380}
381
Lei Zhange98901a2018-04-25 23:23:59382void DocumentLoaderImpl::ReadComplete() {
Lei Zhang32094aac2018-03-28 18:29:15383 if (GetDocumentSize() != 0) {
384 // If there is remaining data in |chunk_|, then save whatever can be saved.
385 // e.g. In the underrun case.
386 if (chunk_.data_size != 0)
387 SaveChunkData();
388 } else {
Lei Zhang6fbfb092018-03-28 16:58:56389 uint32_t eof = EndOfCurrentChunk();
Artem Stryginaf1b43422017-09-12 18:48:24390 if (!chunk_stream_.filled_chunks().IsEmpty()) {
391 eof = std::max(
392 chunk_stream_.filled_chunks().Last().end() * DataStream::kChunkSize,
393 eof);
[email protected]1b1e9eff2014-05-20 01:56:40394 }
K. Moon6f6c6e42020-03-19 04:21:53395 chunk_stream_.set_eof_pos(eof);
Lei Zhang32094aac2018-03-28 18:29:15396 if (eof == EndOfCurrentChunk())
397 SaveChunkData();
[email protected]1b1e9eff2014-05-20 01:56:40398 }
Artem Stryginaf1b43422017-09-12 18:48:24399 loader_.reset();
[email protected]1b1e9eff2014-05-20 01:56:40400 if (IsDocumentComplete()) {
401 client_->OnDocumentComplete();
Artem Stryginaf1b43422017-09-12 18:48:24402 } else {
403 client_->OnDocumentCanceled();
[email protected]1b1e9eff2014-05-20 01:56:40404 }
[email protected]1b1e9eff2014-05-20 01:56:40405}
406
407} // namespace chrome_pdf