diff --git a/content/browser/btm/btm_bounce_detector.cc b/content/browser/btm/btm_bounce_detector.cc
index af8edf0f4a80e..36a8f951c357a 100644
--- a/content/browser/btm/btm_bounce_detector.cc
+++ b/content/browser/btm/btm_bounce_detector.cc
@@ -295,51 +295,6 @@ std::set<std::string> BtmRedirectContext::AllSitesWithUserActivationOrAuthn()
   return sites;
 }
 
-std::map<std::string, std::pair<GURL, bool>>
-BtmRedirectContext::GetRedirectHeuristicURLs(
-    const GURL& first_party_url,
-    base::optional_ref<std::set<std::string>> allowed_sites,
-    bool require_current_interaction) const {
-  std::map<std::string, std::pair<GURL, bool>>
-      sites_to_url_and_current_interaction;
-
-  std::set<std::string> sites_with_current_interaction =
-      AllSitesWithUserActivationOrAuthn();
-
-  const std::string& first_party_site = GetSiteForBtm(first_party_url);
-  for (const auto& redirect : redirects_) {
-    const GURL& url = redirect->redirecting_url.url;
-    const std::string& site = redirect->site;
-
-    // The redirect heuristic does not apply for first-party cookie access.
-    if (site == first_party_site) {
-      continue;
-    }
-
-    // Check the list of allowed sites, if provided.
-    if (allowed_sites.has_value() && !allowed_sites->contains(site)) {
-      continue;
-    }
-
-    // Check for a current interaction, if the flag requires it.
-    if (require_current_interaction &&
-        !sites_with_current_interaction.contains(site)) {
-      continue;
-    }
-
-    // Add the url to the map, but do not override a previous current
-    // interaction.
-    auto& [prev_url, had_current_interaction] =
-        sites_to_url_and_current_interaction[site];
-    if (prev_url.is_empty() || !had_current_interaction) {
-      prev_url = url;
-      had_current_interaction = sites_with_current_interaction.contains(site);
-    }
-  }
-
-  return sites_to_url_and_current_interaction;
-}
-
 base::span<const BtmRedirectInfoPtr>
 BtmRedirectContext::GetServerRedirectsSinceLastPrimaryPageChange() const {
   size_t index = size();
diff --git a/content/browser/btm/btm_bounce_detector.h b/content/browser/btm/btm_bounce_detector.h
index 22b46bf024f0a..a6c2b057ef15c 100644
--- a/content/browser/btm/btm_bounce_detector.h
+++ b/content/browser/btm/btm_bounce_detector.h
@@ -161,16 +161,6 @@ class CONTENT_EXPORT BtmRedirectContext {
   // Return all sites that had an interaction in the current redirect context.
   std::set<std::string> AllSitesWithUserActivationOrAuthn() const;
 
-  // Returns a map of (site, (url, has_current_interaction)) for all URLs in the
-  // current redirect chain that satisfy the redirect heuristic. This performs
-  // all checks except for the presence of a past interaction, which should be
-  // checked by the caller using the DIPS db. If `allowed_sites` is present,
-  // only sites in `allowed_sites` should be included.
-  std::map<std::string, std::pair<GURL, bool>> GetRedirectHeuristicURLs(
-      const GURL& first_party_url,
-      base::optional_ref<std::set<std::string>> allowed_sites,
-      bool require_current_interaction) const;
-
   // Returns the server redirects from the last navigation. Note that due to
   // limitations in C++ the BtmRedirectInfo objects are unavoidably mutable.
   // Clients must not modify them.
diff --git a/content/browser/btm/btm_bounce_detector_unittest.cc b/content/browser/btm/btm_bounce_detector_unittest.cc
index 441375c14ee63..2fef476c5b41b 100644
--- a/content/browser/btm/btm_bounce_detector_unittest.cc
+++ b/content/browser/btm/btm_bounce_detector_unittest.cc
@@ -1506,118 +1506,6 @@ TEST(BtmRedirectContextTest, AddLateCookieAccess) {
                         HasBtmDataAccessType(BtmDataAccessType::kRead))));
 }
 
