CVE-2026-91713
Overview
Background
- `SupervisedUserNavigationObserver`
- the browser-process
WebContentsObserverthat stores supervised-user interstitials and implements theSupervisedUserCommandsmojo interface. - `GetInterstitialForFrame`
- helper that looks up the stored
SupervisedUserInterstitialfor the current mojo target frame by itsFrameTreeNodeId. - Back/Forward Cache (BFCache)
- a cache that keeps a navigated-away page’s
RenderFrameHostalive but inactive so it can later be restored.
Root Cause Analysis
fc1379decbca), which an audit found was a preparatory/helper change, not the fix. This page now points to the commit whose diff removes the bug.The SupervisedUserCommands handlers — GoBack (supervised_user_navigation_observer.cc:480), RequestUrlAccessRemote (:494), RequestUrlAccessLocal (:508), and LearnMore (:520) — all resolve their target through GetInterstitialForFrame() at :468-478. Pre-fix, that helper reads receivers_.CurrentTargetFrame() and its GetFrameTreeNodeId() (:470-471) and looks the id up in supervised_user_interstitials_ (:473-476) with no check that the calling frame is still active. The interface’s own contract (header comment at .h:126-128) says these commands “should not be called when an interstitial is no longer showing” and that this “should be enforced by the mojo caller,” an invariant the handlers never verified themselves. Because the receiver set still delivers messages from a BFCached frame and the subframe’s interstitial entry is not cleared when the page enters BFCache (per the added browsertest), an inactive frame could still drive the URL-access commands (RequestUrlAccessRemote, RequestUrlAccessLocal) against the stored interstitial; GoBack was already limited to the primary main frame (:482), which a BFCached frame is not, and the commit message notes GoBack/LearnMore “don’t have any external interactions”.
The fix adds if (!target_frame.IsActive()) { return nullptr; } at the top of GetInterstitialForFrame(), so every handler now treats an inactive frame as having no interstitial — RequestUrlAccessRemote/RequestUrlAccessLocal run their callback with request_issued=false and the others return early.
The patch TIGHTENS authorization by adding the missing liveness check.
RequestUrlAccessRemote/RequestUrlAccessLocal) authorized callers solely by a FrameTreeNodeId map lookup and never checked frame liveness (only GoBack added an IsInPrimaryMainFrame() gate); adding an IsActive() gate in GetInterstitialForFrame() rejects requests from inactive/BFCached frames.Attack Path
- Embed a blocked subframe
A supervised user loads a page containing a subframe whose URL is blocked, creating a stored
SupervisedUserInterstitialentry for that subframe. - Push the page into BFCache The main frame navigates away, so the page and its blocked subframe become inactive but are retained in BFCache with the interstitial entry still in the map.
- Drive a command from the inactive frame
A compromised renderer sends e.g.
RequestUrlAccessRemoteover the still-bound pipe from the BFCached frame. (frame dispatch per test; renderer compromise inferred) - Request processed unauthorized
Pre-fix,
GetInterstitialForFramereturns the leaked interstitial and the URL-access command is issued on behalf of a page that is no longer active.
Impact Assessment
RequestUrlAccessRemote/RequestUrlAccessLocal) on behalf of an interstitial that is no longer showing; GoBack was already blocked for such frames by its IsInPrimaryMainFrame() check (:482), and the commit message says GoBack/LearnMore have no external interactions. Preconditions are a supervised profile and a compromised renderer (inferred). The advisory rates this medium severity, classifies it as missing authorization, and states it was not exploited in the wild.Changed Functions
| Function | Change | Notes |
|---|---|---|
GetInterstitialForFramechrome/browser/supervised_user/supervised_user_navigation_observer.cc |
modified | |
receivers_for_testchrome/browser/supervised_user/supervised_user_navigation_observer.h |
modified | |
SupervisedUserBFCacheIframeTestchrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc |
modified | |
SupervisedUserBFCacheIframeTestchrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc |
modified |
Files Changed
chrome/browser/supervised_user/supervised_user_navigation_observer.ccchrome/browser/supervised_user/supervised_user_navigation_observer.hchrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc
Audit Directions
- Frame-id lookup without livenessFlag browser-process mojo handlers that resolve per-frame state by
FrameTreeNodeIdwithout first checkingRenderFrameHost::IsActive(). - BFCache message deliveryVerify interface handlers either reject or safely tolerate messages that arrive from BFCached or otherwise inactive frames.
Patch
diff --git a/chrome/browser/supervised_user/supervised_user_navigation_observer.cc b/chrome/browser/supervised_user/supervised_user_navigation_observer.cc
index c60e00f..11ec8327 100644
--- a/chrome/browser/supervised_user/supervised_user_navigation_observer.cc
+++ b/chrome/browser/supervised_user/supervised_user_navigation_observer.cc
@@ -468,6 +468,11 @@
supervised_user::SupervisedUserInterstitial*
SupervisedUserNavigationObserver::GetInterstitialForFrame() {
content::RenderFrameHost& target_frame = receivers_.CurrentTargetFrame();
+
+ if (!target_frame.IsActive()) {
+ return nullptr;
+ }
+
content::FrameTreeNodeId frame_id = target_frame.GetFrameTreeNodeId();
if (auto it = supervised_user_interstitials_.find(frame_id);
diff --git a/chrome/browser/supervised_user/supervised_user_navigation_observer.h b/chrome/browser/supervised_user/supervised_user_navigation_observer.h
index 4e87d0b..d15316eb 100644
--- a/chrome/browser/supervised_user/supervised_user_navigation_observer.h
+++ b/chrome/browser/supervised_user/supervised_user_navigation_observer.h
@@ -94,6 +94,12 @@
return requested_hosts_;
}
+ content::RenderFrameHostReceiverSet<
+ supervised_user::mojom::SupervisedUserCommands>&
+ receivers_for_test() {
+ return receivers_;
+ }
+
private:
friend class content::WebContentsUserData<SupervisedUserNavigationObserver>;
diff --git a/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc b/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc
index 4c3b37d..b21bfdf 100644
--- a/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc
+++ b/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc
@@ -43,11 +43,13 @@
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/page_type.h"
+#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/page_transition_types.h"
@@ -1363,4 +1365,179 @@
EXPECT_TRUE(rfh_host2);
}
+struct SupervisedUserBFCacheIframeTestParam {
+ using RequestUrlAccessMethod =
+ void (supervised_user::mojom::SupervisedUserCommands::*)(
+ base::OnceCallback<void(bool)>);
+
+ std::string test_name;
+ // References either local or remote url access method, which have different
+ // function behavior but identical signatures and semantic meaning.
+ RequestUrlAccessMethod request_url_access_method;
+};
+
+// Security regression test: verifies that a blocked subframe that has been
+// placed into the Back/Forward Cache cannot drive RequestUrlAccessRemote /
+// RequestUrlAccessLocal on behalf of an unrelated, currently-active page, but
+// once restored from the Back/Forward Cache, the active frame can successfully
+// drive the request.
+class SupervisedUserBFCacheIframeTest
+ : public SupervisedUserIframeFilterTest,
+ public testing::WithParamInterface<SupervisedUserBFCacheIframeTestParam> {
+ protected:
+ SupervisedUserBFCacheIframeTest() {
+ bfcache_feature_list_.InitWithFeaturesAndParameters(
+ content::GetDefaultEnabledBackForwardCacheFeaturesForTesting(),
+ content::GetDefaultDisabledBackForwardCacheFeaturesForTesting());
+ }
+
+ private:
+ base::test::ScopedFeatureList bfcache_feature_list_;
+};
+
+// Tests if BFCached subframes are blocked from issuing approval requests unless
+// they are successfully restored.
+IN_PROC_BROWSER_TEST_P(SupervisedUserBFCacheIframeTest,
+ BFCachedSubframeCantIssueApprovalRequest) {
+ // Step 1: block one iframe host, load an allowed top-level page that embeds
+ // it. The browser stores a SupervisedUserInterstitial for the subframe.
+ BlockHost(kIframeHost2);
+ GURL allowed_url_with_iframes = embedded_test_server()->GetURL(
+ kExampleHost, "/supervised_user/with_iframes.html");
+
+ kids_management_api_mock().AllowSubsequentClassifyUrl();
+ ASSERT_TRUE(
+ ui_test_utils::NavigateToURL(browser(), allowed_url_with_iframes));
+ ASSERT_FALSE(IsInterstitialBeingShownInMainFrame(browser()));
+
+ std::vector<content::FrameTreeNodeId> blocked = GetBlockedFrames();
+ ASSERT_EQ(blocked.size(), 1u);
+ content::FrameTreeNodeId blocked_frame_id = blocked[0];
+ content::RenderFrameHost* blocked_rfh = tracker()->GetHost(blocked_frame_id);
+ ASSERT_TRUE(blocked_rfh);
+ ASSERT_TRUE(blocked_rfh->IsActive());
+ content::RenderFrameHostWrapper blocked_rfh_wrapper(blocked_rfh);
+
+ // Step 2: navigate the main frame cross-origin so the original page (and
+ // its blocked subframe) becomes eligible for BFCache.
+ GURL second_page = embedded_test_server()->GetURL(
+ kExampleHost2, "/supervised_user/simple.html");
+ kids_management_api_mock().AllowSubsequentClassifyUrl();
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), second_page));
+ ASSERT_FALSE(IsInterstitialBeingShownInMainFrame(browser()));
+
+ // Step 3: confirm the page actually entered BFCache and the subframe RFH
+ // was *not* deleted. This validates that FrameDeleted() never fired for it.
+ ASSERT_FALSE(blocked_rfh_wrapper.IsDestroyed())
+ << "Subframe RFH was destroyed; page did not enter BFCache.";
+ ASSERT_EQ(blocked_rfh_wrapper->GetLifecycleState(),
+ content::RenderFrameHost::LifecycleState::kInBackForwardCache);
+ ASSERT_FALSE(blocked_rfh_wrapper->IsActive());
+
+ // Step 4: the interstitial entry for the BFCached subframe is still
+ // present. DidFinishNavigation() only clears the entry whose
+ // FrameTreeNodeId matches the navigating frame (the *main* frame here), so
+ // the subframe entry leaks.
+ ASSERT_TRUE(
+ supervised_user_navigation_observer()->interstitials_for_test().contains(
+ blocked_frame_id))
+ << "Interstitial map was cleared on BFCache entry; bug not reachable.";
+
+ // Step 5: simulate a compromised renderer sending RequestUrlAccessRemote()
+ // from the inactive (BFCached) frame. RenderFrameHostReceiverSet does not
+ // drop messages from BFCached RFHs, so a real renderer-controlled pipe
+ // could do exactly this. We use SetCurrentTargetFrameForTesting() to model
+ // that dispatch context without needing to actually run JS in a frozen
+ // renderer.
+ permission_creator()->SetPermissionResult(true);
+ supervised_user_navigation_observer()
+ ->receivers_for_test()
+ .SetCurrentTargetFrameForTesting(blocked_rfh_wrapper.get());
+
+ base::RunLoop run_loop;
+ bool request_issued = false;
+
+ supervised_user::mojom::SupervisedUserCommands* commands =
+ static_cast<supervised_user::mojom::SupervisedUserCommands*>(
+ supervised_user_navigation_observer());
+
+ (commands->*(GetParam().request_url_access_method))(base::BindOnce(
+ [](base::OnceClosure quit, bool* out, bool issued) {
+ *out = issued;
+ std::move(quit).Run();
+ },
+ run_loop.QuitClosure(), &request_issued));
+ run_loop.Run();
+
+ supervised_user_navigation_observer()
+ ->receivers_for_test()
+ .SetCurrentTargetFrameForTesting(nullptr);
+
+ // Request should have been rejected.
+ EXPECT_FALSE(request_issued)
+ << "Handler should have rejected the inactive-frame call.";
+ EXPECT_EQ(permission_creator()->url_requests().size(), 0u);
+ if (!permission_creator()->url_requests().empty()) {
+ EXPECT_EQ(permission_creator()->url_requests()[0].GetHost(), kIframeHost2);
+ }
+
+ // Step 6: Navigate back to the original page from BFCache.
+ ASSERT_TRUE(content::HistoryGoBack(web_contents()));
+
+ // The page and subframe are restored to active state.
+ ASSERT_FALSE(blocked_rfh_wrapper.IsDestroyed());
+ ASSERT_EQ(blocked_rfh_wrapper->GetLifecycleState(),
+ content::RenderFrameHost::LifecycleState::kActive);
+ ASSERT_TRUE(blocked_rfh_wrapper->IsActive());
+
+ // Step 7: Now that the frame is restored and active, the user can
+ // successfully re-trigger the URL approval request.
+ supervised_user_navigation_observer()
+ ->receivers_for_test()
+ .SetCurrentTargetFrameForTesting(blocked_rfh_wrapper.get());
+
+ base::RunLoop run_loop_after_restore;
+ bool request_issued_after_restore = false;
+
+ (commands->*(GetParam().request_url_access_method))(base::BindOnce(
+ [](base::OnceClosure quit, bool* out, bool issued) {
+ *out = issued;
+ std::move(quit).Run();
+ },
+ run_loop_after_restore.QuitClosure(), &request_issued_after_restore));
+ run_loop_after_restore.Run();
+
+ supervised_user_navigation_observer()
+ ->receivers_for_test()
+ .SetCurrentTargetFrameForTesting(nullptr);
+
+ EXPECT_TRUE(request_issued_after_restore)
+ << "Handler should have processed the request from the restored active "
+ "frame.";
+ if (GetParam().test_name == "RequestUrlAccessRemote") {
+ EXPECT_EQ(permission_creator()->url_requests().size(), 1u);
+ if (!permission_creator()->url_requests().empty()) {
+ EXPECT_EQ(permission_creator()->url_requests()[0].GetHost(),
+ kIframeHost2);
+ }
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ ,
+ SupervisedUserBFCacheIframeTest,
+ testing::Values(
+ SupervisedUserBFCacheIframeTestParam{
+ .test_name = "RequestUrlAccessRemote",
+ .request_url_access_method =
+ &supervised_user::mojom::SupervisedUserCommands::
+ RequestUrlAccessRemote},
+ SupervisedUserBFCacheIframeTestParam{
+ .test_name = "RequestUrlAccessLocal",
+ .request_url_access_method =
+ &supervised_user::mojom::SupervisedUserCommands::
+ RequestUrlAccessLocal}),
+ [](const testing::TestParamInfo<SupervisedUserBFCacheIframeTestParam>&
+ info) { return info.param.test_name; });
+
} // namespace
Regression Test / PoC
diff --git a/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc b/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc
index 4c3b37d..b21bfdf 100644
--- a/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc
+++ b/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc
@@ -43,11 +43,13 @@
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/page_type.h"
+#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/page_transition_types.h"
@@ -1363,4 +1365,179 @@
EXPECT_TRUE(rfh_host2);
}
+struct SupervisedUserBFCacheIframeTestParam {
+ using RequestUrlAccessMethod =
+ void (supervised_user::mojom::SupervisedUserCommands::*)(
+ base::OnceCallback<void(bool)>);
+
+ std::string test_name;
+ // References either local or remote url access method, which have different
+ // function behavior but identical signatures and semantic meaning.
+ RequestUrlAccessMethod request_url_access_method;
+};
+
+// Security regression test: verifies that a blocked subframe that has been
+// placed into the Back/Forward Cache cannot drive RequestUrlAccessRemote /
+// RequestUrlAccessLocal on behalf of an unrelated, currently-active page, but
+// once restored from the Back/Forward Cache, the active frame can successfully
+// drive the request.
+class SupervisedUserBFCacheIframeTest
+ : public SupervisedUserIframeFilterTest,
+ public testing::WithParamInterface<SupervisedUserBFCacheIframeTestParam> {
+ protected:
+ SupervisedUserBFCacheIframeTest() {
+ bfcache_feature_list_.InitWithFeaturesAndParameters(
+ content::GetDefaultEnabledBackForwardCacheFeaturesForTesting(),
+ content::GetDefaultDisabledBackForwardCacheFeaturesForTesting());
+ }
+
+ private:
+ base::test::ScopedFeatureList bfcache_feature_list_;
+};
+
+// Tests if BFCached subframes are blocked from issuing approval requests unless
+// they are successfully restored.
+IN_PROC_BROWSER_TEST_P(SupervisedUserBFCacheIframeTest,
+ BFCachedSubframeCantIssueApprovalRequest) {
+ // Step 1: block one iframe host, load an allowed top-level page that embeds
+ // it. The browser stores a SupervisedUserInterstitial for the subframe.
+ BlockHost(kIframeHost2);
+ GURL allowed_url_with_iframes = embedded_test_server()->GetURL(
+ kExampleHost, "/supervised_user/with_iframes.html");
+
+ kids_management_api_mock().AllowSubsequentClassifyUrl();
+ ASSERT_TRUE(
+ ui_test_utils::NavigateToURL(browser(), allowed_url_with_iframes));
+ ASSERT_FALSE(IsInterstitialBeingShownInMainFrame(browser()));
+
+ std::vector<content::FrameTreeNodeId> blocked = GetBlockedFrames();
+ ASSERT_EQ(blocked.size(), 1u);
+ content::FrameTreeNodeId blocked_frame_id = blocked[0];
+ content::RenderFrameHost* blocked_rfh = tracker()->GetHost(blocked_frame_id);
+ ASSERT_TRUE(blocked_rfh);
+ ASSERT_TRUE(blocked_rfh->IsActive());
+ content::RenderFrameHostWrapper blocked_rfh_wrapper(blocked_rfh);
+
+ // Step 2: navigate the main frame cross-origin so the original page (and
+ // its blocked subframe) becomes eligible for BFCache.
+ GURL second_page = embedded_test_server()->GetURL(
+ kExampleHost2, "/supervised_user/simple.html");
+ kids_management_api_mock().AllowSubsequentClassifyUrl();
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), second_page));
+ ASSERT_FALSE(IsInterstitialBeingShownInMainFrame(browser()));
+
+ // Step 3: confirm the page actually entered BFCache and the subframe RFH
+ // was *not* deleted. This validates that FrameDeleted() never fired for it.
+ ASSERT_FALSE(blocked_rfh_wrapper.IsDestroyed())
+ << "Subframe RFH was destroyed; page did not enter BFCache.";
+ ASSERT_EQ(blocked_rfh_wrapper->GetLifecycleState(),
+ content::RenderFrameHost::LifecycleState::kInBackForwardCache);
+ ASSERT_FALSE(blocked_rfh_wrapper->IsActive());
+
+ // Step 4: the interstitial entry for the BFCached subframe is still
+ // present. DidFinishNavigation() only clears the entry whose
+ // FrameTreeNodeId matches the navigating frame (the *main* frame here), so
+ // the subframe entry leaks.
+ ASSERT_TRUE(
+ supervised_user_navigation_observer()->interstitials_for_test().contains(
+ blocked_frame_id))
+ << "Interstitial map was cleared on BFCache entry; bug not reachable.";
+
+ // Step 5: simulate a compromised renderer sending RequestUrlAccessRemote()
+ // from the inactive (BFCached) frame. RenderFrameHostReceiverSet does not
+ // drop messages from BFCached RFHs, so a real renderer-controlled pipe
+ // could do exactly this. We use SetCurrentTargetFrameForTesting() to model
+ // that dispatch context without needing to actually run JS in a frozen
+ // renderer.
+ permission_creator()->SetPermissionResult(true);
+ supervised_user_navigation_observer()
+ ->receivers_for_test()
+ .SetCurrentTargetFrameForTesting(blocked_rfh_wrapper.get());
+
+ base::RunLoop run_loop;
+ bool request_issued = false;
+
+ supervised_user::mojom::SupervisedUserCommands* commands =
+ static_cast<supervised_user::mojom::SupervisedUserCommands*>(
+ supervised_user_navigation_observer());
+
+ (commands->*(GetParam().request_url_access_method))(base::BindOnce(
+ [](base::OnceClosure quit, bool* out, bool issued) {
+ *out = issued;
+ std::move(quit).Run();
+ },
+ run_loop.QuitClosure(), &request_issued));
+ run_loop.Run();
+
+ supervised_user_navigation_observer()
+ ->receivers_for_test()
+ .SetCurrentTargetFrameForTesting(nullptr);
+
+ // Request should have been rejected.
+ EXPECT_FALSE(request_issued)
+ << "Handler should have rejected the inactive-frame call.";
+ EXPECT_EQ(permission_creator()->url_requests().size(), 0u);
+ if (!permission_creator()->url_requests().empty()) {
+ EXPECT_EQ(permission_creator()->url_requests()[0].GetHost(), kIframeHost2);
+ }
+
+ // Step 6: Navigate back to the original page from BFCache.
+ ASSERT_TRUE(content::HistoryGoBack(web_contents()));
+
+ // The page and subframe are restored to active state.
+ ASSERT_FALSE(blocked_rfh_wrapper.IsDestroyed());
+ ASSERT_EQ(blocked_rfh_wrapper->GetLifecycleState(),
+ content::RenderFrameHost::LifecycleState::kActive);
+ ASSERT_TRUE(blocked_rfh_wrapper->IsActive());
+
+ // Step 7: Now that the frame is restored and active, the user can
+ // successfully re-trigger the URL approval request.
+ supervised_user_navigation_observer()
+ ->receivers_for_test()
+ .SetCurrentTargetFrameForTesting(blocked_rfh_wrapper.get());
+
+ base::RunLoop run_loop_after_restore;
+ bool request_issued_after_restore = false;
+
+ (commands->*(GetParam().request_url_access_method))(base::BindOnce(
+ [](base::OnceClosure quit, bool* out, bool issued) {
+ *out = issued;
+ std::move(quit).Run();
+ },
+ run_loop_after_restore.QuitClosure(), &request_issued_after_restore));
+ run_loop_after_restore.Run();
+
+ supervised_user_navigation_observer()
+ ->receivers_for_test()
+ .SetCurrentTargetFrameForTesting(nullptr);
+
+ EXPECT_TRUE(request_issued_after_restore)
+ << "Handler should have processed the request from the restored active "
+ "frame.";
+ if (GetParam().test_name == "RequestUrlAccessRemote") {
+ EXPECT_EQ(permission_creator()->url_requests().size(), 1u);
+ if (!permission_creator()->url_requests().empty()) {
+ EXPECT_EQ(permission_creator()->url_requests()[0].GetHost(),
+ kIframeHost2);
+ }
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ ,
+ SupervisedUserBFCacheIframeTest,
+ testing::Values(
+ SupervisedUserBFCacheIframeTestParam{
+ .test_name = "RequestUrlAccessRemote",
+ .request_url_access_method =
+ &supervised_user::mojom::SupervisedUserCommands::
+ RequestUrlAccessRemote},
+ SupervisedUserBFCacheIframeTestParam{
+ .test_name = "RequestUrlAccessLocal",
+ .request_url_access_method =
+ &supervised_user::mojom::SupervisedUserCommands::
+ RequestUrlAccessLocal}),
+ [](const testing::TestParamInfo<SupervisedUserBFCacheIframeTestParam>&
+ info) { return info.param.test_name; });
+
} // namespace