blob: de92640c45ce5de359404bd8c1c62e2229c15374 [file] [log] [blame]
// Copyright 2019 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 "content/renderer/loader/navigation_body_loader.h"
#include "base/bind.h"
#include "base/macros.h"
#include "content/public/common/referrer.h"
#include "content/renderer/loader/resource_load_stats.h"
#include "content/renderer/loader/web_url_loader_impl.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/blink/public/platform/web_code_cache_loader.h"
#include "third_party/blink/public/web/web_navigation_params.h"
namespace content {
// static
constexpr uint32_t NavigationBodyLoader::kMaxNumConsumedBytesInTask;
// static
void NavigationBodyLoader::FillNavigationParamsResponseAndBodyLoader(
mojom::CommonNavigationParamsPtr common_params,
mojom::CommitNavigationParamsPtr commit_params,
int request_id,
network::mojom::URLResponseHeadPtr response_head,
mojo::ScopedDataPipeConsumerHandle response_body,
network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
int render_frame_id,
bool is_main_frame,
blink::WebNavigationParams* navigation_params) {
// Use the original navigation url to start with. We'll replay the redirects
// afterwards and will eventually arrive to the final url.
GURL url = !commit_params->original_url.is_empty()
? commit_params->original_url
: common_params->url;
auto resource_load_info = NotifyResourceLoadInitiated(
render_frame_id, request_id, url,
!commit_params->original_method.empty() ? commit_params->original_method
: common_params->method,
common_params->referrer->url,
// TODO(kinuko): This should use the same value as in the request that
// was used in browser process, i.e. what CreateResourceRequest in
// content/browser/loader/navigation_url_loader_impl.cc gives.
// (Currently we don't propagate the value from the browser on navigation
// commit.)
is_main_frame ? network::mojom::RequestDestination::kDocument
: network::mojom::RequestDestination::kIframe,
is_main_frame ? net::HIGHEST : net::LOWEST);
size_t redirect_count = commit_params->redirect_response.size();
navigation_params->redirects.reserve(redirect_count);
navigation_params->redirects.resize(redirect_count);
for (size_t i = 0; i < redirect_count; ++i) {
blink::WebNavigationParams::RedirectInfo& redirect =
navigation_params->redirects[i];
auto& redirect_info = commit_params->redirect_infos[i];
auto& redirect_response = commit_params->redirect_response[i];
WebURLLoaderImpl::PopulateURLResponse(
url, *redirect_response, &redirect.redirect_response,
response_head->ssl_info.has_value(), request_id);
NotifyResourceRedirectReceived(render_frame_id, resource_load_info.get(),
redirect_info, std::move(redirect_response));
if (url.SchemeIs(url::kDataScheme))
redirect.redirect_response.SetHttpStatusCode(200);
redirect.new_url = redirect_info.new_url;
redirect.new_referrer =
blink::WebString::FromUTF8(redirect_info.new_referrer);
redirect.new_referrer_policy =
Referrer::NetReferrerPolicyToBlinkReferrerPolicy(
redirect_info.new_referrer_policy);
redirect.new_http_method =
blink::WebString::FromLatin1(redirect_info.new_method);
url = redirect_info.new_url;
}
WebURLLoaderImpl::PopulateURLResponse(
url, *response_head, &navigation_params->response,
response_head->ssl_info.has_value(), request_id);
if (url.SchemeIs(url::kDataScheme))
navigation_params->response.SetHttpStatusCode(200);
if (url_loader_client_endpoints) {
navigation_params->body_loader.reset(new NavigationBodyLoader(
std::move(response_head), std::move(response_body),
std::move(url_loader_client_endpoints), task_runner, render_frame_id,
std::move(resource_load_info)));
}
}
NavigationBodyLoader::NavigationBodyLoader(
network::mojom::URLResponseHeadPtr response_head,
mojo::ScopedDataPipeConsumerHandle response_body,
network::mojom::URLLoaderClientEndpointsPtr endpoints,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
int render_frame_id,
blink::mojom::ResourceLoadInfoPtr resource_load_info)
: render_frame_id_(render_frame_id),
response_head_(std::move(response_head)),
response_body_(std::move(response_body)),
endpoints_(std::move(endpoints)),
task_runner_(std::move(task_runner)),
resource_load_info_(std::move(resource_load_info)),
handle_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL,
task_runner_) {}
NavigationBodyLoader::~NavigationBodyLoader() {
if (!has_received_completion_ || !has_seen_end_of_data_) {
NotifyResourceLoadCanceled(render_frame_id_, std::move(resource_load_info_),
net::ERR_ABORTED);
}
}
void NavigationBodyLoader::OnReceiveResponse(
network::mojom::URLResponseHeadPtr head) {
// This has already happened in the browser process.
NOTREACHED();
}
void NavigationBodyLoader::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr head) {
// This has already happened in the browser process.
NOTREACHED();
}
void NavigationBodyLoader::OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) {
// This has already happened in the browser process.
NOTREACHED();
}
void NavigationBodyLoader::OnReceiveCachedMetadata(mojo_base::BigBuffer data) {
// Even if IsolatedCodeCaching is landed, this code is still used by
// ServiceWorker.
// TODO(horo, kinuko): Make a test to cover this function.
// TODO(https://crbug.com/930000): Add support for inline script code caching
// with the service worker service.
client_->BodyCodeCacheReceived(std::move(data));
}
void NavigationBodyLoader::OnTransferSizeUpdated(int32_t transfer_size_diff) {
NotifyResourceTransferSizeUpdated(render_frame_id_, resource_load_info_.get(),
transfer_size_diff);
}
void NavigationBodyLoader::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle handle) {
TRACE_EVENT1("loading", "NavigationBodyLoader::OnStartLoadingResponseBody",
"url",
resource_load_info_->original_url.possibly_invalid_spec());
DCHECK(!has_received_body_handle_);
DCHECK(!has_received_completion_);
has_received_body_handle_ = true;
has_seen_end_of_data_ = false;
handle_ = std::move(handle);
DCHECK(handle_.is_valid());
handle_watcher_.Watch(handle_.get(), MOJO_HANDLE_SIGNAL_READABLE,
base::BindRepeating(&NavigationBodyLoader::OnReadable,
base::Unretained(this)));
OnReadable(MOJO_RESULT_OK);
}
void NavigationBodyLoader::OnComplete(
const network::URLLoaderCompletionStatus& status) {
// Except for errors, there must always be a response's body.
DCHECK(has_received_body_handle_ || status.error_code != net::OK);
has_received_completion_ = true;
status_ = status;
NotifyCompletionIfAppropriate();
}
void NavigationBodyLoader::SetDefersLoading(bool defers) {
if (is_deferred_ == defers)
return;
is_deferred_ = defers;
if (handle_.is_valid())
OnReadable(MOJO_RESULT_OK);
}
void NavigationBodyLoader::StartLoadingBody(
WebNavigationBodyLoader::Client* client,
bool use_isolated_code_cache) {
TRACE_EVENT1("loading", "NavigationBodyLoader::StartLoadingBody", "url",
resource_load_info_->original_url.possibly_invalid_spec());
client_ = client;
base::Time response_head_response_time = response_head_->response_time;
NotifyResourceResponseReceived(render_frame_id_, resource_load_info_.get(),
std::move(response_head_),
blink::PreviewsTypes::PREVIEWS_OFF);
if (use_isolated_code_cache) {
code_cache_loader_ = blink::WebCodeCacheLoader::Create();
code_cache_loader_->FetchFromCodeCache(
blink::mojom::CodeCacheType::kJavascript,
resource_load_info_->original_url,
base::BindOnce(&NavigationBodyLoader::CodeCacheReceived,
weak_factory_.GetWeakPtr(),
response_head_response_time));
return;
}
BindURLLoaderAndStartLoadingResponseBodyIfPossible();
}
void NavigationBodyLoader::CodeCacheReceived(
base::Time response_head_response_time,
base::Time response_time,
mojo_base::BigBuffer data) {
// Check that the times match to ensure that the code cache data is for this
// response. See https://crbug.com/1099587.
if (response_head_response_time == response_time && client_) {
base::WeakPtr<NavigationBodyLoader> weak_self = weak_factory_.GetWeakPtr();
client_->BodyCodeCacheReceived(std::move(data));
if (!weak_self)
return;
}
code_cache_loader_.reset();
// TODO(dgozman): we should explore retrieveing code cache in parallel with
// receiving response or reading the first data chunk.
BindURLLoaderAndStartLoadingResponseBodyIfPossible();
}
void NavigationBodyLoader::BindURLLoaderAndContinue() {
url_loader_.Bind(std::move(endpoints_->url_loader), task_runner_);
url_loader_client_receiver_.Bind(std::move(endpoints_->url_loader_client),
task_runner_);
url_loader_client_receiver_.set_disconnect_handler(base::BindOnce(
&NavigationBodyLoader::OnConnectionClosed, base::Unretained(this)));
}
void NavigationBodyLoader::OnConnectionClosed() {
// If the connection aborts before the load completes, mark it as failed.
if (!has_received_completion_)
OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
}
void NavigationBodyLoader::OnReadable(MojoResult unused) {
TRACE_EVENT1("loading", "NavigationBodyLoader::OnReadable", "url",
resource_load_info_->original_url.possibly_invalid_spec());
if (has_seen_end_of_data_ || is_deferred_ || is_in_on_readable_)
return;
// Protect against reentrancy:
// - when the client calls SetDefersLoading;
// - when nested message loop starts from BodyDataReceived
// and we get notified by the watcher.
// Note: we cannot use AutoReset here since |this| may be deleted
// before reset.
is_in_on_readable_ = true;
base::WeakPtr<NavigationBodyLoader> weak_self = weak_factory_.GetWeakPtr();
ReadFromDataPipe();
if (!weak_self)
return;
is_in_on_readable_ = false;
}
void NavigationBodyLoader::ReadFromDataPipe() {
TRACE_EVENT1("loading", "NavigationBodyLoader::ReadFromDataPipe", "url",
resource_load_info_->original_url.possibly_invalid_spec());
uint32_t num_bytes_consumed = 0;
while (!is_deferred_) {
const void* buffer = nullptr;
uint32_t available = 0;
MojoResult result =
handle_->BeginReadData(&buffer, &available, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
handle_watcher_.ArmOrNotify();
return;
}
if (result == MOJO_RESULT_FAILED_PRECONDITION) {
has_seen_end_of_data_ = true;
NotifyCompletionIfAppropriate();
return;
}
if (result != MOJO_RESULT_OK) {
status_.error_code = net::ERR_FAILED;
has_seen_end_of_data_ = true;
has_received_completion_ = true;
NotifyCompletionIfAppropriate();
return;
}
DCHECK_LE(num_bytes_consumed, kMaxNumConsumedBytesInTask);
available =
std::min(available, kMaxNumConsumedBytesInTask - num_bytes_consumed);
if (available == 0) {
// We've already consumed many bytes in this task. Defer the remaining
// to the next task.
result = handle_->EndReadData(0);
DCHECK_EQ(result, MOJO_RESULT_OK);
handle_watcher_.ArmOrNotify();
return;
}
num_bytes_consumed += available;
base::WeakPtr<NavigationBodyLoader> weak_self = weak_factory_.GetWeakPtr();
client_->BodyDataReceived(
base::make_span(static_cast<const char*>(buffer), available));
if (!weak_self)
return;
result = handle_->EndReadData(available);
DCHECK_EQ(MOJO_RESULT_OK, result);
}
}
void NavigationBodyLoader::NotifyCompletionIfAppropriate() {
if (!has_received_completion_ || !has_seen_end_of_data_)
return;
handle_watcher_.Cancel();
base::Optional<blink::WebURLError> error;
if (status_.error_code != net::OK) {
error = WebURLLoaderImpl::PopulateURLError(
status_, resource_load_info_->original_url);
}
NotifyResourceLoadCompleted(render_frame_id_, std::move(resource_load_info_),
status_);
if (!client_)
return;
// |this| may be deleted after calling into client_, so clear it in advance.
WebNavigationBodyLoader::Client* client = client_;
client_ = nullptr;
client->BodyLoadingFinished(
status_.completion_time, status_.encoded_data_length,
status_.encoded_body_length, status_.decoded_body_length,
status_.should_report_corb_blocking, error);
}
void NavigationBodyLoader::
BindURLLoaderAndStartLoadingResponseBodyIfPossible() {
// Bind the mojo::URLLoaderClient interface in advance, because we will start
// to read from the data pipe immediately which may potentially postpone the
// method calls from the remote. That causes the flakiness of some layout
// tests.
// TODO(minggang): The binding was executed after OnStartLoadingResponseBody
// originally (prior to passing the response body from the browser process
// during navigation), we should try to put it back if all the
// webkit_layout_tests can pass in that way.
BindURLLoaderAndContinue();
DCHECK(response_body_.is_valid());
OnStartLoadingResponseBody(std::move(response_body_));
// Don't use |this| here as it might have been destroyed.
}
} // namespace content