-TEST(BtmRedirectContextTest, GetRedirectHeuristicURLs_NoRequirements) {
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeatureWithParameters(
-      content_settings::features::kTpcdHeuristicsGrants,
-      {{"TpcdRedirectHeuristicRequireABAFlow", "false"}});
-
-  UrlAndSourceId first_party_url = MakeUrlAndId("http://a.test/");
-  UrlAndSourceId current_interaction_url = MakeUrlAndId("http://b.test/");
-  GURL no_current_interaction_url("http://c.test/");
-
-  std::vector<ChainPair> chains;
-  BtmRedirectContext context(
-      base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
-      UrlAndSourceId(),
-      /*redirect_prefix_count=*/0);
-
-  context.AppendCommitted(first_party_url,
-                          {MakeServerRedirects({"http://c.test"})},
-                          current_interaction_url, false);
-  context.AppendCommitted(
-      MakeClientRedirect("http://b.test/", BtmDataAccessType::kNone,
-                         /*has_sticky_activation=*/true),
-      {}, first_party_url, false);
-
-  ASSERT_EQ(context.size(), 2u);
-
-  std::map<std::string, std::pair<GURL, bool>>
-      sites_to_url_and_current_interaction = context.GetRedirectHeuristicURLs(
-          first_party_url.url, std::nullopt,
-          /*require_current_interaction=*/false);
-  EXPECT_THAT(
-      sites_to_url_and_current_interaction,
-      testing::UnorderedElementsAre(
-          std::pair<std::string, std::pair<GURL, bool>>(
-              "b.test", std::make_pair(current_interaction_url.url, true)),
-          std::pair<std::string, std::pair<GURL, bool>>(
-              "c.test", std::make_pair(no_current_interaction_url, false))));
-}
-
-TEST(BtmRedirectContextTest, GetRedirectHeuristicURLs_RequireABAFlow) {
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeatureWithParameters(
-      content_settings::features::kTpcdHeuristicsGrants,
-      {{"TpcdRedirectHeuristicRequireABAFlow", "true"}});
-
-  UrlAndSourceId first_party_url = MakeUrlAndId("http://a.test/");
-  GURL aba_url("http://b.test/");
-  GURL no_aba_url("http://c.test/");
-
-  std::vector<ChainPair> chains;
-  BtmRedirectContext context(
-      base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
-      UrlAndSourceId(),
-      /*redirect_prefix_count=*/0);
-
-  context.AppendCommitted(
-      first_party_url,
-      {MakeServerRedirects({"http://b.test", "http://c.test"})},
-      first_party_url, false);
-
-  ASSERT_EQ(context.size(), 2u);
-
-  std::set<std::string> allowed_sites = {GetSiteForBtm(aba_url)};
-
-  std::map<std::string, std::pair<GURL, bool>>
-      sites_to_url_and_current_interaction = context.GetRedirectHeuristicURLs(
-          first_party_url.url, allowed_sites,
-          /*require_current_interaction=*/false);
-  EXPECT_THAT(sites_to_url_and_current_interaction,
-              testing::UnorderedElementsAre(
-                  std::pair<std::string, std::pair<GURL, bool>>(
-                      "b.test", std::make_pair(aba_url, false))));
-}
-
-TEST(BtmRedirectContextTest,
-     GetRedirectHeuristicURLs_RequireCurrentInteraction) {
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeatureWithParameters(
-      content_settings::features::kTpcdHeuristicsGrants,
-      {{"TpcdRedirectHeuristicRequireABAFlow", "false"}});
-
-  UrlAndSourceId first_party_url = MakeUrlAndId("http://a.test/");
-  UrlAndSourceId current_interaction_url = MakeUrlAndId("http://b.test/");
-  GURL no_current_interaction_url("http://c.test/");
-
-  std::vector<ChainPair> chains;
-  BtmRedirectContext context(
-      base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
-      UrlAndSourceId(),
-      /*redirect_prefix_count=*/0);
-
-  context.AppendCommitted(first_party_url,
-                          {MakeServerRedirects({"http://c.test"})},
-                          current_interaction_url, false);
-  context.AppendCommitted(
-      MakeClientRedirect("http://b.test/", BtmDataAccessType::kNone,
-                         /*has_sticky_activation=*/false, true),
-      {}, first_party_url, false);
-
-  ASSERT_EQ(context.size(), 2u);
-
-  std::map<std::string, std::pair<GURL, bool>>
-      sites_to_url_and_current_interaction = context.GetRedirectHeuristicURLs(
-          first_party_url.url, std::nullopt,
-          /*require_current_interaction=*/true);
-  EXPECT_THAT(
-      sites_to_url_and_current_interaction,
-      testing::UnorderedElementsAre(
-          std::pair<std::string, std::pair<GURL, bool>>(
-              "b.test", std::make_pair(current_interaction_url.url, true))));
-}
-
 TEST(BtmRedirectContextTest,
      GetServerRedirectsSinceLastPrimaryPageChangeNoRedirects) {
   std::vector<ChainPair> chains;
diff --git a/content/browser/tpcd_heuristics/opener_heuristic_utils.cc b/content/browser/tpcd_heuristics/opener_heuristic_utils.cc
index 741d7d2a8a065..c50a7a8f8d7c2 100644
--- a/content/browser/tpcd_heuristics/opener_heuristic_utils.cc
+++ b/content/browser/tpcd_heuristics/opener_heuristic_utils.cc
@@ -4,6 +4,8 @@
 
 #include "content/browser/tpcd_heuristics/opener_heuristic_utils.h"
 
+#include "content/browser/btm/btm_bounce_detector.h"
+#include "content/public/browser/cookie_access_details.h"
 #include "services/network/public/cpp/features.h"
 #include "url/gurl.h"
 
@@ -26,4 +28,52 @@ OptionalBool IsAdTaggedCookieForHeuristics(const CookieAccessDetails& details) {
       net::CookieSettingOverride::kSkipTPCDHeuristicsGrant));
 }
 
