diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc index cbc87162085112..afd924f842dc1a 100644 --- a/content/browser/frame_host/navigation_request.cc +++ b/content/browser/frame_host/navigation_request.cc @@ -2037,6 +2037,13 @@ void NavigationRequest::CommitNavigation() { render_frame_host_->GetProcess()->GetID(), render_frame_host_->GetRoutingID(), &service_worker_provider_info); } + if (subresource_loader_params_ && + !subresource_loader_params_->prefetched_signed_exchanges.empty()) { + DCHECK(base::FeatureList::IsEnabled( + features::kSignedExchangeSubresourcePrefetch)); + commit_params_.prefetched_signed_exchanges = + std::move(subresource_loader_params_->prefetched_signed_exchanges); + } render_frame_host_->CommitNavigation( this, response_.get(), std::move(url_loader_client_endpoints_), common_params_, commit_params_, is_view_source_, diff --git a/content/browser/frame_host/navigation_request.h b/content/browser/frame_host/navigation_request.h index fe06f014641947..df85f2d6f7d4b6 100644 --- a/content/browser/frame_host/navigation_request.h +++ b/content/browser/frame_host/navigation_request.h @@ -753,7 +753,8 @@ class CONTENT_EXPORT NavigationRequest : public NavigationURLLoaderDelegate, base::Closure on_start_checks_complete_closure_; // Used in the network service world to pass the subressource loader params - // to the renderer. Used by AppCache and ServiceWorker. + // to the renderer. Used by AppCache and ServiceWorker, and + // SignedExchangeSubresourcePrefetch. base::Optional subresource_loader_params_; // See comment on accessor. diff --git a/content/browser/loader/prefetched_signed_exchange_cache.cc b/content/browser/loader/prefetched_signed_exchange_cache.cc index 51e630e562f116..472f18ef8c59f4 100644 --- a/content/browser/loader/prefetched_signed_exchange_cache.cc +++ b/content/browser/loader/prefetched_signed_exchange_cache.cc @@ -208,15 +208,70 @@ class InnerResponseURLLoader : public network::mojom::URLLoader { DISALLOW_COPY_AND_ASSIGN(InnerResponseURLLoader); }; +// A URLLoaderFactory which handles a signed exchange subresource request from +// renderer process. +class SubresourceSignedExchangeURLLoaderFactory + : public network::mojom::URLLoaderFactory { + public: + SubresourceSignedExchangeURLLoaderFactory( + network::mojom::URLLoaderFactoryRequest request, + std::unique_ptr entry) + : entry_(std::move(entry)) { + bindings_.AddBinding(this, std::move(request)); + bindings_.set_connection_error_handler(base::BindRepeating( + &SubresourceSignedExchangeURLLoaderFactory::OnConnectionError, + base::Unretained(this))); + } + ~SubresourceSignedExchangeURLLoaderFactory() override {} + + // network::mojom::URLLoaderFactory implementation. + void CreateLoaderAndStart(network::mojom::URLLoaderRequest loader, + int32_t routing_id, + int32_t request_id, + uint32_t options, + const network::ResourceRequest& request, + network::mojom::URLLoaderClientPtr client, + const net::MutableNetworkTrafficAnnotationTag& + traffic_annotation) override { + // TODO(crbug.com/935267): Implement CORS check. + DCHECK_EQ(request.url, entry_->inner_url()); + mojo::MakeStrongBinding( + std::make_unique( + *entry_->inner_response(), + std::make_unique( + *entry_->blob_data_handle()), + *entry_->completion_status(), std::move(client)), + std::move(loader)); + } + void Clone(network::mojom::URLLoaderFactoryRequest request) override { + bindings_.AddBinding(this, std::move(request)); + } + + private: + void OnConnectionError() { + if (!bindings_.empty()) + return; + delete this; + } + + std::unique_ptr entry_; + mojo::BindingSet bindings_; + + DISALLOW_COPY_AND_ASSIGN(SubresourceSignedExchangeURLLoaderFactory); +}; + // A NavigationLoaderInterceptor which handles a request which matches the // prefetched signed exchange that has been stored to a // PrefetchedSignedExchangeCache. class PrefetchedNavigationLoaderInterceptor : public NavigationLoaderInterceptor { public: - explicit PrefetchedNavigationLoaderInterceptor( - std::unique_ptr exchange) - : exchange_(std::move(exchange)), weak_factory_(this) {} + PrefetchedNavigationLoaderInterceptor( + std::unique_ptr exchange, + std::vector info_list) + : exchange_(std::move(exchange)), + info_list_(std::move(info_list)), + weak_factory_(this) {} ~PrefetchedNavigationLoaderInterceptor() override {} @@ -229,15 +284,17 @@ class PrefetchedNavigationLoaderInterceptor // header (eg: HttpVaryData::MatchesRequest()) and Cache-Control header. // And also we shuold check the expires parameter of the signed exchange's // signature. TODO(crbug.com/935267): Implement these checking logic. - if (!outer_request_handled_ && + if (state_ == State::kInitial && tentative_resource_request.url == exchange_->outer_url()) { - outer_request_handled_ = true; + state_ = State::kOuterRequestRequested; std::move(callback).Run(base::BindOnce( &PrefetchedNavigationLoaderInterceptor::StartRedirectResponse, weak_factory_.GetWeakPtr())); return; } if (tentative_resource_request.url == exchange_->inner_url()) { + DCHECK_EQ(State::kOuterRequestRequested, state_); + state_ = State::kInnerResponseRequested; std::move(callback).Run(base::BindOnce( &PrefetchedNavigationLoaderInterceptor::StartInnerResponse, weak_factory_.GetWeakPtr())); @@ -248,12 +305,21 @@ class PrefetchedNavigationLoaderInterceptor base::Optional MaybeCreateSubresourceLoaderParams() override { - // TODO(crbug.com/935267): Implement this to pass the prefetched signed - // exchanges of subresources to the renderer process. - return base::nullopt; + if (state_ != State::kInnerResponseRequested) + return base::nullopt; + + SubresourceLoaderParams params; + params.prefetched_signed_exchanges = std::move(info_list_); + return base::make_optional(std::move(params)); } private: + enum class State { + kInitial, + kOuterRequestRequested, + kInnerResponseRequested + }; + void StartRedirectResponse(const network::ResourceRequest& resource_request, network::mojom::URLLoaderRequest request, network::mojom::URLLoaderClientPtr client) { @@ -263,6 +329,7 @@ class PrefetchedNavigationLoaderInterceptor *exchange_->outer_response(), std::move(client)), std::move(request)); } + void StartInnerResponse(const network::ResourceRequest& resource_request, network::mojom::URLLoaderRequest request, network::mojom::URLLoaderClientPtr client) { @@ -275,9 +342,9 @@ class PrefetchedNavigationLoaderInterceptor std::move(request)); } + State state_ = State::kInitial; std::unique_ptr exchange_; - - bool outer_request_handled_ = false; + std::vector info_list_; base::WeakPtrFactory weak_factory_; @@ -374,7 +441,24 @@ PrefetchedSignedExchangeCache::MaybeCreateInterceptor(const GURL& outer_url) { if (it == exchanges_.end()) return nullptr; return std::make_unique( - it->second->Clone()); + it->second->Clone(), GetInfoList()); +} + +std::vector +PrefetchedSignedExchangeCache::GetInfoList() const { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + std::vector info_list; + + for (const auto& exchange : exchanges_) { + network::mojom::URLLoaderFactoryPtrInfo loader_factory_info; + new SubresourceSignedExchangeURLLoaderFactory( + mojo::MakeRequest(&loader_factory_info), exchange.second->Clone()); + info_list.emplace_back( + exchange.second->outer_url(), *exchange.second->header_integrity(), + exchange.second->inner_url(), *exchange.second->inner_response(), + std::move(loader_factory_info).PassHandle().release()); + } + return info_list; } } // namespace content diff --git a/content/browser/loader/prefetched_signed_exchange_cache.h b/content/browser/loader/prefetched_signed_exchange_cache.h index 7f9e1ecdb07b92..28576709b15bbc 100644 --- a/content/browser/loader/prefetched_signed_exchange_cache.h +++ b/content/browser/loader/prefetched_signed_exchange_cache.h @@ -9,6 +9,7 @@ #include "base/memory/ref_counted.h" #include "content/common/content_export.h" +#include "content/common/prefetched_signed_exchange_info.h" #include "net/base/hash_value.h" #include "services/network/public/cpp/resource_response.h" #include "services/network/public/cpp/url_loader_completion_status.h" @@ -108,6 +109,7 @@ class CONTENT_EXPORT PrefetchedSignedExchangeCache using EntryMap = std::map>; ~PrefetchedSignedExchangeCache(); + std::vector GetInfoList() const; EntryMap exchanges_; diff --git a/content/browser/navigation_subresource_loader_params.cc b/content/browser/navigation_subresource_loader_params.cc index fb7bef7573e19b..67223dbb98b4ba 100644 --- a/content/browser/navigation_subresource_loader_params.cc +++ b/content/browser/navigation_subresource_loader_params.cc @@ -21,6 +21,7 @@ SubresourceLoaderParams& SubresourceLoaderParams::operator=( std::move(other.controller_service_worker_info); controller_service_worker_object_host = other.controller_service_worker_object_host; + prefetched_signed_exchanges = std::move(other.prefetched_signed_exchanges); return *this; } diff --git a/content/browser/navigation_subresource_loader_params.h b/content/browser/navigation_subresource_loader_params.h index 7e20d0e75ea32e..7011f3bdedd06a 100644 --- a/content/browser/navigation_subresource_loader_params.h +++ b/content/browser/navigation_subresource_loader_params.h @@ -7,6 +7,7 @@ #include "base/memory/weak_ptr.h" #include "content/common/content_export.h" +#include "content/common/prefetched_signed_exchange_info.h" #include "services/network/public/mojom/url_loader_factory.mojom.h" #include "third_party/blink/public/mojom/service_worker/controller_service_worker.mojom.h" @@ -41,6 +42,14 @@ struct CONTENT_EXPORT SubresourceLoaderParams { // ServiceWorkerObjectHost::CreateIncompleteObjectInfo() for details. blink::mojom::ControllerServiceWorkerInfoPtr controller_service_worker_info; base::WeakPtr controller_service_worker_object_host; + + // For SignedExchangeSubresourcePrefetch. + // When signed exchanges were prefetched in the previous page and were stored + // to the PrefetchedSignedExchangeCache, and the main resource for the + // navigation was served from the cache, |prefetched_signed_exchanges| + // contains the all prefetched signed exchanges and they will be passed to the + // renderer. + std::vector prefetched_signed_exchanges; }; } // namespace content diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn index b9203908c1e6bd..0b464d47a336c4 100644 --- a/content/common/BUILD.gn +++ b/content/common/BUILD.gn @@ -209,6 +209,8 @@ source_set("common") { "possibly_associated_interface_ptr.h", "possibly_associated_interface_ptr_info.h", "possibly_associated_wrapper_shared_url_loader_factory.h", + "prefetched_signed_exchange_info.cc", + "prefetched_signed_exchange_info.h", "process_type.cc", "render_widget_surface_properties.cc", "render_widget_surface_properties.h", diff --git a/content/common/content_param_traits.cc b/content/common/content_param_traits.cc index 1dc33c2955e15c..5b67990bd0552b 100644 --- a/content/common/content_param_traits.cc +++ b/content/common/content_param_traits.cc @@ -512,6 +512,33 @@ void ParamTraits::Log(const param_type& p, std::string* l) { l->append(")"); } +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + m->WriteData(reinterpret_cast(p.data), sizeof(p.data)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char* data; + int data_length; + if (!iter->ReadData(&data, &data_length)) { + NOTREACHED(); + return false; + } + if (data_length != sizeof(r->data)) { + NOTREACHED(); + return false; + } + memcpy(r->data, data, sizeof(r->data)); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append(""); +} + } // namespace IPC // Generate param traits write methods. diff --git a/content/common/content_param_traits.h b/content/common/content_param_traits.h index e29eff6871daad..e66ca9f7243831 100644 --- a/content/common/content_param_traits.h +++ b/content/common/content_param_traits.h @@ -17,6 +17,7 @@ #include "content/common/content_param_traits_macros.h" #include "content/common/cursors/webcursor.h" #include "ipc/ipc_mojo_param_traits.h" +#include "net/base/hash_value.h" #include "storage/common/blob_storage/blob_handle.h" #include "third_party/blink/public/platform/web_input_event.h" #include "ui/accessibility/ax_mode.h" @@ -175,6 +176,16 @@ struct CONTENT_EXPORT ParamTraits { static void Log(const param_type& p, std::string* l); }; +template <> +struct CONTENT_EXPORT ParamTraits { + typedef net::SHA256HashValue param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + } // namespace IPC #endif // CONTENT_COMMON_CONTENT_PARAM_TRAITS_H_ diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h index 99460d5a74ee65..93080817230a73 100644 --- a/content/common/frame_messages.h +++ b/content/common/frame_messages.h @@ -477,6 +477,14 @@ IPC_STRUCT_TRAITS_BEGIN(content::InitiatorCSPInfo) IPC_STRUCT_TRAITS_MEMBER(initiator_self_source) IPC_STRUCT_TRAITS_END() +IPC_STRUCT_TRAITS_BEGIN(content::PrefetchedSignedExchangeInfo) + IPC_STRUCT_TRAITS_MEMBER(outer_url) + IPC_STRUCT_TRAITS_MEMBER(header_integrity) + IPC_STRUCT_TRAITS_MEMBER(inner_url) + IPC_STRUCT_TRAITS_MEMBER(inner_response) + IPC_STRUCT_TRAITS_MEMBER(loader_factory_handle) +IPC_STRUCT_TRAITS_END() + IPC_STRUCT_TRAITS_BEGIN(content::CommonNavigationParams) IPC_STRUCT_TRAITS_MEMBER(url) IPC_STRUCT_TRAITS_MEMBER(initiator_origin) @@ -531,6 +539,7 @@ IPC_STRUCT_TRAITS_BEGIN(content::CommitNavigationParams) IPC_STRUCT_TRAITS_MEMBER(appcache_host_id) IPC_STRUCT_TRAITS_MEMBER(was_activated) IPC_STRUCT_TRAITS_MEMBER(navigation_token) + IPC_STRUCT_TRAITS_MEMBER(prefetched_signed_exchanges) #if defined(OS_ANDROID) IPC_STRUCT_TRAITS_MEMBER(data_url_as_string) #endif diff --git a/content/common/navigation_params.h b/content/common/navigation_params.h index a02fd14091dd91..7db27078527343 100644 --- a/content/common/navigation_params.h +++ b/content/common/navigation_params.h @@ -19,6 +19,7 @@ #include "content/common/content_security_policy/content_security_policy.h" #include "content/common/content_security_policy/csp_disposition_enum.h" #include "content/common/frame_message_enums.h" +#include "content/common/prefetched_signed_exchange_info.h" #include "content/common/service_worker/service_worker_types.h" #include "content/public/common/navigation_policy.h" #include "content/public/common/page_state.h" @@ -332,6 +333,10 @@ struct CONTENT_EXPORT CommitNavigationParams { // same-document browser-initiated navigations are properly handled as well. base::UnguessableToken navigation_token; + // Prefetched signed exchanges. Used when SignedExchangeSubresourcePrefetch + // feature is enabled. + std::vector prefetched_signed_exchanges; + #if defined(OS_ANDROID) // The real content of the data: URL. Only used in Android WebView for // implementing LoadDataWithBaseUrl API method to circumvent the restriction diff --git a/content/common/prefetched_signed_exchange_info.cc b/content/common/prefetched_signed_exchange_info.cc new file mode 100644 index 00000000000000..2f6fdcae64ee8a --- /dev/null +++ b/content/common/prefetched_signed_exchange_info.cc @@ -0,0 +1,25 @@ +// 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/common/prefetched_signed_exchange_info.h" + +namespace content { + +PrefetchedSignedExchangeInfo::PrefetchedSignedExchangeInfo() = default; +PrefetchedSignedExchangeInfo::PrefetchedSignedExchangeInfo( + const PrefetchedSignedExchangeInfo&) = default; +PrefetchedSignedExchangeInfo::PrefetchedSignedExchangeInfo( + const GURL& outer_url, + const net::SHA256HashValue& header_integrity, + const GURL& inner_url, + const network::ResourceResponseHead& inner_response, + mojo::MessagePipeHandle loader_factory_handle) + : outer_url(outer_url), + header_integrity(header_integrity), + inner_url(inner_url), + inner_response(inner_response), + loader_factory_handle(loader_factory_handle) {} +PrefetchedSignedExchangeInfo::~PrefetchedSignedExchangeInfo() = default; + +} // namespace content diff --git a/content/common/prefetched_signed_exchange_info.h b/content/common/prefetched_signed_exchange_info.h new file mode 100644 index 00000000000000..3555c09e7163cd --- /dev/null +++ b/content/common/prefetched_signed_exchange_info.h @@ -0,0 +1,40 @@ +// 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. + +#ifndef CONTENT_COMMON_PREFETCHED_SIGNED_EXCHANGE_INFO_H_ +#define CONTENT_COMMON_PREFETCHED_SIGNED_EXCHANGE_INFO_H_ + +#include "content/common/content_export.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "net/base/hash_value.h" +#include "services/network/public/cpp/resource_response.h" +#include "url/gurl.h" + +namespace content { + +// Used for SignedExchangeSubresourcePrefetch. +// This struct keeps the information about a prefetched signed exchange. It is +// used in CommitNavigationParams to be passed from the browser process to the +// renderer process in CommitNavigation IPC. +struct CONTENT_EXPORT PrefetchedSignedExchangeInfo { + PrefetchedSignedExchangeInfo(); + PrefetchedSignedExchangeInfo(const PrefetchedSignedExchangeInfo&); + PrefetchedSignedExchangeInfo( + const GURL& outer_url, + const net::SHA256HashValue& header_integrity, + const GURL& inner_url, + const network::ResourceResponseHead& inner_response, + mojo::MessagePipeHandle loader_factory_handle); + ~PrefetchedSignedExchangeInfo(); + + GURL outer_url; + net::SHA256HashValue header_integrity; + GURL inner_url; + network::ResourceResponseHead inner_response; + mojo::MessagePipeHandle loader_factory_handle; +}; + +} // namespace content + +#endif // CONTENT_COMMON_PREFETCHED_SIGNED_EXCHANGE_INFO_H_ diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index 06919d0ef5ba58..b8106b9424b82e 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc @@ -489,6 +489,12 @@ void FillNavigationParamsRequest( } navigation_params->was_discarded = commit_params.was_discarded; + + if (!commit_params.prefetched_signed_exchanges.empty()) { + DCHECK(base::FeatureList::IsEnabled( + features::kSignedExchangeSubresourcePrefetch)); + // TODO(crbug.com/935267): Pass the prefetched signed exchanges to Blink. + } #if defined(OS_ANDROID) navigation_params->had_transient_activation = common_params.has_user_gesture; #endif