Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Topics] add topics URLLoader/Service for fetch()
Introduce BrowsingTopicsURLLoader & Service to intercept fetch(<url>, {browsingTopics: true}) request in the browser process. PR: patcg-individual-drafts/topics#81 Design doc: https://docs.google.com/document/d/1bBuaJSIhfm-r51BDG8v_0UJnnS2yk7_b6IzAE0jPpTo/edit?resourcekey=0-_JQTIIxqAzoZ4nW_j3yoig#heading=h.7cfn3y7i0qjn POC code: https://crrev.com/c/3978874 Bug: 1378433 Change-Id: Ifb8d4ca63dcdee1f1c0025f9530dab7b3373a8b1 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4000440 Commit-Queue: Yao Xiao <yaoxia@chromium.org> Reviewed-by: Matt Menke <mmenke@chromium.org> Reviewed-by: Josh Karlin <jkarlin@chromium.org> Cr-Commit-Position: refs/heads/main@{#1074178}
- Loading branch information
1 parent
e4f0a2e
commit d251109
Showing
9 changed files
with
1,555 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
241 changes: 241 additions & 0 deletions
241
content/browser/browsing_topics/browsing_topics_url_loader.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
// Copyright 2022 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#include "content/browser/browsing_topics/browsing_topics_url_loader.h" | ||
|
||
#include "base/bind.h" | ||
#include "content/browser/browsing_topics/header_util.h" | ||
#include "content/browser/renderer_host/render_frame_host_impl.h" | ||
#include "content/public/browser/content_browser_client.h" | ||
#include "content/public/browser/page.h" | ||
#include "content/public/common/content_client.h" | ||
#include "content/public/common/content_features.h" | ||
#include "services/network/public/cpp/features.h" | ||
#include "services/network/public/cpp/is_potentially_trustworthy.h" | ||
#include "services/network/public/cpp/shared_url_loader_factory.h" | ||
#include "services/network/public/mojom/early_hints.mojom.h" | ||
#include "third_party/blink/public/common/features.h" | ||
#include "third_party/blink/public/common/frame/frame_policy.h" | ||
#include "third_party/blink/public/mojom/browsing_topics/browsing_topics.mojom.h" | ||
|
||
namespace content { | ||
|
||
namespace { | ||
|
||
bool GetTopicsHeaderValueForSubresourceRequest( | ||
WeakDocumentPtr request_initiator_document, | ||
const GURL& url, | ||
std::string& header_value) { | ||
DCHECK(header_value.empty()); | ||
|
||
// Due to the race between the subresource requests and navigations, this | ||
// request may arrive before the commit confirmation is received (i.e. | ||
// NavigationRequest::DidCommitNavigation()), or after the document is | ||
// destroyed. We consider those cases to be ineligible for topics. | ||
// | ||
// TODO(yaoxia): measure how often this happens. | ||
RenderFrameHost* request_initiator_frame = | ||
request_initiator_document.AsRenderFrameHostIfValid(); | ||
if (!request_initiator_frame) | ||
return false; | ||
|
||
// Fenced frames disallow most permissions policies which would let this | ||
// function return false regardless, but adding this check to be more | ||
// explicit. | ||
if (request_initiator_frame->IsNestedWithinFencedFrame()) | ||
return false; | ||
|
||
if (!request_initiator_frame->GetPage().IsPrimary()) | ||
return false; | ||
|
||
// TODO(crbug.com/1244137): IsPrimary() doesn't actually detect portals yet. | ||
// Remove this when it does. | ||
if (!static_cast<RenderFrameHostImpl*>(request_initiator_frame) | ||
->IsOutermostMainFrame()) { | ||
return false; | ||
} | ||
|
||
url::Origin origin = url::Origin::Create(url); | ||
if (origin.opaque()) | ||
return false; | ||
|
||
// TODO(yaoxia): should this be `ReportBadMessage`? On the renderer side, the | ||
// fetch initiator context must be secure. Does it imply that the requested | ||
// `origin` is always potentially trustworthy? | ||
if (!network::IsOriginPotentiallyTrustworthy(origin)) | ||
return false; | ||
|
||
const blink::PermissionsPolicy* permissions_policy = | ||
static_cast<RenderFrameHostImpl*>(request_initiator_frame) | ||
->permissions_policy(); | ||
|
||
if (!permissions_policy->IsFeatureEnabledForOrigin( | ||
blink::mojom::PermissionsPolicyFeature::kBrowsingTopics, origin) || | ||
!permissions_policy->IsFeatureEnabledForOrigin( | ||
blink::mojom::PermissionsPolicyFeature:: | ||
kBrowsingTopicsBackwardCompatible, | ||
origin)) { | ||
return false; | ||
} | ||
|
||
std::vector<blink::mojom::EpochTopicPtr> topics; | ||
bool topics_eligible = GetContentClient()->browser()->HandleTopicsWebApi( | ||
origin, request_initiator_frame->GetMainFrame(), | ||
browsing_topics::ApiCallerSource::kFetch, | ||
/*get_topics=*/true, | ||
/*observe=*/false, topics); | ||
|
||
if (topics_eligible) | ||
header_value = DeriveTopicsHeaderValue(topics); | ||
|
||
return topics_eligible; | ||
} | ||
|
||
void ProcessResponseHeaders(const net::HttpResponseHeaders* response_headers, | ||
WeakDocumentPtr document, | ||
const GURL& url) { | ||
if (!response_headers) | ||
return; | ||
|
||
RenderFrameHost* rfh = document.AsRenderFrameHostIfValid(); | ||
if (!rfh) | ||
return; | ||
|
||
HandleTopicsEligibleResponse(*response_headers, url::Origin::Create(url), | ||
*rfh, browsing_topics::ApiCallerSource::kFetch); | ||
} | ||
|
||
} // namespace | ||
|
||
BrowsingTopicsURLLoader::BrowsingTopicsURLLoader( | ||
WeakDocumentPtr document, | ||
int32_t request_id, | ||
uint32_t options, | ||
const network::ResourceRequest& resource_request, | ||
mojo::PendingRemote<network::mojom::URLLoaderClient> client, | ||
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, | ||
scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory) | ||
: document_(std::move(document)), | ||
url_(resource_request.url), | ||
forwarding_client_(std::move(client)) { | ||
DCHECK(network_loader_factory); | ||
|
||
network::ResourceRequest new_resource_request = resource_request; | ||
|
||
std::string header_value; | ||
topics_eligible_ = GetTopicsHeaderValueForSubresourceRequest( | ||
document_, new_resource_request.url, header_value); | ||
|
||
if (topics_eligible_) { | ||
new_resource_request.headers.SetHeader(kBrowsingTopicsRequestHeaderKey, | ||
header_value); | ||
} | ||
|
||
network_loader_factory->CreateLoaderAndStart( | ||
loader_.BindNewPipeAndPassReceiver(), request_id, options, | ||
new_resource_request, client_receiver_.BindNewPipeAndPassRemote(), | ||
traffic_annotation); | ||
|
||
client_receiver_.set_disconnect_handler( | ||
base::BindOnce(&BrowsingTopicsURLLoader::OnNetworkConnectionError, | ||
base::Unretained(this))); | ||
} | ||
|
||
BrowsingTopicsURLLoader::~BrowsingTopicsURLLoader() = default; | ||
|
||
void BrowsingTopicsURLLoader::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) { | ||
if (new_url) | ||
url_ = new_url.value(); | ||
|
||
std::vector<std::string> new_removed_headers = removed_headers; | ||
net::HttpRequestHeaders new_modified_headers = modified_headers; | ||
|
||
new_removed_headers.push_back(kBrowsingTopicsRequestHeaderKey); | ||
|
||
std::string header_value; | ||
topics_eligible_ = | ||
GetTopicsHeaderValueForSubresourceRequest(document_, url_, header_value); | ||
|
||
if (topics_eligible_) { | ||
new_modified_headers.SetHeader(kBrowsingTopicsRequestHeaderKey, | ||
header_value); | ||
} | ||
|
||
loader_->FollowRedirect(new_removed_headers, new_modified_headers, | ||
modified_cors_exempt_headers, new_url); | ||
} | ||
|
||
void BrowsingTopicsURLLoader::SetPriority(net::RequestPriority priority, | ||
int intra_priority_value) { | ||
loader_->SetPriority(priority, intra_priority_value); | ||
} | ||
|
||
void BrowsingTopicsURLLoader::PauseReadingBodyFromNet() { | ||
loader_->PauseReadingBodyFromNet(); | ||
} | ||
|
||
void BrowsingTopicsURLLoader::ResumeReadingBodyFromNet() { | ||
loader_->ResumeReadingBodyFromNet(); | ||
} | ||
|
||
void BrowsingTopicsURLLoader::OnReceiveEarlyHints( | ||
network::mojom::EarlyHintsPtr early_hints) { | ||
forwarding_client_->OnReceiveEarlyHints(std::move(early_hints)); | ||
} | ||
|
||
void BrowsingTopicsURLLoader::OnReceiveResponse( | ||
network::mojom::URLResponseHeadPtr head, | ||
mojo::ScopedDataPipeConsumerHandle body, | ||
absl::optional<mojo_base::BigBuffer> cached_metadata) { | ||
if (topics_eligible_) { | ||
ProcessResponseHeaders(head->headers.get(), document_, url_); | ||
topics_eligible_ = false; | ||
} | ||
|
||
forwarding_client_->OnReceiveResponse(std::move(head), std::move(body), | ||
std::move(cached_metadata)); | ||
} | ||
|
||
void BrowsingTopicsURLLoader::OnReceiveRedirect( | ||
const net::RedirectInfo& redirect_info, | ||
network::mojom::URLResponseHeadPtr head) { | ||
if (topics_eligible_) { | ||
ProcessResponseHeaders(head->headers.get(), document_, url_); | ||
topics_eligible_ = false; | ||
} | ||
|
||
url_ = redirect_info.new_url; | ||
|
||
forwarding_client_->OnReceiveRedirect(redirect_info, std::move(head)); | ||
} | ||
|
||
void BrowsingTopicsURLLoader::OnUploadProgress( | ||
int64_t current_position, | ||
int64_t total_size, | ||
base::OnceCallback<void()> callback) { | ||
forwarding_client_->OnUploadProgress(current_position, total_size, | ||
std::move(callback)); | ||
} | ||
|
||
void BrowsingTopicsURLLoader::OnTransferSizeUpdated( | ||
int32_t transfer_size_diff) { | ||
forwarding_client_->OnTransferSizeUpdated(transfer_size_diff); | ||
} | ||
|
||
void BrowsingTopicsURLLoader::OnComplete( | ||
const network::URLLoaderCompletionStatus& status) { | ||
forwarding_client_->OnComplete(status); | ||
} | ||
|
||
void BrowsingTopicsURLLoader::OnNetworkConnectionError() { | ||
// The network loader has an error; we should let the client know it's closed | ||
// by dropping this, which will in turn make this loader destroyed. | ||
forwarding_client_.reset(); | ||
} | ||
|
||
} // namespace content |
119 changes: 119 additions & 0 deletions
119
content/browser/browsing_topics/browsing_topics_url_loader.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// Copyright 2022 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef CONTENT_BROWSER_BROWSING_TOPICS_BROWSING_TOPICS_URL_LOADER_H_ | ||
#define CONTENT_BROWSER_BROWSING_TOPICS_BROWSING_TOPICS_URL_LOADER_H_ | ||
|
||
#include <memory> | ||
#include <string> | ||
|
||
#include "base/callback.h" | ||
#include "base/memory/weak_ptr.h" | ||
#include "content/public/browser/weak_document_ptr.h" | ||
#include "mojo/public/cpp/bindings/pending_remote.h" | ||
#include "mojo/public/cpp/bindings/receiver.h" | ||
#include "mojo/public/cpp/bindings/remote.h" | ||
#include "net/traffic_annotation/network_traffic_annotation.h" | ||
#include "services/network/public/cpp/resource_request.h" | ||
#include "services/network/public/mojom/url_loader.mojom.h" | ||
#include "services/network/public/mojom/url_response_head.mojom.h" | ||
#include "third_party/abseil-cpp/absl/types/optional.h" | ||
#include "url/gurl.h" | ||
|
||
namespace network { | ||
class SharedURLLoaderFactory; | ||
} | ||
|
||
namespace content { | ||
|
||
// A URLLoader for handling a topics request, including | ||
// fetch(<url>, {browsingTopics: true}). | ||
// | ||
// This loader works as follows: | ||
// 1. Before making a network request (i.e. BrowsingTopicsURLLoader()), if the | ||
// request is eligible for topics, calculates and adds the topics header. | ||
// Starts the request with `loader_`. | ||
// 2. For any redirect received (i.e. OnReceiveRedirect()), if the previous | ||
// request or redirect was eligible for topics, and if the response header | ||
// indicates an observation should be recorded, stores the observation. | ||
// Forwards the original response back to `forwarding_client_`. | ||
// 3. For any followed redirect (i.e. FollowRedirect()), if the redirect is | ||
// eligible for topics, calculates and adds/updates the topics header. | ||
// Forwards the updated redirect to `loader_`. | ||
// 4. For the last response (i.e. OnReceiveResponse()), if the previous | ||
// request or redirect was eligible for topics, and if the response header | ||
// indicates an observation should be recorded, stores the observation. | ||
// Forwards the original response (e.g. hands off fetching the body) back | ||
// to `forwarding_client_`. | ||
class CONTENT_EXPORT BrowsingTopicsURLLoader | ||
: public network::mojom::URLLoader, | ||
public network::mojom::URLLoaderClient { | ||
public: | ||
BrowsingTopicsURLLoader( | ||
WeakDocumentPtr document, | ||
int32_t request_id, | ||
uint32_t options, | ||
const network::ResourceRequest& resource_request, | ||
mojo::PendingRemote<network::mojom::URLLoaderClient> client, | ||
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, | ||
scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory); | ||
|
||
BrowsingTopicsURLLoader(const BrowsingTopicsURLLoader&) = delete; | ||
BrowsingTopicsURLLoader& operator=(const BrowsingTopicsURLLoader&) = delete; | ||
|
||
~BrowsingTopicsURLLoader() override; | ||
|
||
private: | ||
// network::mojom::URLLoader overrides: | ||
void 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) override; | ||
void SetPriority(net::RequestPriority priority, | ||
int intra_priority_value) override; | ||
void PauseReadingBodyFromNet() override; | ||
void ResumeReadingBodyFromNet() override; | ||
|
||
// network::mojom::URLLoaderClient overrides: | ||
void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override; | ||
void OnReceiveResponse( | ||
network::mojom::URLResponseHeadPtr head, | ||
mojo::ScopedDataPipeConsumerHandle body, | ||
absl::optional<mojo_base::BigBuffer> cached_metadata) override; | ||
void OnReceiveRedirect(const net::RedirectInfo& redirect_info, | ||
network::mojom::URLResponseHeadPtr head) override; | ||
void OnUploadProgress(int64_t current_position, | ||
int64_t total_size, | ||
base::OnceCallback<void()> callback) override; | ||
void OnTransferSizeUpdated(int32_t transfer_size_diff) override; | ||
void OnComplete(const network::URLLoaderCompletionStatus& status) override; | ||
|
||
void OnNetworkConnectionError(); | ||
|
||
// Upon NavigationRequest::DidCommitNavigation(), `document_` will be set to | ||
// the document that this `BrowsingTopicsURLLoader` is associated with. It | ||
// will become null whenever the document navigates away. | ||
WeakDocumentPtr document_; | ||
|
||
// The current request or redirect URL. | ||
GURL url_; | ||
|
||
// Whether the ongoing request or redirect is eligible for topics. Set to the | ||
// desired state when a request/redirect is made. Reset to false when the | ||
// corresponding response is received. | ||
bool topics_eligible_ = false; | ||
|
||
// For the actual request. | ||
mojo::Remote<network::mojom::URLLoader> loader_; | ||
|
||
// The client to forward the response to. | ||
mojo::Remote<network::mojom::URLLoaderClient> forwarding_client_; | ||
|
||
mojo::Receiver<network::mojom::URLLoaderClient> client_receiver_{this}; | ||
}; | ||
|
||
} // namespace content | ||
|
||
#endif // CONTENT_BROWSER_BROWSING_TOPICS_BROWSING_TOPICS_URL_LOADER_H_ |
Oops, something went wrong.