+std::map<std::string, std::pair<GURL, bool>> GetRedirectHeuristicURLs(
+    const BtmRedirectContext& committed_redirect_context,
+    const GURL& first_party_url,
+    base::optional_ref<std::set<std::string>> allowed_sites,
+    bool require_current_interaction) {
+  std::map<std::string, std::pair<GURL, bool>>
+      sites_to_url_and_current_interaction;
+
+  std::set<std::string> sites_with_current_interaction =
+      committed_redirect_context.AllSitesWithUserActivationOrAuthn();
+
+  const std::string& first_party_site = GetSiteForBtm(first_party_url);
+  for (size_t redirect_index = 0;
+       redirect_index < committed_redirect_context.size(); redirect_index++) {
+    const BtmRedirectInfo& redirect =
+        committed_redirect_context[redirect_index];
+    const GURL& url = redirect.redirecting_url.url;
+    const std::string& site = redirect.site;
+
+    // The redirect heuristic does not apply for first-party cookie access.
+    if (site == first_party_site) {
+      continue;
+    }
+
+    // Check the list of allowed sites, if provided.
+    if (allowed_sites.has_value() && !allowed_sites->contains(site)) {
+      continue;
+    }
+
+    // Check for a current interaction, if the flag requires it.
+    if (require_current_interaction &&
+        !sites_with_current_interaction.contains(site)) {
+      continue;
+    }
+
+    // Add the url to the map, but do not override a previous current
+    // interaction.
+    auto& [prev_url, had_current_interaction] =
+        sites_to_url_and_current_interaction[site];
+    if (prev_url.is_empty() || !had_current_interaction) {
+      prev_url = url;
+      had_current_interaction = sites_with_current_interaction.contains(site);
+    }
+  }
+
+  return sites_to_url_and_current_interaction;
+}
+
 }  // namespace content
