blob: 46f165180d29b2860dd765e66469d9f190139ebf [file]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_BROWSING_TOPICS_BROWSING_TOPICS_REDIRECT_OBSERVER_H_
#define COMPONENTS_BROWSING_TOPICS_BROWSING_TOPICS_REDIRECT_OBSERVER_H_
#include "components/browsing_topics/common/common_types.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
namespace browsing_topics {
// Topics API can be misused when a site gets topics and passes it to the next
// site in the URL param via client-side redirect, and repeating this process
// several times allows the cooperating sites to get all the user's topics upon
// a single page visit.
//
// This class tracks the topics usage in a chain of client-side redirects, by
// getting the previous page's redirect status, updating it, and initializing
// the next page with the updated status.
//
// The redirect chain we are tracking is the sequence of renderer, non-user
// initiated top-level navigations occurring in a single `WebContents`. If a
// navigation ends up in an existing page (in bfcache), then the page's redirect
// status won't be updated, as the page won't be able to learn new information
// (via URL params).
//
// Note that this doesn't perfectly match the misuse pattern:
// - False negative case: It doesn't link a popup page with the opener page.
// However, as Chrome blocks automated popups by default, we can overlook this
// exception.
// - False positive case: It may link two pages without direct navigation. For
// example, the user is on page X, a link is clicked and opens a popup page Y,
// and Y triggers an automated opener navigation that navigates X to Z. In
// this case, page X and Z are considered to be in the same redirect chain,
// although a gesture was involved in this process (from X to Y).
//
// While tracking the navigation initiator's context could help fix these
// issues, it would add complexity. We accept some inaccuracies in favor of a
// simpler approach.
class BrowsingTopicsRedirectObserver
: public content::WebContentsObserver,
public content::WebContentsUserData<BrowsingTopicsRedirectObserver> {
public:
static void MaybeCreateForWebContents(content::WebContents* web_contents);
explicit BrowsingTopicsRedirectObserver(content::WebContents* web_contents);
BrowsingTopicsRedirectObserver(const BrowsingTopicsRedirectObserver& other) =
delete;
BrowsingTopicsRedirectObserver& operator=(
const BrowsingTopicsRedirectObserver& other) = delete;
BrowsingTopicsRedirectObserver(BrowsingTopicsRedirectObserver&& other) =
delete;
BrowsingTopicsRedirectObserver& operator=(
BrowsingTopicsRedirectObserver&& other) = delete;
~BrowsingTopicsRedirectObserver() override;
private:
friend class content::WebContentsUserData<BrowsingTopicsRedirectObserver>;
struct PendingNavigationRedirectState {
PendingNavigationRedirectState(
std::set<HashedHost>
pending_navigation_redirect_hosts_with_topics_invoked,
ukm::SourceId source_id_before_redirects);
~PendingNavigationRedirectState();
PendingNavigationRedirectState(
const PendingNavigationRedirectState& other) = delete;
PendingNavigationRedirectState& operator=(
const PendingNavigationRedirectState& other) = delete;
PendingNavigationRedirectState(PendingNavigationRedirectState&& other);
PendingNavigationRedirectState& operator=(
PendingNavigationRedirectState&& other);
std::set<HashedHost> pending_navigation_redirect_hosts_with_topics_invoked;
ukm::SourceId source_id_before_redirects;
};
// WebContentsObserver:
void ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) override;
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
// For the ongoing main frame navigation(s), the redirect state to be
// initialized with for the new page. When a navigation reaches
// `ReadyToCommitNavigation` and if it's eligible for continued redirect
// tracking, an entry will be inserted into the map. In `DidFinishNavigation`
// when the navigation finishes, the entry will be removed from the map.
//
// To be on the safe side, we key the state by `NavigationHandle`, so it works
// even if multiple concurrent navigations reach the state between
// "ready to commit" and "commit". However, for the navigation type we are
// tracking (i.e. renderer-initiated, cross-document), we don't believe the
// race can happen today. Also, when a navigation finishes,
// `DidFinishNavigation` is guaranteed to be called, so the map won't grow
// unbounded.
std::map<raw_ptr<content::NavigationHandle>, PendingNavigationRedirectState>
pending_navigations_redirect_state_;
WEB_CONTENTS_USER_DATA_KEY_DECL();
};
} // namespace browsing_topics
#endif // COMPONENTS_BROWSING_TOPICS_BROWSING_TOPICS_REDIRECT_OBSERVER_H_