Files
brave-core/components/speedreader/speedreader_url_loader.cc
T
Kevin Kuehler ce90b1e434 speedreader: Pass result correctly for CR94
Prior to CR94 we were able to assume that the URLLoaderThrottle would
outlive the URLLoader. This is no longer the case, so we pass the
result_delegate_ to the loader.

Resolves https://github.com/brave/brave-browser/issues/18151
2021-09-15 23:45:10 -07:00

389 lines
13 KiB
C++

/* Copyright 2020 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "brave/components/speedreader/speedreader_url_loader.h"
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "brave/components/speedreader/rust/ffi/speedreader.h"
#include "brave/components/speedreader/speedreader_result_delegate.h"
#include "brave/components/speedreader/speedreader_rewriter_service.h"
#include "brave/components/speedreader/speedreader_throttle.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "services/network/public/mojom/early_hints.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace speedreader {
namespace {
constexpr uint32_t kReadBufferSize = 32768;
} // namespace
// static
std::tuple<mojo::PendingRemote<network::mojom::URLLoader>,
mojo::PendingReceiver<network::mojom::URLLoaderClient>,
SpeedReaderURLLoader*>
SpeedReaderURLLoader::CreateLoader(
base::WeakPtr<SpeedReaderThrottle> throttle,
base::WeakPtr<SpeedreaderResultDelegate> delegate,
const GURL& response_url,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
SpeedreaderRewriterService* rewriter_service) {
mojo::PendingRemote<network::mojom::URLLoader> url_loader;
mojo::PendingRemote<network::mojom::URLLoaderClient> url_loader_client;
mojo::PendingReceiver<network::mojom::URLLoaderClient>
url_loader_client_receiver =
url_loader_client.InitWithNewPipeAndPassReceiver();
auto loader = base::WrapUnique(new SpeedReaderURLLoader(
std::move(throttle), std::move(delegate), response_url,
std::move(url_loader_client), std::move(task_runner), rewriter_service));
SpeedReaderURLLoader* loader_rawptr = loader.get();
mojo::MakeSelfOwnedReceiver(std::move(loader),
url_loader.InitWithNewPipeAndPassReceiver());
return std::make_tuple(std::move(url_loader),
std::move(url_loader_client_receiver), loader_rawptr);
}
SpeedReaderURLLoader::SpeedReaderURLLoader(
base::WeakPtr<SpeedReaderThrottle> throttle,
base::WeakPtr<SpeedreaderResultDelegate> delegate,
const GURL& response_url,
mojo::PendingRemote<network::mojom::URLLoaderClient>
destination_url_loader_client,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
SpeedreaderRewriterService* rewriter_service)
: throttle_(throttle),
delegate_(delegate),
destination_url_loader_client_(std::move(destination_url_loader_client)),
response_url_(response_url),
task_runner_(task_runner),
body_consumer_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL,
task_runner),
body_producer_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL,
std::move(task_runner)),
rewriter_service_(rewriter_service) {}
SpeedReaderURLLoader::~SpeedReaderURLLoader() = default;
void SpeedReaderURLLoader::Start(
mojo::PendingRemote<network::mojom::URLLoader> source_url_loader_remote,
mojo::PendingReceiver<network::mojom::URLLoaderClient>
source_url_client_receiver) {
source_url_loader_.Bind(std::move(source_url_loader_remote));
source_url_client_receiver_.Bind(std::move(source_url_client_receiver),
task_runner_);
}
void SpeedReaderURLLoader::OnReceiveEarlyHints(
network::mojom::EarlyHintsPtr early_hints) {}
void SpeedReaderURLLoader::OnReceiveResponse(
network::mojom::URLResponseHeadPtr response_head) {
// OnReceiveResponse() shouldn't be called because SpeedReaderURLLoader is
// created by SpeedReaderThrottle::WillProcessResponse(), which is equivalent
// to OnReceiveResponse().
NOTREACHED();
}
void SpeedReaderURLLoader::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr response_head) {
// OnReceiveRedirect() shouldn't be called because SpeedReaderURLLoader is
// created by SpeedReaderThrottle::WillProcessResponse(), which is equivalent
// to OnReceiveResponse().
NOTREACHED();
}
void SpeedReaderURLLoader::OnUploadProgress(
int64_t current_position,
int64_t total_size,
OnUploadProgressCallback ack_callback) {
destination_url_loader_client_->OnUploadProgress(current_position, total_size,
std::move(ack_callback));
}
void SpeedReaderURLLoader::OnReceiveCachedMetadata(mojo_base::BigBuffer data) {
destination_url_loader_client_->OnReceiveCachedMetadata(std::move(data));
}
void SpeedReaderURLLoader::OnTransferSizeUpdated(int32_t transfer_size_diff) {
destination_url_loader_client_->OnTransferSizeUpdated(transfer_size_diff);
}
void SpeedReaderURLLoader::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) {
VLOG(2) << __func__ << " " << response_url_;
state_ = State::kLoading;
body_consumer_handle_ = std::move(body);
body_consumer_watcher_.Watch(
body_consumer_handle_.get(),
MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
base::BindRepeating(&SpeedReaderURLLoader::OnBodyReadable,
base::Unretained(this)));
body_consumer_watcher_.ArmOrNotify();
}
void SpeedReaderURLLoader::OnComplete(
const network::URLLoaderCompletionStatus& status) {
DCHECK(!complete_status_.has_value());
switch (state_) {
case State::kWaitForBody:
// An error occured before receiving any data.
DCHECK_NE(net::OK, status.error_code);
state_ = State::kCompleted;
if (!throttle_) {
Abort();
return;
}
throttle_->Resume();
destination_url_loader_client_->OnComplete(status);
return;
case State::kLoading:
case State::kSending:
// Defer calling OnComplete() until distilling has finished and all
// data is sent.
complete_status_ = status;
return;
case State::kCompleted:
destination_url_loader_client_->OnComplete(status);
return;
case State::kAborted:
NOTREACHED();
return;
}
NOTREACHED();
}
void SpeedReaderURLLoader::FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const absl::optional<GURL>& new_url) {
// SpeedReaderURLLoader starts handling the request after
// OnReceivedResponse(). A redirect response is not expected.
NOTREACHED();
}
void SpeedReaderURLLoader::SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) {
if (state_ == State::kAborted)
return;
source_url_loader_->SetPriority(priority, intra_priority_value);
}
void SpeedReaderURLLoader::PauseReadingBodyFromNet() {
if (state_ == State::kAborted)
return;
source_url_loader_->PauseReadingBodyFromNet();
}
void SpeedReaderURLLoader::ResumeReadingBodyFromNet() {
if (state_ == State::kAborted)
return;
source_url_loader_->ResumeReadingBodyFromNet();
}
void SpeedReaderURLLoader::OnBodyReadable(MojoResult) {
DCHECK_EQ(State::kLoading, state_);
size_t start_size = buffered_body_.size();
uint32_t read_bytes = kReadBufferSize;
buffered_body_.resize(start_size + read_bytes);
MojoResult result = body_consumer_handle_->ReadData(
&buffered_body_[0] + start_size, &read_bytes, MOJO_READ_DATA_FLAG_NONE);
switch (result) {
case MOJO_RESULT_OK:
break;
case MOJO_RESULT_FAILED_PRECONDITION:
// Reading is finished.
buffered_body_.resize(start_size);
MaybeLaunchSpeedreader();
return;
case MOJO_RESULT_SHOULD_WAIT:
body_consumer_watcher_.ArmOrNotify();
return;
default:
NOTREACHED();
return;
}
DCHECK_EQ(MOJO_RESULT_OK, result);
buffered_body_.resize(start_size + read_bytes);
// TODO(iefremov): We actually can partially |pumpContent| to speedreader,
// but skipping it for now to simplify things. Pumping is not free in terms
// of CPU ticks, so we will have to keep alive speedreader instance on another
// thread.
body_consumer_watcher_.ArmOrNotify();
}
void SpeedReaderURLLoader::OnBodyWritable(MojoResult r) {
DCHECK_EQ(State::kSending, state_);
if (bytes_remaining_in_buffer_ > 0) {
SendReceivedBodyToClient();
} else {
CompleteSending();
}
}
void SpeedReaderURLLoader::MaybeLaunchSpeedreader() {
DCHECK_EQ(State::kLoading, state_);
if (!throttle_ || !rewriter_service_) {
Abort();
return;
}
VLOG(2) << __func__ << " buffered body size = " << buffered_body_.size();
bytes_remaining_in_buffer_ = buffered_body_.size();
if (bytes_remaining_in_buffer_ > 0) {
// Offload heavy distilling to another thread.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::TaskPriority::USER_BLOCKING},
base::BindOnce(
[](std::string data, std::unique_ptr<Rewriter> rewriter,
const std::string& stylesheet) -> auto {
SCOPED_UMA_HISTOGRAM_TIMER("Brave.Speedreader.Distill");
int written = rewriter->Write(data.c_str(), data.length());
// Error occurred
if (written != 0) {
return data;
}
rewriter->End();
const std::string& transformed = rewriter->GetOutput();
// TODO(brave-browser/issues/10372): would be better to pass
// explicit signal back from rewriter to indicate if content was
// found
if (transformed.length() < 1024) {
return data;
}
return stylesheet + transformed;
},
std::move(buffered_body_),
rewriter_service_->MakeRewriter(response_url_),
rewriter_service_->GetContentStylesheet()),
base::BindOnce(&SpeedReaderURLLoader::CompleteLoading,
weak_factory_.GetWeakPtr()));
return;
}
CompleteLoading(std::move(buffered_body_));
}
void SpeedReaderURLLoader::CompleteLoading(std::string body) {
DCHECK_EQ(State::kLoading, state_);
state_ = State::kSending;
if (!throttle_) {
Abort();
return;
}
buffered_body_ = std::move(body);
bytes_remaining_in_buffer_ = buffered_body_.size();
throttle_->Resume();
mojo::ScopedDataPipeConsumerHandle body_to_send;
MojoResult result =
mojo::CreateDataPipe(nullptr, body_producer_handle_, body_to_send);
if (result != MOJO_RESULT_OK) {
Abort();
return;
}
// Set up the watcher for the producer handle.
body_producer_watcher_.Watch(
body_producer_handle_.get(),
MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
base::BindRepeating(&SpeedReaderURLLoader::OnBodyWritable,
base::Unretained(this)));
// Send deferred message.
destination_url_loader_client_->OnStartLoadingResponseBody(
std::move(body_to_send));
DCHECK(bytes_remaining_in_buffer_);
if (bytes_remaining_in_buffer_) {
SendReceivedBodyToClient();
return;
}
CompleteSending();
}
void SpeedReaderURLLoader::CompleteSending() {
DCHECK_EQ(State::kSending, state_);
state_ = State::kCompleted;
// Call client's OnComplete() if |this|'s OnComplete() has already been
// called.
if (complete_status_.has_value()) {
destination_url_loader_client_->OnComplete(complete_status_.value());
// TODO(keur, iefremov): This API could probably be improved with an enum
// indicating distill success, distill fail, load from cache.
// |complete_status_| has an |exists_in_cache| field.
if (delegate_)
delegate_->OnDistillComplete();
}
body_consumer_watcher_.Cancel();
body_producer_watcher_.Cancel();
body_consumer_handle_.reset();
body_producer_handle_.reset();
}
void SpeedReaderURLLoader::SendReceivedBodyToClient() {
DCHECK_EQ(State::kSending, state_);
// Send the buffered data first.
DCHECK_GT(bytes_remaining_in_buffer_, 0u);
size_t start_position = buffered_body_.size() - bytes_remaining_in_buffer_;
uint32_t bytes_sent = bytes_remaining_in_buffer_;
MojoResult result =
body_producer_handle_->WriteData(buffered_body_.data() + start_position,
&bytes_sent, MOJO_WRITE_DATA_FLAG_NONE);
switch (result) {
case MOJO_RESULT_OK:
break;
case MOJO_RESULT_FAILED_PRECONDITION:
// The pipe is closed unexpectedly. |this| should be deleted once
// URLLoaderPtr on the destination is released.
Abort();
return;
case MOJO_RESULT_SHOULD_WAIT:
body_producer_watcher_.ArmOrNotify();
return;
default:
NOTREACHED();
return;
}
bytes_remaining_in_buffer_ -= bytes_sent;
body_producer_watcher_.ArmOrNotify();
}
void SpeedReaderURLLoader::Abort() {
VLOG(2) << __func__ << " " << response_url_;
state_ = State::kAborted;
body_consumer_watcher_.Cancel();
body_producer_watcher_.Cancel();
source_url_loader_.reset();
source_url_client_receiver_.reset();
destination_url_loader_client_.reset();
// |this| should be removed since the owner will destroy |this| or the owner
// has already been destroyed by some reason.
}
} // namespace speedreader