diff --git a/content/browser/tpcd_heuristics/opener_heuristic_utils.h b/content/browser/tpcd_heuristics/opener_heuristic_utils.h
index b319bfc842310..f4c344420f489 100644
--- a/content/browser/tpcd_heuristics/opener_heuristic_utils.h
+++ b/content/browser/tpcd_heuristics/opener_heuristic_utils.h
@@ -5,13 +5,18 @@
 #ifndef CONTENT_BROWSER_TPCD_HEURISTICS_OPENER_HEURISTIC_UTILS_H_
 #define CONTENT_BROWSER_TPCD_HEURISTICS_OPENER_HEURISTIC_UTILS_H_
 
+#include <map>
+
+#include "base/types/optional_ref.h"
 #include "content/common/content_export.h"
-#include "content/public/browser/cookie_access_details.h"
 
 class GURL;
 
 namespace content {
 
+class BtmRedirectContext;
+struct CookieAccessDetails;
+
 // Common identity providers that open pop-ups, to help estimate the impact of
 // third-party cookie blocking and prioritize mitigations. These values are
 // emitted in metrics and should not be renumbered.
@@ -40,6 +45,18 @@ inline OptionalBool ToOptionalBool(bool b) {
 CONTENT_EXPORT OptionalBool
 IsAdTaggedCookieForHeuristics(const CookieAccessDetails& details);
 
+// Returns a map of (site, (url, has_current_interaction)) for all URLs in the
+// current redirect chain that satisfy the redirect heuristic. This performs
+// all checks except for the presence of a past interaction, which should be
+// checked by the caller using the BTM database. If `allowed_sites` is present,
+// only sites in `allowed_sites` should be included.
+CONTENT_EXPORT std::map<std::string, std::pair<GURL, bool>>
+GetRedirectHeuristicURLs(
+    const BtmRedirectContext& committed_redirect_context,
+    const GURL& first_party_url,
+    base::optional_ref<std::set<std::string>> allowed_sites,
+    bool require_current_interaction);
+
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_TPCD_HEURISTICS_OPENER_HEURISTIC_UTILS_H_
diff --git a/content/browser/tpcd_heuristics/opener_heuristic_utils_unittest.cc b/content/browser/tpcd_heuristics/opener_heuristic_utils_unittest.cc
index 9d17268329e9f..2bc1eea2dcb9a 100644
--- a/content/browser/tpcd_heuristics/opener_heuristic_utils_unittest.cc
+++ b/content/browser/tpcd_heuristics/opener_heuristic_utils_unittest.cc
@@ -5,11 +5,68 @@
 #include "content/browser/tpcd_heuristics/opener_heuristic_utils.h"
 
 #include "base/test/scoped_feature_list.h"
+#include "components/content_settings/core/common/features.h"
+#include "content/browser/btm/btm_bounce_detector.h"
+#include "content/public/browser/btm_redirect_info.h"
 #include "content/public/browser/cookie_access_details.h"
+#include "net/http/http_status_code.h"
 #include "services/network/public/cpp/features.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
+namespace {
+
+using content::BtmDataAccessType;
+using content::BtmRedirectChainInfoPtr;
+using content::BtmRedirectInfo;
+using content::BtmRedirectInfoPtr;
+using content::UrlAndSourceId;
+
+UrlAndSourceId MakeUrlAndId(std::string_view url) {
+  return UrlAndSourceId(GURL(url), ukm::AssignNewSourceId());
+}
+
+using ChainPair =
+    std::pair<BtmRedirectChainInfoPtr, std::vector<BtmRedirectInfoPtr>>;
+
+void AppendChainPair(std::vector<ChainPair>& vec,
+                     std::vector<BtmRedirectInfoPtr> redirects,
+                     BtmRedirectChainInfoPtr chain) {
+  vec.emplace_back(std::move(chain), std::move(redirects));
+}
+
+std::vector<BtmRedirectInfoPtr> MakeServerRedirects(
+    std::vector<std::string> urls,
+    BtmDataAccessType access_type = BtmDataAccessType::kReadWrite) {
+  std::vector<BtmRedirectInfoPtr> redirects;
+  for (const auto& url : urls) {
+    redirects.push_back(BtmRedirectInfo::CreateForServer(
+        /*url=*/MakeUrlAndId(url),
+        /*access_type=*/access_type,
+        /*time=*/base::Time::Now(),
+        /*was_response_cached=*/false,
+        /*response_code=*/net::HTTP_FOUND,
+        /*server_bounce_delay=*/base::TimeDelta()));
+  }
+  return redirects;
+}
+
+BtmRedirectInfoPtr MakeClientRedirect(
+    std::string url,
+    BtmDataAccessType access_type = BtmDataAccessType::kReadWrite,
+    bool has_sticky_activation = false,
+    bool has_web_authn_assertion = false) {
+  return BtmRedirectInfo::CreateForClient(
+      /*url=*/MakeUrlAndId(url),
+      /*access_type=*/access_type,
+      /*time=*/base::Time::Now(),
+      /*client_bounce_delay=*/base::Seconds(1),
+      /*has_sticky_activation=*/has_sticky_activation,
+      /*web_authn_assertion_request_succeeded*/ has_web_authn_assertion);
+}
+}  // namespace
+
 namespace content {
 
 TEST(OpenerHeuristicUtilsTest, GetPopupProvider) {
@@ -67,4 +124,116 @@ TEST(IsAdTaggedCookieForHeuristics, ReturnsCorrectlyWithoutExperimentParam) {
   EXPECT_EQ(IsAdTaggedCookieForHeuristics(details), OptionalBool::kUnknown);
 }
 
+TEST(BtmRedirectContextTest, GetRedirectHeuristicURLs_NoRequirements) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeatureWithParameters(
+      content_settings::features::kTpcdHeuristicsGrants,
+      {{"TpcdRedirectHeuristicRequireABAFlow", "false"}});
+
+  UrlAndSourceId first_party_url = MakeUrlAndId("http://a.test/");
+  UrlAndSourceId current_interaction_url = MakeUrlAndId("http://b.test/");
+  GURL no_current_interaction_url("http://c.test/");
+
+  std::vector<ChainPair> chains;
+  BtmRedirectContext context(
+      base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
+      UrlAndSourceId(),
+      /*redirect_prefix_count=*/0);
+
+  context.AppendCommitted(first_party_url,
+                          {MakeServerRedirects({"http://c.test"})},
+                          current_interaction_url, false);
+  context.AppendCommitted(
+      MakeClientRedirect("http://b.test/", BtmDataAccessType::kNone,
+                         /*has_sticky_activation=*/true),
+      {}, first_party_url, false);
+
+  ASSERT_EQ(context.size(), 2u);
+
+  std::map<std::string, std::pair<GURL, bool>>
+      sites_to_url_and_current_interaction =
+          GetRedirectHeuristicURLs(context, first_party_url.url, std::nullopt,
+                                   /*require_current_interaction=*/false);
+  EXPECT_THAT(
+      sites_to_url_and_current_interaction,
+      testing::UnorderedElementsAre(
+          std::pair<std::string, std::pair<GURL, bool>>(
+              "b.test", std::make_pair(current_interaction_url.url, true)),
+          std::pair<std::string, std::pair<GURL, bool>>(
+              "c.test", std::make_pair(no_current_interaction_url, false))));
+}
+
+TEST(BtmRedirectContextTest, GetRedirectHeuristicURLs_RequireABAFlow) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeatureWithParameters(
+      content_settings::features::kTpcdHeuristicsGrants,
+      {{"TpcdRedirectHeuristicRequireABAFlow", "true"}});
+
+  UrlAndSourceId first_party_url = MakeUrlAndId("http://a.test/");
+  GURL aba_url("http://b.test/");
+  GURL no_aba_url("http://c.test/");
+
+  std::vector<ChainPair> chains;
+  BtmRedirectContext context(
+      base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
+      UrlAndSourceId(),
+      /*redirect_prefix_count=*/0);
+
+  context.AppendCommitted(
+      first_party_url,
+      {MakeServerRedirects({"http://b.test", "http://c.test"})},
+      first_party_url, false);
+
+  ASSERT_EQ(context.size(), 2u);
+
+  std::set<std::string> allowed_sites = {GetSiteForBtm(aba_url)};
+
+  std::map<std::string, std::pair<GURL, bool>>
+      sites_to_url_and_current_interaction =
+          GetRedirectHeuristicURLs(context, first_party_url.url, allowed_sites,
+                                   /*require_current_interaction=*/false);
+  EXPECT_THAT(sites_to_url_and_current_interaction,
+              testing::UnorderedElementsAre(
+                  std::pair<std::string, std::pair<GURL, bool>>(
+                      "b.test", std::make_pair(aba_url, false))));
+}
+
+TEST(BtmRedirectContextTest,
+     GetRedirectHeuristicURLs_RequireCurrentInteraction) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeatureWithParameters(
+      content_settings::features::kTpcdHeuristicsGrants,
+      {{"TpcdRedirectHeuristicRequireABAFlow", "false"}});
+
+  UrlAndSourceId first_party_url = MakeUrlAndId("http://a.test/");
+  UrlAndSourceId current_interaction_url = MakeUrlAndId("http://b.test/");
+  GURL no_current_interaction_url("http://c.test/");
+
+  std::vector<ChainPair> chains;
+  BtmRedirectContext context(
+      base::BindRepeating(AppendChainPair, std::ref(chains)), base::DoNothing(),
+      UrlAndSourceId(),
+      /*redirect_prefix_count=*/0);
+
+  context.AppendCommitted(first_party_url,
+                          {MakeServerRedirects({"http://c.test"})},
+                          current_interaction_url, false);
+  context.AppendCommitted(
+      MakeClientRedirect("http://b.test/", BtmDataAccessType::kNone,
+                         /*has_sticky_activation=*/false, true),
+      {}, first_party_url, false);
+
+  ASSERT_EQ(context.size(), 2u);
+
+  std::map<std::string, std::pair<GURL, bool>>
+      sites_to_url_and_current_interaction =
+          GetRedirectHeuristicURLs(context, first_party_url.url, std::nullopt,
+                                   /*require_current_interaction=*/true);
+  EXPECT_THAT(
+      sites_to_url_and_current_interaction,
+      testing::UnorderedElementsAre(
+          std::pair<std::string, std::pair<GURL, bool>>(
+              "b.test", std::make_pair(current_interaction_url.url, true))));
+}
+
 }  // namespace content
