blob: 0215b9965b1459b2212bf7dea795a1645885351d [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 "third_party/blink/renderer/core/loader/prefetched_signed_exchange_manager.h"
#include "base/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "mojo/public/cpp/bindings/remote.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/task_type.h"
#include "third_party/blink/public/platform/web_url_loader.h"
#include "third_party/blink/public/platform/web_url_loader_client.h"
#include "third_party/blink/public/platform/web_url_loader_factory.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/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/loader/link_header.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.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 WebURLLoader {
public:
PrefetchedSignedExchangeLoader(
const WebURLRequest& request,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: task_runner_(std::move(task_runner)) {
request_.CopyFrom(request);
}
~PrefetchedSignedExchangeLoader() override {}
base::WeakPtr<PrefetchedSignedExchangeLoader> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void SetURLLoader(std::unique_ptr<WebURLLoader> url_loader) {
DCHECK(!url_loader_);
url_loader_ = std::move(url_loader);
ExecutePendingMethodCalls();
}
const WebURLRequest& request() const { return request_; }
// WebURLLoader methods:
void LoadSynchronously(
std::unique_ptr<network::ResourceRequest> request,
scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
int requestor_id,
bool download_to_network_cache_only,
bool pass_response_pipe_to_client,
bool no_mime_sniffing,
base::TimeDelta timeout_interval,
WebURLLoaderClient* client,
WebURLResponse& response,
base::Optional<WebURLError>& error,
WebData& data,
int64_t& encoded_data_length,
int64_t& encoded_body_length,
WebBlobInfo& downloaded_blob) override {
NOTREACHED();
}
void LoadAsynchronously(
std::unique_ptr<network::ResourceRequest> request,
scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
int requestor_id,
bool download_to_network_cache_only,
bool no_mime_sniffing,
WebURLLoaderClient* client) override {
if (url_loader_) {
url_loader_->LoadAsynchronously(
std::move(request), std::move(request_extra_data), requestor_id,
download_to_network_cache_only, no_mime_sniffing, 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::Bind(
&PrefetchedSignedExchangeLoader::LoadAsynchronously, GetWeakPtr(),
std::move(request), std::move(request_extra_data), requestor_id,
download_to_network_cache_only, no_mime_sniffing,
WTF::Unretained(client)));
}
void SetDefersLoading(bool value) override {
if (url_loader_) {
url_loader_->SetDefersLoading(value);
return;
}
pending_method_calls_.push(
WTF::Bind(&PrefetchedSignedExchangeLoader::SetDefersLoading,
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::Bind(&PrefetchedSignedExchangeLoader::DidChangePriority,
GetWeakPtr(), new_priority, intra_priority_value));
}
scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner() 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();
}
}
WebURLRequest request_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
std::unique_ptr<WebURLLoader> url_loader_;
std::queue<base::OnceClosure> pending_method_calls_;
base::WeakPtrFactory<PrefetchedSignedExchangeLoader> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(PrefetchedSignedExchangeLoader);
};
// 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)) {
}
PrefetchedSignedExchangeManager::~PrefetchedSignedExchangeManager() {}
void PrefetchedSignedExchangeManager::Trace(Visitor* visitor) {
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<WebURLLoader>
PrefetchedSignedExchangeManager::MaybeCreateURLLoader(
const WebURLRequest& request) {
if (started_)
return nullptr;
const auto* matching_resource = alternative_resources_->FindMatchingEntry(
request.Url(), request.GetRequestContext(),
frame_->DomWindow()->navigator()->languages());
if (!matching_resource)
return nullptr;
std::unique_ptr<PrefetchedSignedExchangeLoader> loader =
std::make_unique<PrefetchedSignedExchangeLoader>(
request, frame_->GetFrameScheduler()->GetTaskRunner(
TaskType::kInternalLoading));
loaders_.emplace_back(loader->GetWeakPtr());
return loader;
}
std::unique_ptr<WebURLLoader>
PrefetchedSignedExchangeManager::CreateDefaultURLLoader(
const WebURLRequest& request) {
return frame_->GetURLLoaderFactory()->CreateURLLoader(
request,
frame_->GetFrameScheduler()->CreateResourceLoadingTaskRunnerHandle());
}
std::unique_ptr<WebURLLoader>
PrefetchedSignedExchangeManager::CreatePrefetchedSignedExchangeURLLoader(
const WebURLRequest& request,
mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
loader_factory) {
return Platform::Current()
->WrapURLLoaderFactory(loader_factory.PassPipe())
->CreateURLLoader(
request,
frame_->GetFrameScheduler()->CreateResourceLoadingTaskRunnerHandle());
}
void PrefetchedSignedExchangeManager::TriggerLoad() {
Vector<WebNavigationParams::PrefetchedSignedExchange*>
maching_prefetched_exchanges;
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(
loader->request().Url(), loader->request().GetRequestContext(),
frame_->DomWindow()->navigator()->languages());
const auto alternative_url = matching_resource->alternative_url();
if (!alternative_url.IsValid()) {
// There is 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()) {
// There is no matching prefetched exchange.
break;
}
if (String(exchange_it->value->header_integrity) !=
matching_resource->header_integrity()) {
// The header integrity doesn't match.
break;
}
if (KURL(exchange_it->value->inner_url) !=
matching_resource->anchor_url()) {
// The 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()));
}
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(
mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>(
std::move(prefetched_exchange->loader_factory_handle),
network::mojom::URLLoaderFactory::Version_));
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_handle =
loader_factory_clone.PassPipe();
loader->SetURLLoader(CreatePrefetchedSignedExchangeURLLoader(
loader->request(), loader_factory.Unbind()));
}
}
} // namespace blink