[go: nahoru, domu]

blob: 984c6554c5051afb2969a2326bcf5156c199012c [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/loader/prefetched_signed_exchange_manager.h"
#include <optional>
#include <queue>
#include <utility>
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/url_loader_factory.mojom-blink.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/resource_load_info_notifier_wrapper.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_url_request_extra_data.h"
#include "third_party/blink/public/platform/web_url_response.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/navigator.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/loader/alternate_signed_exchange_resource_info.h"
#include "third_party/blink/renderer/core/loader/loader_factory_for_frame.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/loader/fetch/code_cache_host.h"
#include "third_party/blink/renderer/platform/loader/fetch/loader_freeze_mode.h"
#include "third_party/blink/renderer/platform/loader/fetch/url_loader/url_loader.h"
#include "third_party/blink/renderer/platform/loader/fetch/url_loader/url_loader_client.h"
#include "third_party/blink/renderer/platform/loader/fetch/url_loader/url_loader_factory.h"
#include "third_party/blink/renderer/platform/loader/link_header.h"
#include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/kurl_hash.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
class PrefetchedSignedExchangeManager::PrefetchedSignedExchangeLoader
: public URLLoader {
public:
PrefetchedSignedExchangeLoader(
const network::ResourceRequest& request,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
Vector<std::unique_ptr<URLLoaderThrottle>> throttles)
: request_(request),
task_runner_(std::move(task_runner)),
throttles_(std::move(throttles)) {
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("loading",
"PrefetchedSignedExchangeLoader", this,
"url", request_.url.spec());
}
PrefetchedSignedExchangeLoader(const PrefetchedSignedExchangeLoader&) =
delete;
PrefetchedSignedExchangeLoader& operator=(
const PrefetchedSignedExchangeLoader&) = delete;
~PrefetchedSignedExchangeLoader() override {
TRACE_EVENT_NESTABLE_ASYNC_END0("loading", "PrefetchedSignedExchangeLoader",
this);
}
base::WeakPtr<PrefetchedSignedExchangeLoader> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void SetURLLoader(std::unique_ptr<URLLoader> url_loader) {
DCHECK(!url_loader_);
url_loader_ = std::move(url_loader);
ExecutePendingMethodCalls();
}
const network::ResourceRequest& request() const { return request_; }
Vector<std::unique_ptr<URLLoaderThrottle>> TakeThrottles() {
return std::move(throttles_);
}
// URLLoader methods:
void LoadSynchronously(std::unique_ptr<network::ResourceRequest> request,
scoped_refptr<const SecurityOrigin> top_frame_origin,
bool download_to_blob,
bool no_mime_sniffing,
base::TimeDelta timeout_interval,
URLLoaderClient* client,
WebURLResponse& response,
std::optional<WebURLError>& error,
scoped_refptr<SharedBuffer>& data,
int64_t& encoded_data_length,
uint64_t& encoded_body_length,
scoped_refptr<BlobDataHandle>& downloaded_blob,
std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
resource_load_info_notifier_wrapper) override {
NOTREACHED();
}
void LoadAsynchronously(
std::unique_ptr<network::ResourceRequest> request,
scoped_refptr<const SecurityOrigin> top_frame_origin,
bool no_mime_sniffing,
std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
resource_load_info_notifier_wrapper,
CodeCacheHost* code_cache_host,
URLLoaderClient* client) override {
if (url_loader_) {
url_loader_->LoadAsynchronously(
std::move(request), std::move(top_frame_origin), no_mime_sniffing,
std::move(resource_load_info_notifier_wrapper), code_cache_host,
client);
return;
}
// It is safe to use Unretained(client), because |client| is a
// ResourceLoader which owns |this|, and we are binding with weak ptr of
// |this| here.
pending_method_calls_.push(WTF::BindOnce(
[](base::WeakPtr<PrefetchedSignedExchangeLoader> self,
std::unique_ptr<network::ResourceRequest> request,
scoped_refptr<const SecurityOrigin> top_frame_origin,
bool no_mime_sniffing,
std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
resource_load_info_notifier_wrapper,
base::WeakPtr<CodeCacheHost> code_cache_host,
URLLoaderClient* client) {
if (self) {
self->LoadAsynchronously(
std::move(request), top_frame_origin, no_mime_sniffing,
std::move(resource_load_info_notifier_wrapper),
code_cache_host.get(), client);
}
},
GetWeakPtr(), std::move(request), std::move(top_frame_origin),
no_mime_sniffing, std::move(resource_load_info_notifier_wrapper),
code_cache_host ? code_cache_host->GetWeakPtr() : nullptr,
WTF::Unretained(client)));
}
void Freeze(LoaderFreezeMode value) override {
if (url_loader_) {
url_loader_->Freeze(value);
return;
}
pending_method_calls_.push(WTF::BindOnce(
&PrefetchedSignedExchangeLoader::Freeze, GetWeakPtr(), value));
}
void DidChangePriority(WebURLRequest::Priority new_priority,
int intra_priority_value) override {
if (url_loader_) {
url_loader_->DidChangePriority(new_priority, intra_priority_value);
return;
}
pending_method_calls_.push(
WTF::BindOnce(&PrefetchedSignedExchangeLoader::DidChangePriority,
GetWeakPtr(), new_priority, intra_priority_value));
}
scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunnerForBodyLoader()
override {
return task_runner_;
}
private:
void ExecutePendingMethodCalls() {
std::queue<base::OnceClosure> pending_calls =
std::move(pending_method_calls_);
while (!pending_calls.empty()) {
std::move(pending_calls.front()).Run();
pending_calls.pop();
}
}
const network::ResourceRequest request_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
Vector<std::unique_ptr<URLLoaderThrottle>> throttles_;
std::unique_ptr<URLLoader> url_loader_;
std::queue<base::OnceClosure> pending_method_calls_;
base::WeakPtrFactory<PrefetchedSignedExchangeLoader> weak_ptr_factory_{this};
};
// static
PrefetchedSignedExchangeManager* PrefetchedSignedExchangeManager::MaybeCreate(
LocalFrame* frame,
const String& outer_link_header,
const String& inner_link_header,
WebVector<std::unique_ptr<WebNavigationParams::PrefetchedSignedExchange>>
prefetched_signed_exchanges) {
if (prefetched_signed_exchanges.empty())
return nullptr;
std::unique_ptr<AlternateSignedExchangeResourceInfo> alternative_resources =
AlternateSignedExchangeResourceInfo::CreateIfValid(outer_link_header,
inner_link_header);
if (!alternative_resources) {
// There is no "allowed-alt-sxg" link header for this resource.
return nullptr;
}
HashMap<KURL, std::unique_ptr<WebNavigationParams::PrefetchedSignedExchange>>
prefetched_exchanges_map;
for (auto& exchange : prefetched_signed_exchanges) {
const KURL outer_url = exchange->outer_url;
prefetched_exchanges_map.Set(outer_url, std::move(exchange));
}
return MakeGarbageCollected<PrefetchedSignedExchangeManager>(
frame, std::move(alternative_resources),
std::move(prefetched_exchanges_map));
}
PrefetchedSignedExchangeManager::PrefetchedSignedExchangeManager(
LocalFrame* frame,
std::unique_ptr<AlternateSignedExchangeResourceInfo> alternative_resources,
HashMap<KURL,
std::unique_ptr<WebNavigationParams::PrefetchedSignedExchange>>
prefetched_exchanges_map)
: frame_(frame),
alternative_resources_(std::move(alternative_resources)),
prefetched_exchanges_map_(std::move(prefetched_exchanges_map)) {
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("loading",
"PrefetchedSignedExchangeManager", this);
}
PrefetchedSignedExchangeManager::~PrefetchedSignedExchangeManager() {}
void PrefetchedSignedExchangeManager::Trace(Visitor* visitor) const {
visitor->Trace(frame_);
}
void PrefetchedSignedExchangeManager::StartPrefetchedLinkHeaderPreloads() {
DCHECK(!started_);
started_ = true;
TriggerLoad();
// Clears |prefetched_exchanges_map_| to release URLLoaderFactory in the
// browser process.
prefetched_exchanges_map_.clear();
// Clears |alternative_resources_| which will not be used forever.
alternative_resources_.reset();
}
std::unique_ptr<URLLoader>
PrefetchedSignedExchangeManager::MaybeCreateURLLoader(
const network::ResourceRequest& network_request,
base::OnceCallback<Vector<std::unique_ptr<URLLoaderThrottle>>(void)>
create_throttles_callback) {
if (started_)
return nullptr;
const auto* matching_resource = alternative_resources_->FindMatchingEntry(
KURL(network_request.url), network_request.destination,
frame_->DomWindow()->navigator()->languages());
if (!matching_resource)
return nullptr;
std::unique_ptr<PrefetchedSignedExchangeLoader> loader =
std::make_unique<PrefetchedSignedExchangeLoader>(
network_request,
frame_->GetFrameScheduler()->GetTaskRunner(
TaskType::kInternalLoading),
std::move(create_throttles_callback).Run());
loaders_.emplace_back(loader->GetWeakPtr());
return loader;
}
std::unique_ptr<URLLoader>
PrefetchedSignedExchangeManager::CreateDefaultURLLoader(
const network::ResourceRequest& request,
Vector<std::unique_ptr<URLLoaderThrottle>> throttles) {
return std::make_unique<blink::URLLoaderFactory>(
frame_->GetURLLoaderFactory(),
LoaderFactoryForFrame::GetCorsExemptHeaderList(),
/*terminate_sync_load_event=*/nullptr)
->CreateURLLoader(request, frame_->GetTaskRunner(TaskType::kNetworking),
frame_->GetTaskRunner(TaskType::kNetworkingUnfreezable),
/*keep_alive_handle=*/mojo::NullRemote(),
/*back_forward_cache_loader_helper=*/nullptr,
std::move(throttles));
}
std::unique_ptr<URLLoader>
PrefetchedSignedExchangeManager::CreatePrefetchedSignedExchangeURLLoader(
const network::ResourceRequest& request,
Vector<std::unique_ptr<URLLoaderThrottle>> throttles,
mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
loader_factory) {
return std::make_unique<URLLoaderFactory>(
base::MakeRefCounted<network::WrapperSharedURLLoaderFactory>(
CrossVariantMojoRemote<
network::mojom::URLLoaderFactoryInterfaceBase>(
std::move(loader_factory))),
LoaderFactoryForFrame::GetCorsExemptHeaderList(),
/*terminate_sync_load_event=*/nullptr)
->CreateURLLoader(request, frame_->GetTaskRunner(TaskType::kNetworking),
frame_->GetTaskRunner(TaskType::kNetworkingUnfreezable),
/*keep_alive_handle=*/mojo::NullRemote(),
/*back_forward_cache_loader_helper=*/nullptr,
std::move(throttles));
}
void PrefetchedSignedExchangeManager::TriggerLoad() {
Vector<WebNavigationParams::PrefetchedSignedExchange*>
maching_prefetched_exchanges;
const char* failure_reason = nullptr;
for (auto loader : loaders_) {
if (!loader) {
// The loader has been canceled.
maching_prefetched_exchanges.emplace_back(nullptr);
// We can continue the matching, because the distributor can't send
// arbitrary information to the publisher using this resource.
continue;
}
const auto* matching_resource = alternative_resources_->FindMatchingEntry(
KURL(loader->request().url), loader->request().destination,
frame_->DomWindow()->navigator()->languages());
const auto alternative_url = matching_resource->alternative_url();
if (!alternative_url.IsValid()) {
failure_reason =
"no matching \"alternate\" link header in outer response header";
break;
}
const auto exchange_it = prefetched_exchanges_map_.find(alternative_url);
if (exchange_it == prefetched_exchanges_map_.end()) {
failure_reason = "no matching prefetched exchange";
break;
}
if (String(exchange_it->value->header_integrity) !=
matching_resource->header_integrity()) {
failure_reason = "header integrity doesn't match";
break;
}
if (KURL(exchange_it->value->inner_url) !=
matching_resource->anchor_url()) {
failure_reason = "inner URL doesn't match";
break;
}
maching_prefetched_exchanges.emplace_back(exchange_it->value.get());
}
if (loaders_.size() != maching_prefetched_exchanges.size()) {
// Need to load the all original resources in this case to prevent the
// distributor from sending arbitrary information to the publisher.
String message =
"Failed to match prefetched alternative signed exchange subresources. "
"Requesting the all original resources ignoreing all alternative signed"
" exchange responses.";
frame_->GetDocument()->AddConsoleMessage(
MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kNetwork,
mojom::ConsoleMessageLevel::kError, message));
for (auto loader : loaders_) {
if (!loader)
continue;
loader->SetURLLoader(
CreateDefaultURLLoader(loader->request(), loader->TakeThrottles()));
}
TRACE_EVENT_NESTABLE_ASYNC_END2(
"loading", "PrefetchedSignedExchangeManager", this, "match_result",
"failure", "reason", failure_reason);
return;
}
for (wtf_size_t i = 0; i < loaders_.size(); ++i) {
auto loader = loaders_.at(i);
if (!loader)
continue;
auto* prefetched_exchange = maching_prefetched_exchanges.at(i);
mojo::Remote<network::mojom::blink::URLLoaderFactory> loader_factory(
std::move(prefetched_exchange->loader_factory));
mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
loader_factory_clone;
loader_factory->Clone(
loader_factory_clone.InitWithNewPipeAndPassReceiver());
// Reset loader_factory_handle to support loading the same resource again.
prefetched_exchange->loader_factory = std::move(loader_factory_clone);
loader->SetURLLoader(CreatePrefetchedSignedExchangeURLLoader(
loader->request(), loader->TakeThrottles(), loader_factory.Unbind()));
}
TRACE_EVENT_NESTABLE_ASYNC_END1("loading", "PrefetchedSignedExchangeManager",
this, "match_result", "success");
}
} // namespace blink