diff --git a/content/browser/tpcd_heuristics/redirect_heuristic_tab_helper.cc b/content/browser/tpcd_heuristics/redirect_heuristic_tab_helper.cc
index 3d72d9a72fa96..160b27b8df5b8 100644
--- a/content/browser/tpcd_heuristics/redirect_heuristic_tab_helper.cc
+++ b/content/browser/tpcd_heuristics/redirect_heuristic_tab_helper.cc
@@ -207,11 +207,11 @@ void RedirectHeuristicTabHelper::CreateAllRedirectHeuristicGrants(
   }
 
   std::map<std::string, std::pair<GURL, bool>>
-      sites_to_url_and_current_interaction =
-          detector_->CommittedRedirectContext().GetRedirectHeuristicURLs(
-              first_party_url, sites_with_aba_flow,
-              content_settings::features::
-                  kTpcdRedirectHeuristicRequireCurrentInteraction.Get());
+      sites_to_url_and_current_interaction = GetRedirectHeuristicURLs(
+          detector_->CommittedRedirectContext(), first_party_url,
+          sites_with_aba_flow,
+          content_settings::features::
+              kTpcdRedirectHeuristicRequireCurrentInteraction.Get());
 
   for (const auto& kv : sites_to_url_and_current_interaction) {
     auto [url, is_current_interaction] = kv.second;