Skip to content

Commit

Permalink
[Topics] add topics URLLoader/Service for fetch()
Browse files Browse the repository at this point in the history
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
yaoxiachromium authored and Chromium LUCI CQ committed Nov 21, 2022
1 parent e4f0a2e commit d251109
Show file tree
Hide file tree
Showing 9 changed files with 1,555 additions and 3 deletions.
4 changes: 4 additions & 0 deletions content/browser/BUILD.gn
Expand Up @@ -641,6 +641,10 @@ source_set("browser") {
"browsing_topics/browsing_topics_site_data_manager_impl.h",
"browsing_topics/browsing_topics_site_data_storage.cc",
"browsing_topics/browsing_topics_site_data_storage.h",
"browsing_topics/browsing_topics_url_loader.cc",
"browsing_topics/browsing_topics_url_loader.h",
"browsing_topics/browsing_topics_url_loader_service.cc",
"browsing_topics/browsing_topics_url_loader_service.h",
"browsing_topics/header_util.cc",
"browsing_topics/header_util.h",
"buckets/bucket_context.h",
Expand Down
241 changes: 241 additions & 0 deletions content/browser/browsing_topics/browsing_topics_url_loader.cc
@@ -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 content/browser/browsing_topics/browsing_topics_url_loader.h
@@ -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_

0 comments on commit d251109

Please sign in to comment.